"""计划归档按品牌数量校验与持久化""" 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, ) )