softseti-sale-calculator-library
Version:
Sales calculation engine by Softseti
125 lines (104 loc) • 4.14 kB
JavaScript
// src/helpers.js
function findProductInSale(sale, productId) {
return (sale.return_items || []).find(item => item.product_id == productId);
}
function calculatePreviousRefundsTotal(sale, options, currentMemoId) {
let total = 0;
// 1. Get all credit memos from options
const creditMemos = [];
// Handle array format
if (Array.isArray(options.credit_memo)) {
creditMemos.push(...options.credit_memo);
}
// Handle single object format
else if (options.credit_memo && typeof options.credit_memo === 'object') {
creditMemos.push(options.credit_memo);
}
// 2. Sum total_refund from all memos except current
for (const memo of creditMemos) {
if (currentMemoId && memo.id === currentMemoId) continue;
total += parseFloat(memo.total_refund || 0);
}
return total;
}
function validateRefund(sale, returnItems, options, currentMemoId) {
const previousRefundsTotal = calculatePreviousRefundsTotal(sale, options, currentMemoId);
// Check if sale is fully refunded
if (previousRefundsTotal >= sale.total) {
return 'The sale has already been fully refunded';
}
const maxRefundable = sale.total - previousRefundsTotal;
// Validate based on input type
if (options.input_type === 'amount_only') {
if (options.requested_refund_amount === undefined) {
return 'Requested refund amount is required for amount-only refunds';
}
if (options.requested_refund_amount > maxRefundable) {
return 'Requested amount exceeds maximum refundable amount';
}
} else {
// Precompute a map of productId -> total refunded quantity
const refundedQuantityMap = {};
// Process all credit memos from options
const creditMemos = [];
if (Array.isArray(options.credit_memo)) {
creditMemos.push(...options.credit_memo);
} else if (options.credit_memo && typeof options.credit_memo === 'object') {
creditMemos.push(options.credit_memo);
}
for (const memo of creditMemos) {
// Process return items in memo
let memoItems = [];
if (Array.isArray(memo.return_items)) {
memoItems = memo.return_items;
} else if (memo.return_items && typeof memo.return_items === 'object') {
memoItems = [memo.return_items];
}
for (const item of memoItems) {
if (item.product_id) {
const productId = item.product_id;
refundedQuantityMap[productId] =
(refundedQuantityMap[productId] || 0) +
(parseFloat(item.quantity) || 0);
}
}
}
// Validate each return item
for (const returnItem of returnItems) {
const saleItem = findProductInSale(sale, returnItem.product_id);
if (!saleItem) {
return `Product not found: ${returnItem.product_id}`;
}
// Calculate available quantity
const totalRefunded = refundedQuantityMap[returnItem.product_id] || 0;
const availableQuantity = (saleItem.sale_product_quantity || 0) - totalRefunded;
// Handle different input types
let requestedQuantity = 0;
if (options.input_type === 'items_full_refund') {
requestedQuantity = parseFloat(returnItem.quantity) || 0;
}
else if (options.input_type === 'items_with_custom_amount') {
// Convert refund amount to equivalent quantity
const unitPrice = saleItem.price > 0 ? saleItem.price : 1;
requestedQuantity = returnItem.refund_amount
? parseFloat((returnItem.refund_amount / unitPrice).toFixed(2))
: 0;
}
if (requestedQuantity > availableQuantity) {
return `Refund exceeds available quantity for product: ${saleItem.name || saleItem.product_id}. ` +
`Available: ${availableQuantity}, Requested: ${requestedQuantity}, ` +
`Previously refunded: ${totalRefunded}`;
}
}
}
return null;
}
function calculateMaxRefundableAmount(sale, options, currentMemoId) {
return sale.total - calculatePreviousRefundsTotal(sale, options, currentMemoId);
}
module.exports = {
findProductInSale,
calculatePreviousRefundsTotal,
validateRefund,
calculateMaxRefundableAmount
};