@haus-tech/product-import-export-plugin 3.0.3 → 3.0.5

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 (52) hide show
  1. package/package.json +2 -2
  2. package/dist/api/plugin.controller.d.ts +0 -8
  3. package/dist/api/plugin.controller.js +0 -49
  4. package/dist/api/product-export.controller.d.ts +0 -14
  5. package/dist/api/product-export.controller.js +0 -117
  6. package/dist/api/product-import.controller.d.ts +0 -11
  7. package/dist/api/product-import.controller.js +0 -63
  8. package/dist/constants.d.ts +0 -2
  9. package/dist/constants.js +0 -5
  10. package/dist/helpers/generate-asset-hash.d.ts +0 -2
  11. package/dist/helpers/generate-asset-hash.js +0 -15
  12. package/dist/helpers/normalize-string.d.ts +0 -1
  13. package/dist/helpers/normalize-string.js +0 -16
  14. package/dist/helpers/remove-update-at-fields.d.ts +0 -5
  15. package/dist/helpers/remove-update-at-fields.js +0 -19
  16. package/dist/index.d.ts +0 -1
  17. package/dist/index.js +0 -17
  18. package/dist/product-import-export.plugin.d.ts +0 -13
  19. package/dist/product-import-export.plugin.js +0 -153
  20. package/dist/providers/import-providers/asset-importer.d.ts +0 -27
  21. package/dist/providers/import-providers/asset-importer.js +0 -138
  22. package/dist/providers/import-providers/import-parser.d.ts +0 -137
  23. package/dist/providers/import-providers/import-parser.js +0 -600
  24. package/dist/providers/import-providers/product-importer.d.ts +0 -48
  25. package/dist/providers/import-providers/product-importer.js +0 -753
  26. package/dist/services/extended-fast-importer.service.d.ts +0 -44
  27. package/dist/services/extended-fast-importer.service.js +0 -462
  28. package/dist/services/product-export.service.d.ts +0 -23
  29. package/dist/services/product-export.service.js +0 -344
  30. package/dist/services/product-import.service.d.ts +0 -22
  31. package/dist/services/product-import.service.js +0 -84
  32. package/dist/types.d.ts +0 -41
  33. package/dist/types.js +0 -2
  34. package/dist/ui/components/export-dialog.component.d.ts +0 -43
  35. package/dist/ui/components/export-dialog.component.html +0 -78
  36. package/dist/ui/components/export-dialog.component.js +0 -108
  37. package/dist/ui/components/export-dialog.component.ts +0 -142
  38. package/dist/ui/components/product-import.component.d.ts +0 -57
  39. package/dist/ui/components/product-import.component.html +0 -123
  40. package/dist/ui/components/product-import.component.js +0 -413
  41. package/dist/ui/components/product-import.component.ts +0 -550
  42. package/dist/ui/product-export.service.d.ts +0 -14
  43. package/dist/ui/product-export.service.js +0 -107
  44. package/dist/ui/product-export.service.ts +0 -107
  45. package/dist/ui/providers.d.ts +0 -2
  46. package/dist/ui/providers.js +0 -49
  47. package/dist/ui/providers.ts +0 -57
  48. package/dist/ui/routes.d.ts +0 -31
  49. package/dist/ui/routes.js +0 -12
  50. package/dist/ui/routes.ts +0 -11
  51. package/dist/ui/translations/en.json +0 -52
  52. package/dist/ui/translations/sv.json +0 -52
@@ -1,753 +0,0 @@
1
- "use strict";
2
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
- return c > 3 && r && Object.defineProperty(target, key, r), r;
7
- };
8
- var __metadata = (this && this.__metadata) || function (k, v) {
9
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.ProductImporter = void 0;
13
- const common_1 = require("@nestjs/common");
14
- const core_1 = require("@vendure/core");
15
- const rxjs_1 = require("rxjs");
16
- const extended_fast_importer_service_1 = require("../../services/extended-fast-importer.service");
17
- const lodash_1 = require("lodash");
18
- const normalize_string_1 = require("../../helpers/normalize-string");
19
- const import_parser_1 = require("./import-parser");
20
- const asset_importer_1 = require("./asset-importer");
21
- const typeorm_1 = require("typeorm");
22
- const generate_asset_hash_1 = require("../../helpers/generate-asset-hash");
23
- const crypto_1 = require("crypto");
24
- const remove_update_at_fields_1 = require("../../helpers/remove-update-at-fields");
25
- let ProductImporter = class ProductImporter {
26
- connection;
27
- configService;
28
- importParser;
29
- assetImporter;
30
- fastImporter;
31
- translatableSaver;
32
- productService;
33
- channelService;
34
- facetService;
35
- facetValueService;
36
- taxCategoryService;
37
- assetService;
38
- optionGroupService;
39
- taxCategoryMatches = {};
40
- facetMap = new Map();
41
- facetValueMap = new Map();
42
- customFieldTypes = {};
43
- allAssets = [];
44
- constructor(connection, configService, importParser, assetImporter, fastImporter, translatableSaver, productService, channelService, facetService, facetValueService, taxCategoryService, assetService, optionGroupService) {
45
- this.connection = connection;
46
- this.configService = configService;
47
- this.importParser = importParser;
48
- this.assetImporter = assetImporter;
49
- this.fastImporter = fastImporter;
50
- this.translatableSaver = translatableSaver;
51
- this.productService = productService;
52
- this.channelService = channelService;
53
- this.facetService = facetService;
54
- this.facetValueService = facetValueService;
55
- this.taxCategoryService = taxCategoryService;
56
- this.assetService = assetService;
57
- this.optionGroupService = optionGroupService;
58
- }
59
- parseAndImport(input, ctx, updateProductSlug = true, mainLanguage, updatingStrategy) {
60
- return new rxjs_1.Observable((subscriber) => {
61
- const p = this.doParseAndImport(input, updateProductSlug, mainLanguage, updatingStrategy, ctx, (progress) => {
62
- subscriber.next(progress);
63
- }).then((value) => {
64
- subscriber.next({ ...value, currentProduct: 'Complete' });
65
- subscriber.complete();
66
- });
67
- });
68
- }
69
- extractProductIdsFromCSV(csvString) {
70
- // Split the CSV string into lines
71
- const lines = csvString.split('\n');
72
- // Initialize an array to hold the product IDs
73
- const productIds = [];
74
- // Loop through each line in the CSV
75
- lines.forEach((line, index) => {
76
- if (index === 0)
77
- return; // Skip header row
78
- const columns = line.split(',');
79
- const productId = columns[0].trim();
80
- if (productId) {
81
- productIds.push(parseInt(productId, 10));
82
- }
83
- else {
84
- productIds.push(null); // Push null or undefined if productId is missing
85
- }
86
- });
87
- return productIds;
88
- }
89
- preprocessCsv(input, updateProductSlug) {
90
- const customFieldTypes = {};
91
- const lines = input.split('\n');
92
- // Filter out empty lines (lines that are either blank or just commas)
93
- const filteredLines = lines.filter((line) => {
94
- const trimmed = line.trim();
95
- return trimmed !== '' && !/^,+$/.test(trimmed); // Keep lines that are not empty or comma-only
96
- });
97
- const splitLines = (text) => {
98
- // Split text by commas, but not if comma is inside double quotes
99
- const splittedText = text.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
100
- return splittedText;
101
- };
102
- const headers = splitLines(filteredLines[0]); // Use splitCsvLine to split headers correctly
103
- const cleanedHeaders = headers.map((header) => {
104
- if (header.startsWith('product:') || header.startsWith('variant:')) {
105
- const parts = header.split(':');
106
- if (parts.length === 3) {
107
- const [prefix, fieldName, type] = parts;
108
- const fullFieldName = `${prefix}:${fieldName}`;
109
- customFieldTypes[fullFieldName] = type.trim();
110
- return fullFieldName;
111
- }
112
- }
113
- return header;
114
- });
115
- // Recreate the CSV with cleaned headers
116
- filteredLines[0] = cleanedHeaders.join(',');
117
- // If the updateProductSlug flag is set, find all slug columns and set their values to an empty string.
118
- if (updateProductSlug) {
119
- const slugIndices = cleanedHeaders
120
- .map((header, index) => (header.includes('slug') ? index : -1))
121
- .filter((index) => index !== -1); // Get all indices of columns that contain 'slug'
122
- if (slugIndices.length > 0) {
123
- filteredLines.forEach((line, index) => {
124
- if (index === 0)
125
- return; // Skip header row
126
- const columns = splitLines(line); // Use splitCsvLine to split the data line correctly
127
- slugIndices.forEach((slugIndex) => {
128
- columns[slugIndex] = ''; // Clear each slug column
129
- });
130
- filteredLines[index] = columns.join(','); // Reassemble the line
131
- });
132
- }
133
- }
134
- return {
135
- cleanedCsv: filteredLines.join('\n'),
136
- customFieldTypes,
137
- };
138
- }
139
- async streamToString(stream) {
140
- const chunks = [];
141
- return new Promise((resolve, reject) => {
142
- stream.on('data', (chunk) => chunks.push(chunk));
143
- stream.on('error', reject);
144
- stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
145
- });
146
- }
147
- async getAllAssets(ctx) {
148
- let offset = 0;
149
- const limit = 100;
150
- const assets = [];
151
- let totalItems = 0;
152
- do {
153
- const paginatedAssets = await this.assetService.findAll(ctx, { take: limit, skip: offset });
154
- assets.push(...paginatedAssets.items);
155
- totalItems = paginatedAssets.totalItems;
156
- offset += limit;
157
- } while (assets.length < totalItems);
158
- return assets;
159
- }
160
- async setMissingAssetHashes(ctx) {
161
- const assets = await this.getAllAssets(ctx);
162
- const assetsWithoutHash = assets.filter((asset) => !asset.customFields.hash);
163
- for (const asset of assetsWithoutHash) {
164
- const hash = (0, generate_asset_hash_1.generateAssetHash)(asset);
165
- if (asset.customFields.hash !== hash) {
166
- asset.customFields.hash = hash;
167
- await this.connection.getRepository(ctx, core_1.Asset).save(asset);
168
- }
169
- }
170
- this.allAssets = assets;
171
- }
172
- async doParseAndImport(input, updateProductSlug, mainLanguage, updatingStrategy, reqCtx, onProgress) {
173
- const ctx = await this.getRequestContext(reqCtx, mainLanguage);
174
- await this.setMissingAssetHashes(ctx);
175
- let csvContent;
176
- if (typeof input === 'string') {
177
- csvContent = input;
178
- }
179
- else {
180
- // If input is a Stream, you need to read it fully into memory
181
- csvContent = await this.streamToString(input);
182
- }
183
- // Preprocess the CSV to extract types and clean headers
184
- const { cleanedCsv, customFieldTypes } = this.preprocessCsv(csvContent, updateProductSlug);
185
- this.customFieldTypes = customFieldTypes;
186
- const parsed = (await this.importParser.parseProducts(cleanedCsv, ctx.languageCode));
187
- const productIds = this.extractProductIdsFromCSV(cleanedCsv);
188
- parsed.results.forEach((result, index) => {
189
- const productId = productIds[index];
190
- if (productId !== null) {
191
- result.product.productId = productId;
192
- }
193
- // Handle the case where productId is null if necessary
194
- });
195
- if (parsed && parsed.results.length) {
196
- try {
197
- const importErrors = await this.importProducts(ctx, parsed.results, updatingStrategy, (progess) => {
198
- onProgress({
199
- ...progess,
200
- processed: parsed.processed,
201
- });
202
- });
203
- return {
204
- errors: parsed.errors.concat(importErrors),
205
- imported: parsed.results.length,
206
- processed: parsed.processed,
207
- };
208
- }
209
- catch (err) {
210
- core_1.Logger.error('Error while importing products:', 'ProductImporter', err?.stack);
211
- return {
212
- errors: [err.message],
213
- imported: 0,
214
- processed: parsed.processed,
215
- };
216
- }
217
- }
218
- else {
219
- return {
220
- errors: parsed.errors,
221
- imported: 0,
222
- processed: parsed.processed,
223
- };
224
- }
225
- }
226
- async getRequestContext(ctx, mainLanguage) {
227
- if (ctx instanceof core_1.RequestContext) {
228
- return new core_1.RequestContext({
229
- apiType: 'admin',
230
- isAuthorized: true,
231
- authorizedAsOwnerOnly: false,
232
- channel: ctx.channel,
233
- languageCode: mainLanguage,
234
- });
235
- }
236
- else {
237
- throw new core_1.InternalServerError('No RequestContext provided');
238
- }
239
- }
240
- async importProducts(ctx, rows, updatingStrategy, onProgress) {
241
- let errors = [];
242
- let imported = 0;
243
- const languageCode = ctx.languageCode;
244
- const taxCategories = await this.taxCategoryService.findAll(ctx);
245
- // Create taxCategory if none exists
246
- if (taxCategories.totalItems === 0) {
247
- await this.taxCategoryService.create(ctx, {
248
- name: 'Standard Tax',
249
- isDefault: true,
250
- });
251
- }
252
- await this.fastImporter.initialize(ctx.channel);
253
- for (const { product, variants } of rows) {
254
- const productMainTranslation = this.getTranslationByCodeOrFirst(product.translations, ctx.languageCode);
255
- const assetsToImport = (0, lodash_1.compact)(product.assetPaths.map((assetPath, idx) => {
256
- const assetID = product.assetsJson?.[idx]?.id;
257
- if (assetPath.startsWith('http://') || assetPath.startsWith('https://')) {
258
- return {
259
- url: assetPath,
260
- name: product.assetsJson?.[idx]?.name || undefined,
261
- id: assetID && this.allAssets.find((a) => a.id === assetID)
262
- ? product.assetsJson?.[idx]?.id
263
- : undefined,
264
- };
265
- }
266
- }));
267
- const createProductAssets = await this.assetImporter.getAssets(assetsToImport, ctx);
268
- const productAssets = createProductAssets.assets;
269
- if (createProductAssets.errors.length) {
270
- errors = errors.concat(createProductAssets.errors);
271
- }
272
- const customFields = await this.processCustomFieldValues(product.translations[0].customFields, this.configService.customFields.Product, ctx);
273
- let checkExistingProduct;
274
- if (product.productId) {
275
- checkExistingProduct = await this.productService.findOne(ctx, product.productId);
276
- }
277
- if (!checkExistingProduct) {
278
- const variantSkus = variants.map((v) => v.sku);
279
- const foundVariants = await this.connection.getRepository(ctx, core_1.ProductVariant).findBy({
280
- sku: (0, typeorm_1.In)(variantSkus),
281
- });
282
- if (foundVariants.length) {
283
- checkExistingProduct = { id: foundVariants[0].productId };
284
- }
285
- }
286
- const existingProduct = await this.getProductByIdWithRelations(ctx, checkExistingProduct?.id);
287
- try {
288
- if (existingProduct?.deletedAt) {
289
- existingProduct.deletedAt = null;
290
- try {
291
- await this.connection.getRepository(ctx, core_1.Product).save([existingProduct]);
292
- }
293
- catch (error) {
294
- errors.push(`Error while restoring the product: ${error}`);
295
- core_1.Logger.error('Error while restoring the product:', 'ProductImporter', error?.stack);
296
- }
297
- }
298
- }
299
- catch (error) {
300
- errors.push(`Error while restoring the product: ${error}`);
301
- core_1.Logger.error('Error while restoring the product:', 'ProductImporter', error?.stack);
302
- }
303
- const existingProductName = existingProduct?.translations.find((t) => t.languageCode === languageCode)?.name;
304
- const productNameHasChanged = existingProduct && existingProductName !== productMainTranslation.name
305
- ? {
306
- name: productMainTranslation.name,
307
- previousName: existingProductName,
308
- normalizedName: (0, normalize_string_1.normalizeString)(productMainTranslation.name, '-', languageCode),
309
- previousNormalizedName: (0, normalize_string_1.normalizeString)(existingProductName),
310
- }
311
- : undefined;
312
- if (existingProduct && updatingStrategy === 'replace') {
313
- await this.fastImporter.removeAllAssetsFromProduct(existingProduct);
314
- }
315
- const productData = {
316
- featuredAssetId: existingProduct?.featuredAsset
317
- ? undefined
318
- : productAssets.length
319
- ? productAssets[0].id
320
- : undefined,
321
- assetIds: productAssets.map((a) => a.id),
322
- facetValueIds: updatingStrategy === 'replace' ? [] : undefined,
323
- translations: await Promise.all(product.translations.map(async (translation) => {
324
- return {
325
- languageCode: translation.languageCode,
326
- name: translation.name,
327
- description: translation.description,
328
- slug: translation.slug,
329
- customFields: await this.processCustomFieldValues(translation.customFields, this.configService.customFields.Product, ctx),
330
- };
331
- })),
332
- customFields,
333
- };
334
- const createdProductId = existingProduct
335
- ? await this.fastImporter.updateProduct(existingProduct.id, {
336
- ...productData,
337
- id: existingProduct.id,
338
- })
339
- : await this.fastImporter.createProduct(productData);
340
- for (const facet of product.facets) {
341
- const facetMainTranslation = this.getTranslationByCodeOrFirst(facet.translations, ctx.languageCode);
342
- const code = (0, normalize_string_1.normalizeString)(facetMainTranslation.facet, '-', facetMainTranslation.languageCode);
343
- const valueCode = (0, normalize_string_1.normalizeString)(facetMainTranslation.value, '-', facetMainTranslation.languageCode);
344
- const facetValueId = await this.createFacetAndValue(facet, code, valueCode);
345
- await this.fastImporter.addFacetValueToProduct(createdProductId, facetValueId);
346
- }
347
- const optionsMap = {};
348
- for (const [optionGroup, optionGroupIndex] of product.optionGroups.map((group, i) => [group, i])) {
349
- const optionGroupMainTranslation = this.getTranslationByCodeOrFirst(optionGroup.translations, ctx.languageCode);
350
- const code = (0, normalize_string_1.normalizeString)(`${productMainTranslation.name}-${optionGroupMainTranslation.name}`, '-', optionGroupMainTranslation.languageCode);
351
- const productOptionsGroups = await this.optionGroupService.getOptionGroupsByProductId(ctx, createdProductId);
352
- const previousCode = (0, normalize_string_1.normalizeString)(`${productNameHasChanged?.previousName}-${optionGroupMainTranslation.name}`, '-', optionGroupMainTranslation.languageCode);
353
- const foundOptionGroup = productOptionsGroups.find((group) => group.code === previousCode);
354
- let groupId = foundOptionGroup
355
- ? await this.fastImporter.updateProductOptionGroup({
356
- id: foundOptionGroup.id,
357
- code,
358
- options: optionGroupMainTranslation.values.map((name) => ({})),
359
- translations: optionGroup.translations
360
- .map((translation) => {
361
- return {
362
- languageCode: translation.languageCode,
363
- name: translation.name,
364
- };
365
- })
366
- .filter((t) => t.name),
367
- })
368
- : await this.fastImporter.createProductOptionGroup({
369
- code,
370
- options: optionGroupMainTranslation.values.map((name) => ({})),
371
- translations: optionGroup.translations
372
- .map((translation) => {
373
- return {
374
- languageCode: translation.languageCode,
375
- name: translation.name,
376
- };
377
- })
378
- .filter((t) => t.name),
379
- });
380
- for (const [optionIndex, value] of optionGroupMainTranslation.values.map((val, index) => [index, val])) {
381
- const createdOptionId = await this.fastImporter.createProductOption({
382
- productOptionGroupId: groupId,
383
- code: (0, normalize_string_1.normalizeString)(value, '-', optionGroupMainTranslation.languageCode),
384
- translations: optionGroup.translations
385
- .map((translation) => {
386
- return {
387
- languageCode: translation.languageCode,
388
- name: translation.values[optionIndex],
389
- };
390
- })
391
- .filter((t) => t.name),
392
- });
393
- optionsMap[`${optionGroupIndex}_${value}`] = createdOptionId;
394
- }
395
- await this.fastImporter.addOptionGroupToProduct(createdProductId, groupId);
396
- }
397
- for (const variant of variants) {
398
- const variantMainTranslation = this.getTranslationByCodeOrFirst(variant.translations, ctx.languageCode);
399
- const assetsToImport = (0, lodash_1.compact)(variant.assetPaths.map((assetPath, idx) => {
400
- const assetID = variant.assetsJson?.[idx]?.id;
401
- if (assetPath.startsWith('http://') || assetPath.startsWith('https://')) {
402
- return {
403
- url: assetPath,
404
- name: variant.assetsJson?.[idx]?.name || undefined,
405
- id: assetID && this.allAssets.find((a) => a.id === assetID)
406
- ? variant.assetsJson?.[idx]?.id
407
- : undefined,
408
- };
409
- }
410
- }));
411
- const createVariantAssets = await this.assetImporter.getAssets(assetsToImport);
412
- const variantAssets = createVariantAssets.assets;
413
- if (createVariantAssets.errors.length) {
414
- errors = errors.concat(createVariantAssets.errors);
415
- }
416
- let facetValueIds = [];
417
- if (0 < variant.facets.length) {
418
- facetValueIds = await this.getFacetValueIds(ctx, variant.facets, languageCode);
419
- }
420
- const variantCustomFields = await this.processCustomFieldValues(variantMainTranslation.customFields, this.configService.customFields.ProductVariant, ctx);
421
- const optionIds = variantMainTranslation.optionValues.map((v, index) => optionsMap[`${index}_${v}`]);
422
- const existingVariant = await this.connection.getRepository(ctx, core_1.ProductVariant).findOne({
423
- where: {
424
- sku: variant.sku,
425
- },
426
- withDeleted: true,
427
- relations: ['facetValues', 'facetValues.facet', 'assets', 'featuredAsset'],
428
- });
429
- if (existingVariant?.deletedAt) {
430
- existingVariant.deletedAt = null;
431
- try {
432
- await this.connection.getRepository(ctx, core_1.ProductVariant).save([existingVariant]);
433
- }
434
- catch (error) {
435
- errors.push(`Error while restoring the variant: ${error}`);
436
- core_1.Logger.error('Error while restoring the variant:', 'ProductImporter', error?.stack);
437
- }
438
- }
439
- if (existingVariant && updatingStrategy === 'replace') {
440
- // Remove all assets from the product
441
- await this.fastImporter.removeAllAssetsFromVariant(existingVariant);
442
- }
443
- const variantData = {
444
- productId: createdProductId,
445
- facetValueIds: updatingStrategy === 'replace' ? [] : undefined,
446
- featuredAssetId: existingVariant?.featuredAsset
447
- ? undefined
448
- : variantAssets.length
449
- ? variantAssets[0].id
450
- : undefined,
451
- assetIds: variantAssets.map((a) => a.id),
452
- sku: variant.sku,
453
- taxCategoryId: this.getMatchingTaxCategoryId(variant.taxCategory, taxCategories.items),
454
- stockOnHand: variant.stockOnHand,
455
- trackInventory: variant.trackInventory,
456
- optionIds,
457
- translations: await Promise.all(variant.translations.map(async (translation) => {
458
- const productTranslation = product.translations.find((t) => t.languageCode === translation.languageCode);
459
- if (!productTranslation) {
460
- throw new core_1.InternalServerError(`No translation '${translation.languageCode}' for product with slug '${productMainTranslation.slug}'`);
461
- }
462
- return {
463
- languageCode: translation.languageCode,
464
- name: [productTranslation.name, ...translation.optionValues].join(' '),
465
- customFields: await this.processCustomFieldValues(translation.customFields, this.configService.customFields.ProductVariant, ctx),
466
- };
467
- })),
468
- price: Math.round(variant.price * 100),
469
- customFields: variantCustomFields,
470
- };
471
- const createdVariantId = existingVariant
472
- ? await this.fastImporter.updateProductVariant({ ...variantData, id: existingVariant.id })
473
- : await this.fastImporter.createProductVariant(variantData);
474
- for (const facet of variant.facets) {
475
- const facetMainTranslation = this.getTranslationByCodeOrFirst(facet.translations, ctx.languageCode);
476
- const code = (0, normalize_string_1.normalizeString)(facetMainTranslation.facet, '-', facetMainTranslation.languageCode);
477
- const valueCode = (0, normalize_string_1.normalizeString)(facetMainTranslation.value, '-', facetMainTranslation.languageCode);
478
- const facetValueId = await this.createFacetAndValue(facet, code, valueCode);
479
- await this.fastImporter.addFacetValueToProductVariant(createdVariantId, facetValueId);
480
- }
481
- }
482
- if (existingProduct) {
483
- const productAfterUpdate = (await this.getProductByIdWithRelations(ctx, createdProductId)); // We know that the product exists, so we can safely cast it to Product
484
- const existingProductHash = (0, crypto_1.createHash)('md5')
485
- .update(JSON.stringify((0, remove_update_at_fields_1.removeUpdatedAtFields)(existingProduct)))
486
- .digest('hex');
487
- const productAfterUpdateHash = (0, crypto_1.createHash)('md5')
488
- .update(JSON.stringify((0, remove_update_at_fields_1.removeUpdatedAtFields)(productAfterUpdate)))
489
- .digest('hex');
490
- // Update product updatedAt to existing product updatedAt
491
- if (existingProductHash === productAfterUpdateHash) {
492
- await this.connection.getRepository(ctx, core_1.Product).update(productAfterUpdate.id, {
493
- updatedAt: existingProduct.updatedAt,
494
- });
495
- }
496
- }
497
- imported++;
498
- onProgress({
499
- processed: 0,
500
- imported,
501
- errors,
502
- currentProduct: productMainTranslation.name,
503
- });
504
- }
505
- return errors;
506
- }
507
- async getFacetValueIds(ctx, facets, languageCode) {
508
- const facetValueIds = [];
509
- for (const item of facets) {
510
- const itemMainTranslation = this.getTranslationByCodeOrFirst(item.translations, languageCode);
511
- const facetName = itemMainTranslation.facet;
512
- const valueName = itemMainTranslation.value;
513
- let facetEntity;
514
- const cachedFacet = this.facetMap.get(facetName);
515
- if (cachedFacet) {
516
- facetEntity = cachedFacet;
517
- }
518
- else {
519
- const existing = await this.facetService.findByCode(ctx, (0, normalize_string_1.normalizeString)(facetName, '-', languageCode), languageCode);
520
- if (existing) {
521
- facetEntity = existing;
522
- }
523
- else {
524
- facetEntity = await this.facetService.create(ctx, {
525
- isPrivate: false,
526
- code: (0, normalize_string_1.normalizeString)(facetName, '-', languageCode),
527
- translations: item.translations.map((translation) => {
528
- return {
529
- languageCode: translation.languageCode,
530
- name: translation.facet,
531
- };
532
- }),
533
- });
534
- }
535
- this.facetMap.set(facetName, facetEntity);
536
- }
537
- let facetValueEntity;
538
- const facetValueMapKey = `${facetName}:${valueName}`;
539
- const cachedFacetValue = this.facetValueMap.get(facetValueMapKey);
540
- if (cachedFacetValue) {
541
- facetValueEntity = cachedFacetValue;
542
- }
543
- else {
544
- const existing = facetEntity.values.find((v) => (0, normalize_string_1.normalizeString)(v.name, '-') === (0, normalize_string_1.normalizeString)(valueName, '-', languageCode));
545
- if (existing) {
546
- facetValueEntity = existing;
547
- }
548
- else {
549
- facetValueEntity = await this.facetValueService.create(ctx, facetEntity, {
550
- code: (0, normalize_string_1.normalizeString)(valueName, '-', languageCode),
551
- translations: item.translations.map((translation) => {
552
- return {
553
- languageCode: translation.languageCode,
554
- name: translation.value,
555
- };
556
- }),
557
- });
558
- }
559
- this.facetValueMap.set(facetValueMapKey, facetValueEntity);
560
- }
561
- facetValueIds.push(facetValueEntity.id);
562
- }
563
- return facetValueIds;
564
- }
565
- async processCustomFieldValues(customFields, config, ctx) {
566
- function parseBoolean(input) {
567
- if (input == null) {
568
- return false;
569
- }
570
- switch (input.toLowerCase()) {
571
- case 'true':
572
- case '1':
573
- case 'yes':
574
- return true;
575
- default:
576
- return false;
577
- }
578
- }
579
- const processed = {};
580
- for (const fieldDef of config) {
581
- const foundCustomFieldType = (0, lodash_1.find)(this.customFieldTypes, (_type, key) => key.split(':')[1] === fieldDef.name);
582
- const value = customFields[fieldDef.name];
583
- if (fieldDef.list === true) {
584
- processed[fieldDef.name] = value?.split('|').filter((val) => val.trim() !== '');
585
- }
586
- else if (fieldDef.type === 'boolean') {
587
- processed[fieldDef.name] = parseBoolean(value);
588
- }
589
- else if (fieldDef.type === 'relation') {
590
- if (foundCustomFieldType === 'asset') {
591
- // Asset if of type json
592
- if ((0, lodash_1.startsWith)(value, '{') || (0, lodash_1.startsWith)(value, '[')) {
593
- if (typeof value === 'string') {
594
- const assetValue = JSON.parse(value.replace(/'/g, '"').replace(/\s+/g, ' ').replace(/,\s*'/g, ", '"));
595
- if (Array.isArray(assetValue)) {
596
- assetValue.forEach(async (asset) => {
597
- if (asset.url.startsWith('http://') || asset.url.startsWith('https://')) {
598
- const createdAssets = await this.assetImporter.getAssets([], ctx);
599
- processed[fieldDef.name] = createdAssets.assets[0]?.id;
600
- }
601
- const foundAsset = this.allAssets.find((a) => a.id === asset.id);
602
- if (foundAsset && foundAsset.name !== asset.name) {
603
- this.connection.getRepository(ctx, core_1.Asset).update(foundAsset.id, {
604
- name: asset.name,
605
- });
606
- }
607
- });
608
- }
609
- else {
610
- if (assetValue.url.startsWith('http://') || assetValue.url.startsWith('https://')) {
611
- const createdAssets = await this.assetImporter.getAssets([assetValue], ctx);
612
- processed[fieldDef.name] = createdAssets.assets[0]?.id;
613
- }
614
- const foundAsset = this.allAssets.find((a) => a.id === assetValue.id);
615
- if (foundAsset && foundAsset.name !== assetValue.name) {
616
- this.connection.getRepository(ctx, core_1.Asset).update(foundAsset.id, {
617
- name: assetValue.name,
618
- });
619
- }
620
- }
621
- }
622
- // Asset if of type string
623
- }
624
- else {
625
- if (value.startsWith('http://') || value.startsWith('https://')) {
626
- // If value is an external URL, we need to import the asset
627
- const createdAssets = await this.assetImporter.getAssets([{ url: value }], ctx);
628
- processed[fieldDef.name] = createdAssets.assets[0]?.id;
629
- }
630
- }
631
- }
632
- else {
633
- processed[fieldDef.name] = value ? value : undefined;
634
- }
635
- }
636
- else {
637
- if ((0, lodash_1.startsWith)(value, '{') || (0, lodash_1.startsWith)(value, '[')) {
638
- if (typeof value === 'string') {
639
- processed[fieldDef.name] = value.replace(/'/g, '"').replace(/\s+/g, ' ');
640
- }
641
- else {
642
- processed[fieldDef.name] = JSON.stringify(value)
643
- .replace(/'/g, '"')
644
- .replace(/\s+/g, ' ')
645
- .replace(/,\s*'/g, ", '");
646
- }
647
- }
648
- else {
649
- // Default to string
650
- processed[fieldDef.name] = value ? value : undefined;
651
- }
652
- }
653
- }
654
- return processed;
655
- }
656
- /**
657
- * Attempts to match a TaxCategory entity against the name supplied in the import table. If no matches
658
- * are found, the first TaxCategory id is returned.
659
- */
660
- getMatchingTaxCategoryId(name, taxCategories) {
661
- if (this.taxCategoryMatches[name]) {
662
- return this.taxCategoryMatches[name];
663
- }
664
- const regex = new RegExp(name, 'i');
665
- const found = taxCategories.find((tc) => !!tc.name.match(regex));
666
- const match = found ? found : taxCategories[0];
667
- if (!match) {
668
- throw new core_1.InternalServerError(`No TaxCategory found for name '${name}'`);
669
- }
670
- this.taxCategoryMatches[name] = match.id;
671
- return match.id;
672
- }
673
- getTranslationByCodeOrFirst(translations, languageCode) {
674
- let translation = translations.find((t) => t.languageCode === languageCode);
675
- if (!translation) {
676
- translation = translations[0];
677
- }
678
- return translation;
679
- }
680
- async createFacetAndValue(facet, facetCode, valueCode) {
681
- const facetId = await this.fastImporter.createFacet({
682
- code: facetCode,
683
- isPrivate: false,
684
- translations: facet.translations.map((translation) => {
685
- return {
686
- languageCode: translation.languageCode,
687
- name: translation.facet,
688
- value: translation.value,
689
- };
690
- }),
691
- });
692
- const facetValueId = await this.fastImporter.createFacetValue({
693
- facetId,
694
- code: valueCode,
695
- translations: facet.translations.map((translation) => {
696
- return {
697
- languageCode: translation.languageCode,
698
- name: translation.value,
699
- };
700
- }),
701
- });
702
- return facetValueId;
703
- }
704
- async getProductByIdWithRelations(ctx, id) {
705
- if (!id) {
706
- return undefined;
707
- }
708
- const product = (await this.connection.getRepository(ctx, core_1.Product).findOne({
709
- where: { id },
710
- withDeleted: true,
711
- })) ?? undefined;
712
- if (product) {
713
- const [facetValues, optionGroups, variants, assets, featuredAsset] = await Promise.all([
714
- this.connection
715
- .getRepository(ctx, core_1.FacetValue)
716
- .find({ where: { products: { id: product.id } }, relations: ['facet'] }),
717
- this.connection
718
- .getRepository(ctx, core_1.ProductOptionGroup)
719
- .find({ where: { product: { id: product.id } }, relations: ['options'] }),
720
- this.connection
721
- .getRepository(ctx, core_1.ProductVariant)
722
- .find({ where: { product: { id: product.id } }, relations: ['facetValues'] }),
723
- this.connection.getRepository(ctx, core_1.ProductAsset).find({ where: { productId: product.id } }),
724
- this.connection
725
- .getRepository(ctx, core_1.Asset)
726
- .findOne({ where: { id: product.featuredAssetId } }),
727
- ]);
728
- product.facetValues = facetValues;
729
- product.optionGroups = optionGroups;
730
- product.variants = variants;
731
- product.assets = assets;
732
- featuredAsset && (product.featuredAsset = featuredAsset);
733
- }
734
- return product;
735
- }
736
- };
737
- ProductImporter = __decorate([
738
- (0, common_1.Injectable)(),
739
- __metadata("design:paramtypes", [core_1.TransactionalConnection,
740
- core_1.ConfigService,
741
- import_parser_1.ImportParser,
742
- asset_importer_1.ExtendedAssetImporter,
743
- extended_fast_importer_service_1.ExtendedFastImporterService,
744
- core_1.TranslatableSaver,
745
- core_1.ProductService,
746
- core_1.ChannelService,
747
- core_1.FacetService,
748
- core_1.FacetValueService,
749
- core_1.TaxCategoryService,
750
- core_1.AssetService,
751
- core_1.ProductOptionGroupService])
752
- ], ProductImporter);
753
- exports.ProductImporter = ProductImporter;