@steedos-labs/content-compliance 0.8.8 → 0.8.10

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 (178) hide show
  1. package/dist/index.js +1 -1
  2. package/lib/actions/bypassApproval.js +18 -0
  3. package/lib/actions/bypassApproval.js.map +1 -1
  4. package/lib/actions/bypassMaterial.js +12 -0
  5. package/lib/actions/bypassMaterial.js.map +1 -1
  6. package/lib/index.js +153 -0
  7. package/lib/index.js.map +1 -1
  8. package/lib/methods/AI.d.ts +6 -0
  9. package/lib/methods/AI.js +116 -15
  10. package/lib/methods/AI.js.map +1 -1
  11. package/lib/methods/__tests__/plsReview.test.d.ts +1 -0
  12. package/lib/methods/__tests__/plsReview.test.js +736 -0
  13. package/lib/methods/__tests__/plsReview.test.js.map +1 -0
  14. package/lib/methods/__tests__/postponeReview.test.js +21 -0
  15. package/lib/methods/__tests__/postponeReview.test.js.map +1 -1
  16. package/lib/methods/__tests__/qcParallelReview.test.d.ts +1 -0
  17. package/lib/methods/__tests__/qcParallelReview.test.js +53 -0
  18. package/lib/methods/__tests__/qcParallelReview.test.js.map +1 -0
  19. package/lib/methods/__tests__/qcPreReview.test.d.ts +1 -0
  20. package/lib/methods/__tests__/qcPreReview.test.js +65 -0
  21. package/lib/methods/__tests__/qcPreReview.test.js.map +1 -0
  22. package/lib/methods/approval_engine.d.ts +4 -0
  23. package/lib/methods/approval_engine.js +388 -221
  24. package/lib/methods/approval_engine.js.map +1 -1
  25. package/lib/methods/consts.d.ts +35 -0
  26. package/lib/methods/consts.js +2 -0
  27. package/lib/methods/consts.js.map +1 -1
  28. package/lib/methods/material.js +13 -0
  29. package/lib/methods/material.js.map +1 -1
  30. package/lib/methods/pls.d.ts +37 -0
  31. package/lib/methods/pls.js +480 -130
  32. package/lib/methods/pls.js.map +1 -1
  33. package/lib/pages/__tests__/aiReviewCardStyles.test.d.ts +1 -0
  34. package/lib/pages/__tests__/aiReviewCardStyles.test.js +36 -0
  35. package/lib/pages/__tests__/aiReviewCardStyles.test.js.map +1 -0
  36. package/lib/pages/__tests__/aiReviewChineseColons.test.d.ts +1 -0
  37. package/lib/pages/__tests__/aiReviewChineseColons.test.js +132 -0
  38. package/lib/pages/__tests__/aiReviewChineseColons.test.js.map +1 -0
  39. package/lib/pages/__tests__/clientCssCacheVersion.test.d.ts +1 -0
  40. package/lib/pages/__tests__/clientCssCacheVersion.test.js +31 -0
  41. package/lib/pages/__tests__/clientCssCacheVersion.test.js.map +1 -0
  42. package/lib/pages/__tests__/materialDetailAIReviewFilter.test.d.ts +1 -0
  43. package/lib/pages/__tests__/materialDetailAIReviewFilter.test.js +51 -0
  44. package/lib/pages/__tests__/materialDetailAIReviewFilter.test.js.map +1 -0
  45. package/lib/pages/__tests__/plsReviewCardStyles.test.d.ts +1 -0
  46. package/lib/pages/__tests__/plsReviewCardStyles.test.js +98 -0
  47. package/lib/pages/__tests__/plsReviewCardStyles.test.js.map +1 -0
  48. package/lib/pages/__tests__/plsReviewPage.test.d.ts +1 -0
  49. package/lib/pages/__tests__/plsReviewPage.test.js +338 -0
  50. package/lib/pages/__tests__/plsReviewPage.test.js.map +1 -0
  51. package/main/default/applications/pepsico_content_setting.app.yml +4 -0
  52. package/main/default/client/pepsico_content.client.js +8 -3
  53. package/main/default/objects/ai_process_detail/ai_process_detail.object.yml +1 -0
  54. package/main/default/objects/ai_process_detail/fields/pls_extracted_data.field.yml +9 -0
  55. package/main/default/objects/ai_process_detail/fields/pls_file_ids.field.yml +9 -0
  56. package/main/default/objects/ai_process_detail/fields/pls_product_name_matched.field.yml +9 -0
  57. package/main/default/objects/ai_process_detail/fields/pls_rule_unique_matched.field.yml +9 -0
  58. package/main/default/objects/pepesio_ch__c/fields/company_id.field.yml +14 -0
  59. package/main/default/objects/pepesio_ch__c/fields/company_ids.field.yml +16 -0
  60. package/main/default/objects/pepesio_ch__c/fields/created.field.yml +10 -0
  61. package/main/default/objects/pepesio_ch__c/fields/created_by.field.yml +9 -0
  62. package/main/default/objects/pepesio_ch__c/fields/file_name.field.yml +8 -0
  63. package/main/default/objects/pepesio_ch__c/fields/instance_state.field.yml +25 -0
  64. package/main/default/objects/pepesio_ch__c/fields/locked.field.yml +12 -0
  65. package/main/default/objects/pepesio_ch__c/fields/material_type.field.yml +10 -0
  66. package/main/default/objects/pepesio_ch__c/fields/media_type.field.yml +13 -0
  67. package/main/default/objects/pepesio_ch__c/fields/modified.field.yml +10 -0
  68. package/main/default/objects/pepesio_ch__c/fields/modified_by.field.yml +9 -0
  69. package/main/default/objects/pepesio_ch__c/fields/name.field.yml +11 -0
  70. package/main/default/objects/pepesio_ch__c/fields/owner.field.yml +12 -0
  71. package/main/default/objects/pepesio_ch__c/fields/product.field.yml +10 -0
  72. package/main/default/objects/pepesio_ch__c/fields/time.field.yml +8 -0
  73. package/main/default/objects/pepesio_ch__c/fields/type.field.yml +22 -0
  74. package/main/default/objects/pepesio_ch__c/listviews/all.listview.yml +21 -0
  75. package/main/default/objects/pepesio_ch__c/listviews/recent.listview.yml +11 -0
  76. package/main/default/objects/pepesio_ch__c/pepesio_ch__c.object.yml +31 -0
  77. package/main/default/objects/pepesio_ch__c/permissions/admin.permission.yml +9 -0
  78. package/main/default/objects/pepesio_ch__c/permissions/user.permission.yml +9 -0
  79. package/main/default/objects/pepsico_ai_review/buttons/pepsico_adopt.button.yml +15 -3
  80. package/main/default/objects/pepsico_ai_review/fields/is_active.field.yml +7 -0
  81. package/main/default/objects/pepsico_ai_review/fields/pepsico_material_approval.field.yml +7 -0
  82. package/main/default/objects/pepsico_ai_review/fields/pls_file.field.yml +7 -0
  83. package/main/default/objects/pepsico_ai_review/fields/review_type.field.yml +12 -0
  84. package/main/default/objects/pepsico_approval_process/fields/type.field.yml +5 -1
  85. package/main/default/objects/pepsico_km_pls/fields/allergen_statement.field.yml +9 -0
  86. package/main/default/objects/pepsico_km_pls/fields/ingredient_list.field.yml +9 -0
  87. package/main/default/objects/pepsico_km_pls/listviews/all.listview.yml +6 -0
  88. package/main/default/objects/pepsico_km_pls_file/listviews/lookup.listview.yml +19 -0
  89. package/main/default/objects/pepsico_km_pls_file/permissions/user.permission.yml +2 -2
  90. package/main/default/objects/pepsico_material/fields/pre_qc_approval_status.field.yml +16 -0
  91. package/main/default/objects/pepsico_material/fields/qc_approval_status.field.yml +16 -0
  92. package/main/default/objects/pepsico_material/fields/qc_bypass_reason.field.yml +11 -0
  93. package/main/default/objects/pepsico_material/fields/qc_bypassed.field.yml +12 -0
  94. package/main/default/objects/pepsico_material/fields/qc_bypassed_at.field.yml +11 -0
  95. package/main/default/objects/pepsico_material/fields/qc_bypassed_by.field.yml +12 -0
  96. package/main/default/objects/pepsico_material/listviews/all.listview.yml +1 -0
  97. package/main/default/objects/pepsico_material_approval/buttons/pepsico_modify_pls.button.yml +70 -0
  98. package/main/default/objects/pepsico_material_approval/buttons/pepsico_postpone_review.button.yml +62 -37
  99. package/main/default/objects/pepsico_material_approval/buttons/pre_review_approve.button.yml +1 -1
  100. package/main/default/objects/pepsico_material_approval/buttons/pre_review_reject.button.yml +1 -1
  101. package/main/default/objects/pepsico_material_approval/fields/approved_flows.field.yml +2 -2
  102. package/main/default/objects/pepsico_material_approval/fields/assistance.field.yml +1 -1
  103. package/main/default/objects/pepsico_material_approval/fields/ca_assistance.field.yml +1 -1
  104. package/main/default/objects/pepsico_material_approval/fields/classification.field.yml +15 -0
  105. package/main/default/objects/pepsico_material_approval/fields/ec_check_food_name.field.yml +18 -0
  106. package/main/default/objects/pepsico_material_approval/fields/ec_check_ingredients.field.yml +18 -0
  107. package/main/default/objects/pepsico_material_approval/fields/ec_check_net_content.field.yml +18 -0
  108. package/main/default/objects/pepsico_material_approval/fields/ec_check_producer_address.field.yml +18 -0
  109. package/main/default/objects/pepsico_material_approval/fields/ec_check_producer_name.field.yml +18 -0
  110. package/main/default/objects/pepsico_material_approval/fields/ec_check_shelf_life.field.yml +18 -0
  111. package/main/default/objects/pepsico_material_approval/fields/ec_check_standard_code.field.yml +18 -0
  112. package/main/default/objects/pepsico_material_approval/fields/ec_check_storage_condition.field.yml +18 -0
  113. package/main/default/objects/pepsico_material_approval/fields/ec_has_detail_page.field.yml +18 -0
  114. package/main/default/objects/pepsico_material_approval/fields/ec_producers.$.field.yml +5 -0
  115. package/main/default/objects/pepsico_material_approval/fields/ec_producers.$.producer_address.field.yml +7 -0
  116. package/main/default/objects/pepsico_material_approval/fields/ec_producers.$.producer_name.field.yml +7 -0
  117. package/main/default/objects/pepsico_material_approval/fields/ec_producers.field.yml +16 -0
  118. package/main/default/objects/pepsico_material_approval/fields/ec_qc_production_email.field.yml +16 -0
  119. package/main/default/objects/pepsico_material_approval/fields/ec_related_approval.field.yml +9 -0
  120. package/main/default/objects/pepsico_material_approval/fields/form_version.field.yml +10 -0
  121. package/main/default/objects/pepsico_material_approval/fields/handler_preca.field.yml +1 -1
  122. package/main/default/objects/pepsico_material_approval/fields/handler_preguding.field.yml +1 -1
  123. package/main/default/objects/pepsico_material_approval/fields/handler_prens.field.yml +1 -1
  124. package/main/default/objects/pepsico_material_approval/fields/handler_preqc.field.yml +22 -0
  125. package/main/default/objects/pepsico_material_approval/fields/handler_presra.field.yml +1 -1
  126. package/main/default/objects/pepsico_material_approval/fields/handler_qc.field.yml +20 -0
  127. package/main/default/objects/pepsico_material_approval/fields/has_nutrition_table.field.yml +18 -0
  128. package/main/default/objects/pepsico_material_approval/fields/legal_assistance.field.yml +1 -1
  129. package/main/default/objects/pepsico_material_approval/fields/ns_assistance.field.yml +1 -1
  130. package/main/default/objects/pepsico_material_approval/fields/pls_file.field.yml +2 -2
  131. package/main/default/objects/pepsico_material_approval/fields/pls_km_files.field.yml +28 -0
  132. package/main/default/objects/pepsico_material_approval/fields/pls_upload_files.field.yml +21 -0
  133. package/main/default/objects/pepsico_material_approval/fields/pre_ca_reviewer.field.yml +1 -1
  134. package/main/default/objects/pepsico_material_approval/fields/pre_ns_reviewer.field.yml +1 -1
  135. package/main/default/objects/pepsico_material_approval/fields/pre_qc_approval_status.field.yml +17 -0
  136. package/main/default/objects/pepsico_material_approval/fields/pre_qc_materials_quantity.field.yml +21 -0
  137. package/main/default/objects/pepsico_material_approval/fields/pre_qc_reviewer.field.yml +19 -0
  138. package/main/default/objects/pepsico_material_approval/fields/pre_reviewer.field.yml +1 -1
  139. package/main/default/objects/pepsico_material_approval/fields/pre_sra_reviewer.field.yml +1 -1
  140. package/main/default/objects/pepsico_material_approval/fields/product.field.yml +15 -0
  141. package/main/default/objects/pepsico_material_approval/fields/qc_approval_status.field.yml +18 -0
  142. package/main/default/objects/pepsico_material_approval/fields/qc_assistance.field.yml +22 -0
  143. package/main/default/objects/pepsico_material_approval/fields/qc_bypass_reason.field.yml +8 -0
  144. package/main/default/objects/pepsico_material_approval/fields/qc_bypassed.field.yml +12 -0
  145. package/main/default/objects/pepsico_material_approval/fields/qc_bypassed_at.field.yml +8 -0
  146. package/main/default/objects/pepsico_material_approval/fields/qc_bypassed_by.field.yml +8 -0
  147. package/main/default/objects/pepsico_material_approval/fields/qc_materials_quantity.field.yml +21 -0
  148. package/main/default/objects/pepsico_material_approval/fields/qc_reviewer.field.yml +22 -0
  149. package/main/default/objects/pepsico_material_approval/fields/sra_assistance.field.yml +1 -1
  150. package/main/default/objects/pepsico_material_approval/listviews/all.listview.yml +2 -0
  151. package/main/default/objects/pepsico_material_approval/pepsico_material_approval.object.yml +4 -0
  152. package/main/default/objects/pepsico_pls_change_log/fields/after_pls_files.field.yml +9 -0
  153. package/main/default/objects/pepsico_pls_change_log/fields/after_pls_names.field.yml +7 -0
  154. package/main/default/objects/pepsico_pls_change_log/fields/approval.field.yml +7 -0
  155. package/main/default/objects/pepsico_pls_change_log/fields/approval_name.field.yml +6 -0
  156. package/main/default/objects/pepsico_pls_change_log/fields/before_pls_files.field.yml +9 -0
  157. package/main/default/objects/pepsico_pls_change_log/fields/before_pls_names.field.yml +7 -0
  158. package/main/default/objects/pepsico_pls_change_log/fields/material.field.yml +8 -0
  159. package/main/default/objects/pepsico_pls_change_log/fields/operator.field.yml +7 -0
  160. package/main/default/objects/pepsico_pls_change_log/fields/scene.field.yml +13 -0
  161. package/main/default/objects/pepsico_pls_change_log/fields/source.field.yml +14 -0
  162. package/main/default/objects/pepsico_pls_change_log/fields/space.field.yml +6 -0
  163. package/main/default/objects/pepsico_pls_change_log/listviews/all.listview.yml +25 -0
  164. package/main/default/objects/pepsico_pls_change_log/pepsico_pls_change_log.object.yml +16 -0
  165. package/main/default/objects/pepsico_pls_change_log/permissions/admin.permission.yml +15 -0
  166. package/main/default/objects/pepsico_pls_change_log/permissions/user.permission.yml +14 -0
  167. package/main/default/objects/pepsico_review_postpone/fields/department_type.field.yml +4 -0
  168. package/main/default/objects/pepsico_reviewer/fields/pre_qc_reviewer.field.yml +10 -0
  169. package/main/default/objects/pepsico_reviewer/fields/qc_assistance.field.yml +14 -0
  170. package/main/default/objects/pepsico_reviewer/fields/qc_reviewer.field.yml +12 -0
  171. package/main/default/objects/pepsico_reviewer/listviews/all.listview.yml +4 -0
  172. package/main/default/pages/pepsico_content_review_detail.page.amis.json +19 -1
  173. package/main/default/pages/pepsico_material_detail.page.amis.json +698 -52
  174. package/main/default/pages/pepsico_material_detail_mobile.page.amis.json +332 -8
  175. package/main/default/tabs/object_pepsico_pls_change_log.tab.yml +14 -0
  176. package/main/default/triggers/pepsico_material_approval_change.trigger.yml +3 -0
  177. package/package.json +1 -1
  178. package/public/content-review/amis.css +344 -9
@@ -0,0 +1,736 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var tslib_1 = require("tslib");
4
+ var pls_1 = require("../pls");
5
+ describe('PLS review adoption permission', function () {
6
+ it('allows adoption only for a pending user in a review step', function () {
7
+ expect((0, pls_1.canAdoptReview)({
8
+ current_step: 'departmental_review',
9
+ person_pending_approve: ['reviewer-1']
10
+ }, 'reviewer-1')).toBe(true);
11
+ expect((0, pls_1.canAdoptReview)({
12
+ current_step: 'personnel_review',
13
+ person_pending_approve: ['reviewer-1']
14
+ }, 'reviewer-1')).toBe(true);
15
+ expect((0, pls_1.canAdoptReview)({
16
+ current_step: 'departmental_review',
17
+ person_pending_approve: ['reviewer-2']
18
+ }, 'reviewer-1')).toBe(false);
19
+ expect((0, pls_1.canAdoptReview)({
20
+ current_step: 'conclusion',
21
+ person_pending_approve: ['reviewer-1']
22
+ }, 'reviewer-1')).toBe(false);
23
+ expect((0, pls_1.canAdoptReview)(null, 'reviewer-1')).toBe(false);
24
+ });
25
+ it.each(['awaiting_deployment', 'conclusion'])('allows a spot-check operator to adopt in %s', function (currentStep) {
26
+ expect((0, pls_1.canAdoptReview)({
27
+ current_step: currentStep,
28
+ person_pending_approve: []
29
+ }, 'spot-checker-1', ['user', 'pepsico_spot_check'])).toBe(true);
30
+ });
31
+ it('does not let spot-check permission bypass reviewer-node assignment', function () {
32
+ expect((0, pls_1.canAdoptReview)({
33
+ current_step: 'departmental_review',
34
+ person_pending_approve: ['reviewer-2']
35
+ }, 'spot-checker-1', ['pepsico_spot_check'])).toBe(false);
36
+ });
37
+ it('denies post-review adoption without the spot-check permission set', function () {
38
+ expect((0, pls_1.canAdoptReview)({
39
+ current_step: 'conclusion',
40
+ person_pending_approve: ['viewer-1']
41
+ }, 'viewer-1', ['user'])).toBe(false);
42
+ });
43
+ it('denies adoption in unrelated states even with the spot-check permission set', function () {
44
+ expect((0, pls_1.canAdoptReview)({
45
+ current_step: 'draft',
46
+ person_pending_approve: []
47
+ }, 'spot-checker-1', ['pepsico_spot_check'])).toBe(false);
48
+ });
49
+ it('returns material adoption permission from its approval', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
50
+ var ctx;
51
+ return tslib_1.__generator(this, function (_a) {
52
+ switch (_a.label) {
53
+ case 0:
54
+ ctx = {
55
+ meta: { user: { userId: 'reviewer-1' } },
56
+ getObject: function (name) {
57
+ if (name === 'pepsico_material') {
58
+ return {
59
+ findOne: jest.fn().mockResolvedValue({
60
+ pepsico_material_approval: 'approval-1'
61
+ })
62
+ };
63
+ }
64
+ if (name === 'pepsico_material_approval') {
65
+ return {
66
+ findOne: jest.fn().mockResolvedValue({
67
+ current_step: 'departmental_review',
68
+ person_pending_approve: ['reviewer-1'],
69
+ pls_km_files: ['pls-1']
70
+ })
71
+ };
72
+ }
73
+ throw new Error("Unexpected object ".concat(name));
74
+ }
75
+ };
76
+ return [4, expect((0, pls_1.getMaterialReviewPermissions)(ctx, 'material-1'))
77
+ .resolves.toEqual({ canAdopt: true, hasPLSFile: true })];
78
+ case 1:
79
+ _a.sent();
80
+ return [2];
81
+ }
82
+ });
83
+ }); });
84
+ it('returns material adoption permission for a spot-check operator after review', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
85
+ var ctx;
86
+ return tslib_1.__generator(this, function (_a) {
87
+ switch (_a.label) {
88
+ case 0:
89
+ ctx = {
90
+ meta: {
91
+ user: {
92
+ userId: 'spot-checker-1',
93
+ roles: ['user', 'pepsico_spot_check']
94
+ }
95
+ },
96
+ getObject: function (name) {
97
+ if (name === 'pepsico_material') {
98
+ return {
99
+ findOne: jest.fn().mockResolvedValue({
100
+ pepsico_material_approval: 'approval-1'
101
+ })
102
+ };
103
+ }
104
+ if (name === 'pepsico_material_approval') {
105
+ return {
106
+ findOne: jest.fn().mockResolvedValue({
107
+ current_step: 'awaiting_deployment',
108
+ person_pending_approve: [],
109
+ pls_km_files: ['pls-1']
110
+ })
111
+ };
112
+ }
113
+ throw new Error("Unexpected object ".concat(name));
114
+ }
115
+ };
116
+ return [4, expect((0, pls_1.getMaterialReviewPermissions)(ctx, 'material-1'))
117
+ .resolves.toEqual({ canAdopt: true, hasPLSFile: true })];
118
+ case 1:
119
+ _a.sent();
120
+ return [2];
121
+ }
122
+ });
123
+ }); });
124
+ it('fails closed when the approval or current user is missing', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
125
+ var buildCtx;
126
+ return tslib_1.__generator(this, function (_a) {
127
+ switch (_a.label) {
128
+ case 0:
129
+ buildCtx = function (approval, userId) { return ({
130
+ meta: { user: userId ? { userId: userId } : undefined },
131
+ getObject: function (name) {
132
+ if (name === 'pepsico_material') {
133
+ return {
134
+ findOne: jest.fn().mockResolvedValue({
135
+ pepsico_material_approval: 'approval-1'
136
+ })
137
+ };
138
+ }
139
+ if (name === 'pepsico_material_approval') {
140
+ return { findOne: jest.fn().mockResolvedValue(approval) };
141
+ }
142
+ throw new Error("Unexpected object ".concat(name));
143
+ }
144
+ }); };
145
+ return [4, expect((0, pls_1.getMaterialReviewPermissions)(buildCtx(null, 'reviewer-1'), 'material-1'))
146
+ .resolves.toEqual({ canAdopt: false, hasPLSFile: false })];
147
+ case 1:
148
+ _a.sent();
149
+ return [4, expect((0, pls_1.getMaterialReviewPermissions)(buildCtx({
150
+ current_step: 'departmental_review',
151
+ person_pending_approve: ['reviewer-1']
152
+ }), 'material-1')).resolves.toEqual({ canAdopt: false, hasPLSFile: false })];
153
+ case 2:
154
+ _a.sent();
155
+ return [2];
156
+ }
157
+ });
158
+ }); });
159
+ it.each([
160
+ ['empty array', []],
161
+ ['null', null],
162
+ ['missing field', undefined],
163
+ ['empty values', [null, '']]
164
+ ])('treats %s PLS lookup values as empty', function (_label, plsKmFiles) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
165
+ var ctx;
166
+ return tslib_1.__generator(this, function (_a) {
167
+ switch (_a.label) {
168
+ case 0:
169
+ ctx = {
170
+ meta: { user: { userId: 'viewer-1' } },
171
+ getObject: function (name) {
172
+ if (name === 'pepsico_material') {
173
+ return {
174
+ findOne: jest.fn().mockResolvedValue({
175
+ pepsico_material_approval: 'approval-1'
176
+ })
177
+ };
178
+ }
179
+ if (name === 'pepsico_material_approval') {
180
+ return {
181
+ findOne: jest.fn().mockResolvedValue({
182
+ pls_km_files: plsKmFiles
183
+ })
184
+ };
185
+ }
186
+ throw new Error("Unexpected object ".concat(name));
187
+ }
188
+ };
189
+ return [4, expect((0, pls_1.getMaterialReviewPermissions)(ctx, 'material-1'))
190
+ .resolves.toEqual({ canAdopt: false, hasPLSFile: false })];
191
+ case 1:
192
+ _a.sent();
193
+ return [2];
194
+ }
195
+ });
196
+ }); });
197
+ });
198
+ describe('PLS change log', function () {
199
+ var buildCtx = function (changeLogInsert) { return ({
200
+ meta: { user: { userId: 'sra-1' } },
201
+ getObject: function (name) {
202
+ if (name === 'pepsico_km_pls_file') {
203
+ return {
204
+ directFind: jest.fn().mockResolvedValue([
205
+ { _id: 'pls-a', name: 'PLS 旧版' },
206
+ { _id: 'pls-d', name: 'PLS 新版' }
207
+ ])
208
+ };
209
+ }
210
+ if (name === 'pepsico_pls_change_log') {
211
+ return { directInsert: changeLogInsert };
212
+ }
213
+ throw new Error("Unexpected object ".concat(name));
214
+ }
215
+ }); };
216
+ it('records the change with redundant file names', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
217
+ var changeLogInsert;
218
+ return tslib_1.__generator(this, function (_a) {
219
+ switch (_a.label) {
220
+ case 0:
221
+ changeLogInsert = jest.fn();
222
+ return [4, (0, pls_1.logPLSChange)(buildCtx(changeLogInsert), {
223
+ approval: { _id: 'approval-1', name: 'AP-001', space: 'space-1' },
224
+ scene: 'reviewer_change',
225
+ materialId: 'material-1',
226
+ beforeIds: ['pls-a'],
227
+ afterIds: ['pls-d'],
228
+ source: 'upload'
229
+ })];
230
+ case 1:
231
+ _a.sent();
232
+ expect(changeLogInsert).toHaveBeenCalledWith(expect.objectContaining({
233
+ approval: 'approval-1',
234
+ approval_name: 'AP-001',
235
+ material: 'material-1',
236
+ scene: 'reviewer_change',
237
+ source: 'upload',
238
+ before_pls_files: ['pls-a'],
239
+ before_pls_names: 'PLS 旧版',
240
+ after_pls_files: ['pls-d'],
241
+ after_pls_names: 'PLS 新版',
242
+ operator: 'sra-1',
243
+ space: 'space-1'
244
+ }));
245
+ return [2];
246
+ }
247
+ });
248
+ }); });
249
+ it('skips approvals that never had a PLS file', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
250
+ var changeLogInsert;
251
+ return tslib_1.__generator(this, function (_a) {
252
+ switch (_a.label) {
253
+ case 0:
254
+ changeLogInsert = jest.fn();
255
+ return [4, (0, pls_1.logPLSChange)(buildCtx(changeLogInsert), {
256
+ approval: { _id: 'approval-1' },
257
+ scene: 'submit',
258
+ beforeIds: [],
259
+ afterIds: []
260
+ })];
261
+ case 1:
262
+ _a.sent();
263
+ expect(changeLogInsert).not.toHaveBeenCalled();
264
+ return [2];
265
+ }
266
+ });
267
+ }); });
268
+ it('never throws when the log write fails', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
269
+ var ctx;
270
+ return tslib_1.__generator(this, function (_a) {
271
+ switch (_a.label) {
272
+ case 0:
273
+ ctx = {
274
+ meta: { user: { userId: 'sra-1' } },
275
+ getObject: function () { throw new Error('boom'); }
276
+ };
277
+ return [4, expect((0, pls_1.logPLSChange)(ctx, {
278
+ approval: { _id: 'approval-1' },
279
+ scene: 'submit',
280
+ beforeIds: ['pls-a'],
281
+ afterIds: ['pls-a']
282
+ })).resolves.toBeUndefined()];
283
+ case 1:
284
+ _a.sent();
285
+ return [2];
286
+ }
287
+ });
288
+ }); });
289
+ });
290
+ describe('PLS review source management', function () {
291
+ it('backfills the approval on the PLS record without touching pls_km_files', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
292
+ var plsUpdate, approvalUpdate, ctx, result;
293
+ return tslib_1.__generator(this, function (_a) {
294
+ switch (_a.label) {
295
+ case 0:
296
+ plsUpdate = jest.fn();
297
+ approvalUpdate = jest.fn();
298
+ ctx = {
299
+ meta: { user: { userId: 'sra-1', spaceId: 'space-1' } },
300
+ getObject: function (name) {
301
+ if (name === 'cfs_files_filerecord') {
302
+ return {
303
+ directFind: jest.fn().mockResolvedValue([
304
+ { _id: 'file-1', original: { name: 'PLS新规.pdf', type: 'application/pdf' } }
305
+ ])
306
+ };
307
+ }
308
+ if (name === 'pepsico_km_pls_file') {
309
+ return {
310
+ directInsert: jest.fn().mockResolvedValue({ _id: 'pls-new', name: 'PLS新规' }),
311
+ directUpdate: plsUpdate
312
+ };
313
+ }
314
+ if (name === 'pepsico_material_approval') {
315
+ return { directUpdate: approvalUpdate };
316
+ }
317
+ throw new Error("Unexpected object ".concat(name));
318
+ }
319
+ };
320
+ return [4, (0, pls_1.createApplicationPLSFiles)(ctx, ['file-1'], {
321
+ _id: 'approval-1',
322
+ pls_km_files: ['pls-a']
323
+ })];
324
+ case 1:
325
+ result = _a.sent();
326
+ expect(result.records).toEqual([{ id: 'pls-new', name: 'PLS新规' }]);
327
+ expect(plsUpdate).toHaveBeenCalledWith('pls-new', expect.objectContaining({
328
+ pepsico_material_approval: 'approval-1'
329
+ }));
330
+ expect(approvalUpdate).not.toHaveBeenCalled();
331
+ return [2];
332
+ }
333
+ });
334
+ }); });
335
+ it('shows PLS opinions with true, null, or missing active status', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
336
+ var reviews, ctx, result;
337
+ return tslib_1.__generator(this, function (_a) {
338
+ switch (_a.label) {
339
+ case 0:
340
+ reviews = [
341
+ { _id: 'review-true', pls_file: 'pls-a', is_active: true },
342
+ { _id: 'review-false', pls_file: 'pls-a', is_active: false },
343
+ { _id: 'review-null', pls_file: 'pls-a', is_active: null },
344
+ { _id: 'review-missing', pls_file: 'pls-a' }
345
+ ];
346
+ ctx = {
347
+ meta: { user: { userId: 'viewer-1' } },
348
+ getObject: function (name) {
349
+ if (name === 'pepsico_material') {
350
+ return {
351
+ findOne: jest.fn().mockResolvedValue({
352
+ _id: 'material-1',
353
+ pepsico_material_approval: 'approval-1'
354
+ })
355
+ };
356
+ }
357
+ if (name === 'pepsico_material_approval') {
358
+ return {
359
+ findOne: jest.fn().mockResolvedValue({
360
+ _id: 'approval-1',
361
+ current_step: 'departmental_review',
362
+ person_pending_approve: ['viewer-1']
363
+ })
364
+ };
365
+ }
366
+ if (name === 'pepsico_ai_review') {
367
+ return { find: jest.fn().mockResolvedValue(reviews) };
368
+ }
369
+ if (name === 'pepsico_km_pls_file') {
370
+ return {
371
+ directFind: jest.fn().mockResolvedValue([
372
+ { _id: 'pls-a', name: 'PLS-Lay's & More', is_active: true }
373
+ ])
374
+ };
375
+ }
376
+ throw new Error("Unexpected object ".concat(name));
377
+ }
378
+ };
379
+ return [4, (0, pls_1.getPLSReviewGroups)(ctx, 'material-1')];
380
+ case 1:
381
+ result = _a.sent();
382
+ expect(result.groups).toHaveLength(1);
383
+ expect(result.groups[0].reviews.map(function (review) { return review._id; })).toEqual([
384
+ 'review-true',
385
+ 'review-null',
386
+ 'review-missing'
387
+ ]);
388
+ expect(result.groups[0].plsName).toBe("PLS-Lay's & More");
389
+ expect(result.canAdopt).toBe(true);
390
+ expect(result.groups[0].canAdopt).toBe(true);
391
+ return [2];
392
+ }
393
+ });
394
+ }); });
395
+ it('returns adoptable PLS groups for a spot-check operator after review', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
396
+ var ctx, result;
397
+ return tslib_1.__generator(this, function (_a) {
398
+ switch (_a.label) {
399
+ case 0:
400
+ ctx = {
401
+ meta: {
402
+ user: {
403
+ userId: 'spot-checker-1',
404
+ roles: ['user', 'pepsico_spot_check']
405
+ }
406
+ },
407
+ getObject: function (name) {
408
+ if (name === 'pepsico_material') {
409
+ return {
410
+ findOne: jest.fn().mockResolvedValue({
411
+ _id: 'material-1',
412
+ pepsico_material_approval: 'approval-1'
413
+ })
414
+ };
415
+ }
416
+ if (name === 'pepsico_material_approval') {
417
+ return {
418
+ findOne: jest.fn().mockResolvedValue({
419
+ _id: 'approval-1',
420
+ current_step: 'conclusion',
421
+ person_pending_approve: []
422
+ })
423
+ };
424
+ }
425
+ if (name === 'pepsico_ai_review') {
426
+ return {
427
+ find: jest.fn().mockResolvedValue([
428
+ { _id: 'review-1', pls_file: 'pls-a', is_active: true }
429
+ ])
430
+ };
431
+ }
432
+ if (name === 'pepsico_km_pls_file') {
433
+ return {
434
+ directFind: jest.fn().mockResolvedValue([
435
+ { _id: 'pls-a', name: 'PLS A', is_active: true }
436
+ ])
437
+ };
438
+ }
439
+ throw new Error("Unexpected object ".concat(name));
440
+ }
441
+ };
442
+ return [4, (0, pls_1.getPLSReviewGroups)(ctx, 'material-1')];
443
+ case 1:
444
+ result = _a.sent();
445
+ expect(result.canAdopt).toBe(true);
446
+ expect(result.groups[0].canAdopt).toBe(true);
447
+ return [2];
448
+ }
449
+ });
450
+ }); });
451
+ it('returns review groups with adoption disabled when the approval is missing', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
452
+ var ctx;
453
+ return tslib_1.__generator(this, function (_a) {
454
+ switch (_a.label) {
455
+ case 0:
456
+ ctx = {
457
+ meta: { user: { userId: 'viewer-1' } },
458
+ getObject: function (name) {
459
+ if (name === 'pepsico_material') {
460
+ return {
461
+ findOne: jest.fn().mockResolvedValue({
462
+ _id: 'material-1',
463
+ pepsico_material_approval: 'approval-missing'
464
+ })
465
+ };
466
+ }
467
+ if (name === 'pepsico_material_approval') {
468
+ return { findOne: jest.fn().mockResolvedValue(null) };
469
+ }
470
+ if (name === 'pepsico_ai_review') {
471
+ return {
472
+ find: jest.fn().mockResolvedValue([
473
+ { _id: 'review-1', pls_file: 'pls-a', is_active: true }
474
+ ])
475
+ };
476
+ }
477
+ if (name === 'pepsico_km_pls_file') {
478
+ return {
479
+ directFind: jest.fn().mockResolvedValue([
480
+ { _id: 'pls-a', name: 'PLS A', is_active: true }
481
+ ])
482
+ };
483
+ }
484
+ throw new Error("Unexpected object ".concat(name));
485
+ }
486
+ };
487
+ return [4, expect((0, pls_1.getPLSReviewGroups)(ctx, 'material-1')).resolves.toMatchObject({
488
+ canManage: false,
489
+ canAdopt: false,
490
+ groups: [{
491
+ approvalId: 'approval-missing',
492
+ canManage: false,
493
+ canAdopt: false
494
+ }]
495
+ })];
496
+ case 1:
497
+ _a.sent();
498
+ return [2];
499
+ }
500
+ });
501
+ }); });
502
+ it('offers PLS files with true, null, or missing active status', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
503
+ var ctx, options;
504
+ return tslib_1.__generator(this, function (_a) {
505
+ switch (_a.label) {
506
+ case 0:
507
+ ctx = {
508
+ getObject: function (name) {
509
+ if (name === 'pepsico_km_pls_file') {
510
+ return {
511
+ find: jest.fn().mockResolvedValue([
512
+ { _id: 'pls-true', name: 'PLS-Lay's & More', is_active: true },
513
+ { _id: 'pls-false', name: 'False', is_active: false },
514
+ { _id: 'pls-null', name: 'Null', is_active: null },
515
+ { _id: 'pls-missing', name: 'Missing' }
516
+ ])
517
+ };
518
+ }
519
+ throw new Error("Unexpected object ".concat(name));
520
+ }
521
+ };
522
+ return [4, (0, pls_1.getActivePLSOptions)(ctx)];
523
+ case 1:
524
+ options = _a.sent();
525
+ expect(options.map(function (option) { return option.value; })).toEqual([
526
+ 'pls-true',
527
+ 'pls-null',
528
+ 'pls-missing'
529
+ ]);
530
+ expect(options[0].label).toBe("PLS-Lay's & More");
531
+ return [2];
532
+ }
533
+ });
534
+ }); });
535
+ it('invalidates PLS opinions unless they are explicitly inactive', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
536
+ var reviewUpdate, ctx, invalidated;
537
+ return tslib_1.__generator(this, function (_a) {
538
+ switch (_a.label) {
539
+ case 0:
540
+ reviewUpdate = jest.fn();
541
+ ctx = {
542
+ getObject: function (name) {
543
+ if (name === 'pepsico_ai_review') {
544
+ return {
545
+ directFind: jest.fn().mockResolvedValue([
546
+ { _id: 'review-true', is_active: true },
547
+ { _id: 'review-false', is_active: false },
548
+ { _id: 'review-null', is_active: null },
549
+ { _id: 'review-missing' }
550
+ ]),
551
+ directUpdateMany: reviewUpdate
552
+ };
553
+ }
554
+ throw new Error("Unexpected object ".concat(name));
555
+ }
556
+ };
557
+ return [4, (0, pls_1.invalidatePLSReviews)(ctx, 'approval-1')];
558
+ case 1:
559
+ invalidated = _a.sent();
560
+ expect(invalidated).toBe(3);
561
+ expect(reviewUpdate).toHaveBeenCalledWith(['_id', 'in', ['review-true', 'review-null', 'review-missing']], expect.objectContaining({ is_active: false }));
562
+ return [2];
563
+ }
564
+ });
565
+ }); });
566
+ it('recognizes users from the current reviewer configuration', function () {
567
+ var approval = {
568
+ sra_reviewer: ['sra-1'],
569
+ qc_reviewer: ['qc-1'],
570
+ reviewer: ['fixed-1']
571
+ };
572
+ expect((0, pls_1.isApprovalReviewer)(approval, 'sra-1')).toBe(true);
573
+ expect((0, pls_1.isApprovalReviewer)(approval, 'qc-1')).toBe(true);
574
+ expect((0, pls_1.isApprovalReviewer)(approval, 'owner-1')).toBe(false);
575
+ });
576
+ it('limits invalidation to one material when a material is given', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
577
+ var reviewFind, ctx;
578
+ return tslib_1.__generator(this, function (_a) {
579
+ switch (_a.label) {
580
+ case 0:
581
+ reviewFind = jest.fn().mockResolvedValue([]);
582
+ ctx = {
583
+ getObject: function (name) {
584
+ if (name === 'pepsico_ai_review') {
585
+ return { directFind: reviewFind, directUpdateMany: jest.fn() };
586
+ }
587
+ throw new Error("Unexpected object ".concat(name));
588
+ }
589
+ };
590
+ return [4, (0, pls_1.invalidatePLSReviews)(ctx, 'approval-1', 'pls-a', 'material-1')];
591
+ case 1:
592
+ _a.sent();
593
+ expect(reviewFind).toHaveBeenCalledWith(expect.objectContaining({
594
+ filters: [
595
+ ['pepsico_material_approval', '=', 'approval-1'],
596
+ ['review_type', '=', 'pls'],
597
+ ['pls_file', '=', 'pls-a'],
598
+ ['pepsico_material', '=', 'material-1']
599
+ ]
600
+ }));
601
+ return [2];
602
+ }
603
+ });
604
+ }); });
605
+ it('replaces only the selected PLS source and invalidates its old opinions', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
606
+ var approval, approvalUpdate, reviewUpdate, reviewFind, changeLogInsert, ctx, result;
607
+ return tslib_1.__generator(this, function (_a) {
608
+ switch (_a.label) {
609
+ case 0:
610
+ approval = {
611
+ _id: 'approval-1',
612
+ sra_reviewer: ['sra-1'],
613
+ pls_km_files: ['pls-a', 'pls-b', 'pls-c']
614
+ };
615
+ approvalUpdate = jest.fn();
616
+ reviewUpdate = jest.fn();
617
+ reviewFind = jest.fn().mockResolvedValue([{ _id: 'review-a' }]);
618
+ changeLogInsert = jest.fn();
619
+ ctx = {
620
+ meta: { user: { userId: 'sra-1' } },
621
+ getObject: function (name) {
622
+ if (name === 'pepsico_material_approval') {
623
+ return { findOne: jest.fn().mockResolvedValue(approval), directUpdate: approvalUpdate };
624
+ }
625
+ if (name === 'pepsico_km_pls_file') {
626
+ return { directFind: jest.fn().mockResolvedValue([{ _id: 'pls-d' }]) };
627
+ }
628
+ if (name === 'pepsico_ai_review') {
629
+ return {
630
+ directFind: reviewFind,
631
+ directUpdateMany: reviewUpdate
632
+ };
633
+ }
634
+ if (name === 'pepsico_pls_change_log') {
635
+ return { directInsert: changeLogInsert };
636
+ }
637
+ throw new Error("Unexpected object ".concat(name));
638
+ }
639
+ };
640
+ return [4, (0, pls_1.replaceApprovalPLSSource)(ctx, 'approval-1', 'pls-a', 'pls-d', 'material-1', 'existing')];
641
+ case 1:
642
+ result = _a.sent();
643
+ expect(result.pls_km_files).toEqual(['pls-d', 'pls-b', 'pls-c']);
644
+ expect(approvalUpdate).toHaveBeenCalledWith('approval-1', expect.objectContaining({
645
+ pls_km_files: ['pls-d', 'pls-b', 'pls-c']
646
+ }));
647
+ expect(reviewUpdate).toHaveBeenCalledWith(['_id', 'in', ['review-a']], expect.objectContaining({ is_active: false }));
648
+ expect(reviewFind).toHaveBeenCalledWith(expect.objectContaining({
649
+ filters: expect.arrayContaining([['pepsico_material', '=', 'material-1']])
650
+ }));
651
+ expect(changeLogInsert).toHaveBeenCalledWith(expect.objectContaining({
652
+ scene: 'reviewer_change',
653
+ material: 'material-1',
654
+ source: 'existing',
655
+ before_pls_files: ['pls-a'],
656
+ after_pls_files: ['pls-d']
657
+ }));
658
+ return [2];
659
+ }
660
+ });
661
+ }); });
662
+ it('accepts replacement sources that are not active yet', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
663
+ var approvalUpdate, ctx, result;
664
+ return tslib_1.__generator(this, function (_a) {
665
+ switch (_a.label) {
666
+ case 0:
667
+ approvalUpdate = jest.fn();
668
+ ctx = {
669
+ meta: { user: { userId: 'qc-1' } },
670
+ getObject: function (name) {
671
+ if (name === 'pepsico_material_approval') {
672
+ return {
673
+ findOne: jest.fn().mockResolvedValue({
674
+ _id: 'approval-1',
675
+ qc_reviewer: ['qc-1'],
676
+ pls_km_files: ['pls-a']
677
+ }),
678
+ directUpdate: approvalUpdate
679
+ };
680
+ }
681
+ if (name === 'pepsico_ai_review') {
682
+ return {
683
+ directFind: jest.fn().mockResolvedValue([]),
684
+ directUpdateMany: jest.fn()
685
+ };
686
+ }
687
+ if (name === 'pepsico_km_pls_file') {
688
+ return { directFind: jest.fn().mockResolvedValue([]) };
689
+ }
690
+ if (name === 'pepsico_pls_change_log') {
691
+ return { directInsert: jest.fn() };
692
+ }
693
+ throw new Error("Unexpected object ".concat(name));
694
+ }
695
+ };
696
+ return [4, (0, pls_1.replaceApprovalPLSSource)(ctx, 'approval-1', 'pls-a', 'pls-inactive')];
697
+ case 1:
698
+ result = _a.sent();
699
+ expect(result.pls_km_files).toEqual(['pls-inactive']);
700
+ expect(approvalUpdate).toHaveBeenCalledWith('approval-1', expect.objectContaining({
701
+ pls_km_files: ['pls-inactive']
702
+ }));
703
+ return [2];
704
+ }
705
+ });
706
+ }); });
707
+ it('still rejects sources that are not linked to the approval', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
708
+ var ctx;
709
+ return tslib_1.__generator(this, function (_a) {
710
+ switch (_a.label) {
711
+ case 0:
712
+ ctx = {
713
+ meta: { user: { userId: 'qc-1' } },
714
+ getObject: function (name) {
715
+ if (name === 'pepsico_material_approval') {
716
+ return {
717
+ findOne: jest.fn().mockResolvedValue({
718
+ _id: 'approval-1',
719
+ qc_reviewer: ['qc-1'],
720
+ pls_km_files: ['pls-a']
721
+ })
722
+ };
723
+ }
724
+ throw new Error("Unexpected object ".concat(name));
725
+ }
726
+ };
727
+ return [4, expect((0, pls_1.replaceApprovalPLSSource)(ctx, 'approval-1', 'pls-unlinked', 'pls-new'))
728
+ .rejects.toThrow('当前PLS来源未关联到审批单')];
729
+ case 1:
730
+ _a.sent();
731
+ return [2];
732
+ }
733
+ });
734
+ }); });
735
+ });
736
+ //# sourceMappingURL=plsReview.test.js.map