""" Sheet CRUD API — port of ac-helper api.php sheet management. All routes scoped to the authenticated user. """ import logging from quart import Blueprint, jsonify, request from ..auth.middleware import auth_required, get_user_id from ..sheets.manager import ( get_user_sheets, create_sheet, load_sheet_data, update_sheet, delete_sheet, rename_sheet, duplicate_sheet, generate_next_id, set_sheet_client_id, ) logger = logging.getLogger(__name__) sheets_bp = Blueprint('sheets', __name__, url_prefix='/api/sheets') @sheets_bp.route('', methods=['GET']) @auth_required async def list_sheets(): user_id = get_user_id() sheets = get_user_sheets(user_id) return jsonify({'sheets': sheets}) @sheets_bp.route('', methods=['POST']) @auth_required async def create_new_sheet(): user_id = get_user_id() body = await request.get_json() or {} name = body.get('name', '') data = body.get('data', []) client_id = body.get('client_id', '') sheet = create_sheet(user_id, name, data, client_id) return jsonify({'sheet': sheet}), 201 @sheets_bp.route('//client', methods=['PATCH']) @auth_required async def update_sheet_client(sheet_id: str): """Update the client associated with an existing sheet.""" user_id = get_user_id() body = await request.get_json() or {} client_id = body.get('client_id', '') set_sheet_client_id(user_id, sheet_id, client_id) return jsonify({'success': True}) @sheets_bp.route('/', methods=['GET']) @auth_required async def get_sheet(sheet_id: str): user_id = get_user_id() data = load_sheet_data(user_id, sheet_id) if data is None: return jsonify({'error': 'not_found'}), 404 return jsonify({'data': data}) @sheets_bp.route('/', methods=['PUT']) @auth_required async def update_sheet_data(sheet_id: str): user_id = get_user_id() body = await request.get_json() or {} data = body.get('data', []) update_sheet(user_id, sheet_id, data) return jsonify({'success': True}) @sheets_bp.route('/', methods=['DELETE']) @auth_required async def delete_sheet_route(sheet_id: str): user_id = get_user_id() delete_sheet(user_id, sheet_id) return jsonify({'success': True}) @sheets_bp.route('/', methods=['PATCH']) @auth_required async def rename_sheet_route(sheet_id: str): user_id = get_user_id() body = await request.get_json() or {} name = body.get('name', '') success = rename_sheet(user_id, sheet_id, name) if not success: return jsonify({'error': 'not_found'}), 404 return jsonify({'success': True}) @sheets_bp.route('//duplicate', methods=['POST']) @auth_required async def duplicate_sheet_route(sheet_id: str): user_id = get_user_id() sheet = duplicate_sheet(user_id, sheet_id) if sheet is None: return jsonify({'error': 'not_found'}), 404 return jsonify({'sheet': sheet}), 201 @sheets_bp.route('//import', methods=['POST']) @auth_required async def import_deliverables(sheet_id: str): """ Import a list of deliverables into an existing sheet. Body: { "deliverables": [...], "mode": "append" | "replace" } """ user_id = get_user_id() body = await request.get_json() or {} incoming = body.get('deliverables', []) mode = body.get('mode', 'append') existing = load_sheet_data(user_id, sheet_id) if existing is None: return jsonify({'error': 'not_found'}), 404 base = [] if mode == 'replace' else list(existing) for row in incoming: row['Number'] = generate_next_id(base) row.setdefault('Status', 'Booked') row.setdefault('Quantity', 1) # Strip internal brief metadata fields for k in list(row.keys()): if k.startswith('_'): del row[k] base.append(row) update_sheet(user_id, sheet_id, base) return jsonify({'success': True, 'imported': len(incoming), 'total': len(base)})