"""Warranty plugin main class. Asset-general plugin: owns its warranties + warrantyassets tables, an API surface, and a sidebar entry. Not tied to any one asset type - a warranty can cover a PC, printer, network device, or machine. """ import json import logging from pathlib import Path from typing import List, Dict, Optional, Type from flask import Flask, Blueprint from shopdb.plugins.base import BasePlugin, PluginMeta from .api import warranty_bp from .models import Warranty, WarrantyAsset logger = logging.getLogger(__name__) class WarrantyPlugin(BasePlugin): """Warranty tracking plugin.""" def __init__(self): self._manifest = self._load_manifest() def _load_manifest(self) -> Dict: manifest_path = Path(__file__).parent / 'manifest.json' if manifest_path.exists(): with open(manifest_path, 'r') as f: return json.load(f) return {} @property def meta(self) -> PluginMeta: return PluginMeta( name=self._manifest.get('name', 'warranty'), version=self._manifest.get('version', '1.0.0'), description=self._manifest.get('description', 'Asset warranty tracking'), author=self._manifest.get('author', 'ShopDB Team'), dependencies=self._manifest.get('dependencies', []), core_version=self._manifest.get('core_version', '>=0.2.0,<1.0.0'), api_prefix=self._manifest.get('api_prefix', '/api/warranty'), ) def get_blueprint(self) -> Optional[Blueprint]: return warranty_bp def get_models(self) -> List[Type]: return [Warranty, WarrantyAsset] def get_navigation_items(self) -> List[Dict]: return [ { 'name': 'Warranties', 'icon': 'shield', 'route': '/warranties', 'position': 8, }, ] def get_reports(self) -> List[Dict]: return [ { 'id': 'warranty', 'name': 'Warranty Report', 'description': 'Assets bucketed by coverage: expired, expiring soon, active', 'category': 'warranty', 'route': '/reports/warranty', }, ] def get_asset_panels(self) -> List[Dict]: # ADR-010 pilot. Warranty is asset-general so it shows on every detail # page (['*']). Declares the existing per-asset warranty endpoint the # WarrantyPanel already reads; the generic core panel renderer consumes # this instead of each detail view hand-composing the panel. return [ { 'id': 'warranty', 'title': 'Warranty', 'assettypes': ['*'], 'endpoint': '/api/warranty/asset/{assetid}', 'render': 'list', # Field map: the generic renderer builds each list item from the # warranty payload without any warranty-specific frontend code. 'map': { 'title': 'vendor', 'badge': { 'label': 'status', 'color': 'statuscolor', 'labelmap': { 'active': 'Active', 'expiring': 'Expiring Soon', 'expired': 'Expired', 'unknown': 'Unknown', }, }, 'meta': [ {'key': 'servicelevel'}, {'key': 'enddate', 'label': 'Ends', 'format': 'date'}, {'key': 'servicetag', 'label': 'Tag', 'mono': True}, ], }, 'empty': 'No warranty on record.', 'manage': { 'to': '/warranties?addfor={assetid}', 'label': 'Add / manage', 'emptylabel': 'Add one', }, 'position': 30, }, ] def get_permissions(self) -> List: """Return the RBAC permissions this plugin owns.""" return [ ('warranty.view', 'View warranties', 'warranty'), ('warranty.create', 'Create warranties', 'warranty'), ('warranty.edit', 'Edit warranties', 'warranty'), ('warranty.delete', 'Delete warranties', 'warranty'), ] def init_app(self, app: Flask, db_instance) -> None: logger.info(f"Warranty plugin initialized (v{self.meta.version})")