"""Models (vendor model catalog) API endpoints - Full CRUD.""" from flask import Blueprint, request from flask_jwt_extended import jwt_required from shopdb.extensions import db from shopdb.core.models import Model from shopdb.utils.responses import ( success_response, error_response, paginated_response, ErrorCodes ) from shopdb.utils.pagination import get_pagination_params, paginate_query from shopdb.utils.authz import require_permission, require_role from shopdb.utils.import_mode import apply_import_timestamps models_bp = Blueprint('models', __name__) @models_bp.route('', methods=['GET']) @jwt_required(optional=True) def list_models(): """List all vendor catalog models.""" page, per_page = get_pagination_params(request) query = Model.query if request.args.get('active', 'true').lower() != 'false': query = query.filter(Model.isactive == True) if vendor_id := request.args.get('vendor', type=int): query = query.filter(Model.vendorid == vendor_id) if modeltype_id := request.args.get('modeltype', type=int): query = query.filter(Model.modeltypeid == modeltype_id) # Exact-match natural-key lookup for idempotent import. Natural key is # modelnumber + vendor; pair this with ?vendor= to disambiguate. if exactmodelnumber := request.args.get('modelnumber'): query = query.filter(Model.modelnumber == exactmodelnumber) if search := request.args.get('search'): query = query.filter(Model.modelnumber.ilike(f'%{search}%')) query = query.order_by(Model.modelnumber) items, total = paginate_query(query, page, per_page) data = [] for m in items: d = m.to_dict() d['vendor'] = m.vendor.vendor if m.vendor else None d['modeltype'] = m.modeltype.modeltype if m.modeltype else None data.append(d) return paginated_response(data, page, per_page, total) @models_bp.route('/', methods=['GET']) @jwt_required(optional=True) def get_model(model_id: int): """Get a single model.""" m = db.session.get(Model, model_id) if not m: return error_response( ErrorCodes.NOT_FOUND, f'Model with ID {model_id} not found', http_code=404 ) data = m.to_dict() data['vendor'] = m.vendor.to_dict() if m.vendor else None data['modeltype'] = m.modeltype.to_dict() if m.modeltype else None return success_response(data) @models_bp.route('', methods=['POST']) @jwt_required() @require_role('admin') def create_model(): """Create a new model.""" data = request.get_json() if not data or not data.get('modelnumber'): return error_response(ErrorCodes.VALIDATION_ERROR, 'modelnumber is required') # Check duplicate existing = Model.query.filter_by( modelnumber=data['modelnumber'], vendorid=data.get('vendorid') ).first() if existing: return error_response( ErrorCodes.CONFLICT, f"Model '{data['modelnumber']}' already exists for this vendor", http_code=409 ) m = Model( modelnumber=data['modelnumber'], vendorid=data.get('vendorid'), modeltypeid=data.get('modeltypeid'), description=data.get('description'), imageurl=data.get('imageurl'), documentationurl=data.get('documentationurl'), notes=data.get('notes') ) db.session.add(m) apply_import_timestamps(m, data) db.session.commit() return success_response(m.to_dict(), message='Model created', http_code=201) @models_bp.route('/', methods=['PUT']) @jwt_required() @require_role('admin') def update_model(model_id: int): """Update a model.""" m = db.session.get(Model, model_id) if not m: return error_response( ErrorCodes.NOT_FOUND, f'Model with ID {model_id} not found', http_code=404 ) data = request.get_json() if not data: return error_response(ErrorCodes.VALIDATION_ERROR, 'No data provided') for key in ['modelnumber', 'vendorid', 'modeltypeid', 'description', 'imageurl', 'documentationurl', 'notes', 'isactive']: if key in data: setattr(m, key, data[key]) apply_import_timestamps(m, data) db.session.commit() return success_response(m.to_dict(), message='Model updated') @models_bp.route('/', methods=['DELETE']) @jwt_required() @require_role('admin') def delete_model(model_id: int): """Delete (deactivate) a model.""" m = db.session.get(Model, model_id) if not m: return error_response( ErrorCodes.NOT_FOUND, f'Model with ID {model_id} not found', http_code=404 ) m.isactive = False db.session.commit() return success_response(message='Model deleted')