支持移动端样本编码远程关键字检索、分期起始日刷新与鸿蒙微信附件选取,并同步收款账户选择、角色页与相关设计文档。 Co-authored-by: Cursor <cursoragent@cursor.com>
1096 lines
38 KiB
Python
1096 lines
38 KiB
Python
"""市场专员/地区经理移动端专用 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,
|
|
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,
|
|
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,
|
|
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,
|
|
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')
|