Files
shanxia2.0/docs/superpowers/plans/2026-06-29-creator-only-delete.md
T
2026-06-29 11:15:39 +08:00

265 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.
# 仅创建人可删除 — 删除接口 / 页面对照表与分阶段改造方案
> **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:** 业务数据删除时,仅允许 `create_by === 当前用户`(`super_admin` 可例外);后端强制校验,前端按创建人显隐/禁用删除按钮。
**Architecture:** 后端新增统一 `assert_row_deletable_by_creator()`,在各模块 `soft_delete` / `delete_*` 入口调用;前端新增 `canDeleteByCreator()` 工具与可选 `CreatorDeleteButton` 包装;列表批量删除过滤非本人数据并提示。系统管理类主数据默认**不纳入**本规则(见 §4)。
**Tech Stack:** FastAPI + SQLAlchemy(backend)、Vue 3 + Element Plus(web-admin)
**默认产品假设(实施前需客户签字确认):**
| 项 | 默认 |
|----|------|
| 超管 | `super_admin` 可删除任意业务数据 |
| 系统管理 | 租户/用户/角色/菜单/部门/字典/站点/职位/流程定义/请假 — **不适用**创建人规则 |
| 子表 | 跟进、联系人、开票信息、商机产品等 — **适用**创建人规则 |
| 历史空 `create_by` | 仅 `super_admin` 可删;普通用户不可删 |
| 比对字段 | 后端用 `user_id`(UUID);前端用 `row.createBy`,禁止用 `realName` |
---
## 1. 统计摘要
| 类别 | 后端删除接口(约) | 前端含「删除」页面(约) |
|------|-------------------|-------------------------|
| CRM 业务 | 18 | 22 |
| PL 物流 | 8 | 9 |
| DB 地磅 | 3 | 2 |
| BX 报销 | 5 | 5 |
| 系统管理 | 11 | 11 |
| **UI 本地删除(不改)** | — | ~8 组件/页 |
| **合计(业务范围内)** | **34** | **38** |
---
## 2. 需改造对照表(业务数据 — 纳入范围)
### 2.1 CRM
| # | 实体 | 后端接口 | 后端实现位置 | 前端 API | 前端页面 / 入口 | 批量删除 | 现状 |
|---|------|----------|--------------|----------|-----------------|----------|------|
| C01 | 线索 | `POST /web/crm/clue/delete` | `crm.py` → `soft_delete_clue` | `delClue` | `crm/clue/index.vue` 行删 | 否 | 仅 data_scope |
| C02 | 线索 | 同上 | 同上 | `delClue` | `crm/clue/detailDrawer.vue` 主删 | — | 无创建人校验 |
| C03 | 线索跟进 | `POST /web/crm/clue/follow/delete` | `delete_clue_follow` | `delClueFollow` | `detailDrawer.vue` 跟进删 | 否 | 前端 `canManageFollow`(createBy);后端 data_scope |
| C04 | 客户 | `POST /web/crm/customer/delete` | `soft_delete_customer` | `fetchDeleteCustomer` | `crm/customer/index.vue` | 否 | 无 |
| C05 | 客户 | 同上 | 同上 | 同上 | `CustomerDetailDialog.vue` 主删 | — | 无 |
| C06 | 客户跟进 | `POST /web/crm/customer/follow/delete` | `soft_delete_follow` | `fetchDeleteCustomerFollow` | `CustomerDetailDialog.vue` | 否 | 前端 realName+admin(脆弱) |
| C07 | 联系人 | `POST /web/crm/contact/delete` | `soft_delete_contact` | `fetchDeleteContact` | `CustomerDetailDialog.vue`、`ContactList.vue` | 否 | 无 |
| C08 | 开票信息 | `POST /web/crm/invoice-info/delete` | `soft_delete_invoice_info` | `delInvoiceInfo` | `InvoiceInfo.vue` | 否 | 无 |
| C09 | 商机 | `POST /web/crm/opportunity/delete` | `soft_delete_opportunities` | `delOpportunity` | `opportunity/index.vue` | 否 | 无 |
| C10 | 商机 | 同上 | 同上 | 同上 | `OpportunityDetailDrawer.vue` 主删 | — | 无 |
| C11 | 商机跟进 | `POST /web/crm/opportunity/follow/delete` | `soft_delete_opportunity_follow` | `delOpportunityFollow` | `OpportunityDetailDrawer.vue` | 否 | 无 |
| C12 | 商机产品 | `POST /web/crm/opportunity/product/delete` | `soft_delete_opportunity_products` | `delOpportunityProduct` | 表单内(若有持久化删) | 否 | 无 |
| C13 | 报价 | `POST /web/crm/quote/delete` | `soft_delete_quotes` | `delQuote` | `quote/index.vue` | 是 | 无 |
| C14 | 报价 | 同上 | 同上 | 同上 | `quote/detail.vue`、`QuoteDetailDialog.vue` | — | 无 |
| C15 | 报价产品 | `POST /web/crm/quote/product/delete` | `soft_delete_quote_products` | `delQuoteProduct` | 已保存明细删(若有) | 否 | 无;表单内行删见 §3 |
| C16 | 合同 | `DELETE /web/crm/contract/{ids}` | `soft_delete_contracts` | `delContract` | `contract/index.vue` | 是 | 无 |
| C17 | 合同 | 同上 | 同上 | 同上 | `contract/detail.vue`、`ContractDetailDialog.vue` | — | 无 |
| C18 | 合同变更 | `DELETE /web/crm/contract/change/{ids}` | `soft_delete_contract_changes` | `delChange` | `contract/change/index.vue` | 是 | 无 |
| C19 | 合同模板 | `DELETE /web/crm/contract/template/{ids}` | `soft_delete_contract_templates` | `delTemplate` | `contract/template/index.vue` | 是 | 无 |
| C20 | 产品 | `POST /web/crm/products/delete` | `soft_delete_product` | `fetchDeleteProduct` | `crm/product/index.vue` | 是 | 无 |
| C21 | 产品分类 | `POST /web/crm/product-categories/delete` | `soft_delete_category` | `fetchDeleteProductCategory` | `crm/product-category/index.vue` | 否 | 无 |
| C22 | 公海规则 | `POST /web/crm/customer/pool/rule/delete` | `delete_pool_rules` | `fetchDeletePoolRule` | `customer/pool/RuleIndex.vue` | 否 | 无 |
| C23 | 供应商信息 | `POST /web/crm/supplierInfo/delete` | `soft_delete_supplier_info` | `delSupplierInfo` | **暂无独立列表删入口** | — | 预留后端 |
### 2.2 PL 物流
| # | 实体 | 后端接口 | 后端实现位置 | 前端 API | 前端页面 | 批量 | 现状 |
|---|------|----------|--------------|----------|----------|------|------|
| P01 | 承运商 | `DELETE /transport/carrier/{id}` | `pl/base_data.py` | `carrierApi.delete` | `pl/carrier/index.vue` | 否 | data_scope |
| P02 | 车辆 | `DELETE /transport/vehicle/{id}` | 同上 | `vehicleApi.delete` | `pl/vehicle/index.vue` | 否 | data_scope |
| P03 | 自有车辆 | 同上 | 同上 | 同上 | `pl/self-vehicle/index.vue` | 否 | data_scope |
| P04 | 人员 | `DELETE /transport/personnel/{id}` | 同上 | `personnelApi.delete` | `pl/personnel/index.vue` | 否 | data_scope |
| P05 | 司机 | `DELETE /transport/driver/{id}` | 同上 | `driverApi.delete` | `pl/driver/index.vue` | 否 | data_scope |
| P06 | 车辆-司机关联 | `DELETE /transport/vehicle-driver/{id}` | 同上 | (内嵌于车辆/人员维护) | 车辆/人员表单内 | 否 | data_scope |
| P07 | 运输计划 | `DELETE /transport/plan/{id}` | `pl/plan.py` | `planApi.delete` | `pl/plan/index.vue` | 否 | data_scope |
| P08 | 运费结算单 | `DELETE /transport/freight/{id}` | `pl/freight.py` → `delete_draft_settlement` | `freightApi.delete` | `pl/freight/SettlementBillPanel.vue` | 否 | 仅草稿;无 create_by 校验 |
### 2.3 DB 地磅
| # | 实体 | 后端接口 | 后端实现位置 | 前端 API | 前端页面 | 现状 |
|---|------|----------|--------------|----------|----------|------|
| D01 | 库存记录 | `DELETE /inventory/{id}` | `db/inventory.py` | `inventoryApi.delete` | `db/inventory/index.vue` | 前端日期规则;后端 data_scope |
| D02 | 地磅计划 | `DELETE /plans/{id}` | `db/plans.py` | `plansApi.delete` | `db/plans/index.vue` | data_scope |
| D03 | 过磅记录 | `DELETE /weighing/{id}` | `db/weighing.py` | `weighingApi.delete` | 公开页若有 | data_scope |
### 2.4 BX 报销
| # | 实体 | 后端接口 | 后端实现位置 | 前端 API | 前端页面 | 现状 |
|---|------|----------|--------------|----------|----------|------|
| B01 | 报销申请 | `DELETE /reimburses/{id}` | `bx/reimburse.py` → `ReimburseService.soft_delete` | `reimburseApi.delete` | `bx/reimburse/apply/index.vue` | data_scope |
| B02 | 预支申请 | `DELETE /advances/{id}` | `bx/advance.py` | `advanceApi.delete` | `bx/advance/apply/index.vue` | data_scope |
| B03 | 费用项 | `DELETE /expense-items/{id}` | `bx/expense_item.py` | `expenseItemApi.delete` | `bx/expense-item/index.vue` | data_scope |
| B04 | 附件 | `DELETE /attachments/{id}` | `bx/attachment.py` | `attachmentApi.delete` | `BxAttachmentUpload` 等 | 随主单;删附件也需创建人 |
| B05 | BX 工作流配置 | `DELETE /workflows/{id}` | `bx/workflow.py` | `workflowApi.delete` | `bx/workflow/reimburse|advance/index.vue` | **建议归入系统配置,默认不改造** |
---
## 3. 不纳入改造(UI 本地 / 非持久化删除)
| 页面 / 组件 | 说明 |
|-------------|------|
| `QuoteFormPanel.vue` `handleDeleteProducts` / `handleRemoveProduct` | 编辑表单内未提交行,非 API 删库 |
| `contract/form.vue` 行项删除 | 同上 |
| `ContractAttachmentPanel.vue` | 表单附件列表本地 splice |
| `attachment-upload-field`、`BxAttachmentUpload` | 未保存附件卡片 |
| `FlowDesigner.vue` | 流程设计器节点/连线 |
| `system/user/detail.vue` 地址删除 | 本地 mock 数组 |
| `art-global-search` 搜索历史 | 本地 storage |
---
## 4. 默认排除(系统管理 — 待客户确认是否纳入)
| # | 实体 | 后端接口 | 前端页面 |
|---|------|----------|----------|
| S01 | 租户 | `DELETE /web/admin/tenants/{id}` | `system/tenant/index.vue` |
| S02 | 用户 | `DELETE /web/admin/users/{id}` | `system/user/index.vue` |
| S03 | 角色 | `DELETE /web/admin/role-management/{id}` | `system/role/index.vue` |
| S04 | 菜单 | `DELETE /web/admin/menus/{id}` | `system/menu/index.vue` |
| S05 | 部门 | `DELETE /web/admin/depts/{id}` | `system/dept/index.vue` |
| S06 | 字典类型/数据 | `DELETE .../dict-types|dict-data/{id}` | `system/dict/index.vue` |
| S07 | 站点 | `DELETE /web/admin/sites/{id}` | `system/site/index.vue` |
| S08 | 职位 | `DELETE /web/admin/posts/{id}` | `system/post/index.vue` |
| S09 | 流程定义 | `DELETE /web/admin/workflow/defs/{id}` | `system/workflow/process/index.vue` |
| S10 | 请假 | `DELETE /web/admin/leave/{id}` | `system/leave/index.vue` |
---
## 5. 分阶段改造方案
### Phase 0 — 基础设施(必须先做)
**后端**
- [ ] 新建 `backend/app/core/delete_guard.py`
- `assert_row_deletable_by_creator(row, user_info, *, message="仅创建人可删除")`
- `assert_rows_deletable_by_creator(rows, user_info)` — 批量时全量校验
- `super_admin` bypass;`create_by` 为空时仅超管可删
- [ ] 单元测试 `backend/tests/test_delete_guard.py`(创建人匹配 / 不匹配 / 超管 / 空 create_by)
**前端**
- [ ] 新建 `web-admin/src/utils/creator-delete.ts`
- `getCurrentUserId()` — 从 `useUserStore().info` 取 `userId` / `id`
- `canDeleteByCreator(row, currentUserId?)` — 比对 `row.createBy`
- `filterDeletableRows(rows)` — 批量删前过滤
- `CREATOR_DELETE_TIP = '仅创建人可删除'`
- [ ] 可选:`web-admin/src/components/business/creator-delete-button/index.vue` — 统一 disabled + tooltip
**文档**
- [ ] OpenSpec 变更提案:`openspec/changes/creator-only-delete/`(permission + api conventions 各一条 Requirement)
---
### Phase 1 — CRM 主实体(优先级最高)
**后端(每个 soft_delete 入口加 assert)**
- [ ] C01–C02 线索 — `soft_delete_clue`
- [ ] C04–C05 客户 — `soft_delete_customer`
- [ ] C09–C10 商机 — `soft_delete_opportunities`
- [ ] C13–C14 报价 — `soft_delete_quotes`
- [ ] C16–C17 合同 — `soft_delete_contracts`
- [ ] C18 合同变更 — `soft_delete_contract_changes`
- [ ] C19 合同模板 — `soft_delete_contract_templates`
**前端**
- [ ] 上表对应 10 个页面/抽屉:行删 / 工具栏删 `v-if="canDeleteByCreator(row)"`
- [ ] 批量删(报价/合同/变更/模板):选中时过滤 + `ElMessage.warning` 提示跳过条数
- [ ] 列表接口确认返回 `createBy`(缺则补 `_row_to_dict`)
**验收**
- [ ] 用户 A 创建、用户 B(同部门、有删权限)调用 API 返回 403
- [ ] 用户 A 可删自己的数据;超管可删任意
---
### Phase 2 — CRM 子实体 + 主数据
**后端**
- [ ] C03/C06–C08/C11–C12 跟进、联系人、开票、商机产品
- [ ] C20–C22 产品、分类、公海规则
- [ ] C23 供应商(仅后端,无 UI 也先加固)
**前端**
- [ ] 替换 `CustomerDetailDialog` 跟进删:`realName` → `canDeleteByCreator`
- [ ] 统一 `clue/detailDrawer` `canManageFollow` 改用公共 util
- [ ] `InvoiceInfo`、`ContactList`、`product`、`product-category`、`RuleIndex`
---
### Phase 3 — PL + DB
**后端**
- [ ] P01–P08:`delete_carrier/vehicle/personnel/driver/plan` + `delete_draft_settlement` 加 create_by
- [ ] P06 `vehicle-driver` 删关联
- [ ] D01–D03 库存/计划/过磅
**前端**
- [ ] PL 9 页 + DB 2 页按钮控制
- [ ] `db/inventory`:**保留**现有日期规则,与创建人规则 **AND** 关系
---
### Phase 4 — BX 报销
**后端**
- [ ] B01–B04:在 `BxCrudService.soft_delete` 或各 Service 包装层加 create_by(注意 BX 部分历史 `create_by` 存 name 的脏数据,需迁移或兼容比对)
**前端**
- [ ] `bx/reimburse/apply`、`bx/advance/apply`、`bx/expense-item`、附件组件
**备注:** B05 BX 工作流配置默认跳过;若客户要求纳入,单独开 Phase 4b。
---
### Phase 5 — 系统管理(可选,需客户确认)
- [ ] 若确认纳入:S01–S10 后端 + 11 个 system 页面
- [ ] 若不纳入:在帮助文档注明「系统管理不受创建人删除限制」
---
## 6. 批量删除统一行为
| 场景 | 前端 | 后端 |
|------|------|------|
| 选中含他人数据 | 禁用批量删或只提交本人 id 并提示 | 对每条 assert;部分失败返回明细 |
| 选中全无 createBy | 普通用户禁用;超管可删 | 空 create_by 仅超管 |
| 混合选中 | 推荐:过滤后仅删本人,Message 汇总 | 事务:建议**全失败**或**逐条**(与 CRM 现有 delClue 部分失败风格对齐) |
---
## 7. 实施顺序与估时
| Phase | 范围 | 后端 | 前端 | 估时 |
|-------|------|------|------|------|
| 0 | 基础设施 | 0.5d | 0.5d | 1d |
| 1 | CRM 主实体 | 1d | 1.5d | 2.5d |
| 2 | CRM 子实体 | 1d | 1d | 2d |
| 3 | PL + DB | 1d | 1d | 2d |
| 4 | BX | 1d | 0.5d | 1.5d |
| 5 | 系统管理(可选) | 1d | 1d | 2d |
**建议迭代:** 每 Phase 独立 PR,先后端再前端,每模块手工回归 2 用户(创建人 / 非创建人)。
---
## 8. 下一步
1. 客户确认 § 默认产品假设 + §4 系统管理是否纳入
2. 执行 **Phase 0** 并合入主分支
3. 按 Phase 1 → 4 逐步交付,每阶段更新本文件 checkbox