Remediate the 2026-09-03 feature-test findings
This commit is contained in:
@@ -7,7 +7,8 @@ from shopdb.api import db, Asset, AssetType, AuditLog, Vendor, success_response,
|
||||
|
||||
from ..models import Machine, MachineType
|
||||
|
||||
from shopdb.api import require_permission, apply_import_timestamps
|
||||
from shopdb.api import (require_permission, apply_import_timestamps, intarg,
|
||||
DateValueError, parse_date_fields)
|
||||
|
||||
machines_bp = Blueprint('machines', __name__)
|
||||
|
||||
@@ -197,20 +198,20 @@ def list_machines():
|
||||
)
|
||||
|
||||
# Machine type filter
|
||||
if type_id := request.args.get('typeid', request.args.get('type_id')):
|
||||
query = query.filter(Machine.machinetypeid == int(type_id))
|
||||
if (type_id := intarg('typeid', intarg('type_id'))) is not None:
|
||||
query = query.filter(Machine.machinetypeid == type_id)
|
||||
|
||||
# Vendor filter
|
||||
if vendor_id := request.args.get('vendorid', request.args.get('vendor_id')):
|
||||
query = query.filter(Machine.vendorid == int(vendor_id))
|
||||
if (vendor_id := intarg('vendorid', intarg('vendor_id'))) is not None:
|
||||
query = query.filter(Machine.vendorid == vendor_id)
|
||||
|
||||
# Location filter
|
||||
if location_id := request.args.get('locationid', request.args.get('location_id')):
|
||||
query = query.filter(Asset.locationid == int(location_id))
|
||||
if (location_id := intarg('locationid', intarg('location_id'))) is not None:
|
||||
query = query.filter(Asset.locationid == location_id)
|
||||
|
||||
# Business unit filter
|
||||
if bu_id := request.args.get('businessunitid', request.args.get('businessunit_id')):
|
||||
query = query.filter(Asset.businessunitid == int(bu_id))
|
||||
if (bu_id := intarg('businessunitid', intarg('businessunit_id'))) is not None:
|
||||
query = query.filter(Asset.businessunitid == bu_id)
|
||||
|
||||
# Sorting
|
||||
sort_by = request.args.get('sort', 'assetnumber')
|
||||
@@ -336,6 +337,14 @@ def create_machine():
|
||||
- mapx, mapy, notes
|
||||
"""
|
||||
data = request.get_json()
|
||||
# Refused rather than stored raw or nulled: these are db.Date columns
|
||||
# (migration 7d40), so a string would be rejected by the engine and a
|
||||
# mistyped one used to become NULL further down the stack.
|
||||
try:
|
||||
maintenancedates = parse_date_fields(
|
||||
data, ('lastmaintenancedate', 'nextmaintenancedate'))
|
||||
except DateValueError as bad:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, str(bad))
|
||||
|
||||
if not data:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'No data provided')
|
||||
@@ -388,8 +397,8 @@ def create_machine():
|
||||
modelnumberid=data.get('modelnumberid'),
|
||||
requiresmanualconfig=data.get('requiresmanualconfig', False),
|
||||
islocationonly=data.get('islocationonly', False),
|
||||
lastmaintenancedate=data.get('lastmaintenancedate'),
|
||||
nextmaintenancedate=data.get('nextmaintenancedate'),
|
||||
lastmaintenancedate=maintenancedates.get('lastmaintenancedate'),
|
||||
nextmaintenancedate=maintenancedates.get('nextmaintenancedate'),
|
||||
maintenanceintervaldays=data.get('maintenanceintervaldays'),
|
||||
controllervendorid=data.get('controllervendorid'),
|
||||
controllermodelid=data.get('controllermodelid')
|
||||
@@ -427,6 +436,14 @@ def update_machine(machine_id: int):
|
||||
)
|
||||
|
||||
data = request.get_json()
|
||||
# Refused rather than stored raw or nulled: these are db.Date columns
|
||||
# (migration 7d40), so a string would be rejected by the engine and a
|
||||
# mistyped one used to become NULL further down the stack.
|
||||
try:
|
||||
maintenancedates = parse_date_fields(
|
||||
data, ('lastmaintenancedate', 'nextmaintenancedate'))
|
||||
except DateValueError as bad:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, str(bad))
|
||||
if not data:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'No data provided')
|
||||
|
||||
@@ -471,7 +488,7 @@ def update_machine(machine_id: int):
|
||||
# Update machine fields
|
||||
machine_fields = ['machinetypeid', 'vendorid', 'modelnumberid',
|
||||
'requiresmanualconfig', 'islocationonly',
|
||||
'lastmaintenancedate', 'nextmaintenancedate', 'maintenanceintervaldays',
|
||||
'maintenanceintervaldays',
|
||||
'controllervendorid', 'controllermodelid']
|
||||
for key in machine_fields:
|
||||
if key in data:
|
||||
@@ -481,6 +498,14 @@ def update_machine(machine_id: int):
|
||||
changes[key] = {'old': old_val, 'new': new_val}
|
||||
setattr(mach, key, data[key])
|
||||
|
||||
# The maintenance dates are real dates now (migration 7d40), so they are
|
||||
# parsed rather than assigned as whatever string arrived - and a mistyped
|
||||
# one is refused instead of stored or nulled, like every other date.
|
||||
for key, parsed in maintenancedates.items():
|
||||
if getattr(mach, key) != parsed:
|
||||
changes[key] = {'old': getattr(mach, key), 'new': parsed}
|
||||
setattr(mach, key, parsed)
|
||||
|
||||
# Audit log if there were changes
|
||||
if changes:
|
||||
AuditLog.log('updated', 'Machine', entityid=mach.machineid,
|
||||
@@ -553,16 +578,22 @@ def dashboard_summary():
|
||||
by_type_query = by_type_query.filter(Asset.assetid.notin_(secondaryassetids))
|
||||
by_type = by_type_query.group_by(MachineType.machinetype).all()
|
||||
|
||||
# Count by status
|
||||
# Count by status. The dualpath exclusion applies here too: it was applied
|
||||
# to total and bytype and NOT to this one, so the status bars summed to
|
||||
# more than the total they sit under - by exactly the number of dualpath
|
||||
# secondaries. A breakdown that does not add up to its own headline is the
|
||||
# kind of thing somebody spends an afternoon reconciling.
|
||||
from shopdb.api import AssetStatus
|
||||
by_status = db.session.query(
|
||||
by_status_query = db.session.query(
|
||||
AssetStatus.status,
|
||||
db.func.count(Machine.machineid)
|
||||
).join(Asset, Asset.assetid == Machine.assetid
|
||||
).join(AssetStatus, AssetStatus.statusid == Asset.statusid
|
||||
).filter(Asset.isactive == True
|
||||
).group_by(AssetStatus.status
|
||||
).all()
|
||||
).filter(Asset.isactive == True)
|
||||
if secondaryassetids:
|
||||
by_status_query = by_status_query.filter(
|
||||
Asset.assetid.notin_(secondaryassetids))
|
||||
by_status = by_status_query.group_by(AssetStatus.status).all()
|
||||
|
||||
return success_response({
|
||||
'total': total,
|
||||
|
||||
Reference in New Issue
Block a user