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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/api/plugin.controller.d.ts +8 -0
  2. package/dist/api/plugin.controller.js +49 -0
  3. package/dist/api/product-export.controller.d.ts +14 -0
  4. package/dist/api/product-export.controller.js +117 -0
  5. package/dist/api/product-import.controller.d.ts +11 -0
  6. package/dist/api/product-import.controller.js +63 -0
  7. package/dist/constants.d.ts +2 -0
  8. package/dist/constants.js +5 -0
  9. package/dist/helpers/generate-asset-hash.d.ts +2 -0
  10. package/dist/helpers/generate-asset-hash.js +15 -0
  11. package/dist/helpers/normalize-string.d.ts +1 -0
  12. package/dist/helpers/normalize-string.js +16 -0
  13. package/dist/helpers/remove-update-at-fields.d.ts +5 -0
  14. package/dist/helpers/remove-update-at-fields.js +19 -0
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.js +17 -0
  17. package/dist/product-import-export.plugin.d.ts +13 -0
  18. package/dist/product-import-export.plugin.js +153 -0
  19. package/dist/providers/import-providers/asset-importer.d.ts +31 -0
  20. package/dist/providers/import-providers/asset-importer.js +159 -0
  21. package/dist/providers/import-providers/import-parser.d.ts +137 -0
  22. package/dist/providers/import-providers/import-parser.js +600 -0
  23. package/dist/providers/import-providers/product-importer.d.ts +48 -0
  24. package/dist/providers/import-providers/product-importer.js +754 -0
  25. package/dist/services/extended-fast-importer.service.d.ts +44 -0
  26. package/dist/services/extended-fast-importer.service.js +474 -0
  27. package/dist/services/product-export.service.d.ts +23 -0
  28. package/dist/services/product-export.service.js +344 -0
  29. package/dist/services/product-import.service.d.ts +22 -0
  30. package/dist/services/product-import.service.js +84 -0
  31. package/dist/types.d.ts +41 -0
  32. package/dist/types.js +2 -0
  33. package/dist/ui/components/export-dialog.component.d.ts +43 -0
  34. package/dist/ui/components/export-dialog.component.html +78 -0
  35. package/dist/ui/components/export-dialog.component.js +108 -0
  36. package/dist/ui/components/export-dialog.component.ts +142 -0
  37. package/dist/ui/components/product-import.component.d.ts +57 -0
  38. package/dist/ui/components/product-import.component.html +123 -0
  39. package/dist/ui/components/product-import.component.js +413 -0
  40. package/dist/ui/components/product-import.component.ts +550 -0
  41. package/dist/ui/product-export.service.d.ts +14 -0
  42. package/dist/ui/product-export.service.js +107 -0
  43. package/dist/ui/product-export.service.ts +107 -0
  44. package/dist/ui/providers.d.ts +2 -0
  45. package/dist/ui/providers.js +49 -0
  46. package/dist/ui/providers.ts +57 -0
  47. package/dist/ui/routes.d.ts +31 -0
  48. package/dist/ui/routes.js +12 -0
  49. package/dist/ui/routes.ts +11 -0
  50. package/dist/ui/translations/en.json +52 -0
  51. package/dist/ui/translations/sv.json +52 -0
  52. package/package.json +1 -1
@@ -0,0 +1,344 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ 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;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || function (mod) {
25
+ if (mod && mod.__esModule) return mod;
26
+ var result = {};
27
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
28
+ __setModuleDefault(result, mod);
29
+ return result;
30
+ };
31
+ var __metadata = (this && this.__metadata) || function (k, v) {
32
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
33
+ };
34
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
35
+ return function (target, key) { decorator(target, key, paramIndex); }
36
+ };
37
+ var __importDefault = (this && this.__importDefault) || function (mod) {
38
+ return (mod && mod.__esModule) ? mod : { "default": mod };
39
+ };
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.ProductExportService = void 0;
42
+ const common_1 = require("@nestjs/common");
43
+ const core_1 = require("@vendure/core");
44
+ const constants_1 = require("../constants");
45
+ const csv_writer_1 = require("csv-writer");
46
+ const path = __importStar(require("path"));
47
+ const os_1 = __importDefault(require("os"));
48
+ const lodash_1 = require("lodash");
49
+ let ProductExportService = class ProductExportService {
50
+ options;
51
+ productService;
52
+ stockLevelService;
53
+ entityHydratorService;
54
+ channelService;
55
+ configService;
56
+ constructor(options, productService, stockLevelService, entityHydratorService, channelService, configService) {
57
+ this.options = options;
58
+ this.productService = productService;
59
+ this.stockLevelService = stockLevelService;
60
+ this.entityHydratorService = entityHydratorService;
61
+ this.channelService = channelService;
62
+ this.configService = configService;
63
+ }
64
+ async createExportFile(ctx, selectionIds, fileName, selectedCustomFields, exportAssetsAs, selectedExportFields) {
65
+ const products = await this.productService.findByIds(ctx, selectionIds);
66
+ const channel = await this.channelService.findOne(ctx, ctx.channelId);
67
+ if (!channel) {
68
+ throw new Error('Channel not found');
69
+ }
70
+ const languages = channel.availableLanguageCodes;
71
+ const allCustomFieldNames = await this.getCustomFields(ctx, selectionIds);
72
+ const filteredCustomFieldNames = selectedCustomFields
73
+ .split(',')
74
+ .filter((field) => allCustomFieldNames.some((f) => f.name === field))
75
+ .map((field) => {
76
+ const found = allCustomFieldNames.find((f) => f.name === field);
77
+ return found ? `${found.name}:${found.type}` : '';
78
+ });
79
+ const exportFile = path.join(os_1.default.tmpdir(), fileName);
80
+ const headers = [];
81
+ headers.push({ id: 'productId', title: 'productId' });
82
+ // Add headers for translations
83
+ for (const lang of languages) {
84
+ headers.push({ id: `name:${lang}`, title: `name:${lang}` });
85
+ headers.push({ id: `slug:${lang}`, title: `slug:${lang}` });
86
+ headers.push({ id: `description:${lang}`, title: `description:${lang}` });
87
+ }
88
+ headers.push({ id: 'assets', title: 'assets' }, ...languages.map((lang) => ({ id: `facets:${lang}`, title: `facets:${lang}` })), ...languages.map((lang) => ({ id: `optionGroups:${lang}`, title: `optionGroups:${lang}` })), ...languages.map((lang) => ({ id: `optionValues:${lang}`, title: `optionValues:${lang}` })), { id: 'sku', title: 'sku' }, { id: 'price', title: 'price' }, { id: 'taxCategory', title: 'taxCategory' }, { id: 'stockOnHand', title: 'stockOnHand' }, { id: 'trackInventory', title: 'trackInventory' }, { id: 'variantAssets', title: 'variantAssets' }, ...languages.map((lang) => ({ id: `variantFacets:${lang}`, title: `variantFacets:${lang}` })), ...filteredCustomFieldNames.map((field) => ({
89
+ id: field,
90
+ title: field,
91
+ })));
92
+ const selectedExportFieldsArray = selectedExportFields.split(',');
93
+ const selectedExportFieldsSet = new Set(selectedExportFieldsArray);
94
+ const filteredHeaders = headers.filter((header) => {
95
+ // Ensure custom fields are not filtered out
96
+ if (filteredCustomFieldNames.includes(header.id)) {
97
+ return true;
98
+ }
99
+ if (header.id.includes(':')) {
100
+ const field = header.id.split(':')[0];
101
+ return selectedExportFieldsSet.has(field);
102
+ }
103
+ return selectedExportFieldsSet.has(header.id);
104
+ });
105
+ const csvWriter = (0, csv_writer_1.createObjectCsvWriter)({
106
+ path: exportFile,
107
+ header: filteredHeaders,
108
+ });
109
+ for (const product of products) {
110
+ const records = [];
111
+ const hydratedProduct = await this.entityHydratorService.hydrate(ctx, product, {
112
+ relations: [
113
+ 'variants',
114
+ 'facetValues',
115
+ 'facetValues.facet',
116
+ 'optionGroups',
117
+ 'assets',
118
+ 'variants.assets',
119
+ 'variants.facetValues',
120
+ 'variants.facetValues.facet',
121
+ 'variants.options',
122
+ ],
123
+ });
124
+ const { assets = [], facetValues = [], optionGroups = [], variants = [], translations = [], customFields = {}, } = hydratedProduct;
125
+ const nameTranslations = this.mapTranslations(translations, 'name', languages);
126
+ const slugTranslations = this.mapTranslations(translations, 'slug', languages);
127
+ const descriptionTranslations = this.mapTranslations(translations, 'description', languages);
128
+ // Filter out all variants that are soft deleted
129
+ const activeVariants = variants.filter((v) => !v.deletedAt);
130
+ const productAssets = assets.length === 0
131
+ ? ''
132
+ : assets.length > 0
133
+ ? this.handleAssets(assets.map(({ asset }) => asset), exportAssetsAs)
134
+ : this.handleAssets([hydratedProduct.featuredAsset], exportAssetsAs);
135
+ const productFacets = languages.reduce((acc, lang) => {
136
+ acc[lang] = facetValues
137
+ .map((facetValue) => this.mapFacetTranslations(facetValue, lang))
138
+ .filter((facet) => facet)
139
+ .join('|');
140
+ return acc;
141
+ }, {});
142
+ const optionGroupNames = languages.reduce((acc, lang) => {
143
+ acc[lang] = (0, lodash_1.sortBy)(optionGroups, (g) => g.id)
144
+ .map((group) => this.mapTranslations(group.translations, 'name', [lang])[lang])
145
+ .filter((name) => name)
146
+ .join('|');
147
+ return acc;
148
+ }, {});
149
+ for (const variant of activeVariants) {
150
+ const variantValues = languages.reduce((acc, lang) => {
151
+ acc[lang] = ((0, lodash_1.sortBy)(variant.options, (o) => o.groupId) || [])
152
+ .map((option) => this.mapTranslations(option.translations, 'name', [lang])[lang])
153
+ .filter((name) => name)
154
+ .join('|');
155
+ return acc;
156
+ }, {});
157
+ const variantAssets = this.handleAssets(variant.assets.map(({ asset }) => asset), exportAssetsAs);
158
+ const variantFacets = languages.reduce((acc, lang) => {
159
+ acc[lang] = (variant.facetValues || [])
160
+ .map((facet) => this.mapFacetTranslations(facet, lang))
161
+ .filter((facet) => facet)
162
+ .join('|');
163
+ return acc;
164
+ }, {});
165
+ const stockOnHand = await this.getStockOnHand(ctx, variant.id); // Adjusted to fetch stock details
166
+ const variantTranslations = variant.translations;
167
+ const variantNameTranslations = this.mapTranslations(variantTranslations, 'name', languages);
168
+ const record = {};
169
+ const convertToHTML = (text) => {
170
+ return text
171
+ .replace(/\n/g, '<br>')
172
+ .replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
173
+ .replace(/,\s*'/g, ", '");
174
+ };
175
+ for (const lang of languages) {
176
+ const escapedDescription = convertToHTML(descriptionTranslations[lang]);
177
+ const escapedName = nameTranslations[lang]
178
+ .replace(/"/g, "'")
179
+ .replace(/\s+/g, ' ')
180
+ .replace(/,\s*'/g, ", '")
181
+ .replace(/'/g, "''");
182
+ record.productId = records.length === 0 ? product.id : '';
183
+ record[`name:${lang}`] = records.length === 0 ? escapedName || '' : '';
184
+ record[`slug:${lang}`] = records.length === 0 ? slugTranslations[lang] || '' : '';
185
+ record[`description:${lang}`] = records.length === 0 ? escapedDescription || '' : '';
186
+ record[`facets:${lang}`] = records.length === 0 ? productFacets[lang] : '';
187
+ record[`optionGroups:${lang}`] = records.length === 0 ? optionGroupNames[lang] : '';
188
+ record[`optionValues:${lang}`] = variantValues[lang];
189
+ record[`variantFacets:${lang}`] = variantFacets[lang];
190
+ }
191
+ const productVariantPrices = variant.productVariantPrices.filter((price) => price.channelId === ctx.channelId);
192
+ record.assets = records.length === 0 ? productAssets : '';
193
+ record.sku = variant.sku;
194
+ record.price = productVariantPrices[0]?.price / 100; // Assuming the price is stored in cents
195
+ record.taxCategory = 'standard'; // Replace with actual tax category if available
196
+ record.stockOnHand = stockOnHand;
197
+ record.trackInventory = variant.trackInventory.toLowerCase();
198
+ record.variantAssets = variantAssets;
199
+ for (const field of filteredCustomFieldNames) {
200
+ const [entity, fieldName, type] = field.split(':');
201
+ if (entity === 'product') {
202
+ record[field] =
203
+ this.handleCustomFields(customFields, fieldName, type, exportAssetsAs) || '';
204
+ }
205
+ else if (entity === 'variant') {
206
+ record[field] =
207
+ this.handleCustomFields(variant.customFields, fieldName, type, exportAssetsAs) || '';
208
+ }
209
+ }
210
+ records.push(record);
211
+ }
212
+ await csvWriter.writeRecords(records);
213
+ }
214
+ return exportFile;
215
+ }
216
+ handleAssets(assets, exportAssetsAs) {
217
+ if (!assets.length) {
218
+ return '';
219
+ }
220
+ if (exportAssetsAs === 'url') {
221
+ return assets.map((asset) => asset.source).join('|');
222
+ }
223
+ return JSON.stringify(assets.length > 1
224
+ ? assets.map((asset) => ({
225
+ id: asset.id,
226
+ name: asset.name,
227
+ url: asset.source,
228
+ }))
229
+ : {
230
+ id: assets[0].id,
231
+ name: assets[0].name,
232
+ url: assets[0].source,
233
+ })
234
+ .replace(/"/g, "'") // Replace double quotes with single quotes
235
+ .replace(/\s+/g, ' ') // Remove unnecessary whitespace and line breaks
236
+ .replace(/,\s*'/g, ", '"); // Ensure clean spacing after commas
237
+ }
238
+ handleCustomFields(customFields, fieldName, type, exportAssetsAs) {
239
+ const fieldValue = customFields[fieldName];
240
+ if (!fieldValue) {
241
+ return;
242
+ }
243
+ const relationsTypes = [
244
+ ...this.configService.customFields.Product.map((field) => {
245
+ if (field.type === 'relation')
246
+ return field.entity.name.toLowerCase();
247
+ }),
248
+ ...this.configService.customFields.ProductVariant.map((field) => {
249
+ if (field.type === 'relation')
250
+ return field.entity.name.toLowerCase();
251
+ }),
252
+ ].filter((type) => type);
253
+ if (relationsTypes.includes(type)) {
254
+ if (type === 'asset') {
255
+ return (customFields[fieldName] = this.handleAssets([fieldValue], exportAssetsAs));
256
+ }
257
+ else {
258
+ return (customFields[fieldName] = fieldValue.id);
259
+ }
260
+ return;
261
+ }
262
+ if (typeof fieldValue === 'object') {
263
+ customFields[fieldName] = customFields[fieldName] = JSON.stringify(fieldValue)
264
+ .replace(/"/g, "'") // Replace double quotes with single quotes
265
+ .replace(/\s+/g, ' ') // Remove unnecessary whitespace and line breaks
266
+ .replace(/,\s*'/g, ", '"); // Ensure clean spacing after commas
267
+ }
268
+ else if ((0, lodash_1.startsWith)(fieldValue, '{') || (0, lodash_1.startsWith)(fieldValue, '[')) {
269
+ customFields[fieldName] = fieldValue.replace(/"/g, "'").replace(/\s+/g, ' ');
270
+ return customFields[fieldName];
271
+ }
272
+ return customFields[fieldName];
273
+ }
274
+ // Method to fetch stock on hand for a variant
275
+ async getStockOnHand(ctx, variantId) {
276
+ const stockLevel = await this.stockLevelService.getAvailableStock(ctx, variantId);
277
+ return stockLevel.stockOnHand;
278
+ }
279
+ // Method to map translations for a specific field
280
+ mapTranslations(translations, field, languages) {
281
+ const translationMap = {};
282
+ for (const lang of languages) {
283
+ const translation = translations.find((t) => t.languageCode === lang);
284
+ translationMap[lang] = translation ? translation[field] : '';
285
+ }
286
+ return translationMap;
287
+ }
288
+ // Method to map facet translations
289
+ mapFacetTranslations(facetValue, lang) {
290
+ const facetNameTranslations = this.mapTranslations(facetValue.facet.translations, 'name', [
291
+ lang,
292
+ ]);
293
+ const facetValueTranslations = this.mapTranslations(facetValue.translations, 'name', [lang]);
294
+ if (!facetNameTranslations[lang] || !facetValueTranslations[lang]) {
295
+ return '';
296
+ }
297
+ return `${facetNameTranslations[lang]}:${facetValueTranslations[lang]}`;
298
+ }
299
+ async getCustomFields(ctx, productIds) {
300
+ const customFields = new Set();
301
+ (0, lodash_1.forEach)(this.configService.customFields.Product, (field) => {
302
+ if (field.type === 'relation') {
303
+ const entity = field.entity.name;
304
+ customFields.add({
305
+ name: `product:${field.name}`,
306
+ type: entity.toLowerCase(),
307
+ });
308
+ return;
309
+ }
310
+ customFields.add({
311
+ name: `product:${field.name}`,
312
+ type: field.type,
313
+ });
314
+ });
315
+ (0, lodash_1.forEach)(this.configService.customFields.ProductVariant, (field) => {
316
+ if (field.type === 'relation') {
317
+ const entity = field.entity.name;
318
+ customFields.add({
319
+ name: `variant:${field.name}`,
320
+ type: entity.toLowerCase(),
321
+ });
322
+ return;
323
+ }
324
+ customFields.add({
325
+ name: `variant:${field.name}`,
326
+ type: field.type,
327
+ });
328
+ });
329
+ return Array.from(customFields);
330
+ }
331
+ async getConfig() {
332
+ return this.options;
333
+ }
334
+ };
335
+ ProductExportService = __decorate([
336
+ (0, common_1.Injectable)(),
337
+ __param(0, (0, common_1.Inject)(constants_1.PRODUCT_IMPORT_EXPORT_PLUGIN_OPTIONS)),
338
+ __metadata("design:paramtypes", [Object, core_1.ProductService,
339
+ core_1.StockLevelService,
340
+ core_1.EntityHydrator,
341
+ core_1.ChannelService,
342
+ core_1.ConfigService])
343
+ ], ProductExportService);
344
+ exports.ProductExportService = ProductExportService;
@@ -0,0 +1,22 @@
1
+ /// <reference types="Multer" />
2
+ import { OnModuleInit } from '@nestjs/common';
3
+ import { RequestContext, JobQueueService, SerializedRequestContext, SearchService, LanguageCode } from '@vendure/core';
4
+ import { PluginInitOptions, UpdatingStrategy } from '../types';
5
+ import { ProductImporter } from '../providers/import-providers/product-importer';
6
+ export declare class ProductImportService implements OnModuleInit {
7
+ private options;
8
+ private jobQueueService;
9
+ private productImporter;
10
+ private searchService;
11
+ private productImportQueue;
12
+ constructor(options: PluginInitOptions, jobQueueService: JobQueueService, productImporter: ProductImporter, searchService: SearchService);
13
+ onModuleInit(): Promise<void>;
14
+ processFile(ctx: RequestContext, file: Express.Multer.File, updateProductSlug: boolean, mainLanguage: LanguageCode, updatingStrategy: UpdatingStrategy): Promise<import("@vendure/core/dist/job-queue/subscribable-job").SubscribableJob<{
15
+ ctx: SerializedRequestContext;
16
+ fileContent: string;
17
+ updateProductSlug: boolean;
18
+ mainLanguage: LanguageCode;
19
+ updatingStrategy: UpdatingStrategy;
20
+ }>>;
21
+ private triggerProductImport;
22
+ }
@@ -0,0 +1,84 @@
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
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ProductImportService = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const core_1 = require("@vendure/core");
18
+ const constants_1 = require("../constants");
19
+ const product_importer_1 = require("../providers/import-providers/product-importer");
20
+ const lodash_1 = require("lodash");
21
+ let ProductImportService = class ProductImportService {
22
+ options;
23
+ jobQueueService;
24
+ productImporter;
25
+ searchService;
26
+ productImportQueue;
27
+ constructor(options, jobQueueService, productImporter, searchService) {
28
+ this.options = options;
29
+ this.jobQueueService = jobQueueService;
30
+ this.productImporter = productImporter;
31
+ this.searchService = searchService;
32
+ }
33
+ async onModuleInit() {
34
+ this.productImportQueue = await this.jobQueueService.createQueue({
35
+ name: 'product-import',
36
+ process: async (job) => {
37
+ // Deserialize the RequestContext from the job data
38
+ const ctx = core_1.RequestContext.deserialize(job.data.ctx);
39
+ let jobResult;
40
+ return new Promise((resolve, reject) => {
41
+ this.productImporter
42
+ .parseAndImport(job.data.fileContent, ctx, job.data.updateProductSlug, job.data.mainLanguage, job.data.updatingStrategy)
43
+ .subscribe({
44
+ next: (result) => {
45
+ const processedCount = result.processed;
46
+ const importedCount = result.imported;
47
+ const percentage = (importedCount / processedCount) * 100;
48
+ job.setProgress(percentage);
49
+ jobResult = result;
50
+ },
51
+ complete: async () => {
52
+ await this.searchService.reindex(ctx);
53
+ resolve((0, lodash_1.omit)(jobResult, 'currentProduct'));
54
+ },
55
+ error: (err) => {
56
+ reject(err);
57
+ },
58
+ });
59
+ });
60
+ },
61
+ });
62
+ }
63
+ async processFile(ctx, file, updateProductSlug, mainLanguage, updatingStrategy) {
64
+ const fileContent = file.buffer.toString('utf-8');
65
+ return this.triggerProductImport(ctx, fileContent, updateProductSlug, mainLanguage, updatingStrategy);
66
+ }
67
+ triggerProductImport(ctx, fileContent, updateProductSlug, mainLanguage, updatingStrategy) {
68
+ return this.productImportQueue.add({
69
+ ctx: ctx.serialize(),
70
+ fileContent,
71
+ updateProductSlug,
72
+ mainLanguage,
73
+ updatingStrategy,
74
+ });
75
+ }
76
+ };
77
+ ProductImportService = __decorate([
78
+ (0, common_1.Injectable)(),
79
+ __param(0, (0, common_1.Inject)(constants_1.PRODUCT_IMPORT_EXPORT_PLUGIN_OPTIONS)),
80
+ __metadata("design:paramtypes", [Object, core_1.JobQueueService,
81
+ product_importer_1.ProductImporter,
82
+ core_1.SearchService])
83
+ ], ProductImportService);
84
+ exports.ProductImportService = ProductImportService;
@@ -0,0 +1,41 @@
1
+ import { ID } from '@vendure/core';
2
+ import { ParsedProduct, ParsedProductWithVariants } from './providers/import-providers/import-parser';
3
+ /**
4
+ * @description
5
+ * The plugin can be configured using the following options:
6
+ */
7
+ type ImportOptions = {
8
+ /**
9
+ * @description
10
+ * updateProductSlug: If true, the product slug will be updated based on the `name` field in the CSV file.
11
+ */
12
+ updateProductSlug?: boolean;
13
+ };
14
+ export interface PluginInitOptions {
15
+ importOptions: {
16
+ visibleOptions?: Array<keyof ImportOptions>;
17
+ defaultOptions?: ImportOptions;
18
+ };
19
+ exportOptions: {
20
+ defaultFileName?: string;
21
+ exportAssetsAsOptions?: Array<'url' | 'json'>;
22
+ defaultExportAssetsAs?: 'url' | 'json';
23
+ defaultExportFields?: ExportFields;
24
+ requiredExportFields?: ExportFields;
25
+ };
26
+ }
27
+ export type ParsedProductWithId = ParsedProductWithVariants & {
28
+ product: ParsedProduct & {
29
+ productId: number | null;
30
+ };
31
+ };
32
+ export type JsonAsset = {
33
+ id?: ID;
34
+ url: string;
35
+ name?: string;
36
+ };
37
+ type ExportFields = Array<ProductFields | VariantFields>;
38
+ type ProductFields = 'productId' | 'name' | 'slug' | 'description' | 'assets' | 'facets' | 'optionGroups';
39
+ type VariantFields = 'sku' | 'optionValues' | 'price' | 'taxCategory' | 'stockOnHand' | 'trackInventory' | 'variantAssets' | 'variantFacets';
40
+ export type UpdatingStrategy = 'replace' | 'merge';
41
+ export {};
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,43 @@
1
+ import { ElementRef, AfterViewInit, OnInit } from '@angular/core';
2
+ import { Dialog } from '@vendure/admin-ui/core';
3
+ type ProductFields = 'productId' | 'name' | 'slug' | 'description' | 'assets' | 'facets' | 'optionGroups';
4
+ type VariantFields = 'sku' | 'optionValues' | 'price' | 'taxCategory' | 'stockOnHand' | 'trackInventory' | 'variantAssets' | 'variantFacets';
5
+ type ExportFields = Array<ProductFields | VariantFields>;
6
+ interface PluginInitOptions {
7
+ defaultFileName?: string;
8
+ exportAssetsAsOptions?: Array<'url' | 'json'>;
9
+ defaultExportAssetsAs?: 'url' | 'json';
10
+ defaultExportFields?: ExportFields;
11
+ requiredExportFields?: ExportFields;
12
+ }
13
+ export declare class ExportDialogComponent implements Dialog<{
14
+ result: boolean;
15
+ fileName?: string;
16
+ selectedFields?: string[];
17
+ exportAssetsAs?: 'url' | 'json';
18
+ selectedExportFields?: ExportFields;
19
+ }>, AfterViewInit, OnInit {
20
+ fileNameElement: ElementRef<HTMLInputElement>;
21
+ resolveWith: (result?: {
22
+ result: boolean;
23
+ fileName?: string;
24
+ selectedFields?: string[];
25
+ exportAssetsAs?: 'url' | 'json';
26
+ selectedExportFields?: ExportFields;
27
+ }) => void;
28
+ selection: any[];
29
+ fileName: string;
30
+ customFields: string[];
31
+ selectedFields: string[];
32
+ exportAssetsAs: 'url' | 'json';
33
+ selectedExportFields: ExportFields;
34
+ availableExportFields: ExportFields;
35
+ config: PluginInitOptions;
36
+ ngOnInit(): void;
37
+ ngAfterViewInit(): void;
38
+ export(): void;
39
+ cancel(): void;
40
+ toggleFieldSelection(fieldName: string): void;
41
+ toggleExportFieldSelection(fieldName: ProductFields | VariantFields): void;
42
+ }
43
+ export {};
@@ -0,0 +1,78 @@
1
+ <ng-template vdrDialogTitle>{{ 'product-export.dialog.title' | translate }}</ng-template>
2
+ <div class="input-row">
3
+ <vdr-form-field [label]="'product-export.dialog.label' | translate">
4
+ <input
5
+ type="text"
6
+ [(ngModel)]="fileName"
7
+ [placeholder]="config?.defaultFileName || 'products_export.csv'"
8
+ style="width: 100%"
9
+ #fileNameInput
10
+ />
11
+ </vdr-form-field>
12
+ </div>
13
+
14
+ <div class="input-row" style="margin-top: 16px">
15
+ <label style="margin-bottom: 8px; display: inline-block">{{
16
+ 'product-export.dialog.availableExportFieldsLabel' | translate
17
+ }}</label>
18
+ <div *ngFor="let field of availableExportFields">
19
+ <input
20
+ type="checkbox"
21
+ [value]="field"
22
+ (change)="toggleExportFieldSelection(field)"
23
+ [checked]="selectedExportFields.includes(field)"
24
+ [disabled]="config.requiredExportFields.includes(field)"
25
+ />
26
+ {{ field }}
27
+ </div>
28
+ </div>
29
+
30
+ <div class="input-row" style="margin-top: 16px">
31
+ <label style="margin-bottom: 8px; display: inline-block">{{
32
+ 'product-export.dialog.customFieldsLabel' | translate
33
+ }}</label>
34
+ <div *ngFor="let field of customFields">
35
+ <input
36
+ type="checkbox"
37
+ [value]="field"
38
+ (change)="toggleFieldSelection(field)"
39
+ [checked]="selectedFields.includes(field)"
40
+ />
41
+ {{ field }}
42
+ </div>
43
+ </div>
44
+
45
+ <span style="margin-top: 16px; margin-bottom: 8px; display: block">{{
46
+ 'product-export.dialog.exportAssetsAs' | translate
47
+ }}</span>
48
+ <div class="input-row" style="margin-top: 8px">
49
+ <label *ngIf="config?.exportAssetsAsOptions?.includes('url')">
50
+ <input
51
+ type="radio"
52
+ name="exportAssets"
53
+ value="url"
54
+ [(ngModel)]="exportAssetsAs"
55
+ style="width: fit-content"
56
+ />
57
+ {{ 'product-export.dialog.exportAssetsAsOptions.url' | translate }}
58
+ </label>
59
+ <label *ngIf="config?.exportAssetsAsOptions?.includes('json')">
60
+ <input
61
+ type="radio"
62
+ name="exportAssets"
63
+ value="json"
64
+ [(ngModel)]="exportAssetsAs"
65
+ style="width: fit-content"
66
+ />
67
+ {{ 'product-export.dialog.exportAssetsAsOptions.json' | translate }}
68
+ </label>
69
+ </div>
70
+
71
+ <ng-template vdrDialogButtons>
72
+ <button type="button" class="btn" (click)="cancel()">
73
+ {{ 'common.cancel' | translate }}
74
+ </button>
75
+ <button type="button" class="btn btn-primary" (click)="export()">
76
+ {{ 'product-export.dialog.export' | translate }}
77
+ </button>
78
+ </ng-template>