docs: 出入库单现场链接与信息完善实现计划
按已确认 design 拆分为 enrichment、公共页、列表弹窗与计划回传共 7 个可勾选任务。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
9a1e43e371
commit
dbd0d9f205
1 file changed
+628
@@ -0,0 +1,628 @@
|
||||
# 入库/出库单信息完善与现场链接对齐 — Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 按 Demo「提案」完善入库/出库现场公共页,并同步管理端列表、编辑弹窗、计划查看回传:展示取送货与完整产品字段(含包装规格、单位中文标签)。
|
||||
|
||||
**Architecture:** 后端在 `pl_io_order_service.order_header_to_dict` 读时 enrichment:按 `plan_id` 取 `pl_plan`、按明细 `plan_product_id` 取 `pl_plan_product`,附加物流字段与 `productNo`/`packageSpec`/`unitLabel`/`remark`;公共页 `unit` 直接返回中文标签。前端改共享页 `PublicIoOrderPage`、`IoOrderListPage`、`PlPlanViewTabs`。
|
||||
|
||||
**Tech Stack:** FastAPI + SQLAlchemy;Vue 3 + Vant(公共页)+ Element Plus(管理端)
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-07-19-pl-io-order-site-link-enrichment-design.md`
|
||||
**Demo:** `docs/superpowers/brainstorm/2026-07-19-pl-outbound-site-link-demo.html`
|
||||
|
||||
---
|
||||
|
||||
## 文件结构
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `backend/app/services/pl_io_order_service.py` | `enrich_io_order_dict`:计划物流 + 产品扩展 + 单位标签 |
|
||||
| `backend/app/schemas/pl/io_order.py` | `IoOrderItemRead` / `IoOrderRead` 增加可选字段 |
|
||||
| `backend/app/api/pl/public_io_order.py` | `_build_page` 调用 enrichment;公共页 `unit` 用标签 |
|
||||
| `backend/tests/test_pl_io_order_enrichment.py` | enrichment 纯函数/字典断言(无 DB 时可测 helper) |
|
||||
| `web-admin/src/api/pl/index.ts` | `PlIoOrder` / `PlIoOrderItem` 类型扩展 |
|
||||
| `web-admin/src/api/pl/public-io-order.ts` | 公共页类型扩展 |
|
||||
| `web-admin/src/views/public/pl/_shared/PublicIoOrderPage.vue` | 按 Demo 重排 UI |
|
||||
| `web-admin/src/views/pl/_shared/IoOrderListPage.vue` | 列表列 + 编辑弹窗 |
|
||||
| `web-admin/src/components/business/pl-plan-view-tabs/index.vue` | 回传 KV + 产品表 + 快捷编辑表 |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 后端 enrichment helper + `order_header_to_dict`
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/services/pl_io_order_service.py`
|
||||
- Create: `backend/tests/test_pl_io_order_enrichment.py`
|
||||
|
||||
- [ ] **Step 1: 写失败测试(单位标签与计划字段合并)**
|
||||
|
||||
```python
|
||||
# backend/tests/test_pl_io_order_enrichment.py
|
||||
from app.services.pl_io_order_service import (
|
||||
resolve_unit_label,
|
||||
PLAN_TYPE_BUSINESS_LABEL,
|
||||
merge_plan_logistics_into_order,
|
||||
merge_plan_product_into_item,
|
||||
)
|
||||
|
||||
|
||||
def test_resolve_unit_label_maps_dun():
|
||||
assert resolve_unit_label({"dun": "吨"}, "dun") == "吨"
|
||||
assert resolve_unit_label({}, "dun") == "dun"
|
||||
assert resolve_unit_label({}, None) == "吨"
|
||||
assert resolve_unit_label({}, "") == "吨"
|
||||
|
||||
|
||||
def test_merge_plan_logistics_into_order():
|
||||
data = {"remark": "order-remark"}
|
||||
plan = type(
|
||||
"P",
|
||||
(),
|
||||
{
|
||||
"plan_type": "outbound",
|
||||
"remark": "plan-remark",
|
||||
"pickup_address": "取货路1号",
|
||||
"pickup_lng": 117.0,
|
||||
"pickup_lat": 30.5,
|
||||
"pickup_time": None,
|
||||
"pickup_notes": "戴帽",
|
||||
"destination_name": "仓库A",
|
||||
"destination_address": "送货路2号",
|
||||
"destination_lng": None,
|
||||
"destination_lat": None,
|
||||
"delivery_time": None,
|
||||
"destination_notes": "北门",
|
||||
},
|
||||
)()
|
||||
merge_plan_logistics_into_order(data, plan)
|
||||
assert data["businessType"] == "出库"
|
||||
assert data["planRemark"] == "plan-remark"
|
||||
assert data["remark"] == "order-remark" # 单据备注不被覆盖
|
||||
assert data["pickupAddress"] == "取货路1号"
|
||||
assert data["destinationAddress"] == "送货路2号"
|
||||
assert data["destinationName"] == "仓库A"
|
||||
|
||||
|
||||
def test_merge_plan_product_into_item():
|
||||
item = {"unit": "dun", "productName": "尿素"}
|
||||
product = type(
|
||||
"PP",
|
||||
(),
|
||||
{
|
||||
"product_no": "P001",
|
||||
"package_spec": "50kg/袋",
|
||||
"remark": "易潮",
|
||||
"unit": "dun",
|
||||
"model": "46.4%",
|
||||
"product_name": "工业级尿素",
|
||||
},
|
||||
)()
|
||||
merge_plan_product_into_item(item, product, unit_map={"dun": "吨"})
|
||||
assert item["productNo"] == "P001"
|
||||
assert item["packageSpec"] == "50kg/袋"
|
||||
assert item["remark"] == "易潮"
|
||||
assert item["unitLabel"] == "吨"
|
||||
assert item["unit"] == "dun"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行确认失败**
|
||||
|
||||
```bash
|
||||
cd backend && source venv/bin/activate && pytest tests/test_pl_io_order_enrichment.py -v
|
||||
```
|
||||
|
||||
Expected: FAIL(函数未定义)
|
||||
|
||||
- [ ] **Step 3: 在 `pl_io_order_service.py` 实现 helper(放在 `order_header_to_dict` 之前)**
|
||||
|
||||
```python
|
||||
CRM_PRODUCT_UNIT = "crm_product_unit"
|
||||
|
||||
PLAN_TYPE_BUSINESS_LABEL = {
|
||||
"inbound": "入库",
|
||||
"outbound": "出库",
|
||||
"direct": "直销",
|
||||
}
|
||||
|
||||
|
||||
def resolve_unit_label(unit_map: dict[str, str], unit_code: Optional[str]) -> str:
|
||||
code = (unit_code or "").strip()
|
||||
if not code:
|
||||
return "吨"
|
||||
return unit_map.get(code, code)
|
||||
|
||||
|
||||
def merge_plan_logistics_into_order(data: dict, plan: Optional[PlPlan]) -> None:
|
||||
if not plan:
|
||||
data.setdefault("businessType", None)
|
||||
data.setdefault("planRemark", None)
|
||||
data.setdefault("pickupAddress", None)
|
||||
data.setdefault("pickupLng", None)
|
||||
data.setdefault("pickupLat", None)
|
||||
data.setdefault("pickupTime", None)
|
||||
data.setdefault("pickupNotes", None)
|
||||
data.setdefault("destinationName", None)
|
||||
data.setdefault("destinationAddress", None)
|
||||
data.setdefault("destinationLng", None)
|
||||
data.setdefault("destinationLat", None)
|
||||
data.setdefault("deliveryTime", None)
|
||||
data.setdefault("destinationNotes", None)
|
||||
return
|
||||
data["businessType"] = PLAN_TYPE_BUSINESS_LABEL.get(plan.plan_type, plan.plan_type)
|
||||
data["planRemark"] = (plan.remark or "").strip() or None
|
||||
data["pickupAddress"] = plan.pickup_address
|
||||
data["pickupLng"] = plan.pickup_lng
|
||||
data["pickupLat"] = plan.pickup_lat
|
||||
data["pickupTime"] = plan.pickup_time
|
||||
data["pickupNotes"] = plan.pickup_notes
|
||||
data["destinationName"] = plan.destination_name
|
||||
data["destinationAddress"] = plan.destination_address or plan.destination_name
|
||||
data["destinationLng"] = plan.destination_lng
|
||||
data["destinationLat"] = plan.destination_lat
|
||||
data["deliveryTime"] = plan.delivery_time
|
||||
data["destinationNotes"] = plan.destination_notes
|
||||
|
||||
|
||||
def merge_plan_product_into_item(
|
||||
item: dict,
|
||||
product: Optional[PlPlanProduct],
|
||||
*,
|
||||
unit_map: dict[str, str],
|
||||
) -> None:
|
||||
if not product:
|
||||
item.setdefault("productNo", None)
|
||||
item.setdefault("packageSpec", None)
|
||||
item.setdefault("remark", None)
|
||||
item["unitLabel"] = resolve_unit_label(unit_map, item.get("unit"))
|
||||
return
|
||||
item["productNo"] = product.product_no
|
||||
item["packageSpec"] = product.package_spec
|
||||
item["remark"] = (product.remark or "").strip() or None
|
||||
if not item.get("specification") and product.model:
|
||||
item["specification"] = product.model
|
||||
item["unitLabel"] = resolve_unit_label(unit_map, item.get("unit") or product.unit)
|
||||
|
||||
|
||||
def enrich_io_order_dict(
|
||||
db: Session,
|
||||
data: dict,
|
||||
*,
|
||||
tenant_no: str,
|
||||
plan_id: Optional[str],
|
||||
items: list[Any] | None,
|
||||
public_unit_as_label: bool = False,
|
||||
) -> dict:
|
||||
"""读时附加计划取送货与产品扩展字段。"""
|
||||
from app.utils.excel_export import load_dict_label_maps
|
||||
|
||||
plan = None
|
||||
if plan_id:
|
||||
plan = (
|
||||
db.query(PlPlan)
|
||||
.filter(PlPlan.id == plan_id, PlPlan.del_flag == 0)
|
||||
.first()
|
||||
)
|
||||
merge_plan_logistics_into_order(data, plan)
|
||||
|
||||
unit_map = load_dict_label_maps(
|
||||
db, [CRM_PRODUCT_UNIT], tenant_no=tenant_no
|
||||
).get(CRM_PRODUCT_UNIT, {})
|
||||
|
||||
raw_items = data.get("items")
|
||||
if raw_items is None:
|
||||
return data
|
||||
|
||||
product_ids = [
|
||||
it.get("planProductId")
|
||||
for it in raw_items
|
||||
if it.get("planProductId")
|
||||
]
|
||||
products_by_id: dict[str, PlPlanProduct] = {}
|
||||
if product_ids:
|
||||
rows = (
|
||||
db.query(PlPlanProduct)
|
||||
.filter(
|
||||
PlPlanProduct.id.in_(product_ids),
|
||||
PlPlanProduct.del_flag == 0,
|
||||
)
|
||||
.all()
|
||||
)
|
||||
products_by_id = {r.id: r for r in rows}
|
||||
|
||||
for it in raw_items:
|
||||
pp = products_by_id.get(it.get("planProductId") or "")
|
||||
merge_plan_product_into_item(it, pp, unit_map=unit_map)
|
||||
if public_unit_as_label:
|
||||
it["unit"] = it.get("unitLabel") or resolve_unit_label(unit_map, it.get("unit"))
|
||||
|
||||
return data
|
||||
```
|
||||
|
||||
确保文件顶部已 import:`PlPlan`, `PlPlanProduct`(若尚未导入则补上)。
|
||||
|
||||
- [ ] **Step 4: 改造 `order_header_to_dict` 签名与调用 enrichment**
|
||||
|
||||
将函数改为接受 `db`(可选,有则 enrichment):
|
||||
|
||||
```python
|
||||
def order_header_to_dict(
|
||||
kind: str,
|
||||
row: OrderHeader,
|
||||
items: list[Any] | None = None,
|
||||
*,
|
||||
db: Session | None = None,
|
||||
public_unit_as_label: bool = False,
|
||||
) -> dict:
|
||||
# ... existing data / items build ...
|
||||
if db is not None:
|
||||
enrich_io_order_dict(
|
||||
db,
|
||||
data,
|
||||
tenant_no=row.tenant_no,
|
||||
plan_id=row.plan_id,
|
||||
items=items,
|
||||
public_unit_as_label=public_unit_as_label,
|
||||
)
|
||||
return data
|
||||
```
|
||||
|
||||
明细 dict 中保持 `unit` 为库内码;`unitLabel` 由 enrichment 写入。
|
||||
|
||||
- [ ] **Step 5: 跑通单元测试**
|
||||
|
||||
```bash
|
||||
cd backend && source venv/bin/activate && pytest tests/test_pl_io_order_enrichment.py -v
|
||||
```
|
||||
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/services/pl_io_order_service.py backend/tests/test_pl_io_order_enrichment.py
|
||||
git commit -m "feat(pl): 出入库单读时 enrichment 取送货与产品扩展字段"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Schema + 管理端 / 公共 API 接入 enrichment
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/schemas/pl/io_order.py`
|
||||
- Modify: `backend/app/api/pl/io_order.py`
|
||||
- Modify: `backend/app/api/pl/public_io_order.py`
|
||||
|
||||
- [ ] **Step 1: 扩展 Pydantic 模型**
|
||||
|
||||
`IoOrderItemRead` 增加:
|
||||
|
||||
```python
|
||||
product_no: Optional[str] = Field(default=None, alias="productNo")
|
||||
package_spec: Optional[str] = Field(default=None, alias="packageSpec")
|
||||
remark: Optional[str] = None
|
||||
unit_label: Optional[str] = Field(default=None, alias="unitLabel")
|
||||
```
|
||||
|
||||
`IoOrderRead` 增加(均为 Optional):
|
||||
|
||||
```python
|
||||
business_type: Optional[str] = Field(default=None, alias="businessType")
|
||||
plan_remark: Optional[str] = Field(default=None, alias="planRemark")
|
||||
pickup_address: Optional[str] = Field(default=None, alias="pickupAddress")
|
||||
pickup_lng: Optional[float] = Field(default=None, alias="pickupLng")
|
||||
pickup_lat: Optional[float] = Field(default=None, alias="pickupLat")
|
||||
pickup_time: Optional[datetime] = Field(default=None, alias="pickupTime")
|
||||
pickup_notes: Optional[str] = Field(default=None, alias="pickupNotes")
|
||||
destination_name: Optional[str] = Field(default=None, alias="destinationName")
|
||||
destination_address: Optional[str] = Field(default=None, alias="destinationAddress")
|
||||
destination_lng: Optional[float] = Field(default=None, alias="destinationLng")
|
||||
destination_lat: Optional[float] = Field(default=None, alias="destinationLat")
|
||||
delivery_time: Optional[datetime] = Field(default=None, alias="deliveryTime")
|
||||
destination_notes: Optional[str] = Field(default=None, alias="destinationNotes")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 管理端 `_to_read` 传入 db**
|
||||
|
||||
```python
|
||||
def _to_read(kind: str, header, items, db: Session) -> IoOrderRead:
|
||||
return IoOrderRead.model_validate(
|
||||
order_header_to_dict(kind, header, items, db=db)
|
||||
)
|
||||
```
|
||||
|
||||
所有 `_to_read(...)` 调用补上 `db`(list / get / patch / by-plan)。
|
||||
|
||||
- [ ] **Step 3: 公共 `_build_page`**
|
||||
|
||||
```python
|
||||
def _build_page(db: Session, kind: str, header, items) -> dict:
|
||||
plan = db.query(PlPlan).filter(PlPlan.id == header.plan_id, PlPlan.del_flag == 0).first()
|
||||
voided = header.status == "voided" or bool(plan and plan.follow_status == "cancelled")
|
||||
data = order_header_to_dict(
|
||||
kind, header, items, db=db, public_unit_as_label=True
|
||||
)
|
||||
data["voided"] = voided
|
||||
data["title"] = "入库单" if kind == "inbound" else "出库单"
|
||||
# enrichment 已写入 pickup/destination;勿再重复简陋覆盖丢失经纬度
|
||||
return data
|
||||
```
|
||||
|
||||
删除旧的仅写 `pickupAddress`/`destinationAddress` 两行简陋赋值(由 enrichment 统一提供)。
|
||||
|
||||
- [ ] **Step 4: 冒烟 import**
|
||||
|
||||
```bash
|
||||
cd backend && source venv/bin/activate && python -c "
|
||||
from app.api.pl.io_order import inbound_router
|
||||
from app.api.pl.public_io_order import outbound_public_router
|
||||
print('ok')
|
||||
"
|
||||
```
|
||||
|
||||
Expected: `ok`
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/schemas/pl/io_order.py backend/app/api/pl/io_order.py backend/app/api/pl/public_io_order.py
|
||||
git commit -m "feat(pl): 出入库 API 接入 enrichment 并扩展 schema"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 前端 API 类型
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/api/pl/index.ts`
|
||||
- Modify: `web-admin/src/api/pl/public-io-order.ts`
|
||||
|
||||
- [ ] **Step 1: `PlIoOrderItem` 扩展**
|
||||
|
||||
```typescript
|
||||
export interface PlIoOrderItem {
|
||||
id: string
|
||||
orderId: string
|
||||
planProductId: string
|
||||
productId?: string
|
||||
productNo?: string
|
||||
productName?: string
|
||||
specification?: string
|
||||
unit?: string
|
||||
unitLabel?: string
|
||||
planQty?: number
|
||||
actualQty?: number
|
||||
qtySource?: string
|
||||
packageSpec?: string
|
||||
remark?: string
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: `PlIoOrder` 扩展**
|
||||
|
||||
```typescript
|
||||
businessType?: string
|
||||
planRemark?: string
|
||||
pickupAddress?: string
|
||||
pickupLng?: number | null
|
||||
pickupLat?: number | null
|
||||
pickupTime?: string
|
||||
pickupNotes?: string
|
||||
destinationName?: string
|
||||
destinationAddress?: string
|
||||
destinationLng?: number | null
|
||||
destinationLat?: number | null
|
||||
deliveryTime?: string
|
||||
destinationNotes?: string
|
||||
```
|
||||
|
||||
- [ ] **Step 3: `PublicIoOrderPage` / `PublicIoOrderItem` 对齐公共响应**
|
||||
|
||||
`PublicIoOrderItem`:`packageSpec?`、`remark?`、`productNo?`(可选)
|
||||
`PublicIoOrderPage`:与上列物流字段一致 + `planRemark?` + `businessType?` + `date?`(可用 `planDate`)
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/api/pl/index.ts web-admin/src/api/pl/public-io-order.ts
|
||||
git commit -m "feat(web-admin): 扩展出入库单与公共页 TypeScript 类型"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 现场公共页 UI(对齐 Demo)
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/public/pl/_shared/PublicIoOrderPage.vue`
|
||||
- Reference: `web-admin/src/views/public/pl/site/index.vue`(导航、地址 cell)
|
||||
- Reference: `docs/superpowers/brainstorm/2026-07-19-pl-outbound-site-link-demo.html`
|
||||
|
||||
- [ ] **Step 1: 模板按 Demo 区块重排**
|
||||
|
||||
概况 `van-cell-group`:业务类型、日期(`planDate`)、往来单位、车牌、计划号、单号、状态、`planRemark`(有则显示)。
|
||||
|
||||
取货 / 送货两组:地址 + `buildAmapNavigationUrl` 导航按钮、时间 `formatDateTime`、注意事项;样式复用 site 页 `address-cell` / `multiline-cell`。
|
||||
|
||||
产品区:每项 `.product-card` — 名称、规格、包装规格、计划数量+`unit`(已是标签)、实际数量 `van-field`;样式按 Demo(绿边卡片、大字输入)。
|
||||
|
||||
回执、现场备注、上次保存、底部提交保留。
|
||||
|
||||
- [ ] **Step 2: script 补导航与格式化**
|
||||
|
||||
```typescript
|
||||
import { buildAmapNavigationUrl } from '@/utils/amap/navigation'
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return '—'
|
||||
const normalized = String(value).replace('T', ' ')
|
||||
return normalized.length > 16 ? normalized.slice(0, 16) : normalized
|
||||
}
|
||||
|
||||
const pickupNavUrl = computed(() => {
|
||||
if (!page.value) return null
|
||||
return buildAmapNavigationUrl(
|
||||
page.value.pickupLng,
|
||||
page.value.pickupLat,
|
||||
page.value.pickupAddress || '取货点'
|
||||
)
|
||||
})
|
||||
// destinationNavUrl 同理
|
||||
```
|
||||
|
||||
`navTitle` 可用:`出库单 · 现场回执` / `入库单 · 现场回执`。
|
||||
|
||||
- [ ] **Step 3: 本地打开任一 token 页目视核对**
|
||||
|
||||
对照 Demo:单位为「吨」、有包装规格、有取送货。
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/views/public/pl/_shared/PublicIoOrderPage.vue
|
||||
git commit -m "feat(web-admin): 出入库现场链接页对齐 Demo 信息密度与样式"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 管理端列表 + 编辑弹窗
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/pl/_shared/IoOrderListPage.vue`
|
||||
|
||||
- [ ] **Step 1: 列表增加两列(放在「产品」前或「车牌」后)**
|
||||
|
||||
```vue
|
||||
<el-table-column
|
||||
prop="pickupAddress"
|
||||
label="取货地址"
|
||||
min-width="160"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="送货地址" min-width="160" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.destinationAddress || row.destinationName || '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 弹窗加宽并补主信息**
|
||||
|
||||
`width="1080px"`。在往来单位后增加只读项:
|
||||
|
||||
- 取货地址、取货时间、取货注意事项(注意事项 / 地址 `form-col-span-2`)
|
||||
- 送货地址、送达时间、送货注意事项
|
||||
|
||||
时间用简单展示:`editForm.pickupTime` 字符串即可。
|
||||
|
||||
- [ ] **Step 3: 产品表改为 8 列**
|
||||
|
||||
```vue
|
||||
<el-table-column prop="productNo" label="产品编号" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="productName" label="产品名称" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="specification" label="型号/规格" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="单位" min-width="72" align="center">
|
||||
<template #default="{ row }">{{ row.unitLabel || row.unit || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="planQty" label="计划数量" min-width="100" align="right" />
|
||||
<el-table-column label="实际数量" min-width="120">
|
||||
<!-- 既有 el-input-number -->
|
||||
</el-table-column>
|
||||
<el-table-column prop="packageSpec" label="包装规格" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/views/pl/_shared/IoOrderListPage.vue
|
||||
git commit -m "feat(web-admin): 出入库列表与编辑弹窗展示取送货及完整产品列"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 计划查看回传对齐
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/components/business/pl-plan-view-tabs/index.vue`
|
||||
|
||||
- [ ] **Step 1: 扩展 `ioOrderViewFields`**
|
||||
|
||||
在既有字段后追加:
|
||||
|
||||
```typescript
|
||||
{ label: '取货地址', value: ioOrder.value.pickupAddress, span: 'full' },
|
||||
{ label: '取货时间', value: formatDateTime(ioOrder.value.pickupTime) || '—' },
|
||||
{ label: '取货注意事项', value: ioOrder.value.pickupNotes, span: 'full' },
|
||||
{
|
||||
label: '送货地址',
|
||||
value: ioOrder.value.destinationAddress || ioOrder.value.destinationName,
|
||||
span: 'full'
|
||||
},
|
||||
{ label: '送达时间', value: formatDateTime(ioOrder.value.deliveryTime) || '—' },
|
||||
{ label: '送货注意事项', value: ioOrder.value.destinationNotes, span: 'full' },
|
||||
```
|
||||
|
||||
(若 `ViewField` 无 `span` 则按该组件既有 full-width 写法。)
|
||||
|
||||
- [ ] **Step 2: 只读子表列对齐 Task 5 的 8 列**
|
||||
|
||||
替换现有仅 `productName` / `specification` / `planQty` / `actualQty` 的列定义。
|
||||
|
||||
- [ ] **Step 3: 快捷编辑弹窗 `ioOrderEditForm` 产品表同样 8 列**(实际数量可编辑)
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/components/business/pl-plan-view-tabs/index.vue
|
||||
git commit -m "feat(web-admin): 计划查看出入库回传补齐取送货与产品列"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: 图谱更新与手工验收
|
||||
|
||||
**Files:** 无新增业务文件时仍更新图谱
|
||||
|
||||
- [ ] **Step 1: 更新知识图谱**
|
||||
|
||||
```bash
|
||||
./scripts/update-graph.sh all
|
||||
```
|
||||
|
||||
或分别 `backend` / `frontend`。
|
||||
|
||||
- [ ] **Step 2: 手工验收对照 Spec §8**
|
||||
|
||||
| ID | 操作 |
|
||||
|----|------|
|
||||
| V1 | 打开出库现场链接:取送货、导航、包装规格、单位「吨」 |
|
||||
| V2 | 入库现场链接对称 |
|
||||
| V3 | `/#/pl/.../inbound` 与 outbound 列表有取货/送货列 |
|
||||
| V4 | 编辑弹窗 8 列产品 + 取送货;改实际数量保存成功 |
|
||||
| V5 | 计划查看回传区一致 |
|
||||
| V6 | 作废单现场不可提交 |
|
||||
|
||||
- [ ] **Step 3: 若有图谱变更则 commit**
|
||||
|
||||
```bash
|
||||
git add backend/graphify-out web-admin/graphify-out graphify-out 2>/dev/null || true
|
||||
git commit -m "chore: 更新 graphify 图谱(出入库 enrichment)" || true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Spec 覆盖自检
|
||||
|
||||
| Spec 项 | Task |
|
||||
|---------|------|
|
||||
| D1 读时 enrichment | Task 1–2 |
|
||||
| D2 入库出库对称 | Task 4–6 共享组件 |
|
||||
| D3 表面 C | Task 5–6 |
|
||||
| D4 单位标签 | Task 1 `unitLabel` / 公共 `public_unit_as_label` |
|
||||
| D5 列表不铺满产品列 | Task 5 仅地址列 |
|
||||
| D6 列表物流列 | Task 5 |
|
||||
| D7 扩展只读 | Task 5–6 |
|
||||
| 公共页 Demo | Task 4 |
|
||||
| V1–V6 | Task 7 |
|
||||
|
||||
无 DDL;不改旧 `/public/pl/site`。
|
||||
Reference in new issue
Block a user