feat: add acquisition and pricing domain rules

This commit is contained in:
2026-09-08 17:51:51 -04:00
parent d964b4f0ac
commit 8576c8f788
3 changed files with 177 additions and 0 deletions
@@ -0,0 +1,64 @@
function requireNonNegativeMinorUnit(value, fieldName) {
if (!Number.isInteger(value) || value < 0) {
throw new Error(`${fieldName} must be a non-negative integer minor unit.`);
}
return value;
}
function requiredText(value, fieldName) {
if (typeof value !== 'string' || value.trim().length === 0) {
throw new Error(`${fieldName} is required.`);
}
return value.trim();
}
export function createAcquisition({
inventoryItemId,
method,
costMinor,
currency = 'USD',
date,
source,
location,
shippingMinor = 0,
taxMinor = 0,
otherFeesMinor = 0,
estimatedTradeValueMinor = 0,
notes,
}) {
return {
inventoryItemId: requiredText(inventoryItemId, 'Inventory item ID'),
method: requiredText(method, 'Acquisition method'),
costMinor: requireNonNegativeMinorUnit(costMinor, 'Acquisition cost'),
currency: requiredText(currency, 'Currency'),
...(date ? { date } : {}),
...(typeof source === 'string' && source.trim().length > 0 ? { source: source.trim() } : {}),
...(typeof location === 'string' && location.trim().length > 0 ? { location: location.trim() } : {}),
shippingMinor: requireNonNegativeMinorUnit(shippingMinor, 'Shipping cost'),
taxMinor: requireNonNegativeMinorUnit(taxMinor, 'Tax'),
otherFeesMinor: requireNonNegativeMinorUnit(otherFeesMinor, 'Other fees'),
estimatedTradeValueMinor: requireNonNegativeMinorUnit(
estimatedTradeValueMinor,
'Estimated trade value',
),
...(typeof notes === 'string' && notes.trim().length > 0 ? { notes: notes.trim() } : {}),
};
}
export function calculateAcquisitionCostBasis({
costMinor,
shippingMinor = 0,
taxMinor = 0,
otherFeesMinor = 0,
estimatedTradeValueMinor = 0,
}) {
return (
requireNonNegativeMinorUnit(costMinor, 'Acquisition cost') +
requireNonNegativeMinorUnit(shippingMinor, 'Shipping cost') +
requireNonNegativeMinorUnit(taxMinor, 'Tax') +
requireNonNegativeMinorUnit(otherFeesMinor, 'Other fees') -
requireNonNegativeMinorUnit(estimatedTradeValueMinor, 'Estimated trade value')
);
}
@@ -0,0 +1,54 @@
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;
}
export function createPricingObservation({
productId,
inventoryItemId,
providerId,
priceMinor,
currency = 'USD',
sourceIdentifier,
conditionId,
sourceUrl,
qualityScore,
}) {
return {
productId: requiredText(productId, 'Product ID'),
inventoryItemId: requiredText(inventoryItemId, 'Inventory item ID'),
providerId: requiredText(providerId, 'Provider ID'),
priceMinor: requirePositiveMinorUnit(priceMinor, 'Observed price'),
currency: requiredText(currency, 'Currency'),
sourceIdentifier: requiredText(sourceIdentifier, 'Source identifier'),
conditionId: requiredText(conditionId, 'Condition ID'),
...(typeof sourceUrl === 'string' && sourceUrl.trim().length > 0 ? { sourceUrl: sourceUrl.trim() } : {}),
...(typeof qualityScore === 'number' ? { qualityScore } : {}),
};
}
export function calculateMedianPriceMinor(prices) {
if (!Array.isArray(prices) || prices.length === 0) {
throw new Error('At least one price is required.');
}
const sorted = [...prices].sort((left, right) => left - right);
const middle = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return Math.round((sorted[middle - 1] + sorted[middle]) / 2);
}
return sorted[middle];
}