Files
shanxia2.0/backend/app/services/pl_brand_archive.py
T
2026-08-27 19:38:01 +08:00

235 lines
7.0 KiB
Python

"""计划归档按品牌数量校验与持久化"""
from decimal import Decimal
from typing import Any, Optional
from sqlalchemy import or_
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.models.pl_models import PlPlanProductBrandQty
def _is_plan_level_row(row: PlPlanProductBrandQty) -> bool:
return not (getattr(row, "stop_product_id", None) or "").strip()
def _row_to_brand_dict(row: PlPlanProductBrandQty) -> dict[str, Any]:
data: dict[str, Any] = {
"brandId": row.brand_id or "",
"brandName": row.brand_name,
"qty": Decimal(str(row.qty or 0)),
}
if row.unit_price is not None:
data["unitPrice"] = Decimal(str(row.unit_price))
return data
def _parse_unit_price(item: dict[str, Any]) -> Optional[Decimal]:
raw = item.get("unitPrice")
if raw is None:
raw = item.get("unit_price")
if raw is None:
raw = item.get("price")
if raw is None or raw == "":
return None
return Decimal(str(raw))
def load_brand_qtys_by_plan_product_ids(
db: Session,
*,
tenant_no: str,
plan_product_ids: list[str],
) -> dict[str, list[dict[str, Any]]]:
"""按计划产品明细 ID 批量加载计划级归档品牌数量(brandId/brandName/qty/unitPrice)。"""
ids = [pid for pid in plan_product_ids if pid]
if not ids:
return {}
rows = (
db.query(PlPlanProductBrandQty)
.filter(
PlPlanProductBrandQty.plan_product_id.in_(ids),
PlPlanProductBrandQty.tenant_no == tenant_no,
PlPlanProductBrandQty.del_flag == 0,
or_(
PlPlanProductBrandQty.stop_product_id.is_(None),
PlPlanProductBrandQty.stop_product_id == "",
),
)
.order_by(PlPlanProductBrandQty.order_num.asc())
.all()
)
grouped: dict[str, list[dict[str, Any]]] = {}
for row in rows:
qty = Decimal(str(row.qty or 0))
if qty <= 0:
continue
grouped.setdefault(row.plan_product_id, []).append(_row_to_brand_dict(row))
return grouped
def load_brand_qtys_by_stop_product_ids(
db: Session,
*,
tenant_no: str,
stop_product_ids: list[str],
) -> dict[str, list[dict[str, Any]]]:
"""按装货/送货段产品行 ID 批量加载段级归档品牌数量。"""
ids = [pid for pid in stop_product_ids if pid]
if not ids:
return {}
rows = (
db.query(PlPlanProductBrandQty)
.filter(
PlPlanProductBrandQty.stop_product_id.in_(ids),
PlPlanProductBrandQty.tenant_no == tenant_no,
PlPlanProductBrandQty.del_flag == 0,
)
.order_by(PlPlanProductBrandQty.order_num.asc())
.all()
)
grouped: dict[str, list[dict[str, Any]]] = {}
for row in rows:
qty = Decimal(str(row.qty or 0))
if qty <= 0:
continue
stop_id = (row.stop_product_id or "").strip()
if not stop_id:
continue
grouped.setdefault(stop_id, []).append(_row_to_brand_dict(row))
return grouped
def validate_brand_qty_rows(
*,
brand_mode: Optional[str],
required_brand_id: Optional[str],
actual_qty: Decimal,
rows: list[dict],
) -> None:
actual_qty = Decimal(str(actual_qty))
if not brand_mode:
return
if brand_mode in ("required", "any"):
total = sum((Decimal(str(r.get("qty") or 0)) for r in rows), Decimal("0"))
if total != actual_qty:
raise ValueError("各品牌数量合计必须等于归档实际数量")
return
raise ValueError("无效的 brand_mode")
def persist_plan_product_brand_qtys(
db: Session,
*,
tenant_no: str,
plan_id: str,
plan_product_id: str,
brand_mode: Optional[str],
rows: list[dict[str, Any]],
user_id: str,
dept_id: str = "",
) -> None:
"""软删计划级旧品牌量行后按归档结果插入;无品牌约定则只软删不插入。不触碰段级行。"""
existing = (
db.query(PlPlanProductBrandQty)
.filter(
PlPlanProductBrandQty.plan_product_id == plan_product_id,
PlPlanProductBrandQty.tenant_no == tenant_no,
PlPlanProductBrandQty.del_flag == 0,
)
.all()
)
for row in existing:
if not _is_plan_level_row(row):
continue
row.del_flag = 1
row.update_by = user_id
if not brand_mode:
return
for idx, item in enumerate(rows):
qty = Decimal(str(item.get("qty") or 0))
if qty <= 0:
continue
brand_id = item.get("brandId") or item.get("brand_id") or ""
brand_name = item.get("brandName") or item.get("brand_name")
db.add(
PlPlanProductBrandQty(
id=generate_uuid(),
tenant_no=tenant_no,
plan_id=plan_id,
plan_product_id=plan_product_id,
stop_type=None,
stop_product_id=None,
brand_id=brand_id,
brand_name=brand_name,
qty=qty,
unit_price=_parse_unit_price(item),
order_num=idx,
create_by=user_id,
create_dept_id=dept_id or "",
update_by=user_id,
)
)
def persist_stop_product_brand_qtys(
db: Session,
*,
tenant_no: str,
plan_id: str,
plan_product_id: str,
stop_type: str,
stop_product_id: str,
brand_mode: Optional[str],
rows: list[dict[str, Any]],
user_id: str,
dept_id: str = "",
) -> None:
"""软删该段产品行旧品牌量后写入;无有效数量则只软删。"""
stop_id = (stop_product_id or "").strip()
if not stop_id:
return
existing = (
db.query(PlPlanProductBrandQty)
.filter(
PlPlanProductBrandQty.stop_product_id == stop_id,
PlPlanProductBrandQty.tenant_no == tenant_no,
PlPlanProductBrandQty.del_flag == 0,
)
.all()
)
for row in existing:
row.del_flag = 1
row.update_by = user_id
positive = [item for item in rows if Decimal(str(item.get("qty") or 0)) > 0]
if not brand_mode and not positive:
return
for idx, item in enumerate(rows):
qty = Decimal(str(item.get("qty") or 0))
if qty <= 0:
continue
brand_id = item.get("brandId") or item.get("brand_id") or ""
brand_name = item.get("brandName") or item.get("brand_name")
db.add(
PlPlanProductBrandQty(
id=generate_uuid(),
tenant_no=tenant_no,
plan_id=plan_id,
plan_product_id=plan_product_id,
stop_type=stop_type,
stop_product_id=stop_id,
brand_id=brand_id,
brand_name=brand_name,
qty=qty,
unit_price=_parse_unit_price(item),
order_num=idx,
create_by=user_id,
create_dept_id=dept_id or "",
update_by=user_id,
)
)