@arathron/n8n-nodes-zoho-books
Version:
n8n community nodes for Zoho Books and Zoho Inventory API integration - Complete CRUD operations for Sales Orders, Invoices, Items, Vendors, Credit Notes, Payments, Purchase Orders, Bills, Composite Items, and Assemblies
394 lines • 15.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.taxPercentageCache = void 0;
exports.getTaxPercentage = getTaxPercentage;
exports.adjustRateForTax = adjustRateForTax;
exports.validateAndNormalizeSalesOrder = validateAndNormalizeSalesOrder;
exports.createValidationError = createValidationError;
exports.generateSampleSalesOrder = generateSampleSalesOrder;
exports.sanitizeSalesOrderPayload = sanitizeSalesOrderPayload;
const n8n_workflow_1 = require("n8n-workflow");
exports.taxPercentageCache = new Map();
async function getTaxPercentage(taxId, zohoApiRequest, context) {
if (exports.taxPercentageCache.has(taxId)) {
return exports.taxPercentageCache.get(taxId);
}
try {
const taxes = await zohoApiRequest.call(context, 'GET', '/settings/taxes');
for (const tax of taxes) {
const percentage = parseFloat(tax.tax_percentage) || 0;
exports.taxPercentageCache.set(tax.tax_id, percentage);
}
return exports.taxPercentageCache.get(taxId) || 0;
}
catch (error) {
console.warn(`Failed to fetch tax percentage for tax_id ${taxId}:`, error);
return 0;
}
}
function adjustRateForTax(item, taxPercentage, isGlobalTaxInclusive = false) {
const isTaxInclusive = item.rate_includes_tax ?? isGlobalTaxInclusive;
if (!isTaxInclusive || !item.tax_id || taxPercentage <= 0) {
return item;
}
const netRate = +(item.rate / (1 + taxPercentage / 100)).toFixed(2);
return {
...item,
rate: netRate,
rate_includes_tax: undefined
};
}
function validateAndNormalizeSalesOrder(inputData, _context = 'Sales Order Creation') {
const result = {
isValid: false,
errors: [],
warnings: [],
};
try {
if (!inputData || typeof inputData !== 'object') {
result.errors.push('Input data must be a valid object');
return result;
}
const normalized = {
customer_id: '',
line_items: [],
};
if (!inputData.customer_id) {
result.errors.push('customer_id is required');
}
else {
normalized.customer_id = String(inputData.customer_id).trim();
if (!normalized.customer_id) {
result.errors.push('customer_id cannot be empty');
}
}
if (!inputData.line_items || !Array.isArray(inputData.line_items)) {
result.errors.push('line_items must be an array');
}
else if (inputData.line_items.length === 0) {
result.errors.push('At least one line item is required');
}
else {
normalized.line_items = [];
for (let i = 0; i < inputData.line_items.length; i++) {
const item = inputData.line_items[i];
const itemValidation = validateLineItem(item, i);
if (!itemValidation.isValid) {
result.errors.push(...itemValidation.errors);
}
else if (itemValidation.normalizedItem) {
normalized.line_items.push(itemValidation.normalizedItem);
}
}
}
if (inputData.salesorder_number !== undefined) {
normalized.salesorder_number = String(inputData.salesorder_number).trim();
}
if (inputData.date !== undefined) {
const dateStr = String(inputData.date).trim();
if (dateStr && !isValidDate(dateStr)) {
result.warnings.push(`Invalid date format for 'date': ${dateStr}. Expected YYYY-MM-DD format.`);
}
else {
normalized.date = dateStr;
}
}
if (inputData.reference_number !== undefined) {
normalized.reference_number = String(inputData.reference_number).trim();
}
if (inputData.billing_address_id !== undefined) {
normalized.billing_address_id = String(inputData.billing_address_id).trim();
}
if (inputData.shipping_address_id !== undefined) {
normalized.shipping_address_id = String(inputData.shipping_address_id).trim();
}
if (inputData.currency_id !== undefined) {
normalized.currency_id = String(inputData.currency_id).trim();
}
if (inputData.exchange_rate !== undefined) {
const rate = parseFloat(inputData.exchange_rate);
if (isNaN(rate) || rate <= 0) {
result.warnings.push('Invalid exchange_rate. Must be a positive number.');
}
else {
normalized.exchange_rate = rate;
}
}
if (inputData.shipping_charge !== undefined) {
const charge = parseFloat(inputData.shipping_charge);
if (isNaN(charge) || charge < 0) {
result.warnings.push('Invalid shipping_charge. Must be a non-negative number.');
}
else {
normalized.shipping_charge = charge;
}
}
if (inputData.adjustment !== undefined) {
const adjustment = parseFloat(inputData.adjustment);
if (isNaN(adjustment)) {
result.warnings.push('Invalid adjustment. Must be a number.');
}
else {
normalized.adjustment = adjustment;
}
}
if (inputData.adjustment_description !== undefined) {
normalized.adjustment_description = String(inputData.adjustment_description).trim();
}
if (inputData.notes !== undefined) {
normalized.notes = String(inputData.notes).trim();
}
if (inputData.terms !== undefined) {
normalized.terms = String(inputData.terms).trim();
}
if (inputData.subject !== undefined) {
normalized.subject = String(inputData.subject).trim();
}
if (inputData.discount !== undefined) {
const discount = parseFloat(inputData.discount);
if (isNaN(discount) || discount < 0) {
result.warnings.push('Invalid discount. Must be a non-negative number.');
}
else {
normalized.discount = discount;
}
}
if (inputData.discount_type !== undefined) {
if (!['entity_level', 'item_level'].includes(inputData.discount_type)) {
result.warnings.push('Invalid discount_type. Must be "entity_level" or "item_level".');
}
else {
normalized.discount_type = inputData.discount_type;
}
}
if (inputData.is_discount_before_tax !== undefined) {
normalized.is_discount_before_tax = Boolean(inputData.is_discount_before_tax);
}
if (inputData.template_id !== undefined) {
normalized.template_id = String(inputData.template_id).trim();
}
if (inputData.salesperson_name !== undefined) {
normalized.salesperson_name = String(inputData.salesperson_name).trim();
}
if (inputData.delivery_date !== undefined) {
const dateStr = String(inputData.delivery_date).trim();
if (dateStr && !isValidDate(dateStr)) {
result.warnings.push(`Invalid date format for 'delivery_date': ${dateStr}. Expected YYYY-MM-DD format.`);
}
else {
normalized.delivery_date = dateStr;
}
}
if (inputData.shipment_date !== undefined) {
const dateStr = String(inputData.shipment_date).trim();
if (dateStr && !isValidDate(dateStr)) {
result.warnings.push(`Invalid date format for 'shipment_date': ${dateStr}. Expected YYYY-MM-DD format.`);
}
else {
normalized.shipment_date = dateStr;
}
}
if (inputData.place_of_supply !== undefined) {
normalized.place_of_supply = String(inputData.place_of_supply).trim();
}
if (inputData.gst_treatment !== undefined) {
if (!['business_gst', 'business_none', 'overseas', 'consumer'].includes(inputData.gst_treatment)) {
result.warnings.push('Invalid gst_treatment. Must be one of: business_gst, business_none, overseas, consumer.');
}
else {
normalized.gst_treatment = inputData.gst_treatment;
}
}
if (inputData.gst_no !== undefined) {
normalized.gst_no = String(inputData.gst_no).trim();
}
if (inputData.custom_fields !== undefined) {
if (!Array.isArray(inputData.custom_fields)) {
result.warnings.push('custom_fields must be an array');
}
else {
normalized.custom_fields = [];
for (const field of inputData.custom_fields) {
if (field && field.customfield_id && field.value !== undefined) {
normalized.custom_fields.push({
customfield_id: String(field.customfield_id).trim(),
value: String(field.value).trim(),
});
}
else {
result.warnings.push('Invalid custom field. Must have customfield_id and value.');
}
}
}
}
if (result.errors.length === 0) {
result.isValid = true;
result.normalizedData = normalized;
}
}
catch (error) {
result.errors.push(`Unexpected error during validation: ${error instanceof Error ? error.message : String(error)}`);
}
return result;
}
function validateLineItem(item, index) {
const errors = [];
const normalizedItem = {
quantity: 0,
rate: 0,
};
if (item.quantity === undefined || item.quantity === null) {
errors.push(`Line item ${index + 1}: quantity is required`);
}
else {
const quantity = Number(item.quantity);
if (isNaN(quantity) || quantity <= 0) {
errors.push(`Line item ${index + 1}: quantity must be a positive number`);
}
else {
normalizedItem.quantity = quantity;
}
}
if (item.rate === undefined || item.rate === null) {
errors.push(`Line item ${index + 1}: rate is required`);
}
else {
const rate = Number(item.rate);
if (isNaN(rate) || rate < 0) {
errors.push(`Line item ${index + 1}: rate must be a non-negative number`);
}
else {
normalizedItem.rate = rate;
}
}
if (item.item_id !== undefined) {
normalizedItem.item_id = String(item.item_id).trim();
}
if (item.name !== undefined) {
normalizedItem.name = String(item.name).trim();
}
if (item.description !== undefined) {
normalizedItem.description = String(item.description).trim();
}
if (item.tax_id !== undefined) {
normalizedItem.tax_id = String(item.tax_id).trim();
}
if (item.discount !== undefined) {
const discount = Number(item.discount);
if (!isNaN(discount) && discount >= 0) {
normalizedItem.discount = discount;
}
else {
errors.push(`Line item ${index + 1}: discount should be a non-negative number`);
}
}
if (item.unit !== undefined) {
normalizedItem.unit = String(item.unit).trim();
}
if (item.item_total !== undefined) {
const total = Number(item.item_total);
if (!isNaN(total) && total >= 0) {
normalizedItem.item_total = total;
}
else {
errors.push(`Line item ${index + 1}: item_total should be a non-negative number`);
}
}
if (item.rate_includes_tax !== undefined) {
normalizedItem.rate_includes_tax = Boolean(item.rate_includes_tax);
}
return {
isValid: errors.length === 0,
errors,
normalizedItem: errors.length === 0 ? normalizedItem : undefined,
};
}
function isValidDate(dateString) {
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateRegex.test(dateString)) {
return false;
}
const date = new Date(dateString);
return date instanceof Date && !isNaN(date.getTime());
}
function createValidationError(result, context = 'Sales Order') {
const errorMessage = result.errors.length > 0
? `${context} validation failed: ${result.errors.join('; ')}`
: `${context} validation failed`;
const description = result.warnings.length > 0
? `Warnings: ${result.warnings.join('; ')}`
: 'Please check the input data and try again';
return new n8n_workflow_1.NodeApiError({}, {
message: errorMessage,
description,
httpCode: '400',
});
}
function generateSampleSalesOrder() {
return {
customer_id: '123456789',
salesorder_number: 'SO-100045',
date: '2025-01-15',
reference_number: 'WEB-ORDER-8787',
line_items: [
{
item_id: '432198765',
name: 'Blue Widget XL',
description: 'Custom engraving',
quantity: 2,
rate: 29.99,
tax_id: '111222333',
discount: 0,
},
{
item_id: '654321987',
name: 'Red Widget Pro',
quantity: 1,
rate: 120.00,
tax_id: '111222333',
},
{
item_id: '987654321',
name: 'Premium Widget (Tax-Inclusive)',
quantity: 1,
rate: 118.00,
tax_id: '111222333',
rate_includes_tax: true,
},
{
item_id: '555666777',
name: 'Standard Widget (Net Rate)',
quantity: 3,
rate: 50.00,
tax_id: '111222333',
rate_includes_tax: false,
},
],
shipping_charge: 10.0,
adjustment: -5.0,
adjustment_description: 'Promo',
notes: 'Thank you for your business!',
terms: 'Payable on delivery.',
};
}
function sanitizeSalesOrderPayload(payload) {
const sanitized = { ...payload };
Object.keys(sanitized).forEach(key => {
if (sanitized[key] === undefined || sanitized[key] === null) {
delete sanitized[key];
}
});
if (sanitized.line_items && Array.isArray(sanitized.line_items)) {
sanitized.line_items = sanitized.line_items.map(item => {
const sanitizedItem = { ...item };
Object.keys(sanitizedItem).forEach(key => {
if (sanitizedItem[key] === undefined || sanitizedItem[key] === null) {
delete sanitizedItem[key];
}
});
return sanitizedItem;
});
}
return sanitized;
}
//# sourceMappingURL=SalesOrderNormalizer.js.map