打通 T+ 快照、业务、发货、回款和核算准备链路,保留来源差异并提供待补状态。 新增业务自动接入任务、跨平台读取服务托管、权限复核、来源关联和前端核对页面。 补充迁移、隔离集成测试、浏览器模拟验收及角色授权边界修复。
88 lines
3.8 KiB
Python
88 lines
3.8 KiB
Python
"""解析已登记的月度指导成本,不按名称或单位猜测。"""
|
|
|
|
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
|