feat: add sale domain rules
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
function requiredText(value, fieldName) {
|
||||
if (typeof value !== 'string' || value.trim().length === 0) {
|
||||
throw new Error(`${fieldName} is required.`);
|
||||
}
|
||||
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function requirePositiveMinorUnit(value, fieldName) {
|
||||
if (!Number.isInteger(value) || value <= 0) {
|
||||
throw new Error(`${fieldName} must be a positive integer minor unit.`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function requireNonNegativeMinorUnit(value, fieldName) {
|
||||
if (!Number.isInteger(value) || value < 0) {
|
||||
throw new Error(`${fieldName} must be a non-negative integer minor unit.`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function createSale({
|
||||
sellerId,
|
||||
saleDate,
|
||||
saleLocation,
|
||||
soldPriceMinor,
|
||||
currency = 'USD',
|
||||
platformFeesMinor = 0,
|
||||
paymentProcessingFeesMinor = 0,
|
||||
shippingCostMinor = 0,
|
||||
taxesMinor = 0,
|
||||
otherFeesMinor = 0,
|
||||
buyerName,
|
||||
buyerContact,
|
||||
notes,
|
||||
}) {
|
||||
return {
|
||||
sellerId: requiredText(sellerId, 'Seller ID'),
|
||||
saleDate: requiredText(saleDate, 'Sale date'),
|
||||
...(typeof saleLocation === 'string' && saleLocation.trim().length > 0
|
||||
? { saleLocation: saleLocation.trim() }
|
||||
: {}),
|
||||
soldPriceMinor: requirePositiveMinorUnit(soldPriceMinor, 'Sold price'),
|
||||
currency: requiredText(currency, 'Currency'),
|
||||
platformFeesMinor: requireNonNegativeMinorUnit(platformFeesMinor, 'Platform fees'),
|
||||
paymentProcessingFeesMinor: requireNonNegativeMinorUnit(
|
||||
paymentProcessingFeesMinor,
|
||||
'Payment processing fees',
|
||||
),
|
||||
shippingCostMinor: requireNonNegativeMinorUnit(shippingCostMinor, 'Shipping cost'),
|
||||
taxesMinor: requireNonNegativeMinorUnit(taxesMinor, 'Taxes'),
|
||||
otherFeesMinor: requireNonNegativeMinorUnit(otherFeesMinor, 'Other fees'),
|
||||
...(typeof buyerName === 'string' && buyerName.trim().length > 0
|
||||
? { buyerName: buyerName.trim() }
|
||||
: {}),
|
||||
...(typeof buyerContact === 'string' && buyerContact.trim().length > 0
|
||||
? { buyerContact: buyerContact.trim() }
|
||||
: {}),
|
||||
...(typeof notes === 'string' && notes.trim().length > 0 ? { notes: notes.trim() } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function createSaleItem({
|
||||
saleId,
|
||||
inventoryItemId,
|
||||
soldPriceMinor,
|
||||
costBasisMinor,
|
||||
}) {
|
||||
return {
|
||||
saleId: requiredText(saleId, 'Sale ID'),
|
||||
inventoryItemId: requiredText(inventoryItemId, 'Inventory item ID'),
|
||||
soldPriceMinor: requirePositiveMinorUnit(soldPriceMinor, 'Sold price'),
|
||||
costBasisMinor: requireNonNegativeMinorUnit(costBasisMinor, 'Cost basis'),
|
||||
};
|
||||
}
|
||||
|
||||
export function calculateNetSaleProceeds({
|
||||
soldPriceMinor,
|
||||
platformFeesMinor = 0,
|
||||
paymentProcessingFeesMinor = 0,
|
||||
shippingCostMinor = 0,
|
||||
taxesMinor = 0,
|
||||
otherFeesMinor = 0,
|
||||
}) {
|
||||
const salePrice = requirePositiveMinorUnit(soldPriceMinor, 'Sold price');
|
||||
const platformFees = requireNonNegativeMinorUnit(platformFeesMinor, 'Platform fees');
|
||||
const processingFees = requireNonNegativeMinorUnit(
|
||||
paymentProcessingFeesMinor,
|
||||
'Payment processing fees',
|
||||
);
|
||||
const shipping = requireNonNegativeMinorUnit(shippingCostMinor, 'Shipping cost');
|
||||
const taxes = requireNonNegativeMinorUnit(taxesMinor, 'Taxes');
|
||||
const otherFees = requireNonNegativeMinorUnit(otherFeesMinor, 'Other fees');
|
||||
|
||||
return salePrice - platformFees - processingFees - shipping - taxes - otherFees;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import {
|
||||
calculateNetSaleProceeds,
|
||||
createSale,
|
||||
createSaleItem,
|
||||
} from '../../src/modules/sales/domain/sale.js';
|
||||
|
||||
test('creates a valid sale record and calculates net proceeds after fees', () => {
|
||||
const sale = createSale({
|
||||
sellerId: 'seller-id',
|
||||
saleDate: '2026-09-08',
|
||||
saleLocation: 'eBay',
|
||||
soldPriceMinor: 4999,
|
||||
currency: 'USD',
|
||||
platformFeesMinor: 300,
|
||||
paymentProcessingFeesMinor: 120,
|
||||
shippingCostMinor: 50,
|
||||
taxesMinor: 90,
|
||||
otherFeesMinor: 40,
|
||||
buyerName: 'Alex',
|
||||
buyerContact: 'alex@example.com',
|
||||
});
|
||||
|
||||
assert.equal(sale.soldPriceMinor, 4999);
|
||||
assert.equal(sale.currency, 'USD');
|
||||
assert.equal(calculateNetSaleProceeds({
|
||||
soldPriceMinor: 4999,
|
||||
platformFeesMinor: 300,
|
||||
paymentProcessingFeesMinor: 120,
|
||||
shippingCostMinor: 50,
|
||||
taxesMinor: 90,
|
||||
otherFeesMinor: 40,
|
||||
}), 4399);
|
||||
});
|
||||
|
||||
test('rejects invalid sales and negative fee values', () => {
|
||||
assert.throws(
|
||||
() => createSale({
|
||||
sellerId: 'seller-id',
|
||||
saleDate: '2026-09-08',
|
||||
soldPriceMinor: 0,
|
||||
}),
|
||||
/must be a positive integer minor unit/,
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() => createSaleItem({
|
||||
saleId: 'sale-id',
|
||||
inventoryItemId: 'inventory-item-id',
|
||||
soldPriceMinor: 2000,
|
||||
costBasisMinor: -1,
|
||||
}),
|
||||
/must be a non-negative integer minor unit/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user