"""解析已登记的月度指导成本,不按名称或单位猜测。""" from datetime import date from decimal import Decimal, InvalidOperation from sqlalchemy import func, select from app.core.exceptions import BadRequestError from app.models import PayrollInput, TPlusFact def latest_guidance_facts(session, source): """只读取指定账套每个指导成本事实键的最新版本。""" ids = select(func.max(TPlusFact.id)).where( TPlusFact.source_database == source, TPlusFact.kind == "GUIDANCE" ).group_by(TPlusFact.fact_key) return {(fact.kind, fact.fact_key): fact for fact in session.scalars(select(TPlusFact).where(TPlusFact.id.in_(ids)))} def resolve_guidance(facts, product_id, line_id, month, unit): """返回匹配状态和已封存事实;存在多条候选时不选择其中一条。""" candidates = [ fact.data_json for (kind, _), fact in facts.items() if kind == "GUIDANCE" and fact.data_json.get("product_id") == product_id and fact.data_json.get("month") == month and (fact.data_json.get("basis") == "UNIT" or fact.data_json.get("shipment_line_id") == line_id) ] if not candidates: return "MISSING", None if len(candidates) != 1: return "AMBIGUOUS", None guidance = candidates[0] if guidance.get("unit") != unit: return "UNIT_MISMATCH", None if not guidance.get("is_finalized"): return "UNFINALIZED", None return "FINALIZED", guidance def check_unit_guidance_conflicts(facts, product_id, month, unit, amount, frozen_shipments): """新增产品月度单价前检查重叠事实和全部相关的历史封存明细。""" if any( kind == "GUIDANCE" and fact.data_json.get("product_id") == product_id and fact.data_json.get("month") == month for (kind, _), fact in facts.items() ): raise BadRequestError("指导成本存在重叠记录,请先核对") try: expected = Decimal(str(amount)) if not expected.is_finite() or expected < 0: raise ValueError for shipment in frozen_shipments: old = Decimal(str(shipment.get("unit_cost"))) if not old.is_finite() or old < 0 or old != expected or shipment.get("unit") != unit: raise ValueError except (InvalidOperation, TypeError, ValueError): raise BadRequestError("已封存发货的成本或单位与指导价不一致,请先核对") from None def frozen_guidance_shipments(session, source, product_id, month): """按不可变快照中的来源产品读取历史,不能依赖当前正式明细。""" query = ( select(PayrollInput.data_json) .where(PayrollInput.source_database == source) .order_by(PayrollInput.id) .execution_options(yield_per=100) ) for data in session.scalars(query): shipments = data.get("shipments") if isinstance(data, dict) else None if not isinstance(shipments, list): raise BadRequestError("已封存发货数据异常,请先核对") for shipment in shipments: if not isinstance(shipment, dict) or not shipment.get("line_id"): raise BadRequestError("已封存发货数据异常,请先核对") try: ship_date = date.fromisoformat(shipment["date"]) except (KeyError, TypeError, ValueError): raise BadRequestError("已封存发货日期异常,请先核对") from None if ship_date.strftime("%Y-%m") != month: continue frozen_product = shipment.get("product_id") if frozen_product is None or str(frozen_product).strip() in ("", "None"): raise BadRequestError("已封存发货缺少来源产品身份,请先核对") if str(frozen_product) == str(product_id): yield shipment