Dashboard cards: rebuild deployable dists

This commit is contained in:
2026-08-11 14:31:24 -04:00
parent 0ed8be75d9
commit 3b37603691
14 changed files with 507 additions and 62 deletions

View File

@@ -5,7 +5,7 @@ never stored. Warranties link to assets many-to-many via warrantyassets, though
the common case is one warranty per asset.
"""
from datetime import date, datetime, timezone
from datetime import date, datetime, timedelta, timezone
from flask import Blueprint, request
from flask_jwt_extended import jwt_required
@@ -391,3 +391,54 @@ def warranty_report():
'counts': {k: len(v) for k, v in buckets.items()},
'buckets': buckets,
})
@warranty_bp.route('/dashboard/expiring', methods=['GET'])
@jwt_required()
@require_permission('warranty.view')
def dashboard_expiring():
"""Warranties running out, and ones that already have.
Already-expired first, then soonest. Expired stays on the list rather than
dropping off: a machine out of warranty is a purchasing decision someone
still has to make, and silently removing it the day it lapses is how it gets
missed entirely.
Horizon is a setting because 90 days suits a site that budgets quarterly and
nobody else (ADR-015).
"""
from shopdb.api import Setting
days = 90
setting = Setting.query.filter_by(key='warranty_expiringdays').first()
if setting and (setting.value or '').strip():
try:
days = int(setting.value)
except (TypeError, ValueError):
pass
today = date.today()
horizon = today + timedelta(days=days)
rows = []
query = (db.session.query(Warranty, WarrantyAsset, Asset)
.join(WarrantyAsset, WarrantyAsset.warrantyid == Warranty.warrantyid)
.join(Asset, Asset.assetid == WarrantyAsset.assetid)
.filter(Warranty.isactive.is_(True),
Warranty.enddate.isnot(None),
Warranty.enddate <= horizon,
Asset.isactive.is_(True)))
for warranty, _link, asset in query.all():
remaining = (warranty.enddate - today).days
rows.append({
'assetid': asset.assetid,
'assetnumber': asset.assetnumber or asset.name or str(asset.assetid),
'enddate': warranty.enddate.isoformat(),
'provider': warranty.provider,
'daysleft': remaining,
'state': 'expired' if remaining < 0 else 'expiring',
})
rows.sort(key=lambda r: r['daysleft'])
return success_response(rows[:50])

View File

@@ -114,6 +114,49 @@ class WarrantyPlugin(BasePlugin):
},
]
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 [