@isoftdata/svelte-ecommerce 1.0.0-beta.4 → 1.0.0-beta.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.
- package/README.md +56 -56
- package/dist/EcommerceCategoryMapConfiguration.svelte +251 -251
- package/dist/EcommerceConditionMapConfiguration.svelte +192 -192
- package/dist/EcommerceConfiguration.svelte +204 -199
- package/dist/EcommerceDefaults.svelte +388 -352
- package/dist/EcommerceListingDetails.svelte +854 -852
- package/dist/EcommercePartTypeConfig.svelte +283 -277
- package/dist/EcommerceStoreConfiguration.svelte +251 -251
- package/dist/PolicyList.svelte +53 -54
- package/dist/helpers/listing.d.ts +19 -1
- package/dist/helpers/listing.js +112 -16
- package/dist/utils.d.ts +2 -1
- package/package.json +14 -13
package/dist/helpers/listing.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { convertAndApplyTemplate, validateInventoryListingDetails } from './index.js';
|
|
2
|
+
import financialNumber from 'financial-number';
|
|
2
3
|
import { klona } from 'klona';
|
|
3
4
|
const newInventoryListingDetailTemplate = Object.freeze({
|
|
4
5
|
active: false,
|
|
@@ -79,9 +80,7 @@ export const buildEbayListing = (buildEbayListingInput) => {
|
|
|
79
80
|
return;
|
|
80
81
|
}
|
|
81
82
|
// Check if part type + category configuration is inactive
|
|
82
|
-
|
|
83
|
-
return; // Part type configuration set to inactive, do not create listing row
|
|
84
|
-
}
|
|
83
|
+
const { shouldSkip, reason } = shouldSkipListingDueToInactiveConfiguration(inventoryRow, inventoryTypeListingDefaults);
|
|
85
84
|
// TODO: consider the possibility of an override flag on the listing details
|
|
86
85
|
// Pull out the various levels of defaults, if they exist
|
|
87
86
|
const globalDefaults = ecommercePartnerConfiguration.defaults?.global;
|
|
@@ -110,10 +109,10 @@ export const buildEbayListing = (buildEbayListingInput) => {
|
|
|
110
109
|
}
|
|
111
110
|
// Static
|
|
112
111
|
listingDetails.ecommercePartnerId = ecommercePartnerId;
|
|
113
|
-
listingDetails.partnerSpecificDetails.merchantLocationKey
|
|
112
|
+
listingDetails.partnerSpecificDetails.merchantLocationKey =
|
|
114
113
|
listingDetails.partnerSpecificDetails.merchantLocationKey ??
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
matchingStoreConfig?.merchantLocationKey ??
|
|
115
|
+
null;
|
|
117
116
|
// Mapped
|
|
118
117
|
listingDetails.ecommerceCategoryId = listingDetails.ecommerceCategoryId
|
|
119
118
|
? listingDetails.ecommerceCategoryId
|
|
@@ -124,13 +123,21 @@ export const buildEbayListing = (buildEbayListingInput) => {
|
|
|
124
123
|
listingDetails.inventoryId = inventoryRow.inventoryId;
|
|
125
124
|
listingDetails.storeId = listingDetails.storeId || inventoryRow.storeId;
|
|
126
125
|
listingDetails.sku = listingDetails.sku || inventoryRow.tagNumber;
|
|
127
|
-
listingDetails.price = listingDetails.price || inventoryRow.retailPrice || null
|
|
126
|
+
//listingDetails.price = listingDetails.price || inventoryRow.retailPrice || null
|
|
128
127
|
listingDetails.upc = listingDetails.upc ?? inventoryRow.upc ?? null;
|
|
129
128
|
listingDetails.weight = listingDetails.weight ?? inventoryRow.weight ?? null;
|
|
130
129
|
listingDetails.shippingHeight = listingDetails.shippingHeight ?? inventoryRow.shippingHeight ?? null;
|
|
131
130
|
listingDetails.shippingLength = listingDetails.shippingLength ?? inventoryRow.shippingLength ?? null;
|
|
132
131
|
listingDetails.shippingWidth = listingDetails.shippingWidth ?? inventoryRow.shippingWidth ?? null;
|
|
133
132
|
listingDetails.inventoryDescription = listingDetails.inventoryDescription ?? inventoryRow.description ?? null;
|
|
133
|
+
// Calculate price adjustments, if appropriate
|
|
134
|
+
listingDetails.price = computeAdjustedPrice({
|
|
135
|
+
existingListingPrice: listingDetails.price,
|
|
136
|
+
partTypeDefaults,
|
|
137
|
+
storeDefaults,
|
|
138
|
+
globalDefaults,
|
|
139
|
+
inventoryRow
|
|
140
|
+
});
|
|
134
141
|
// 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)
|
|
135
142
|
// TODO: I suppose we could've made this easier by also having a defaults-shaped stringified json column on inventorylistingdetail
|
|
136
143
|
listingDetails.ecommerceConditionId = getFirstValidValue(listingDetails.ecommerceConditionId, partTypeDefaults?.conditionId, storeDefaults?.conditionId, globalDefaults?.conditionId);
|
|
@@ -160,8 +167,16 @@ export const buildEbayListing = (buildEbayListingInput) => {
|
|
|
160
167
|
}
|
|
161
168
|
// With all known fields set, validate for errors
|
|
162
169
|
const validationErrors = validateInventoryListingDetails(listingDetails);
|
|
163
|
-
// Set active, status and message props
|
|
164
|
-
|
|
170
|
+
if (shouldSkip && reason) { // Set active, status and message props if matching configuration set to inActive
|
|
171
|
+
listingDetails.listingStatus = 'cancelled';
|
|
172
|
+
const errObj = {
|
|
173
|
+
type: 'inactive',
|
|
174
|
+
messages: [reason]
|
|
175
|
+
};
|
|
176
|
+
listingDetails.message = [errObj];
|
|
177
|
+
listingDetails.active = false;
|
|
178
|
+
}
|
|
179
|
+
else if (validationErrors.length) { // Set active, status and message props depending on validation result
|
|
165
180
|
listingDetails.listingStatus = 'error';
|
|
166
181
|
const errObj = {
|
|
167
182
|
type: 'validation',
|
|
@@ -180,6 +195,66 @@ export const buildEbayListing = (buildEbayListingInput) => {
|
|
|
180
195
|
// TODO: In the old func there's an explicit reassignment for convertedListingDetails but shouldn't matter if we're cloning?
|
|
181
196
|
return listingDetails;
|
|
182
197
|
};
|
|
198
|
+
/**
|
|
199
|
+
* Maps PriceLevel string to the corresponding inventory price field
|
|
200
|
+
*/
|
|
201
|
+
const getPriceFromInventory = (priceType, inventoryRow) => {
|
|
202
|
+
switch (priceType) {
|
|
203
|
+
case 'Retail':
|
|
204
|
+
return inventoryRow.retailPrice;
|
|
205
|
+
case 'Wholesale':
|
|
206
|
+
return inventoryRow.wholesalePrice;
|
|
207
|
+
case 'Cost':
|
|
208
|
+
return inventoryRow.cost;
|
|
209
|
+
case 'List':
|
|
210
|
+
return inventoryRow.listPrice;
|
|
211
|
+
default:
|
|
212
|
+
return inventoryRow.retailPrice;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* Computes the adjusted price for a listing based on the following priority:
|
|
217
|
+
* 1. If existingListingPrice is set (not null/undefined/0), return it as-is (user has manually adjusted)
|
|
218
|
+
* 2. Otherwise, determine base price from defaultPriceType in priority order:
|
|
219
|
+
* - partTypeDefaults.defaultPriceType
|
|
220
|
+
* - storeDefaults.defaultPriceType
|
|
221
|
+
* - globalDefaults.defaultPriceType
|
|
222
|
+
* - Ultimate fallback: inventoryRow.retailPrice
|
|
223
|
+
* 3. Apply pricingModifier (with same priority fallback, default 100 = no change)
|
|
224
|
+
* 4. Formula: basePrice * (pricingModifier / 100)
|
|
225
|
+
*/
|
|
226
|
+
export const computeAdjustedPrice = ({ existingListingPrice, partTypeDefaults, storeDefaults, globalDefaults, inventoryRow, }) => {
|
|
227
|
+
// Step 1: If existing listing price is set (not null/undefined/0), return it as-is
|
|
228
|
+
// User has manually adjusted the price, so don't apply any modifiers
|
|
229
|
+
if (existingListingPrice != null && existingListingPrice !== 0) {
|
|
230
|
+
return existingListingPrice;
|
|
231
|
+
}
|
|
232
|
+
// Step 2: Determine the base price to use from inventory
|
|
233
|
+
// Check for defaultPriceType in priority order
|
|
234
|
+
const priceType = getFirstValidValue(partTypeDefaults?.defaultPriceType, storeDefaults?.defaultPriceType, globalDefaults?.defaultPriceType);
|
|
235
|
+
// Get price from inventory based on the price type
|
|
236
|
+
let basePrice = getPriceFromInventory(priceType, inventoryRow);
|
|
237
|
+
// Ultimate fallback to retailPrice if basePrice is still null
|
|
238
|
+
if (basePrice == null) {
|
|
239
|
+
basePrice = inventoryRow.retailPrice;
|
|
240
|
+
}
|
|
241
|
+
// If we still don't have a price, return retailPrice as is
|
|
242
|
+
if (basePrice == null) {
|
|
243
|
+
return inventoryRow.retailPrice;
|
|
244
|
+
}
|
|
245
|
+
// Step 3: Determine the pricing modifier with priority fallback
|
|
246
|
+
const pricingModifier = partTypeDefaults?.pricingModifier ??
|
|
247
|
+
storeDefaults?.pricingModifier ??
|
|
248
|
+
globalDefaults?.pricingModifier ??
|
|
249
|
+
100;
|
|
250
|
+
// Step 4: Calculate adjusted price
|
|
251
|
+
// Formula: basePrice * (pricingModifier / 100)
|
|
252
|
+
// Using financialNumber for precision
|
|
253
|
+
const adjustedPriceStr = financialNumber(basePrice.toString())
|
|
254
|
+
.times(financialNumber(pricingModifier.toString()).times('0.01'))
|
|
255
|
+
.toString(2);
|
|
256
|
+
return parseFloat(adjustedPriceStr);
|
|
257
|
+
};
|
|
183
258
|
const getFirstValidArray = (...arrays) => {
|
|
184
259
|
return arrays.find(arr => arr != null && arr.length > 0) ?? [];
|
|
185
260
|
};
|
|
@@ -291,32 +366,53 @@ function findMatchingPartTypeConfig(inventoryRow, inventoryTypeListingDetails) {
|
|
|
291
366
|
}
|
|
292
367
|
return inventoryTypeListingDetails.find(config => config.categoryName === '');
|
|
293
368
|
}
|
|
369
|
+
// TODO: this needs a unit test
|
|
294
370
|
// Needed a separate method to check what I think are all of the ways we'd need to check
|
|
295
371
|
// part type
|
|
296
372
|
function shouldSkipListingDueToInactiveConfiguration(inventoryRow, inventoryTypeListingDetails) {
|
|
297
373
|
// Early return if no configurations to check
|
|
298
374
|
if (!inventoryTypeListingDetails || inventoryTypeListingDetails.length === 0) {
|
|
299
|
-
return false; // No restrictions, continue processing
|
|
375
|
+
return { shouldSkip: false }; // No restrictions, continue processing
|
|
300
376
|
}
|
|
301
377
|
// Early return if inventory has no category
|
|
302
378
|
if (!inventoryRow.category) {
|
|
303
|
-
return
|
|
379
|
+
return { shouldSkip: false }; // No category to match against, continue processing
|
|
304
380
|
}
|
|
305
381
|
// Scenario 1: Single config with no category (applies to all categories)
|
|
306
382
|
if (inventoryTypeListingDetails.length === 1 && !inventoryTypeListingDetails[0].categoryId) {
|
|
307
|
-
|
|
383
|
+
const isInactive = !inventoryTypeListingDetails[0].active;
|
|
384
|
+
return {
|
|
385
|
+
shouldSkip: isInactive,
|
|
386
|
+
reason: isInactive
|
|
387
|
+
? 'Matching single inventory type configuration with no category is set to inactive'
|
|
388
|
+
: undefined,
|
|
389
|
+
};
|
|
308
390
|
}
|
|
309
391
|
// Scenario 2 & 3: Multiple configs or single config with category
|
|
310
392
|
// Try to find exact category match first
|
|
311
393
|
const categoryMatch = inventoryTypeListingDetails.find(config => config.categoryName === inventoryRow.category);
|
|
312
394
|
if (categoryMatch) {
|
|
313
|
-
|
|
395
|
+
const isInactive = !categoryMatch.active;
|
|
396
|
+
return {
|
|
397
|
+
shouldSkip: isInactive,
|
|
398
|
+
reason: isInactive
|
|
399
|
+
? `Inventory type configuration for category "${inventoryRow.category}" is set to inactive`
|
|
400
|
+
: undefined,
|
|
401
|
+
};
|
|
314
402
|
}
|
|
403
|
+
// TODO: I'm the least certain on this one
|
|
404
|
+
// We've gotten this far, now check if there's an inventorytype with an empty categoryid and it's set to inactive
|
|
315
405
|
// No exact category match - check if there's a "no category" fallback
|
|
316
406
|
const noCategoryConfig = inventoryTypeListingDetails.find(config => !config.categoryId);
|
|
317
407
|
if (noCategoryConfig) {
|
|
318
|
-
|
|
408
|
+
const isInactive = !noCategoryConfig.active;
|
|
409
|
+
return {
|
|
410
|
+
shouldSkip: isInactive,
|
|
411
|
+
reason: isInactive
|
|
412
|
+
? 'Override inventory type configuration (no category) is set to inactive'
|
|
413
|
+
: undefined,
|
|
414
|
+
};
|
|
319
415
|
}
|
|
320
|
-
// No matching configuration found, continue processing
|
|
321
|
-
return false;
|
|
416
|
+
// No matching configuration found that is explicitly set to inactive, continue processing
|
|
417
|
+
return { shouldSkip: false };
|
|
322
418
|
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -90,7 +90,8 @@ export type EcommerceSharedDefaults = {
|
|
|
90
90
|
oemNumberMapping?: string;
|
|
91
91
|
packageType?: string;
|
|
92
92
|
paymentPolicies?: Array<string>;
|
|
93
|
-
pricingModifier
|
|
93
|
+
pricingModifier: number;
|
|
94
|
+
defaultPriceType: string | null;
|
|
94
95
|
returnPolicies?: Array<string>;
|
|
95
96
|
shippingLengthUnit?: string;
|
|
96
97
|
shippingWeightUnit?: string;
|
package/package.json
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isoftdata/svelte-ecommerce",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
4
|
-
"scripts": {
|
|
5
|
-
"dev": "vite dev",
|
|
6
|
-
"build": "vite build && npm run prepack",
|
|
7
|
-
"preview": "vite preview",
|
|
8
|
-
"prepare": "svelte-kit sync || echo ''",
|
|
9
|
-
"prepack": "svelte-kit sync && svelte-package && publint",
|
|
10
|
-
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
11
|
-
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
12
|
-
"lint": "eslint . && prettier --check .",
|
|
13
|
-
"format": "prettier --write ."
|
|
14
|
-
},
|
|
3
|
+
"version": "1.0.0-beta.6",
|
|
15
4
|
"files": [
|
|
16
5
|
"dist",
|
|
17
6
|
"!dist/**/*.test.*",
|
|
@@ -57,6 +46,7 @@
|
|
|
57
46
|
"publint": "^0.3.2",
|
|
58
47
|
"svelte": "^5.0.0",
|
|
59
48
|
"svelte-check": "^4.0.0",
|
|
49
|
+
"tsx": "^4.21.0",
|
|
60
50
|
"typescript": "^5.0.0",
|
|
61
51
|
"typescript-eslint": "^8.44.1",
|
|
62
52
|
"vite": "^6.0.0"
|
|
@@ -81,8 +71,19 @@
|
|
|
81
71
|
"@isoftdata/utility-string": "^2.2.0",
|
|
82
72
|
"@lukeed/uuid": "2.0.1",
|
|
83
73
|
"camelcase": "^9.0.0",
|
|
74
|
+
"financial-number": "^5.0.0",
|
|
84
75
|
"klona": "^2.0.6",
|
|
85
76
|
"svelte": "^5.23.2",
|
|
86
77
|
"to-title-case": "^1.0.0"
|
|
78
|
+
},
|
|
79
|
+
"scripts": {
|
|
80
|
+
"dev": "vite dev",
|
|
81
|
+
"build": "vite build && npm run prepack",
|
|
82
|
+
"preview": "vite preview",
|
|
83
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
84
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
85
|
+
"lint": "eslint . && prettier --check .",
|
|
86
|
+
"format": "prettier --write .",
|
|
87
|
+
"test": "node --import tsx --test src/lib/helpers/listing.test.ts"
|
|
87
88
|
}
|
|
88
|
-
}
|
|
89
|
+
}
|