feat: 合同班按供应商合单并映射草稿
This commit is contained in:
parent
5f5ec4874f
commit
e8ef1a77fc
|
|
@ -0,0 +1,89 @@
|
|||
"""合同班合单:可录判定、按供应商分组、草稿映射。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import OrderedDict
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
|
||||
from contract_draft import ContractDraft
|
||||
from contract_flow_steps import ContractGoodsLine
|
||||
from repo import PurchaseContractRow
|
||||
|
||||
|
||||
def is_claimable(row: PurchaseContractRow) -> bool:
|
||||
if int(row.process_status or 0) != 0:
|
||||
return False
|
||||
if row.contract_entry_status is not None and int(row.contract_entry_status) == 0:
|
||||
if (row.contract_no or "").strip():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def group_by_supplier(rows: list[PurchaseContractRow]) -> list[list[PurchaseContractRow]]:
|
||||
groups: OrderedDict[str, list[PurchaseContractRow]] = OrderedDict()
|
||||
for row in sorted(rows, key=lambda item: item.fid):
|
||||
groups.setdefault(row.supplier_id, []).append(row)
|
||||
return list(groups.values())
|
||||
|
||||
|
||||
def missing_mdm_reason(rows: list[PurchaseContractRow]) -> str | None:
|
||||
if not rows:
|
||||
return "缺少供应商MDM码"
|
||||
if not (rows[0].supplier_mdm_code or "").strip():
|
||||
return "缺少供应商MDM码"
|
||||
return None
|
||||
|
||||
|
||||
def money4(value) -> str:
|
||||
amount = Decimal(str(value)).quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP)
|
||||
return f"{amount:.4f}"
|
||||
|
||||
|
||||
def iso_date(value) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
if isinstance(value, datetime):
|
||||
return value.date().isoformat()
|
||||
if isinstance(value, date):
|
||||
return value.isoformat()
|
||||
return str(value)[:10]
|
||||
|
||||
|
||||
def draft_from_group(rows: list[PurchaseContractRow]) -> ContractDraft:
|
||||
first = rows[0]
|
||||
sign = iso_date(first.contract_sign_date)
|
||||
total = sum(
|
||||
(Decimal(str(row.qty)) * Decimal(str(row.unit_price)) for row in rows),
|
||||
Decimal("0"),
|
||||
)
|
||||
goods = [
|
||||
ContractGoodsLine(
|
||||
operation_code=row.goods_id,
|
||||
goods_code=row.goods_id,
|
||||
goods_name=row.goods_name,
|
||||
approval_no=row.reg_no,
|
||||
spec=row.spec,
|
||||
unit=row.unit,
|
||||
quantity=float(row.qty),
|
||||
mdm_code=row.goods_mdm_code or "",
|
||||
unit_price=money4(row.unit_price),
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
return ContractDraft(
|
||||
supplier_name=first.supplier_name,
|
||||
order_date=sign,
|
||||
source_type="",
|
||||
customer_code=(first.supplier_mdm_code or "").strip(),
|
||||
customer_name=first.supplier_name,
|
||||
contract_category="采购协议",
|
||||
amount=money4(total),
|
||||
contract_sign_date=sign,
|
||||
contract_start_date=iso_date(first.contract_start_date),
|
||||
contract_end_date=iso_date(first.contract_end_date),
|
||||
is_prepayment="否",
|
||||
belong_year=sign[:4],
|
||||
payment_method="账期支付",
|
||||
payment_days=str(first.payment_term),
|
||||
goods_lines=goods,
|
||||
)
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
"""表 3 合同行与仓库。Memory / Oracle 在后续任务补全。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
@dataclass
|
||||
class PurchaseContractRow:
|
||||
fid: int
|
||||
oms_id: int
|
||||
supplier_id: str
|
||||
supplier_name: str
|
||||
contract_sign_date: date | None
|
||||
contract_start_date: date | None
|
||||
contract_end_date: date | None
|
||||
payment_term: int
|
||||
goods_id: str
|
||||
goods_name: str
|
||||
reg_no: str
|
||||
spec: str
|
||||
unit: str
|
||||
qty: Decimal | float | int
|
||||
unit_price: Decimal | float | int
|
||||
purchaser_id: str
|
||||
purchaser_name: str
|
||||
planner_id: str
|
||||
planner_name: str
|
||||
contract_entry_status: int | None
|
||||
contract_no: str | None
|
||||
process_status: int
|
||||
org_code: str | None
|
||||
goods_mdm_code: str | None
|
||||
supplier_mdm_code: str | None
|
||||
process_result: str = ""
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from group import (
|
||||
draft_from_group,
|
||||
group_by_supplier,
|
||||
is_claimable,
|
||||
missing_mdm_reason,
|
||||
money4,
|
||||
)
|
||||
from repo import PurchaseContractRow
|
||||
|
||||
|
||||
def _row(**overrides) -> PurchaseContractRow:
|
||||
data = dict(
|
||||
fid=1,
|
||||
oms_id=2026100501,
|
||||
supplier_id="SUP001",
|
||||
supplier_name="示例供应商",
|
||||
contract_sign_date=date(2026, 10, 1),
|
||||
contract_start_date=date(2026, 10, 2),
|
||||
contract_end_date=date(2027, 9, 30),
|
||||
payment_term=90,
|
||||
goods_id="G0001",
|
||||
goods_name="阿莫西林胶囊",
|
||||
reg_no="国药准字H20201234",
|
||||
spec="0.25g*24粒",
|
||||
unit="盒",
|
||||
qty=Decimal("10.0000"),
|
||||
unit_price=Decimal("100.0000"),
|
||||
purchaser_id="P001",
|
||||
purchaser_name="张三",
|
||||
planner_id="PL001",
|
||||
planner_name="李四",
|
||||
contract_entry_status=None,
|
||||
contract_no=None,
|
||||
process_status=0,
|
||||
org_code="901",
|
||||
goods_mdm_code="QX0100167583",
|
||||
supplier_mdm_code="DW0100046361",
|
||||
)
|
||||
data.update(overrides)
|
||||
return PurchaseContractRow(**data)
|
||||
|
||||
|
||||
def test_protocol_rerun_is_not_claimable():
|
||||
row = _row(contract_entry_status=0, contract_no="HT-OLD")
|
||||
assert is_claimable(row) is False
|
||||
|
||||
|
||||
def test_blank_contract_no_is_still_claimable():
|
||||
assert is_claimable(_row(contract_entry_status=0, contract_no=" ")) is True
|
||||
assert is_claimable(_row(contract_entry_status=0, contract_no="")) is True
|
||||
assert is_claimable(_row(contract_entry_status=0, contract_no=None)) is True
|
||||
|
||||
|
||||
def test_processing_or_done_is_not_claimable():
|
||||
assert is_claimable(_row(process_status=3)) is False
|
||||
assert is_claimable(_row(process_status=1)) is False
|
||||
|
||||
|
||||
def test_group_by_supplier_keeps_fid_order_and_first_header():
|
||||
rows = [
|
||||
_row(fid=2, supplier_id="B", payment_term=30, supplier_mdm_code="MDM-B"),
|
||||
_row(fid=1, supplier_id="A", payment_term=90, supplier_mdm_code="MDM-A"),
|
||||
_row(fid=3, supplier_id="A", payment_term=60, oms_id=2026100502, goods_id="G0002"),
|
||||
]
|
||||
groups = group_by_supplier(rows)
|
||||
assert [g[0].supplier_id for g in groups] == ["A", "B"]
|
||||
assert [r.fid for r in groups[0]] == [1, 3]
|
||||
draft = draft_from_group(groups[0])
|
||||
assert draft.customer_code == "MDM-A"
|
||||
assert draft.payment_days == "90"
|
||||
assert draft.order_date == "2026-10-01"
|
||||
assert draft.contract_sign_date == "2026-10-01"
|
||||
assert draft.contract_start_date == "2026-10-02"
|
||||
assert draft.contract_end_date == "2027-09-30"
|
||||
assert draft.amount == "2000.0000"
|
||||
assert draft.source_type == ""
|
||||
assert draft.is_prepayment == "否"
|
||||
assert len(draft.goods_lines) == 2
|
||||
assert draft.goods_lines[0].goods_code == "G0001"
|
||||
assert draft.goods_lines[0].operation_code == "G0001"
|
||||
assert draft.goods_lines[0].unit_price == "100.0000"
|
||||
assert draft.belong_year == "2026"
|
||||
|
||||
|
||||
def test_missing_mdm_reason():
|
||||
rows = [_row(supplier_mdm_code="")]
|
||||
assert missing_mdm_reason(rows) == "缺少供应商MDM码"
|
||||
assert missing_mdm_reason([_row()]) is None
|
||||
|
||||
|
||||
def test_money4():
|
||||
assert money4(Decimal("10.5")) == "10.5000"
|
||||
Loading…
Reference in New Issue