提交
This commit is contained in:
1 parent
6d9354a194
commit
aff71d296b
2759 files changed
+860425
-13211
No files matched your search
@@ -0,0 +1,39 @@
|
||||
# PL 入库单 / 出库单 Implementation Plan
|
||||
|
||||
> **For agentic workers:** Execute task-by-task. Spec: `docs/superpowers/specs/2026-07-19-pl-inbound-outbound-order-design.md`
|
||||
|
||||
**Goal:** 安排运输后落 PL 入库/出库主从表;一单一链免登录填实际数量;管理端库存管理菜单列表编辑;作废计划作废单据;已安排计划冻结(无调整运输)。
|
||||
|
||||
**Architecture:** `pl_inbound_order`/`pl_outbound_order` + item;安排运输时创建(替代新 `pl_transport_site_link`);地磅数量仅预填;公共页 `/public/pl/inbound|outbound/{token}`。
|
||||
|
||||
**Tech Stack:** FastAPI + SQLAlchemy + Vue3/Element Plus;菜单 `sys_menu`;Fresh Mint UI。
|
||||
|
||||
---
|
||||
|
||||
## File map
|
||||
|
||||
| 职责 | 路径 |
|
||||
|------|------|
|
||||
| DDL 迁移 | `backend/scripts/migration_20260719_pl_inbound_outbound_order.sql` |
|
||||
| 模型 | `backend/app/models/pl_models.py` |
|
||||
| 服务 | `backend/app/services/pl_io_order_service.py` |
|
||||
| Schema | `backend/app/schemas/pl/io_order.py` |
|
||||
| 管理 API | `backend/app/api/pl/io_order.py` |
|
||||
| 公共 API | `backend/app/api/pl/public_io_order.py` |
|
||||
| 安排/作废接入 | `backend/app/api/pl/transport.py`, `plan.py` |
|
||||
| 路由注册 | `backend/app/api/pl/__init__.py`, `main.py` |
|
||||
| 菜单 | 同上 migration SQL;`web-admin/src/router/modules/pl.ts` |
|
||||
| 前端 API | `web-admin/src/api/pl/index.ts` |
|
||||
| 列表/弹窗 | `web-admin/src/views/pl/inbound/index.vue`, `outbound/index.vue` |
|
||||
| 公共页 | `web-admin/src/views/public/pl/inbound/index.vue`, `outbound/...` |
|
||||
| 计划冻结 UX | `web-admin/src/views/pl/plan/index.vue`, arrange dialog |
|
||||
|
||||
### Task 1: DDL + Models + Menu SQL
|
||||
### Task 2: pl_io_order_service + arrange/cancel wire + block rearrange
|
||||
### Task 3: Admin + Public APIs
|
||||
### Task 4: Frontend lists/dialogs/public/freeze/archive
|
||||
### Task 5: graphify update + smoke check
|
||||
|
||||
---
|
||||
|
||||
**执行方式:** 本会话 Inline 连续实现(用户已确认「继续」)。
|
||||
@@ -0,0 +1,360 @@
|
||||
# 运输计划 · 保存并安排运输 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:** 在运输计划新增/编辑弹窗增加「创建/保存并安排运输」主按钮(前端串联打开安排弹窗),并在运输计划归档页隐藏编辑入口。
|
||||
|
||||
**Architecture:** 仅前端改动。`PlPlanFormDialog` 保存成功后通过 `success({ planId, openArrange })` 通知列表页;列表页刷新后按需打开现有 `PlPlanArrangeDialog`。危化品门禁复用 `buildMissingHazardCertReadyMessage`(保存后 `GET` 计划详情再判断)。无新后端 API。
|
||||
|
||||
**Tech Stack:** Vue 3 + Element Plus + TypeScript;`@/api/pl`、`@/utils/http/bx`(`pickBxData`)、`usePlToxicProductCertCheck`。
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-07-19-pl-plan-save-and-arrange-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File map
|
||||
|
||||
| 职责 | 路径 |
|
||||
|------|------|
|
||||
| 表单底栏 + 保存/保存并安排 | `web-admin/src/components/business/pl-plan-form-dialog/index.vue` |
|
||||
| 列表接事件并打开安排弹窗 | `web-admin/src/views/pl/plan/index.vue` |
|
||||
| 归档页隐藏编辑 | `web-admin/src/views/pl/plan-archive/index.vue` |
|
||||
| 设计文档(已完成) | `docs/superpowers/specs/2026-07-19-pl-plan-save-and-arrange-design.md` |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: PlPlanFormDialog — 底栏显隐与双提交
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/components/business/pl-plan-form-dialog/index.vue`
|
||||
|
||||
- [ ] **Step 1: 扩展 emit 与编辑态运输状态**
|
||||
|
||||
在 `<script setup>` 中:
|
||||
|
||||
1. 将 `Van` 加入 `@element-plus/icons-vue` 的 import。
|
||||
2. 增加 import:
|
||||
|
||||
```ts
|
||||
import {
|
||||
enrichPlanProductsHazardFromCrm,
|
||||
buildMissingHazardCertReadyMessage
|
||||
} from '@/composables/usePlToxicProductCertCheck'
|
||||
import { pickBxData } from '@/utils/http/bx'
|
||||
```
|
||||
|
||||
(注意:文件已 import `enrichPlanProductsHazardFromCrm`,合并进同一条 import,勿重复。)
|
||||
|
||||
3. 将 emit 改为:
|
||||
|
||||
```ts
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
success: [payload?: { planId: string; openArrange: boolean }]
|
||||
}>()
|
||||
```
|
||||
|
||||
4. 增加 ref,并在 `resetForm` 中清零;在 `loadPlanForEdit` 赋值:
|
||||
|
||||
```ts
|
||||
const loadedTransportStatus = ref('pending')
|
||||
const loadedHazardCertReady = ref(0)
|
||||
|
||||
// resetForm 内:
|
||||
loadedTransportStatus.value = 'pending'
|
||||
loadedHazardCertReady.value = 0
|
||||
|
||||
// loadPlanForEdit 在 Object.assign 之后:
|
||||
loadedTransportStatus.value = data.transportStatus || 'pending'
|
||||
loadedHazardCertReady.value = Number(data.hazardCertReady ?? 0)
|
||||
```
|
||||
|
||||
5. 增加计算属性:
|
||||
|
||||
```ts
|
||||
const showSaveAndArrange = computed(
|
||||
() => !isEdit.value || loadedTransportStatus.value === 'pending'
|
||||
)
|
||||
|
||||
const saveButtonLabel = computed(() => (isEdit.value ? '保存' : '创建'))
|
||||
|
||||
const saveAndArrangeLabel = computed(() =>
|
||||
isEdit.value ? '保存并安排运输' : '创建并安排运输'
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 改 template footer**
|
||||
|
||||
将现有 `#footer` 替换为:
|
||||
|
||||
```vue
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="innerVisible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button
|
||||
:type="showSaveAndArrange ? 'default' : 'primary'"
|
||||
:class="showSaveAndArrange ? 'btn-ghost' : undefined"
|
||||
:loading="submitLoading"
|
||||
@click="handleSubmit('save')"
|
||||
>
|
||||
<el-icon><Check /></el-icon>{{ saveButtonLabel }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="showSaveAndArrange"
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
@click="handleSubmit('saveAndArrange')"
|
||||
>
|
||||
<el-icon><Van /></el-icon>{{ saveAndArrangeLabel }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 重写 `handleSubmit`**
|
||||
|
||||
用下列实现替换现有 `handleSubmit`(保留 `validateBusinessRules` 不变):
|
||||
|
||||
```ts
|
||||
async function handleSubmit(mode: 'save' | 'saveAndArrange' = 'save') {
|
||||
const valid = await basicFormRef.value?.validate().catch(() => false)
|
||||
if (!valid || !validateBusinessRules()) return
|
||||
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const commonPayload = buildPlanCommonPayload({ allowClearTransport: isEdit.value })
|
||||
let planId = props.planId || ''
|
||||
|
||||
if (isEdit.value) {
|
||||
if (!props.planId) return
|
||||
await planApi.update(props.planId, commonPayload)
|
||||
planId = props.planId
|
||||
ElMessage.success('保存成功')
|
||||
} else {
|
||||
const res = await planApi.createFull({
|
||||
...commonPayload,
|
||||
contractId: formData.contractId
|
||||
} as Parameters<typeof planApi.createFull>[0])
|
||||
const created = pickBxData<Plan>(res)
|
||||
planId = String(created?.id || '')
|
||||
if (!planId) {
|
||||
ElMessage.error('创建成功但未返回计划 ID')
|
||||
return
|
||||
}
|
||||
ElMessage.success('创建成功')
|
||||
}
|
||||
|
||||
let openArrange = false
|
||||
if (mode === 'saveAndArrange' && planId) {
|
||||
const detailRes = await planApi.get(planId)
|
||||
const plan = pickBxData<Plan>(detailRes)
|
||||
const rawProducts = Array.isArray(plan?.products) ? plan.products : []
|
||||
const enrichedProducts = await enrichPlanProductsHazardFromCrm(rawProducts)
|
||||
const certMessage = buildMissingHazardCertReadyMessage(plan, enrichedProducts)
|
||||
if (certMessage) {
|
||||
await ElMessageBox.alert(
|
||||
`${certMessage},请至【危化品证上传】页面操作。`,
|
||||
'无法安排运输',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
openArrange = false
|
||||
} else {
|
||||
openArrange = true
|
||||
}
|
||||
}
|
||||
|
||||
innerVisible.value = false
|
||||
emit('success', { planId, openArrange })
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
说明:拦截文案与 `pl-plan-arrange-dialog` 保持一致;`loadedHazardCertReady` 供后续扩展,门禁以 `GET` 详情为准。
|
||||
|
||||
- [ ] **Step 4: 本地目视确认**
|
||||
|
||||
Run: 打开 `http://localhost:3006/#/pl/plan-module/plan`,点「新增计划」,确认底栏为「取消 · 创建 · 创建并安排运输」(后者主色)。
|
||||
|
||||
Expected: 双按钮布局正确;未点提交即可。
|
||||
|
||||
- [ ] **Step 5: Commit(若用户要求提交时再执行)**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/components/business/pl-plan-form-dialog/index.vue
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(pl): add save-and-arrange action on transport plan form dialog
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 运输计划列表 — 串联安排弹窗
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/pl/plan/index.vue`
|
||||
|
||||
- [ ] **Step 1: 扩展 `handlePlanMutationSuccess`**
|
||||
|
||||
将:
|
||||
|
||||
```ts
|
||||
async function handlePlanMutationSuccess() {
|
||||
await fetchData()
|
||||
await refreshViewIfOpen()
|
||||
}
|
||||
```
|
||||
|
||||
替换为:
|
||||
|
||||
```ts
|
||||
async function handlePlanMutationSuccess(payload?: { planId?: string; openArrange?: boolean }) {
|
||||
await fetchData()
|
||||
await refreshViewIfOpen()
|
||||
if (payload?.openArrange && payload.planId) {
|
||||
arrangePlanId.value = payload.planId
|
||||
arrangeDialogVisible.value = true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
说明:`PlPlanArrangeDialog` 的 `@success="handlePlanMutationSuccess"` 无 payload,行为仍为仅刷新,兼容不变。
|
||||
|
||||
- [ ] **Step 2: 手动验收(对照 spec V1–V5)**
|
||||
|
||||
| 用例 | 操作 | Expected |
|
||||
|------|------|----------|
|
||||
| V1 | 新增弹窗底栏 | 创建(描边)+ 创建并安排运输(主色) |
|
||||
| V3 | 普通品「创建并安排运输」 | 保存后自动打开安排运输弹窗 |
|
||||
| V5 | 安排弹窗点取消 | 列表有该计划,运输状态待安排 |
|
||||
| V4 | 含危化品且证书未齐 | 计划已保存;Alert 说明原因;不打开安排 |
|
||||
| V2 | 编辑已安排(直销若仍可编辑) | 无「保存并安排运输」,仅「保存」主色 |
|
||||
|
||||
- [ ] **Step 3: Commit(若用户要求提交时再执行)**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/views/pl/plan/index.vue
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(pl): open arrange dialog after plan save-and-arrange
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 运输计划归档 — 隐藏编辑
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/pl/plan-archive/index.vue`
|
||||
|
||||
- [ ] **Step 1: 去掉工具栏编辑按钮**
|
||||
|
||||
删除模板中约 99–106 行的编辑按钮块:
|
||||
|
||||
```vue
|
||||
<el-button
|
||||
v-if="selectedPlan && selectedPlan.followStatus !== 'cancelled'"
|
||||
class="btn-ghost"
|
||||
:disabled="selectedRows.length !== 1"
|
||||
@click="handleToolbarEdit"
|
||||
>
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-button>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 去掉查看弹窗 hero「编辑」**
|
||||
|
||||
在 `PlPlanViewDialog` 的 `#hero-actions` 中删除编辑按钮(保留归档按钮),即删除:
|
||||
|
||||
```vue
|
||||
<el-button
|
||||
v-if="viewingPlan.followStatus !== 'cancelled'"
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
@click="handleEdit(viewingPlan)"
|
||||
>
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-button>
|
||||
```
|
||||
|
||||
若删除后 hero 仅剩归档按钮,将归档按钮改为 `type="primary"` / `class="btn-main"`(保持一行一个主色)。
|
||||
|
||||
- [ ] **Step 3: 移除闲置表单弹窗与相关代码**
|
||||
|
||||
删除:
|
||||
|
||||
1. 模板中的 `<PlPlanFormDialog ... />`
|
||||
2. `import PlPlanFormDialog ...`
|
||||
3. `import { Edit } from ...`(若已无其它引用)
|
||||
4. `formDialogVisible`、`editingPlanId`
|
||||
5. `handleEdit`、`handleToolbarEdit`
|
||||
6. `handlePlanMutationSuccess` 中若仅服务于表单,可改为只服务归档弹窗的 refresh(保留对 archive dialog `@success` 的刷新逻辑)
|
||||
|
||||
核对:归档弹窗、查看弹窗仍正常工作。
|
||||
|
||||
- [ ] **Step 4: 手动验收 V6**
|
||||
|
||||
Run: 打开 `http://localhost:3006/#/pl/plan-module/plan-archive`
|
||||
|
||||
Expected: 工具栏与查看弹窗均无「编辑」;归档/调整归档仍可用。
|
||||
|
||||
- [ ] **Step 5: Commit(若用户要求提交时再执行)**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/views/pl/plan-archive/index.vue
|
||||
git commit -m "$(cat <<'EOF'
|
||||
fix(pl): hide edit transport plan on archive list page
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 收尾
|
||||
|
||||
**Files:**
|
||||
- 无新文件;按需更新知识图谱
|
||||
|
||||
- [ ] **Step 1: 对照 spec 勾选覆盖**
|
||||
|
||||
| Spec | Task |
|
||||
|------|------|
|
||||
| D1 底栏方案 A | Task 1 |
|
||||
| D2 前端串联 | Task 1–2 |
|
||||
| D3 证书未齐备仍保存 | Task 1 Step 3 |
|
||||
| D4 显隐 pending | Task 1 Step 1 |
|
||||
| D5 取消安排保留待安排 | Task 2(行为天然满足) |
|
||||
| D6 归档隐藏编辑 | Task 3 |
|
||||
| V1–V6 | Task 2 Step 2 + Task 3 Step 4 |
|
||||
|
||||
- [ ] **Step 2: 更新 graphify(仅改了 web-admin)**
|
||||
|
||||
```bash
|
||||
./scripts/update-graph.sh frontend
|
||||
```
|
||||
|
||||
Expected: 脚本成功结束,无报错。
|
||||
|
||||
- [ ] **Step 3: 将 design 状态改为已实现(实现完成后)**
|
||||
|
||||
在 `docs/superpowers/specs/2026-07-19-pl-plan-save-and-arrange-design.md` 将 `**状态:** 待实现` 改为 `**状态:** 已实现`。
|
||||
|
||||
---
|
||||
|
||||
## Spec coverage self-check
|
||||
|
||||
- D1–D6、V1–V6 均有对应 Task/步骤。
|
||||
- 无 TBD /「类似 Task N」占位。
|
||||
- `success` payload 形状在 Task 1 与 Task 2 一致:`{ planId, openArrange }`。
|
||||
- 归档页不再挂载表单,故无需处理归档页 `openArrange`。
|
||||
|
||||
## Out of scope(勿做)
|
||||
|
||||
- 后端合并接口、表单内嵌安排区、「保存并调整运输」、修改安排弹窗内部逻辑。
|
||||
Reference in new issue
Block a user