You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
DELETE /api/v1/data/crm_opportunity/<id>
→ 409 {"error":"Cannot delete crm_opportunity (<id>): 1 dependent crm_opportunity_line_item
record(s) reference it via crm_opportunity (crm_opportunity is required, so it cannot
be cleared). Delete or reassign them first, or set deleteBehavior:'cascade' on
crm_opportunity_line_item.crm_opportunity.",
"code":"DELETE_RESTRICTED","dependentObject":"crm_opportunity_line_item",...}
crm_quote → crm_quote_line_item 完全同构:
DELETE /api/v1/data/crm_quote/<id>
→ 409 "Cannot delete crm_quote (<id>): 1 dependent crm_quote_line_item record(s) reference it
via crm_quote (crm_quote is required...). ... set deleteBehavior:'cascade' on
crm_quote_line_item.crm_quote."
实测:删除被拦后 GET /data/crm_opportunity/<id> 仍 200;把那条明细行删掉后重试 DELETE 即 200。已两次复现。
期望 vs 实际
期望:明细行是商机/报价的从属记录 —— opportunity_line_item.hook.ts 明说 "Keeps crm_opportunity.amount in sync with the sum of its line items … once a deal is itemised, the line sum is authoritative",即父记录的头号金额本身派生自明细行;opportunity_line_item.object.ts 也写着 "A line item has no meaning apart from its deal"。删除父记录时,这些从属行理应随父一起消失(Salesforce master-detail 语义),或者至少给出面向业务用户的拒绝文案。
实际:父记录删除被 409 硬拦,并把 set deleteBehavior:'cascade' on crm_opportunity_line_item.crm_opportunity 这类开发者向建议原样抛给调用方。
环境:
hotcrm@0899b4f + @objectstack 17.0.0-rc.2(基线 main;经核对当前 main 上opportunity_line_item.object.ts/quote_line_item.object.ts的相关声明未被 #712/#719 触及,故本缺陷在最新 main 同样成立,非基线陈旧产物)。现象
删除任何"挂有明细行"的
crm_opportunity或crm_quote都会被引擎以409 DELETE_RESTRICTED拦截,且抛出的是面向开发者的报错文案(直接把内部字段路径和deleteBehavior:'cascade'的修法建议暴露给最终用户/API 调用方)。必须先手动删掉全部明细行,父记录才删得掉。crm_quote→crm_quote_line_item完全同构:复现步骤(可粘贴执行)
实测:删除被拦后
GET /data/crm_opportunity/<id>仍 200;把那条明细行删掉后重试DELETE即 200。已两次复现。期望 vs 实际
opportunity_line_item.hook.ts明说 "Keepscrm_opportunity.amountin sync with the sum of its line items … once a deal is itemised, the line sum is authoritative",即父记录的头号金额本身派生自明细行;opportunity_line_item.object.ts也写着 "A line item has no meaning apart from its deal"。删除父记录时,这些从属行理应随父一起消失(Salesforce master-detail 语义),或者至少给出面向业务用户的拒绝文案。409硬拦,并把set deleteBehavior:'cascade' on crm_opportunity_line_item.crm_opportunity这类开发者向建议原样抛给调用方。落点分析
引擎的行为本身是合理默认,不是引擎 bug:
packages/objectql/src/engine.ts:5523-5539(cascadeDeleteRelations)对一个 REQUIRED 且未声明deleteBehavior的 lookup,把默认的set_null升级为restrict(NOT NULL FK 不能置空)。落点在 HotCRM 元数据:src/objects/opportunity_line_item.object.ts的crm_opportunity: Field.lookup('crm_opportunity', { required: true, storage:{notNull:true} })—— 无deleteBehavior。src/objects/quote_line_item.object.ts的crm_quote: Field.lookup('crm_quote', { required: true, ... })—— 同样无deleteBehavior。这与 #696 / #711 是**同一族"没人写过 deleteBehavior 导致默认落到 restrict/set_null"**的潜伏缺陷,只是症状相反:#696/#711 是"人删不掉"(party lookup 被置空撞校验),这里是"父记录删不掉"(明细行把父钉住)。
需要区别对待的一点(供裁决,不擅自下结论):#696 的验收结论明确把 campaign_member.crm_campaign / event_attendee.crm_event 的 restrict 判为"有意保留",理由是活动的成员名单是历史记录、不应随手滑级联。但那条理由不适用于 line item —— 明细行没有独立留存价值,父金额还派生自它。因此:
deleteBehavior: 'cascade'(与 fix(campaign-member): 报名过市场活动的线索/联系人恢复可删除 —— 参与行随人级联删除 (#696) #712 给 campaign_member party lookup 的修法同形)。deleteBehavior:'cascade'的内部建议丢给用户。证据
409 DELETE_RESTRICTED,dependentObject: crm_opportunity_line_item;删明细行后重删200。409 DELETE_RESTRICTED,dependentObject: crm_quote_line_item。crm_account → crm_contact(真 master-detail,deleteBehavior:'cascade')删除父客户会正确级联删子联系人(实测 200 且子记录 GET 404),证明"级联删除"路径在本应用里是通的,唯独两个 line-item 对象没走这条路。严重度建议 p2:有绕行(先删明细行),非数据丢失/越权;但属正常生命周期操作被非预期拦截 + 文案受众错配,建议纳入 17.0 收口裁决。