| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811 |
- """配送管理接口"""
- from typing import Optional, List
- from datetime import datetime
- from decimal import Decimal
- from fastapi import APIRouter, Depends, Query
- from sqlalchemy.orm import Session
- from sqlalchemy import and_, desc, func
- from app.core.database import get_db
- from app.core.auth import get_current_user
- from app.core.response import success_response, error_response
- from app.crud.region import region_crud
- from app.crud.user import user_crud
- from app.crud.order import order_crud, order_item_crud
- from app.crud.product import product_crud
- from app.crud.unit import unit_crud
- from app.crud.address import address_crud
- from app.core.order_status_compat import derive_legacy_status_from_three
- from app.models.models import BizPriceListItem, SysRegion, SysUser, BizOrder, BizDeliveryTaskItem, BasAddress
- from app.api.admin import _resolve_admin_payload_with_crypto_fallback
- router = APIRouter(prefix="/web", tags=["运营端-配送管理"])
- def _numeric_to_float_optional(v) -> Optional[float]:
- if v is None:
- return None
- return float(v)
- def _category_path_display(db: Session, category_id: Optional[str]) -> str:
- """一级/二级分类用 / 连接(与订单详情一致)。"""
- from app.crud.category import category_crud
- if not category_id:
- return ""
- names: List[str] = []
- cid = str(category_id).strip()
- seen = set()
- depth = 0
- while cid and depth < 24 and cid not in seen:
- seen.add(cid)
- cat = category_crud.get(db, cid)
- if not cat:
- break
- names.insert(0, (getattr(cat, "name", None) or "").strip())
- pid = getattr(cat, "parent_id", None)
- cid = str(pid).strip() if pid else ""
- depth += 1
- return "/".join([n for n in names if n])
- def _supplier_unit_yuan_from_pli(pli: Optional[BizPriceListItem]) -> Optional[float]:
- """价格清单行 price 为面向客户的最终单价;pricing_coefficient 为调价系数(%),供货单价 = price / (1 + 系数/100)。"""
- if pli is None or getattr(pli, "price", None) is None:
- return None
- final_price = Decimal(str(pli.price))
- raw_coeff = getattr(pli, "pricing_coefficient", None)
- if raw_coeff is None:
- coeff_pct = Decimal("0")
- else:
- try:
- coeff_pct = Decimal(str(raw_coeff))
- except Exception:
- coeff_pct = Decimal("0")
- factor = Decimal("1") + coeff_pct / Decimal("100")
- if factor <= Decimal("0"):
- factor = Decimal("1")
- supplier_unit = (final_price / factor).quantize(Decimal("0.01"))
- return float(supplier_unit)
- def _price_list_item_map(db: Session, price_list_ids: List[str]) -> dict:
- """(price_list_id, product_id, unit_id) -> BizPriceListItem"""
- if not price_list_ids:
- return {}
- rows = (
- db.query(BizPriceListItem)
- .filter(BizPriceListItem.price_list_id.in_(price_list_ids), BizPriceListItem.is_del == 0)
- .all()
- )
- return {(r.price_list_id, r.product_id, r.unit_id): r for r in rows}
- def _supplier_pickup_address_block(db: Session, supplier_id: Optional[str]) -> dict:
- """供应商默认取货地址;无默认时取第一条 pickup 类型地址。"""
- empty = {"fullAddress": None, "contactPerson": None, "contactPhone": None}
- if not supplier_id:
- return empty
- addr = address_crud.get_default(db, supplier_id, "pickup")
- if not addr:
- rows = address_crud.get_by_user_and_type(db, supplier_id, "pickup")
- addr = rows[0] if rows else None
- if not addr:
- return empty
- segs = [
- getattr(addr, "province", None),
- getattr(addr, "city", None),
- getattr(addr, "district", None),
- getattr(addr, "detail_address", None),
- ]
- full = "".join(s for s in segs if s)
- return {
- "fullAddress": full or None,
- "contactPerson": addr.contact_person,
- "contactPhone": addr.contact_phone,
- }
- def _batch_supplier_pickup_address_map(db: Session, supplier_ids: List[str]) -> dict:
- """批量获取供应商取货地址,避免列表接口逐条查询。"""
- if not supplier_ids:
- return {}
- rows = (
- db.query(BasAddress)
- .filter(
- BasAddress.user_id.in_(supplier_ids),
- BasAddress.address_type == "pickup",
- BasAddress.is_del == 0,
- )
- .order_by(BasAddress.user_id.asc(), BasAddress.is_default.desc(), BasAddress.create_at.desc(), BasAddress.id.desc())
- .all()
- )
- addr_map = {}
- for addr in rows:
- uid = str(getattr(addr, "user_id", "") or "")
- if not uid or uid in addr_map:
- continue
- segs = [
- getattr(addr, "province", None),
- getattr(addr, "city", None),
- getattr(addr, "district", None),
- getattr(addr, "detail_address", None),
- ]
- full = "".join(s for s in segs if s)
- addr_map[uid] = {
- "fullAddress": full or None,
- "contactPerson": getattr(addr, "contact_person", None),
- "contactPhone": getattr(addr, "contact_phone", None),
- }
- return addr_map
- def require_admin(current_user: dict = Depends(get_current_user)):
- """验证运营人员权限"""
- if current_user.get("role") not in {"admin", "super_admin"}:
- return error_response(403, "需要运营人员权限")
- return current_user
- # 配送任务状态映射
- DELIVERY_STATUS_MAP = {
- "pending": "待取货",
- "assigned": "已分配",
- "picking": "取货中",
- "delivering": "配送中",
- "completed": "已完成",
- "cancelled": "已取消"
- }
- @router.get("/delivery-tasks", response_model=dict)
- def list_delivery_tasks(
- page: int = Query(1, ge=1, description="页码"),
- pageSize: int = Query(10, ge=1, le=100, description="每页条数"),
- regionId: Optional[str] = Query(None, description="区域ID"),
- status: Optional[str] = Query(None, description="状态"),
- orderNo: Optional[str] = Query(None, description="订单号"),
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_admin)
- ):
- """获取配送任务列表"""
- from app.crud.delivery import delivery_task_crud
- tasks, total = delivery_task_crud.get_multi_with_filters(
- db,
- region_id=regionId,
- status=status,
- order_no=orderNo,
- skip=(page - 1) * pageSize,
- limit=pageSize
- )
- region_map = {}
- supplier_map = {}
- deliver_map = {}
- client_map = {}
- task_ids = [t.id for t in tasks if getattr(t, "id", None)]
- region_ids = list({t.region_id for t in tasks if getattr(t, "region_id", None)})
- supplier_ids = list({t.supplier_id for t in tasks if getattr(t, "supplier_id", None)})
- deliver_ids = list({t.deliver_id for t in tasks if getattr(t, "deliver_id", None)})
- order_ids = list({t.order_id for t in tasks if getattr(t, "order_id", None)})
- # 批量加载区域
- if region_ids:
- regions = db.query(SysRegion).filter(SysRegion.id.in_(region_ids), SysRegion.is_del == 0).all()
- region_map = {r.id: r.name for r in regions}
- # 批量加载供应商/配送员
- user_ids = list({*supplier_ids, *deliver_ids})
- if user_ids:
- users = db.query(SysUser).filter(SysUser.id.in_(user_ids), SysUser.is_del == 0).all()
- user_map = {u.id: u for u in users}
- for sid in supplier_ids:
- u = user_map.get(sid)
- supplier_map[sid] = {"name": u.real_name if u else None, "phone": u.phone if u else None}
- for did in deliver_ids:
- u = user_map.get(did)
- deliver_map[did] = {"name": u.real_name if u else None, "phone": u.phone if u else None}
- # 批量加载订单与客户
- order_map = {}
- if order_ids:
- orders = db.query(BizOrder).filter(BizOrder.id.in_(order_ids), BizOrder.is_del == 0).all()
- order_map = {o.id: o for o in orders}
- client_ids = list({o.client_id for o in orders if getattr(o, "client_id", None)})
- if client_ids:
- clients = db.query(SysUser).filter(SysUser.id.in_(client_ids), SysUser.is_del == 0).all()
- client_map = {u.id: {"name": u.real_name if u else None, "phone": u.phone if u else None} for u in clients}
- # 批量聚合任务明细数量
- item_count_map = {}
- if task_ids:
- rows = (
- db.query(BizDeliveryTaskItem.task_id, func.count(BizDeliveryTaskItem.id))
- .filter(BizDeliveryTaskItem.task_id.in_(task_ids), BizDeliveryTaskItem.is_del == 0)
- .group_by(BizDeliveryTaskItem.task_id)
- .all()
- )
- item_count_map = {task_id: int(cnt or 0) for task_id, cnt in rows}
- # 批量供应商取货地址
- pickup_addr_map = _batch_supplier_pickup_address_map(db, supplier_ids)
- result = []
- for task in tasks:
- # 获取订单信息
- order_info = {}
- if task.order_id:
- order = order_map.get(task.order_id)
- if order:
- order_info = {
- "orderNo": order.order_no,
- "totalAmount": float(order.total_amount) if order.total_amount else 0,
- "clientId": order.client_id,
- "clientName": client_map.get(order.client_id, {}).get("name") if order.client_id else None,
- "contactPerson": order.contact_person,
- "contactPhone": order.contact_phone,
- "deliveryAddress": order.delivery_address
- }
- item_count = item_count_map.get(task.id, 0)
- # 供应商取货地址(用于列表展示)
- supplier_pickup = pickup_addr_map.get(task.supplier_id, {"fullAddress": None, "contactPerson": None, "contactPhone": None})
- result.append({
- "id": task.id,
- "taskNo": task.task_no,
- "orderId": task.order_id,
- "orderNo": order_info.get("orderNo"),
- "regionId": task.region_id,
- "regionName": region_map.get(task.region_id),
- "supplierId": task.supplier_id,
- "supplierName": supplier_map.get(task.supplier_id, {}).get("name"),
- "supplierAddress": supplier_pickup.get("fullAddress"),
- "deliverId": task.deliver_id,
- "deliverName": deliver_map.get(task.deliver_id, {}).get("name"),
- "deliverPhone": deliver_map.get(task.deliver_id, {}).get("phone"),
- "status": task.status,
- "totalAmount": order_info.get("totalAmount"),
- "itemCount": item_count,
- "clientId": order_info.get("clientId"),
- "clientName": order_info.get("clientName"),
- "contactPerson": order_info.get("contactPerson"),
- "contactPhone": order_info.get("contactPhone"),
- "deliveryAddress": order_info.get("deliveryAddress"),
- "dispatchRemark": task.dispatch_remark,
- "createAt": str(task.create_at) if task.create_at else None,
- "assignTime": str(task.create_at) if task.deliver_id else None,
- "pickupTime": str(task.pickup_time) if task.pickup_time else None,
- "deliveryTime": str(task.delivery_time) if task.delivery_time else None,
- "expireTime": str(task.effective_time) if task.effective_time else None,
- "isExpiring": False
- })
- return success_response({
- "list": result,
- "total": total,
- "page": page,
- "pageSize": pageSize,
- "totalPages": (total + pageSize - 1) // pageSize
- })
- @router.get("/delivery-tasks/{task_id}", response_model=dict)
- def get_delivery_task(
- task_id: str,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_admin)
- ):
- """获取配送任务详情"""
- from app.crud.delivery import delivery_task_crud, delivery_task_item_crud
- task = delivery_task_crud.get(db, task_id)
- if not task:
- return error_response(404, "配送任务不存在")
- # 获取任务明细
- items = delivery_task_item_crud.get_by_task(db, task_id)
- # 获取关联信息
- region_name = None
- if task.region_id:
- region = region_crud.get(db, task.region_id)
- region_name = region.name if region else None
- supplier_info = {"name": None, "phone": None}
- if task.supplier_id:
- supplier = user_crud.get(db, task.supplier_id)
- if supplier:
- supplier_info = {"name": supplier.real_name, "phone": supplier.phone}
- deliver_info = {"name": None, "phone": None}
- if task.deliver_id:
- deliver = user_crud.get(db, task.deliver_id)
- if deliver:
- deliver_info = {"name": deliver.real_name, "phone": deliver.phone}
- # 获取订单信息
- order_info = {}
- order_price_list_id: Optional[str] = None
- if task.order_id:
- order = order_crud.get(db, task.order_id)
- if order:
- order_info = {
- "orderNo": order.order_no,
- "totalAmount": float(order.total_amount) if order.total_amount else 0,
- "contactPerson": order.contact_person,
- "contactPhone": order.contact_phone,
- "deliveryAddress": order.delivery_address
- }
- order_price_list_id = getattr(order, "price_list_id", None)
- pickup_addr = _supplier_pickup_address_block(db, task.supplier_id)
- # 处理任务明细(商品展示字段对齐运营端订单明细)
- item_details = []
- pli_map = _price_list_item_map(db, [order_price_list_id]) if order_price_list_id else {}
- for item in items:
- product = product_crud.get(db, item.product_id)
- order_line = order_item_crud.get(db, item.order_item_id) if item.order_item_id else None
- unit_row = None
- if order_line and order_line.unit_id:
- unit_row = unit_crud.get(db, order_line.unit_id)
- elif product and product.unit_id:
- unit_row = unit_crud.get(db, product.unit_id)
- image_url = getattr(product, "image_url", None) if product else None
- category_path = ""
- if product and getattr(product, "category_id", None):
- category_path = _category_path_display(db, str(product.category_id))
- measure_value = _numeric_to_float_optional(getattr(product, "measure_value", None)) if product else None
- qty = float(item.quantity) if item.quantity else 0.0
- unit_price = float(order_line.unit_price) if order_line and order_line.unit_price is not None else None
- amount = float(order_line.amount) if order_line and order_line.amount is not None else None
- if amount is None and unit_price is not None:
- amount = round(qty * float(unit_price), 2)
- client = user_crud.get(db, item.client_id) if item.client_id else None
- # 供货/售价字段:对齐「订单详情」逻辑:优先使用价格清单计算供货单价;无价格清单时兜底订单行售价
- selling_unit_price = unit_price
- selling_amount = amount
- unit_id_for_price = None
- if order_line and getattr(order_line, "unit_id", None):
- unit_id_for_price = order_line.unit_id
- elif product and getattr(product, "unit_id", None):
- unit_id_for_price = product.unit_id
- supplier_unit_price = None
- if order_price_list_id and item.product_id and unit_id_for_price:
- pli = pli_map.get((order_price_list_id, item.product_id, unit_id_for_price))
- supplier_unit_price = _supplier_unit_yuan_from_pli(pli)
- if supplier_unit_price is None:
- supplier_unit_price = unit_price
- supplier_amount = None
- if supplier_unit_price is not None:
- supplier_amount = round(qty * float(supplier_unit_price), 2)
- item_details.append({
- "id": item.id,
- "orderItemId": item.order_item_id,
- "productId": item.product_id,
- "productName": product.name if product else None,
- "imageUrl": image_url,
- "categoryName": category_path or None,
- "measureValue": measure_value,
- "unitName": unit_row.name if unit_row else None,
- "unitPrice": unit_price,
- "amount": amount,
- "sellingUnitPrice": selling_unit_price,
- "sellingAmount": selling_amount,
- "supplierId": task.supplier_id,
- "supplierName": supplier_info.get("name"),
- "supplierUnitPrice": supplier_unit_price,
- "supplierAmount": supplier_amount,
- "quantity": qty,
- "deliveredQuantity": float(item.delivered_quantity) if item.delivered_quantity else 0,
- "clientName": client.real_name if client else None,
- "clientPhone": client.phone if client else None,
- "clientAddress": item.delivery_address,
- "contactPerson": item.contact_person,
- "contactPhone": item.contact_phone,
- "deliveryAddress": item.delivery_address,
- "status": item.status
- })
- # 处理图片
- pickup_images = []
- if task.pickup_image_url:
- pickup_images = task.pickup_image_url.split(",")
- delivery_images = []
- if task.delivery_image_url:
- delivery_images = task.delivery_image_url.split(",")
- return success_response({
- "id": task.id,
- "taskNo": task.task_no,
- "orderId": task.order_id,
- "orderNo": order_info.get("orderNo"),
- "regionId": task.region_id,
- "regionName": region_name,
- "supplierId": task.supplier_id,
- "supplierName": supplier_info.get("name"),
- "supplierPhone": supplier_info.get("phone"),
- "supplierAddress": pickup_addr.get("fullAddress"),
- "supplierPickup": {
- "address": pickup_addr.get("fullAddress"),
- "contactPerson": pickup_addr.get("contactPerson"),
- "contactPhone": pickup_addr.get("contactPhone"),
- },
- "customerDelivery": {
- "address": order_info.get("deliveryAddress"),
- "contactPerson": order_info.get("contactPerson"),
- "contactPhone": order_info.get("contactPhone"),
- },
- "deliverId": task.deliver_id,
- "deliverName": deliver_info.get("name"),
- "deliverPhone": deliver_info.get("phone"),
- "status": task.status,
- "totalAmount": order_info.get("totalAmount"),
- "createAt": str(task.create_at) if task.create_at else None,
- "assignTime": str(task.create_at) if task.deliver_id else None,
- "pickupTime": str(task.pickup_time) if task.pickup_time else None,
- "pickupRemark": task.pickup_remark,
- "pickupImages": pickup_images,
- "deliveryTime": str(task.delivery_time) if task.delivery_time else None,
- "deliveryRemark": task.delivery_remark,
- "deliveryImages": delivery_images,
- "dispatchRemark": task.dispatch_remark,
- "items": item_details
- })
- @router.post("/delivery-tasks/assign", response_model=dict)
- def assign_delivery_task(
- request: dict,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_admin)
- ):
- """分配配送任务"""
- request, req_err = _resolve_admin_payload_with_crypto_fallback(request, current_user)
- if req_err is not None:
- return req_err
- from app.crud.delivery import delivery_task_crud
- from app.crud.order import order_crud
- order_id = request.get("orderId")
- deliver_id = request.get("deliverId")
- if not order_id:
- return error_response(400, "请选择订单")
- if not deliver_id:
- return error_response(400, "请选择配送员")
- # 验证配送员存在
- deliver = user_crud.get(db, deliver_id)
- if not deliver:
- return error_response(404, "配送员不存在")
- if deliver.role != "deliver":
- return error_response(400, "该用户不是配送员")
- task = delivery_task_crud.assign_deliver(
- db,
- order_id=order_id,
- deliver_id=deliver_id,
- operator_id=current_user.get("user_id")
- )
- if not task:
- return error_response(400, "分配失败,请检查订单状态")
- # 阶段D前收尾:分配配送优先维护 delivery_status
- order = order_crud.get(db, order_id)
- if order:
- order.delivery_status = "delivering"
- if order.order_status != "cancelled":
- order.order_status = "pending_confirm"
- order.status = derive_legacy_status_from_three(
- order_status=order.order_status,
- delivery_status=order.delivery_status,
- pay_status=order.pay_status,
- )
- order.update_at = datetime.now()
- db.commit()
- return success_response({
- "id": task.id,
- "status": task.status,
- "message": "分配成功"
- })
- @router.post("/delivery-tasks/assign-batch", response_model=dict)
- def assign_delivery_task_batch(
- request: dict,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_admin)
- ):
- """批量分配配送任务"""
- request, req_err = _resolve_admin_payload_with_crypto_fallback(request, current_user)
- if req_err is not None:
- return req_err
- from app.crud.delivery import delivery_task_crud
- from app.crud.order import order_crud
- assignments = request.get("assignments", [])
- if not assignments:
- return error_response(400, "请选择要分配的订单")
- results = []
- for assignment in assignments:
- order_id = assignment.get("orderId")
- deliver_id = assignment.get("deliverId")
- if order_id and deliver_id:
- task = delivery_task_crud.assign_deliver(
- db,
- order_id=order_id,
- deliver_id=deliver_id,
- operator_id=current_user.get("user_id")
- )
- if task:
- # 阶段D前收尾:分配配送优先维护 delivery_status
- order = order_crud.get(db, order_id)
- if order:
- order.delivery_status = "delivering"
- if order.order_status != "cancelled":
- order.order_status = "pending_confirm"
- order.status = derive_legacy_status_from_three(
- order_status=order.order_status,
- delivery_status=order.delivery_status,
- pay_status=order.pay_status,
- )
- order.update_at = datetime.now()
- db.commit()
- results.append({"orderId": order_id, "success": True})
- else:
- results.append({"orderId": order_id, "success": False, "reason": "分配失败"})
- success_count = sum(1 for r in results if r.get("success"))
- return success_response({
- "total": len(results),
- "success": success_count,
- "failed": len(results) - success_count,
- "results": results
- })
- @router.get("/delivery-tasks/warnings", response_model=dict)
- def list_delivery_warnings(
- page: int = Query(1, ge=1, description="页码"),
- pageSize: int = Query(10, ge=1, le=100, description="每页条数"),
- regionId: Optional[str] = Query(None, description="区域ID"),
- withinMinutes: int = Query(60, description="多少分钟内即将过期"),
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_admin)
- ):
- """获取配送时效预警列表"""
- from app.crud.delivery import delivery_task_crud
- tasks, total, warning_count = delivery_task_crud.get_warning_tasks(
- db,
- region_id=regionId,
- within_minutes=withinMinutes,
- skip=(page - 1) * pageSize,
- limit=pageSize
- )
- region_map = {}
- deliver_map = {}
- result = []
- for task in tasks:
- if task.region_id and task.region_id not in region_map:
- region = region_crud.get(db, task.region_id)
- region_map[task.region_id] = region.name if region else None
- if task.deliver_id and task.deliver_id not in deliver_map:
- deliver = user_crud.get(db, task.deliver_id)
- deliver_map[task.deliver_id] = deliver.real_name if deliver else None
- order_info = {}
- if task.order_id:
- order = order_crud.get(db, task.order_id)
- if order:
- order_info = {"orderNo": order.order_no}
- # 计算剩余时间和预警级别
- remaining_minutes = 0
- warning_level = "normal"
- is_overdue = False
- if task.effective_time:
- now = datetime.now()
- remaining = task.effective_time - now
- remaining_minutes = int(remaining.total_seconds() / 60)
- if remaining_minutes < 0:
- is_overdue = True
- warning_level = "overdue"
- elif remaining_minutes <= 15:
- warning_level = "urgent"
- elif remaining_minutes <= 30:
- warning_level = "warning"
- result.append({
- "id": task.id,
- "taskNo": task.task_no,
- "orderId": task.order_id,
- "orderNo": order_info.get("orderNo"),
- "regionId": task.region_id,
- "regionName": region_map.get(task.region_id),
- "deliverId": task.deliver_id,
- "deliverName": deliver_map.get(task.deliver_id),
- "status": task.status,
- "expireTime": str(task.effective_time) if task.effective_time else None,
- "remainingMinutes": remaining_minutes,
- "isOverdue": is_overdue,
- "warningLevel": warning_level
- })
- return success_response({
- "list": result,
- "total": total,
- "warningCount": warning_count,
- "page": page,
- "pageSize": pageSize,
- "totalPages": (total + pageSize - 1) // pageSize
- })
- @router.post("/delivery-tasks/{task_id}/cancel", response_model=dict)
- def cancel_delivery_task(
- task_id: str,
- request: dict,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_admin)
- ):
- """取消配送任务"""
- request, req_err = _resolve_admin_payload_with_crypto_fallback(request, current_user)
- if req_err is not None:
- return req_err
- from app.crud.delivery import delivery_task_crud
- task = delivery_task_crud.get(db, task_id)
- if not task:
- return error_response(404, "配送任务不存在")
- reason = request.get("reason")
- task = delivery_task_crud.cancel_task(
- db,
- task_id=task_id,
- reason=reason,
- operator_id=current_user.get("user_id"),
- operator_real_name=current_user.get("real_name"),
- )
- if not task:
- return error_response(400, "当前状态不支持取消")
- return success_response({
- "id": task.id,
- "status": task.status,
- "message": "取消成功"
- })
- @router.post("/delivery-tasks/{task_id}/reassign", response_model=dict)
- def reassign_delivery_task(
- task_id: str,
- request: dict,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_admin)
- ):
- """重新分配配送任务"""
- request, req_err = _resolve_admin_payload_with_crypto_fallback(request, current_user)
- if req_err is not None:
- return req_err
- from app.crud.delivery import delivery_task_crud
- task = delivery_task_crud.get(db, task_id)
- if not task:
- return error_response(404, "配送任务不存在")
- new_deliver_id = request.get("deliverId")
- if not new_deliver_id:
- return error_response(400, "请选择新配送员")
- # 验证新配送员
- new_deliver = user_crud.get(db, new_deliver_id)
- if not new_deliver:
- return error_response(404, "配送员不存在")
- if new_deliver.role != "deliver":
- return error_response(400, "该用户不是配送员")
- task = delivery_task_crud.reassign(
- db,
- task_id=task_id,
- new_deliver_id=new_deliver_id,
- operator_id=current_user.get("user_id"),
- operator_real_name=current_user.get("real_name"),
- )
- if not task:
- return error_response(400, "当前状态不支持重新分配")
- return success_response({
- "id": task.id,
- "deliverId": task.deliver_id,
- "message": "重新分配成功"
- })
- @router.get("/delivery-tasks/pending", response_model=dict)
- def list_pending_delivery_tasks(
- page: int = Query(1, ge=1, description="页码"),
- pageSize: int = Query(10, ge=1, le=100, description="每页条数"),
- regionId: Optional[str] = Query(None, description="区域ID"),
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_admin)
- ):
- """获取待分配的配送任务"""
- from app.crud.delivery import delivery_task_crud, delivery_task_item_crud
- tasks, total = delivery_task_crud.get_pending_list(
- db,
- region_id=regionId,
- skip=(page - 1) * pageSize,
- limit=pageSize
- )
- region_map = {}
- result = []
- for task in tasks:
- if task.region_id and task.region_id not in region_map:
- region = region_crud.get(db, task.region_id)
- region_map[task.region_id] = region.name if region else None
- order_info = {}
- if task.order_id:
- order = order_crud.get(db, task.order_id)
- if order:
- order_info = {
- "orderNo": order.order_no,
- "totalAmount": float(order.total_amount) if order.total_amount else 0,
- "contactPerson": order.contact_person,
- "contactPhone": order.contact_phone,
- "deliveryAddress": order.delivery_address
- }
- items = delivery_task_item_crud.get_by_task(db, task.id)
- result.append({
- "id": task.id,
- "taskNo": task.task_no,
- "orderId": task.order_id,
- "orderNo": order_info.get("orderNo"),
- "regionId": task.region_id,
- "regionName": region_map.get(task.region_id),
- "status": task.status,
- "totalAmount": order_info.get("totalAmount"),
- "itemCount": len(items),
- "createAt": str(task.create_at) if task.create_at else None
- })
- return success_response({
- "list": result,
- "total": total,
- "page": page,
- "pageSize": pageSize,
- "totalPages": (total + pageSize - 1) // pageSize
- })
|