|
|
@@ -0,0 +1,199 @@
|
|
|
+"""顺丰月结列表、结算、补填。只处理 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}
|