PayrollManagementSystem/backend/app/services/tplus/guidance.py

88 lines
3.8 KiB
Python
Raw Normal View History

"""解析已登记的月度指导成本,不按名称或单位猜测。"""
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