394 lines
14 KiB
Markdown
394 lines
14 KiB
Markdown
# 地磅系统移植 Implementation Plan
|
||
|
||
> **⚠️ 策略已修订(2026-06-07)**
|
||
> 旧版「CRM 式重写 `/api/web/admin/db/*`」代码已删除。
|
||
> **权威实施方案**:[`docs/db-migration/00-migration-plan.md`](../../db-migration/00-migration-plan.md)(**原样搬运 + 门户适配**,对齐 bx 思路)
|
||
> 端点清单:[`docs/db-migration/01-api-inventory.md`](../../db-migration/01-api-inventory.md)
|
||
|
||
> **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:** 将原地磅系统五模块(仪表盘、计划、出入库、地磅单、车牌)迁入 shanxia2.0,含 4 张 `db_*` 表、后端联动逻辑与 Fresh Mint 前端。
|
||
|
||
**Architecture:** 参照 bx 子域模式,新建 `app/api/db/` + `app/services/db/`;主数据方案 A(CRM 下拉写名称文本);地磅保存事务内更新计划并生成出入库单。
|
||
|
||
**Tech Stack:** Python 3.9 FastAPI · SQLAlchemy · Pydantic v2 · Vue 3 · Element Plus · MySQL 5.7
|
||
|
||
> **设计文档**: `docs/superpowers/specs/2026-06-07-dibang-migration-design.md`
|
||
> **OpenSpec**: `openspec/changes/dibang-migration/`
|
||
|
||
---
|
||
|
||
## File Map
|
||
|
||
### SQL — 新建
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `docs/sql/db_tables.sql` | 四张 `db_*` 表 DDL |
|
||
| `docs/sql/db_menu_permissions.sql` | 地磅菜单与按钮权限 |
|
||
| `scripts/migrate_dibang_data.py` | 历史数据 int→UUID 迁移 |
|
||
|
||
### Backend — 新建
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `backend/app/models/db_models.py` | 4 个 SQLAlchemy 模型 |
|
||
| `backend/app/services/db/base.py` | DbCrudService 基类 |
|
||
| `backend/app/services/db/vehicle.py` | 车牌业务 |
|
||
| `backend/app/services/db/plan.py` | 计划业务 + plan_no 生成 |
|
||
| `backend/app/services/db/inventory.py` | 出入库业务 |
|
||
| `backend/app/services/db/weighing.py` | 地磅单 + 联动事务 |
|
||
| `backend/app/services/db/dashboard.py` | 统计聚合 |
|
||
| `backend/app/schemas/db/*.py` | Pydantic schemas |
|
||
| `backend/app/api/db/__init__.py` | db_router 聚合 |
|
||
| `backend/app/api/db/vehicle.py` | 车牌路由 |
|
||
| `backend/app/api/db/plan.py` | 计划路由 |
|
||
| `backend/app/api/db/inventory.py` | 出入库路由 |
|
||
| `backend/app/api/db/weighing.py` | 地磅路由 |
|
||
| `backend/app/api/db/dashboard.py` | 仪表盘路由 |
|
||
| `backend/app/tasks/db_auto_plan.py` | 按月自动生成计划 |
|
||
|
||
### Backend — 修改
|
||
| 文件 | 变更 |
|
||
|------|------|
|
||
| `backend/app/api/__init__.py` | export db_router |
|
||
| `backend/app/main.py` | include_router(db_router) + import db_models |
|
||
|
||
### Frontend — 新建
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `web-admin/src/api/db/*.ts` | 5 个 API 模块 |
|
||
| `web-admin/src/views/db/vehicle/index.vue` | 车牌 P01 |
|
||
| `web-admin/src/views/db/plan/index.vue` | 计划 P02 |
|
||
| `web-admin/src/views/db/inventory/index.vue` | 出入库 P02 |
|
||
| `web-admin/src/views/db/weighing/index.vue` | 地磅 P02 |
|
||
| `web-admin/src/views/db/dashboard/index.vue` | 仪表盘 |
|
||
|
||
### Frontend — 修改
|
||
| 文件 | 变更 |
|
||
|------|------|
|
||
| `web-admin/src/router/modules/sqxp.ts`(或等价) | 地磅菜单路由 |
|
||
|
||
### 参考源码(只读)
|
||
| 路径 | 用途 |
|
||
|------|------|
|
||
| `simple-dibang-xiaoshou/backend/app/api/routes/weighing.py` | 联动逻辑 |
|
||
| `simple-dibang-xiaoshou/backend/app/api/routes/plans.py` | 计划号生成 |
|
||
| `simple-dibang-xiaoshou/art-design-pro/src/views/weighing/index.vue` | 字段与交互 |
|
||
| `backend/app/services/bx/base.py` | CRUD 基类模式 |
|
||
| `web-admin/src/views/system/order/index.vue` | 列表 UI 模板 |
|
||
|
||
---
|
||
|
||
## Task 1: 编写四表 DDL
|
||
|
||
**Files:**
|
||
- Create: `docs/sql/db_tables.sql`
|
||
- Modify: `docs/sql/shanxia2.0.sql`(追加四表定义)
|
||
|
||
- [x] **Step 1: 编写 `db_vehicle` 表**
|
||
|
||
```sql
|
||
CREATE TABLE `db_vehicle` (
|
||
`id` varchar(64) NOT NULL COMMENT '主键ID(UUID)',
|
||
`vehicle_no` varchar(255) NOT NULL COMMENT '车牌号',
|
||
`tenant_no` varchar(64) NOT NULL DEFAULT 'shanxia2.0',
|
||
`order_num` int(11) DEFAULT NULL,
|
||
`remark` varchar(512) DEFAULT NULL,
|
||
`create_by` varchar(64) DEFAULT NULL,
|
||
`create_dept_id` varchar(64) NOT NULL DEFAULT '',
|
||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`update_by` varchar(64) DEFAULT NULL,
|
||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
`del_flag` smallint(6) NOT NULL DEFAULT '0',
|
||
PRIMARY KEY (`id`),
|
||
KEY `idx_vehicle_no` (`vehicle_no`),
|
||
KEY `idx_tenant` (`tenant_no`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='地磅-车牌';
|
||
```
|
||
|
||
- [x] **Step 2: 编写 `db_purchase_plan` 表**(业务字段参照 `dibang.sql` purchase_plans,关联 ID 改 VARCHAR(64),日期改 DATE/DATETIME)
|
||
|
||
- [x] **Step 3: 编写 `db_weighing_slip` 表**(保留 `is_voided`,关联 ID VARCHAR(64))
|
||
|
||
- [x] **Step 4: 编写 `db_inbound_outbound` 表**
|
||
|
||
- [ ] **Step 5: 在 MySQL 执行 DDL 验证**
|
||
|
||
Run: `mysql -u... shanxia2.0 < docs/sql/db_tables.sql`
|
||
Expected: `SHOW TABLES LIKE 'db_%';` 返回 4 行
|
||
|
||
- [x] **Step 6: 将 DDL 合并进 `docs/sql/shanxia2.0.sql`**
|
||
|
||
---
|
||
|
||
## Task 2: SQLAlchemy 模型
|
||
|
||
**Files:**
|
||
- Create: `backend/app/models/db_models.py`
|
||
- Modify: `backend/app/main.py`
|
||
|
||
- [ ] **Step 1: 创建 DbVehicle 模型**
|
||
|
||
```python
|
||
from app.core.database import Base
|
||
from sqlalchemy import Column, String, DateTime, Integer, SmallInteger
|
||
from datetime import datetime
|
||
|
||
class DbVehicle(Base):
|
||
__tablename__ = 'db_vehicle'
|
||
id = Column(String(64), primary_key=True)
|
||
vehicle_no = Column(String(255), nullable=False, index=True)
|
||
tenant_no = Column(String(64), nullable=False, default='shanxia2.0', index=True)
|
||
order_num = Column(Integer)
|
||
remark = Column(String(512))
|
||
create_by = Column(String(64))
|
||
create_dept_id = Column(String(64), nullable=False, default='')
|
||
create_time = Column(DateTime, nullable=False, default=datetime.now)
|
||
update_by = Column(String(64))
|
||
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
|
||
del_flag = Column(SmallInteger, nullable=False, default=0)
|
||
```
|
||
|
||
- [ ] **Step 2: 创建 DbPurchasePlan、DbWeighingSlip、DbInboundOutbound**(字段与 DDL 一一对应)
|
||
|
||
- [ ] **Step 3: 在 main.py 添加** `import app.models.db_models # noqa: F401`
|
||
|
||
---
|
||
|
||
## Task 3: DbCrudService 与 db_router 骨架
|
||
|
||
**Files:**
|
||
- Create: `backend/app/services/db/base.py`
|
||
- Create: `backend/app/api/db/__init__.py`
|
||
- Modify: `backend/app/api/__init__.py`, `backend/app/main.py`
|
||
|
||
- [ ] **Step 1: 复制并改名 BxCrudService → DbCrudService**(`backend/app/services/bx/base.py`)
|
||
|
||
- [ ] **Step 2: 创建 db_router 并注册 health 端点**
|
||
|
||
```python
|
||
from fastapi import APIRouter
|
||
db_router = APIRouter(tags=['地磅管理'])
|
||
|
||
@db_router.get('/db/health')
|
||
def db_health():
|
||
return {'code': 0, 'data': {'status': 'ok', 'module': 'db'}}
|
||
```
|
||
|
||
- [ ] **Step 3: main.py 注册** `app.include_router(db_router, prefix=settings.API_V1_PREFIX)`
|
||
|
||
- [ ] **Step 4: 启动后端验证** `./start-local.sh`,访问 `/docs` 可见 db health
|
||
|
||
---
|
||
|
||
## Task 4: 车牌 CRUD(后端 + 前端)
|
||
|
||
**Files:**
|
||
- Create: `backend/app/schemas/db/vehicle.py`
|
||
- Create: `backend/app/services/db/vehicle.py`
|
||
- Create: `backend/app/api/db/vehicle.py`
|
||
- Create: `web-admin/src/api/db/vehicle.ts`
|
||
- Create: `web-admin/src/views/db/vehicle/index.vue`
|
||
|
||
- [ ] **Step 1: 实现 `_row_to_dict` + list/create/update/soft_delete**(tenant_no 过滤,vehicle_no 唯一校验)
|
||
|
||
- [ ] **Step 2: 路由** `GET/POST/PUT/DELETE /api/.../db/vehicles`(路径与现有 admin 风格对齐)
|
||
|
||
- [ ] **Step 3: 前端 API 函数** `fetchVehicleList`, `fetchCreateVehicle`, ...
|
||
|
||
- [ ] **Step 4: 前端页面** 参照 `web-admin/src/views/system/unit/index.vue`:筛选、工具栏、表格、分页、弹窗
|
||
|
||
- [ ] **Step 5: 注册路由** `/#/db/vehicle`
|
||
|
||
- [ ] **Step 6: 手工验证** 新增/编辑/删除车牌
|
||
|
||
---
|
||
|
||
## Task 5: 计划管理(后端 + 前端)
|
||
|
||
**Files:**
|
||
- Create: `backend/app/schemas/db/plan.py`
|
||
- Create: `backend/app/services/db/plan.py`
|
||
- Create: `backend/app/api/db/plan.py`
|
||
- Create: `web-admin/src/api/db/plan.ts`
|
||
- Create: `web-admin/src/views/db/plan/index.vue`
|
||
|
||
- [ ] **Step 1: 移植 plan_no 生成逻辑**(参照 `simple-dibang-xiaoshou/backend/app/api/routes/plans.py` 中生成规则)
|
||
|
||
```python
|
||
def generate_plan_no() -> str:
|
||
from datetime import datetime
|
||
return f"SX-{datetime.now().strftime('%Y%m%d%H%M%S')}{'001'}" # 对齐原系统序号逻辑
|
||
```
|
||
|
||
- [ ] **Step 2: 实现计划 CRUD + 分页筛选**(business_type、execution_status、plan_no、日期范围)
|
||
|
||
- [ ] **Step 3: 表单支持 auto_generate_enabled / auto_generate_end_month**
|
||
|
||
- [ ] **Step 4: 前端列表** 参照 order 模板;执行状态用 `<el-tag>`
|
||
|
||
- [ ] **Step 5: 表单产品/客户下拉** 调用 CRM API,选中写入 `product`、`customer_name` 文本
|
||
|
||
- [ ] **Step 6: 验证** 创建计划 → 列表可见 → 编辑 → 软删除
|
||
|
||
---
|
||
|
||
## Task 6: 出入库管理(后端 + 前端)
|
||
|
||
**Files:**
|
||
- Create: `backend/app/schemas/db/inventory.py`
|
||
- Create: `backend/app/services/db/inventory.py`
|
||
- Create: `backend/app/api/db/inventory.py`
|
||
- Create: `web-admin/src/api/db/inventory.ts`
|
||
- Create: `web-admin/src/views/db/inventory/index.vue`
|
||
|
||
- [ ] **Step 1: 实现出入库 CRUD**(字段映射原 inbound_outbound)
|
||
|
||
- [ ] **Step 2: 列表筛选** date、partner_company、business_type、status、vehicle_no
|
||
|
||
- [ ] **Step 3: 前端页面** P02 模板 + 状态 Tag
|
||
|
||
- [ ] **Step 4: 验证** 独立 CRUD 可用(联动在 Task 7 验收)
|
||
|
||
---
|
||
|
||
## Task 7: 地磅单管理与联动(核心)
|
||
|
||
**Files:**
|
||
- Create: `backend/app/schemas/db/weighing.py`
|
||
- Create: `backend/app/services/db/weighing.py`
|
||
- Create: `backend/app/api/db/weighing.py`
|
||
- Create: `web-admin/src/api/db/weighing.ts`
|
||
- Create: `web-admin/src/views/db/weighing/index.vue`
|
||
|
||
- [ ] **Step 1: 阅读原 `weighing.py` 全部保存/作废逻辑**,列出函数清单
|
||
|
||
- [ ] **Step 2: 实现 `save_weighing_slip` 服务方法(单事务)**
|
||
|
||
```python
|
||
def save_weighing_slip(db, tenant_no, user_id, dept_id, payload):
|
||
# 1. 创建或更新 db_weighing_slip
|
||
# 2. 若有关联 plan:更新 executed_quantity, execution_status, last_weighing_slip_id
|
||
# 3. 创建或更新 db_inbound_outbound,回写 inbound_outbound_id
|
||
# 4. db.commit() — 任一步失败 rollback
|
||
```
|
||
|
||
- [ ] **Step 3: 实现 `void_weighing_slip`**:is_voided=1 + 回滚计划量
|
||
|
||
- [ ] **Step 4: API 路由** list / get / post / put / post:void
|
||
|
||
- [ ] **Step 5: 前端表单** 毛重/皮重/扣杂/净重/本次量;CRM 产品客户下拉;车牌下拉
|
||
|
||
- [ ] **Step 6: 全链路验证**
|
||
|
||
Run 手工流程:创建计划 → 保存地磅单 → 检查计划 executed_quantity 变化 → 检查出入库单生成
|
||
|
||
Expected: 三表数据一致,单位 kg/t 与原系统行为相同
|
||
|
||
---
|
||
|
||
## Task 8: 仪表盘
|
||
|
||
**Files:**
|
||
- Create: `backend/app/services/db/dashboard.py`
|
||
- Create: `backend/app/api/db/dashboard.py`
|
||
- Create: `web-admin/src/api/db/dashboard.ts`
|
||
- Create: `web-admin/src/views/db/dashboard/index.vue`
|
||
|
||
- [ ] **Step 1: 移植原 dashboard 统计 SQL/聚合**(参照 `simple-dibang-xiaoshou/backend/app/api/routes/dashboard.py`)
|
||
|
||
- [ ] **Step 2: 返回概览:计划数、地磅单数、出入库数、近期趋势**
|
||
|
||
- [ ] **Step 3: 前端 dashboard 页渲染卡片 + 图表(ECharts 与项目现有用法一致)**
|
||
|
||
---
|
||
|
||
## Task 9: 计划自动生成定时任务
|
||
|
||
**Files:**
|
||
- Create: `backend/app/tasks/db_auto_plan.py`
|
||
- Modify: `backend/app/tasks/__init__.py`
|
||
|
||
- [ ] **Step 1: 移植 `auto_create_monthly_plan.py` 核心查询与复制逻辑**
|
||
|
||
- [ ] **Step 2: 注册为 APScheduler 每月 1 日 01:30 任务(cron 可配置)**
|
||
|
||
- [ ] **Step 3: 提供手动触发入口或管理命令用于测试**
|
||
|
||
Run: 手动调用 task 函数
|
||
Expected: 模板计划生成新记录,`auto_generate_last_month` 更新
|
||
|
||
---
|
||
|
||
## Task 10: 菜单权限与路由
|
||
|
||
**Files:**
|
||
- Create: `docs/sql/db_menu_permissions.sql`
|
||
- Modify: `web-admin/src/router/modules/sqxp.ts`
|
||
|
||
- [ ] **Step 1: SQL 插入「地磅管理」一级菜单及 5 个子菜单**
|
||
|
||
- [ ] **Step 2: 按钮权限** db:plan:list/add/edit/remove 等
|
||
|
||
- [ ] **Step 3: 前端 hash 路由** `#/db/dashboard`, `#/db/plan`, ...
|
||
|
||
- [ ] **Step 4: 管理员角色绑定菜单**
|
||
|
||
---
|
||
|
||
## Task 11: 历史数据迁移
|
||
|
||
**Files:**
|
||
- Create: `scripts/migrate_dibang_data.py`
|
||
|
||
- [ ] **Step 1: 读取原库四表导出 CSV 或直连 dibang-test**
|
||
|
||
- [ ] **Step 2: 生成 UUID,写 mapping 表 `{table, old_id, new_id}`**
|
||
|
||
- [ ] **Step 3: 按依赖顺序导入** plan → inbound_outbound → weighing → vehicle
|
||
|
||
- [ ] **Step 4: 校验** 记录数一致;抽样检查关联 ID
|
||
|
||
Run: `python scripts/migrate_dibang_data.py --dry-run`
|
||
Expected: 打印将迁移行数,无 orphan 关联
|
||
|
||
---
|
||
|
||
## Task 12: 收尾验证
|
||
|
||
- [ ] **Step 1: 运行 field consistency 检查**(若 db 模块纳入脚本)
|
||
|
||
- [ ] **Step 2: `./scripts/update-graph.sh all`**
|
||
|
||
- [ ] **Step 3: 前端 lint** `pnpm lint`(仅 db 相关文件)
|
||
|
||
- [ ] **Step 4: 对照原系统 5 个核心场景写验收清单并逐项通过**
|
||
|
||
---
|
||
|
||
## Spec Coverage Checklist
|
||
|
||
| Spec 要求 | Task |
|
||
|-----------|------|
|
||
| 四表 DDL + 公共字段 | Task 1 |
|
||
| 历史迁移脚本 | Task 11 |
|
||
| 车牌 CRUD | Task 4 |
|
||
| 计划 CRUD + plan_no | Task 5 |
|
||
| 出入库 CRUD | Task 6 |
|
||
| 地磅联动 + 作废 | Task 7 |
|
||
| 仪表盘 API | Task 8 |
|
||
| auto_generate 定时任务 | Task 9 |
|
||
| 前端五页 + Fresh Mint | Task 4–8, 10 |
|
||
| CRM 下拉方案 A | Task 5, 7 |
|
||
| ecosystem 代码路径 | OpenSpec delta(已写入) |
|
||
|
||
---
|
||
|
||
**Plan complete.** 实施时可按 Task 1→12 顺序执行;Task 7 为风险最高项,建议单独评审原 `weighing.py` 后再编码。
|
||
|
||
**执行方式:**
|
||
|
||
1. **Subagent-Driven(推荐)** — 每 Task 派发子 agent,任务间 Review
|
||
2. **Inline Execution** — 本会话按 Task 批量实现,检查点暂停
|
||
|
||
如需开始实现,请说明采用哪种方式,或直接说「开始 Task 1」。
|