import json from datetime import date, datetime from flask import (Blueprint, render_template, redirect, url_for, flash, request, current_app, jsonify) from flask_login import login_required, current_user from app.extensions import db from app.models.maintenance import MaintenanceRecord, MAINTENANCE_STATUSES from app.models.asset import Asset, ASSET_STATUSES from app.models.audit_log import AuditLog from app.utils.decorators import editor_required bp = Blueprint('maintenance', __name__, url_prefix='/maintenance') def _parse_date(value): if not value: return None if isinstance(value, date): return value try: return date.fromisoformat(value.strip()) except (ValueError, AttributeError): return None def _log(action, record_id, description, old=None, new=None): entry = AuditLog( table_name='maintenance_records', record_id=record_id, action=action, old_values=json.dumps(old) if old else None, new_values=json.dumps(new) if new else None, performed_by_id=current_user.id, ip_address=request.remote_addr, description=description, ) db.session.add(entry) # ── List ───────────────────────────────────────────────────────────────────── @bp.route('/') @login_required def index(): status_filter = request.args.get('status', '') asset_filter = request.args.get('asset_id', type=int) page = request.args.get('page', 1, type=int) query = MaintenanceRecord.query.join(MaintenanceRecord.asset) if status_filter: query = query.filter(MaintenanceRecord.status == status_filter) if asset_filter: query = query.filter(MaintenanceRecord.asset_id == asset_filter) pagination = query.order_by(MaintenanceRecord.start_date.desc()).paginate( page=page, per_page=current_app.config['ITEMS_PER_PAGE'], error_out=False ) # Assets available for the "add record" dropdown all_assets = Asset.query.order_by(Asset.serial_number).all() return render_template( 'maintenance/index.html', pagination=pagination, status_filter=status_filter, asset_filter=asset_filter, statuses=MAINTENANCE_STATUSES, all_assets=all_assets, edit_record=None, ) # ── Create ─────────────────────────────────────────────────────────────────── @bp.route('/new', methods=['POST']) @login_required @editor_required def create(): asset_id = request.form.get('asset_id', type=int) asset = Asset.query.get(asset_id) if asset_id else None if not asset: flash('Please select a valid asset.', 'danger') return redirect(url_for('maintenance.index')) start = _parse_date(request.form.get('start_date')) if not start: flash('Start date is required.', 'danger') return redirect(url_for('maintenance.index')) record = MaintenanceRecord( asset_id = asset.id, start_date = start, end_date = _parse_date(request.form.get('end_date')), status = request.form.get('status', 'open'), initial_diagnosis = request.form.get('initial_diagnosis', '').strip() or None, action_taken = request.form.get('action_taken', '').strip() or None, action_result = request.form.get('action_result', '').strip() or None, created_by_id = current_user.id, ) db.session.add(record) # Auto-set asset status to 'maintenance' when a new open record is created if record.status in ('open', 'in_progress') and asset.status != 'maintenance': asset.status = 'maintenance' db.session.flush() _log('create', record.id, f'Created maintenance record for asset SN={asset.serial_number}', new={'asset_id': asset.id, 'status': record.status, 'start_date': str(start)}) db.session.commit() flash(f'Maintenance record added for {asset.serial_number}.', 'success') return redirect(url_for('maintenance.index')) # ── Edit (GET — opens modal pre-filled) ───────────────────────────────────── @bp.route('//edit') @login_required @editor_required def edit(record_id): record = MaintenanceRecord.query.get_or_404(record_id) all_assets = Asset.query.order_by(Asset.serial_number).all() status_filter = request.args.get('status', '') asset_filter = request.args.get('asset_id', type=int) page = request.args.get('page', 1, type=int) query = MaintenanceRecord.query.join(MaintenanceRecord.asset) if status_filter: query = query.filter(MaintenanceRecord.status == status_filter) if asset_filter: query = query.filter(MaintenanceRecord.asset_id == asset_filter) pagination = query.order_by(MaintenanceRecord.start_date.desc()).paginate( page=page, per_page=current_app.config['ITEMS_PER_PAGE'], error_out=False ) return render_template( 'maintenance/index.html', pagination=pagination, status_filter=status_filter, asset_filter=asset_filter, statuses=MAINTENANCE_STATUSES, all_assets=all_assets, edit_record=record, ) # ── Update ─────────────────────────────────────────────────────────────────── @bp.route('//update', methods=['POST']) @login_required @editor_required def update(record_id): record = MaintenanceRecord.query.get_or_404(record_id) old = { 'status': record.status, 'start_date': str(record.start_date), 'end_date': str(record.end_date) if record.end_date else None, 'initial_diagnosis': record.initial_diagnosis, 'action_taken': record.action_taken, 'action_result': record.action_result, } new_status = request.form.get('status', record.status) record.start_date = _parse_date(request.form.get('start_date')) or record.start_date record.end_date = _parse_date(request.form.get('end_date')) record.status = new_status record.initial_diagnosis = request.form.get('initial_diagnosis', '').strip() or None record.action_taken = request.form.get('action_taken', '').strip() or None record.action_result = request.form.get('action_result', '').strip() or None # If closed, clear asset's maintenance status (if no other open records) if new_status == 'closed' and record.asset: other_open = MaintenanceRecord.query.filter( MaintenanceRecord.asset_id == record.asset_id, MaintenanceRecord.status != 'closed', MaintenanceRecord.id != record.id, ).first() if not other_open and record.asset.status == 'maintenance': record.asset.status = 'available' _log('update', record.id, f'Updated maintenance record id={record_id}', old=old, new={'status': record.status, 'end_date': str(record.end_date) if record.end_date else None}) db.session.commit() flash('Maintenance record updated.', 'success') return redirect(url_for('maintenance.index')) # ── Delete ─────────────────────────────────────────────────────────────────── @bp.route('//delete', methods=['POST']) @login_required @editor_required def delete(record_id): record = MaintenanceRecord.query.get_or_404(record_id) asset_sn = record.asset.serial_number if record.asset else '?' _log('delete', record.id, f'Deleted maintenance record for asset SN={asset_sn}') db.session.delete(record) db.session.commit() flash('Maintenance record deleted.', 'success') return redirect(url_for('maintenance.index'))