"""市场专员/地区经理移动端专用 API。""" from __future__ import annotations from typing import Literal, Optional from fastapi import APIRouter, Depends, File, Query, UploadFile from sqlalchemy.orm import Session from app.core.database import get_db from app.core.mobile_menu_guard import ( require_market_menu_code, user_has_tenant_menu_perm, ) from app.core.response import error_response, success_response from app.schemas.market_mobile import ( MarketAttachmentDownloadBody, MarketFreightCreateBody, MarketFreightItemsBody, MarketFreightUpdateBody, MarketPaymentTransferBody, MarketPaymentTransferCreateBody, MarketPresignOrderCreateBody, MarketPresignOrderBody, MarketPresignTransferBody, MarketProtocolBody, ) from app.services import ( market_freight, market_payment, market_presign, market_protocol, market_support, ) from app.services.market_field_scope import MarketFieldContext, ScopeKind from app.services.web_auth_roles import resolve_web_role_for_user router = APIRouter(tags=["移动市场"]) def _context(user: dict, scope_kind: ScopeKind) -> MarketFieldContext: return MarketFieldContext( tenant_no=str(user.get("tenant_no") or ""), user_id=str(user.get("user_id") or ""), scope_kind=scope_kind, ) def _has_menu(db: Session, user: dict, permission: str) -> bool: tenant_no = str(user.get('tenant_no') or '').strip() user_id = str(user.get('user_id') or '').strip() return resolve_web_role_for_user( db, user_id, tenant_no=tenant_no ) == 'super_admin' or user_has_tenant_menu_perm( db, user_id=user_id, tenant_no=tenant_no, perm_code=permission, ) def _register_presign_routes(scope_kind: ScopeKind) -> None: prefix = f"/mini/market/{scope_kind}/presign-orders" permission = f"mini:market:{scope_kind}:presign" permission_guard = require_market_menu_code(permission) @router.get(prefix) async def list_orders( page: int = Query(1, ge=1), page_size: int = Query(10, ge=1, le=100, alias="pageSize"), keyword: Optional[str] = None, project_id: Optional[str] = Query(None, alias='projectId'), team_id: Optional[str] = Query(None, alias='teamId'), order_status: Optional[str] = Query(None, alias="orderStatus"), user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): ctx = _context(user, scope_kind) items, total = market_presign.list_presign_orders( db, ctx=ctx, user_info=user, page=page, page_size=page_size, keyword=keyword, project_id=project_id, team_id=team_id, order_status=order_status, ) items = [ market_support.decorate_allowed_actions( 'presign', item, scope_kind=scope_kind ) for item in items ] return success_response( { "list": items, "total": total, "page": page, "pageSize": page_size, "allowedActions": market_support.allowed_actions( 'presign-list', status='', scope_kind=scope_kind ), } ) @router.get(f"{prefix}/form-options") async def presign_form_options( region_id: Optional[str] = Query(None, alias="regionId"), business_id: Optional[str] = Query(None, alias="businessId"), user_keyword: Optional[str] = Query(None, alias='userKeyword', max_length=64), user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: return success_response( market_support.get_form_options( db, module="presign", ctx=_context(user, scope_kind), user_info=user, region_id=region_id, business_id=business_id, user_keyword=user_keyword, ) ) except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.get(f"{prefix}/{{order_id}}") async def get_order( order_id: str, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): return success_response( market_support.decorate_allowed_actions( 'presign', market_presign.get_presign_order( db, order_id=order_id, ctx=_context(user, scope_kind), user_info=user, ), scope_kind=scope_kind, detail=True, ) ) @router.post(prefix) async def create_order( body: MarketPresignOrderCreateBody, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: data = market_presign.create_presign_order( db, body=body, ctx=_context(user, scope_kind), user_info=user, ) return success_response(data, message="创建成功") except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.put(f"{prefix}/{{order_id}}") async def update_order( order_id: str, body: MarketPresignOrderBody, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: data = market_presign.update_presign_order( db, order_id=order_id, body=body, ctx=_context(user, scope_kind), user_info=user, ) return success_response(data, message="保存成功") except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.delete(f"{prefix}/{{order_id}}") async def delete_order( order_id: str, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: market_presign.delete_presign_order( db, order_id=order_id, ctx=_context(user, scope_kind), user_info=user, ) return success_response(message="删除成功") except PermissionError as exc: return error_response(403, str(exc)) except ValueError as exc: return error_response(400, str(exc)) @router.post(f"{prefix}/{{order_id}}/transfers") async def create_transfer( order_id: str, body: MarketPresignTransferBody, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: data = market_presign.create_presign_transfer( db, order_id=order_id, body=body, ctx=_context(user, scope_kind), user_info=user, ) return success_response(data, message="创建成功") except PermissionError as exc: return error_response(403, str(exc)) except ValueError as exc: return error_response(400, str(exc)) transfer_prefix = f"/mini/market/{scope_kind}/presign-transfers" @router.put(f"{transfer_prefix}/{{transfer_id}}") async def update_transfer( transfer_id: str, body: MarketPresignTransferBody, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: data = market_presign.update_presign_transfer( db, transfer_id=transfer_id, body=body, ctx=_context(user, scope_kind), user_info=user, ) return success_response(data, message="保存成功") except PermissionError as exc: return error_response(403, str(exc)) except ValueError as exc: return error_response(400, str(exc)) @router.delete(f"{transfer_prefix}/{{transfer_id}}") async def delete_transfer( transfer_id: str, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: market_presign.delete_presign_transfer( db, transfer_id=transfer_id, ctx=_context(user, scope_kind), user_info=user, ) return success_response(message="删除成功") except PermissionError as exc: return error_response(403, str(exc)) except ValueError as exc: return error_response(400, str(exc)) @router.post(f"{transfer_prefix}/{{transfer_id}}/submit") async def submit_transfer( transfer_id: str, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: data = market_presign.submit_presign_transfer( db, transfer_id=transfer_id, ctx=_context(user, scope_kind), user_info=user, ) return success_response(data, message="已提交财务确认") except PermissionError as exc: return error_response(403, str(exc)) except ValueError as exc: return error_response(400, str(exc)) @router.post(f"{transfer_prefix}/{{transfer_id}}/withdraw") async def withdraw_transfer( transfer_id: str, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: data = market_presign.withdraw_presign_transfer( db, transfer_id=transfer_id, ctx=_context(user, scope_kind), user_info=user, ) return success_response(data, message="已撤回") except PermissionError as exc: return error_response(403, str(exc)) except ValueError as exc: return error_response(400, str(exc)) @router.post(f"{prefix}/{{order_id}}/convert") async def convert_order( order_id: str, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: data = market_presign.convert_presign_order( db, order_id=order_id, ctx=_context(user, scope_kind), user_info=user, ) return success_response(data, message="已转正式协议") except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) _register_presign_routes("specialist") _register_presign_routes("manager") def _register_protocol_routes(scope_kind: ScopeKind) -> None: prefix = f'/mini/market/{scope_kind}/protocols' permission = f'mini:market:{scope_kind}:protocol' permission_guard = require_market_menu_code(permission) @router.get(prefix) async def list_protocols( page: int = Query(1, ge=1), page_size: int = Query(10, ge=1, le=100, alias='pageSize'), keyword: Optional[str] = None, project_id: Optional[str] = Query(None, alias='projectId'), team_id: Optional[str] = Query(None, alias='teamId'), source: Optional[Literal['cell_storage', 'research_donation']] = None, protocol_status: Optional[str] = Query(None, alias='protocolStatus'), create_time_start: Optional[str] = Query(None, alias='createTimeStart'), create_time_end: Optional[str] = Query(None, alias='createTimeEnd'), sample_collect_time_start: Optional[str] = Query( None, alias='sampleCollectTimeStart' ), sample_collect_time_end: Optional[str] = Query( None, alias='sampleCollectTimeEnd' ), user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: items, total = market_protocol.list_protocols( db, ctx=_context(user, scope_kind), user_info=user, page=page, page_size=page_size, keyword=keyword, project_id=project_id, team_id=team_id, source=source, protocol_status=protocol_status, create_time_start=create_time_start, create_time_end=create_time_end, sample_collect_time_start=sample_collect_time_start, sample_collect_time_end=sample_collect_time_end, ) except ValueError as exc: return error_response(400, str(exc)) items = [ market_support.decorate_allowed_actions( 'protocol', item, scope_kind=scope_kind ) for item in items ] return success_response( { 'list': items, 'total': total, 'page': page, 'pageSize': page_size, 'allowedActions': market_support.allowed_actions( 'protocol-list', status='', scope_kind=scope_kind ), } ) @router.get(f'{prefix}/form-options') async def protocol_form_options( region_id: Optional[str] = Query(None, alias='regionId'), business_id: Optional[str] = Query(None, alias='businessId'), code_filter: Optional[str] = Query(None, alias='codeFilter'), keyword: Optional[str] = Query(None, max_length=64), user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: return success_response( market_support.get_form_options( db, module='protocol', ctx=_context(user, scope_kind), user_info=user, region_id=region_id, business_id=business_id, code_filter=code_filter, sample_code_keyword=keyword, ) ) except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.get(f'{prefix}/customers') async def protocol_customers( keyword: Optional[str] = None, page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100, alias='pageSize'), user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: return success_response( market_support.search_customers_for_protocol( db, ctx=_context(user, scope_kind), user_info=user, keyword=keyword, page=page, page_size=page_size, ) ) except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.get(f'{prefix}/{{protocol_id}}') async def get_protocol( protocol_id: str, source: Optional[ Literal['cell_storage', 'research_donation'] ] = Query(None), user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): return success_response( market_support.decorate_allowed_actions( 'protocol', market_protocol.get_protocol( db, source=source, protocol_id=protocol_id, ctx=_context(user, scope_kind), user_info=user, ), scope_kind=scope_kind, has_payment_permission=_has_menu( db, user, f'mini:market:{scope_kind}:payment' ), ) ) @router.post(prefix) async def create_protocol( body: MarketProtocolBody, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: data = market_protocol.create_protocol( db, body=body, ctx=_context(user, scope_kind), user_info=user, ) return success_response(data, message='创建成功') except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.put(f'{prefix}/{{protocol_id}}') async def update_protocol( protocol_id: str, body: MarketProtocolBody, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: data = market_protocol.update_protocol( db, source=body.source, protocol_id=protocol_id, body=body, ctx=_context(user, scope_kind), user_info=user, ) return success_response(data, message='保存成功') except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.delete(f'{prefix}/{{protocol_id}}') async def delete_protocol( protocol_id: str, source: Literal['cell_storage', 'research_donation'] = Query(...), user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: market_protocol.delete_protocol( db, source=source, protocol_id=protocol_id, ctx=_context(user, scope_kind), user_info=user, ) return success_response(message='删除成功') except PermissionError as exc: return error_response(403, str(exc)) except ValueError as exc: return error_response(400, str(exc)) _register_protocol_routes('specialist') _register_protocol_routes('manager') def _register_payment_routes(scope_kind: ScopeKind) -> None: prefix = f'/mini/market/{scope_kind}' chase_guard = require_market_menu_code( f'mini:market:{scope_kind}:payment-chase' ) payment_guard = require_market_menu_code( f'mini:market:{scope_kind}:payment' ) @router.get(f'{prefix}/payment-chase') async def list_payment_chase( page: int = Query(1, ge=1), page_size: int = Query(10, ge=1, le=100, alias='pageSize'), keyword: Optional[str] = None, chase_type: Optional[Literal['overdue', 'upcoming']] = Query( None, alias='chaseType' ), pay_mode: Optional[str] = Query(None, alias='payMode'), sales_user_id: Optional[str] = Query(None, alias='salesUserId'), user: dict = Depends(chase_guard), db: Session = Depends(get_db), ): items, total = market_payment.list_payment_chase( db, ctx=_context(user, scope_kind), user_info=user, page=page, page_size=page_size, keyword=keyword, chase_type=chase_type, pay_mode=pay_mode, sales_user_id=sales_user_id, ) items = [ market_support.decorate_allowed_actions( 'payment-chase', item, scope_kind=scope_kind, ) for item in items ] return success_response( {'list': items, 'total': total, 'page': page, 'pageSize': page_size} ) @router.get(f'{prefix}/payment-chase/{{schedule_id}}') async def get_payment_chase_detail( schedule_id: str, view: Literal['payment', 'protocol'] = Query(...), user: dict = Depends(chase_guard), db: Session = Depends(get_db), ): try: return success_response( market_payment.get_payment_chase_detail( db, schedule_id=schedule_id, view=view, ctx=_context(user, scope_kind), user_info=user, ) ) except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.get(f'{prefix}/payments') async def list_payments( page: int = Query(1, ge=1), page_size: int = Query(10, ge=1, le=100, alias='pageSize'), keyword: Optional[str] = None, pay_mode: Optional[str] = Query(None, alias='payMode'), progress_status: Optional[str] = Query(None, alias='progressStatus'), installment_due_date_start: Optional[str] = Query( None, alias='installmentDueDateStart' ), installment_due_date_end: Optional[str] = Query( None, alias='installmentDueDateEnd' ), user: dict = Depends(payment_guard), db: Session = Depends(get_db), ): try: items, total = market_payment.list_payments( db, ctx=_context(user, scope_kind), user_info=user, page=page, page_size=page_size, keyword=keyword, pay_mode=pay_mode, progress_status=progress_status, installment_due_date_start=installment_due_date_start, installment_due_date_end=installment_due_date_end, ) except ValueError as exc: return error_response(400, str(exc)) items = [ market_support.decorate_allowed_actions( 'payment', item, scope_kind=scope_kind ) for item in items ] return success_response( {'list': items, 'total': total, 'page': page, 'pageSize': page_size} ) @router.get(f'{prefix}/payments/form-options') async def payment_form_options( user: dict = Depends(payment_guard), db: Session = Depends(get_db), ): return success_response( market_support.get_form_options( db, module='payment', ctx=_context(user, scope_kind), user_info=user, ) ) @router.get(f'{prefix}/payments/{{payment_id}}') async def get_payment( payment_id: str, user: dict = Depends(payment_guard), db: Session = Depends(get_db), ): return success_response( market_support.decorate_allowed_actions( 'payment', market_payment.get_payment_detail( db, payment_id=payment_id, ctx=_context(user, scope_kind), user_info=user, ), scope_kind=scope_kind, ) ) @router.post(f'{prefix}/payment-transfers') async def create_payment_transfer( body: MarketPaymentTransferCreateBody, user: dict = Depends(payment_guard), db: Session = Depends(get_db), ): try: fields = body.model_dump(by_alias=True, exclude_unset=True) schedule_id = fields.pop('scheduleId') return success_response( market_support.decorate_allowed_actions( 'payment-transfer', market_payment.create_transfer( db, schedule_id=schedule_id, fields=fields, ctx=_context(user, scope_kind), user_info=user, ), scope_kind=scope_kind, ), message='创建成功', ) except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.put(f'{prefix}/payment-transfers/{{transfer_id}}') async def update_payment_transfer( transfer_id: str, body: MarketPaymentTransferBody, user: dict = Depends(payment_guard), db: Session = Depends(get_db), ): try: return success_response( market_support.decorate_allowed_actions( 'payment-transfer', market_payment.update_transfer( db, transfer_id=transfer_id, fields=body.model_dump(by_alias=True, exclude_unset=True), ctx=_context(user, scope_kind), user_info=user, ), scope_kind=scope_kind, ), message='保存成功', ) except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.post(f'{prefix}/payment-transfers/{{transfer_id}}/submit') async def submit_payment_transfer( transfer_id: str, user: dict = Depends(payment_guard), db: Session = Depends(get_db), ): try: return success_response( market_support.decorate_allowed_actions( 'payment-transfer', market_payment.submit_transfer( db, transfer_id=transfer_id, ctx=_context(user, scope_kind), user_info=user, ), scope_kind=scope_kind, ), message='已提交财务确认', ) except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.delete(f'{prefix}/payment-transfers/{{transfer_id}}') async def delete_payment_transfer( transfer_id: str, user: dict = Depends(payment_guard), db: Session = Depends(get_db), ): try: return success_response( market_payment.delete_transfer( db, transfer_id=transfer_id, ctx=_context(user, scope_kind), user_info=user, ), message='删除成功', ) except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.post(f'{prefix}/payment-transfers/{{transfer_id}}/withdraw') async def withdraw_payment_transfer( transfer_id: str, user: dict = Depends(payment_guard), db: Session = Depends(get_db), ): try: return success_response( market_support.decorate_allowed_actions( 'payment-transfer', market_payment.withdraw_transfer( db, transfer_id=transfer_id, ctx=_context(user, scope_kind), user_info=user, ), scope_kind=scope_kind, ), message='已撤回', ) except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) _register_payment_routes('specialist') _register_payment_routes('manager') def _register_freight_routes(scope_kind: ScopeKind) -> None: prefix = f'/mini/market/{scope_kind}/freights' permission_guard = require_market_menu_code( f'mini:market:{scope_kind}:freight' ) @router.get(prefix) async def list_freights( page: int = Query(1, ge=1), page_size: int = Query(10, ge=1, le=100, alias='pageSize'), keyword: Optional[str] = None, transport_mode: Optional[str] = Query(None, alias='transportMode'), sample_source: Optional[ Literal['cell_storage', 'research_donation'] ] = Query(None, alias='sampleSource'), include_items: bool = Query(False, alias='includeItems'), create_time_start: Optional[str] = Query(None, alias='createTimeStart'), create_time_end: Optional[str] = Query(None, alias='createTimeEnd'), user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: items, total = market_freight.list_freights( db, ctx=_context(user, scope_kind), user_info=user, page=page, page_size=page_size, keyword=keyword, transport_mode=transport_mode, sample_source=sample_source, include_items=include_items, create_time_start=create_time_start, create_time_end=create_time_end, ) except ValueError as exc: return error_response(400, str(exc)) items = [ market_support.decorate_allowed_actions( 'freight', item, scope_kind=scope_kind ) for item in items ] return success_response( { 'list': items, 'total': total, 'page': page, 'pageSize': page_size, 'allowedActions': market_support.allowed_actions( 'freight-list', status='', scope_kind=scope_kind ), } ) @router.get(f'{prefix}/sample-options') async def list_freight_sample_options( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100, alias='pageSize'), keyword: Optional[str] = None, sample_source: Optional[ Literal['cell_storage', 'research_donation'] ] = Query(None, alias='sampleSource'), user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): items, total = market_freight.list_sample_options( db, ctx=_context(user, scope_kind), user_info=user, page=page, page_size=page_size, keyword=keyword, sample_source=sample_source, ) return success_response( {'list': items, 'total': total, 'page': page, 'pageSize': page_size} ) @router.get(f'{prefix}/form-options') async def freight_form_options( user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): return success_response( market_support.get_form_options( db, module='freight', ctx=_context(user, scope_kind), user_info=user, ) ) @router.get(f'{prefix}/{{freight_id}}') async def get_freight( freight_id: str, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): return success_response( market_support.decorate_allowed_actions( 'freight', market_freight.get_freight_detail( db, freight_id, ctx=_context(user, scope_kind), user_info=user, ), scope_kind=scope_kind, detail=True, ) ) @router.post(prefix) async def create_freight( body: MarketFreightCreateBody, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: return success_response( market_freight.create_freight( db, ctx=_context(user, scope_kind), user_info=user, transport_mode=body.transport_mode, freight_amount=body.freight_amount, waybill_no=body.waybill_no, items=[ item.model_dump(by_alias=True) for item in body.items ], ), message='创建成功', ) except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.put(f'{prefix}/{{freight_id}}') async def update_freight( freight_id: str, body: MarketFreightUpdateBody, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: return success_response( market_freight.update_freight( db, freight_id, ctx=_context(user, scope_kind), user_info=user, transport_mode=body.transport_mode, freight_amount=body.freight_amount, waybill_no=body.waybill_no, waybill_supplied='waybill_no' in body.model_fields_set, amount_supplied='freight_amount' in body.model_fields_set, ), message='保存成功', ) except PermissionError as exc: return error_response(403, str(exc)) except ValueError as exc: return error_response(400, str(exc)) @router.post(f'{prefix}/{{freight_id}}/link') async def link_freight_samples( freight_id: str, body: MarketFreightItemsBody, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: return success_response( market_freight.link_samples( db, freight_id, ctx=_context(user, scope_kind), user_info=user, items=[ item.model_dump(by_alias=True) for item in body.items ], ), message='关联成功', ) except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.post(f'{prefix}/{{freight_id}}/unlink') async def unlink_freight_samples( freight_id: str, body: MarketFreightItemsBody, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: return success_response( market_freight.unlink_samples( db, freight_id, ctx=_context(user, scope_kind), user_info=user, items=[ item.model_dump(by_alias=True) for item in body.items ], ), message='已取消关联', ) except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) @router.delete(f'{prefix}/{{freight_id}}') async def delete_freight( freight_id: str, user: dict = Depends(permission_guard), db: Session = Depends(get_db), ): try: return success_response( market_freight.delete_freight( db, freight_id, ctx=_context(user, scope_kind), user_info=user, ), message='删除成功', ) except PermissionError as exc: return error_response(403, str(exc)) except ValueError as exc: return error_response(400, str(exc)) _register_freight_routes('specialist') _register_freight_routes('manager') def _register_attachment_routes(scope_kind: ScopeKind) -> None: for module in ('presign', 'protocol', 'payment'): permission_guard = require_market_menu_code( f'mini:market:{scope_kind}:{module}' ) path = f'/mini/market/{scope_kind}/{module}/attachments' download_path = f'{path}/download-url' def build_upload_endpoint(bound_module, bound_guard): async def upload_market_attachment( file: UploadFile = File(...), user: dict = Depends(bound_guard), db: Session = Depends(get_db), ): try: max_bytes = ( market_support.PRESIGN_MAX_UPLOAD_BYTES if bound_module == 'presign' else market_support.PROTOCOL_MAX_UPLOAD_BYTES if bound_module == 'protocol' else market_support.MAX_UPLOAD_BYTES ) content = await file.read(max_bytes + 1) return success_response( market_support.save_market_attachment( db, content=content, filename=file.filename or '', content_type=file.content_type or '', module=bound_module, ctx=_context(user, scope_kind), user_info=user, ), message='上传成功', ) except (PermissionError, ValueError) as exc: return error_response(400, str(exc)) finally: await file.close() return upload_market_attachment router.add_api_route( path, build_upload_endpoint(module, permission_guard), methods=['POST'], name=f'upload_market_{scope_kind}_{module}_attachment', ) def build_download_endpoint(bound_module, bound_guard): async def download_market_attachment( body: MarketAttachmentDownloadBody, user: dict = Depends(bound_guard), db: Session = Depends(get_db), ): try: return success_response( market_support.get_market_attachment_download_url( db, file_url=body.file_url, file_name=body.file_name, module=bound_module, ctx=_context(user, scope_kind), user_info=user, ) ) except (PermissionError, ValueError): return error_response(403, '附件不存在或无权下载') except Exception: return error_response(500, '获取下载链接失败') return download_market_attachment router.add_api_route( download_path, build_download_endpoint(module, permission_guard), methods=['POST'], name=f'download_market_{scope_kind}_{module}_attachment', ) _register_attachment_routes('specialist') _register_attachment_routes('manager') def _register_project_filter_routes(scope_kind: ScopeKind) -> None: from app.core.auth import get_current_user from fastapi import HTTPException from app.services import project_management from app.services.market_field_scope import apply_presign_scope, apply_cell_protocol_scope, apply_donation_scope @router.get(f'/mini/market/{scope_kind}/project-options') def project_filter_options(bizType: Literal['presign', 'cell_storage', 'research_donation'], user: dict = Depends(get_current_user), db: Session = Depends(get_db)): module = 'presign' if bizType == 'presign' else 'protocol' if not _has_menu(db, user, f'mini:market:{scope_kind}:{module}'): raise HTTPException(403, '无移动端菜单权限') model = project_management.BUSINESS[bizType][0] apply_scope = {'presign': apply_presign_scope, 'cell_storage': apply_cell_protocol_scope, 'research_donation': apply_donation_scope}[bizType] query = apply_scope(db.query(model), db, _context(user, scope_kind), user_info=user) return success_response(project_management.filter_options( db, user['tenant_no'], user, bizType, business_scope=query)) _register_project_filter_routes('specialist') _register_project_filter_routes('manager')