Files
shanxia2.0/docs/superpowers/plans/2026-06-07-dibang-migration.md
T
2026-06-10 22:59:34 +08:00

394 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 地磅系统移植 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」。