@isoftdata/svelte-ecommerce 1.0.0-beta.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.
Files changed (38) hide show
  1. package/README.md +58 -0
  2. package/dist/EcommerceCategoryMapConfiguration.svelte +266 -0
  3. package/dist/EcommerceCategoryMapConfiguration.svelte.d.ts +14 -0
  4. package/dist/EcommerceConditionMapConfiguration.svelte +214 -0
  5. package/dist/EcommerceConditionMapConfiguration.svelte.d.ts +10 -0
  6. package/dist/EcommerceConfiguration.svelte +195 -0
  7. package/dist/EcommerceConfiguration.svelte.d.ts +22 -0
  8. package/dist/EcommerceDefaults.svelte +357 -0
  9. package/dist/EcommerceDefaults.svelte.d.ts +12 -0
  10. package/dist/EcommerceListingDetails.svelte +986 -0
  11. package/dist/EcommerceListingDetails.svelte.d.ts +18 -0
  12. package/dist/EcommercePartTypeConfig.svelte +305 -0
  13. package/dist/EcommercePartTypeConfig.svelte.d.ts +15 -0
  14. package/dist/EcommerceStoreConfiguration.svelte +263 -0
  15. package/dist/EcommerceStoreConfiguration.svelte.d.ts +13 -0
  16. package/dist/PolicyList.svelte +66 -0
  17. package/dist/PolicyList.svelte.d.ts +10 -0
  18. package/dist/data/ebay.d.ts +11 -0
  19. package/dist/data/ebay.js +31 -0
  20. package/dist/data/htp.d.ts +9 -0
  21. package/dist/data/htp.js +22 -0
  22. package/dist/data/index.d.ts +4 -0
  23. package/dist/data/index.js +6 -0
  24. package/dist/data/types.d.ts +4 -0
  25. package/dist/data/types.js +1 -0
  26. package/dist/helpers/index.d.ts +3 -0
  27. package/dist/helpers/index.js +3 -0
  28. package/dist/helpers/listing.d.ts +22 -0
  29. package/dist/helpers/listing.js +324 -0
  30. package/dist/helpers/template.d.ts +31 -0
  31. package/dist/helpers/template.js +131 -0
  32. package/dist/helpers/validation.d.ts +2 -0
  33. package/dist/helpers/validation.js +91 -0
  34. package/dist/index.d.ts +11 -0
  35. package/dist/index.js +11 -0
  36. package/dist/utils.d.ts +321 -0
  37. package/dist/utils.js +1 -0
  38. package/package.json +66 -0
@@ -0,0 +1,11 @@
1
+ import type { PackageType } from './types.js';
2
+ export type LengthUnit = 'INCH' | 'FEET' | 'CENTIMETER' | 'METER';
3
+ export type WeightUnit = 'POUND' | 'KILOGRAM' | 'OUNCE' | 'GRAM';
4
+ export type TimeUnit = 'YEAR' | 'MONTH' | 'DAY' | 'HOUR' | 'CALENDAR_DAY' | 'BUSINESS_DAY';
5
+ export interface EbayStaticData {
6
+ lengthUnits: LengthUnit[];
7
+ weightUnits: WeightUnit[];
8
+ timeUnits: TimeUnit[];
9
+ packageTypes: PackageType[];
10
+ }
11
+ export declare const ebayStaticData: EbayStaticData;
@@ -0,0 +1,31 @@
1
+ export const ebayStaticData = {
2
+ lengthUnits: ['INCH', 'FEET', 'CENTIMETER', 'METER'],
3
+ weightUnits: ['POUND', 'KILOGRAM', 'OUNCE', 'GRAM'],
4
+ timeUnits: ['YEAR', 'MONTH', 'DAY', 'HOUR', 'CALENDAR_DAY', 'BUSINESS_DAY'],
5
+ packageTypes: [
6
+ {
7
+ name: 'Bulky Goods',
8
+ partnerValue: 'BULKY_GOODS',
9
+ },
10
+ {
11
+ name: 'Letter',
12
+ partnerValue: 'LETTER',
13
+ },
14
+ {
15
+ name: 'Mailing Box',
16
+ partnerValue: 'MAILING_BOX',
17
+ },
18
+ {
19
+ name: 'Pallet',
20
+ partnerValue: 'ONE_WAY_PALLET',
21
+ },
22
+ {
23
+ name: 'Parcel or Padded Envelope',
24
+ partnerValue: 'PARCEL_OR_PADDED_ENVELOPE',
25
+ },
26
+ {
27
+ name: 'USPS Flat Rate Envelope',
28
+ partnerValue: 'USPS_FLAT_RATE_ENVELOPE',
29
+ },
30
+ ],
31
+ };
@@ -0,0 +1,9 @@
1
+ import type { PackageType } from './types.js';
2
+ export type LengthUnit = 'in' | 'cm' | 'm' | 'ft';
3
+ export type WeightUnit = 'g' | 'lb' | 'kg' | 'oz';
4
+ export interface HtpStaticData {
5
+ lengthUnits: LengthUnit[];
6
+ weightUnits: WeightUnit[];
7
+ packageTypes: PackageType[];
8
+ }
9
+ export declare const htpStaticData: HtpStaticData;
@@ -0,0 +1,22 @@
1
+ export const htpStaticData = {
2
+ lengthUnits: ['in', 'cm', 'm', 'ft'],
3
+ weightUnits: ['g', 'lb', 'kg', 'oz'],
4
+ packageTypes: [
5
+ {
6
+ name: 'Box',
7
+ partnerValue: 'Box',
8
+ },
9
+ {
10
+ name: 'Envelope',
11
+ partnerValue: 'Envelope',
12
+ },
13
+ {
14
+ name: 'Letter',
15
+ partnerValue: 'Letter',
16
+ },
17
+ {
18
+ name: 'Pallet',
19
+ partnerValue: 'Pallet',
20
+ },
21
+ ]
22
+ };
@@ -0,0 +1,4 @@
1
+ export declare const ecommercePartnerStaticData: {
2
+ htp: import("./htp.js").HtpStaticData;
3
+ ebay: import("./ebay.js").EbayStaticData;
4
+ };
@@ -0,0 +1,6 @@
1
+ import { htpStaticData } from './htp.js';
2
+ import { ebayStaticData } from './ebay.js';
3
+ export const ecommercePartnerStaticData = {
4
+ htp: htpStaticData,
5
+ ebay: ebayStaticData,
6
+ };
@@ -0,0 +1,4 @@
1
+ export interface PackageType {
2
+ name: string;
3
+ partnerValue: string;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export { validateInventoryListingDetails } from './validation.js';
2
+ export { convertAndApplyTemplate, replaceInventoryTokens } from './template.js';
3
+ export { buildEbayListing, processImageUrls } from './listing.js';
@@ -0,0 +1,3 @@
1
+ export { validateInventoryListingDetails } from './validation.js';
2
+ export { convertAndApplyTemplate, replaceInventoryTokens } from './template.js';
3
+ export { buildEbayListing, processImageUrls } from './listing.js';
@@ -0,0 +1,22 @@
1
+ import type { BuildEbayListingInput, FileItem, ImageUrl, InventoryListingDetail, NewInventoryListingDetail } from '../utils.js';
2
+ /**
3
+ * Shared method for creating an inventorylistingdetail row
4
+ * Includes minimum validation and may return undefined if certain configuration settings determine the inventory item doesn't get a listing created
5
+ */
6
+ export declare const buildEbayListing: (buildEbayListingInput: BuildEbayListingInput) => InventoryListingDetail | NewInventoryListingDetail | undefined;
7
+ /**
8
+ * Accepts:
9
+ * the raw string of imageUrls from the listing, should be stringified json
10
+ * file list results from db
11
+ * Returns: stringified imageUrl list that can be tacked right onto the listing object for insert/update
12
+ *
13
+ * This function tries to compare the most recent file list for the inventory record with anything saved on the listing and:
14
+ * Filters for acceptable mime types
15
+ * Adds new items to the url array
16
+ * Updates existing items if rank or name changes
17
+ * Re-sorts to keep rank 1 at "top"
18
+ *
19
+ * TODO: need to make sure a specific type is used for file list so it's sharable
20
+ * TODO: I believe there's a max 12 image, probably have to account for that including returning a message if exceeded?
21
+ */
22
+ export declare function processImageUrls(partFileList: FileItem[]): ImageUrl[] | null;
@@ -0,0 +1,324 @@
1
+ import { convertAndApplyTemplate, validateInventoryListingDetails } from './index.js';
2
+ import klona from 'klona';
3
+ const newInventoryListingDetailTemplate = {
4
+ active: false,
5
+ convertedListingDetails: null,
6
+ duration: 'GTC',
7
+ ecommercePartnerId: 0,
8
+ ecommerceCategoryId: null,
9
+ ecommerceConditionId: null,
10
+ ecommerceConditionDescription: null,
11
+ fulfillmentTime: null,
12
+ fulfillmentTimeUnit: null,
13
+ imageUrls: [],
14
+ inventoryId: 0,
15
+ inventoryDescription: null,
16
+ lastUpdate: '',
17
+ listingDescription: null,
18
+ listingStatus: 'pending',
19
+ listingTitle: null,
20
+ manufacturerPartNumber: null,
21
+ message: [],
22
+ partnerSpecificDetails: {
23
+ brand: '',
24
+ listingFormat: 'FIXED_PRICE',
25
+ fulfillmentPolicy: [],
26
+ marketplaceId: 'EBAY_MOTORS',
27
+ merchantLocationKey: '',
28
+ oemNumber: '',
29
+ paymentPolicy: [],
30
+ returnPolicy: []
31
+ },
32
+ price: null,
33
+ quantity: null,
34
+ shippingLength: null,
35
+ shippingWidth: null,
36
+ shippingHeight: null,
37
+ shippingLengthUnit: null,
38
+ shippingPackage: null,
39
+ sku: null,
40
+ storeId: 0,
41
+ upc: '',
42
+ weight: null,
43
+ weightUnit: null
44
+ };
45
+ /**
46
+ * Shared method for creating an inventorylistingdetail row
47
+ * Includes minimum validation and may return undefined if certain configuration settings determine the inventory item doesn't get a listing created
48
+ */
49
+ export const buildEbayListing = (buildEbayListingInput) => {
50
+ const { categoryMapping, conditionMapping, ecommercePartnerConfiguration, existingListing, inventoryRow, inventoryTypeListingDefaults, inventoryType, partFileList } = buildEbayListingInput;
51
+ if (!ecommercePartnerConfiguration) {
52
+ // No partner config, no listing
53
+ return;
54
+ }
55
+ const ecommercePartnerId = ecommercePartnerConfiguration.ecommercePartnerId;
56
+ // Check various fields/configs to determine if we should go skip creating the listing details
57
+ if (!inventoryRow.isWorldViewable) {
58
+ // Worldviewable is false, do not create listing row
59
+ return;
60
+ }
61
+ // TODO: this is problematic, we need to offer a way out of 'cancelled'
62
+ // Alternatively, build the listing anyway but keep the status?
63
+ if (existingListing && existingListing.listingStatus === 'cancelled') {
64
+ return;
65
+ }
66
+ // Is the ecommerce config set to active?
67
+ if (ecommercePartnerConfiguration && !ecommercePartnerConfiguration.active) {
68
+ // Ecommerce config set to inactive
69
+ return;
70
+ }
71
+ // Is store mapped and active?
72
+ const matchingStoreConfig = ecommercePartnerConfiguration?.defaults?.store?.find(store => store.storeId === inventoryRow.storeId);
73
+ if (!matchingStoreConfig) {
74
+ // No store/location configured, do not create listing row
75
+ return;
76
+ }
77
+ if (matchingStoreConfig && !matchingStoreConfig.active) {
78
+ // Store configured to inactive, do not create listing row
79
+ return;
80
+ }
81
+ // Check if part type + category configuration is inactive
82
+ if (shouldSkipListingDueToInactiveConfiguration(inventoryRow, inventoryTypeListingDefaults)) {
83
+ return; // Part type configuration set to inactive, do not create listing row
84
+ }
85
+ // TODO: consider the possibility of an override flag on the listing details
86
+ // Pull out the various levels of defaults, if they exist
87
+ const globalDefaults = ecommercePartnerConfiguration.defaults?.global;
88
+ const storeDefaults = ecommercePartnerConfiguration.defaults?.store?.find(row => row?.storeId === inventoryRow.storeId)?.defaults;
89
+ const partTypeDefaults = findMatchingPartTypeConfig(inventoryRow, inventoryTypeListingDefaults)?.defaults;
90
+ // Clone existing listing details or template to start
91
+ let listingDetails;
92
+ if (existingListing) {
93
+ listingDetails = klona(existingListing);
94
+ // Also, if partnerSpecificDetails doesn't exist, create it. (Get weird type errors if we don't)
95
+ if (!listingDetails.partnerSpecificDetails || Object.keys(listingDetails.partnerSpecificDetails).length === 0) {
96
+ listingDetails.partnerSpecificDetails = {
97
+ brand: '',
98
+ listingFormat: 'FIXED_PRICE',
99
+ fulfillmentPolicy: [],
100
+ marketplaceId: 'EBAY_MOTORS',
101
+ merchantLocationKey: '',
102
+ oemNumber: '',
103
+ paymentPolicy: [],
104
+ returnPolicy: []
105
+ };
106
+ }
107
+ }
108
+ else {
109
+ listingDetails = klona(newInventoryListingDetailTemplate);
110
+ }
111
+ // Static
112
+ listingDetails.ecommercePartnerId = ecommercePartnerId;
113
+ listingDetails.partnerSpecificDetails.merchantLocationKey ?? listingDetails.partnerSpecificDetails.merchantLocationKey ?? matchingStoreConfig?.merchantLocationKey ?? null;
114
+ // Mapped
115
+ listingDetails.ecommerceCategoryId = listingDetails.ecommerceCategoryId
116
+ ? listingDetails.ecommerceCategoryId
117
+ : (categoryMapping?.ebayCategoryId ?? null);
118
+ // Use latest from inventory
119
+ // TODO: I'll need to check some of these, they may not allow changes on the config screen and inventory.* is the only truth avail
120
+ listingDetails.quantity = inventoryRow.quantity;
121
+ listingDetails.storeId = listingDetails.storeId || inventoryRow.storeId;
122
+ listingDetails.sku = listingDetails.sku || inventoryRow.tagNumber;
123
+ listingDetails.price = listingDetails.price || inventoryRow.retailPrice || null;
124
+ listingDetails.upc = listingDetails.upc ?? inventoryRow.upc ?? null;
125
+ listingDetails.weight = listingDetails.weight ?? inventoryRow.weight ?? null;
126
+ listingDetails.shippingHeight = listingDetails.shippingHeight ?? inventoryRow.shippingHeight ?? null;
127
+ listingDetails.shippingLength = listingDetails.shippingLength ?? inventoryRow.shippingLength ?? null;
128
+ listingDetails.shippingWidth = listingDetails.shippingWidth ?? inventoryRow.shippingWidth ?? null;
129
+ listingDetails.inventoryDescription = listingDetails.inventoryDescription ?? inventoryRow.description ?? null;
130
+ // For this MVP, we'll default to the existing listing info first (this would include any edits on the part config page, then part type, store, finally global or nothing)
131
+ // TODO: I suppose we could've made this easier by also having a defaults-shaped stringified json column on inventorylistingdetail
132
+ listingDetails.ecommerceConditionId = getFirstValidValue(listingDetails.ecommerceConditionId, partTypeDefaults?.conditionId, storeDefaults?.conditionId, globalDefaults?.conditionId);
133
+ listingDetails.ecommerceConditionDescription = getFirstValidValue(listingDetails.ecommerceConditionDescription, partTypeDefaults?.conditionDescription, storeDefaults?.conditionDescription, globalDefaults?.conditionDescription);
134
+ listingDetails.fulfillmentTime = getFirstValidValue(listingDetails.fulfillmentTime, partTypeDefaults?.fulfillmentTimeValue, storeDefaults?.fulfillmentTimeValue, globalDefaults?.fulfillmentTimeValue);
135
+ listingDetails.fulfillmentTimeUnit = getFirstValidValue(listingDetails.fulfillmentTimeUnit, partTypeDefaults?.fulfillmentTimeUnit, storeDefaults?.fulfillmentTimeUnit, globalDefaults?.fulfillmentTimeUnit);
136
+ listingDetails.partnerSpecificDetails.fulfillmentPolicy = getFirstValidArray(listingDetails.partnerSpecificDetails.fulfillmentPolicy, partTypeDefaults?.fulfillmentPolicies, storeDefaults?.fulfillmentPolicies, globalDefaults?.fulfillmentPolicies);
137
+ listingDetails.partnerSpecificDetails.paymentPolicy = getFirstValidArray(listingDetails.partnerSpecificDetails.paymentPolicy, partTypeDefaults?.paymentPolicies, storeDefaults?.paymentPolicies, globalDefaults?.paymentPolicies);
138
+ listingDetails.partnerSpecificDetails.returnPolicy = getFirstValidArray(listingDetails.partnerSpecificDetails.returnPolicy, partTypeDefaults?.returnPolicies, storeDefaults?.returnPolicies, globalDefaults?.returnPolicies);
139
+ listingDetails.shippingPackage = getFirstValidValue(listingDetails.shippingPackage, partTypeDefaults?.packageType, storeDefaults?.packageType, globalDefaults?.packageType);
140
+ listingDetails.weightUnit = getFirstValidValue(listingDetails.weightUnit, partTypeDefaults?.shippingWeightUnit, storeDefaults?.shippingWeightUnit, globalDefaults?.shippingWeightUnit);
141
+ listingDetails.shippingLengthUnit = getFirstValidValue(listingDetails.shippingLengthUnit, partTypeDefaults?.shippingLengthUnit, storeDefaults?.shippingLengthUnit, globalDefaults?.shippingLengthUnit);
142
+ // Handle Templates with same fallback pattern as above
143
+ listingDetails.partnerSpecificDetails.brand = getFirstValidTemplateValue(listingDetails.partnerSpecificDetails.brand, partTypeDefaults?.brandMapping, storeDefaults?.brandMapping, globalDefaults?.brandMapping, inventoryRow, inventoryType?.name);
144
+ listingDetails.listingTitle = getFirstValidTemplateValue(listingDetails.listingTitle, partTypeDefaults?.listingTitleTemplate, storeDefaults?.listingTitleTemplate, globalDefaults?.listingTitleTemplate, inventoryRow, inventoryType?.name);
145
+ listingDetails.listingDescription = getFirstValidTemplateValue(listingDetails.listingDescription, partTypeDefaults?.listingDescriptionTemplate, storeDefaults?.listingDescriptionTemplate, globalDefaults?.listingDescriptionTemplate, inventoryRow, inventoryType?.name);
146
+ listingDetails.manufacturerPartNumber = getFirstValidTemplateValue(listingDetails.manufacturerPartNumber, partTypeDefaults?.manufacturerPartNumberMapping, storeDefaults?.manufacturerPartNumberMapping, globalDefaults?.manufacturerPartNumberMapping, inventoryRow, inventoryType?.name);
147
+ listingDetails.partnerSpecificDetails.oemNumber = getFirstValidTemplateValue(listingDetails.partnerSpecificDetails.oemNumber, partTypeDefaults?.oemNumberMapping, storeDefaults?.oemNumberMapping, globalDefaults?.oemNumberMapping, inventoryRow, inventoryType?.name);
148
+ // Process image URLs if partFileList is provided
149
+ if (partFileList && partFileList.length > 0) {
150
+ const processedImageUrls = processImageUrls(partFileList);
151
+ listingDetails.imageUrls = processedImageUrls || [];
152
+ }
153
+ else {
154
+ // Ensure imageUrls is always an array
155
+ listingDetails.imageUrls = listingDetails.imageUrls || [];
156
+ }
157
+ // With all known fields set, validate for errors
158
+ const validationErrors = validateInventoryListingDetails(listingDetails);
159
+ // Set active, status and message props depending on validation result
160
+ if (validationErrors.length) {
161
+ listingDetails.listingStatus = 'error';
162
+ const errObj = {
163
+ type: "validation",
164
+ messages: validationErrors
165
+ };
166
+ listingDetails.message = [errObj];
167
+ listingDetails.active = false;
168
+ }
169
+ else {
170
+ // Clean up old messages
171
+ listingDetails.message = [];
172
+ // set to pending
173
+ listingDetails.active = true;
174
+ listingDetails.listingStatus = 'pending';
175
+ }
176
+ // TODO: In the old func there's an explicit reassignment for convertedListingDetails but shouldn't matter if we're cloning?
177
+ return listingDetails;
178
+ };
179
+ const getFirstValidArray = (...arrays) => {
180
+ return arrays.find(arr => arr != null && arr.length > 0) ?? [];
181
+ };
182
+ /**
183
+ * Returns the first non-null/undefined value from the provided arguments
184
+ */
185
+ const getFirstValidValue = (...values) => {
186
+ return values.find(value => value != null) ?? null;
187
+ };
188
+ /**
189
+ * Returns the first valid template result using the fallback pattern:
190
+ * 1. Use existing value if it exists and is not empty
191
+ * 2. Try templates in order: partType -> store -> global
192
+ * 3. Return null if no valid template produces a result
193
+ */
194
+ const getFirstValidTemplateValue = (existingValue, partTypeTemplate, storeTemplate, globalTemplate, inventoryRow, inventoryTypeName) => {
195
+ // If existing value exists and is not empty, use it
196
+ if (existingValue && existingValue.trim() !== '') {
197
+ return existingValue;
198
+ }
199
+ // Try templates in fallback order
200
+ const templates = [partTypeTemplate, storeTemplate, globalTemplate];
201
+ for (const template of templates) {
202
+ if (template) {
203
+ try {
204
+ const result = convertAndApplyTemplate({
205
+ inventory: inventoryRow,
206
+ inventoryTypeName,
207
+ template
208
+ });
209
+ if (result && result.trim() !== '') {
210
+ return result;
211
+ }
212
+ }
213
+ catch (error) {
214
+ // Log error but continue to next template
215
+ console.warn('Template processing error:', error);
216
+ continue;
217
+ }
218
+ }
219
+ }
220
+ return null;
221
+ };
222
+ /**
223
+ * Accepts:
224
+ * the raw string of imageUrls from the listing, should be stringified json
225
+ * file list results from db
226
+ * Returns: stringified imageUrl list that can be tacked right onto the listing object for insert/update
227
+ *
228
+ * This function tries to compare the most recent file list for the inventory record with anything saved on the listing and:
229
+ * Filters for acceptable mime types
230
+ * Adds new items to the url array
231
+ * Updates existing items if rank or name changes
232
+ * Re-sorts to keep rank 1 at "top"
233
+ *
234
+ * TODO: need to make sure a specific type is used for file list so it's sharable
235
+ * TODO: I believe there's a max 12 image, probably have to account for that including returning a message if exceeded?
236
+ */
237
+ export function processImageUrls(partFileList) {
238
+ // Accepted image MIME types for eBay picture service
239
+ const acceptedMimeTypes = [
240
+ 'image/avif',
241
+ 'image/bmp',
242
+ 'image/gif',
243
+ 'image/heic',
244
+ 'image/jpeg',
245
+ 'image/jpg',
246
+ 'image/png',
247
+ 'image/tiff',
248
+ 'image/tif',
249
+ 'image/webp'
250
+ ];
251
+ // Create a new array that will only contain valid files
252
+ const updatedImageUrlArray = [];
253
+ // Process each file in the most recent partFileList results for an inventory item
254
+ for (const file of partFileList) {
255
+ // Check if the file has an accepted mimeType
256
+ if (!acceptedMimeTypes.includes(file.mimeType.toLowerCase())) {
257
+ continue; // Skip files with unsupported mimeTypes
258
+ }
259
+ // Check if the file is public (skip if public is false)
260
+ if (file.public === false) {
261
+ continue; // Skip files that are not public (removes them from existing list)
262
+ }
263
+ updatedImageUrlArray.push({
264
+ fileId: file.fileId,
265
+ fileName: file.name,
266
+ rank: file.rank ?? 0,
267
+ mimeType: file.mimeType
268
+ });
269
+ }
270
+ // Sort by rank to maintain order, presumably the "top" image in the list of images given to eBay for a listing is shown first
271
+ updatedImageUrlArray.sort((a, b) => a.rank - b.rank);
272
+ // Stringify it so we return it in same format it was received
273
+ return updatedImageUrlArray.length > 0 ? updatedImageUrlArray : null;
274
+ }
275
+ // TODO: pretty much identical to the next method, could probably have just combined and returned two things
276
+ function findMatchingPartTypeConfig(inventoryRow, inventoryTypeListingDetails) {
277
+ // Early return if no configurations to check
278
+ if (!inventoryTypeListingDetails || inventoryTypeListingDetails.length === 0) {
279
+ return;
280
+ }
281
+ // Scenario 1: Single config with no category (applies to all categories)
282
+ if (inventoryTypeListingDetails.length === 1 && !inventoryTypeListingDetails[0].categoryId) {
283
+ return inventoryTypeListingDetails[0]; // Skip if inactive
284
+ }
285
+ const categoryMatch = inventoryTypeListingDetails.find(config => config.categoryName === inventoryRow.category);
286
+ if (categoryMatch) {
287
+ return categoryMatch;
288
+ }
289
+ // TODO: this one's iffy, double-check if types match reality, also make sure we can't have more than one
290
+ const allCategoryMatch = inventoryTypeListingDetails.find(config => config.categoryName === '');
291
+ if (allCategoryMatch) {
292
+ return allCategoryMatch;
293
+ }
294
+ return;
295
+ }
296
+ // Needed a separate method to check what I think are all of the ways we'd need to check
297
+ // part type
298
+ function shouldSkipListingDueToInactiveConfiguration(inventoryRow, inventoryTypeListingDetails) {
299
+ // Early return if no configurations to check
300
+ if (!inventoryTypeListingDetails || inventoryTypeListingDetails.length === 0) {
301
+ return false; // No restrictions, continue processing
302
+ }
303
+ // Early return if inventory has no category
304
+ if (!inventoryRow.category) {
305
+ return true; // No category to match against, continue processing
306
+ }
307
+ // Scenario 1: Single config with no category (applies to all categories)
308
+ if (inventoryTypeListingDetails.length === 1 && !inventoryTypeListingDetails[0].categoryId) {
309
+ return !inventoryTypeListingDetails[0].active; // Skip if inactive
310
+ }
311
+ // Scenario 2 & 3: Multiple configs or single config with category
312
+ // Try to find exact category match first
313
+ const categoryMatch = inventoryTypeListingDetails.find(config => config.categoryName === inventoryRow.category);
314
+ if (categoryMatch) {
315
+ return !categoryMatch.active; // Skip if category match is inactive
316
+ }
317
+ // No exact category match - check if there's a "no category" fallback
318
+ const noCategoryConfig = inventoryTypeListingDetails.find(config => !config.categoryId);
319
+ if (noCategoryConfig) {
320
+ return !noCategoryConfig.active; // Skip if fallback is inactive
321
+ }
322
+ // No matching configuration found, continue processing
323
+ return false;
324
+ }
@@ -0,0 +1,31 @@
1
+ import type { InventoryRow, InventoryDetailsForTemplate } from "../utils.js";
2
+ /**
3
+ *
4
+ * @param template Configured in ecommerce settings
5
+ * @returns a string with template values replaced by inventory values
6
+ * Convert product inventory row to a shared type for the token replacement
7
+ */
8
+ export declare function convertAndApplyTemplate({ inventory, inventoryTypeName, template }: {
9
+ inventory: InventoryRow;
10
+ inventoryTypeName: string | null | undefined;
11
+ template: string | null | undefined;
12
+ }): string | null;
13
+ /**
14
+ * Replaces tokens in a template string with values from an inventory record
15
+ * @param template - The template string containing tokens like "{$PartType}"
16
+ * @param inventoryDetails - The inventory object containing values to replace tokens
17
+ * @returns The processed string with tokens replaced by values
18
+ *
19
+ * @example
20
+ * // Example usage:
21
+ * const template = "This is a {$PartType} made by {$Manufacturer}, model {$AssemblyModel}"
22
+ * const inventoryDetails = {
23
+ * partType: "Engine",
24
+ * manufacturer: "Toyota",
25
+ * model: "2JZ-GTE"
26
+ * }
27
+ * const description = replaceInventoryTokens(template, inventoryDetails)
28
+ * // Result: "This is a Engine made by Toyota, model 2JZ-GTE"
29
+ * https://wikido.isoftdata.com//index.php?title=Ebay_templates
30
+ */
31
+ export declare function replaceInventoryTokens(template: string, inventoryDetails: Partial<InventoryDetailsForTemplate>): string;
@@ -0,0 +1,131 @@
1
+ /**
2
+ *
3
+ * @param template Configured in ecommerce settings
4
+ * @returns a string with template values replaced by inventory values
5
+ * Convert product inventory row to a shared type for the token replacement
6
+ */
7
+ export function convertAndApplyTemplate({ inventory, inventoryTypeName, template }) {
8
+ if (!template)
9
+ return null;
10
+ // Map the inventory object to the InventoryDetails type expected by replaceInventoryTokens
11
+ const mappedInventoryDetails = {
12
+ inventoryId: inventory.inventoryId,
13
+ vehicleMake: inventory.make || undefined,
14
+ vehicleModel: inventory.model || undefined,
15
+ year: inventory.year || undefined,
16
+ description: inventory.description || undefined,
17
+ price: inventory.retailPrice || undefined,
18
+ manufacturer: inventory.partManufacturer || undefined,
19
+ model: inventory.partModel || undefined,
20
+ partType: inventoryTypeName || undefined,
21
+ category: inventory.category || undefined,
22
+ store: inventory.storeId?.toString() || undefined,
23
+ tagNumber: inventory.tagNumber || undefined,
24
+ condition: inventory.condition || undefined,
25
+ flexLabel1: inventory.label1 || undefined,
26
+ flexLabel2: inventory.label2 || undefined,
27
+ flexLabel3: inventory.label3 || undefined,
28
+ flexLabel4: inventory.label4 || undefined,
29
+ flexValue1: inventory.data1 || undefined,
30
+ flexValue2: inventory.data2 || undefined,
31
+ flexValue3: inventory.data3 || undefined,
32
+ flexValue4: inventory.data4 || undefined,
33
+ oemNumber: inventory.oemNumber || undefined,
34
+ side: inventory.side || undefined,
35
+ serialNumber: inventory.serialNumber || undefined,
36
+ weight: inventory.weight || undefined,
37
+ upc: inventory.upc || undefined,
38
+ notes: inventory.notes || undefined,
39
+ quantity: inventory.quantity || undefined,
40
+ partManufacturer: inventory.partManufacturer || undefined,
41
+ partModel: inventory.partModel || undefined,
42
+ };
43
+ // Replace tokens in the template and set the listingDescription
44
+ return replaceInventoryTokens(template, mappedInventoryDetails);
45
+ }
46
+ /**
47
+ * Replaces tokens in a template string with values from an inventory record
48
+ * @param template - The template string containing tokens like "{$PartType}"
49
+ * @param inventoryDetails - The inventory object containing values to replace tokens
50
+ * @returns The processed string with tokens replaced by values
51
+ *
52
+ * @example
53
+ * // Example usage:
54
+ * const template = "This is a {$PartType} made by {$Manufacturer}, model {$AssemblyModel}"
55
+ * const inventoryDetails = {
56
+ * partType: "Engine",
57
+ * manufacturer: "Toyota",
58
+ * model: "2JZ-GTE"
59
+ * }
60
+ * const description = replaceInventoryTokens(template, inventoryDetails)
61
+ * // Result: "This is a Engine made by Toyota, model 2JZ-GTE"
62
+ * https://wikido.isoftdata.com//index.php?title=Ebay_templates
63
+ */
64
+ export function replaceInventoryTokens(template, inventoryDetails) {
65
+ if (!template)
66
+ return '';
67
+ if (!inventoryDetails)
68
+ return template;
69
+ // Define token mappings (token name -> inventory property)
70
+ const generalTokens = {
71
+ '{?InventoryID}': 'inventoryId',
72
+ '{?Make}': 'vehicleMake',
73
+ '{?Model}': 'model',
74
+ '{?Year}': 'year',
75
+ '{?Description}': 'description',
76
+ '{?Price}': 'price',
77
+ '{?Manufacturer}': 'manufacturer',
78
+ '{?AssemblyModel}': 'model',
79
+ '{?PartType}': 'partType',
80
+ '{?Category}': 'category',
81
+ '{?Store}': 'store',
82
+ '{?ProductCode}': 'productCode',
83
+ '{?TagNumber}': 'tagNumber',
84
+ '{?Condition}': 'condition',
85
+ '{?FlexLabel1}': 'flexLabel1',
86
+ '{?FlexLabel2}': 'flexLabel2',
87
+ '{?FlexLabel3}': 'flexLabel3',
88
+ '{?FlexLabel4}': 'flexLabel4',
89
+ '{?FlexValue1}': 'flexValue1',
90
+ '{?FlexValue2}': 'flexValue2',
91
+ '{?FlexValue3}': 'flexValue3',
92
+ '{?FlexValue4}': 'flexValue4',
93
+ '{?OEMNumber}': 'oemNumber',
94
+ '{?Side}': 'side'
95
+ // Add more mappings as needed
96
+ };
97
+ // Pro-specific token mappings
98
+ const proTokens = {
99
+ '{?PartNum}': 'inventoryId'
100
+ };
101
+ // Enterprise-specific token mappings, need to confirm these
102
+ const enterpriseTokenMappings = {
103
+ '{?PartNumber}': 'inventoryId', // not sure on this one, supposed to be vendor part number
104
+ '{?PartManufacturer}': 'partManufacturer',
105
+ '{?PartModel}': 'partModel'
106
+ };
107
+ // Combine all token mappings
108
+ const tokenMappings = {
109
+ ...generalTokens,
110
+ ...proTokens,
111
+ ...enterpriseTokenMappings
112
+ };
113
+ // Replace each token with its corresponding value
114
+ let result = template;
115
+ for (const [token, property] of Object.entries(tokenMappings)) {
116
+ // Create a global regex pattern to replace all occurrences
117
+ const tokenPattern = new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
118
+ // Check if the property exists in inventoryDetails
119
+ if (inventoryDetails[property]) {
120
+ result = result.replace(tokenPattern, String(inventoryDetails[property]));
121
+ }
122
+ else {
123
+ // Replace with empty string if no match found
124
+ result = result.replace(tokenPattern, '');
125
+ }
126
+ }
127
+ // Second pass: replace any remaining tokens that weren't in our mapping with empty strings
128
+ // This handles tokens like {?MissingToken} that don't exist in our tokenMappings
129
+ result = result.replace(/\{\?[^}]+\}/g, '');
130
+ return result;
131
+ }
@@ -0,0 +1,2 @@
1
+ import type { InventoryListingDetail, NewInventoryListingDetail } from '../index.js';
2
+ export declare function validateInventoryListingDetails(listingDetails: InventoryListingDetail | NewInventoryListingDetail): string[];