Import the wilm agent store, WINC adoption work and batches CLI, with four fixes
This commit is contained in:
@@ -595,6 +595,134 @@ def import_asset_csvs():
|
||||
})
|
||||
|
||||
|
||||
def _winc_upload_text():
|
||||
"""Read the single uploaded WINC export as text, or (None, error_response)."""
|
||||
uploads = request.files.getlist('files')
|
||||
if not uploads:
|
||||
return None, error_response(ErrorCodes.VALIDATION_ERROR,
|
||||
'No export file was uploaded')
|
||||
upload = uploads[0]
|
||||
name = upload.filename or ''
|
||||
if not name.lower().endswith('.csv'):
|
||||
return None, error_response(ErrorCodes.VALIDATION_ERROR,
|
||||
'The WINC export must be a .csv file')
|
||||
try:
|
||||
return upload.read().decode('utf-8-sig'), None
|
||||
except (UnicodeDecodeError, OSError):
|
||||
return None, error_response(ErrorCodes.VALIDATION_ERROR,
|
||||
'Could not read %s as UTF-8 text' % name)
|
||||
|
||||
|
||||
@settings_bp.route('/asset-import/winc/preview', methods=['POST'])
|
||||
@require_role('admin')
|
||||
def winc_import_preview():
|
||||
"""Read-only WINC analysis: report what a commit would do. Writes nothing.
|
||||
|
||||
WINC (a Maximo-style asset dump) is not a canonical-CSV source: it updates
|
||||
existing machines only, needs acknowledgment before creating business units,
|
||||
and is reversible. This returns the approval packet the UI renders - counts,
|
||||
the business units it would create, and any unresolved values that block it.
|
||||
"""
|
||||
from shopdb.core.services.wincimport import preview, WincImportError
|
||||
|
||||
export_text, failure = _winc_upload_text()
|
||||
if failure is not None:
|
||||
return failure
|
||||
try:
|
||||
report = preview(export_text)
|
||||
except WincImportError as exc:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, str(exc))
|
||||
finally:
|
||||
# A preview must leave nothing behind even though it only read: the
|
||||
# analyzer runs against the request-scoped session.
|
||||
db.session.rollback()
|
||||
return success_response(report)
|
||||
|
||||
|
||||
@settings_bp.route('/asset-import/winc/commit', methods=['POST'])
|
||||
@require_role('admin')
|
||||
def winc_import_commit():
|
||||
"""Apply a WINC plan reversibly. Requires acknowledgment for needs-create.
|
||||
|
||||
The 'acknowledge' form field is a comma-separated list of needs-create group
|
||||
names the admin confirmed (e.g. 'businessunit'). The whole write is one
|
||||
transaction recorded to the import journal, so it can be undone later.
|
||||
"""
|
||||
from shopdb.core.services.wincimport import commit as winc_commit
|
||||
from shopdb.core.services.wincimport import WincImportError
|
||||
|
||||
export_text, failure = _winc_upload_text()
|
||||
if failure is not None:
|
||||
return failure
|
||||
|
||||
acknowledge = [
|
||||
part.strip() for part in
|
||||
(request.form.get('acknowledge') or '').split(',') if part.strip()]
|
||||
|
||||
# The hash the PREVIEW returned. Required here, because without it the
|
||||
# writer's drift check compares a freshly built packet against the plan it
|
||||
# was built from and can never fail - so "the admin approved this plan"
|
||||
# meant only "the admin ticked these group names".
|
||||
planhash = (request.form.get('planhash') or '').strip()
|
||||
if not planhash:
|
||||
return error_response(
|
||||
ErrorCodes.VALIDATION_ERROR,
|
||||
'planhash is required: send the value the preview returned, so the '
|
||||
'commit can prove it is applying the plan that was approved.')
|
||||
|
||||
try:
|
||||
report = winc_commit(export_text, acknowledge=acknowledge,
|
||||
createdby=get_jwt_identity(), planhash=planhash)
|
||||
AuditLog.log(
|
||||
'imported', 'SiteAssets', entityname='winc-machine-adoption',
|
||||
changes={'batchid': report.get('batchid'),
|
||||
'rows': report.get('rowcount')})
|
||||
db.session.commit()
|
||||
except WincImportError as exc:
|
||||
db.session.rollback()
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, str(exc))
|
||||
except Exception:
|
||||
db.session.rollback()
|
||||
raise
|
||||
return success_response(report)
|
||||
|
||||
|
||||
@settings_bp.route('/asset-import/winc/batches', methods=['GET'])
|
||||
@require_role('admin')
|
||||
def winc_import_batches():
|
||||
"""List applied/undone WINC import batches, newest first (for the undo UI)."""
|
||||
from shopdb.core.services.wincimport import list_batches
|
||||
|
||||
return success_response({'batches': list_batches()})
|
||||
|
||||
|
||||
@settings_bp.route('/asset-import/winc/undo', methods=['POST'])
|
||||
@require_role('admin')
|
||||
def winc_import_undo():
|
||||
"""Reverse an applied WINC import batch by id. Conservative and reversible."""
|
||||
from shopdb.core.services.wincimport import undo as winc_undo
|
||||
|
||||
payload = request.get_json(silent=True) or {}
|
||||
batchid = (payload.get('batchid') or request.form.get('batchid') or '').strip()
|
||||
if not batchid:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'A batchid is required')
|
||||
|
||||
try:
|
||||
result = winc_undo(batchid)
|
||||
AuditLog.log(
|
||||
'undone', 'SiteAssets', entityname='winc-machine-adoption',
|
||||
changes={'batchid': batchid, 'reverted': result['reverted'],
|
||||
'conflicts': len(result['conflicts'])})
|
||||
db.session.commit()
|
||||
except ValueError as exc:
|
||||
db.session.rollback()
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, str(exc))
|
||||
except Exception:
|
||||
db.session.rollback()
|
||||
raise
|
||||
return success_response(result)
|
||||
|
||||
|
||||
@settings_bp.route('', methods=['GET'])
|
||||
@jwt_required(optional=True)
|
||||
def list_settings():
|
||||
|
||||
Reference in New Issue
Block a user