# 仅创建人可删除 — 删除接口 / 页面对照表与分阶段改造方案 > **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