Wave one complete. Three cards, no new data and no migrations. Printer supplies reuses the existing low-supplies query and its five-minute cache; a Zabbix round-trip per printer on every dashboard load would make this the slowest page in the app. One row per printer listing every depleted cartridge, criticals first - a row per cartridge would report one printer three times and read as three problems, and showing only the worst class would hide a low cartridge behind a critical one on the same machine when whoever walks out there wants to carry both. While there: the low-supplies REPORT itself was including healthy cartridges. A printer with one empty black and three full colour ones listed all four, so the reader had to find the problem inside the row. It now lists only what needs replacing, and the test that asserted the old behaviour now asserts the new. Expiring warranties keeps already-expired entries on the list rather than dropping them the day they lapse, which is how they get missed. Horizon is warranty_expiringdays, default 90, because that suits a site budgeting quarterly and nobody else. Mis-numbered bays promotes check-shared-machines out of a CLI command nobody will remember to run - it found seven bays that had been wrong for weeks. It reports only numbers with NO child assets, so part markers legitimately sharing an operation stay silent: that distinction is the whole card, and without it it would list correct data beside faults and be ignored. Printers also loses its dead component-named widget; notifications, network and machines still have theirs.
171 lines
6.1 KiB
Python
171 lines
6.1 KiB
Python
"""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_dashboard_widgets(self) -> List[Dict]:
|
|
"""Dashboard card: warranties running out, and ones already expired.
|
|
|
|
Expired stays listed rather than dropping off. A machine out of
|
|
warranty is a purchasing decision someone still has to make, and
|
|
removing it the day it lapses is how it gets missed entirely.
|
|
"""
|
|
return [
|
|
{
|
|
'id': 'warranty-expiring',
|
|
'title': 'Warranties expiring',
|
|
'endpoint': '/api/warranty/dashboard/expiring',
|
|
'render': 'exceptions',
|
|
'severity': 'info',
|
|
'permission': 'warranty.view',
|
|
'empty': 'hide',
|
|
'position': 50,
|
|
'map': {
|
|
'title': 'assetnumber',
|
|
'detail': 'state',
|
|
'meta': [
|
|
{'key': 'enddate', 'label': 'ends', 'format': 'date'},
|
|
{'key': 'provider'},
|
|
],
|
|
'link': '/assets/{assetid}',
|
|
},
|
|
},
|
|
]
|
|
|
|
def get_settings_defaults(self) -> List[Dict]:
|
|
"""Settings this plugin owns."""
|
|
return [
|
|
{
|
|
'key': 'warranty_expiringdays',
|
|
'value': '90',
|
|
'valuetype': 'integer',
|
|
'category': 'warranty',
|
|
'description': 'Days ahead to list an expiring warranty on the '
|
|
'dashboard. Already-expired warranties are always '
|
|
'listed.',
|
|
},
|
|
]
|
|
|
|
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})")
|