feat: 展示PDD商品和采购订单信息 (#164)

This commit is contained in:
chengma
2026-08-11 16:46:49 +08:00
parent 24497c55fd
commit 1f48d3a857
9 changed files with 319 additions and 31 deletions
+117 -1
View File
@@ -5,7 +5,7 @@
"""
from dataclasses import dataclass
from typing import Any, Dict, Iterable, List, Mapping
from typing import Any, Dict, Iterable, List, Mapping, Optional
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
from PyQt5.QtGui import QCloseEvent, QKeySequence
@@ -148,6 +148,21 @@ class ColorPriceView:
price_inconsistent: bool = False
@dataclass(frozen=True)
class PurchaseResultView:
"""采购任务的下单与核单结果。"""
mode: str
confirmed_spec: str
quantity: str
unit_price: str
total_price: str
order_no: str
ordered_at: str
match_status: str
payment_status: str
@dataclass(frozen=True)
class TaskDetailViewData:
"""详情窗口直接使用的展示数据。"""
@@ -169,6 +184,7 @@ class TaskDetailViewData:
last_error: str
colors: List[ColorPriceView]
sizes: List[str]
purchase: Optional[PurchaseResultView] = None
def _non_empty_text(value: Any) -> str:
@@ -228,6 +244,72 @@ def _format_metric(value: Any) -> str:
return "未采集"
def _format_cent(value: Any) -> str:
if isinstance(value, int) and not isinstance(value, bool) and value >= 0:
return f"¥{value / 100:.2f}"
return "—"
def _purchase_result_view(
task_type: TaskType, pdd_data: Mapping[str, Any]
) -> Optional[PurchaseResultView]:
"""整理采购结果;非采购任务不显示采购信息卡片。"""
if task_type is not TaskType.PURCHASE:
return None
purchase = pdd_data.get("purchase")
if not isinstance(purchase, Mapping):
purchase = {}
confirmed = purchase.get("confirmed")
if not isinstance(confirmed, Mapping):
confirmed = {}
options = confirmed.get("options")
if not isinstance(options, Mapping):
options = {}
confirmed_spec = " / ".join(
_non_empty_text(value) for value in options.values()
if _non_empty_text(value)
) or "—"
mode_text = {"live": "真实采购", "dry_run": "采购演练"}
match_text = {
"matched": "已匹配",
"not_submitted": "未提交",
"not_found": "未找到",
"ambiguous": "多个候选",
"unknown": "结果不确定",
}
payment_text = {
"unpaid": "待付款",
"paid": "已支付",
"other": "其他状态",
"unknown": "未知",
}
quantity = confirmed.get("quantity")
return PurchaseResultView(
mode=mode_text.get(_non_empty_text(purchase.get("mode")), "—"),
confirmed_spec=confirmed_spec,
quantity=(
str(quantity)
if isinstance(quantity, int) and not isinstance(quantity, bool)
else "—"
),
unit_price=_format_cent(confirmed.get("unit_price_cent")),
total_price=_format_cent(confirmed.get("total_price_cent")),
order_no=_non_empty_text(purchase.get("order_no")) or "—",
ordered_at=(
_non_empty_text(purchase.get("ordered_at_raw"))
or _non_empty_text(purchase.get("ordered_at"))
or "—"
),
match_status=match_text.get(
_non_empty_text(purchase.get("match_status")), "—"
),
payment_status=payment_text.get(
_non_empty_text(purchase.get("payment_status")), "—"
),
)
def build_task_detail_view_data(detail: TaskDetail) -> TaskDetailViewData:
"""从完整任务记录生成详情展示数据。
@@ -310,6 +392,7 @@ def build_task_detail_view_data(detail: TaskDetail) -> TaskDetailViewData:
last_error=last_error,
colors=colors,
sizes=sizes,
purchase=_purchase_result_view(detail.task_type, pdd_data),
)
@@ -401,6 +484,8 @@ class TaskDetailWindow(QWidget):
)
)
content_layout.addWidget(self._build_basic_card(content))
if self.data.purchase is not None:
content_layout.addWidget(self._build_purchase_card(content))
content_layout.addWidget(
self._build_spec_card("颜色", self.data.colors, content)
)
@@ -508,6 +593,37 @@ class TaskDetailWindow(QWidget):
layout.setColumnStretch(1, 1)
return card
def _build_purchase_card(self, parent: QWidget) -> CardWidget:
"""显示采购任务已经保存在本地的下单和核单结果。"""
purchase = self.data.purchase
assert purchase is not None
card = CardWidget(parent)
card.setObjectName("purchaseResultCard")
layout = QGridLayout(card)
layout.setContentsMargins(16, 14, 16, 14)
layout.setHorizontalSpacing(18)
layout.setVerticalSpacing(8)
values = (
("采购模式", purchase.mode),
("确认规格", purchase.confirmed_spec),
("确认数量", purchase.quantity),
("商品单价", purchase.unit_price),
("订单总价", purchase.total_price),
("订单号", purchase.order_no),
("下单时间", purchase.ordered_at),
("订单匹配", purchase.match_status),
("付款状态", purchase.payment_status),
)
layout.addWidget(SubtitleLabel("采购结果", card), 0, 0, 1, 2)
for row, (label, value) in enumerate(values, start=1):
layout.addWidget(CaptionLabel(label, card), row, 0)
text = self._copyable_label(value, label, card)
text.setWordWrap(True)
layout.addWidget(text, row, 1)
layout.setColumnStretch(1, 1)
return card
def _copyable_label(
self, value: str, field_name: str, parent: QWidget
) -> CopyableBodyLabel: