@unchainedshop/core 4.5.0 → 4.6.1
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/bulk-exporter/createBulkExporter.d.ts +33 -0
- package/lib/bulk-exporter/createBulkExporter.js +46 -0
- package/lib/bulk-exporter/handlers/exportAssortmentsHandler.d.ts +38 -0
- package/lib/bulk-exporter/handlers/exportAssortmentsHandler.js +137 -0
- package/lib/bulk-exporter/handlers/exportFiltersHandler.d.ts +26 -0
- package/lib/bulk-exporter/handlers/exportFiltersHandler.js +88 -0
- package/lib/bulk-exporter/handlers/exportProductsHandler.d.ts +40 -0
- package/lib/bulk-exporter/handlers/exportProductsHandler.js +238 -0
- package/lib/bulk-exporter/handlers/exportUsersHandler.d.ts +40 -0
- package/lib/bulk-exporter/handlers/exportUsersHandler.js +340 -0
- package/lib/bulk-exporter/handlers/generateCSVFileAndUrl.d.ts +13 -0
- package/lib/bulk-exporter/handlers/generateCSVFileAndUrl.js +21 -0
- package/lib/bulk-exporter/handlers/toCSV.d.ts +2 -0
- package/lib/bulk-exporter/handlers/toCSV.js +5 -0
- package/lib/bulk-exporter/index.d.ts +3 -0
- package/lib/bulk-exporter/index.js +3 -0
- package/lib/bulk-importer/handlers/assortment/create.d.ts +0 -2
- package/lib/bulk-importer/handlers/assortment/create.js +0 -1
- package/lib/bulk-importer/handlers/assortment/update.d.ts +0 -2
- package/lib/bulk-importer/handlers/assortment/update.js +0 -1
- package/lib/bulk-importer/handlers/product/create.js +2 -2
- package/lib/bulk-importer/handlers/product/update.js +2 -2
- package/lib/core-index.d.ts +7 -1
- package/lib/core-index.js +5 -1
- package/lib/directors/EnrollmentDirector.d.ts +4 -1
- package/lib/directors/PaymentDirector.js +2 -0
- package/lib/directors/WorkerAdapter.d.ts +2 -0
- package/lib/services/fulfillQuotation.d.ts +3 -0
- package/lib/services/{fullfillQuotation.js → fulfillQuotation.js} +3 -3
- package/lib/services/index.d.ts +2 -2
- package/lib/services/index.js +2 -2
- package/lib/services/processOrder.js +17 -9
- package/lib/services/rejectQuotation.js +1 -1
- package/package.json +21 -20
- package/lib/services/fullfillQuotation.d.ts +0 -3
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import exportProductsHandler from './handlers/exportProductsHandler.ts';
|
|
2
|
+
import exportAssortmentsHandler from './handlers/exportAssortmentsHandler.ts';
|
|
3
|
+
import exportFiltersHandler from './handlers/exportFiltersHandler.ts';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import type { CSVFileResult } from './handlers/generateCSVFileAndUrl.ts';
|
|
6
|
+
export declare const EXPORTS_DIRECTORY = "exports";
|
|
7
|
+
export type ExportFiles = Record<string, CSVFileResult | null>;
|
|
8
|
+
export interface BulkExportOperationResult {
|
|
9
|
+
entity: string;
|
|
10
|
+
success: boolean;
|
|
11
|
+
files: ExportFiles;
|
|
12
|
+
}
|
|
13
|
+
export interface BulkExportHandler<T = unknown> {
|
|
14
|
+
payloadSchema?: z.ZodObject<z.ZodRawShape>;
|
|
15
|
+
(params: Record<string, unknown>, locales: string[], unchainedAPI: T): Promise<ExportFiles>;
|
|
16
|
+
}
|
|
17
|
+
export interface BulkExporterOptions {
|
|
18
|
+
handlers?: Record<string, BulkExportHandler>;
|
|
19
|
+
}
|
|
20
|
+
export default function createBulkExporterFactory(bulkExporterOptions?: BulkExporterOptions): {
|
|
21
|
+
createBulkExporter: ({ entity }: {
|
|
22
|
+
entity: string;
|
|
23
|
+
}) => {
|
|
24
|
+
validate: (payload: Record<string, unknown>) => Promise<void>;
|
|
25
|
+
execute: <T>(payload: Record<string, unknown>, locales: string[], unchainedApi: T) => Promise<({
|
|
26
|
+
entity: string;
|
|
27
|
+
success: boolean;
|
|
28
|
+
files: ExportFiles;
|
|
29
|
+
} | null)[]>;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export type BulkExporter = ReturnType<typeof createBulkExporterFactory>;
|
|
33
|
+
export { exportAssortmentsHandler, exportProductsHandler, exportFiltersHandler };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import exportProductsHandler from "./handlers/exportProductsHandler.js";
|
|
2
|
+
import exportAssortmentsHandler from "./handlers/exportAssortmentsHandler.js";
|
|
3
|
+
import exportFiltersHandler from "./handlers/exportFiltersHandler.js";
|
|
4
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import exportUsersHandler from "./handlers/exportUsersHandler.js";
|
|
7
|
+
const logger = createLogger('unchained:bulk-export');
|
|
8
|
+
export const EXPORTS_DIRECTORY = 'exports';
|
|
9
|
+
let bulkOperationHandlers = {};
|
|
10
|
+
export default function createBulkExporterFactory(bulkExporterOptions) {
|
|
11
|
+
bulkOperationHandlers = {
|
|
12
|
+
ASSORTMENTS: exportAssortmentsHandler,
|
|
13
|
+
PRODUCTS: exportProductsHandler,
|
|
14
|
+
FILTERS: exportFiltersHandler,
|
|
15
|
+
USER: exportUsersHandler,
|
|
16
|
+
...(bulkExporterOptions?.handlers || {}),
|
|
17
|
+
};
|
|
18
|
+
const createBulkExporter = ({ entity }) => {
|
|
19
|
+
const type = entity.toUpperCase();
|
|
20
|
+
const exportHandler = bulkOperationHandlers[type];
|
|
21
|
+
if (!exportHandler) {
|
|
22
|
+
throw new Error(`Export entity (${entity}) is not supported`);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
validate: async (payload) => {
|
|
26
|
+
logger.debug(`Validating ${type} export payload`);
|
|
27
|
+
try {
|
|
28
|
+
if (exportHandler.payloadSchema) {
|
|
29
|
+
exportHandler.payloadSchema.parse(payload);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
throw new Error(`${type}: ${e.message}`);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
execute: async (payload, locales, unchainedApi) => {
|
|
37
|
+
const files = await exportHandler(payload, locales, unchainedApi);
|
|
38
|
+
return [{ entity: type, success: true, files }, null];
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
createBulkExporter,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export { exportAssortmentsHandler, exportProductsHandler, exportFiltersHandler };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { UnchainedCore } from '../../core-index.ts';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const AssortmentExportPayloadSchema: z.ZodObject<{
|
|
4
|
+
exportAssortments: z.ZodOptional<z.ZodBoolean>;
|
|
5
|
+
exportLinks: z.ZodOptional<z.ZodBoolean>;
|
|
6
|
+
exportProducts: z.ZodOptional<z.ZodBoolean>;
|
|
7
|
+
exportFilters: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
+
queryString: z.ZodOptional<z.ZodString>;
|
|
9
|
+
includeInactive: z.ZodOptional<z.ZodBoolean>;
|
|
10
|
+
includeLeaves: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export interface AssortmentExportParams {
|
|
14
|
+
exportAssortments?: boolean;
|
|
15
|
+
exportLinks?: boolean;
|
|
16
|
+
exportProducts?: boolean;
|
|
17
|
+
exportFilters?: boolean;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
}
|
|
20
|
+
declare const exportAssortmentsHandler: {
|
|
21
|
+
({ exportAssortments, exportFilters, exportLinks, exportProducts, ...params }: AssortmentExportParams, locales: string[], unchainedAPI: UnchainedCore): Promise<{
|
|
22
|
+
assortments: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
23
|
+
filters: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
24
|
+
products: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
25
|
+
children: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
26
|
+
}>;
|
|
27
|
+
payloadSchema: z.ZodObject<{
|
|
28
|
+
exportAssortments: z.ZodOptional<z.ZodBoolean>;
|
|
29
|
+
exportLinks: z.ZodOptional<z.ZodBoolean>;
|
|
30
|
+
exportProducts: z.ZodOptional<z.ZodBoolean>;
|
|
31
|
+
exportFilters: z.ZodOptional<z.ZodBoolean>;
|
|
32
|
+
queryString: z.ZodOptional<z.ZodString>;
|
|
33
|
+
includeInactive: z.ZodOptional<z.ZodBoolean>;
|
|
34
|
+
includeLeaves: z.ZodOptional<z.ZodBoolean>;
|
|
35
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
36
|
+
}, z.core.$strip>;
|
|
37
|
+
};
|
|
38
|
+
export default exportAssortmentsHandler;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import generateCSVFileAndURL from "./generateCSVFileAndUrl.js";
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { EXPORTS_DIRECTORY } from "../createBulkExporter.js";
|
|
4
|
+
export const AssortmentExportPayloadSchema = z.object({
|
|
5
|
+
exportAssortments: z.boolean().optional(),
|
|
6
|
+
exportLinks: z.boolean().optional(),
|
|
7
|
+
exportProducts: z.boolean().optional(),
|
|
8
|
+
exportFilters: z.boolean().optional(),
|
|
9
|
+
queryString: z.string().optional(),
|
|
10
|
+
includeInactive: z.boolean().optional(),
|
|
11
|
+
includeLeaves: z.boolean().optional(),
|
|
12
|
+
tags: z.array(z.string()).optional(),
|
|
13
|
+
});
|
|
14
|
+
const ASSORTMENT_CSV_SCHEMA = {
|
|
15
|
+
base: ['_id', 'isActive', 'isBase', 'isRoot', 'sequence', 'tags'],
|
|
16
|
+
textFields: ['title', 'subtitle', 'description', 'slug'],
|
|
17
|
+
filterFields: ['_id', 'assortmentId', 'filterId', 'tags', 'sortKey'],
|
|
18
|
+
productFields: ['_id', 'assortmentId', 'productId', 'tags', 'sortKey'],
|
|
19
|
+
childrenFields: ['_id', 'assortmentId', 'childAssortmentId', 'tags', 'sortKey'],
|
|
20
|
+
};
|
|
21
|
+
const buildAssortmentHeaders = (locales) => [
|
|
22
|
+
...ASSORTMENT_CSV_SCHEMA.base,
|
|
23
|
+
...locales.flatMap((locale) => ASSORTMENT_CSV_SCHEMA.textFields.map((field) => `texts.${locale}.${field}`)),
|
|
24
|
+
'meta',
|
|
25
|
+
];
|
|
26
|
+
const buildFilterHeaders = () => ASSORTMENT_CSV_SCHEMA.filterFields;
|
|
27
|
+
const buildProductHeaders = () => ASSORTMENT_CSV_SCHEMA.productFields;
|
|
28
|
+
const buildChildrenHeaders = () => ASSORTMENT_CSV_SCHEMA.childrenFields;
|
|
29
|
+
const fetchAssortmentTexts = async (modules, assortmentId) => {
|
|
30
|
+
const texts = await modules.assortments.texts.findTexts({ assortmentId });
|
|
31
|
+
return texts.reduce((acc, t) => ({ ...acc, [t.locale]: t }), {});
|
|
32
|
+
};
|
|
33
|
+
const exportAssortmentsHandler = async ({ exportAssortments, exportFilters, exportLinks, exportProducts, ...params }, locales, unchainedAPI) => {
|
|
34
|
+
const { modules } = unchainedAPI;
|
|
35
|
+
const assortments = await modules.assortments.findAssortments({ ...params });
|
|
36
|
+
const assortmentRows = [];
|
|
37
|
+
const filterRows = [];
|
|
38
|
+
const productRows = [];
|
|
39
|
+
const childrenRows = [];
|
|
40
|
+
for await (const assortment of assortments) {
|
|
41
|
+
if (exportAssortments) {
|
|
42
|
+
const texts = await fetchAssortmentTexts(modules, assortment._id);
|
|
43
|
+
const row = { ...assortment };
|
|
44
|
+
locales.forEach((locale) => {
|
|
45
|
+
const t = texts[locale] || {};
|
|
46
|
+
ASSORTMENT_CSV_SCHEMA.textFields.forEach((field) => {
|
|
47
|
+
let value = t[field];
|
|
48
|
+
if (Array.isArray(value))
|
|
49
|
+
value = value.join(';');
|
|
50
|
+
row[`texts.${locale}.${field}`] = value ?? '';
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
row['meta'] = typeof assortment?.meta === 'object' ? JSON.stringify(assortment.meta) : '';
|
|
54
|
+
assortmentRows.push(row);
|
|
55
|
+
}
|
|
56
|
+
if (exportFilters) {
|
|
57
|
+
const assortmentFilters = await modules.assortments.filters.findFilters({
|
|
58
|
+
assortmentId: assortment._id,
|
|
59
|
+
});
|
|
60
|
+
filterRows.push(...assortmentFilters.map(({ _id, filterId, tags, sortKey }) => ({
|
|
61
|
+
_id,
|
|
62
|
+
assortmentId: assortment._id,
|
|
63
|
+
filterId,
|
|
64
|
+
tags: tags || '',
|
|
65
|
+
sortKey,
|
|
66
|
+
})));
|
|
67
|
+
}
|
|
68
|
+
if (exportProducts) {
|
|
69
|
+
const assortmentProducts = await modules.assortments.products.findAssortmentProducts({
|
|
70
|
+
assortmentId: assortment._id,
|
|
71
|
+
});
|
|
72
|
+
productRows.push(...assortmentProducts.map(({ _id, productId, tags, sortKey }) => ({
|
|
73
|
+
_id,
|
|
74
|
+
assortmentId: assortment._id,
|
|
75
|
+
productId,
|
|
76
|
+
tags: tags || '',
|
|
77
|
+
sortKey,
|
|
78
|
+
})));
|
|
79
|
+
}
|
|
80
|
+
if (exportLinks) {
|
|
81
|
+
const links = await modules.assortments.links.findLinks({ assortmentId: assortment._id });
|
|
82
|
+
childrenRows.push(...links
|
|
83
|
+
.filter((l) => l.childAssortmentId !== assortment._id)
|
|
84
|
+
.map(({ _id, childAssortmentId, tags, sortKey }) => ({
|
|
85
|
+
_id,
|
|
86
|
+
assortmentId: assortment._id,
|
|
87
|
+
childAssortmentId,
|
|
88
|
+
tags: tags || '',
|
|
89
|
+
sortKey,
|
|
90
|
+
})));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const assortmentsCSV = exportAssortments
|
|
94
|
+
? await generateCSVFileAndURL({
|
|
95
|
+
headers: buildAssortmentHeaders(locales),
|
|
96
|
+
rows: assortmentRows,
|
|
97
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
98
|
+
fileName: 'assortments_export.csv',
|
|
99
|
+
unchainedAPI,
|
|
100
|
+
})
|
|
101
|
+
: null;
|
|
102
|
+
const filtersCSV = exportFilters
|
|
103
|
+
? await generateCSVFileAndURL({
|
|
104
|
+
headers: buildFilterHeaders(),
|
|
105
|
+
rows: filterRows,
|
|
106
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
107
|
+
fileName: 'assortment_filters_export.csv',
|
|
108
|
+
unchainedAPI,
|
|
109
|
+
})
|
|
110
|
+
: null;
|
|
111
|
+
const productsCSV = exportProducts
|
|
112
|
+
? await generateCSVFileAndURL({
|
|
113
|
+
headers: buildProductHeaders(),
|
|
114
|
+
rows: productRows,
|
|
115
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
116
|
+
fileName: 'assortment_products_export.csv',
|
|
117
|
+
unchainedAPI,
|
|
118
|
+
})
|
|
119
|
+
: null;
|
|
120
|
+
const childrenCSV = exportLinks
|
|
121
|
+
? await generateCSVFileAndURL({
|
|
122
|
+
headers: buildChildrenHeaders(),
|
|
123
|
+
rows: childrenRows,
|
|
124
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
125
|
+
fileName: 'assortment_links_export.csv',
|
|
126
|
+
unchainedAPI,
|
|
127
|
+
})
|
|
128
|
+
: null;
|
|
129
|
+
return {
|
|
130
|
+
assortments: assortmentsCSV,
|
|
131
|
+
filters: filtersCSV,
|
|
132
|
+
products: productsCSV,
|
|
133
|
+
children: childrenCSV,
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
export default exportAssortmentsHandler;
|
|
137
|
+
exportAssortmentsHandler.payloadSchema = AssortmentExportPayloadSchema;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { UnchainedCore } from '../../core-index.ts';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const FilterExportPayloadSchema: z.ZodObject<{
|
|
4
|
+
exportFilters: z.ZodOptional<z.ZodBoolean>;
|
|
5
|
+
exportFilterOptions: z.ZodOptional<z.ZodBoolean>;
|
|
6
|
+
queryString: z.ZodOptional<z.ZodString>;
|
|
7
|
+
includeInactive: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
export interface FilterExportParams {
|
|
10
|
+
exportFilters?: boolean;
|
|
11
|
+
exportFilterOptions?: boolean;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
declare const exportFiltersHandler: {
|
|
15
|
+
({ exportFilterOptions, exportFilters, ...params }: FilterExportParams, locales: string[], unchainedAPI: UnchainedCore): Promise<{
|
|
16
|
+
filters: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
17
|
+
filterOptions: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
18
|
+
}>;
|
|
19
|
+
payloadSchema: z.ZodObject<{
|
|
20
|
+
exportFilters: z.ZodOptional<z.ZodBoolean>;
|
|
21
|
+
exportFilterOptions: z.ZodOptional<z.ZodBoolean>;
|
|
22
|
+
queryString: z.ZodOptional<z.ZodString>;
|
|
23
|
+
includeInactive: z.ZodOptional<z.ZodBoolean>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
};
|
|
26
|
+
export default exportFiltersHandler;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import generateCSVFileAndURL from "./generateCSVFileAndUrl.js";
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { EXPORTS_DIRECTORY } from "../createBulkExporter.js";
|
|
4
|
+
export const FilterExportPayloadSchema = z.object({
|
|
5
|
+
exportFilters: z.boolean().optional(),
|
|
6
|
+
exportFilterOptions: z.boolean().optional(),
|
|
7
|
+
queryString: z.string().optional(),
|
|
8
|
+
includeInactive: z.boolean().optional(),
|
|
9
|
+
});
|
|
10
|
+
const FILTER_CSV_SCHEMA = {
|
|
11
|
+
filterFields: ['_id', 'key', 'type', 'isActive'],
|
|
12
|
+
optionFields: ['optionId', 'filterId', 'value'],
|
|
13
|
+
textFields: ['title', 'subtitle'],
|
|
14
|
+
};
|
|
15
|
+
const buildFilterHeaders = (locales) => [
|
|
16
|
+
...FILTER_CSV_SCHEMA.filterFields,
|
|
17
|
+
...locales.flatMap((locale) => FILTER_CSV_SCHEMA.textFields.map((field) => `texts.${locale}.${field}`)),
|
|
18
|
+
'meta',
|
|
19
|
+
];
|
|
20
|
+
const buildOptionHeaders = (locales) => [
|
|
21
|
+
...FILTER_CSV_SCHEMA.optionFields,
|
|
22
|
+
...locales.flatMap((locale) => FILTER_CSV_SCHEMA.textFields.map((field) => `texts.${locale}.${field}`)),
|
|
23
|
+
];
|
|
24
|
+
const mapTextsToRow = (row, texts, locales) => {
|
|
25
|
+
locales.forEach((locale) => {
|
|
26
|
+
const t = texts[locale] || {};
|
|
27
|
+
FILTER_CSV_SCHEMA.textFields.forEach((field) => {
|
|
28
|
+
row[`texts.${locale}.${field}`] = t[field] ?? '';
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const fetchTexts = async (modules, filterId, filterOptionValue) => {
|
|
33
|
+
const texts = await modules.filters.texts.findTexts({ filterId, filterOptionValue });
|
|
34
|
+
return texts.reduce((acc, t) => ({ ...acc, [t.locale]: t }), {});
|
|
35
|
+
};
|
|
36
|
+
const exportFiltersHandler = async ({ exportFilterOptions, exportFilters, ...params }, locales, unchainedAPI) => {
|
|
37
|
+
const { modules } = unchainedAPI;
|
|
38
|
+
const filters = await modules.filters.findFilters({ ...params });
|
|
39
|
+
const filterRows = [];
|
|
40
|
+
const optionRows = [];
|
|
41
|
+
for await (const filter of filters) {
|
|
42
|
+
if (exportFilters) {
|
|
43
|
+
const filterTexts = await fetchTexts(modules, filter._id);
|
|
44
|
+
const row = {
|
|
45
|
+
_id: filter._id,
|
|
46
|
+
key: filter.key,
|
|
47
|
+
type: filter.type,
|
|
48
|
+
isActive: filter.isActive,
|
|
49
|
+
meta: typeof filter?.meta === 'object' ? JSON.stringify(filter.meta) : '',
|
|
50
|
+
};
|
|
51
|
+
mapTextsToRow(row, filterTexts, locales);
|
|
52
|
+
filterRows.push(row);
|
|
53
|
+
}
|
|
54
|
+
if (exportFilterOptions) {
|
|
55
|
+
for await (const optionValue of filter.options) {
|
|
56
|
+
const optionTexts = await fetchTexts(modules, filter._id, optionValue);
|
|
57
|
+
const optionRow = {
|
|
58
|
+
optionId: `${filter._id}:${optionValue}`,
|
|
59
|
+
filterId: filter._id,
|
|
60
|
+
value: optionValue,
|
|
61
|
+
};
|
|
62
|
+
mapTextsToRow(optionRow, optionTexts, locales);
|
|
63
|
+
optionRows.push(optionRow);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const filtersCSV = exportFilters
|
|
68
|
+
? await generateCSVFileAndURL({
|
|
69
|
+
headers: buildFilterHeaders(locales),
|
|
70
|
+
rows: filterRows,
|
|
71
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
72
|
+
fileName: 'filters_export.csv',
|
|
73
|
+
unchainedAPI,
|
|
74
|
+
})
|
|
75
|
+
: null;
|
|
76
|
+
const optionsCSV = exportFilterOptions
|
|
77
|
+
? await generateCSVFileAndURL({
|
|
78
|
+
headers: buildOptionHeaders(locales),
|
|
79
|
+
rows: optionRows,
|
|
80
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
81
|
+
fileName: 'filter_options_export.csv',
|
|
82
|
+
unchainedAPI,
|
|
83
|
+
})
|
|
84
|
+
: null;
|
|
85
|
+
return { filters: filtersCSV, filterOptions: optionsCSV };
|
|
86
|
+
};
|
|
87
|
+
export default exportFiltersHandler;
|
|
88
|
+
exportFiltersHandler.payloadSchema = FilterExportPayloadSchema;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { UnchainedCore } from '../../core-index.ts';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const ProductExportPayloadSchema: z.ZodObject<{
|
|
4
|
+
exportProducts: z.ZodOptional<z.ZodBoolean>;
|
|
5
|
+
exportBundleItems: z.ZodOptional<z.ZodBoolean>;
|
|
6
|
+
exportPrices: z.ZodOptional<z.ZodBoolean>;
|
|
7
|
+
exportVariations: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
+
exportVariationOptions: z.ZodOptional<z.ZodBoolean>;
|
|
9
|
+
queryString: z.ZodOptional<z.ZodString>;
|
|
10
|
+
includeDrafts: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export interface ProductExportParams {
|
|
14
|
+
exportProducts?: boolean;
|
|
15
|
+
exportBundleItems?: boolean;
|
|
16
|
+
exportPrices?: boolean;
|
|
17
|
+
exportVariations?: boolean;
|
|
18
|
+
exportVariationOptions?: boolean;
|
|
19
|
+
[key: string]: any;
|
|
20
|
+
}
|
|
21
|
+
declare const exportProductsHandler: {
|
|
22
|
+
({ exportBundleItems, exportPrices, exportProducts, exportVariationOptions, exportVariations, ...params }: ProductExportParams, locales: string[], unchainedAPI: UnchainedCore): Promise<{
|
|
23
|
+
products: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
24
|
+
prices: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
25
|
+
bundles: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
26
|
+
variations: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
27
|
+
variationOptions: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
28
|
+
}>;
|
|
29
|
+
payloadSchema: z.ZodObject<{
|
|
30
|
+
exportProducts: z.ZodOptional<z.ZodBoolean>;
|
|
31
|
+
exportBundleItems: z.ZodOptional<z.ZodBoolean>;
|
|
32
|
+
exportPrices: z.ZodOptional<z.ZodBoolean>;
|
|
33
|
+
exportVariations: z.ZodOptional<z.ZodBoolean>;
|
|
34
|
+
exportVariationOptions: z.ZodOptional<z.ZodBoolean>;
|
|
35
|
+
queryString: z.ZodOptional<z.ZodString>;
|
|
36
|
+
includeDrafts: z.ZodOptional<z.ZodBoolean>;
|
|
37
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
};
|
|
40
|
+
export default exportProductsHandler;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import generateCSVFileAndURL from "./generateCSVFileAndUrl.js";
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { EXPORTS_DIRECTORY } from "../createBulkExporter.js";
|
|
4
|
+
export const ProductExportPayloadSchema = z.object({
|
|
5
|
+
exportProducts: z.boolean().optional(),
|
|
6
|
+
exportBundleItems: z.boolean().optional(),
|
|
7
|
+
exportPrices: z.boolean().optional(),
|
|
8
|
+
exportVariations: z.boolean().optional(),
|
|
9
|
+
exportVariationOptions: z.boolean().optional(),
|
|
10
|
+
queryString: z.string().optional(),
|
|
11
|
+
includeDrafts: z.boolean().optional(),
|
|
12
|
+
tags: z.array(z.string()).optional(),
|
|
13
|
+
});
|
|
14
|
+
const PRODUCT_CSV_SCHEMA = {
|
|
15
|
+
base: ['_id', 'sku', 'baseUnit', 'sequence', 'status', 'tags', 'updated', 'published', 'type'],
|
|
16
|
+
textFields: ['title', 'subtitle', 'description', 'vendor', 'brand', 'labels', 'slug'],
|
|
17
|
+
priceFields: [
|
|
18
|
+
'productId',
|
|
19
|
+
'amount',
|
|
20
|
+
'currencyCode',
|
|
21
|
+
'countryCode',
|
|
22
|
+
'isTaxable',
|
|
23
|
+
'isNetPrice',
|
|
24
|
+
'maxQuantity',
|
|
25
|
+
],
|
|
26
|
+
bundleItemHeaders: ['productId', 'bundleItemProductId', 'quantity', 'configuration'],
|
|
27
|
+
variationItemHeaders: ['productId', 'variationId', 'key', 'type'],
|
|
28
|
+
variationTextFields: ['title', 'subtitle'],
|
|
29
|
+
variationOptionItemHeaders: ['variationId', 'value'],
|
|
30
|
+
variationOptionTextFields: ['title', 'subtitle'],
|
|
31
|
+
};
|
|
32
|
+
const buildProductHeaders = (locales) => [
|
|
33
|
+
...PRODUCT_CSV_SCHEMA.base,
|
|
34
|
+
...locales.flatMap((l) => PRODUCT_CSV_SCHEMA.textFields.map((f) => `texts.${l}.${f}`)),
|
|
35
|
+
'supply.weightInGram',
|
|
36
|
+
'supply.heightInMillimeters',
|
|
37
|
+
'supply.lengthInMillimeters',
|
|
38
|
+
'supply.widthInMillimeters',
|
|
39
|
+
'meta',
|
|
40
|
+
];
|
|
41
|
+
const buildPriceHeaders = () => PRODUCT_CSV_SCHEMA.priceFields;
|
|
42
|
+
const buildBundleHeaders = () => PRODUCT_CSV_SCHEMA.bundleItemHeaders;
|
|
43
|
+
const buildVariationHeaders = (locales) => [
|
|
44
|
+
...PRODUCT_CSV_SCHEMA.variationItemHeaders,
|
|
45
|
+
...locales.flatMap((l) => PRODUCT_CSV_SCHEMA.variationTextFields.map((f) => `texts.${l}.${f}`)),
|
|
46
|
+
];
|
|
47
|
+
const buildVariationOptionsHeaders = (locales) => [
|
|
48
|
+
...PRODUCT_CSV_SCHEMA.variationOptionItemHeaders,
|
|
49
|
+
...locales.flatMap((l) => PRODUCT_CSV_SCHEMA.variationOptionTextFields.map((f) => `texts.${l}.${f}`)),
|
|
50
|
+
];
|
|
51
|
+
const mapTextsToRow = (row, texts, locales, fields) => {
|
|
52
|
+
locales.forEach((locale) => {
|
|
53
|
+
const t = texts.find((x) => x.locale === locale) || {};
|
|
54
|
+
fields.forEach((f) => {
|
|
55
|
+
row[`texts.${locale}.${f}`] = Array.isArray(t[f]) ? t[f].join(';') : (t[f] ?? '');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
const buildProductRow = (product, locales) => {
|
|
60
|
+
const row = {};
|
|
61
|
+
PRODUCT_CSV_SCHEMA.base.forEach((k) => (row[k] = product[k] ?? ''));
|
|
62
|
+
row['supply.weightInGram'] = product?.dimensions?.weight ?? '';
|
|
63
|
+
row['supply.heightInMillimeters'] = product?.dimensions?.height ?? '';
|
|
64
|
+
row['supply.lengthInMillimeters'] = product?.dimensions?.length ?? '';
|
|
65
|
+
row['supply.widthInMillimeters'] = product?.dimensions?.width ?? '';
|
|
66
|
+
row['meta'] =
|
|
67
|
+
typeof product?.meta === 'object'
|
|
68
|
+
? JSON.stringify(product?.meta, Object.keys(product?.meta).sort())
|
|
69
|
+
: product?.meta;
|
|
70
|
+
mapTextsToRow(row, product.texts ?? [], locales, PRODUCT_CSV_SCHEMA.textFields);
|
|
71
|
+
return row;
|
|
72
|
+
};
|
|
73
|
+
const buildPriceRows = (productId, prices = []) => prices.map((p) => ({
|
|
74
|
+
productId,
|
|
75
|
+
amount: p.amount ?? '',
|
|
76
|
+
isNetPrice: p.isNetPrice ?? '',
|
|
77
|
+
isTaxable: p.isTaxable ?? '',
|
|
78
|
+
currencyCode: p.currency?.isoCode ?? '',
|
|
79
|
+
countryCode: p.country?.isoCode ?? '',
|
|
80
|
+
maxQuantity: p.maxQuantity ?? '',
|
|
81
|
+
}));
|
|
82
|
+
const buildBundleRows = (productId, bundles = []) => bundles.map((b) => ({
|
|
83
|
+
productId,
|
|
84
|
+
bundleItemProductId: b.productId,
|
|
85
|
+
quantity: b.quantity ?? 1,
|
|
86
|
+
configuration: (b.configuration || []).map((c) => Object.values(c).join(':')).join(';'),
|
|
87
|
+
}));
|
|
88
|
+
const buildVariationRows = (productId, variations = [], locales) => {
|
|
89
|
+
const variationRows = [];
|
|
90
|
+
const optionRows = [];
|
|
91
|
+
variations.forEach((v) => {
|
|
92
|
+
const row = {
|
|
93
|
+
productId,
|
|
94
|
+
variationId: v._id,
|
|
95
|
+
key: v.key,
|
|
96
|
+
type: v.type,
|
|
97
|
+
};
|
|
98
|
+
mapTextsToRow(row, Object.values(v.texts || {}), locales, PRODUCT_CSV_SCHEMA.variationTextFields);
|
|
99
|
+
variationRows.push(row);
|
|
100
|
+
const options = v.options?.[v._id] ?? [];
|
|
101
|
+
options.forEach((o) => {
|
|
102
|
+
const optRow = {
|
|
103
|
+
variationId: v._id,
|
|
104
|
+
value: o.productVariationOption,
|
|
105
|
+
};
|
|
106
|
+
mapTextsToRow(optRow, Object.values(o.texts || {}), locales, PRODUCT_CSV_SCHEMA.variationOptionTextFields);
|
|
107
|
+
optionRows.push(optRow);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
return { variationRows, optionRows };
|
|
111
|
+
};
|
|
112
|
+
const exportProductsHandler = async ({ exportBundleItems, exportPrices, exportProducts, exportVariationOptions, exportVariations, ...params }, locales, unchainedAPI) => {
|
|
113
|
+
const { queryString, includeDrafts, tags } = params;
|
|
114
|
+
const products = await unchainedAPI.modules.products.findProducts({
|
|
115
|
+
includeDrafts,
|
|
116
|
+
queryString,
|
|
117
|
+
tags,
|
|
118
|
+
});
|
|
119
|
+
const normalized = { products: {}, prices: {}, bundles: {}, variations: {} };
|
|
120
|
+
for await (const p of products) {
|
|
121
|
+
const productId = p._id;
|
|
122
|
+
const productTexts = exportProducts
|
|
123
|
+
? await unchainedAPI.modules.products.texts.findTexts({ productId })
|
|
124
|
+
: null;
|
|
125
|
+
const variations = exportVariations
|
|
126
|
+
? await unchainedAPI.modules.products.variations.findProductVariations({ productId })
|
|
127
|
+
: [];
|
|
128
|
+
const normalizedVariations = [];
|
|
129
|
+
if (exportVariations) {
|
|
130
|
+
for await (const v of variations) {
|
|
131
|
+
const variationTexts = exportVariations
|
|
132
|
+
? await unchainedAPI.modules.products.variations.texts.findVariationTexts({
|
|
133
|
+
productVariationId: v._id,
|
|
134
|
+
productVariationOptionValue: null,
|
|
135
|
+
})
|
|
136
|
+
: [];
|
|
137
|
+
const options = [];
|
|
138
|
+
if (exportVariationOptions) {
|
|
139
|
+
for await (const o of v.options || []) {
|
|
140
|
+
const optionTexts = await unchainedAPI.modules.products.variations.texts.findVariationTexts({
|
|
141
|
+
productVariationId: v._id,
|
|
142
|
+
productVariationOptionValue: o,
|
|
143
|
+
});
|
|
144
|
+
options.push({
|
|
145
|
+
_id: `${v._id}:${o}`,
|
|
146
|
+
productVariationOption: o,
|
|
147
|
+
texts: Object.fromEntries(optionTexts.map((t) => [t.locale, t])),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
normalizedVariations.push({
|
|
152
|
+
...v,
|
|
153
|
+
texts: exportVariations
|
|
154
|
+
? Object.fromEntries(variationTexts.map((t) => [t.locale, t]))
|
|
155
|
+
: {},
|
|
156
|
+
options: exportVariationOptions ? { [v._id]: options } : {},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
normalized.products[productId] = exportProducts ? { ...p, texts: productTexts } : p;
|
|
161
|
+
normalized.prices[productId] = exportPrices ? (p.commerce?.pricing ?? []) : [];
|
|
162
|
+
normalized.bundles[productId] = exportBundleItems ? (p.bundleItems ?? []) : [];
|
|
163
|
+
normalized.variations[productId] = normalizedVariations;
|
|
164
|
+
}
|
|
165
|
+
const productRows = [];
|
|
166
|
+
const priceRows = [];
|
|
167
|
+
const bundleRows = [];
|
|
168
|
+
const variationRows = [];
|
|
169
|
+
const variationOptionRows = [];
|
|
170
|
+
for (const pid in normalized.products) {
|
|
171
|
+
if (exportProducts) {
|
|
172
|
+
productRows.push(buildProductRow(normalized.products[pid], locales));
|
|
173
|
+
}
|
|
174
|
+
if (exportPrices) {
|
|
175
|
+
priceRows.push(...buildPriceRows(pid, normalized.prices[pid]));
|
|
176
|
+
}
|
|
177
|
+
if (exportBundleItems) {
|
|
178
|
+
bundleRows.push(...buildBundleRows(pid, normalized.bundles[pid]));
|
|
179
|
+
}
|
|
180
|
+
const { variationRows: vr, optionRows: or } = buildVariationRows(pid, normalized.variations[pid], locales);
|
|
181
|
+
variationRows.push(...vr);
|
|
182
|
+
variationOptionRows.push(...or);
|
|
183
|
+
}
|
|
184
|
+
const productsCSV = exportProducts
|
|
185
|
+
? await generateCSVFileAndURL({
|
|
186
|
+
headers: buildProductHeaders(locales),
|
|
187
|
+
rows: productRows,
|
|
188
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
189
|
+
fileName: 'products_export.csv',
|
|
190
|
+
unchainedAPI,
|
|
191
|
+
})
|
|
192
|
+
: null;
|
|
193
|
+
const pricesCSV = exportPrices
|
|
194
|
+
? await generateCSVFileAndURL({
|
|
195
|
+
headers: buildPriceHeaders(),
|
|
196
|
+
rows: priceRows,
|
|
197
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
198
|
+
fileName: 'products_prices_export.csv',
|
|
199
|
+
unchainedAPI,
|
|
200
|
+
})
|
|
201
|
+
: null;
|
|
202
|
+
const bundlesCSV = exportBundleItems
|
|
203
|
+
? await generateCSVFileAndURL({
|
|
204
|
+
headers: buildBundleHeaders(),
|
|
205
|
+
rows: bundleRows,
|
|
206
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
207
|
+
fileName: 'products_bundle_items_export.csv',
|
|
208
|
+
unchainedAPI,
|
|
209
|
+
})
|
|
210
|
+
: null;
|
|
211
|
+
const variationsCSV = exportVariations
|
|
212
|
+
? await generateCSVFileAndURL({
|
|
213
|
+
headers: buildVariationHeaders(locales),
|
|
214
|
+
rows: variationRows,
|
|
215
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
216
|
+
fileName: 'products_variations_export.csv',
|
|
217
|
+
unchainedAPI,
|
|
218
|
+
})
|
|
219
|
+
: null;
|
|
220
|
+
const variationOptionsCSV = exportVariationOptions
|
|
221
|
+
? await generateCSVFileAndURL({
|
|
222
|
+
headers: buildVariationOptionsHeaders(locales),
|
|
223
|
+
rows: variationOptionRows,
|
|
224
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
225
|
+
fileName: 'products_variation_options_export.csv',
|
|
226
|
+
unchainedAPI,
|
|
227
|
+
})
|
|
228
|
+
: null;
|
|
229
|
+
return {
|
|
230
|
+
products: productsCSV,
|
|
231
|
+
prices: pricesCSV,
|
|
232
|
+
bundles: bundlesCSV,
|
|
233
|
+
variations: variationsCSV,
|
|
234
|
+
variationOptions: variationOptionsCSV,
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
export default exportProductsHandler;
|
|
238
|
+
exportProductsHandler.payloadSchema = ProductExportPayloadSchema;
|