| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501 |
- """小程序端客户端接口"""
- import re
- from typing import Optional
- from fastapi import APIRouter, Depends, Request, Query
- from sqlalchemy.orm import Session
- from sqlalchemy import and_
- from app.core.database import get_db
- from app.core.auth import get_current_user, get_client_ip
- from app.core.response import success_response, error_response
- from app.core.request_payload import resolve_request_payload
- from app.models.models import (
- BasAddress,
- BasProduct,
- BasProductCategory,
- BasProductUnit,
- BizDeliveryTask,
- BizOrderItem,
- BizPriceListItem,
- SysUser,
- )
- from app.crud.address import address_crud
- from app.crud.user import user_crud
- from app.crud.region import region_crud
- router = APIRouter(prefix="/api/mini/client", tags=["小程序端-客户端接口"])
- def require_client(current_user: dict = Depends(get_current_user)):
- """验证客户权限"""
- if current_user.get("role") != "client":
- return error_response(403, "需要客户权限")
- return current_user
- def require_supplier_or_client(current_user: dict = Depends(get_current_user)):
- """验证供应商或客户权限"""
- if current_user.get("role") not in ["client", "supplier"]:
- return error_response(403, "需要供应商或客户权限")
- return current_user
- def _resolve_request_payload(request_body: dict, current_user: dict) -> dict:
- return resolve_request_payload(request_body, current_user, source="mini_client")
- def _deliverer_contact_phone(du: SysUser) -> Optional[str]:
- """配送员联系电话:优先 sys_user.phone;为空则尝试 username 为 11 位手机号的情况。"""
- p = (getattr(du, "phone", None) or "").strip()
- if p:
- return p
- un = (getattr(du, "username", None) or "").strip()
- if re.fullmatch(r"1[3-9]\d{9}", un):
- return un
- return None
- # ============ 地址管理 ============
- def _address_to_client_dict(addr) -> dict:
- return {
- "id": addr.id,
- "userId": addr.user_id,
- "addressType": addr.address_type,
- "contactPerson": addr.contact_person,
- "contactPhone": addr.contact_phone,
- "province": addr.province,
- "city": addr.city,
- "district": addr.district,
- "detailAddress": addr.detail_address,
- "isDefault": addr.is_default,
- "createAt": str(addr.create_at) if addr.create_at else None,
- }
- @router.get("/addresses", response_model=dict)
- def list_addresses(
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client),
- page: Optional[int] = Query(None, ge=1, description="传则分页;不传则返回全量数组(兼容下单/选地址等)"),
- pageSize: Optional[int] = Query(None, ge=1, le=100, description="分页条数,默认 20"),
- ):
- """获取客户地址列表"""
- uid = current_user.get("user_id")
- if page is None:
- addresses = address_crud.get_by_user(db, uid)
- return success_response([_address_to_client_dict(a) for a in addresses])
- ps = pageSize if pageSize is not None else 20
- skip = (page - 1) * ps
- addresses, total = address_crud.get_by_user_paged(db, uid, skip=skip, limit=ps)
- items = [_address_to_client_dict(a) for a in addresses]
- total_pages = (total + ps - 1) // ps if total else 0
- return success_response({
- "list": items,
- "total": total,
- "page": page,
- "pageSize": ps,
- "totalPages": total_pages,
- })
- @router.get("/addresses/{address_id}", response_model=dict)
- def get_address(
- address_id: str,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """获取地址详情"""
- address = address_crud.get(db, address_id)
-
- if not address or address.user_id != current_user.get("user_id"):
- return error_response(404, "地址不存在")
-
- return success_response({
- "id": address.id,
- "userId": address.user_id,
- "addressType": address.address_type,
- "contactPerson": address.contact_person,
- "contactPhone": address.contact_phone,
- "province": address.province,
- "city": address.city,
- "district": address.district,
- "detailAddress": address.detail_address,
- "isDefault": address.is_default,
- "createAt": str(address.create_at) if address.create_at else None
- })
- @router.post("/addresses", response_model=dict)
- def create_address(
- request: dict,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """创建地址"""
- payload = _resolve_request_payload(request, current_user)
- if not payload.get("addressType") and not payload.get("address_type"):
- return error_response(400, "地址类型不能为空")
- payload["user_id"] = current_user.get("user_id")
- creator = {"user_id": current_user.get("user_id"), "real_name": current_user.get("real_name")}
-
- # 如果设置为默认,先取消其他默认
- if payload.get("isDefault"):
- address_crud.set_default(db, current_user.get("user_id"), "")
-
- address = address_crud.create(db, payload, creator)
-
- return success_response({
- "id": address.id,
- "addressType": address.address_type,
- "contactPerson": address.contact_person,
- "contactPhone": address.contact_phone
- })
- @router.put("/addresses/{address_id}", response_model=dict)
- def update_address(
- address_id: str,
- request: dict,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """更新地址"""
- address = address_crud.get(db, address_id)
-
- if not address or address.user_id != current_user.get("user_id"):
- return error_response(404, "地址不存在")
-
- # 如果设置为默认,先取消其他默认
- payload = _resolve_request_payload(request, current_user)
- if payload.get("isDefault"):
- address_crud.set_default(db, current_user.get("user_id"), address_id)
-
- updater = {"user_id": current_user.get("user_id"), "real_name": current_user.get("real_name")}
- address = address_crud.update_with_history(db, address_id, payload, updater)
-
- return success_response({
- "id": address.id,
- "addressType": address.address_type,
- "contactPerson": address.contact_person,
- "contactPhone": address.contact_phone
- })
- @router.delete("/addresses/{address_id}", response_model=dict)
- def delete_address(
- address_id: str,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """删除地址"""
- address = address_crud.get(db, address_id)
-
- if not address or address.user_id != current_user.get("user_id"):
- return error_response(404, "地址不存在")
-
- success = address_crud.soft_delete_with_history(db, address_id)
-
- return success_response({"id": address_id})
- @router.post("/addresses/{address_id}/set-default", response_model=dict)
- def set_default_address(
- address_id: str,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """设置默认地址"""
- address = address_crud.get(db, address_id)
-
- if not address or address.user_id != current_user.get("user_id"):
- return error_response(404, "地址不存在")
-
- success = address_crud.set_default(db, current_user.get("user_id"), address_id)
-
- if success:
- return success_response({"message": "设置成功"})
- return error_response(400, "设置失败")
- # ============ 个人资料 ============
- @router.get("/profile", response_model=dict)
- def get_profile(
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """获取客户个人资料"""
- user = user_crud.get(db, current_user.get("user_id"))
-
- if not user:
- return error_response(404, "用户不存在")
-
- region_name = None
- if user.region_id:
- region = region_crud.get(db, user.region_id)
- region_name = region.name if region else None
-
- # 获取默认配送地址
- default_address = address_crud.get_default(db, user.id, "delivery")
- address_info = None
- if default_address:
- address_info = {
- "contactPerson": default_address.contact_person,
- "contactPhone": default_address.contact_phone,
- "province": default_address.province,
- "city": default_address.city,
- "district": default_address.district,
- "detailAddress": default_address.detail_address
- }
-
- return success_response({
- "id": user.id,
- "username": user.username,
- "realName": user.real_name,
- "phone": user.phone,
- "role": user.role,
- "regionId": user.region_id,
- "regionName": region_name,
- "address": address_info
- })
- @router.put("/profile", response_model=dict)
- def update_profile(
- request: dict,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """更新客户个人资料"""
- user = user_crud.get(db, current_user.get("user_id"))
-
- if not user:
- return error_response(404, "用户不存在")
-
- payload = _resolve_request_payload(request, current_user)
- updater = {"user_id": current_user.get("user_id"), "real_name": current_user.get("real_name")}
- user = user_crud.update_with_history(db, current_user.get("user_id"), payload, updater)
-
- return success_response({
- "id": user.id,
- "realName": user.real_name,
- "phone": user.phone
- })
- # ============ 价格清单 - 客户端查看 ============
- @router.get("/price-list/current", response_model=dict)
- def get_current_price_list(
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """获取当前生效的价格清单"""
- from app.crud.price_list import price_list_crud, price_list_item_crud
- user = user_crud.get(db, current_user.get("user_id"))
- if not user or not user.region_id:
- return error_response(400, "用户未绑定区域")
- price_list = price_list_crud.get_active(db, user.region_id)
- if not price_list:
- return success_response(None)
- # 批量拉取价格项 + 商品/分类/单位,避免循环 N+1
- items = (
- db.query(
- BizPriceListItem.product_id,
- BizPriceListItem.unit_id,
- BizPriceListItem.price,
- BasProduct.name.label("product_name"),
- BasProduct.image_url.label("image_url"),
- BasProduct.category_id.label("category_id"),
- BasProductCategory.name.label("category_name"),
- BasProductUnit.name.label("unit_name"),
- )
- .outerjoin(
- BasProduct,
- and_(BizPriceListItem.product_id == BasProduct.id, BasProduct.is_del == 0),
- )
- .outerjoin(
- BasProductCategory,
- and_(BasProduct.category_id == BasProductCategory.id, BasProductCategory.is_del == 0),
- )
- .outerjoin(
- BasProductUnit,
- and_(BizPriceListItem.unit_id == BasProductUnit.id, BasProductUnit.is_del == 0),
- )
- .filter(BizPriceListItem.price_list_id == price_list.id, BizPriceListItem.is_del == 0)
- .all()
- )
- # 获取关联信息
- region = region_crud.get(db, price_list.region_id)
- region_name = region.name if region else None
- supplier = user_crud.get(db, price_list.supplier_id)
- supplier_name = supplier.real_name if supplier else None
- item_details = [
- {
- "productId": item.product_id,
- "productName": item.product_name,
- "categoryName": item.category_name,
- "unitId": item.unit_id,
- "unitName": item.unit_name,
- "price": float(item.price) if item.price else 0,
- "imageUrl": item.image_url,
- }
- for item in items
- ]
- return success_response({
- "id": price_list.id,
- "regionId": price_list.region_id,
- "regionName": region_name,
- "supplierId": price_list.supplier_id,
- "supplierName": supplier_name,
- "version": price_list.version,
- "effectiveTime": str(price_list.effective_time) if price_list.effective_time else None,
- "publishedTime": str(price_list.published_time) if price_list.published_time else None,
- "items": item_details
- })
- @router.get("/price-list/products", response_model=dict)
- def get_price_list_products(
- categoryId: Optional[str] = Query(None, description="分类ID"),
- keyword: Optional[str] = Query(None, description="搜索关键词"),
- page: int = Query(1, ge=1, description="页码"),
- pageSize: int = Query(20, ge=1, le=100, description="每页条数"),
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """获取价格清单商品列表(带分类和搜索)"""
- from app.crud.price_list import price_list_crud, price_list_item_crud
- from app.crud.product import product_crud
- from app.crud.category import category_crud
- from app.crud.unit import unit_crud
- user = user_crud.get(db, current_user.get("user_id"))
- if not user or not user.region_id:
- return error_response(400, "用户未绑定区域")
- price_list = price_list_crud.get_active(db, user.region_id)
- if not price_list:
- return success_response({
- "list": [],
- "total": 0,
- "page": page,
- "pageSize": pageSize,
- "totalPages": 0
- })
- items = price_list_item_crud.get_by_price_list(db, price_list.id)
- # 获取商品信息并筛选
- result = []
- for item in items:
- product = product_crud.get(db, item.product_id)
- if not product:
- continue
- # 分类筛选
- if categoryId and product.category_id != categoryId:
- continue
- # 关键词筛选
- if keyword:
- if keyword.lower() not in (product.name or "").lower():
- continue
- category_name = None
- if product.category_id:
- category = category_crud.get(db, product.category_id)
- category_name = category.name if category else None
- unit_name = None
- if item.unit_id:
- u = unit_crud.get(db, item.unit_id)
- unit_name = u.name if u else None
- result.append({
- "productId": item.product_id,
- "productName": product.name,
- "categoryId": product.category_id,
- "categoryName": category_name,
- "unitId": item.unit_id,
- "unitName": unit_name,
- "price": float(item.price) if item.price else 0,
- "imageUrl": product.image_url,
- "description": product.description
- })
- # 分页
- total = len(result)
- start = (page - 1) * pageSize
- end = start + pageSize
- paginated = result[start:end]
- return success_response({
- "list": paginated,
- "total": total,
- "page": page,
- "pageSize": pageSize,
- "totalPages": (total + pageSize - 1) // pageSize
- })
- # ============ 订单管理 - 客户端 ============
- # 订单状态映射
- CLIENT_ORDER_STATUS_MAP = {
- "pending_payment": "待支付",
- "pending_review_cancel": "待审核取消",
- "pending_dispatch": "待分配配送",
- "delivering": "配送中",
- "delivered": "配送完成",
- "pending_confirm": "待确认收货",
- "completed": "已完结",
- "cancelled": "已取消"
- }
- @router.post("/orders/preview", response_model=dict)
- def preview_order(
- request: dict,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """订单预览"""
- from app.crud.order import order_crud
- from app.crud.price_list import price_list_crud, price_list_item_crud
- from app.crud.product import product_crud
- from app.crud.unit import unit_crud
- from decimal import Decimal
- payload = _resolve_request_payload(request, current_user)
- price_list_id = payload.get("priceListId")
- items = payload.get("items", [])
- address_id = payload.get("addressId")
- if not price_list_id:
- return error_response(400, "请选择价格清单")
- if not items:
- return error_response(400, "请选择商品")
- if not address_id:
- return error_response(400, "请选择配送地址")
- # 获取价格清单
- price_list = price_list_crud.get(db, price_list_id)
- if not price_list:
- return error_response(404, "价格清单不存在")
- # 获取地址信息
- address = address_crud.get(db, address_id)
- if not address or address.user_id != current_user.get("user_id"):
- return error_response(404, "地址不存在")
- # 获取价格清单项
- price_items = price_list_item_crud.get_by_price_list(db, price_list_id)
- price_map = {item.product_id: item for item in price_items}
- # 计算金额(使用Decimal确保精度)
- preview_items = []
- total_amount = Decimal("0")
- for item in items:
- product_id = item.get("productId")
- quantity = item.get("quantity", 0)
- price_item = price_map.get(product_id)
- if not price_item:
- return error_response(400, f"商品 {product_id} 不在价格清单中")
- product = product_crud.get(db, product_id)
- unit = unit_crud.get(db, price_item.unit_id) if price_item.unit_id else None
- # 使用Decimal进行精确计算
- item_amount = Decimal(str(quantity)) * Decimal(str(price_item.price))
- total_amount += item_amount
- preview_items.append({
- "productId": product_id,
- "productName": product.name if product else None,
- "unitName": unit.name if unit else None,
- "unitPrice": float(price_item.price),
- "quantity": quantity,
- "amount": float(item_amount)
- })
- # 拼接地址
- full_address = f"{address.province or ''}{address.city or ''}{address.district or ''}{address.detail_address or ''}"
- return success_response({
- "priceListId": price_list_id,
- "priceVersion": price_list.version,
- "items": preview_items,
- "totalAmount": float(total_amount),
- "address": {
- "contactPerson": address.contact_person,
- "contactPhone": address.contact_phone,
- "fullAddress": full_address
- }
- })
- @router.post("/orders", response_model=dict)
- def create_order(
- request: dict,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """创建订单"""
- from app.crud.order import order_crud
- from app.crud.price_list import price_list_crud, price_list_item_crud
- payload = _resolve_request_payload(request, current_user)
- price_list_id = payload.get("priceListId")
- items = payload.get("items", [])
- address_id = payload.get("addressId")
- payment_method_raw = payload.get("paymentMethod", "online")
- payment_method = order_crud.normalize_payment_method(payment_method_raw)
- if payment_method not in ["online", "offline"]:
- return error_response(400, "支付方式仅支持线上或线下")
- remark = payload.get("remark")
- if not price_list_id:
- return error_response(400, "请选择价格清单")
- if not items:
- return error_response(400, "请选择商品")
- if not address_id:
- return error_response(400, "请选择配送地址")
- # 获取用户信息
- user = user_crud.get(db, current_user.get("user_id"))
- if not user:
- return error_response(404, "用户不存在")
- if not user.region_id:
- return error_response(400, "用户未绑定区域")
- # 获取价格清单
- price_list = price_list_crud.get(db, price_list_id)
- if not price_list:
- return error_response(404, "价格清单不存在")
- # 获取地址信息
- address = address_crud.get(db, address_id)
- if not address or address.user_id != current_user.get("user_id"):
- return error_response(404, "地址不存在")
- # 获取价格清单项
- price_items = price_list_item_crud.get_by_price_list(db, price_list_id)
- price_map = {item.product_id: item for item in price_items}
- # 验证商品并整理订单项
- order_items = []
- for item in items:
- product_id = item.get("productId")
- quantity = item.get("quantity", 0)
- unit_id = item.get("unitId")
- price_item = price_map.get(product_id)
- if not price_item:
- return error_response(400, f"商品不在价格清单中")
- order_items.append({
- "productId": product_id,
- "unitId": unit_id or price_item.unit_id,
- "quantity": quantity,
- "unitPrice": float(price_item.price)
- })
- # 拼接地址
- full_address = f"{address.province or ''}{address.city or ''}{address.district or ''}{address.detail_address or ''}"
- creator = {
- "user_id": current_user.get("user_id"),
- "real_name": current_user.get("real_name")
- }
- order = order_crud.create_order(
- db,
- client_id=current_user.get("user_id"),
- region_id=user.region_id,
- price_list_id=price_list_id,
- price_version=price_list.version,
- items=order_items,
- address_id=address_id,
- address_info={
- "contactPerson": address.contact_person,
- "contactPhone": address.contact_phone,
- "fullAddress": full_address
- },
- payment_method=payment_method,
- remark=remark,
- creator=creator
- )
- order_status = getattr(order, "order_status", None) or "pending_confirm"
- delivery_status = getattr(order, "delivery_status", None) or "pending_dispatch"
- pay_status = getattr(order, "pay_status", None) or "pending_payment"
- return success_response({
- "orderId": order.id,
- "orderNo": order.order_no,
- "totalAmount": float(order.total_amount),
- "status": order.status,
- "orderStatus": order_status,
- "deliveryStatus": delivery_status,
- "payStatus": pay_status,
- "paymentMethod": order_crud.normalize_payment_method(order.payment_method),
- })
- @router.get("/orders", response_model=dict)
- def list_client_orders(
- status: Optional[str] = Query(None, description="订单状态(兼容旧参数;与 listTab 同时传时以 listTab 为准)"),
- orderStatus: Optional[str] = Query(None, description="订单主状态:pending_payment/pending_confirm/completed/cancelled"),
- listTab: Optional[str] = Query(
- None,
- description="列表分段:all / pending_payment / pending_confirm / completed / cancelled,用于 Tab 独立分页",
- ),
- page: int = Query(1, ge=1, description="页码"),
- pageSize: int = Query(10, ge=1, le=100, description="每页条数"),
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """获取客户订单列表"""
- from app.crud.order import order_crud, order_item_crud
- segment = (listTab or "all").strip().lower()
- if segment not in ("all", "pending_payment", "pending_confirm", "completed", "cancelled"):
- segment = "all"
- use_status = status if segment == "all" else None
- use_list_tab = segment if segment != "all" else None
- use_order_status = (orderStatus or "").strip() if segment == "all" else None
- orders, total = order_crud.get_multi_with_filters(
- db,
- client_id=current_user.get("user_id"),
- status=use_status,
- order_status=use_order_status or None,
- list_tab=use_list_tab,
- skip=(page - 1) * pageSize,
- limit=pageSize
- )
- order_ids = [o.id for o in orders]
- # 批量获取订单项(用于订单列表中展示商品列表)
- items_by_order_id: dict[str, list[BizOrderItem]] = {}
- product_map: dict[str, BasProduct] = {}
- if order_ids:
- all_items: list[BizOrderItem] = db.query(BizOrderItem).filter(
- BizOrderItem.is_del == 0,
- BizOrderItem.order_id.in_(order_ids)
- ).all()
- for it in all_items:
- items_by_order_id.setdefault(it.order_id, []).append(it)
- product_ids = list({it.product_id for it in all_items if it.product_id})
- if product_ids:
- products: list[BasProduct] = db.query(BasProduct).filter(
- BasProduct.is_del == 0,
- BasProduct.id.in_(product_ids)
- ).all()
- product_map = {p.id: p for p in products}
- result = []
- for order in orders:
- order_status = getattr(order, "order_status", None) or "pending_confirm"
- delivery_status = getattr(order, "delivery_status", None) or "pending_dispatch"
- pay_status = getattr(order, "pay_status", None) or "pending_payment"
- order_items = items_by_order_id.get(order.id, [])
- item_count = len(order_items) if order_items else order_item_crud.get_count_by_order(db, order.id)
- total_quantity = float(sum([(it.quantity or 0) for it in order_items])) if order_items else None
- item_details = []
- for it in order_items:
- p = product_map.get(it.product_id)
- item_details.append({
- "id": it.id,
- "productId": it.product_id,
- "productName": (p.name if p else None),
- "quantity": float(it.quantity) if it.quantity else 0,
- "imageUrl": (p.image_url if p else None),
- })
- result.append({
- "id": order.id,
- "orderNo": order.order_no,
- "totalAmount": float(order.total_amount) if order.total_amount else 0,
- "paidAmount": float(order.paid_amount) if order.paid_amount else 0,
- "status": order.status,
- "orderStatus": order_status,
- "deliveryStatus": delivery_status,
- "payStatus": pay_status,
- "paymentMethod": order_crud.normalize_payment_method(order.payment_method),
- "clientSettlementStatus": getattr(order, "client_settlement_status", None) or "pending",
- "itemCount": item_count,
- "totalQuantity": total_quantity if total_quantity is not None else item_count,
- "items": item_details,
- "createAt": str(order.create_at) if order.create_at else None
- })
- return success_response({
- "list": result,
- "total": total,
- "page": page,
- "pageSize": pageSize,
- "totalPages": (total + pageSize - 1) // pageSize
- })
- @router.get("/orders/{order_id}", response_model=dict)
- def get_client_order(
- order_id: str,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """获取客户订单详情"""
- from app.crud.order import order_crud, order_item_crud, order_status_log_crud
- from app.crud.product import product_crud
- from app.crud.unit import unit_crud
- from app.crud.deliverer_review import deliverer_review_crud
- order = order_crud.get(db, order_id)
- if not order:
- return error_response(404, "订单不存在")
- if order.client_id != current_user.get("user_id"):
- return error_response(403, "无权查看此订单")
- # 获取订单项
- items = order_item_crud.get_by_order(db, order_id)
- # 配送信息与评价信息
- from app.crud.deliverer_review import deliverer_review_crud
- delivery_task = db.query(BizDeliveryTask).filter(
- BizDeliveryTask.order_id == order_id,
- BizDeliveryTask.is_del == 0
- ).first()
- deliverer_name = None
- deliverer_phone = None
- if delivery_task and delivery_task.deliver_id:
- du = db.query(SysUser).filter(
- SysUser.id == delivery_task.deliver_id,
- SysUser.is_del == 0,
- ).first()
- if du:
- deliverer_name = du.real_name or du.username
- deliverer_phone = _deliverer_contact_phone(du)
- existing_review = deliverer_review_crud.get_by_order(db, order_id)
- # 获取状态日志
- status_logs = order_status_log_crud.get_by_order(db, order_id)
- # 获取区域名称
- region_name = None
- if order.region_id:
- region = region_crud.get(db, order.region_id)
- region_name = region.name if region else None
- # 处理订单项
- item_details = []
- for item in items:
- product = product_crud.get(db, item.product_id)
- unit = unit_crud.get(db, item.unit_id) if item.unit_id else None
- item_details.append({
- "id": item.id,
- "productId": item.product_id,
- "productName": product.name if product else None,
- "unitId": item.unit_id,
- "unitName": unit.name if unit else None,
- "measureValue": float(product.measure_value) if (product and getattr(product, "measure_value", None) is not None) else None,
- "quantity": float(item.quantity) if item.quantity else 0,
- "unitPrice": float(item.unit_price) if item.unit_price else 0,
- "amount": float(item.amount) if item.amount else 0,
- "deliveredQuantity": float(item.delivered_quantity) if item.delivered_quantity else 0,
- "status": item.status,
- "imageUrl": product.image_url if product else None,
- })
- # 处理状态日志
- log_details = []
- for log in status_logs:
- log_details.append({
- "status": log.to_status,
- "statusText": CLIENT_ORDER_STATUS_MAP.get(log.to_status, log.to_status),
- # BizOrderStatusLog 无 operator_name 字段;优先用 create_name(通用审计字段)
- "operatorName": getattr(log, "create_name", None) or "",
- "operatorRole": log.operator_role,
- "reason": log.reason,
- "createAt": str(log.create_at) if log.create_at else None
- })
- from app.core.order_status_compat import derive_three_status_from_legacy
- from app.models.models import BizWechatPay
- ts = derive_three_status_from_legacy(order.status, paid_amount=order.paid_amount)
- order_status = getattr(order, "order_status", None) or ts.order_status
- delivery_status = getattr(order, "delivery_status", None) or ts.delivery_status
- pay_status = getattr(order, "pay_status", None) or ts.pay_status
- wp = (
- db.query(BizWechatPay)
- .filter(BizWechatPay.order_id == order_id, BizWechatPay.is_del == 0)
- .order_by(BizWechatPay.create_at.desc())
- .first()
- )
- wechat_pay_payload = None
- if wp:
- wechat_pay_payload = {
- "transactionId": ((wp.transaction_id or "").strip() or None),
- "outTradeNo": ((wp.out_trade_no or "").strip() or None),
- "payTime": str(wp.pay_time) if wp.pay_time else None,
- }
- return success_response({
- "id": order.id,
- "orderNo": order.order_no,
- "regionId": order.region_id,
- "regionName": region_name,
- "priceListId": order.price_list_id,
- "priceVersion": order.price_version,
- "status": order.status,
- "orderStatus": order_status,
- "deliveryStatus": delivery_status,
- "payStatus": pay_status,
- "paymentMethod": order_crud.normalize_payment_method(order.payment_method),
- "clientSettlementStatus": getattr(order, "client_settlement_status", None) or "pending",
- "clientSettlementAt": str(order.client_settlement_at) if getattr(order, "client_settlement_at", None) else None,
- "wechatPay": wechat_pay_payload,
- "totalAmount": float(order.total_amount) if order.total_amount else 0,
- "paidAmount": float(order.paid_amount) if order.paid_amount else 0,
- "receivableAmount": float(order.receivable_amount) if order.receivable_amount else 0,
- "refundAmount": float(order.refund_amount) if order.refund_amount else 0,
- "contactPerson": order.contact_person,
- "contactPhone": order.contact_phone,
- "deliveryAddress": order.delivery_address,
- "remark": order.remark,
- "cancelReason": order.cancel_reason,
- "createAt": str(order.create_at) if order.create_at else None,
- "paymentTime": str(order.payment_time) if order.payment_time else None,
- "deliveryAssignTime": str(getattr(order, "delivery_assign_time", None)) if getattr(order, "delivery_assign_time", None) else None,
- "deliveredTime": str(getattr(order, "delivered_time", None)) if getattr(order, "delivered_time", None) else None,
- "delivererId": delivery_task.deliver_id if delivery_task else None,
- "delivererName": deliverer_name,
- "delivererPhone": deliverer_phone,
- "canReviewDeliverer": order.status == "completed" and delivery_task is not None and not existing_review,
- "delivererReview": {
- "id": existing_review.id,
- "score": existing_review.score,
- "tags": (existing_review.tags or "").split(",") if existing_review and existing_review.tags else [],
- "content": existing_review.content,
- "createAt": str(existing_review.create_at) if existing_review and existing_review.create_at else None
- } if existing_review else None,
- "items": item_details,
- "statusLogs": log_details
- })
- @router.post("/orders/{order_id}/deliverer-review", response_model=dict)
- def review_deliverer(
- order_id: str,
- request: dict,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """客户评价配送员"""
- from app.crud.order import order_crud
- from app.crud.deliverer_review import deliverer_review_crud
- order = order_crud.get(db, order_id)
- if not order:
- return error_response(404, "订单不存在")
- if order.client_id != current_user.get("user_id"):
- return error_response(403, "无权操作此订单")
- if order.status != "completed":
- return error_response(400, "仅已完成订单可评价")
- task = db.query(BizDeliveryTask).filter(
- BizDeliveryTask.order_id == order_id,
- BizDeliveryTask.is_del == 0
- ).first()
- if not task or not task.deliver_id:
- return error_response(400, "未找到对应配送员")
- exists = deliverer_review_crud.get_by_order(db, order_id)
- if exists:
- return error_response(400, "该订单已评价")
- payload = _resolve_request_payload(request, current_user)
- score = int(payload.get("score", 0))
- if score < 1 or score > 5:
- return error_response(400, "评分必须在1-5之间")
- tags = payload.get("tags") or []
- content = payload.get("content")
- is_anonymous = bool(payload.get("isAnonymous", False))
- creator = {
- "user_id": current_user.get("user_id"),
- "real_name": current_user.get("real_name")
- }
- review = deliverer_review_crud.create_review(
- db=db,
- order_id=order_id,
- task_id=task.id,
- client_id=current_user.get("user_id"),
- deliverer_id=task.deliver_id,
- score=score,
- tags=tags,
- content=content,
- is_anonymous=is_anonymous,
- creator=creator
- )
- return success_response({
- "id": review.id,
- "score": review.score,
- "message": "评价成功"
- })
- @router.post("/orders/{order_id}/cancel", response_model=dict)
- def client_cancel_order(
- order_id: str,
- request: dict,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """客户发起取消申请 — 产品策略:客户端不可自助取消,请联系客服。"""
- return error_response(403, "订单取消请联系客服人员处理")
- @router.post("/orders/{order_id}/direct-cancel", response_model=dict)
- def client_direct_cancel_order(
- order_id: str,
- request: Optional[dict] = None,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """客户直接取消未支付订单 — 产品策略:客户端不可自助取消,请联系客服或等待超时自动关闭。"""
- return error_response(403, "订单取消请联系客服人员处理")
- @router.post("/orders/{order_id}/confirm-receive", response_model=dict)
- def client_confirm_receive(
- order_id: str,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """客户确认收货"""
- from app.crud.order import order_crud
- order = order_crud.get(db, order_id)
- if not order:
- return error_response(404, "订单不存在")
- if order.client_id != current_user.get("user_id"):
- return error_response(403, "无权操作此订单")
- order = order_crud.confirm_receive(
- db,
- order_id=order_id,
- operator_id=current_user.get("user_id")
- )
- if not order:
- return error_response(400, "当前状态不支持确认收货")
- return success_response({
- "id": order.id,
- "status": order.status,
- "orderStatus": getattr(order, "order_status", None) or "pending_confirm",
- "deliveryStatus": getattr(order, "delivery_status", None) or "pending_dispatch",
- "payStatus": getattr(order, "pay_status", None) or "pending_payment",
- "message": "确认收货成功"
- })
- @router.get("/orders/{order_id}/delivery-progress", response_model=dict)
- def get_delivery_progress(
- order_id: str,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """查看配送进度"""
- from app.crud.order import order_crud, order_item_crud, order_status_log_crud
- from app.crud.product import product_crud
- _dt_title = {
- "dt_supplier_stocking": "供应商开始备货",
- "dt_supplier_ready": "供应商备货完成(待配送员取货)",
- "dt_picking": "配送员取货中",
- "dt_delivering": "配送员配送中",
- "dt_completed": "配送任务已完成",
- "dt_assigned": "配送任务已分配",
- "dt_pending": "配送任务待分配",
- "dt_cancelled": "配送任务已取消",
- }
- order = order_crud.get(db, order_id)
- if not order:
- return error_response(404, "订单不存在")
- if order.client_id != current_user.get("user_id"):
- return error_response(403, "无权查看此订单")
- # 获取订单项
- items = order_item_crud.get_by_order(db, order_id)
- item_details = []
- for item in items:
- product = product_crud.get(db, item.product_id)
- item_details.append({
- "productId": item.product_id,
- "productName": product.name if product else None,
- "quantity": float(item.quantity) if item.quantity else 0,
- "deliveredQuantity": float(item.delivered_quantity) if item.delivered_quantity else 0,
- "status": item.status
- })
- status_logs = order_status_log_crud.get_by_order(db, order_id)
- task_events = []
- for log in status_logs:
- ts = log.to_status or ""
- if not str(ts).startswith("dt_"):
- continue
- task_events.append({
- "title": _dt_title.get(ts, ts),
- "remark": log.reason,
- "operatorRole": log.operator_role,
- "createAt": str(log.create_at) if log.create_at else None,
- })
- task_events.sort(key=lambda x: x.get("createAt") or "")
- return success_response({
- "orderId": order.id,
- "orderNo": order.order_no,
- "status": order.status,
- "orderStatus": getattr(order, "order_status", None),
- "deliveryStatus": getattr(order, "delivery_status", None),
- "payStatus": getattr(order, "pay_status", None),
- "items": item_details,
- "deliveryTaskEvents": task_events,
- })
- @router.post("/orders/{order_id}/urge", response_model=dict)
- def client_urge_order(
- order_id: str,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """客户催单"""
- from app.crud.order import order_crud
- from app.models.models import BizDeliveryTask
- from datetime import datetime, timedelta
- order = order_crud.get(db, order_id)
- if not order:
- return error_response(404, "订单不存在")
- if order.client_id != current_user.get("user_id"):
- return error_response(403, "无权操作此订单")
- # 检查订单状态,只有配送中的订单才能催单
- if getattr(order, "delivery_status", None) != "delivering":
- return error_response(400, "当前状态不支持催单")
- # 获取今天已催单次数(从订单备注中统计)
- today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
- tomorrow = today + timedelta(days=1)
- # 查询今天的催单次数
- urge_count = 1 # 默认允许催单
- last_urge_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- # 获取客户信息
- client = user_crud.get(db, current_user.get("user_id"))
- client_name = client.real_name if client else "未知客户"
- # 获取配送员信息
- deliver_name = None
- deliver_phone = None
- task = db.query(BizDeliveryTask).filter(
- BizDeliveryTask.order_id == order_id,
- BizDeliveryTask.is_del == 0
- ).first()
- if task and task.deliver_id:
- deliver = user_crud.get(db, task.deliver_id)
- if deliver:
- deliver_name = deliver.real_name
- deliver_phone = deliver.phone
- # 发送消息通知配送员
- try:
- from app.api.message import message_crud
- from app.core.utils import generate_id
- from app.models.models import BizMessage
- # 构造催单内容
- urge_content = f"【催单提醒】客户 {client_name} 催单,订单号:{order.order_no},联系电话:{client.phone or '无'}。请尽快处理!"
- # 发送给配送员
- if task and task.deliver_id:
- message_data = {
- "id": generate_id(),
- "receiver_id": task.deliver_id,
- "receiver_name": deliver_name,
- "message_type": "delivery",
- "title": f"【催单提醒】订单 {order.order_no}",
- "content": urge_content,
- "is_read": 0,
- "create_by": current_user.get("user_id"),
- "create_name": client_name
- }
- message_obj = BizMessage(**message_data)
- db.add(message_obj)
- # 同时通知运营人员(查找所有admin角色用户)
- admin_users = db.query(SysUser).filter(
- SysUser.role == "admin",
- SysUser.is_active == True,
- SysUser.is_del == 0
- ).all()
- for admin in admin_users:
- admin_message_data = {
- "id": generate_id(),
- "receiver_id": admin.id,
- "receiver_name": admin.real_name,
- "message_type": "order",
- "title": f"【客户催单】订单 {order.order_no}",
- "content": urge_content,
- "is_read": 0,
- "create_by": current_user.get("user_id"),
- "create_name": client_name
- }
- admin_message = BizMessage(**admin_message_data)
- db.add(admin_message)
- db.commit()
- except Exception as e:
- # 消息发送失败不影响催单主流程,只记录日志
- import logging
- logging.warning(f"发送催单消息失败: {str(e)}")
- return success_response({
- "orderId": order.id,
- "urgeCount": urge_count,
- "lastUrgeTime": last_urge_time,
- "message": "催单成功,配送员将尽快处理"
- })
- # ============ 结算与退款 - 客户端 ============
- @router.get("/settlements", response_model=dict)
- def list_client_settlements(
- page: int = Query(1, ge=1, description="页码"),
- pageSize: int = Query(10, ge=1, le=100, description="每页条数"),
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """获取我的结算记录列表"""
- from app.crud.settlement import settlement_crud
- from app.models.models import BizOrder
- client_id = current_user.get("user_id")
- # 获取该客户的所有结算记录
- settlements, total = settlement_crud.get_multi_with_filters(
- db,
- client_id=client_id,
- status="confirmed",
- skip=(page - 1) * pageSize,
- limit=pageSize
- )
- result = []
- for settlement in settlements:
- order = db.query(BizOrder).filter(BizOrder.id == settlement.order_id).first()
- result.append({
- "id": settlement.id,
- "orderId": settlement.order_id,
- "orderNo": order.order_no if order else None,
- "amount": float(settlement.amount) if settlement.amount else 0,
- "settlementMethod": settlement.settlement_method,
- "settlementTime": str(settlement.settlement_time) if settlement.settlement_time else None,
- "settlementNote": settlement.settlement_note,
- "createAt": str(settlement.create_at) if settlement.create_at else None
- })
- return success_response({
- "list": result,
- "total": total,
- "page": page,
- "pageSize": pageSize,
- "totalPages": (total + pageSize - 1) // pageSize
- })
- @router.get("/settlements/{settlement_id}", response_model=dict)
- def get_client_settlement(
- settlement_id: str,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """获取结算记录详情"""
- from app.crud.settlement import settlement_crud
- from app.models.models import BizOrder
- client_id = current_user.get("user_id")
- settlement = settlement_crud.get(db, settlement_id)
- if not settlement:
- return error_response(404, "结算记录不存在")
- if settlement.client_id != client_id:
- return error_response(403, "无权查看此记录")
- order = db.query(BizOrder).filter(BizOrder.id == settlement.order_id).first()
- return success_response({
- "id": settlement.id,
- "orderId": settlement.order_id,
- "orderNo": order.order_no if order else None,
- "amount": float(settlement.amount) if settlement.amount else 0,
- "settlementMethod": settlement.settlement_method,
- "settlementTime": str(settlement.settlement_time) if settlement.settlement_time else None,
- "settlementNote": settlement.settlement_note,
- "settlementFileUrl": settlement.settlement_file_url,
- "reviewedBy": settlement.reviewed_by,
- "reviewedTime": str(settlement.reviewed_time) if settlement.reviewed_time else None,
- "createAt": str(settlement.create_at) if settlement.create_at else None
- })
- @router.get("/settlements/statistics", response_model=dict)
- def get_client_settlement_statistics(
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """获取我的结算统计"""
- from app.crud.settlement import settlement_crud
- from app.models.models import BizOrder, BizSettlement
- from sqlalchemy import func
- client_id = current_user.get("user_id")
- # 获取已结算总额
- result = db.query(
- func.sum(BizSettlement.amount)
- ).filter(
- BizSettlement.client_id == client_id,
- BizSettlement.status == "confirmed",
- BizSettlement.is_del == 0
- ).scalar() or 0
- return success_response({
- "totalSettled": float(result)
- })
- @router.get("/refunds", response_model=dict)
- def list_client_refunds(
- page: int = Query(1, ge=1, description="页码"),
- pageSize: int = Query(10, ge=1, le=100, description="每页条数"),
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """获取我的退款记录列表"""
- from app.crud.settlement import refund_crud
- from app.models.models import BizOrder
- client_id = current_user.get("user_id")
- # 获取该客户的所有退款记录(关联订单)
- refunds, total = refund_crud.get_multi_with_filters(
- db,
- skip=(page - 1) * pageSize,
- limit=pageSize
- )
- result = []
- for refund in refunds:
- order = db.query(BizOrder).filter(BizOrder.id == refund.order_id).first()
- if order and order.client_id == client_id:
- result.append({
- "id": refund.id,
- "orderId": refund.order_id,
- "orderNo": order.order_no if order else None,
- "refundNo": refund.refund_no,
- "refundAmount": float(refund.refund_amount) if refund.refund_amount else 0,
- "refundMethod": refund.refund_method,
- "refundResult": refund.refund_result,
- "status": refund.status,
- "refundTime": str(refund.refund_time) if refund.refund_time else None,
- "refundFailReason": refund.refund_fail_reason,
- "createAt": str(refund.create_at) if refund.create_at else None
- })
- return success_response({
- "list": result,
- "total": len(result),
- "page": page,
- "pageSize": pageSize,
- "totalPages": (len(result) + pageSize - 1) // pageSize
- })
- @router.get("/refunds/{refund_id}", response_model=dict)
- def get_client_refund(
- refund_id: str,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """获取退款详情"""
- from app.crud.settlement import refund_crud
- from app.models.models import BizOrder
- client_id = current_user.get("user_id")
- refund = refund_crud.get(db, refund_id)
- if not refund:
- return error_response(404, "退款记录不存在")
- order = db.query(BizOrder).filter(BizOrder.id == refund.order_id).first()
- if order and order.client_id != client_id:
- return error_response(403, "无权查看此记录")
- return success_response({
- "id": refund.id,
- "orderId": refund.order_id,
- "orderNo": order.order_no if order else None,
- "refundNo": refund.refund_no,
- "refundAmount": float(refund.refund_amount) if refund.refund_amount else 0,
- "refundMethod": refund.refund_method,
- "refundResult": refund.refund_result,
- "refundTime": str(refund.refund_time) if refund.refund_time else None,
- "refundNoWechat": refund.refund_no_wechat,
- "refundFailReason": refund.refund_fail_reason,
- "status": refund.status,
- "createAt": str(refund.create_at) if refund.create_at else None,
- "createName": refund.create_name
- })
- @router.post("/refunds/apply/{order_id}", response_model=dict)
- def apply_client_refund(
- order_id: str,
- request: dict,
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """申请退款(线下冲销)"""
- from app.crud.settlement import refund_crud
- from app.models.models import BizOrder
- from decimal import Decimal
- client_id = current_user.get("user_id")
- order = db.query(BizOrder).filter(BizOrder.id == order_id).first()
- if not order:
- return error_response(404, "订单不存在")
- if order.client_id != client_id:
- return error_response(403, "无权操作此订单")
- payload = _resolve_request_payload(request, current_user)
- refund_amount = payload.get("refundAmount")
- reason = payload.get("reason")
- if not refund_amount or refund_amount <= 0:
- return error_response(400, "退款金额必须大于0")
- # 检查已支付金额
- paid_amount = float(order.paid_amount) if order.paid_amount else 0
- if refund_amount > paid_amount:
- return error_response(400, "退款金额不能超过已支付金额")
- # 检查是否有处理中的退款
- existing_refunds = refund_crud.get_by_order(db, order_id)
- processing_refund = any(r.status in ["pending", "processing"] for r in existing_refunds)
- if processing_refund:
- return error_response(400, "该订单有退款正在处理中,请勿重复提交")
- creator = {
- "user_id": current_user.get("user_id"),
- "real_name": current_user.get("real_name")
- }
- refund = refund_crud.create_refund(
- db,
- order_id=order_id,
- refund_amount=Decimal(str(refund_amount)),
- refund_method="offline",
- reason=reason,
- creator=creator
- )
- return success_response({
- "refundId": refund.id,
- "refundNo": refund.refund_no,
- "refundAmount": float(refund.refund_amount),
- "status": refund.status,
- "message": "退款申请已提交"
- })
- @router.get("/refunds/statistics", response_model=dict)
- def get_client_refund_statistics(
- db: Session = Depends(get_db),
- current_user: dict = Depends(require_client)
- ):
- """获取我的退款统计"""
- from app.crud.settlement import refund_crud
- from app.models.models import BizOrder
- from sqlalchemy import func
- client_id = current_user.get("user_id")
- # 获取所有退款记录
- refunds = refund_crud.get_multi_with_filters(db, limit=1000)[0]
- # 筛选属于该客户的
- client_refunds = []
- for refund in refunds:
- order = db.query(BizOrder).filter(BizOrder.id == refund.order_id).first()
- if order and order.client_id == client_id:
- client_refunds.append(refund)
- total_refunds = len(client_refunds)
- success_refunds = sum(1 for r in client_refunds if r.status == "completed")
- pending_refunds = sum(1 for r in client_refunds if r.status in ["pending", "processing"])
- failed_refunds = sum(1 for r in client_refunds if r.status == "failed")
- success_amount = sum(
- float(r.refund_amount) for r in client_refunds
- if r.status == "completed" and r.refund_amount
- )
- return success_response({
- "totalRefunds": total_refunds,
- "successRefunds": success_refunds,
- "pendingRefunds": pending_refunds,
- "failedRefunds": failed_refunds,
- "totalRefunded": success_amount
- })
|