feed-common 1.10.5 → 1.10.6

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.
@@ -1,180 +1,172 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  import {
2
- OptionType,
3
- OptionTypeItem,
4
- OptionTypeGroup,
5
- ProductUploadProfile,
6
- ProductUploadMapping,
7
- ProductUploadRuleFilterType,
8
- RuleOperatorsType,
9
- ProductUploadRuleItem,
10
- ProductUploadRuleSection,
11
- ProductUploadRules,
3
+ OptionType,
4
+ OptionTypeItem,
5
+ OptionTypeGroup,
6
+ ProductUploadProfile,
7
+ ProductUploadMapping,
8
+ ProductUploadRuleFilterType,
9
+ RuleOperatorsType,
10
+ ProductUploadRuleItem,
11
+ ProductUploadRuleSection,
12
+ ProductUploadRules,
12
13
  } from '../types/profile.types.js';
13
14
  import { ProductUploadMappings } from '../constants/profile.constants.js';
14
15
  import { code } from './utils.js';
15
16
 
16
- export function checkRuleDuplication (rules: ProductUploadRuleItem[]): string[] {
17
- return rules
18
- .map((r) => {
19
- for (const rule of rules) {
20
- if (
21
- r.id !== rule.id &&
22
- r.attribute === rule.attribute &&
23
- r.operator === rule.operator &&
24
- r.value === rule.value
25
- ) {
26
- return r.id;
27
- }
28
- }
29
- return null;
30
- })
31
- .filter(Boolean) as string[];
17
+ /**
18
+ * Check for rules duplication
19
+ * @param rules
20
+ * @returns
21
+ */
22
+ export function checkRuleDuplication(rules: ProductUploadRuleItem[]): string[] {
23
+ return rules
24
+ .map(r => {
25
+ for (const rule of rules) {
26
+ if (
27
+ r.id !== rule.id &&
28
+ r.attribute === rule.attribute &&
29
+ r.operator === rule.operator &&
30
+ r.value === rule.value
31
+ ) {
32
+ return r.id;
33
+ }
34
+ }
35
+ return null;
36
+ })
37
+ .filter(Boolean) as string[];
32
38
  }
33
39
 
34
- export function checkRuleAlwaysness (rules: ProductUploadRuleItem[]): string[] {
35
- return rules
36
- .map((r) => {
37
- for (const rule of rules) {
38
- if (isExlusive(r, rule)) {
39
- return r.id;
40
- }
41
- }
42
- return null;
43
- })
44
- .filter(Boolean) as string[];
40
+ /**
41
+ * Check if rules mutually exclusive
42
+ * @param rules
43
+ * @returns
44
+ */
45
+ export function checkRuleAlwaysness(rules: ProductUploadRuleItem[]): string[] {
46
+ return rules
47
+ .map(r => {
48
+ for (const rule of rules) {
49
+ if (isExclusive(r, rule)) {
50
+ return r.id;
51
+ }
52
+ }
53
+ return null;
54
+ })
55
+ .filter(Boolean) as string[];
45
56
  }
46
57
 
47
- export function checkRuleDeadlocks (
48
- sections: ProductUploadRuleSection[]
49
- ): string[] {
50
- let totalList = [] as string[];
51
-
52
- for (let i = 0; i < sections.length; i++) {
53
- const currentSection = sections[i];
54
- const currentList = [] as string[];
55
-
56
- for (let y = 0; y < sections.length; y++) {
57
- if (i === y) {
58
- continue;
59
- }
60
-
61
- from_start: for (const ruleA of currentSection.ruleItems) {
62
- for (const ruleB of sections[y].ruleItems) {
63
- if (isExlusive(ruleA, ruleB)) {
64
- currentList.push(ruleA.id);
65
- continue from_start;
66
- }
58
+ /**
59
+ * Checks for contradictions in rules, e.g A == 1 and A != 1
60
+ * @param sections
61
+ * @returns
62
+ */
63
+ export function checkRuleDeadlocks(input: ProductUploadRuleSection[]): string[] {
64
+ const totalList = [] as string[][];
65
+ const sections = structuredClone(input);
66
+
67
+ for (let i = 0; i < sections.length - 1; i++) {
68
+ const currentSection = sections[i];
69
+
70
+ for (let y = i + 1; y < sections.length; y++) {
71
+ from_start: for (let x = 0; x < currentSection.ruleItems.length; x++) {
72
+ const ruleA = currentSection.ruleItems[x];
73
+ for (let z = 0; z < sections[y].ruleItems.length; z++) {
74
+ const ruleB = sections[y].ruleItems[z];
75
+ if (isExclusive(ruleA, ruleB)) {
76
+ totalList.push([ruleA.id, ruleB.id]);
77
+ currentSection.ruleItems.splice(x, 1);
78
+ sections[y].ruleItems.splice(z, 1);
79
+ continue from_start;
80
+ }
81
+ }
82
+ }
67
83
  }
68
- }
69
84
  }
70
85
 
71
- if (currentList.length === currentSection.ruleItems.length) {
72
- totalList = [...totalList, ...currentList];
73
- }
74
- }
75
-
76
- return totalList;
86
+ return sections.some(s => s.ruleItems.length === 0) ? totalList.flat() : [];
77
87
  }
78
88
 
79
- function isExlusive (
80
- rule1: ProductUploadRuleItem,
81
- rule2: ProductUploadRuleItem
82
- ): boolean {
83
- return (
84
- rule1.id !== rule2.id &&
85
- rule1.attribute === rule2.attribute &&
86
- rule1.operator ===
87
- getOperatorCounterpart(rule2.operator as keyof RuleOperatorsType) &&
88
- rule1.value === rule2.value
89
- );
89
+ function isExclusive(rule1: ProductUploadRuleItem, rule2: ProductUploadRuleItem): boolean {
90
+ return (
91
+ rule1.id !== rule2.id &&
92
+ rule1.attribute === rule2.attribute &&
93
+ rule1.operator === getOperatorCounterpart(rule2.operator as keyof RuleOperatorsType) &&
94
+ rule1.value === rule2.value
95
+ );
90
96
  }
91
97
 
92
- function getOperatorCounterpart (
93
- operator: keyof RuleOperatorsType
94
- ): keyof RuleOperatorsType {
95
- switch (operator) {
96
- case 'equals':
97
- return 'notEquals';
98
- case 'notEquals':
99
- return 'equals';
100
- case 'contains':
101
- return 'notContains';
102
- case 'notContains':
103
- return 'contains';
104
- case 'startsWith':
105
- return 'notStartsWith';
106
- case 'notStartsWith':
107
- return 'startsWith';
108
- case 'endsWith':
109
- return 'notEndsWith';
110
- case 'notEndsWith':
111
- return 'endsWith';
112
- case 'in':
113
- return 'notIn';
114
- case 'notIn':
115
- return 'in';
116
- case 'greater':
117
- return 'less';
118
- case 'less':
119
- return 'greater';
120
- case 'greaterOrEqual':
121
- return 'lessOrEqual';
122
- case 'lessOrEqual':
123
- return 'greaterOrEqual';
124
- default:
125
- throw new Error(`Unknown operator ${String(operator)}`);
126
- }
98
+ export function getOperatorCounterpart(operator: keyof RuleOperatorsType): keyof RuleOperatorsType {
99
+ switch (operator) {
100
+ case 'equals':
101
+ return 'notEquals';
102
+ case 'notEquals':
103
+ return 'equals';
104
+ case 'contains':
105
+ return 'notContains';
106
+ case 'notContains':
107
+ return 'contains';
108
+ case 'startsWith':
109
+ return 'notStartsWith';
110
+ case 'notStartsWith':
111
+ return 'startsWith';
112
+ case 'endsWith':
113
+ return 'notEndsWith';
114
+ case 'notEndsWith':
115
+ return 'endsWith';
116
+ case 'in':
117
+ return 'notIn';
118
+ case 'notIn':
119
+ return 'in';
120
+ case 'greater':
121
+ return 'less';
122
+ case 'less':
123
+ return 'greater';
124
+ case 'greaterOrEqual':
125
+ return 'lessOrEqual';
126
+ case 'lessOrEqual':
127
+ return 'greaterOrEqual';
128
+ default:
129
+ throw new Error(`Unknown operator ${String(operator)}`);
130
+ }
127
131
  }
128
132
 
129
- export function getEmptyRuleItem (
130
- attribute: ProductUploadRuleFilterType['value'] = ''
131
- ): ProductUploadRuleItem {
132
- return { attribute, operator: '', value: '', id: code() };
133
+ export function getEmptyRuleItem(attribute: ProductUploadRuleFilterType['value'] = ''): ProductUploadRuleItem {
134
+ return { attribute, operator: '', value: '', id: code() };
133
135
  }
134
136
 
135
- export function sortMappings (
136
- a: ProductUploadMapping,
137
- b: ProductUploadMapping
138
- ): number {
139
- if (!a.attribute) {
140
- return 1;
141
- }
142
-
143
- if (!b.attribute) {
144
- return -1;
145
- }
146
-
147
- const sourceA = ProductUploadMappings.find(
148
- (m) => m.attribute === a.attribute
149
- );
150
- const sourceB = ProductUploadMappings.find(
151
- (m) => m.attribute === b.attribute
152
- );
153
-
154
- if (sourceA?.required && !sourceB?.required) {
155
- return -1;
156
- }
157
-
158
- if (sourceB?.required && !sourceA?.required) {
159
- return 1;
160
- }
161
-
162
- if (a.attribute < b.attribute) {
163
- return -1;
164
- }
165
-
166
- if (a.attribute > b.attribute) {
167
- return 1;
168
- }
169
-
170
- return 0;
137
+ export function sortMappings(a: ProductUploadMapping, b: ProductUploadMapping): number {
138
+ if (!a.attribute) {
139
+ return 1;
140
+ }
141
+
142
+ if (!b.attribute) {
143
+ return -1;
144
+ }
145
+
146
+ const sourceA = ProductUploadMappings.find(m => m.attribute === a.attribute);
147
+ const sourceB = ProductUploadMappings.find(m => m.attribute === b.attribute);
148
+
149
+ if (sourceA?.required && !sourceB?.required) {
150
+ return -1;
151
+ }
152
+
153
+ if (sourceB?.required && !sourceA?.required) {
154
+ return 1;
155
+ }
156
+
157
+ if (a.attribute < b.attribute) {
158
+ return -1;
159
+ }
160
+
161
+ if (a.attribute > b.attribute) {
162
+ return 1;
163
+ }
164
+
165
+ return 0;
171
166
  }
172
167
 
173
- export function hasRules (rules: ProductUploadRules): boolean {
174
- return (
175
- Array.isArray(rules?.sections) &&
176
- rules.sections.some((section) => section.ruleItems.length > 0)
177
- );
168
+ export function hasRules(rules: ProductUploadRules): boolean {
169
+ return Array.isArray(rules?.sections) && rules.sections.some(section => section.ruleItems.length > 0);
178
170
  }
179
171
 
180
172
  /**
@@ -183,170 +175,148 @@ export function hasRules (rules: ProductUploadRules): boolean {
183
175
  * @param b
184
176
  * @returns
185
177
  */
186
- export function compareRules (
187
- a: ProductUploadRules = { sections: [] },
188
- b: ProductUploadRules = { sections: [] }
178
+ export function compareRules(
179
+ a: ProductUploadRules = { sections: [] },
180
+ b: ProductUploadRules = { sections: [] }
189
181
  ): boolean {
190
- const matchedSections = [] as number[];
191
- const sameRule = (
192
- rA: ProductUploadRuleItem,
193
- rB: ProductUploadRuleItem
194
- ): boolean => {
195
- return (
196
- rA.attribute === rB.attribute &&
197
- rA.operator === rB.operator &&
198
- compareValues(rA.value, rB.value)
199
- );
200
- };
182
+ const matchedSections = [] as number[];
183
+ const sameRule = (rA: ProductUploadRuleItem, rB: ProductUploadRuleItem): boolean => {
184
+ return rA.attribute === rB.attribute && rA.operator === rB.operator && compareValues(rA.value, rB.value);
185
+ };
201
186
 
202
- a.sections = a.sections.filter((s) => s.ruleItems.length > 0);
203
- b.sections = b.sections.filter((s) => s.ruleItems.length > 0);
187
+ a.sections = a.sections.filter(s => s.ruleItems.length > 0);
188
+ b.sections = b.sections.filter(s => s.ruleItems.length > 0);
204
189
 
205
- if (a.sections.length !== b.sections.length) {
206
- return false;
207
- }
190
+ if (a.sections.length !== b.sections.length) {
191
+ return false;
192
+ }
208
193
 
209
- for (let i = 0; i < a.sections.length; i++) {
210
- const rulesA = a.sections[i].ruleItems;
194
+ for (let i = 0; i < a.sections.length; i++) {
195
+ const rulesA = a.sections[i].ruleItems;
211
196
 
212
- for (let j = 0; j < b.sections.length; j++) {
213
- if (matchedSections.includes(j)) {
214
- continue;
215
- }
197
+ for (let j = 0; j < b.sections.length; j++) {
198
+ if (matchedSections.includes(j)) {
199
+ continue;
200
+ }
216
201
 
217
- const rulesB = b.sections[j].ruleItems;
202
+ const rulesB = b.sections[j].ruleItems;
218
203
 
219
- if (rulesA.length !== rulesB.length) {
220
- continue;
221
- }
204
+ if (rulesA.length !== rulesB.length) {
205
+ continue;
206
+ }
222
207
 
223
- if (
224
- rulesA.every((ruleA) => rulesB.some((ruleB) => sameRule(ruleA, ruleB)))
225
- ) {
226
- matchedSections.push(j);
227
- break;
228
- }
208
+ if (rulesA.every(ruleA => rulesB.some(ruleB => sameRule(ruleA, ruleB)))) {
209
+ matchedSections.push(j);
210
+ break;
211
+ }
212
+ }
229
213
  }
230
- }
231
214
 
232
- return matchedSections.length === a.sections.length;
215
+ return matchedSections.length === a.sections.length;
233
216
  }
234
217
 
235
- function compareValues (a: any, b: any): boolean {
236
- if (Array.isArray(a) && Array.isArray(b)) {
237
- return a.length === b.length && a.every((i) => b.includes(i));
238
- }
218
+ function compareValues(a: any, b: any): boolean {
219
+ if (Array.isArray(a) && Array.isArray(b)) {
220
+ return a.length === b.length && a.every(i => b.includes(i));
221
+ }
239
222
 
240
- return a === b;
223
+ return a === b;
241
224
  }
242
225
 
243
- export function compareMappings (
244
- a: ProductUploadMapping[],
245
- b: ProductUploadMapping[]
246
- ): boolean {
247
- const matchedMappings = [] as number[];
248
-
249
- if (a.length !== b.length) {
250
- return false;
251
- }
252
-
253
- for (let i = 0; i < a.length; i++) {
254
- const mappingA = a[i];
255
-
256
- for (let j = 0; j < b.length; j++) {
257
- if (matchedMappings.includes(j)) {
258
- continue;
259
- }
260
-
261
- const mappingB = b[j];
262
-
263
- if (
264
- mappingA.attribute === mappingB.attribute &&
265
- compareValues(mappingA.value, mappingB.value) &&
266
- compareRules(mappingA.rules, mappingB.rules)
267
- ) {
268
- matchedMappings.push(j);
269
- break;
270
- }
226
+ export function compareMappings(a: ProductUploadMapping[], b: ProductUploadMapping[]): boolean {
227
+ const matchedMappings = [] as number[];
228
+
229
+ if (a.length !== b.length) {
230
+ return false;
231
+ }
232
+
233
+ for (let i = 0; i < a.length; i++) {
234
+ const mappingA = a[i];
235
+
236
+ for (let j = 0; j < b.length; j++) {
237
+ if (matchedMappings.includes(j)) {
238
+ continue;
239
+ }
240
+
241
+ const mappingB = b[j];
242
+
243
+ if (
244
+ mappingA.attribute === mappingB.attribute &&
245
+ compareValues(mappingA.value, mappingB.value) &&
246
+ compareRules(mappingA.rules, mappingB.rules)
247
+ ) {
248
+ matchedMappings.push(j);
249
+ break;
250
+ }
251
+ }
271
252
  }
272
- }
273
253
 
274
- return matchedMappings.length === a.length;
254
+ return matchedMappings.length === a.length;
275
255
  }
276
- export function optionIsString (option: OptionType): option is string {
277
- return typeof option === 'string';
256
+
257
+ export function optionIsString(option: OptionType): option is string {
258
+ return typeof option === 'string';
278
259
  }
279
260
 
280
- export function optionIsItem (option: OptionType): option is OptionTypeItem {
281
- return typeof option !== 'string' && 'value' in option;
261
+ export function optionIsItem(option: OptionType): option is OptionTypeItem {
262
+ return typeof option !== 'string' && 'value' in option;
282
263
  }
283
264
 
284
- export function optionIsGroup (option: OptionType): option is OptionTypeGroup {
285
- return typeof option !== 'string' && 'options' in option;
265
+ export function optionIsGroup(option: OptionType): option is OptionTypeGroup {
266
+ return typeof option !== 'string' && 'options' in option;
286
267
  }
287
- export function detectProfileChange (
288
- a: ProductUploadProfile,
289
- b: ProductUploadProfile
268
+
269
+ export function detectProfileChange(
270
+ a: ProductUploadProfile,
271
+ b: ProductUploadProfile
290
272
  ): { name: boolean; rules: boolean; mappings: boolean; id: boolean } {
291
- const changes = {
292
- name: false,
293
- rules: false,
294
- mappings: false,
295
- id: false,
296
- };
297
-
298
- if (a.name !== b.name) {
299
- changes.name = true;
300
- }
301
-
302
- if (a.mappings !== b.mappings) {
303
- const mappingsA = a.mappings?.filter(
304
- (m) =>
305
- m.attribute !== 'targetCountry' && m.attribute !== 'contentLanguage'
306
- );
307
- const mappingsB = b.mappings?.filter(
308
- (m) =>
309
- m.attribute !== 'targetCountry' && m.attribute !== 'contentLanguage'
310
- );
311
- changes.mappings = !compareMappings(mappingsA, mappingsB);
312
- }
273
+ const changes = {
274
+ name: false,
275
+ rules: false,
276
+ mappings: false,
277
+ id: false,
278
+ };
279
+
280
+ if (a.name !== b.name) {
281
+ changes.name = true;
282
+ }
313
283
 
314
- if (
315
- ['targetCountry', 'contentLanguage'].some((key) => {
316
- const mappingA = a.mappings?.filter((m) => m.attribute === key) ?? [];
317
- const mappingB = b.mappings?.filter((m) => m.attribute === key) ?? [];
284
+ if (
285
+ ProductUploadMappings.filter(m => m.required)
286
+ .map(m => m.attribute)
287
+ .some(key => {
288
+ const mappingA = a.mappings?.filter(m => m.attribute === key) ?? [];
289
+ const mappingB = b.mappings?.filter(m => m.attribute === key) ?? [];
290
+ return !compareMappings(mappingA, mappingB);
291
+ })
292
+ ) {
293
+ changes.id = true;
294
+ }
318
295
 
319
- return !compareMappings(mappingA, mappingB);
320
- })
321
- ) {
322
- changes.id = true;
323
- }
296
+ const requiredAttrs = ProductUploadMappings.filter(m => m.required).map(m => m.attribute);
297
+ const mappingsA = a.mappings?.filter(m => !requiredAttrs.includes(m.attribute));
298
+ const mappingsB = b.mappings?.filter(m => !requiredAttrs.includes(m.attribute));
299
+ changes.mappings = !compareMappings(mappingsA, mappingsB);
324
300
 
325
- if (!compareRules(a.rules, b.rules)) {
326
- changes.rules = true;
327
- }
301
+ if (!compareRules(a.rules, b.rules)) {
302
+ changes.rules = true;
303
+ }
328
304
 
329
- return changes;
305
+ return changes;
330
306
  }
331
307
 
332
- export function sanitizeUploadProfile (
333
- profile: ProductUploadProfile
334
- ): ProductUploadProfile {
335
- if (profile.rules?.sections) {
336
- profile.rules.sections = profile.rules.sections.filter(
337
- (section) => section.ruleItems.length > 0
338
- );
339
- }
340
-
341
- profile.mappings = profile.mappings?.map((mapping) => {
342
- if (mapping.rules?.sections) {
343
- mapping.rules.sections = mapping.rules.sections.filter(
344
- (section) => section.ruleItems.length > 0
345
- );
308
+ export function sanitizeUploadProfile(profile: ProductUploadProfile): ProductUploadProfile {
309
+ if (profile.rules?.sections) {
310
+ profile.rules.sections = profile.rules.sections.filter(section => section.ruleItems.length > 0);
346
311
  }
347
312
 
348
- return mapping;
349
- });
313
+ profile.mappings = profile.mappings?.map(mapping => {
314
+ if (mapping.rules?.sections) {
315
+ mapping.rules.sections = mapping.rules.sections.filter(section => section.ruleItems.length > 0);
316
+ }
317
+
318
+ return mapping;
319
+ });
350
320
 
351
- return profile;
321
+ return profile;
352
322
  }
@@ -0,0 +1,49 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import {
3
+ extractSubscriptionCode,
4
+ makeSubscriptionCode,
5
+ makeSubscriptionName,
6
+ parseSubscriptionName,
7
+ } from '../src/utils/company.js';
8
+ import { ShopifyRecurringCharge } from '../src/types/shopify.types.js';
9
+
10
+ describe('Company', () => {
11
+ test('Make subscription code', () => {
12
+ expect(
13
+ makeSubscriptionCode({
14
+ code: 'G',
15
+ level: 20,
16
+ products: 100,
17
+ profiles: 100,
18
+ price: 100,
19
+ })
20
+ ).toBe('G20');
21
+ });
22
+
23
+ test('Make subscription name', () => {
24
+ expect(
25
+ makeSubscriptionName({
26
+ code: 'G',
27
+ level: 10,
28
+ products: 100,
29
+ profiles: 100,
30
+ price: 100,
31
+ })
32
+ ).toBe('G10');
33
+ });
34
+
35
+ test('Extract subscription code', () => {
36
+ expect(
37
+ extractSubscriptionCode({
38
+ name: 'G50 subscription',
39
+ price: 100,
40
+ status: 'active',
41
+ trial_ends_at: '2022-01-01',
42
+ } as unknown as ShopifyRecurringCharge)
43
+ ).toBe('G50');
44
+ });
45
+
46
+ test('Parse subscription name', () => {
47
+ expect(parseSubscriptionName('G50')).toEqual({ code: 'G', level: '50' });
48
+ });
49
+ });