191 lines
7.5 KiB
JavaScript
191 lines
7.5 KiB
JavaScript
import { defineTool, ToolError } from 'libllmorbiter';
|
|
|
|
const DEFAULT_TAX_RATE = 19;
|
|
const DEFAULT_PRECISION = 6;
|
|
|
|
/**
|
|
* Calculates net-to-gross or gross-to-net for a single price.
|
|
*
|
|
* @param {object} params
|
|
* @param {number} [params.net]
|
|
* @param {number} [params.gross]
|
|
* @param {number} [params.amount]
|
|
* @param {'net'|'gross'} [params.from]
|
|
* @param {number} [params.taxRate=19]
|
|
* @param {number} [params.precision=6]
|
|
* @returns {object}
|
|
*/
|
|
export function calculatePrice({ net, gross, amount, from, taxRate = DEFAULT_TAX_RATE, precision = DEFAULT_PRECISION }) {
|
|
const rate = taxRate != null ? Number(taxRate) : DEFAULT_TAX_RATE;
|
|
if (!Number.isFinite(rate) || rate < 0) {
|
|
throw new ToolError(`Invalid taxRate: ${taxRate}. Must be a non-negative number.`);
|
|
}
|
|
|
|
const prec = precision != null ? Math.min(8, Math.max(2, Number(precision) || DEFAULT_PRECISION)) : DEFAULT_PRECISION;
|
|
const factor = 1 + rate / 100;
|
|
|
|
let inputType = null;
|
|
let inputValue = null;
|
|
|
|
if (net != null && gross == null) {
|
|
inputType = 'net';
|
|
inputValue = Number(net);
|
|
} else if (gross != null && net == null) {
|
|
inputType = 'gross';
|
|
inputValue = Number(gross);
|
|
} else if (amount != null) {
|
|
inputValue = Number(amount);
|
|
inputType = from === 'net' ? 'net' : 'gross';
|
|
} else if (net != null && gross != null) {
|
|
// Both provided: validate consistency
|
|
const n = Number(net);
|
|
const g = Number(gross);
|
|
const calculatedGross = +(n * factor).toFixed(2);
|
|
const effectiveRate = n > 0 ? +(((g - n) / n) * 100).toFixed(2) : 0;
|
|
return {
|
|
direction: 'verify',
|
|
net: +n.toFixed(2),
|
|
gross: +g.toFixed(2),
|
|
taxRate: rate,
|
|
effectiveTaxRate: effectiveRate,
|
|
isConsistent: Math.abs(calculatedGross - g) <= 0.02,
|
|
taxAmount: +(g - n).toFixed(2),
|
|
};
|
|
} else {
|
|
throw new ToolError('net_gross_calc requires "net", "gross", or "amount".');
|
|
}
|
|
|
|
if (!Number.isFinite(inputValue) || inputValue < 0) {
|
|
throw new ToolError(`Invalid price amount: ${inputValue}. Must be a non-negative number.`);
|
|
}
|
|
|
|
if (inputType === 'gross') {
|
|
const grossVal = +inputValue.toFixed(2);
|
|
const exactNet = inputValue / factor;
|
|
const netRounded = +exactNet.toFixed(2);
|
|
const netPrecision = +exactNet.toFixed(prec);
|
|
const taxAmount = +(grossVal - netRounded).toFixed(2);
|
|
|
|
return {
|
|
direction: 'gross_to_net',
|
|
gross: grossVal,
|
|
net: netRounded,
|
|
netPrecision,
|
|
netPrecise: exactNet,
|
|
taxRate: rate,
|
|
taxAmount,
|
|
apiNetValue: netPrecision,
|
|
formatted: {
|
|
gross: `${grossVal.toFixed(2)} €`,
|
|
net: `${netRounded.toFixed(2)} €`,
|
|
tax: `${taxAmount.toFixed(2)} €`,
|
|
},
|
|
};
|
|
} else {
|
|
const netVal = +inputValue.toFixed(2);
|
|
const exactGross = inputValue * factor;
|
|
const grossRounded = +exactGross.toFixed(2);
|
|
const taxAmount = +(grossRounded - netVal).toFixed(2);
|
|
|
|
return {
|
|
direction: 'net_to_gross',
|
|
net: netVal,
|
|
gross: grossRounded,
|
|
grossPrecise: exactGross,
|
|
taxRate: rate,
|
|
taxAmount,
|
|
formatted: {
|
|
net: `${netVal.toFixed(2)} €`,
|
|
gross: `${grossRounded.toFixed(2)} €`,
|
|
tax: `${taxAmount.toFixed(2)} €`,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the net_gross_calc tool.
|
|
*/
|
|
export function createNetGrossCalcTool() {
|
|
return defineTool({
|
|
name: 'net_gross_calc',
|
|
description:
|
|
'Convert between net and gross prices (net to gross, gross to net) with VAT/tax rate (default 19%). ' +
|
|
'Computes 2-decimal rounded display values and 6-decimal high-precision net values for API article creation.',
|
|
strict: false,
|
|
parameters: {
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
properties: {
|
|
note: {
|
|
type: 'string',
|
|
description: 'A note explaining what this price calculation is doing. This note will be displayed to the user while executing.',
|
|
},
|
|
gross: {
|
|
type: ['number', 'null'],
|
|
description: 'Gross price (incl. VAT) or null. When provided, computes net price and tax amount.',
|
|
},
|
|
net: {
|
|
type: ['number', 'null'],
|
|
description: 'Net price (excl. VAT) or null. When provided, computes gross price and tax amount.',
|
|
},
|
|
taxRate: {
|
|
type: ['number', 'null'],
|
|
description: `Tax/VAT rate percentage (e.g. 19 for 19%, 7 for 7%) or null. Default ${DEFAULT_TAX_RATE}.`,
|
|
},
|
|
amount: {
|
|
type: ['number', 'null'],
|
|
description: 'Generic price amount or null when used with "from" ("gross" or "net").',
|
|
},
|
|
from: {
|
|
type: ['string', 'null'],
|
|
enum: ['gross', 'net', null],
|
|
description: 'Direction when using "amount": "gross" (default) or "net".',
|
|
},
|
|
precision: {
|
|
type: ['number', 'null'],
|
|
description: `Decimal precision for net price API value or null (default ${DEFAULT_PRECISION}, between 2 and 8).`,
|
|
},
|
|
items: {
|
|
type: ['array', 'null'],
|
|
description: 'Optional array of prices or objects for batch calculation: [19.99, 49.95] or [{ gross: 19.99, taxRate: 7 }].',
|
|
items: {
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
properties: {
|
|
gross: { type: ['number', 'null'] },
|
|
net: { type: ['number', 'null'] },
|
|
amount: { type: ['number', 'null'] },
|
|
from: { type: ['string', 'null'], enum: ['gross', 'net', null] },
|
|
taxRate: { type: ['number', 'null'] },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
invoke: async ({ note, gross, net, amount, from, taxRate = DEFAULT_TAX_RATE, precision = DEFAULT_PRECISION, items } = {}) => {
|
|
if (Array.isArray(items) && items.length > 0) {
|
|
const results = items.map((item, idx) => {
|
|
const itemRate = item.taxRate != null ? item.taxRate : taxRate;
|
|
try {
|
|
return calculatePrice({
|
|
net: item.net,
|
|
gross: item.gross,
|
|
amount: item.amount,
|
|
from: item.from || from,
|
|
taxRate: itemRate,
|
|
precision,
|
|
});
|
|
} catch (err) {
|
|
return { itemIndex: idx, error: err.message };
|
|
}
|
|
});
|
|
return JSON.stringify({ count: results.length, results }, null, 2);
|
|
}
|
|
|
|
const result = calculatePrice({ net, gross, amount, from, taxRate, precision });
|
|
return JSON.stringify(result, null, 2);
|
|
},
|
|
});
|
|
}
|