cloudcc-cli 2.2.7 → 2.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.cloudcc-cache.json +35 -4
  2. package/.cursor/skills/cloudcc-cli-dev/BACKEND_CLASS.md +97 -0
  3. package/.cursor/skills/cloudcc-cli-dev/BACKEND_SCHEDULE.md +78 -0
  4. package/.cursor/skills/cloudcc-cli-dev/BACKEND_TRIGGER.md +137 -0
  5. package/.cursor/skills/cloudcc-cli-dev/CLI_CHEATSHEET.md +215 -0
  6. package/{skill → .cursor/skills/cloudcc-cli-dev}/SKILL.md +13 -2
  7. package/.cursor/skills/cloudcc-cli-dev/VUE_CUSTOM_COMPONENT.md +151 -0
  8. package/README.md +25 -2
  9. package/bin/index.js +2 -0
  10. package/build/component-cc-test-001.common.js +831 -0
  11. package/build/component-cc-test-001.common.js.map +1 -0
  12. package/build/component-cc-test-001.css +1 -0
  13. package/build/component-cc-test-001.umd.js +874 -0
  14. package/build/component-cc-test-001.umd.js.map +1 -0
  15. package/build/component-cc-test-001.umd.min.js +8 -0
  16. package/build/component-cc-test-001.umd.min.js.map +1 -0
  17. package/build/demo.html +1 -0
  18. package/docs/CloudCC/350/207/252/345/256/232/344/271/211/347/273/204/344/273/266/344/275/277/347/224/250/350/257/264/346/230/216.md +130 -0
  19. package/docs/cloudcc/345/256/232/346/227/266/344/275/234/344/270/232.md +472 -0
  20. package/docs/cloudcc/345/256/232/346/227/266/347/261/273.md +302 -0
  21. package/docs//350/207/252/345/256/232/344/271/211/347/261/273.md +258 -0
  22. package/docs//350/247/246/345/217/221/345/231/250/347/261/273.md +404 -0
  23. package/package.json +1 -1
  24. package/plugins/cc-test-001/cc-test-001.vue +32 -0
  25. package/plugins/cc-test-001/components/HelloWorld.vue +11 -0
  26. package/plugins/cc-test-001/config.json +6 -0
  27. package/src/classes/index.js +1 -6
  28. package/src/plugin/delete.js +91 -0
  29. package/src/plugin/doc.js +76 -0
  30. package/src/plugin/get.js +0 -1
  31. package/src/plugin/index.js +1 -0
  32. package/src/plugin/publish1.js +34 -20
  33. package/src/plugin/pull.js +69 -31
  34. package/src/triggers/doc.js +258 -222
  35. package/src/triggers/pullList.js +3 -0
  36. package/target/ccopenapi-0.0.4-classes.jar +0 -0
  37. package/target/ccopenapi-0.0.4.jar +0 -0
  38. package/target/maven-archiver/pom.properties +3 -0
  39. package/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +20 -0
  40. package/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +19 -0
  41. package/skill/BACKEND_CODE.md +0 -114
  42. package/skill/CLI_CHEATSHEET.md +0 -90
  43. package/skill/VUE_CUSTOM_COMPONENT.md +0 -50
  44. /package/{skill → .cursor/skills/cloudcc-cli-dev}/INSTALL_AND_BOOTSTRAP.md +0 -0
  45. /package/{skill → .cursor/skills/cloudcc-cli-dev}/OBJECTS_AND_FIELDS.md +0 -0
  46. /package/{skill → .cursor/skills/cloudcc-cli-dev}/REQUIREMENTS_BREAKDOWN.md +0 -0
@@ -6,304 +6,340 @@ const path = require("path");
6
6
 
7
7
  function generateFullMarkdownDoc() {
8
8
  const lines = [
9
- "# CloudCC 触发器开发指南",
9
+ "# CloudCC 触发器使用总结",
10
10
  "",
11
- "> 本文档参考 CloudCC 官方「触发器」文档整理而成:[触发器](https://help.cloudcc.cn/product03/hong-fa-qi/)。 ",
12
- "> 目标:帮助开发者正确编写、调试和使用触发器,理解触发时机、可用变量、与自定义类/Email/时间工具的协同,以及避免循环调用等常见坑。",
11
+ "> 基于 25 个生产环境触发器实例分析。",
12
+ "> 文档版本:v1.0",
13
+ "> 更新时间:2026-03-24",
13
14
  "",
14
15
  "---",
15
16
  "",
16
- "## 1. 概念与入口",
17
+ "## 一、什么是触发器",
17
18
  "",
18
- "### 1.1 什么是触发器",
19
+ "CloudCC 触发器是完全符合 Java 语法规范的代码,在特定业务事件发生时自动执行,实现业务逻辑自动化处理。",
19
20
  "",
20
- "- 触发器是在特定数据操作事件发生**之前或之后**执行的一段 Java 代码。 ",
21
- "- 典型触发时间:",
22
- " - 在对象记录插入数据库之前 / 之后",
23
- " - 在对象记录更新之前 / 之后",
24
- " - 在对象记录删除之前 / 之后",
25
- " - 在审批相关场景(`approval`)中 ",
26
- "- 主要用于:",
27
- " - 复杂字段校验(必填、多字段联动、一致性约束)",
28
- " - 自动带值 / 派生字段写入",
29
- " - 与其他对象联动更新",
21
+ "触发器本质:",
22
+ "- 一段 Java 代码,运行在 CloudCC 平台沙箱环境中",
23
+ "- 使用 CCService、CCObject 等平台提供的 API 类",
24
+ "- 在数据变更的特定时刻(前/后)自动触发执行",
30
25
  "",
31
- "### 1.2 触发器开发入口",
26
+ "## 二、触发器能干什么",
32
27
  "",
33
- "两种方式创建触发器:",
28
+ "### 2.1 核心能力",
34
29
  "",
35
- "1. **通过开发者平台** ",
36
- " - 登录系统 → 右上角头像 → 「开发者平台」(管理员简档可见) ",
37
- " - 左侧菜单:`扩展 触发器 新建`",
30
+ "| 能力 | 说明 | 示例 |",
31
+ "|------|------|------|",
32
+ "| 自动赋值 | 在记录保存前自动计算并填充字段 | 个人业绩/部门业绩自动赋值 |",
33
+ "| 数据校验 | 阻止不符合业务规则的数据保存 | 预算金额校验、审批金额验证 |",
34
+ "| 关联更新 | 修改一条记录时自动更新关联记录 | 回款明细汇总到收款计划 |",
35
+ "| 自动生成 | 根据业务规则自动创建新记录 | 审批通过后自动生成回款记录 |",
36
+ "| 权限控制 | 自动设置数据共享权限 | 按部门/人员自动共享 |",
37
+ "| 状态同步 | 审批状态变更后同步更新相关字段 | 预算审批状态回写 |",
38
+ "| 汇总计算 | 实时汇总明细数据到主记录 | 部门业绩字段汇总 |",
38
39
  "",
39
- "2. **在对象后台设置中创建** ",
40
- " - 进入目标对象的后台设置(对象管理) ",
41
- " - 将页面滚动到底部,在「触发器」区域点击「新建`",
40
+ "### 2.2 技术能力(基于 CCService API)",
42
41
  "",
43
- "---",
44
- "",
45
- "## 2. 触发器配置项与触发时机",
46
- "",
47
- "### 2.1 基本配置",
48
- "",
49
- "- **名称**:汉字说明,用于描述触发器完成的功能 ",
50
- "- **触发对象**:在哪个对象上执行触发器操作 ",
51
- "- **触发时间(Trigger Event)**:决定在什么时机触发",
52
- "",
53
- "常用触发时间包括:",
54
- "",
55
- "- `beforeInsert`:新建/复制记录行满足触发条件前执行 ",
56
- "- `beforeUpdate`:编辑记录行满足触发条件前执行 ",
57
- "- `beforeUpsert`:新建和编辑/复制统一入口前执行 ",
58
- "- `beforeDelete`:删除记录前执行 ",
59
- "- `afterInsert`:新建/复制记录后执行(可获取记录 ID) ",
60
- "- `afterUpdate`:修改记录后执行 ",
61
- "- `afterUpsert`:新建或编辑/复制后执行 ",
62
- "- `afterDelete`:删除记录后执行 ",
63
- "- `afterInsertCommit`:插入事务提交后执行,**报错不会回滚主事务** ",
64
- "- `afterUpdateCommit`:更新提交后执行,报错不会回滚 ",
65
- "- `afterUpsertCommit`:保存提交后执行,报错不会回滚 ",
66
- "- `afterDeleteCommit`:删除提交后执行,报错不会回滚 ",
67
- "- `approval`:审批流程相关触发",
68
- "",
69
- "**提示**: ",
70
- "`before*` 阶段通常用于**校验与字段赋值**;`after*` 阶段适合做**关联对象更新、日志记录、异步操作触发**等。",
71
- "",
72
- "---",
73
- "",
74
- "## 3. 触发器中可用变量与基础语法",
75
- "",
76
- "### 3.1 可直接使用的变量",
42
+ "```java",
43
+ "// 1. 新增记录",
44
+ "CCObject opp = new CCObject(\"Opportunity\");",
45
+ "opp.put(\"name\", \"value\");",
46
+ "cs.insert(opp);",
47
+ "",
48
+ "// 2. 查询记录(指定字段)",
49
+ "List<CCObject> opps1 = cs.cqueryByFields(",
50
+ " \"Opportunity\",",
51
+ " \"khmc__c='\" + record_new.get(\"id\") + \"'\",",
52
+ " \"id,name,createdate\"",
53
+ ");",
77
54
  "",
78
- "- `(UserInfo) userInfo`:当前操作用户信息 ",
79
- "- `(Map) record_new`:当前记录的**最新值**(新建/修改后) ",
80
- "- `(Map) record_old`:当前记录的**旧值**(修改前),在 `insert` 场景通常为 `null` ",
81
- "- `trigger`:触发器上下文对象,可用于添加错误提示等",
55
+ "// 3. 查询记录(全部字段)",
56
+ "List<CCObject> opps2 = cs.cquery(",
57
+ " \"Opportunity\",",
58
+ " \"khmc__c='\" + record_new.get(\"id\") + \"'\"",
59
+ ");",
82
60
  "",
83
- "### 3.2 常用语法与含义",
61
+ "// 4. 复杂查询",
62
+ "List<CCObject> ccobjs = cs.cqlQuery(",
63
+ " \"Opportunity\",",
64
+ " \"select sum(amount) as sumAmount from Opportunity where khmc='0012001238ytwuiw'\"",
65
+ ");",
84
66
  "",
85
- "| 语法 | 返回值 | 含义 |",
86
- "|----------------------------------------|----------|------------------------------------|",
87
- "| `record_new.get(\"字段API\")` | String | 获取记录中字段的最新值 |",
88
- "| `record_new.put(\"字段API\", \"字段值\")` | void | 为记录的某字段赋值 |",
89
- "| `record_old.get(\"字段API\")` | String | 获取记录中字段的前一个值 |",
90
- "| `record_old == null` | boolean | 在 `beforeUpsert/afterUpsert` 中识别是否为新建 |",
91
- "| `trigger.addErrorMessage(\"提示内容\")` | void | 在保存前用于提示并中止保存 |",
67
+ "// 5. 修改/删除",
68
+ "cs.update(opp);",
69
+ "cs.delete(opp);",
92
70
  "",
93
- "**提示**: ",
94
- "字段名使用**字段 API 名称**(如 `project_manager`、`start_date`),自定义字段一般以 `__c` 结尾。",
71
+ "// 6. 发送邮件通知",
72
+ "SendEmail sendEmail = new SendEmail(userInfo);",
73
+ "sendEmail.sendMailFromSystem(toAddress, ccAddress, bccAddress, subject, content, isText);",
74
+ "```",
95
75
  "",
96
76
  "---",
97
77
  "",
98
- "## 4. 常见案例",
99
- "",
100
- "### 4.1 案例 1:字段联动带值(查询赋值)",
101
- "",
102
- "**需求** ",
103
- "在项目对象中,将当前项目经理的邮箱自动带入本对象的 `email` 字段。 ",
78
+ "## 三、触发器的触发时机详解",
104
79
  "",
105
- "**触发事件**:`beforeUpsert`",
106
- "",
107
- "**代码示例**",
80
+ "### 3.1 beforeUpsert(创建或更新前)",
81
+ "- 执行时机:记录保存之前,数据尚未写入数据库",
82
+ "- 典型用途:自动计算字段、数据格式校验、阻止非法数据保存、自动填充默认值",
83
+ "- 生产实例:个人业绩/部门业绩赋值、计算产研成本/净业绩、生成部门/个人业绩",
108
84
  "",
109
85
  "```java",
110
- "String managerId = record_new.get(\"project_manager\") == null ? \"\" : record_new.get(\"project_manager\") + \"\"; // 新值",
111
- "String managerId_old = record_old == null || record_old.get(\"project_manager\") == null ? \"\" : record_old.get(\"project_manager\") + \"\"; // 旧值",
112
- "",
113
- "// 项目经理字段新值和老值不相同时判定为该字段值发生改变,需重新查询用户表获取邮箱带到本记录中",
114
- "if (!managerId.equals(managerId_old)) {",
115
- " List<CCObject> userlist = cquery(\"User\", \"id='\" + managerId + \"'\");",
116
- " if (userlist.size() > 0) {",
117
- " String email = userlist.get(0).get(\"email\") == null ? \"\" : userlist.get(0).get(\"email\") + \"\";",
118
- " record_new.put(\"email\", email);",
119
- " }",
86
+ "// beforeUpsert 示例",
87
+ "CCObject record = (CCObject) data.get(\"record\");",
88
+ "BigDecimal income = record.getBigDecimal(\"ysr\");",
89
+ "BigDecimal cost = record.getBigDecimal(\"cycb\");",
90
+ "BigDecimal net = income.subtract(cost);",
91
+ "record.put(\"jyj\", net);",
92
+ "if (net.compareTo(BigDecimal.ZERO) < 0) {",
93
+ " throw new Exception(\"毛利不能为负数!\");",
120
94
  "}",
121
95
  "```",
122
96
  "",
123
- "要点:",
124
- "- 使用 `record_new` / `record_old` 比较判断字段是否变更,**避免无谓查询与写入**。",
125
- "- 查询用户对象时使用 `cquery(\"User\", ...)`。",
126
- "",
127
- "### 4.2 案例 2:复杂必填校验",
128
- "",
129
- "**需求** ",
130
- "当项目经理不为空时,项目开始日期与结束日期必填,否则不允许保存。",
97
+ "### 3.2 afterInsert(创建后)",
98
+ "- 执行时机:记录创建成功后(可获取 ID)",
99
+ "- 典型用途:基于新记录 ID 创建关联记录、发送创建通知、初始化关联数据",
100
+ "- 生产实例:根据净业绩拆分生成净业绩拆分回款",
131
101
  "",
132
- "**触发时间**:`beforeUpsert`",
102
+ "```java",
103
+ "// afterInsert 示例",
104
+ "CCObject newRecord = (CCObject) data.get(\"record\");",
105
+ "String recordId = newRecord.getString(\"id\");",
106
+ "CCObject hkRecord = new CCObject(\"Huikuan\");",
107
+ "hkRecord.put(\"mingxi__c\", recordId);",
108
+ "hkRecord.put(\"je\", newRecord.get(\"je\"));",
109
+ "cs.insert(hkRecord);",
110
+ "```",
133
111
  "",
134
- "**代码示例**",
112
+ "### 3.3 afterUpsert(创建或更新后)",
113
+ "- 执行时机:记录保存成功后(新增或修改)",
114
+ "- 典型用途:汇总数据到父记录、触发下游流程、更新关联记录状态",
115
+ "- 生产实例:金额汇总到收款计划、更新部门业绩字段、更新回款明细",
135
116
  "",
136
117
  "```java",
137
- "String managerId = record_new.get(\"project_manager\") == null ? \"\" : record_new.get(\"project_manager\") + \"\"; // 项目经理",
138
- "String start_date = record_new.get(\"start_date\") == null ? \"\" : record_new.get(\"start_date\") + \"\"; // 项目开始日期",
139
- "String end_date = record_new.get(\"end_date\") == null ? \"\" : record_new.get(\"end_date\") + \"\"; // 项目结束日期",
140
- "",
141
- "if (!\"\".equals(managerId) && (\"\".equals(start_date) || \"\".equals(end_date))) {",
142
- " trigger.addErrorMessage(\"请填写项目开始日期及项目结束日期\");",
118
+ "// afterUpsert 示例 - 汇总金额到父记录",
119
+ "CCObject detail = (CCObject) data.get(\"record\");",
120
+ "String planId = detail.getString(\"skjh__c\");",
121
+ "List<CCObject> plans = cs.cqueryByFields(",
122
+ " \"ShoukuanJihua\",",
123
+ " \"id='\" + planId + \"'\",",
124
+ " \"id,shyjje,yishouje\"",
125
+ ");",
126
+ "if (!plans.isEmpty()) {",
127
+ " CCObject plan = plans.get(0);",
128
+ " BigDecimal current = plan.getBigDecimal(\"yishouje\");",
129
+ " BigDecimal detailAmount = detail.getBigDecimal(\"je\");",
130
+ " plan.put(\"yishouje\", current.add(detailAmount));",
131
+ " cs.update(plan);",
143
132
  "}",
144
133
  "```",
145
134
  "",
146
- "要点:",
147
- "- 通过 `trigger.addErrorMessage` 阻断保存并提示用户。",
148
- "- 适合放在 `beforeInsert` / `beforeUpdate` / `beforeUpsert` 等阶段。",
135
+ "### 3.4 afterDelete(删除后)",
136
+ "- 执行时机:记录删除成功后",
137
+ "- 典型用途:清理关联数据、反向汇总更新",
138
+ "- 生产实例:回款明细删除后重新计算回款金额",
149
139
  "",
150
- "---",
140
+ "### 3.5 approval(审批时)",
141
+ "- 执行时机:记录提交审批或审批通过时",
142
+ "- 典型用途:审批前校验、审批通过后执行业务操作",
143
+ "- 生产实例:更新拆分审批状态、订单审批判断、审批通过计算剩余欠款",
151
144
  "",
152
- "## 5. 进阶用法与注意事项",
145
+ "---",
153
146
  "",
154
- "### 5.1 触发器可直接引用的参数",
147
+ "## 四、为什么要用触发器",
155
148
  "",
156
- "- `UserInfo userInfo`:当前操作用户 ",
157
- "- `Map record_new` / `Map record_old`",
149
+ "### 4.1 解决的核心问题",
158
150
  "",
159
- "取字段值时直接使用字段 API 名称:",
151
+ "| 问题 | 传统方案 | 触发器方案 |",
152
+ "|------|----------|------------|",
153
+ "| 数据一致性 | 依赖用户手动操作,容易遗漏 | 自动执行,保证数据准确 |",
154
+ "| 业务规则执行 | 前端校验可绕过 | 统一在平台层执行,无法绕过 |",
155
+ "| 跨对象联动 | 需要多次手动操作 | 自动关联更新 |",
156
+ "| 审批复杂逻辑 | 审批流配置难以承载 | Java 代码可实现复杂逻辑 |",
157
+ "| 实时性要求 | 定时任务有延迟 | 实时触发,立即执行 |",
160
158
  "",
161
- "```java",
162
- "String name = record_new.get(\"name\") == null ? \"\" : record_new.get(\"name\") + \"\";",
163
- "```",
159
+ "### 4.2 与其他方案对比",
164
160
  "",
165
- "### 5.2 循环调用限制",
161
+ "| 方案 | 优点 | 缺点 | 适用场景 |",
162
+ "|------|------|------|----------|",
163
+ "| 触发器 | 实时、自动、无法绕过 | 需要 Java 开发能力 | 核心业务逻辑 |",
164
+ "| 审批流 | 配置简单、可视化 | 逻辑能力有限 | 简单审批流程 |",
165
+ "| 工作流 | 可视化编排 | 复杂逻辑实现困难 | 跨系统流程编排 |",
166
+ "| 定时任务 | 适合批量处理 | 有延迟 | 数据同步、报表生成 |",
166
167
  "",
167
- "触发器之间可以间接导致**循环调用**,例如:",
168
- "- 在客户对象的 `update` 触发器中更新业务机会 ",
169
- "- 在业务机会对象的 `update` 触发器中又更新客户(再次触发客户的 `update` 触发器) ",
168
+ "---",
170
169
  "",
171
- "如果在触发器中不加条件控制,可能形成无限循环。平台限制:",
172
- "- **最多允许 3 次循环调用** ",
173
- "- 超过 3 次系统会报错提示并终止执行",
170
+ "## 五、典型业务场景",
171
+ "",
172
+ "### 5.1 财务业绩管理(核心场景)",
173
+ "",
174
+ "```text",
175
+ "订单 (Order)",
176
+ " └─ approval:审批判断",
177
+ "预算 (Budget)",
178
+ " ├─ beforeUpsert:计算产研成本/净业绩",
179
+ " └─ afterUpsert:自动拆分/生成回款",
180
+ "净业绩拆分 (jyjcf)",
181
+ " ├─ beforeUpsert:生成部门/个人业绩",
182
+ " └─ afterUpsert:校验金额累计是否超预算",
183
+ "部门业绩 (bmyj)",
184
+ " ├─ afterUpsert:更新部门业绩字段",
185
+ " └─ afterUpdate:汇总部门资金池",
186
+ "回款明细 (cloudccproceeddetail)",
187
+ " ├─ afterInsert:生成净业绩拆分回款",
188
+ " ├─ afterUpsert:金额汇总到收款计划",
189
+ " ├─ afterUpsert:更新回款明细",
190
+ " └─ afterDelete:回款明细删除",
191
+ "```",
174
192
  "",
175
- "建议:",
176
- "- 在跨对象更新时增加「状态字段」或「字段变更判断」作为保护条件 ",
177
- "- 避免在 A 的触发器中无条件更新 B,同时在 B 的触发器中又无条件更新 A ",
178
- "- 将复杂联动逻辑尽量下沉到自定义类中集中处理",
193
+ "### 5.2 费用报销管理",
179
194
  "",
180
- "### 5.3 触发器中发送邮件通知",
195
+ "```text",
196
+ "费用报销提交",
197
+ " -> approval 触发器:验证审批时校验预算(报销金额 <= 预算余额)",
198
+ "审批通过",
199
+ " -> approval 触发器:计算剩余欠款(借款 - 报销)",
200
+ "```",
181
201
  "",
182
- "触发器可以使用 `SendEmail` 发送系统邮件:",
202
+ "### 5.3 借款还款管理",
183
203
  "",
184
- "```java",
185
- "SendEmail sendEmail = new SendEmail(userInfo);",
204
+ "| 触发器 | 对象 | 时机 | 作用 |",
205
+ "|--------|------|------|------|",
206
+ "| 借款单触发器 | 借款单 | beforeUpsert | 借款前校验、自动计算 |",
207
+ "| 还款记录审批通过更新借款单 | 还款记录 | approval | 审批后回写借款单 |",
186
208
  "",
187
- "// 直接发送邮件",
188
- "sendEmail.sendMailFromSystem(",
189
- " new String[]{\"to@example.com\"}, // 收件人",
190
- " new String[]{\"cc@example.com\"}, // 抄送",
191
- " new String[]{\"bcc@example.com\"}, // 密送",
192
- " \"邮件主题\",",
193
- " \"邮件内容\",",
194
- " true // 是否文本邮件",
195
- ");",
209
+ "---",
196
210
  "",
197
- "// 使用邮件模板发送",
198
- "sendEmail.sendEmailNew(",
199
- " \"emailTemplateId\",",
200
- " new String[]{\"to@example.com\"},",
201
- " new String[]{\"cc@example.com\"},",
202
- " new String[]{\"bcc@example.com\"},",
203
- " record_new.get(\"id\") + \"\" // 关联记录 ID",
204
- ");",
205
- "```",
211
+ "## 六、触发器开发最佳实践",
206
212
  "",
207
- "### 5.4 获取邮件模板内容",
213
+ "### 6.1 代码规范",
208
214
  "",
209
215
  "```java",
210
- "/**",
211
- " * lang:语言",
212
- " * emailtemplateid:邮件模板ID",
213
- " * id:记录id(可选)",
214
- " */",
215
- "Map<String, String> m = this.getMessageByTemplateId(lang, emailtemplateid, record_new.get(\"id\") + \"\");",
216
- "",
217
- "// 邮件标题",
218
- "String subject = m.get(\"subject\");",
219
- "// 邮件内容",
220
- "String content = m.get(\"context\");",
221
- "// 邮件是否为文本形式",
222
- "String isText = m.get(\"istext\");",
216
+ "try {",
217
+ " CCObject record = (CCObject) data.get(\"record\");",
218
+ " cs.update(record);",
219
+ "} catch (Exception e) {",
220
+ " System.out.println(\"触发器执行失败:\" + e.getMessage());",
221
+ " throw new Exception(\"业务校验失败:\" + e.getMessage());",
222
+ "}",
223
223
  "```",
224
224
  "",
225
- "可以先获取模板内容,再根据业务需要调整后发送。",
225
+ "### 6.2 性能优化",
226
226
  "",
227
- "### 5.5 TimeUtil:多时区时间支持",
227
+ "```java",
228
+ "// 推荐:字段过滤,只查询需要的字段",
229
+ "List<CCObject> records = cs.cqueryByFields(",
230
+ " \"Object__c\",",
231
+ " \"status__c='active'\",",
232
+ " \"id,name,amount__c\"",
233
+ ");",
228
234
  "",
229
- "与自定义类中用法相同,触发器也应使用 `TimeUtil` 来处理与时区相关的时间:",
235
+ "// 推荐:批量更新,降低递归风险",
236
+ "cs.updateLt(records);",
237
+ "```",
230
238
  "",
231
- "- 获取当前时间(按用户时区):",
239
+ "### 6.3 避免递归触发",
232
240
  "",
233
241
  "```java",
234
- "TpSysTask task = new TpSysTask();",
235
- "task.setBeginTime(TimeUtil.getNowDate(userInfo));",
242
+ "// 方法 1:使用 updateLt",
243
+ "cs.updateLt(records);",
244
+ "",
245
+ "// 方法 2:增加标志位(示例)",
246
+ "if (\"true\".equals(System.getProperty(\"trigger.running\"))) {",
247
+ " return;",
248
+ "}",
249
+ "System.setProperty(\"trigger.running\", \"true\");",
250
+ "cs.update(record);",
251
+ "System.setProperty(\"trigger.running\", \"false\");",
236
252
  "```",
237
253
  "",
238
- "- 获取带时区的 `SimpleDateFormat`:",
254
+ "---",
239
255
  "",
240
- "```java",
241
- "SimpleDateFormat myDateFormat = TimeUtil.getSimpleDateFormat(\"yyyy-MM-dd\", userInfo);",
242
- "```",
256
+ "## 七、触发器调试与监控",
243
257
  "",
244
- "- 获取带时区的 `Calendar`:",
258
+ "### 7.1 日志记录",
245
259
  "",
246
260
  "```java",
247
- "Calendar cal = TimeUtil.getCalendar(userInfo);",
261
+ "System.out.println(\"=== 触发器开始执行 ===\");",
262
+ "System.out.println(\"对象:\" + record.getSObjectName());",
263
+ "System.out.println(\"操作:\" + data.get(\"action\"));",
264
+ "System.out.println(\"记录 ID:\" + record.get(\"id\"));",
248
265
  "```",
249
266
  "",
250
- "---",
251
- "",
252
- "## 6. 与 CCService / 自定义类的协同",
267
+ "### 7.2 常见问题排查",
253
268
  "",
254
- "### 6.1 在触发器中使用 CCService",
269
+ "| 问题 | 可能原因 | 解决方案 |",
270
+ "|------|----------|----------|",
271
+ "| 触发器不执行 | 未激活、触发时机不对 | 检查状态与配置 |",
272
+ "| 递归触发 | 更新操作再次触发触发器 | 使用 `updateLt` 或增加条件判断 |",
273
+ "| 性能问题 | 查询数据量过大 | 增加查询条件、字段过滤 |",
274
+ "| 数据不一致 | 触发器执行失败 | 增加异常处理与关键日志 |",
255
275
  "",
256
- "触发器中同样可以实例化并使用 `CCService` 进行增删改查:",
276
+ "---",
257
277
  "",
258
- "```java",
259
- "CCService cs = new CCService((UserInfo) userInfo);",
260
- "",
261
- "// 示例:根据当前记录字段查询机会",
262
- "String accountId = record_new.get(\"khmc__c\") == null ? \"\" : record_new.get(\"khmc__c\") + \"\";",
263
- "if (!\"\".equals(accountId)) {",
264
- " List<CCObject> opps = cs.cquery(",
265
- " \"Opportunity\",",
266
- " \"khmc__c = '\" + accountId + \"'\",",
267
- " \"jine__c desc\"",
268
- " );",
269
- " // TODO: 根据查询结果执行相应逻辑",
270
- "}",
271
- "```",
278
+ "## 八、总结",
272
279
  "",
273
- "具体 `CCService` 能力详见 `custom-class-dev.md`。",
280
+ "### 8.1 触发器价值",
281
+ "1. 自动化:减少人工操作,降低出错率",
282
+ "2. 一致性:保证数据准确与规则统一执行",
283
+ "3. 实时性:业务变更立即响应",
284
+ "4. 灵活性:Java 代码可承载复杂逻辑",
274
285
  "",
275
- "### 6.2 将复杂逻辑下沉到自定义类",
286
+ "### 8.2 适用与不适用场景",
276
287
  "",
277
- "推荐模式:",
288
+ "**适合使用触发器:**",
289
+ "- 需要实时数据联动",
290
+ "- 核心业务规则校验",
291
+ "- 跨对象数据同步",
292
+ "- 审批流程复杂逻辑",
293
+ "- 自动创建或更新关联记录",
278
294
  "",
279
- "1. 触发器中只负责:",
280
- " - 判断是否需要执行逻辑(字段是否变化、状态是否满足、避免循环等)",
281
- " - 准备必需参数(如 `userInfo`、`record_new` / `record_old` 等)",
282
- "2. 将主要业务逻辑封装到自定义类方法中:",
295
+ "**不适合使用触发器:**",
296
+ "- 简单字段默认值(优先配置化能力)",
297
+ "- 大批量离线数据处理",
298
+ "- 跨系统集成调用",
299
+ "- 强交互型前端界面逻辑",
283
300
  "",
284
- "```java",
285
- "// 触发器中",
286
- "if (/* 条件满足 */) {",
287
- " new ProjectService(userInfo).handleAfterUpdate(record_old, record_new);",
288
- "}",
289
- "```",
301
+ "### 8.3 触发器开发 Checklist",
290
302
  "",
291
- "好处:",
292
- "- 逻辑集中、可复用、易测试 ",
293
- "- 触发器代码保持精简,可读性更高",
303
+ "- [ ] 明确触发时机(before/after/approval)",
304
+ "- [ ] 评估并规避递归触发风险",
305
+ "- [ ] 添加异常处理与关键日志",
306
+ "- [ ] 覆盖边界条件测试",
307
+ "- [ ] 完成大数据量场景性能测试",
294
308
  "",
295
309
  "---",
296
310
  "",
297
- "## 7. 触发器开发 Checklist",
298
- "",
299
- "- [ ] 已选择正确的**触发对象**与**触发时间**(before/after/commit/approval) ",
300
- "- [ ] 校验逻辑集中在 `before*` 阶段,并通过 `trigger.addErrorMessage` 给出清晰提示 ",
301
- "- [ ] 使用 `record_new` / `record_old` 判断字段是否变化,避免不必要的数据库操作 ",
302
- "- [ ] 涉及跨对象更新时,已经考虑并避免循环调用(尤其是双向更新) ",
303
- "- [ ] 发送邮件时使用 `SendEmail` / 模板,且确认模板与收件人配置正确 ",
304
- "- [ ] 与时间相关逻辑使用 `TimeUtil`,避免跨时区错误 ",
305
- "- [ ] 复杂业务逻辑拆分到自定义类中,通过触发器调用,触发器本身保持简洁 ",
306
- "- [ ] 已在测试环境完整验证:新建、编辑、删除、审批等全场景,确认不会出现循环触发或异常回滚问题 ",
311
+ "## 附录:25 个生产触发器清单",
312
+ "",
313
+ "| # | 名称 | 对象 | 时机 | 状态 |",
314
+ "|---|------|------|------|------|",
315
+ "| 1 | 个人业绩/部门业绩赋值 | 对外付款 | beforeUpsert | ✅ |",
316
+ "| 2 | 个人业绩/部门业绩赋值 | 费用报销 | beforeUpsert | ❌ |",
317
+ "| 3 | 本部业绩统计 | 本部业绩统计 | beforeUpsert | ✅ |",
318
+ "| 4 | 更新拆分审批状态 | 预算 | approval | ✅ |",
319
+ "| 5 | 订单审批判断 | 订单 | approval | ✅ |",
320
+ "| 6 | 根据净业绩拆分生成净业绩拆分回款 | 回款明细 | afterInsert | ✅ |",
321
+ "| 7 | 金额汇总到收款计划 | 回款明细 | afterUpsert | ✅ |",
322
+ "| 8 | 回款明细删除 | 回款明细 | afterDelete | ❌ |",
323
+ "| 9 | 计算产研成本/净业绩 | 预算 | beforeUpsert | ✅ |",
324
+ "| 10 | 保证金回款记录 | 概算 | afterUpdate | ✅ |",
325
+ "| 11 | 校验金额累计是否超预算 | 净业绩拆分 | afterUpsert | ✅ |",
326
+ "| 12 | 更新部门业绩字段 | 部门业绩 | afterUpsert | ✅ |",
327
+ "| 13 | 汇总部门资金池 | 部门业绩 | afterUpdate | ✅ |",
328
+ "| 14 | 编辑更新部门业绩 | 净业绩拆分回款 | beforeUpsert | ✅ |",
329
+ "| 15 | 生成部门/个人业绩 | 净业绩拆分 | beforeUpsert | ✅ |",
330
+ "| 16 | 更新回款明细 | 回款明细 | afterUpsert | ✅ |",
331
+ "| 17 | 新建自动拆分/审批通过自动生成回款 | 预算 | afterUpsert | ✅ |",
332
+ "| 18 | 提交审批验证金额 | 对外付款 | approval | ❌ |",
333
+ "| 19 | 还款记录审批通过更新借款单 | 还款记录 | approval | ✅ |",
334
+ "| 20 | 审批通过计算剩余欠款 | 费用报销 | approval | ✅ |",
335
+ "| 21 | 借款单触发器 | 借款单 | beforeUpsert | ✅ |",
336
+ "| 22 | 创建编辑项目任务时判断 | 项目任务 | beforeUpsert | ✅ |",
337
+ "| 23 | 任务派工工时限制 | 项目任务 | afterUpsert | ✅ |",
338
+ "| 24 | 测试 AI 代码生成 | 客户 | beforeInsert | ❌ |",
339
+ "| 25 | 验证审批时校验预算 | 费用报销 | approval | ❌ |",
340
+ "",
341
+ "文档生成时间:2026-03-24",
342
+ "基于 CloudCC 生产环境 25 个触发器实例分析",
307
343
  "",
308
344
  ];
309
345
 
@@ -1,3 +1,6 @@
1
+
2
+
3
+
1
4
  const fs = require("fs");
2
5
  const path = require("path")
3
6
  const { getPackageJson } = require("../../utils/config");
Binary file
@@ -0,0 +1,3 @@
1
+ artifactId=ccopenapi
2
+ groupId=com.cloudcc.core
3
+ version=0.0.4
@@ -0,0 +1,20 @@
1
+ com/cloudcc/core/CCObject.class
2
+ com/cloudcc/core/TriggerTimeEnum.class
3
+ com/cloudcc/core/UserInfo.class
4
+ com/cloudcc/core/SendEmail.class
5
+ com/cloudcc/core/BusiException.class
6
+ com/cloudcc/core/PeakInterf.class
7
+ com/cloudcc/core/TimeUtil.class
8
+ com/cloudcc/core/CCTriggerHandler.class
9
+ com/cloudcc/core/Tool.class
10
+ com/cloudcc/core/TriggerInvoker.class
11
+ com/cloudcc/core/CCSchedule.class
12
+ com/cloudcc/core/BaseException.class
13
+ com/cloudcc/core/OperatationEnum.class
14
+ com/cloudcc/core/TriggerMethod.class
15
+ com/cloudcc/core/ServiceResult.class
16
+ com/cloudcc/core/StringUtils.class
17
+ com/cloudcc/core/DevLogger.class
18
+ com/cloudcc/core/CCService.class
19
+ com/cloudcc/core/CCTrigger.class
20
+ com/cloudcc/core/Tool$1.class
@@ -0,0 +1,19 @@
1
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/ServiceResult.java
2
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/TriggerInvoker.java
3
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/UserInfo.java
4
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/Tool.java
5
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/CCService.java
6
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/CCSchedule.java
7
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/SendEmail.java
8
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/OperatationEnum.java
9
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/BaseException.java
10
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/CCTriggerHandler.java
11
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/TriggerMethod.java
12
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/TriggerTimeEnum.java
13
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/CCTrigger.java
14
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/StringUtils.java
15
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/BusiException.java
16
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/DevLogger.java
17
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/PeakInterf.java
18
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/TimeUtil.java
19
+ /Users/xuhm/Documents/cloudcc-cli/java/com/cloudcc/core/CCObject.java