AutoRpa/rpa_cart/settle.py

151 lines
4.4 KiB
Python

"""1 号药城导出行 → 表 2 operate_type=2。"""
from __future__ import annotations
from dataclasses import dataclass, replace
from decimal import Decimal, ROUND_HALF_UP
from typing import Any
YAOEX_PLATFORM_TYPE = 66
OPERATE_TYPE_PAY = 2
PUSH_NOT_SENT = 0
PAID_STATUSES = {"待发货", "待收货", "已完成"}
def money4(value: object) -> Decimal:
return Decimal(str(value)).quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP)
@dataclass(frozen=True)
class MallLine:
order_no: str
parent_order_no: str
supplier_name: str
status: str
goods_name: str
product_code: str
bar_code: str
unit_price: Decimal
pay_price: Decimal
qty: int
goods_amount: Decimal
order_paid: Decimal
pay_order_dtl_id: str = ""
@dataclass(frozen=True)
class CartCandidate:
fid: int
oms_id: int
supplier_name: str
bar_code: str
purchase_qty: int
org_code: str
multi_platform_flag: int
@dataclass
class MatchResult:
rows: list[dict[str, Any]]
unmatched: list[tuple[MallLine, str]]
skipped_dup: list[MallLine]
skipped_status: list[MallLine]
def attach_detail_ids(
lines: list[MallLine], details: dict[str, dict[str, Any]]
) -> list[MallLine]:
remaining: dict[str, list[dict[str, Any]]] = {}
for order_no, payload in details.items():
remaining[order_no] = list(payload.get("productList") or [])
attached: list[MallLine] = []
for line in lines:
pool = remaining.get(line.order_no) or []
idx = _find_product(pool, line)
if idx is None:
attached.append(line)
continue
product = pool.pop(idx)
dtl_id = product.get("orderDetailId")
attached.append(replace(line, pay_order_dtl_id="" if dtl_id is None else str(dtl_id)))
return attached
def match_pay_lines(
lines: list[MallLine],
cart_rows: list[CartCandidate],
existing_keys: set[tuple[str, str]],
) -> MatchResult:
result = MatchResult(rows=[], unmatched=[], skipped_dup=[], skipped_status=[])
unused = list(cart_rows)
seen = set(existing_keys)
for line in lines:
if line.status not in PAID_STATUSES:
result.skipped_status.append(line)
continue
if not line.pay_order_dtl_id:
result.unmatched.append((line, "NO_DTL_ID"))
continue
key = (line.order_no, line.pay_order_dtl_id)
if key in seen:
result.skipped_dup.append(line)
continue
cart = _pick_cart(unused, line)
if cart is None:
result.unmatched.append((line, "NO_CART_ROW"))
continue
unused.remove(cart)
seen.add(key)
result.rows.append(build_pay_row(line, cart))
return result
def build_pay_row(line: MallLine, cart: CartCandidate) -> dict[str, Any]:
price = money4(line.pay_price)
qty = int(line.qty)
return {
"operate_type": OPERATE_TYPE_PAY,
"oms_id": cart.oms_id,
"platform_type": YAOEX_PLATFORM_TYPE,
"supplier_name": line.supplier_name,
"bar_code": line.bar_code,
"purchase_price": price,
"purchase_qty": qty,
"purchase_total": money4(price * qty),
"status": None,
"fail_reason": None,
"pay_order_no": line.order_no,
"pay_order_dtl_id": line.pay_order_dtl_id,
"push_status": PUSH_NOT_SENT,
"org_code": cart.org_code or None,
"multi_platform_flag": cart.multi_platform_flag,
}
def _pick_cart(unused: list[CartCandidate], line: MallLine) -> CartCandidate | None:
matched = [
row
for row in unused
if row.bar_code == line.bar_code and row.supplier_name == line.supplier_name
]
if not matched:
return None
equal_qty = [row for row in matched if row.purchase_qty == line.qty]
pool = equal_qty or matched
return sorted(pool, key=lambda row: row.fid)[0]
def _find_product(pool: list[dict[str, Any]], line: MallLine) -> int | None:
for idx, product in enumerate(pool):
qty = int(Decimal(str(product.get("quantity") or 0)))
pay_price = money4(product.get("payPrice4") or 0)
name = str(product.get("productName") or "")
if qty != line.qty:
continue
if pay_price != money4(line.pay_price):
continue
if name and line.goods_name and name != line.goods_name:
continue
return idx
return None