@steedos-labs/content-compliance 0.8.25 → 0.8.27
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.
- package/README.md +2 -2
- package/dist/index.js +1 -1
- package/lib/methods/__tests__/approvalReviewerTrigger.test.d.ts +1 -0
- package/lib/methods/__tests__/approvalReviewerTrigger.test.js +389 -0
- package/lib/methods/__tests__/approvalReviewerTrigger.test.js.map +1 -0
- package/lib/pages/__tests__/roomsCredentialResign.test.d.ts +1 -0
- package/lib/pages/__tests__/roomsCredentialResign.test.js +286 -0
- package/lib/pages/__tests__/roomsCredentialResign.test.js.map +1 -0
- package/main/default/client/pepsico_content.client.js +121 -0
- package/main/default/objects/pepesio_ch__c/fields/type.field.yml +2 -0
- package/main/default/objects/pepsico_km_pls_file/fields/pls_file.field.yml +16 -1
- package/main/default/objects/pepsico_km_pls_file/listviews/lookup.listview.yml +3 -1
- package/main/default/objects/space_users/fields/agency_contract_expiry_date.field.yml +9 -0
- package/main/default/objects/space_users/fields/agency_pepsico_contact.field.yml +14 -0
- package/main/default/triggers/pepsico_material_approval_change.trigger.yml +131 -21
- package/package.json +1 -1
|
@@ -6,30 +6,140 @@ when:
|
|
|
6
6
|
isEnabled: true
|
|
7
7
|
handler: |-
|
|
8
8
|
const { _ } = global;
|
|
9
|
-
const { doc } = ctx.params;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const { doc, previousDoc } = ctx.params;
|
|
10
|
+
|
|
11
|
+
// 保存审批单时的审核人兜底与校验 (#450)
|
|
12
|
+
//
|
|
13
|
+
// 审核人原本只由 amis 字段的 onEvent.change 链回填, 服务端没有任何补偿。
|
|
14
|
+
// 前端链一断(链首 ajax 是 ignoreError:false、owner_department 没有 change 事件、
|
|
15
|
+
// 矩阵查不到时 adaptor 返回 {} 把已有值抹掉), 单子就带着空审核人存进来,
|
|
16
|
+
// 一路走到部门审核才因取不到审核人而中断, 静默卡死且无告警。
|
|
17
|
+
//
|
|
18
|
+
// 这里在保存这一刻就按角色矩阵补齐, 补不上就拦下 —— 空审核人的单子根本不该存进库。
|
|
19
|
+
//
|
|
20
|
+
// 只管草稿阶段: 审批过程中的修改、退回给提交人修改等都不该被拦,
|
|
21
|
+
// 那时单子早已在流程里, 再拦只会挡住正常操作。
|
|
22
|
+
|
|
23
|
+
// beforeUpdate 的 doc 只含本次变更字段, 必须和 previousDoc 合并后再判,
|
|
24
|
+
// 否则任何不碰审核人的更新(改个备注)都会被误判成"审核人为空"而拦死。
|
|
25
|
+
const record = _.assign({}, previousDoc || {}, doc);
|
|
26
|
+
|
|
27
|
+
const isBlank = (v) => _.isArray(v) ? _.isEmpty(_.compact(v)) : _.isEmpty(v);
|
|
28
|
+
// tree-select 在不同配置下可能给出字符串或单元素数组, 查矩阵时统一成字符串
|
|
29
|
+
const matrixKey = (v) => _.isArray(v) ? _.compact(v)[0] : v;
|
|
30
|
+
|
|
31
|
+
// 与 src/const.ts 一致: DCO 分类跳过审核直接归档, 不需要审核人
|
|
32
|
+
const dcoTypes = _.split(
|
|
33
|
+
(typeof process !== 'undefined' && process.env && process.env.CONTENT_DCO_TYPES) || 'MD-PO1-PO18',
|
|
34
|
+
','
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// 新建的单子一律是草稿(init 触发器在 beforeInsert 无条件写 current_step=draft),
|
|
38
|
+
// 直接认 isInsert 而不读 doc.current_step —— 两个 beforeInsert 触发器的执行顺序没有保证。
|
|
39
|
+
const isDraft = ctx.params.isInsert === true || record.current_step === 'draft';
|
|
40
|
+
|
|
41
|
+
const needsReviewers = isDraft
|
|
42
|
+
&& record.review_status !== 'terminated'
|
|
43
|
+
&& !_.includes(dcoTypes, record.classification);
|
|
44
|
+
|
|
45
|
+
if(needsReviewers){
|
|
46
|
+
// 角色矩阵(pepsico_reviewer)与审批单上同名的字段。显式列出是为了圈定范围,
|
|
47
|
+
// 避免把矩阵行上的其它字段(name / org / remark 等)一起写进审批单。
|
|
48
|
+
const MATRIX_FIELDS = [
|
|
49
|
+
'reviewer', 'assistance',
|
|
50
|
+
'ns_reviewer', 'ns_assistance',
|
|
51
|
+
'legal_reviewer', 'legal_assistance',
|
|
52
|
+
'sra_reviewer', 'sra_assistance',
|
|
53
|
+
'ca_reviewer', 'ca_assistance',
|
|
54
|
+
'qc_reviewer', 'qc_assistance',
|
|
55
|
+
'can_pass_users',
|
|
56
|
+
'pre_reviewer', 'pre_ns_reviewer', 'pre_legal_reviewer', 'pre_sra_reviewer', 'pre_ca_reviewer', 'pre_qc_reviewer'
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const classification = matrixKey(record.classification);
|
|
60
|
+
const product = matrixKey(record.product);
|
|
61
|
+
const org = matrixKey(record.owner_department);
|
|
62
|
+
|
|
63
|
+
let row = null;
|
|
64
|
+
if(classification && product && org){
|
|
65
|
+
// 矩阵上这三个字段都是 multiple, 审批单上是单值, 所以 '=' 即"矩阵行包含该值"。
|
|
66
|
+
// 用 directFind 绕权限: 发起人通常没有角色矩阵的读权限, 走 find 会读不到而误判成"未配置"。
|
|
67
|
+
const rows = await ctx.getObject('pepsico_reviewer').directFind({
|
|
68
|
+
filters: [
|
|
69
|
+
['classification', '=', classification],
|
|
70
|
+
['product', '=', product],
|
|
71
|
+
['org', '=', org],
|
|
72
|
+
['space', '=', record.space || ctx.params.spaceId]
|
|
73
|
+
],
|
|
74
|
+
sort: 'modified desc',
|
|
75
|
+
top: 1
|
|
76
|
+
});
|
|
77
|
+
row = rows && rows.length > 0 ? rows[0] : null;
|
|
15
78
|
}
|
|
16
79
|
|
|
17
|
-
if(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
80
|
+
if(row){
|
|
81
|
+
// 只补空, 不覆盖 —— 加急换人、管理员手工调整过的值必须保住, 覆盖会伤存量数据
|
|
82
|
+
_.each(MATRIX_FIELDS, (field) => {
|
|
83
|
+
if(isBlank(record[field]) && !isBlank(row[field])){
|
|
84
|
+
doc[field] = row[field];
|
|
85
|
+
record[field] = row[field];
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const deptLabels = {
|
|
91
|
+
ns_reviewer: 'NS',
|
|
92
|
+
legal_reviewer: 'Legal',
|
|
93
|
+
sra_reviewer: 'SRA',
|
|
94
|
+
ca_reviewer: 'CA'
|
|
95
|
+
};
|
|
96
|
+
if(record.ec_has_detail_page === 'yes'){
|
|
97
|
+
deptLabels.qc_reviewer = 'QC';
|
|
98
|
+
}
|
|
99
|
+
const missingDepts = _.filter(_.keys(deptLabels), (field) => isBlank(record[field]));
|
|
100
|
+
const hasPersonnel = !isBlank(record.reviewer);
|
|
101
|
+
|
|
102
|
+
let missing = [];
|
|
103
|
+
if(record.approval_type === 'personnel_review'){
|
|
104
|
+
if(!hasPersonnel){
|
|
105
|
+
missing = ['固定'];
|
|
29
106
|
}
|
|
30
|
-
|
|
31
|
-
|
|
107
|
+
}else if(record.approval_type === 'departmental_review'){
|
|
108
|
+
missing = _.map(missingDepts, (field) => deptLabels[field]);
|
|
109
|
+
}else if(!hasPersonnel && missingDepts.length > 0){
|
|
110
|
+
// approval_type 为空恰恰是最危险的情况: 它和审核人来自同一条前端链, 链断则两者同时为空。
|
|
111
|
+
// 旧逻辑在这里直接跳过校验, 空审核人的单子就是这样进来的, 所以不能以它有值为前提。
|
|
112
|
+
// 此时无法确定后续会走哪条分支(approval_type 由服务端在提交时重算),
|
|
113
|
+
// 要求至少有一套可用的审核人: 固定人员, 或四部门齐全。
|
|
114
|
+
missing = _.map(missingDepts, (field) => deptLabels[field]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if(missing.length > 0){
|
|
118
|
+
// 原始 ID 写系统日志给管理员定位; 发起人就在表单上, 分类/品牌/部门他看得见, 不必回显
|
|
119
|
+
try {
|
|
120
|
+
await ctx.getObject('logs').directInsert({
|
|
121
|
+
name: '保存被拦截: 审核人缺失',
|
|
122
|
+
level: 'error',
|
|
123
|
+
data: JSON.stringify({
|
|
124
|
+
action: 'save-blocked-reviewer-missing',
|
|
125
|
+
id: record._id || ctx.params.id,
|
|
126
|
+
approval_name: record.name,
|
|
127
|
+
is_insert: ctx.params.isInsert === true,
|
|
128
|
+
missing: missing,
|
|
129
|
+
matched_pepsico_reviewer: row ? row._id : null,
|
|
130
|
+
approval_type: record.approval_type || null,
|
|
131
|
+
classification: classification,
|
|
132
|
+
product: product,
|
|
133
|
+
owner_department: org
|
|
134
|
+
}),
|
|
135
|
+
space: record.space || ctx.params.spaceId,
|
|
136
|
+
created: new Date()
|
|
137
|
+
});
|
|
138
|
+
} catch (e) {
|
|
139
|
+
console.error('[pepsico_material_approval_change] 写系统日志失败', e && e.message);
|
|
32
140
|
}
|
|
141
|
+
|
|
142
|
+
throw new Error('缺少' + missing.join('、') + '审核人, 请联系管理员为当前「分类 + 品牌/产品 + 发起人部门」的组合配置角色矩阵后重试。');
|
|
33
143
|
}
|
|
34
144
|
}
|
|
35
145
|
|
|
@@ -40,7 +150,7 @@ handler: |-
|
|
|
40
150
|
}
|
|
41
151
|
}
|
|
42
152
|
}
|
|
43
|
-
|
|
153
|
+
|
|
44
154
|
authentication_type: none
|
|
45
155
|
locked: false
|
|
46
156
|
type: code
|