"""顺丰月结列表、结算、补填。只处理 transport_mode = sfkd。""" from __future__ import annotations from datetime import datetime from typing import Any, Dict, List, Optional, Tuple from sqlalchemy.orm import Session from app.core.data_scope import apply_data_scope from app.crud.sample_freight import _fmt_dt, _normalize_date_bound, _user_nick_name_map from app.models.models import BizSampleFreight from app.services.sf_express_settlement import ( SF_TRANSPORT_MODE, SETTLE_SETTLED, assert_settle_request, freight_amount_label, plan_fill_amount, plan_settle, ) def _base_query(db: Session, *, tenant_no: str, user_info: Optional[dict]): query = db.query(BizSampleFreight).filter( BizSampleFreight.tenant_no == tenant_no, BizSampleFreight.del_flag == 0, BizSampleFreight.transport_mode == SF_TRANSPORT_MODE, ) return apply_data_scope(query, BizSampleFreight, user_info, db, tenant_no) def _apply_filters( query, *, waybill_no: Optional[str], settle_status: Optional[str], create_time_from: Optional[str], create_time_to: Optional[str], ): waybill = (waybill_no or "").strip() if waybill: query = query.filter(BizSampleFreight.waybill_no.like(f"%{waybill}%")) status = (settle_status or "").strip() if status: query = query.filter(BizSampleFreight.settle_status == status) if create_time_from and str(create_time_from).strip(): query = query.filter( BizSampleFreight.create_time >= _normalize_date_bound(str(create_time_from).strip()) ) if create_time_to and str(create_time_to).strip(): query = query.filter( BizSampleFreight.create_time <= _normalize_date_bound(str(create_time_to).strip(), end_of_day=True) ) return query def _to_row(obj: BizSampleFreight, create_by_name: str) -> Dict[str, Any]: amount = obj.freight_amount status = obj.settle_status or "" return { "id": obj.id, "waybillNo": obj.waybill_no or "", "createBy": obj.create_by or "", "createByName": create_by_name or obj.create_by or "", "createTime": _fmt_dt(obj.create_time), "settleStatus": status, "settleStatusLabel": "已结算" if status == SETTLE_SETTLED else "未结算", "freightAmount": float(amount) if amount is not None else None, "freightAmountLabel": freight_amount_label(obj.transport_mode, amount), "settleTime": _fmt_dt(obj.settle_time) if obj.settle_time else "", "transportMode": obj.transport_mode or "", } def list_sf_settlements( db, *, tenant_no: str, user_info: Optional[dict], page: int = 1, page_size: int = 10, waybill_no: Optional[str] = None, settle_status: Optional[str] = None, create_time_from: Optional[str] = None, create_time_to: Optional[str] = None, ) -> Tuple[List[Dict[str, Any]], int]: page = max(1, int(page or 1)) page_size = max(1, min(100, int(page_size or 10))) query = _apply_filters( _base_query(db, tenant_no=tenant_no, user_info=user_info), waybill_no=waybill_no, settle_status=settle_status, create_time_from=create_time_from, create_time_to=create_time_to, ) total = query.count() rows = ( query.order_by(BizSampleFreight.create_time.desc(), BizSampleFreight.id.desc()) .offset((page - 1) * page_size) .limit(page_size) .all() ) names = _user_nick_name_map(db, [row.create_by for row in rows if row.create_by]) return [_to_row(row, names.get(row.create_by or "", "")) for row in rows], total def list_sf_settlements_for_export( db, *, tenant_no: str, user_info: Optional[dict], waybill_no: Optional[str] = None, settle_status: Optional[str] = None, create_time_from: Optional[str] = None, create_time_to: Optional[str] = None, ) -> List[Dict[str, Any]]: query = _apply_filters( _base_query(db, tenant_no=tenant_no, user_info=user_info), waybill_no=waybill_no, settle_status=settle_status, create_time_from=create_time_from, create_time_to=create_time_to, ) rows = query.order_by( BizSampleFreight.create_time.desc(), BizSampleFreight.id.desc() ).all() if not rows: raise ValueError("没有可导出的数据") names = _user_nick_name_map(db, [row.create_by for row in rows if row.create_by]) return [_to_row(row, names.get(row.create_by or "", "")) for row in rows] def _lock_rows( db, *, tenant_no: str, user_info: Optional[dict], ids: List[str] ) -> List[BizSampleFreight]: wanted = [str(item or "").strip() for item in ids if str(item or "").strip()] rows = ( _base_query(db, tenant_no=tenant_no, user_info=user_info) .filter(BizSampleFreight.id.in_(wanted)) .all() ) if len(rows) != len(set(wanted)): raise ValueError("运费批次不存在") return rows def settle_sf_freights( db, *, tenant_no: str, user_id: Optional[str], user_info: Optional[dict], ids: List[str], freight_amount: Any = None, ): assert_settle_request(ids, freight_amount) rows = _lock_rows(db, tenant_no=tenant_no, user_info=user_info, ids=ids) now = datetime.now() submitted = None if len(ids) != 1 else freight_amount for obj in rows: if obj.transport_mode != SF_TRANSPORT_MODE: raise ValueError("只能结算顺丰快递运费") if (obj.settle_status or "") == SETTLE_SETTLED: raise ValueError("该运费已结算") amount, status, settle_time = plan_settle( current_amount=obj.freight_amount, submitted_amount=submitted, now=now ) obj.freight_amount = amount obj.settle_status = status obj.settle_time = settle_time obj.update_by = user_id obj.update_time = now db.commit() return {"ids": [row.id for row in rows]} def fill_sf_freight_amount( db, *, tenant_no: str, user_id: Optional[str], user_info: Optional[dict], freight_id: str, freight_amount: Any, ): rows = _lock_rows(db, tenant_no=tenant_no, user_info=user_info, ids=[freight_id]) obj = rows[0] if obj.transport_mode != SF_TRANSPORT_MODE: raise ValueError("只能补填顺丰快递运费") obj.freight_amount = plan_fill_amount( current_amount=obj.freight_amount, settle_status=obj.settle_status, submitted_amount=freight_amount, ) obj.update_by = user_id obj.update_time = datetime.now() db.commit() return {"id": obj.id}