@haus-tech/product-export-plugin 2.2.0

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.
@@ -0,0 +1,15 @@
1
+ import { RequestContext, ID } from '@vendure/core';
2
+ import { Response } from 'express';
3
+ import { ProductExportService } from '../services/product-export.service';
4
+ import { PluginInitOptions } from '../types';
5
+ export declare class ProductExportController {
6
+ private options;
7
+ private productExportService;
8
+ constructor(options: PluginInitOptions, productExportService: ProductExportService);
9
+ exportProducts(ctx: RequestContext, res: Response, fileName: string, customFields: string, exportAssetsAs: 'url' | 'json', selection: ID[]): Promise<void>;
10
+ getCustomFields(ctx: RequestContext, ids: string[]): Promise<{
11
+ name: string;
12
+ type: string;
13
+ }[]>;
14
+ getConfig(): PluginInitOptions;
15
+ }
@@ -0,0 +1,121 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.ProductExportController = void 0;
39
+ const core_1 = require("@vendure/core");
40
+ const common_1 = require("@nestjs/common");
41
+ const product_export_service_1 = require("../services/product-export.service");
42
+ const fs = __importStar(require("fs"));
43
+ const fs_1 = require("fs");
44
+ const constants_1 = require("../constants");
45
+ let ProductExportController = class ProductExportController {
46
+ options;
47
+ productExportService;
48
+ constructor(options, productExportService) {
49
+ this.options = options;
50
+ this.productExportService = productExportService;
51
+ }
52
+ async exportProducts(ctx, res, fileName, customFields, exportAssetsAs, selection) {
53
+ try {
54
+ if (!selection || !Array.isArray(selection) || selection.length === 0) {
55
+ throw new common_1.UnprocessableEntityException('No products selected');
56
+ }
57
+ if (!fileName) {
58
+ if (this.options.defaultFileName && !this.options.defaultFileName.endsWith('.csv')) {
59
+ fileName = this.options.defaultFileName += '.csv';
60
+ }
61
+ else {
62
+ fileName = this.options.defaultFileName || 'products_export.csv';
63
+ }
64
+ }
65
+ else if (!fileName.endsWith('.csv')) {
66
+ fileName += '.csv';
67
+ }
68
+ const csv = await this.productExportService.createExportFile(ctx, selection, fileName, customFields, exportAssetsAs);
69
+ const readStream = fs.createReadStream(csv);
70
+ res.set({
71
+ 'Content-Type': 'text/csv',
72
+ 'Content-Disposition': `attachment; filename=${fileName}`,
73
+ });
74
+ readStream.on('end', async () => {
75
+ await fs_1.promises.unlink(csv);
76
+ });
77
+ readStream.pipe(res);
78
+ }
79
+ catch (e) {
80
+ throw new common_1.UnprocessableEntityException(e.message);
81
+ }
82
+ }
83
+ async getCustomFields(ctx, ids) {
84
+ return this.productExportService.getCustomFields(ctx, ids);
85
+ }
86
+ getConfig() {
87
+ return this.options;
88
+ }
89
+ };
90
+ __decorate([
91
+ (0, common_1.Post)('export'),
92
+ __param(0, (0, core_1.Ctx)()),
93
+ __param(1, (0, common_1.Res)()),
94
+ __param(2, (0, common_1.Query)('fileName')),
95
+ __param(3, (0, common_1.Query)('customFields')),
96
+ __param(4, (0, common_1.Query)('exportAssetsAs')),
97
+ __param(5, (0, common_1.Body)()),
98
+ __metadata("design:type", Function),
99
+ __metadata("design:paramtypes", [core_1.RequestContext, Object, String, String, String, Array]),
100
+ __metadata("design:returntype", Promise)
101
+ ], ProductExportController.prototype, "exportProducts", null);
102
+ __decorate([
103
+ (0, common_1.Post)('custom-fields'),
104
+ __param(0, (0, core_1.Ctx)()),
105
+ __param(1, (0, common_1.Body)()),
106
+ __metadata("design:type", Function),
107
+ __metadata("design:paramtypes", [core_1.RequestContext, Array]),
108
+ __metadata("design:returntype", Promise)
109
+ ], ProductExportController.prototype, "getCustomFields", null);
110
+ __decorate([
111
+ (0, common_1.Get)('config'),
112
+ __metadata("design:type", Function),
113
+ __metadata("design:paramtypes", []),
114
+ __metadata("design:returntype", void 0)
115
+ ], ProductExportController.prototype, "getConfig", null);
116
+ ProductExportController = __decorate([
117
+ (0, common_1.Controller)('product-export'),
118
+ __param(0, (0, common_1.Inject)(constants_1.PRODUCT_EXPORT_PLUGIN_OPTIONS)),
119
+ __metadata("design:paramtypes", [Object, product_export_service_1.ProductExportService])
120
+ ], ProductExportController);
121
+ exports.ProductExportController = ProductExportController;
@@ -0,0 +1,2 @@
1
+ export declare const PRODUCT_EXPORT_PLUGIN_OPTIONS: unique symbol;
2
+ export declare const loggerCtx = "ProductExportPlugin";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loggerCtx = exports.PRODUCT_EXPORT_PLUGIN_OPTIONS = void 0;
4
+ exports.PRODUCT_EXPORT_PLUGIN_OPTIONS = Symbol('PRODUCT_EXPORT_PLUGIN_OPTIONS');
5
+ exports.loggerCtx = 'ProductExportPlugin';
@@ -0,0 +1,8 @@
1
+ import { AdminUiExtension } from '@vendure/ui-devkit/compiler';
2
+ import { Type } from '@vendure/core';
3
+ import { PluginInitOptions } from './types';
4
+ export declare class ProductExportPlugin {
5
+ static options: PluginInitOptions;
6
+ static init(options: PluginInitOptions): Type<ProductExportPlugin>;
7
+ static ui: AdminUiExtension;
8
+ }
@@ -0,0 +1,83 @@
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 ProductExportPlugin_1;
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.ProductExportPlugin = void 0;
34
+ const path = __importStar(require("path"));
35
+ const core_1 = require("@vendure/core");
36
+ const constants_1 = require("./constants");
37
+ const product_export_service_1 = require("./services/product-export.service");
38
+ const product_export_controller_1 = require("./api/product-export.controller");
39
+ let ProductExportPlugin = ProductExportPlugin_1 = class ProductExportPlugin {
40
+ static options;
41
+ static init(options) {
42
+ if (!options.defaultFileName) {
43
+ options.defaultFileName = 'products_export.csv';
44
+ }
45
+ if (!options.exportAssetsAsOptions) {
46
+ options.exportAssetsAsOptions = ['url', 'json'];
47
+ }
48
+ if (!options.defaultExportAssetsAs) {
49
+ options.defaultExportAssetsAs = 'url';
50
+ }
51
+ this.options = options;
52
+ return ProductExportPlugin_1;
53
+ }
54
+ static ui = {
55
+ id: 'product-export-ui',
56
+ extensionPath: path.join(__dirname, 'ui'),
57
+ translations: {
58
+ en: path.join(__dirname, 'ui/translations/en.json'),
59
+ sv: path.join(__dirname, 'ui/translations/sv.json'),
60
+ },
61
+ routes: [{ route: 'product-export', filePath: 'routes.ts' }],
62
+ providers: ['providers.ts'],
63
+ };
64
+ };
65
+ ProductExportPlugin = ProductExportPlugin_1 = __decorate([
66
+ (0, core_1.VendurePlugin)({
67
+ imports: [core_1.PluginCommonModule],
68
+ providers: [
69
+ { provide: constants_1.PRODUCT_EXPORT_PLUGIN_OPTIONS, useFactory: () => ProductExportPlugin_1.options },
70
+ product_export_service_1.ProductExportService,
71
+ ],
72
+ controllers: [product_export_controller_1.ProductExportController],
73
+ configuration: (config) => {
74
+ // Plugin-specific configuration
75
+ // such as custom fields, custom permissions,
76
+ // strategies etc. can be configured here by
77
+ // modifying the `config` object.
78
+ return config;
79
+ },
80
+ compatibility: '^2.0.0',
81
+ })
82
+ ], ProductExportPlugin);
83
+ exports.ProductExportPlugin = ProductExportPlugin;
@@ -0,0 +1,24 @@
1
+ import { EntityHydrator, ID, ProductService, RequestContext, StockLevelService, ChannelService, ProductVariantService, ConfigService } from '@vendure/core';
2
+ import { PluginInitOptions } from '../types';
3
+ export type RelationType = 'string' | 'boolean' | 'localeString' | 'text' | 'localeText' | 'int' | 'float' | 'datetime' | 'relation';
4
+ export declare class ProductExportService {
5
+ private options;
6
+ private productService;
7
+ private variantService;
8
+ private stockLevelService;
9
+ private entityHydratorService;
10
+ private channelService;
11
+ private configService;
12
+ constructor(options: PluginInitOptions, productService: ProductService, variantService: ProductVariantService, stockLevelService: StockLevelService, entityHydratorService: EntityHydrator, channelService: ChannelService, configService: ConfigService);
13
+ createExportFile(ctx: RequestContext, selectionIds: ID[], fileName: string, selectedCustomFields: string, exportAssetsAs: 'url' | 'json'): Promise<string>;
14
+ private handleAssets;
15
+ private handleCustomFields;
16
+ private getStockOnHand;
17
+ private mapTranslations;
18
+ private mapFacetTranslations;
19
+ getCustomFields(ctx: RequestContext, productIds: string[]): Promise<{
20
+ name: string;
21
+ type: string;
22
+ }[]>;
23
+ getConfig(): Promise<PluginInitOptions>;
24
+ }
@@ -0,0 +1,316 @@
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
+ variantService;
53
+ stockLevelService;
54
+ entityHydratorService;
55
+ channelService;
56
+ configService;
57
+ constructor(options, productService, variantService, stockLevelService, entityHydratorService, channelService, configService) {
58
+ this.options = options;
59
+ this.productService = productService;
60
+ this.variantService = variantService;
61
+ this.stockLevelService = stockLevelService;
62
+ this.entityHydratorService = entityHydratorService;
63
+ this.channelService = channelService;
64
+ this.configService = configService;
65
+ }
66
+ async createExportFile(ctx, selectionIds, fileName, selectedCustomFields, exportAssetsAs) {
67
+ const products = await this.productService.findByIds(ctx, selectionIds);
68
+ const channel = await this.channelService.findOne(ctx, ctx.channelId);
69
+ if (!channel) {
70
+ throw new Error('Channel not found');
71
+ }
72
+ const languages = channel.availableLanguageCodes;
73
+ const allCustomFieldNames = await this.getCustomFields(ctx, selectionIds);
74
+ const filteredCustomFieldNames = selectedCustomFields
75
+ .split(',')
76
+ .filter((field) => allCustomFieldNames.some((f) => f.name === field))
77
+ .map((field) => {
78
+ const found = allCustomFieldNames.find((f) => f.name === field);
79
+ return found ? `${found.name}:${found.type}` : '';
80
+ });
81
+ const exportFile = path.join(os_1.default.tmpdir(), fileName);
82
+ const headers = [];
83
+ headers.push({ id: 'productId', title: 'productId' });
84
+ // Add headers for translations
85
+ for (const lang of languages) {
86
+ headers.push({ id: `name:${lang}`, title: `name:${lang}` });
87
+ headers.push({ id: `slug:${lang}`, title: `slug:${lang}` });
88
+ headers.push({ id: `description:${lang}`, title: `description:${lang}` });
89
+ }
90
+ 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) => ({
91
+ id: field,
92
+ title: field,
93
+ })));
94
+ const csvWriter = (0, csv_writer_1.createObjectCsvWriter)({
95
+ path: exportFile,
96
+ header: headers,
97
+ });
98
+ for (const product of products) {
99
+ const records = [];
100
+ const hydratedProduct = await this.entityHydratorService.hydrate(ctx, product, {
101
+ relations: [
102
+ 'variants',
103
+ 'facetValues',
104
+ 'facetValues.facet',
105
+ 'optionGroups',
106
+ 'assets',
107
+ 'variants.assets',
108
+ 'variants.facetValues',
109
+ 'variants.facetValues.facet',
110
+ 'variants.options',
111
+ ],
112
+ });
113
+ const { assets = [], facetValues = [], optionGroups = [], variants = [], translations = [], customFields = {}, } = hydratedProduct;
114
+ const nameTranslations = this.mapTranslations(translations, 'name', languages);
115
+ const slugTranslations = this.mapTranslations(translations, 'slug', languages);
116
+ const descriptionTranslations = this.mapTranslations(translations, 'description', languages);
117
+ // Filter out all variants that are soft deleted
118
+ const activeVariants = variants.filter((v) => !v.deletedAt);
119
+ const productAssets = assets.length === 0 ? '' :
120
+ assets.length > 0
121
+ ? this.handleAssets(assets.map(({ asset }) => asset), exportAssetsAs)
122
+ : this.handleAssets([hydratedProduct.featuredAsset], exportAssetsAs);
123
+ const productFacets = languages.reduce((acc, lang) => {
124
+ acc[lang] = facetValues
125
+ .map((facetValue) => this.mapFacetTranslations(facetValue, lang))
126
+ .join('|');
127
+ return acc;
128
+ }, {});
129
+ const optionGroupNames = languages.reduce((acc, lang) => {
130
+ acc[lang] = (0, lodash_1.sortBy)(optionGroups, (g) => g.id)
131
+ .map((group) => this.mapTranslations(group.translations, 'name', [lang])[lang])
132
+ .join('|');
133
+ return acc;
134
+ }, {});
135
+ for (const variant of activeVariants) {
136
+ const variantValues = languages.reduce((acc, lang) => {
137
+ acc[lang] = ((0, lodash_1.sortBy)(variant.options, (o) => o.groupId) || [])
138
+ .map((option) => this.mapTranslations(option.translations, 'name', [lang])[lang])
139
+ .join('|');
140
+ return acc;
141
+ }, {});
142
+ const variantAssets = this.handleAssets(variant.assets.map(({ asset }) => asset), exportAssetsAs);
143
+ const variantFacets = languages.reduce((acc, lang) => {
144
+ acc[lang] = (variant.facetValues || [])
145
+ .map((facet) => this.mapFacetTranslations(facet, lang))
146
+ .join('|');
147
+ return acc;
148
+ }, {});
149
+ const stockOnHand = await this.getStockOnHand(ctx, variant.id); // Adjusted to fetch stock details
150
+ const variantTranslations = variant.translations;
151
+ const variantNameTranslations = this.mapTranslations(variantTranslations, 'name', languages);
152
+ const record = {};
153
+ for (const lang of languages) {
154
+ const escapedDescription = descriptionTranslations[lang].replace(/"/g, "'").replace(/\s+/g, ' ').replace(/,\s*'/g, ", '").replace(/'/g, "''");
155
+ const escapedName = nameTranslations[lang].replace(/"/g, "'").replace(/\s+/g, ' ').replace(/,\s*'/g, ", '").replace(/'/g, "''");
156
+ record.productId = records.length === 0 ? product.id : '';
157
+ record[`name:${lang}`] = records.length === 0 ? escapedName || '' : '';
158
+ record[`slug:${lang}`] = records.length === 0 ? slugTranslations[lang] || '' : '';
159
+ record[`description:${lang}`] =
160
+ records.length === 0 ? escapedDescription || '' : '';
161
+ record[`facets:${lang}`] = records.length === 0 ? productFacets[lang] : '';
162
+ record[`optionGroups:${lang}`] = records.length === 0 ? optionGroupNames[lang] : '';
163
+ record[`optionValues:${lang}`] = variantValues[lang];
164
+ record[`variantFacets:${lang}`] = variantFacets[lang];
165
+ }
166
+ record.assets = records.length === 0 ? productAssets : '';
167
+ record.sku = variant.sku;
168
+ record.price = variant.productVariantPrices[0]?.price / 100; // Assuming the price is stored in cents
169
+ record.taxCategory = 'standard'; // Replace with actual tax category if available
170
+ record.stockOnHand = stockOnHand;
171
+ record.trackInventory = variant.trackInventory.toLowerCase();
172
+ record.variantAssets = variantAssets;
173
+ for (const field of filteredCustomFieldNames) {
174
+ const [entity, fieldName, type] = field.split(':');
175
+ if (entity === 'product') {
176
+ record[field] =
177
+ this.handleCustomFields(customFields, fieldName, type, exportAssetsAs) || '';
178
+ }
179
+ else if (entity === 'variant') {
180
+ record[field] =
181
+ this.handleCustomFields(variant.customFields, fieldName, type, exportAssetsAs) || '';
182
+ }
183
+ }
184
+ records.push(record);
185
+ }
186
+ await csvWriter.writeRecords(records);
187
+ }
188
+ return exportFile;
189
+ }
190
+ handleAssets(assets, exportAssetsAs) {
191
+ if (!assets.length) {
192
+ return '';
193
+ }
194
+ if (exportAssetsAs === 'url') {
195
+ return assets.map((asset) => asset.source).join('|');
196
+ }
197
+ return JSON.stringify(assets.length > 1
198
+ ? assets.map((asset) => ({
199
+ id: asset.id,
200
+ name: asset.name,
201
+ url: asset.source,
202
+ }))
203
+ : {
204
+ id: assets[0].id,
205
+ name: assets[0].name,
206
+ url: assets[0].source,
207
+ })
208
+ .replace(/"/g, "'") // Replace double quotes with single quotes
209
+ .replace(/\s+/g, ' ') // Remove unnecessary whitespace and line breaks
210
+ .replace(/,\s*'/g, ", '"); // Ensure clean spacing after commas
211
+ }
212
+ handleCustomFields(customFields, fieldName, type, exportAssetsAs) {
213
+ const fieldValue = customFields[fieldName];
214
+ if (!fieldValue) {
215
+ return;
216
+ }
217
+ const relationsTypes = [
218
+ ...this.configService.customFields.Product.map((field) => {
219
+ if (field.type === 'relation')
220
+ return field.entity.name.toLowerCase();
221
+ }),
222
+ ...this.configService.customFields.ProductVariant.map((field) => {
223
+ if (field.type === 'relation')
224
+ return field.entity.name.toLowerCase();
225
+ }),
226
+ ].filter((type) => type);
227
+ if (relationsTypes.includes(type)) {
228
+ if (type === 'asset') {
229
+ return (customFields[fieldName] = this.handleAssets([fieldValue], exportAssetsAs));
230
+ }
231
+ else {
232
+ return (customFields[fieldName] = fieldValue.id);
233
+ }
234
+ return;
235
+ }
236
+ if (typeof fieldValue === 'object') {
237
+ customFields[fieldName] = customFields[fieldName] = JSON.stringify(fieldValue)
238
+ .replace(/"/g, "'") // Replace double quotes with single quotes
239
+ .replace(/\s+/g, ' ') // Remove unnecessary whitespace and line breaks
240
+ .replace(/,\s*'/g, ", '"); // Ensure clean spacing after commas
241
+ }
242
+ else if ((0, lodash_1.startsWith)(fieldValue, '{') || (0, lodash_1.startsWith)(fieldValue, '[')) {
243
+ customFields[fieldName] = fieldValue.replace(/"/g, "'").replace(/\s+/g, ' ');
244
+ return customFields[fieldName];
245
+ }
246
+ return customFields[fieldName];
247
+ }
248
+ // Method to fetch stock on hand for a variant
249
+ async getStockOnHand(ctx, variantId) {
250
+ const stockLevel = await this.stockLevelService.getAvailableStock(ctx, variantId);
251
+ return stockLevel.stockOnHand;
252
+ }
253
+ // Method to map translations for a specific field
254
+ mapTranslations(translations, field, languages) {
255
+ const translationMap = {};
256
+ for (const lang of languages) {
257
+ const translation = translations.find((t) => t.languageCode === lang);
258
+ translationMap[lang] = translation ? translation[field] : '';
259
+ }
260
+ return translationMap;
261
+ }
262
+ // Method to map facet translations
263
+ mapFacetTranslations(facetValue, lang) {
264
+ const facetNameTranslations = this.mapTranslations(facetValue.facet.translations, 'name', [
265
+ lang,
266
+ ]);
267
+ const facetValueTranslations = this.mapTranslations(facetValue.translations, 'name', [lang]);
268
+ return `${facetNameTranslations[lang]}:${facetValueTranslations[lang]}`;
269
+ }
270
+ async getCustomFields(ctx, productIds) {
271
+ const customFields = new Set();
272
+ (0, lodash_1.forEach)(this.configService.customFields.Product, (field) => {
273
+ if (field.type === 'relation') {
274
+ const entity = field.entity.name;
275
+ customFields.add({
276
+ name: `variant:${field.name}`,
277
+ type: entity.toLowerCase(),
278
+ });
279
+ return;
280
+ }
281
+ customFields.add({
282
+ name: `variant:${field.name}`,
283
+ type: field.type,
284
+ });
285
+ });
286
+ (0, lodash_1.forEach)(this.configService.customFields.ProductVariant, (field) => {
287
+ if (field.type === 'relation') {
288
+ const entity = field.entity.name;
289
+ customFields.add({
290
+ name: `variant:${field.name}`,
291
+ type: entity.toLowerCase(),
292
+ });
293
+ return;
294
+ }
295
+ customFields.add({
296
+ name: `variant:${field.name}`,
297
+ type: field.type,
298
+ });
299
+ });
300
+ return Array.from(customFields);
301
+ }
302
+ async getConfig() {
303
+ return this.options;
304
+ }
305
+ };
306
+ ProductExportService = __decorate([
307
+ (0, common_1.Injectable)(),
308
+ __param(0, (0, common_1.Inject)(constants_1.PRODUCT_EXPORT_PLUGIN_OPTIONS)),
309
+ __metadata("design:paramtypes", [Object, core_1.ProductService,
310
+ core_1.ProductVariantService,
311
+ core_1.StockLevelService,
312
+ core_1.EntityHydrator,
313
+ core_1.ChannelService,
314
+ core_1.ConfigService])
315
+ ], ProductExportService);
316
+ exports.ProductExportService = ProductExportService;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @description
3
+ * The plugin can be configured using the following options:
4
+ */
5
+ export interface PluginInitOptions {
6
+ defaultFileName?: string;
7
+ exportAssetsAsOptions?: Array<'url' | 'json'>;
8
+ defaultExportAssetsAs?: 'url' | 'json';
9
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,33 @@
1
+ import { ElementRef, AfterViewInit, OnInit } from '@angular/core';
2
+ import { Dialog } from '@vendure/admin-ui/core';
3
+ interface PluginInitOptions {
4
+ defaultFileName?: string;
5
+ exportAssetsAsOptions?: Array<'url' | 'json'>;
6
+ defaultExportAssetsAs?: 'url' | 'json';
7
+ }
8
+ export declare class ExportDialogComponent implements Dialog<{
9
+ result: boolean;
10
+ fileName?: string;
11
+ selectedFields?: string[];
12
+ exportAssetsAs?: 'url' | 'json';
13
+ }>, AfterViewInit, OnInit {
14
+ fileNameElement: ElementRef<HTMLInputElement>;
15
+ resolveWith: (result?: {
16
+ result: boolean;
17
+ fileName?: string;
18
+ selectedFields?: string[];
19
+ exportAssetsAs?: 'url' | 'json';
20
+ }) => void;
21
+ selection: any[];
22
+ fileName: string;
23
+ customFields: string[];
24
+ selectedFields: string[];
25
+ exportAssetsAs: 'url' | 'json';
26
+ config: PluginInitOptions;
27
+ ngOnInit(): void;
28
+ ngAfterViewInit(): void;
29
+ export(): void;
30
+ cancel(): void;
31
+ toggleFieldSelection(fieldName: string): void;
32
+ }
33
+ export {};
@@ -0,0 +1,62 @@
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.customFieldsLabel' | translate
17
+ }}</label>
18
+ <div *ngFor="let field of customFields">
19
+ <input
20
+ type="checkbox"
21
+ [value]="field"
22
+ (change)="toggleFieldSelection(field)"
23
+ [checked]="selectedFields.includes(field)"
24
+ />
25
+ {{ field }}
26
+ </div>
27
+ </div>
28
+
29
+ <span style="margin-top: 16px; margin-bottom: 8px; display: block">{{
30
+ 'product-export.dialog.exportAssetsAs' | translate
31
+ }}</span>
32
+ <div class="input-row" style="margin-top: 8px">
33
+ <label *ngIf="config?.exportAssetsAsOptions?.includes('url')">
34
+ <input
35
+ type="radio"
36
+ name="exportAssets"
37
+ value="url"
38
+ [(ngModel)]="exportAssetsAs"
39
+ style="width: fit-content"
40
+ />
41
+ {{ 'product-export.dialog.exportAssetsAsOptions.url' | translate }}
42
+ </label>
43
+ <label *ngIf="config?.exportAssetsAsOptions?.includes('json')">
44
+ <input
45
+ type="radio"
46
+ name="exportAssets"
47
+ value="json"
48
+ [(ngModel)]="exportAssetsAs"
49
+ style="width: fit-content"
50
+ />
51
+ {{ 'product-export.dialog.exportAssetsAsOptions.json' | translate }}
52
+ </label>
53
+ </div>
54
+
55
+ <ng-template vdrDialogButtons>
56
+ <button type="button" class="btn" (click)="cancel()">
57
+ {{ 'common.cancel' | translate }}
58
+ </button>
59
+ <button type="button" class="btn btn-primary" (click)="export()">
60
+ {{ 'product-export.dialog.export' | translate }}
61
+ </button>
62
+ </ng-template>
@@ -0,0 +1,75 @@
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.ExportDialogComponent = void 0;
13
+ const core_1 = require("@angular/core");
14
+ const forms_1 = require("@angular/forms");
15
+ const core_2 = require("@vendure/admin-ui/core");
16
+ let ExportDialogComponent = class ExportDialogComponent {
17
+ fileNameElement;
18
+ resolveWith;
19
+ selection = [];
20
+ fileName = '';
21
+ customFields = [];
22
+ selectedFields = [];
23
+ exportAssetsAs = 'url';
24
+ config;
25
+ ngOnInit() {
26
+ this.selectedFields = [...this.customFields];
27
+ if (this.config.defaultExportAssetsAs?.includes(this.config.defaultExportAssetsAs)) {
28
+ this.exportAssetsAs = this.config.defaultExportAssetsAs;
29
+ }
30
+ else {
31
+ this.exportAssetsAs = this.config.exportAssetsAsOptions?.[0] || 'url';
32
+ }
33
+ }
34
+ ngAfterViewInit() {
35
+ if (this.fileNameElement) {
36
+ setTimeout(() => {
37
+ this.fileNameElement.nativeElement.focus();
38
+ }, 0);
39
+ }
40
+ }
41
+ export() {
42
+ this.fileName = this.fileName?.trim();
43
+ this.resolveWith({
44
+ result: true,
45
+ fileName: this.fileName,
46
+ selectedFields: this.selectedFields,
47
+ exportAssetsAs: this.exportAssetsAs,
48
+ });
49
+ }
50
+ cancel() {
51
+ this.resolveWith({ result: false });
52
+ }
53
+ toggleFieldSelection(fieldName) {
54
+ const index = this.selectedFields.indexOf(fieldName);
55
+ if (index > -1) {
56
+ this.selectedFields.splice(index, 1);
57
+ }
58
+ else {
59
+ this.selectedFields.push(fieldName);
60
+ }
61
+ }
62
+ };
63
+ __decorate([
64
+ (0, core_1.ViewChild)('fileNameInput', { static: false }),
65
+ __metadata("design:type", core_1.ElementRef)
66
+ ], ExportDialogComponent.prototype, "fileNameElement", void 0);
67
+ ExportDialogComponent = __decorate([
68
+ (0, core_1.Component)({
69
+ selector: 'vdr-export-dialog',
70
+ templateUrl: './export-dialog.component.html',
71
+ standalone: true,
72
+ imports: [core_2.SharedModule, forms_1.FormsModule],
73
+ })
74
+ ], ExportDialogComponent);
75
+ exports.ExportDialogComponent = ExportDialogComponent;
@@ -0,0 +1,86 @@
1
+ import { Component, ViewChild, ElementRef, AfterViewInit, OnInit } from '@angular/core'
2
+ import { Dialog } from '@vendure/admin-ui/core'
3
+ import { FormsModule } from '@angular/forms'
4
+ import { SharedModule } from '@vendure/admin-ui/core'
5
+ import { Apollo } from 'apollo-angular'
6
+ import gql from 'graphql-tag'
7
+
8
+ interface PluginInitOptions {
9
+ defaultFileName?: string
10
+ exportAssetsAsOptions?: Array<'url' | 'json'>
11
+ defaultExportAssetsAs?: 'url' | 'json'
12
+ }
13
+
14
+ @Component({
15
+ selector: 'vdr-export-dialog',
16
+ templateUrl: './export-dialog.component.html',
17
+ standalone: true,
18
+ imports: [SharedModule, FormsModule],
19
+ })
20
+ export class ExportDialogComponent
21
+ implements
22
+ Dialog<{
23
+ result: boolean
24
+ fileName?: string
25
+ selectedFields?: string[]
26
+ exportAssetsAs?: 'url' | 'json'
27
+ }>,
28
+ AfterViewInit,
29
+ OnInit
30
+ {
31
+ @ViewChild('fileNameInput', { static: false }) fileNameElement: ElementRef<HTMLInputElement>
32
+
33
+ resolveWith: (result?: {
34
+ result: boolean
35
+ fileName?: string
36
+ selectedFields?: string[]
37
+ exportAssetsAs?: 'url' | 'json'
38
+ }) => void
39
+ selection: any[] = []
40
+ fileName: string = ''
41
+ customFields: string[] = []
42
+ selectedFields: string[] = []
43
+ exportAssetsAs: 'url' | 'json' = 'url'
44
+
45
+ config: PluginInitOptions
46
+
47
+ ngOnInit(): void {
48
+ this.selectedFields = [...this.customFields]
49
+ if (this.config.defaultExportAssetsAs?.includes(this.config.defaultExportAssetsAs)) {
50
+ this.exportAssetsAs = this.config.defaultExportAssetsAs
51
+ } else {
52
+ this.exportAssetsAs = this.config.exportAssetsAsOptions?.[0] || 'url'
53
+ }
54
+ }
55
+
56
+ ngAfterViewInit(): void {
57
+ if (this.fileNameElement) {
58
+ setTimeout(() => {
59
+ this.fileNameElement.nativeElement.focus()
60
+ }, 0)
61
+ }
62
+ }
63
+
64
+ export() {
65
+ this.fileName = this.fileName?.trim()
66
+ this.resolveWith({
67
+ result: true,
68
+ fileName: this.fileName,
69
+ selectedFields: this.selectedFields,
70
+ exportAssetsAs: this.exportAssetsAs,
71
+ })
72
+ }
73
+
74
+ cancel() {
75
+ this.resolveWith({ result: false })
76
+ }
77
+
78
+ toggleFieldSelection(fieldName: string) {
79
+ const index = this.selectedFields.indexOf(fieldName)
80
+ if (index > -1) {
81
+ this.selectedFields.splice(index, 1)
82
+ } else {
83
+ this.selectedFields.push(fieldName)
84
+ }
85
+ }
86
+ }
@@ -0,0 +1,14 @@
1
+ import { DataService, LocalStorageService, NotificationService } from '@vendure/admin-ui/core';
2
+ import { Product } from '@vendure/core';
3
+ export declare class ProductExportService {
4
+ protected dataService: DataService;
5
+ private notificationService;
6
+ private localStorageService;
7
+ serverPath: string;
8
+ constructor(dataService: DataService, notificationService: NotificationService, localStorageService: LocalStorageService);
9
+ getCustomFields(productIds: string[]): Promise<string[]>;
10
+ getConfig(): Promise<any>;
11
+ exportProducts(selection: Product[], fileName?: string, customFields?: string[], exportAssetsAs?: 'url' | 'json'): Promise<void>;
12
+ private getHeaders;
13
+ private downloadBlob;
14
+ }
@@ -0,0 +1,107 @@
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.ProductExportService = void 0;
13
+ const core_1 = require("@angular/core");
14
+ const core_2 = require("@vendure/admin-ui/core");
15
+ let ProductExportService = class ProductExportService {
16
+ dataService;
17
+ notificationService;
18
+ localStorageService;
19
+ serverPath;
20
+ constructor(dataService, notificationService, localStorageService) {
21
+ this.dataService = dataService;
22
+ this.notificationService = notificationService;
23
+ this.localStorageService = localStorageService;
24
+ this.serverPath = (0, core_2.getServerLocation)();
25
+ }
26
+ async getCustomFields(productIds) {
27
+ return fetch(`${this.serverPath}/product-export/custom-fields`, {
28
+ method: 'POST',
29
+ headers: {
30
+ 'Content-Type': 'application/json',
31
+ ...this.getHeaders(),
32
+ },
33
+ body: JSON.stringify(productIds),
34
+ })
35
+ .then((res) => res.json())
36
+ .then((data) => {
37
+ return data.map((field) => field.name);
38
+ });
39
+ }
40
+ async getConfig() {
41
+ return fetch(`${this.serverPath}/product-export/config`, {
42
+ method: 'GET',
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ ...this.getHeaders(),
46
+ },
47
+ })
48
+ .then((res) => res.json())
49
+ .then((data) => data);
50
+ }
51
+ async exportProducts(selection, fileName, customFields, exportAssetsAs) {
52
+ this.notificationService.info(`Exporting ${selection.length} products to ${fileName}.csv`);
53
+ const productIds = selection.map((product) => product.id);
54
+ try {
55
+ const res = await fetch(`${this.serverPath}/product-export/export?fileName=${fileName}&customFields=${customFields}&exportAssetsAs=${exportAssetsAs}`, {
56
+ method: 'POST',
57
+ headers: {
58
+ 'Content-Type': 'application/json',
59
+ ...this.getHeaders(),
60
+ },
61
+ body: JSON.stringify(productIds),
62
+ });
63
+ if (!res.ok) {
64
+ const json = await res.json();
65
+ throw Error(json?.message);
66
+ }
67
+ const header = res.headers.get('Content-Disposition');
68
+ const parts = header.split(';');
69
+ const filename = parts[1].split('=')[1];
70
+ const blob = await res.blob();
71
+ await this.downloadBlob(blob, filename);
72
+ }
73
+ catch (err) {
74
+ console.error(err);
75
+ this.notificationService.error(err.message);
76
+ }
77
+ }
78
+ getHeaders() {
79
+ const headers = {};
80
+ const channelToken = this.localStorageService.get('activeChannelToken');
81
+ if (channelToken) {
82
+ headers['vendure-token'] = channelToken;
83
+ }
84
+ const authToken = this.localStorageService.get('authToken');
85
+ if (authToken) {
86
+ headers.authorization = `Bearer ${authToken}`;
87
+ }
88
+ return headers;
89
+ }
90
+ async downloadBlob(blob, fileName) {
91
+ const blobUrl = window.URL.createObjectURL(blob);
92
+ const a = document.createElement('a');
93
+ document.body.appendChild(a);
94
+ a.setAttribute('hidden', 'true');
95
+ a.href = blobUrl;
96
+ a.download = fileName;
97
+ a.setAttribute('target', '_blank');
98
+ a.click();
99
+ }
100
+ };
101
+ ProductExportService = __decorate([
102
+ (0, core_1.Injectable)(),
103
+ __metadata("design:paramtypes", [core_2.DataService,
104
+ core_2.NotificationService,
105
+ core_2.LocalStorageService])
106
+ ], ProductExportService);
107
+ exports.ProductExportService = ProductExportService;
@@ -0,0 +1,106 @@
1
+ import { Injectable } from '@angular/core'
2
+ import {
3
+ DataService,
4
+ LocalStorageService,
5
+ NotificationService,
6
+ getServerLocation,
7
+ } from '@vendure/admin-ui/core'
8
+ import { Product } from '@vendure/core'
9
+
10
+ @Injectable()
11
+ export class ProductExportService {
12
+ serverPath: string
13
+ constructor(
14
+ protected dataService: DataService,
15
+ private notificationService: NotificationService,
16
+ private localStorageService: LocalStorageService,
17
+ ) {
18
+ this.serverPath = getServerLocation()
19
+ }
20
+
21
+ async getCustomFields(productIds: string[]) {
22
+ return fetch(`${this.serverPath}/product-export/custom-fields`, {
23
+ method: 'POST',
24
+ headers: {
25
+ 'Content-Type': 'application/json',
26
+ ...this.getHeaders(),
27
+ },
28
+ body: JSON.stringify(productIds),
29
+ })
30
+ .then((res) => res.json())
31
+ .then((data: { name: string; type: string }[]) => {
32
+ return data.map((field) => field.name)
33
+ })
34
+ }
35
+
36
+ async getConfig() {
37
+ return fetch(`${this.serverPath}/product-export/config`, {
38
+ method: 'GET',
39
+ headers: {
40
+ 'Content-Type': 'application/json',
41
+ ...this.getHeaders(),
42
+ },
43
+ })
44
+ .then((res) => res.json())
45
+ .then((data) => data)
46
+ }
47
+
48
+ async exportProducts(
49
+ selection: Product[],
50
+ fileName?: string,
51
+ customFields?: string[],
52
+ exportAssetsAs?: 'url' | 'json',
53
+ ) {
54
+ this.notificationService.info(`Exporting ${selection.length} products to ${fileName}.csv`)
55
+ const productIds = selection.map((product) => product.id)
56
+ try {
57
+ const res = await fetch(
58
+ `${this.serverPath}/product-export/export?fileName=${fileName}&customFields=${customFields}&exportAssetsAs=${exportAssetsAs}`,
59
+ {
60
+ method: 'POST',
61
+ headers: {
62
+ 'Content-Type': 'application/json',
63
+ ...this.getHeaders(),
64
+ },
65
+ body: JSON.stringify(productIds),
66
+ },
67
+ )
68
+ if (!res.ok) {
69
+ const json = await res.json()
70
+ throw Error(json?.message)
71
+ }
72
+ const header = res.headers.get('Content-Disposition')
73
+ const parts = header!.split(';')
74
+ const filename = parts[1].split('=')[1]
75
+ const blob = await res.blob()
76
+ await this.downloadBlob(blob, filename)
77
+ } catch (err: any) {
78
+ console.error(err)
79
+ this.notificationService.error(err.message)
80
+ }
81
+ }
82
+
83
+ private getHeaders(): Record<string, string> {
84
+ const headers: Record<string, string> = {}
85
+ const channelToken = this.localStorageService.get('activeChannelToken')
86
+ if (channelToken) {
87
+ headers['vendure-token'] = channelToken
88
+ }
89
+ const authToken = this.localStorageService.get('authToken')
90
+ if (authToken) {
91
+ headers.authorization = `Bearer ${authToken}`
92
+ }
93
+ return headers
94
+ }
95
+
96
+ private async downloadBlob(blob: Blob, fileName: string): Promise<void> {
97
+ const blobUrl = window.URL.createObjectURL(blob)
98
+ const a = document.createElement('a')
99
+ document.body.appendChild(a)
100
+ a.setAttribute('hidden', 'true')
101
+ a.href = blobUrl
102
+ a.download = fileName
103
+ a.setAttribute('target', '_blank')
104
+ a.click()
105
+ }
106
+ }
@@ -0,0 +1,3 @@
1
+ import { ProductExportService } from './product-export.service';
2
+ declare const _default: (typeof ProductExportService | import("@angular/core").FactoryProvider)[];
3
+ export default _default;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@vendure/admin-ui/core");
4
+ const product_export_service_1 = require("./product-export.service");
5
+ const export_dialog_component_1 = require("./export-dialog.component");
6
+ const ngx_translate_extract_marker_1 = require("@biesbjerg/ngx-translate-extract-marker");
7
+ exports.default = [
8
+ (0, core_1.registerBulkAction)({
9
+ location: 'product-list',
10
+ label: (0, ngx_translate_extract_marker_1.marker)('product-export.action'),
11
+ icon: 'export',
12
+ onClick: ({ injector, selection }) => {
13
+ const modalService = injector.get(core_1.ModalService);
14
+ const productExportService = injector.get(product_export_service_1.ProductExportService);
15
+ const productIds = selection.map((product) => product.id);
16
+ const promises = [
17
+ productExportService.getCustomFields(productIds),
18
+ productExportService.getConfig(),
19
+ ];
20
+ Promise.all(promises).then(([customFields, config]) => {
21
+ modalService
22
+ .fromComponent(export_dialog_component_1.ExportDialogComponent, {
23
+ size: 'md',
24
+ locals: {
25
+ selection,
26
+ customFields,
27
+ config,
28
+ },
29
+ closable: true,
30
+ })
31
+ .subscribe((response) => {
32
+ if (response?.result) {
33
+ productExportService.exportProducts(selection, response.fileName, response.selectedFields, response.exportAssetsAs);
34
+ }
35
+ });
36
+ });
37
+ },
38
+ }),
39
+ product_export_service_1.ProductExportService,
40
+ ];
@@ -0,0 +1,47 @@
1
+ import { ModalService, registerBulkAction } from '@vendure/admin-ui/core'
2
+ import { ProductExportService } from './product-export.service'
3
+ import { ExportDialogComponent } from './export-dialog.component'
4
+ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
5
+
6
+ export default [
7
+ registerBulkAction({
8
+ location: 'product-list',
9
+ label: _('product-export.action'),
10
+ icon: 'export',
11
+ onClick: ({ injector, selection }) => {
12
+ const modalService = injector.get(ModalService)
13
+ const productExportService = injector.get(ProductExportService)
14
+
15
+ const productIds = selection.map((product) => product.id)
16
+
17
+ const promises = [
18
+ productExportService.getCustomFields(productIds),
19
+ productExportService.getConfig(),
20
+ ]
21
+
22
+ Promise.all(promises).then(([customFields, config]) => {
23
+ modalService
24
+ .fromComponent(ExportDialogComponent, {
25
+ size: 'md',
26
+ locals: {
27
+ selection,
28
+ customFields,
29
+ config,
30
+ },
31
+ closable: true,
32
+ })
33
+ .subscribe((response) => {
34
+ if (response?.result) {
35
+ productExportService.exportProducts(
36
+ selection,
37
+ response.fileName,
38
+ response.selectedFields,
39
+ response.exportAssetsAs,
40
+ )
41
+ }
42
+ })
43
+ })
44
+ },
45
+ }),
46
+ ProductExportService,
47
+ ]
@@ -0,0 +1,2 @@
1
+ declare const _default: never[];
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = [
4
+ // Add your custom routes here
5
+ ];
@@ -0,0 +1,3 @@
1
+ export default [
2
+ // Add your custom routes here
3
+ ];
@@ -0,0 +1,17 @@
1
+ {
2
+ "product-export": {
3
+ "action": "Export products to CSV",
4
+ "dialog": {
5
+ "title": "Export products to CSV",
6
+ "label": "File name (optional)",
7
+ "export": "Export",
8
+ "cancel": "Cancel",
9
+ "customFieldsLabel": "Custom fields to include in the export",
10
+ "exportAssetsAs": "Export assets as: ",
11
+ "exportAssetsAsOptions": {
12
+ "url": "URL",
13
+ "json": "JSON"
14
+ }
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "product-export": {
3
+ "action": "Exportera produkter till CSV",
4
+ "dialog": {
5
+ "title": "Exportera produkter till CSV",
6
+ "label": "Filnamn (valfritt)",
7
+ "export": "Exportera",
8
+ "cancel": "Avbryt",
9
+ "customFieldsLabel": "Anpassade fält som ska inkluderas i exporten",
10
+ "exportAssetsAs": "Exportera filer som: ",
11
+ "exportAssetsAsOptions": {
12
+ "url": "URL",
13
+ "json": "JSON"
14
+ }
15
+ }
16
+ }
17
+ }
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@haus-tech/product-export-plugin",
3
+ "version": "2.2.0",
4
+ "description": "A Vendure plugin for importing products from a CSV file",
5
+ "author": "Haus Tech",
6
+ "repository": "https://github.com/WeAreHausTech/haus-tech-vendure-plugins",
7
+ "license": "MIT",
8
+ "engines": {
9
+ "node": ">=20.0.0"
10
+ },
11
+ "main": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "files": [
14
+ "dist",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "start": "yarn ts-node test/dev-server.ts",
19
+ "build": "yarn run -T rimraf dist && yarn run -T tsc && yarn run -T copyfiles -u 1 'src/ui/**/*' dist/",
20
+ "test": "jest --preset=\"ts-jest\"",
21
+ "prepublishOnly": "yarn && yarn build"
22
+ }
23
+ }