Tech Tools section with a code generator, toner report that names the part, toner forecasting, warranty proof of cover, application uploads
This commit is contained in:
@@ -1482,3 +1482,102 @@ def dashboard_supplies():
|
||||
|
||||
rows.sort(key=lambda r: (not r['iscritical'], r['printername']))
|
||||
return success_response(rows)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Supply forecast
|
||||
#
|
||||
# The toner report answers "what is empty now". This answers "what will be, and
|
||||
# what have we been getting through" - a purchasing question, on a different
|
||||
# cadence, off data Zabbix has been keeping all along.
|
||||
# =============================================================================
|
||||
|
||||
@printers_asset_bp.route('/supplies/forecast', methods=['GET'])
|
||||
@jwt_required(optional=True)
|
||||
def supplies_forecast():
|
||||
"""Days-to-empty and replacement counts per printer.
|
||||
|
||||
?days=90 how far back to read (Zabbix retention is the real ceiling)
|
||||
|
||||
Printers sort by their soonest supply. Anything without an honest estimate
|
||||
is returned separately with the reason, rather than sorted as though it
|
||||
were fine or dropped as though it did not exist.
|
||||
"""
|
||||
from ..services.supply_history import analyse, soonest
|
||||
|
||||
try:
|
||||
days = max(1, min(365, int(request.args.get('days', 90))))
|
||||
except (TypeError, ValueError):
|
||||
days = 90
|
||||
|
||||
service = ZabbixService()
|
||||
if not service.isconfigured or not service.isreachable:
|
||||
return success_response({
|
||||
'printers': [], 'unestimated': [], 'days': days,
|
||||
'available': False,
|
||||
'reason': 'Zabbix is not configured or not reachable',
|
||||
})
|
||||
|
||||
rows = (
|
||||
db.session.query(Printer, Asset, Communication, Vendor)
|
||||
.join(Asset, Asset.assetid == Printer.assetid)
|
||||
.join(Communication, Communication.assetid == Asset.assetid)
|
||||
.outerjoin(Vendor, Vendor.vendorid == Printer.vendorid)
|
||||
.filter(Asset.isactive == True,
|
||||
Communication.ipaddress.isnot(None),
|
||||
Communication.ipaddress != '')
|
||||
.all()
|
||||
)
|
||||
|
||||
seen = set()
|
||||
estimated, unestimated = [], []
|
||||
|
||||
for printer, asset, comm, vendor in rows:
|
||||
if printer.printerid in seen:
|
||||
continue
|
||||
seen.add(printer.printerid)
|
||||
|
||||
supplies = service.getsuppliesbyip_cached(comm.ipaddress)
|
||||
if not supplies:
|
||||
continue
|
||||
itemids = [s['itemid'] for s in supplies if s.get('itemid')]
|
||||
history = service.gethistory(itemids, days=days)
|
||||
|
||||
analysed = []
|
||||
for supply in supplies:
|
||||
points = history.get(str(supply.get('itemid')), [])
|
||||
detail = analyse(points)
|
||||
detail['name'] = supply.get('name')
|
||||
detail['color'] = supply.get('color')
|
||||
# Trust the live read for the level; history can lag a poll behind.
|
||||
detail['currentlevel'] = supply.get('level', detail['currentlevel'])
|
||||
analysed.append(detail)
|
||||
|
||||
entry = {
|
||||
'printerid': printer.printerid,
|
||||
'printername': asset.name or printer.hostname or '',
|
||||
'assetnumber': asset.assetnumber or '',
|
||||
'ipaddress': comm.ipaddress,
|
||||
'vendor': vendor.vendor if vendor else None,
|
||||
'supplies': analysed,
|
||||
'daysleft': soonest(analysed),
|
||||
'replacements': sum(s['replacements'] for s in analysed),
|
||||
}
|
||||
(estimated if entry['daysleft'] is not None else unestimated).append(entry)
|
||||
|
||||
# Soonest first: the point of the report is what to order next.
|
||||
estimated.sort(key=lambda p: p['daysleft'])
|
||||
unestimated.sort(key=lambda p: p['printername'])
|
||||
|
||||
return success_response({
|
||||
'printers': estimated,
|
||||
'unestimated': unestimated,
|
||||
'days': days,
|
||||
'available': True,
|
||||
'summary': {
|
||||
'estimated': len(estimated),
|
||||
'unestimated': len(unestimated),
|
||||
'replacements': sum(p['replacements'] for p in estimated + unestimated),
|
||||
'duewithin30': sum(1 for p in estimated if p['daysleft'] <= 30),
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user