@contrail/flexplm 1.7.3-alpha.2a853f5 → 1.7.3-alpha.2d7b3be
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/lib/interfaces/interfaces.d.ts +6 -0
- package/lib/interfaces/item-family-changes.d.ts +6 -0
- package/lib/interfaces/item-family-changes.js +8 -0
- package/lib/publish/base-process-publish-assortment.d.ts +5 -0
- package/lib/publish/base-process-publish-assortment.js +71 -2
- package/lib/publish/base-process-publish-assortment.spec.js +256 -0
- package/lib/util/config-defaults.d.ts +2 -0
- package/lib/util/config-defaults.js +2 -0
- package/lib/util/config-defaults.spec.js +47 -0
- package/package.json +1 -1
|
@@ -15,6 +15,12 @@ export interface FCConfig {
|
|
|
15
15
|
vibeEventEndpoint: string;
|
|
16
16
|
csrfEndpoint: string;
|
|
17
17
|
useDistinctRestEndPointForImages?: boolean;
|
|
18
|
+
/** Opt-in. When true, an item family that itself passes meetsCriteria() is published to the
|
|
19
|
+
* season even when every one of its options is still in a pre-development lifecycle stage. */
|
|
20
|
+
publishReleasedFamilyWithPreDevelopmentOptions?: boolean;
|
|
21
|
+
/** Only meaningful when publishReleasedFamilyWithPreDevelopmentOptions is true. When true, such a
|
|
22
|
+
* family is republished on EVERY publish, not only when one of its options actually changed. */
|
|
23
|
+
alwaysPublishReleasedFamilyWithPreDevelopmentOptions?: boolean;
|
|
18
24
|
itemPreDevelopmentLifecycleStages: string[];
|
|
19
25
|
identifierAtts?: {
|
|
20
26
|
[key: string]: string[];
|
|
@@ -12,6 +12,12 @@ export declare class ItemFamilyChanges {
|
|
|
12
12
|
colorDeletes: string[];
|
|
13
13
|
colorUpdates: string[];
|
|
14
14
|
colorUnchanged: string[];
|
|
15
|
+
/** Options that changed but are still pre-development. They do NOT produce
|
|
16
|
+
* LCSSKUSeasonLink events - they only signal that the family needs republishing. */
|
|
17
|
+
preDevelopmentOptionChanges: string[];
|
|
18
|
+
/** True when at least one option row was tracked at family level only, because the option is
|
|
19
|
+
* pre-development while the family itself is released. */
|
|
20
|
+
hasPreDevelopmentOnlyOptions: boolean;
|
|
15
21
|
assortmentItemFullChangeMap: Map<string, any>;
|
|
16
22
|
assortmentItemDeleteMap: Map<string, any>;
|
|
17
23
|
itemToFederatedIdMapping: Map<string, string>;
|
|
@@ -14,6 +14,12 @@ class ItemFamilyChanges {
|
|
|
14
14
|
this.colorDeletes = [];
|
|
15
15
|
this.colorUpdates = [];
|
|
16
16
|
this.colorUnchanged = [];
|
|
17
|
+
/** Options that changed but are still pre-development. They do NOT produce
|
|
18
|
+
* LCSSKUSeasonLink events - they only signal that the family needs republishing. */
|
|
19
|
+
this.preDevelopmentOptionChanges = [];
|
|
20
|
+
/** True when at least one option row was tracked at family level only, because the option is
|
|
21
|
+
* pre-development while the family itself is released. */
|
|
22
|
+
this.hasPreDevelopmentOnlyOptions = false;
|
|
17
23
|
this.assortmentItemFullChangeMap = new Map();
|
|
18
24
|
this.assortmentItemDeleteMap = new Map();
|
|
19
25
|
this.itemToFederatedIdMapping = new Map();
|
|
@@ -32,6 +38,8 @@ class ItemFamilyChanges {
|
|
|
32
38
|
s += ' colorDeletes: [' + this.colorDeletes + ']\n';
|
|
33
39
|
s += ' colorUpdates: [' + this.colorUpdates + ']\n';
|
|
34
40
|
s += ' colorUnchanged: [' + this.colorUnchanged + ']\n';
|
|
41
|
+
s += ' preDevelopmentOptionChanges: [' + this.preDevelopmentOptionChanges + ']\n';
|
|
42
|
+
s += ' hasPreDevelopmentOnlyOptions: ' + this.hasPreDevelopmentOnlyOptions + '\n';
|
|
35
43
|
s += 'assortmentItemFullChangeMap:\n';
|
|
36
44
|
s += 'size: ' + this.assortmentItemFullChangeMap.size + '\n[';
|
|
37
45
|
for (const key of this.assortmentItemFullChangeMap.keys()) {
|
|
@@ -62,6 +62,11 @@ export declare class BaseProcessPublishAssortment {
|
|
|
62
62
|
getFullChangeAssortmentMap(fullChange: any): Map<string, object>;
|
|
63
63
|
getDeleteChangesAssortmentMap(deleteChanges: any[]): Map<string, object>;
|
|
64
64
|
getReleasedForDevelopmentItemAndFamilyIds(fullChange: any, deleteChanges: any): string[];
|
|
65
|
+
/** Adds item families that pass meetsCriteria() but whose option rows are all pre-development.
|
|
66
|
+
* Only ever ADDS family ids - never option ids, and never removes anything. */
|
|
67
|
+
protected addReleasedFamiliesOfPreDevelopmentOptions(aItems: any[], releasedForDevelopmentItemIds: string[]): void;
|
|
68
|
+
protected publishReleasedFamilyWithPreDevOptions(): boolean;
|
|
69
|
+
protected alwaysPublishReleasedFamilyWithPreDevOptions(): boolean;
|
|
65
70
|
meetsCriteria(aItem: any): boolean;
|
|
66
71
|
processPublish(pcd: PublishChangeData, changeDetail: any, fullChange: any, deleteChanges: any): Promise<{
|
|
67
72
|
results: {
|
|
@@ -11,6 +11,7 @@ const fsPromise = require("fs/promises");
|
|
|
11
11
|
const path = require("path");
|
|
12
12
|
const app_framework_1 = require("@contrail/app-framework");
|
|
13
13
|
const event_short_message_status_1 = require("../util/event-short-message-status");
|
|
14
|
+
const config_defaults_1 = require("../util/config-defaults");
|
|
14
15
|
class BaseProcessPublishAssortment {
|
|
15
16
|
constructor(_config, _dc, _mapFileUtil) {
|
|
16
17
|
this.TTL = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
@@ -527,9 +528,43 @@ class BaseProcessPublishAssortment {
|
|
|
527
528
|
}
|
|
528
529
|
}
|
|
529
530
|
}
|
|
531
|
+
if (this.publishReleasedFamilyWithPreDevOptions()) {
|
|
532
|
+
this.addReleasedFamiliesOfPreDevelopmentOptions(assortmentItemsArray, releasedForDevelopmentItemIds);
|
|
533
|
+
this.addReleasedFamiliesOfPreDevelopmentOptions(deleteChanges, releasedForDevelopmentItemIds);
|
|
534
|
+
}
|
|
530
535
|
console.info('releasedForDevelopmentItemIds: ' + releasedForDevelopmentItemIds);
|
|
531
536
|
return releasedForDevelopmentItemIds;
|
|
532
537
|
}
|
|
538
|
+
/** Adds item families that pass meetsCriteria() but whose option rows are all pre-development.
|
|
539
|
+
* Only ever ADDS family ids - never option ids, and never removes anything. */
|
|
540
|
+
addReleasedFamiliesOfPreDevelopmentOptions(aItems, releasedForDevelopmentItemIds) {
|
|
541
|
+
for (const aItem of (aItems || [])) {
|
|
542
|
+
const item = aItem?.item;
|
|
543
|
+
const itemId = item?.id;
|
|
544
|
+
const itemFamilyId = item?.itemFamilyId;
|
|
545
|
+
if (!itemFamilyId || itemId === itemFamilyId) {
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (releasedForDevelopmentItemIds.includes(itemFamilyId)) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (this.meetsCriteria(aItem)) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
const familyItem = item?.itemFamily;
|
|
555
|
+
if (familyItem && this.meetsCriteria({ item: familyItem })) {
|
|
556
|
+
console.info('adding released family with pre-development options: ' + itemFamilyId);
|
|
557
|
+
releasedForDevelopmentItemIds.push(itemFamilyId);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
publishReleasedFamilyWithPreDevOptions() {
|
|
562
|
+
return config_defaults_1.ConfigDefaults.isPropertyTrue(this.config?.publishReleasedFamilyWithPreDevelopmentOptions);
|
|
563
|
+
}
|
|
564
|
+
alwaysPublishReleasedFamilyWithPreDevOptions() {
|
|
565
|
+
return this.publishReleasedFamilyWithPreDevOptions()
|
|
566
|
+
&& config_defaults_1.ConfigDefaults.isPropertyTrue(this.config?.alwaysPublishReleasedFamilyWithPreDevelopmentOptions);
|
|
567
|
+
}
|
|
533
568
|
meetsCriteria(aItem) {
|
|
534
569
|
const item = aItem?.item;
|
|
535
570
|
const lifecycleStage = item?.lifecycleStage;
|
|
@@ -730,7 +765,12 @@ class BaseProcessPublishAssortment {
|
|
|
730
765
|
for (const [itemId, aItem] of assortmentItemFullChangeMap) {
|
|
731
766
|
const projectItem = aItem?.projectItem;
|
|
732
767
|
const itemFamilyId = aItem?.item?.itemFamilyId;
|
|
733
|
-
|
|
768
|
+
const familyInScope = pcd.releasedForDevelopmentItemIds.includes(itemFamilyId);
|
|
769
|
+
const itemInScope = pcd.releasedForDevelopmentItemIds.includes(itemId);
|
|
770
|
+
// Released family + pre-development option: track at family level only (no SKU events).
|
|
771
|
+
const familyOnly = !itemInScope && familyInScope && itemId !== itemFamilyId
|
|
772
|
+
&& this.publishReleasedFamilyWithPreDevOptions();
|
|
773
|
+
if (!familyInScope || (!itemInScope && !familyOnly)) {
|
|
734
774
|
continue;
|
|
735
775
|
}
|
|
736
776
|
const ifc = itemFamilyChanges.get(itemFamilyId) || new item_family_changes_1.ItemFamilyChanges(itemFamilyId, pcd.sinceDate);
|
|
@@ -738,10 +778,29 @@ class BaseProcessPublishAssortment {
|
|
|
738
778
|
ifc.itemFamilyObject = itemId === itemFamilyId ? aItem?.item : aItem?.item?.itemFamily;
|
|
739
779
|
itemFamilyChanges.set(itemFamilyId, ifc);
|
|
740
780
|
}
|
|
781
|
+
else if (itemId === itemFamilyId && this.publishReleasedFamilyWithPreDevOptions()) {
|
|
782
|
+
// A pre-development option row may have created this bucket with the partial
|
|
783
|
+
// item.itemFamily; the family's own row carries the full item, so prefer it.
|
|
784
|
+
ifc.itemFamilyObject = aItem?.item;
|
|
785
|
+
}
|
|
741
786
|
ifc.assortmentItemFullChangeMap.set(itemId, aItem);
|
|
742
787
|
if (pcd.itemToFederatedIdMapping.has(itemId)) {
|
|
743
788
|
ifc.itemToFederatedIdMapping.set(itemId, pcd.itemToFederatedIdMapping.get(itemId));
|
|
744
789
|
}
|
|
790
|
+
if (familyOnly) {
|
|
791
|
+
ifc.hasPreDevelopmentOnlyOptions = true;
|
|
792
|
+
if (addIds.includes(itemId) || deleteIds.includes(itemId) || updateIds.includes(itemId)
|
|
793
|
+
|| (projectItem && new Date(projectItem.updatedOn) > pcd.sinceDate)) {
|
|
794
|
+
ifc.preDevelopmentOptionChanges.push(itemId);
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
ifc.colorUnchanged.push(itemId);
|
|
798
|
+
}
|
|
799
|
+
if (projectItem && !projectItem?.roles && aItem?.item?.roles) {
|
|
800
|
+
projectItem.roles = aItem?.item?.roles;
|
|
801
|
+
}
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
745
804
|
if (itemId === itemFamilyId) {
|
|
746
805
|
if (addIds.includes(itemId))
|
|
747
806
|
ifc.familyAdd = true;
|
|
@@ -780,7 +839,11 @@ class BaseProcessPublishAssortment {
|
|
|
780
839
|
for (const [itemId, delEntity] of assortmentItemDeleteMap) {
|
|
781
840
|
const itemFamilyId = delEntity?.item?.itemFamilyId;
|
|
782
841
|
const item = delEntity?.item;
|
|
783
|
-
|
|
842
|
+
const familyInScope = pcd.releasedForDevelopmentItemIds.includes(itemFamilyId);
|
|
843
|
+
const itemInScope = pcd.releasedForDevelopmentItemIds.includes(itemId);
|
|
844
|
+
const familyOnly = !itemInScope && familyInScope && itemId !== itemFamilyId
|
|
845
|
+
&& this.publishReleasedFamilyWithPreDevOptions();
|
|
846
|
+
if (!familyInScope || (!itemInScope && !familyOnly)) {
|
|
784
847
|
continue;
|
|
785
848
|
}
|
|
786
849
|
else {
|
|
@@ -810,6 +873,10 @@ class BaseProcessPublishAssortment {
|
|
|
810
873
|
if (itemId === itemFamilyId) {
|
|
811
874
|
ifc.familyDelete = true;
|
|
812
875
|
}
|
|
876
|
+
else if (familyOnly) {
|
|
877
|
+
ifc.hasPreDevelopmentOnlyOptions = true;
|
|
878
|
+
ifc.preDevelopmentOptionChanges.push(itemId);
|
|
879
|
+
}
|
|
813
880
|
else {
|
|
814
881
|
ifc.colorDeletes.push(itemId);
|
|
815
882
|
}
|
|
@@ -857,6 +924,8 @@ class BaseProcessPublishAssortment {
|
|
|
857
924
|
const projectItem = this.getProjectItem(itemFamilyChanges, itemFamilyChanges.itemFamilyId);
|
|
858
925
|
const familyAssortmentItem = itemFamilyChanges.familyAdd || itemFamilyChanges.familyUpdate || itemFamilyChanges.familyDelete
|
|
859
926
|
|| itemFamilyChanges.colorAdds.length > 0 || itemFamilyChanges.colorUpdates.length > 0 || itemFamilyChanges.colorDeletes.length > 0
|
|
927
|
+
|| itemFamilyChanges.preDevelopmentOptionChanges.length > 0
|
|
928
|
+
|| (this.alwaysPublishReleasedFamilyWithPreDevOptions() && itemFamilyChanges.hasPreDevelopmentOnlyOptions)
|
|
860
929
|
|| (projectItem && this.updatedSinceDate(projectItem, itemFamilyChanges.sinceDate));
|
|
861
930
|
//familyItemRemoved is used when adding the first option to an assortment
|
|
862
931
|
//and will have updates for the family item.
|
|
@@ -1874,3 +1874,259 @@ describe('sendToFlexPLM / handleVibeIQFile / sendPublishPayloadEvent', () => {
|
|
|
1874
1874
|
});
|
|
1875
1875
|
});
|
|
1876
1876
|
});
|
|
1877
|
+
describe('publishReleasedFamilyWithPreDevelopmentOptions', () => {
|
|
1878
|
+
const FAMILY_ID = 'AERfdvdAt1HHEVvQ';
|
|
1879
|
+
const LIME_ID = 'limeGreenItemId__';
|
|
1880
|
+
const MAROON_ID = 'maroonItemId_____';
|
|
1881
|
+
const SINCE = new Date('2026-09-03T16:54:20.778Z');
|
|
1882
|
+
const BEFORE_SINCE = '2026-09-01T00:00:00.000Z';
|
|
1883
|
+
const AFTER_SINCE = '2026-09-03T17:00:00.000Z';
|
|
1884
|
+
/** The full family item, as it appears on the family's own assortment row. */
|
|
1885
|
+
const fullFamilyItem = {
|
|
1886
|
+
id: FAMILY_ID, itemFamilyId: FAMILY_ID, name: 'Sweatpant', itemNumber: 11,
|
|
1887
|
+
lifecycleStage: 'released', roles: ['family'], richFamilyOnlyField: 'PRESENT'
|
|
1888
|
+
};
|
|
1889
|
+
/** The partial family item, as hydrated onto an option row. */
|
|
1890
|
+
const partialFamilyItem = {
|
|
1891
|
+
id: FAMILY_ID, itemFamilyId: FAMILY_ID, name: 'Sweatpant', itemNumber: 11,
|
|
1892
|
+
lifecycleStage: 'released', roles: ['family']
|
|
1893
|
+
};
|
|
1894
|
+
const familyRow = () => ({
|
|
1895
|
+
entityType: 'assortment-item',
|
|
1896
|
+
id: 'ai-' + FAMILY_ID,
|
|
1897
|
+
itemId: FAMILY_ID,
|
|
1898
|
+
item: { ...fullFamilyItem },
|
|
1899
|
+
projectItem: { id: 'proj:' + FAMILY_ID, itemId: FAMILY_ID, updatedOn: BEFORE_SINCE, roles: ['family'] }
|
|
1900
|
+
});
|
|
1901
|
+
const optionRow = (itemId, optionName, projectItemUpdatedOn) => ({
|
|
1902
|
+
entityType: 'assortment-item',
|
|
1903
|
+
id: 'ai-' + itemId,
|
|
1904
|
+
itemId,
|
|
1905
|
+
item: {
|
|
1906
|
+
id: itemId, itemFamilyId: FAMILY_ID, name: 'Sweatpant', optionName,
|
|
1907
|
+
optionGroup: 'color', lifecycleStage: 'concept', roles: ['color', 'option'],
|
|
1908
|
+
itemFamily: { ...partialFamilyItem }
|
|
1909
|
+
},
|
|
1910
|
+
projectItem: { id: 'proj:' + itemId, itemId, updatedOn: projectItemUpdatedOn, roles: ['color', 'option'] },
|
|
1911
|
+
familyProjectItem: { id: 'proj:' + FAMILY_ID, itemId: FAMILY_ID, updatedOn: BEFORE_SINCE, roles: ['family'] }
|
|
1912
|
+
});
|
|
1913
|
+
const emptyDetail = { adds: [], deletes: [], updates: [], familyItemsRemoved: [] };
|
|
1914
|
+
const baseConfig = { identifierAtts: { LCSProduct: ['itemNumber'] }, itemPreDevelopmentLifecycleStages: ['concept'] };
|
|
1915
|
+
const makePpa2 = (flagOn, alwaysOn) => {
|
|
1916
|
+
const cfg = {
|
|
1917
|
+
...baseConfig,
|
|
1918
|
+
publishReleasedFamilyWithPreDevelopmentOptions: flagOn,
|
|
1919
|
+
alwaysPublishReleasedFamilyWithPreDevelopmentOptions: alwaysOn
|
|
1920
|
+
};
|
|
1921
|
+
const mfu = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
1922
|
+
return new base_process_publish_assortment_1.BaseProcessPublishAssortment(cfg, new data_converter_1.DataConverter(cfg, mfu), mfu);
|
|
1923
|
+
};
|
|
1924
|
+
const makePpa = (flagOn) => makePpa2(flagOn, false);
|
|
1925
|
+
const makePcd = (releasedIds) => {
|
|
1926
|
+
const sf = { entityReference: 'assortment:11EZML5dMn3tREuT', objectClass: 'LCSSeason' };
|
|
1927
|
+
const pcd = new publish_change_data_1.PublishChangeData('11EZML5dMn3tREuT', sf, 'apc1', SINCE);
|
|
1928
|
+
pcd.itemToFederatedIdMapping = new Map();
|
|
1929
|
+
pcd.releasedForDevelopmentItemIds = releasedIds;
|
|
1930
|
+
return pcd;
|
|
1931
|
+
};
|
|
1932
|
+
const eventSeasonFed = { entityReference: 'assortment:a1', objectClass: 'LCSSeason' };
|
|
1933
|
+
const stubEventDeps = (ppa) => {
|
|
1934
|
+
jest.spyOn(ppa, 'getAssortment').mockResolvedValue({ id: 'a1', publishToFlexPLM: true });
|
|
1935
|
+
jest.spyOn(ppa, 'getSeasonalData').mockResolvedValue({});
|
|
1936
|
+
jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockImplementation(async (...args) => args[2]);
|
|
1937
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['itemNumber']);
|
|
1938
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
|
|
1939
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(true);
|
|
1940
|
+
};
|
|
1941
|
+
afterEach(() => { jest.restoreAllMocks(); });
|
|
1942
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
1943
|
+
// getReleasedForDevelopmentItemAndFamilyIds
|
|
1944
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
1945
|
+
it('REGRESSION: flag off - released family with all-concept options stays out of scope', () => {
|
|
1946
|
+
const fullChange = { assortmentItems: [optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE),
|
|
1947
|
+
optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE)] };
|
|
1948
|
+
expect(makePpa(false).getReleasedForDevelopmentItemAndFamilyIds(fullChange, [])).toEqual([]);
|
|
1949
|
+
});
|
|
1950
|
+
it('flag on - released family enters scope; concept options do NOT', () => {
|
|
1951
|
+
const fullChange = { assortmentItems: [optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE),
|
|
1952
|
+
optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE)] };
|
|
1953
|
+
expect(makePpa(true).getReleasedForDevelopmentItemAndFamilyIds(fullChange, [])).toEqual([FAMILY_ID]);
|
|
1954
|
+
});
|
|
1955
|
+
it('flag on - concept family with concept options still stays out of scope', () => {
|
|
1956
|
+
const row = optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE);
|
|
1957
|
+
row.item.itemFamily = { ...partialFamilyItem, lifecycleStage: 'concept' };
|
|
1958
|
+
expect(makePpa(true).getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems: [row] }, [])).toEqual([]);
|
|
1959
|
+
});
|
|
1960
|
+
it('flag on - family reached only through a delete record', () => {
|
|
1961
|
+
const deleted = optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE);
|
|
1962
|
+
expect(makePpa(true).getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems: [] }, [deleted]))
|
|
1963
|
+
.toEqual([FAMILY_ID]);
|
|
1964
|
+
});
|
|
1965
|
+
it('flag on - two concept options of the same family add the family only once', () => {
|
|
1966
|
+
const fullChange = { assortmentItems: [optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE),
|
|
1967
|
+
optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE)] };
|
|
1968
|
+
expect(makePpa(true).getReleasedForDevelopmentItemAndFamilyIds(fullChange, [])).toHaveLength(1);
|
|
1969
|
+
});
|
|
1970
|
+
it('flag on - option row with no hydrated itemFamily is skipped without throwing', () => {
|
|
1971
|
+
const bare = optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE);
|
|
1972
|
+
delete bare.item.itemFamily;
|
|
1973
|
+
expect(makePpa(true).getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems: [bare] }, [])).toEqual([]);
|
|
1974
|
+
});
|
|
1975
|
+
it('released family WITH its own assortment row is in scope with or without the flag', () => {
|
|
1976
|
+
const rows = { assortmentItems: [familyRow(), optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE)] };
|
|
1977
|
+
expect(makePpa(false).getReleasedForDevelopmentItemAndFamilyIds(rows, [])).toEqual([FAMILY_ID]);
|
|
1978
|
+
expect(makePpa(true).getReleasedForDevelopmentItemAndFamilyIds(rows, [])).toEqual([FAMILY_ID]);
|
|
1979
|
+
});
|
|
1980
|
+
it('string flag values from app config are honoured', () => {
|
|
1981
|
+
const fullChange = { assortmentItems: [optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE)] };
|
|
1982
|
+
expect(makePpa('true').getReleasedForDevelopmentItemAndFamilyIds(fullChange, [])).toEqual([FAMILY_ID]);
|
|
1983
|
+
expect(makePpa('false').getReleasedForDevelopmentItemAndFamilyIds(fullChange, [])).toEqual([]);
|
|
1984
|
+
});
|
|
1985
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
1986
|
+
// getItemFamilyChanges
|
|
1987
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
1988
|
+
it('ACTION 2: deleting a concept option marks the released family for republish', () => {
|
|
1989
|
+
const ppa = makePpa(true);
|
|
1990
|
+
const remaining = optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE);
|
|
1991
|
+
const deleted = optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE);
|
|
1992
|
+
const pcd = makePcd([FAMILY_ID]);
|
|
1993
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, ppa.getFullChangeAssortmentMap({ assortmentItems: [remaining] }), ppa.getDeleteChangesAssortmentMap([deleted]));
|
|
1994
|
+
expect(pcd.itemFamilyChanges.size).toEqual(1);
|
|
1995
|
+
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
1996
|
+
expect(ifc.preDevelopmentOptionChanges).toEqual([MAROON_ID]);
|
|
1997
|
+
expect(ifc.hasPreDevelopmentOnlyOptions).toBe(true);
|
|
1998
|
+
expect(ifc.colorDeletes).toEqual([]);
|
|
1999
|
+
expect(ifc.colorAdds).toEqual([]);
|
|
2000
|
+
expect(ifc.itemFamilyObject.id).toEqual(FAMILY_ID);
|
|
2001
|
+
});
|
|
2002
|
+
it('adding a concept option marks the family, not a colorAdd', () => {
|
|
2003
|
+
const ppa = makePpa(true);
|
|
2004
|
+
const added = optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE);
|
|
2005
|
+
const pcd = makePcd([FAMILY_ID]);
|
|
2006
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, { ...emptyDetail, adds: [{ id: MAROON_ID }] }, ppa.getFullChangeAssortmentMap({ assortmentItems: [added] }), new Map());
|
|
2007
|
+
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2008
|
+
expect(ifc.preDevelopmentOptionChanges).toEqual([MAROON_ID]);
|
|
2009
|
+
expect(ifc.colorAdds).toEqual([]);
|
|
2010
|
+
});
|
|
2011
|
+
it('ACTION 1: nothing changed - family bucket exists but signals no change', () => {
|
|
2012
|
+
const ppa = makePpa(true);
|
|
2013
|
+
const pcd = makePcd([FAMILY_ID]);
|
|
2014
|
+
const rows = { assortmentItems: [optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE),
|
|
2015
|
+
optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE)] };
|
|
2016
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, ppa.getFullChangeAssortmentMap(rows), new Map());
|
|
2017
|
+
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2018
|
+
expect(ifc.preDevelopmentOptionChanges).toEqual([]);
|
|
2019
|
+
expect(ifc.colorUnchanged.sort()).toEqual([LIME_ID, MAROON_ID].sort());
|
|
2020
|
+
});
|
|
2021
|
+
it('REGRESSION: flag off - concept option rows produce no ItemFamilyChanges at all', () => {
|
|
2022
|
+
const ppa = makePpa(false);
|
|
2023
|
+
const pcd = makePcd([FAMILY_ID]);
|
|
2024
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, ppa.getFullChangeAssortmentMap({ assortmentItems: [optionRow(LIME_ID, 'Lime Green', AFTER_SINCE)] }), new Map());
|
|
2025
|
+
expect(pcd.itemFamilyChanges.size).toEqual(0);
|
|
2026
|
+
});
|
|
2027
|
+
it('REGRESSION: a released option is still a colorAdd, never a preDevelopmentOptionChange', () => {
|
|
2028
|
+
const ppa = makePpa(true);
|
|
2029
|
+
const released = optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE);
|
|
2030
|
+
released.item.lifecycleStage = 'development';
|
|
2031
|
+
const pcd = makePcd([FAMILY_ID, LIME_ID]);
|
|
2032
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, { ...emptyDetail, adds: [{ id: LIME_ID }] }, ppa.getFullChangeAssortmentMap({ assortmentItems: [released] }), new Map());
|
|
2033
|
+
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2034
|
+
expect(ifc.colorAdds).toEqual([LIME_ID]);
|
|
2035
|
+
expect(ifc.preDevelopmentOptionChanges).toEqual([]);
|
|
2036
|
+
expect(ifc.hasPreDevelopmentOnlyOptions).toBe(false);
|
|
2037
|
+
});
|
|
2038
|
+
it('itemFamilyObject prefers the full family item when the OPTION row is iterated first', () => {
|
|
2039
|
+
const ppa = makePpa(true);
|
|
2040
|
+
const pcd = makePcd([FAMILY_ID]);
|
|
2041
|
+
const map = new Map();
|
|
2042
|
+
map.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2043
|
+
map.set(FAMILY_ID, familyRow());
|
|
2044
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, map, new Map());
|
|
2045
|
+
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2046
|
+
expect(ifc.itemFamilyObject.richFamilyOnlyField).toEqual('PRESENT');
|
|
2047
|
+
});
|
|
2048
|
+
it('itemFamilyObject is the full family item when the FAMILY row is iterated first', () => {
|
|
2049
|
+
const ppa = makePpa(true);
|
|
2050
|
+
const pcd = makePcd([FAMILY_ID]);
|
|
2051
|
+
const map = new Map();
|
|
2052
|
+
map.set(FAMILY_ID, familyRow());
|
|
2053
|
+
map.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2054
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, map, new Map());
|
|
2055
|
+
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2056
|
+
expect(ifc.itemFamilyObject.richFamilyOnlyField).toEqual('PRESENT');
|
|
2057
|
+
});
|
|
2058
|
+
it('delete record with a missing item entity is skipped safely', () => {
|
|
2059
|
+
const ppa = makePpa(true);
|
|
2060
|
+
const pcd = makePcd([FAMILY_ID]);
|
|
2061
|
+
const delMap = new Map();
|
|
2062
|
+
delMap.set('GHOST', { itemId: 'GHOST', item: undefined });
|
|
2063
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, new Map(), delMap);
|
|
2064
|
+
expect(pcd.itemFamilyChanges.size).toEqual(0);
|
|
2065
|
+
});
|
|
2066
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
2067
|
+
// getEventsForItemFamilyChanges
|
|
2068
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
2069
|
+
it('emits exactly one LCSProductSeasonLink and zero LCSSKUSeasonLink', async () => {
|
|
2070
|
+
const ppa = makePpa(true);
|
|
2071
|
+
const ifc = new item_family_changes_1.ItemFamilyChanges(FAMILY_ID, SINCE);
|
|
2072
|
+
ifc.itemFamilyObject = fullFamilyItem;
|
|
2073
|
+
ifc.hasPreDevelopmentOnlyOptions = true;
|
|
2074
|
+
ifc.preDevelopmentOptionChanges.push(MAROON_ID);
|
|
2075
|
+
ifc.assortmentItemFullChangeMap.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2076
|
+
stubEventDeps(ppa);
|
|
2077
|
+
const events = await ppa.getEventsForItemFamilyChanges(ifc, 'a1', eventSeasonFed, new Map());
|
|
2078
|
+
expect(events).toHaveLength(1);
|
|
2079
|
+
expect(events[0].objectClass).toBe('LCSProductSeasonLink');
|
|
2080
|
+
expect(events[0].eventType).toBe('UPSERT_ON_SEASON');
|
|
2081
|
+
expect(events[0].entityReference).toBe('item:' + FAMILY_ID);
|
|
2082
|
+
expect(events[0].LCSProduct['itemNumber']).toBe(11);
|
|
2083
|
+
});
|
|
2084
|
+
it('REGRESSION: an ItemFamilyChanges with no changes still emits nothing', async () => {
|
|
2085
|
+
const ppa = makePpa(true);
|
|
2086
|
+
const ifc = new item_family_changes_1.ItemFamilyChanges(FAMILY_ID, SINCE);
|
|
2087
|
+
ifc.itemFamilyObject = fullFamilyItem;
|
|
2088
|
+
ifc.colorUnchanged.push(LIME_ID, MAROON_ID);
|
|
2089
|
+
ifc.assortmentItemFullChangeMap.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2090
|
+
stubEventDeps(ppa);
|
|
2091
|
+
expect(await ppa.getEventsForItemFamilyChanges(ifc, 'a1', eventSeasonFed, new Map())).toHaveLength(0);
|
|
2092
|
+
});
|
|
2093
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
2094
|
+
// alwaysPublishReleasedFamilyWithPreDevelopmentOptions
|
|
2095
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
2096
|
+
const unchangedFamilyIfc = () => {
|
|
2097
|
+
const ifc = new item_family_changes_1.ItemFamilyChanges(FAMILY_ID, SINCE);
|
|
2098
|
+
ifc.itemFamilyObject = fullFamilyItem;
|
|
2099
|
+
ifc.hasPreDevelopmentOnlyOptions = true;
|
|
2100
|
+
ifc.colorUnchanged.push(LIME_ID, MAROON_ID);
|
|
2101
|
+
ifc.assortmentItemFullChangeMap.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2102
|
+
return ifc;
|
|
2103
|
+
};
|
|
2104
|
+
it('ACTION 1 with always-flag: unchanged family still emits one LCSProductSeasonLink', async () => {
|
|
2105
|
+
const ppa = makePpa2(true, true);
|
|
2106
|
+
stubEventDeps(ppa);
|
|
2107
|
+
const events = await ppa.getEventsForItemFamilyChanges(unchangedFamilyIfc(), 'a1', eventSeasonFed, new Map());
|
|
2108
|
+
expect(events).toHaveLength(1);
|
|
2109
|
+
expect(events[0].objectClass).toBe('LCSProductSeasonLink');
|
|
2110
|
+
});
|
|
2111
|
+
it('always-flag off: unchanged family emits nothing', async () => {
|
|
2112
|
+
const ppa = makePpa2(true, false);
|
|
2113
|
+
stubEventDeps(ppa);
|
|
2114
|
+
expect(await ppa.getEventsForItemFamilyChanges(unchangedFamilyIfc(), 'a1', eventSeasonFed, new Map()))
|
|
2115
|
+
.toHaveLength(0);
|
|
2116
|
+
});
|
|
2117
|
+
it('always-flag cannot act alone when the primary flag is off', async () => {
|
|
2118
|
+
const ppa = makePpa2(false, true);
|
|
2119
|
+
stubEventDeps(ppa);
|
|
2120
|
+
expect(await ppa.getEventsForItemFamilyChanges(unchangedFamilyIfc(), 'a1', eventSeasonFed, new Map()))
|
|
2121
|
+
.toHaveLength(0);
|
|
2122
|
+
});
|
|
2123
|
+
it('REGRESSION: always-flag does not republish an ordinary unchanged released family', async () => {
|
|
2124
|
+
const ppa = makePpa2(true, true);
|
|
2125
|
+
stubEventDeps(ppa);
|
|
2126
|
+
const ifc = new item_family_changes_1.ItemFamilyChanges(FAMILY_ID, SINCE);
|
|
2127
|
+
ifc.itemFamilyObject = fullFamilyItem;
|
|
2128
|
+
ifc.colorUnchanged.push(LIME_ID);
|
|
2129
|
+
ifc.assortmentItemFullChangeMap.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2130
|
+
expect(await ppa.getEventsForItemFamilyChanges(ifc, 'a1', eventSeasonFed, new Map())).toHaveLength(0);
|
|
2131
|
+
});
|
|
2132
|
+
});
|
|
@@ -20,6 +20,8 @@ export declare class ConfigDefaults {
|
|
|
20
20
|
processAsItem: boolean;
|
|
21
21
|
};
|
|
22
22
|
useDistinctRestEndPointForImages: boolean;
|
|
23
|
+
publishReleasedFamilyWithPreDevelopmentOptions: boolean;
|
|
24
|
+
alwaysPublishReleasedFamilyWithPreDevelopmentOptions: boolean;
|
|
23
25
|
csrfEndpoint: string;
|
|
24
26
|
vibeEventEndpoint: string;
|
|
25
27
|
payloadDefaultAsArray: boolean;
|
|
@@ -67,6 +67,8 @@ class ConfigDefaults {
|
|
|
67
67
|
processAsItem: false,
|
|
68
68
|
},
|
|
69
69
|
useDistinctRestEndPointForImages: false,
|
|
70
|
+
publishReleasedFamilyWithPreDevelopmentOptions: false,
|
|
71
|
+
alwaysPublishReleasedFamilyWithPreDevelopmentOptions: false,
|
|
70
72
|
csrfEndpoint: '/servlet/rest/security/csrf',
|
|
71
73
|
vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
|
|
72
74
|
payloadDefaultAsArray: true,
|
|
@@ -393,3 +393,50 @@ describe('all tests', () => {
|
|
|
393
393
|
});
|
|
394
394
|
});
|
|
395
395
|
});
|
|
396
|
+
describe('publishReleasedFamilyWithPreDevelopmentOptions config', () => {
|
|
397
|
+
const config = {
|
|
398
|
+
apiHost: 'http://test.com',
|
|
399
|
+
userName: 'vibeiq',
|
|
400
|
+
password: 'vibeiq',
|
|
401
|
+
plmEnviornment: 'SB'
|
|
402
|
+
};
|
|
403
|
+
it('publishReleasedFamilyWithPreDevelopmentOptions-get default', async () => {
|
|
404
|
+
const startConfig = Object.assign({}, config);
|
|
405
|
+
const fcConfig = await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
|
|
406
|
+
expect(fcConfig['publishReleasedFamilyWithPreDevelopmentOptions']).toBe(false);
|
|
407
|
+
});
|
|
408
|
+
it('alwaysPublishReleasedFamilyWithPreDevelopmentOptions-get default', async () => {
|
|
409
|
+
const startConfig = Object.assign({}, config);
|
|
410
|
+
const fcConfig = await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
|
|
411
|
+
expect(fcConfig['alwaysPublishReleasedFamilyWithPreDevelopmentOptions']).toBe(false);
|
|
412
|
+
});
|
|
413
|
+
it('publishReleasedFamilyWithPreDevelopmentOptions-set boolean true', async () => {
|
|
414
|
+
const startConfig = Object.assign({}, config);
|
|
415
|
+
startConfig['publishReleasedFamilyWithPreDevelopmentOptions'] = true;
|
|
416
|
+
const fcConfig = await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
|
|
417
|
+
expect(config_defaults_1.ConfigDefaults.isPropertyTrue(fcConfig['publishReleasedFamilyWithPreDevelopmentOptions'])).toBe(true);
|
|
418
|
+
});
|
|
419
|
+
it('publishReleasedFamilyWithPreDevelopmentOptions-set string true from app config', async () => {
|
|
420
|
+
const startConfig = Object.assign({}, config);
|
|
421
|
+
startConfig['publishReleasedFamilyWithPreDevelopmentOptions'] = 'true';
|
|
422
|
+
const fcConfig = await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
|
|
423
|
+
expect(config_defaults_1.ConfigDefaults.isPropertyTrue(fcConfig['publishReleasedFamilyWithPreDevelopmentOptions'])).toBe(true);
|
|
424
|
+
});
|
|
425
|
+
it('publishReleasedFamilyWithPreDevelopmentOptions-string false stays off', async () => {
|
|
426
|
+
const startConfig = Object.assign({}, config);
|
|
427
|
+
startConfig['publishReleasedFamilyWithPreDevelopmentOptions'] = 'false';
|
|
428
|
+
const fcConfig = await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
|
|
429
|
+
expect(config_defaults_1.ConfigDefaults.isPropertyTrue(fcConfig['publishReleasedFamilyWithPreDevelopmentOptions'])).toBe(false);
|
|
430
|
+
});
|
|
431
|
+
it('both flags can be delivered through complexConfig', async () => {
|
|
432
|
+
const startConfig = Object.assign({}, config, {
|
|
433
|
+
complexConfig: {
|
|
434
|
+
publishReleasedFamilyWithPreDevelopmentOptions: true,
|
|
435
|
+
alwaysPublishReleasedFamilyWithPreDevelopmentOptions: 'true'
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
const fcConfig = await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
|
|
439
|
+
expect(config_defaults_1.ConfigDefaults.isPropertyTrue(fcConfig['publishReleasedFamilyWithPreDevelopmentOptions'])).toBe(true);
|
|
440
|
+
expect(config_defaults_1.ConfigDefaults.isPropertyTrue(fcConfig['alwaysPublishReleasedFamilyWithPreDevelopmentOptions'])).toBe(true);
|
|
441
|
+
});
|
|
442
|
+
});
|