refactor: Enhance price extraction logic in BOM and export utilities to handle ranges correctly

- Updated `getPriceDisplayFromLinks` and `getPriceDisplayFromLinksAsync` functions to separately extract minimum and maximum prices from price objects, accommodating both single prices and ranges.
- Refactored `generateMarkdownOverview` to improve price display by handling min and max values accurately, ensuring proper formatting in markdown outputs.
- Added checks to return 'N/A' when no valid prices are found, enhancing robustness of pricing display logic.
This commit is contained in:
MunchDev-oss
2026-01-10 03:10:18 -05:00
parent aba0964a59
commit 0c7504b841
2 changed files with 128 additions and 26 deletions

View File

@@ -108,18 +108,45 @@ export const getPriceDisplayFromLinks = (item, targetCurrency = null) => {
const isSingleCurrency = currencies.length === 1; const isSingleCurrency = currencies.length === 1;
// Extract numeric values for min/max calculation // Extract numeric values for min/max calculation
const numericPrices = priceObjects.map(p => { // For overall min: use the minimum values from each price (min from ranges, single prices, etc.)
// For overall max: use the maximum values from each price (max from ranges, single prices, etc.)
const minValues = priceObjects.map(p => {
if (typeof p === 'object' && 'amount' in p) { if (typeof p === 'object' && 'amount' in p) {
const amount = p.amount; const amount = p.amount;
return typeof amount === 'object' && 'min' in amount ? amount.min : (typeof amount === 'number' ? amount : 0); // For ranges, use the min value; for single prices, use the amount
if (typeof amount === 'object' && 'min' in amount) {
return amount.min;
} }
return extractNumericPrice(p); if (typeof amount === 'number') {
return amount;
}
}
const extracted = extractNumericPrice(p);
return extracted != null ? extracted : 0;
}).filter(p => p != null && p > 0); }).filter(p => p != null && p > 0);
if (numericPrices.length === 0) return 'C$0.00'; const maxValues = priceObjects.map(p => {
if (typeof p === 'object' && 'amount' in p) {
const amount = p.amount;
// For ranges, use the max value; for single prices, use the amount
if (typeof amount === 'object' && 'max' in amount) {
return amount.max;
}
if (typeof amount === 'object' && 'min' in amount) {
return amount.min; // If no max, use min (single value range)
}
if (typeof amount === 'number') {
return amount;
}
}
const extracted = extractNumericPrice(p);
return extracted != null ? extracted : 0;
}).filter(p => p != null && p > 0);
const minPrice = Math.min(...numericPrices); if (minValues.length === 0 || maxValues.length === 0) return 'C$0.00';
const maxPrice = Math.max(...numericPrices);
const minPrice = Math.min(...minValues);
const maxPrice = Math.max(...maxValues);
if (minPrice === maxPrice) { if (minPrice === maxPrice) {
// Single price - format with currency from the first link // Single price - format with currency from the first link
@@ -132,17 +159,36 @@ export const getPriceDisplayFromLinks = (item, targetCurrency = null) => {
const currencySymbol = currency === 'CAD' ? 'C$' : currency === 'USD' ? '$' : currency === 'EUR' ? '€' : currency === 'GBP' ? '£' : currency; const currencySymbol = currency === 'CAD' ? 'C$' : currency === 'USD' ? '$' : currency === 'EUR' ? '€' : currency === 'GBP' ? '£' : currency;
return `${currencySymbol}${minPrice.toFixed(2)} - ${currencySymbol}${maxPrice.toFixed(2)}`; return `${currencySymbol}${minPrice.toFixed(2)} - ${currencySymbol}${maxPrice.toFixed(2)}`;
} else { } else {
// Multiple currencies - format each with its currency or target currency // Multiple currencies - find the price objects that contain the overall min and max
const minPriceObj = priceObjects.find(p => { const minPriceObj = priceObjects.find(p => {
const amount = p?.amount; if (typeof p === 'object' && 'amount' in p) {
const val = typeof amount === 'object' && 'min' in amount ? amount.min : (typeof amount === 'number' ? amount : 0); const amount = p.amount;
return val === minPrice; if (typeof amount === 'object' && 'min' in amount) {
}); return amount.min === minPrice;
}
if (typeof amount === 'number') {
return amount === minPrice;
}
}
return extractNumericPrice(p) === minPrice;
}) || priceObjects[0]; // Fallback to first if not found
const maxPriceObj = priceObjects.find(p => { const maxPriceObj = priceObjects.find(p => {
const amount = p?.amount; if (typeof p === 'object' && 'amount' in p) {
const val = typeof amount === 'object' && 'max' in amount ? amount.max : (typeof amount === 'object' && 'min' in amount ? amount.min : (typeof amount === 'number' ? amount : 0)); const amount = p.amount;
return val === maxPrice; if (typeof amount === 'object' && 'max' in amount) {
}); return amount.max === maxPrice;
}
if (typeof amount === 'object' && 'min' in amount) {
return amount.min === maxPrice; // Single value range
}
if (typeof amount === 'number') {
return amount === maxPrice;
}
}
return extractNumericPrice(p) === maxPrice;
}) || priceObjects[priceObjects.length - 1]; // Fallback to last if not found
return `${formatPrice(minPriceObj, targetCurrency || 'CAD')} - ${formatPrice(maxPriceObj, targetCurrency || 'CAD')}`; return `${formatPrice(minPriceObj, targetCurrency || 'CAD')} - ${formatPrice(maxPriceObj, targetCurrency || 'CAD')}`;
} }
} }
@@ -173,18 +219,45 @@ export const getPriceDisplayFromLinksAsync = async (item, targetCurrency = 'CAD'
if (convertedPrices.length === 0) return 'C$0.00'; if (convertedPrices.length === 0) return 'C$0.00';
// Extract numeric values for min/max calculation // Extract numeric values for min/max calculation
const numericPrices = convertedPrices.map(p => { // For overall min: use the minimum values from each price (min from ranges, single prices, etc.)
// For overall max: use the maximum values from each price (max from ranges, single prices, etc.)
const minValues = convertedPrices.map(p => {
if (typeof p === 'object' && 'amount' in p) { if (typeof p === 'object' && 'amount' in p) {
const amount = p.amount; const amount = p.amount;
return typeof amount === 'object' && 'min' in amount ? amount.min : (typeof amount === 'number' ? amount : 0); // For ranges, use the min value; for single prices, use the amount
if (typeof amount === 'object' && 'min' in amount) {
return amount.min;
} }
return extractNumericPrice(p); if (typeof amount === 'number') {
return amount;
}
}
const extracted = extractNumericPrice(p);
return extracted != null ? extracted : 0;
}).filter(p => p != null && p > 0); }).filter(p => p != null && p > 0);
if (numericPrices.length === 0) return 'C$0.00'; const maxValues = convertedPrices.map(p => {
if (typeof p === 'object' && 'amount' in p) {
const amount = p.amount;
// For ranges, use the max value; for single prices, use the amount
if (typeof amount === 'object' && 'max' in amount) {
return amount.max;
}
if (typeof amount === 'object' && 'min' in amount) {
return amount.min; // If no max, use min (single value range)
}
if (typeof amount === 'number') {
return amount;
}
}
const extracted = extractNumericPrice(p);
return extracted != null ? extracted : 0;
}).filter(p => p != null && p > 0);
const minPrice = Math.min(...numericPrices); if (minValues.length === 0 || maxValues.length === 0) return 'C$0.00';
const maxPrice = Math.max(...numericPrices);
const minPrice = Math.min(...minValues);
const maxPrice = Math.max(...maxValues);
if (minPrice === maxPrice) { if (minPrice === maxPrice) {
return await formatPriceWithConversion(convertedPrices[0], targetCurrency, exchangeRates); return await formatPriceWithConversion(convertedPrices[0], targetCurrency, exchangeRates);

View File

@@ -12,11 +12,40 @@ export const generateMarkdownOverview = (config, printedParts, hardwareParts, fi
const getPriceDisplay = (item) => { const getPriceDisplay = (item) => {
if (!item) return 'N/A'; if (!item) return 'N/A';
if (item.links && item.links.length > 0) { if (item.links && item.links.length > 0) {
const prices = item.links.map(link => extractNumericPrice(link.price)).filter(p => p != null && p > 0); // Extract min and max values separately to handle price ranges correctly
if (prices.length === 0) return 'N/A'; const minPrices = item.links.map(link => {
if (prices.length === 1) return formatPrice(prices[0]); if (link.price && typeof link.price === 'object' && 'amount' in link.price) {
const minPrice = Math.min(...prices); const amount = link.price.amount;
const maxPrice = Math.max(...prices); if (typeof amount === 'object' && 'min' in amount) {
return amount.min;
}
if (typeof amount === 'number') {
return amount;
}
}
return extractNumericPrice(link.price);
}).filter(p => p != null && p > 0);
const maxPrices = item.links.map(link => {
if (link.price && typeof link.price === 'object' && 'amount' in link.price) {
const amount = link.price.amount;
if (typeof amount === 'object' && 'max' in amount) {
return amount.max;
}
if (typeof amount === 'object' && 'min' in amount) {
return amount.min; // Single value range
}
if (typeof amount === 'number') {
return amount;
}
}
return extractNumericPrice(link.price);
}).filter(p => p != null && p > 0);
if (minPrices.length === 0 || maxPrices.length === 0) return 'N/A';
const minPrice = Math.min(...minPrices);
const maxPrice = Math.max(...maxPrices);
return minPrice === maxPrice ? formatPrice(minPrice) : `${formatPrice(minPrice)} - ${formatPrice(maxPrice)}`; return minPrice === maxPrice ? formatPrice(minPrice) : `${formatPrice(minPrice)} - ${formatPrice(maxPrice)}`;
} }
return item.price ? formatPrice(item.price) : 'N/A'; return item.price ? formatPrice(item.price) : 'N/A';