@fye/vendor-utils
Version:
Vendor utility functions
606 lines (556 loc) • 21.5 kB
JavaScript
/* eslint-disable no-console */
// const debug = require('debug')('aec-order-feedback');
const fs = require('fs');
const util = require('util');
const aecProductPrefix = 'aec.';
const fyeProductPrefix = 'fye.';
const aecUnitPricePercentage = 0.69; // looking at AOFT files and 69% seems to be a common value
const aecUnitShippingPrice = 1.5;
const aecUnitHandlingPrice = 0.5;
const AecOrderAction = {
Backorder: 0,
Ship: 1,
Cancel: 2,
Return: 3,
};
const unlink = util.promisify(fs.unlink);
const lstat = util.promisify(fs.lstat);
async function exists(path) {
try {
await lstat(path);
return true;
} catch (err) {
return false;
}
}
async function deleteFile(path) {
try {
if (await exists(path)) await unlink(path);
} catch (err) {}
}
function calcAecUnitPrice(amount) {
return amount * aecUnitPricePercentage;
}
function isAecProduct(productId) {
return productId.toLowerCase().indexOf(aecProductPrefix) !== -1;
}
function isFyeProduct(productId) {
return productId.toLowerCase().indexOf(fyeProductPrefix) !== -1;
}
function convertCountryName(country) {
if (country.toLowerCase().indexOf('united') !== -1 && country.toLowerCase().indexOf('states') !== -1) {
return 'UNITED STATES';
}
return country;
}
function formatDate(date) {
const month = date.getMonth();
const formattedMonth = `0${month}`.slice(-2);
const day = date.getDate();
const formattedDay = `0${day}`.slice(-2);
const year = date.getFullYear();
return `${formattedMonth}/${formattedDay}/${year}`;
}
function formatMoney(amount) {
if (amount === 0) {
return 0;
}
const strAmount = amount.toFixed(2);
const index = strAmount.indexOf('.');
if (index !== -1) {
const dollars = strAmount.slice(0, index);
const cents = strAmount.slice(index + '.'.length);
return dollars + cents;
}
return strAmount;
}
function trimLeadingZeros(productId) {
let formattedProductId = productId;
let index = -1;
while (formattedProductId.startsWith('0')) {
index = formattedProductId.indexOf('0');
formattedProductId = formattedProductId.slice(index + '0'.length);
}
return formattedProductId;
}
function formatProductId(productId) {
if (isAecProduct(productId)) {
const index = productId.toLowerCase().indexOf(aecProductPrefix);
return productId.slice(index + aecProductPrefix.length).toUpperCase();
}
if (isFyeProduct(productId)) {
const index = productId.toLowerCase().indexOf(fyeProductPrefix);
return trimLeadingZeros(productId.slice(index + fyeProductPrefix.length).toUpperCase());
}
return productId;
}
function getAecOrderSubtotal(aecProducts) {
let subtotal = 0;
aecProducts.forEach(aecProduct => {
subtotal += aecProduct.aecExtendedPrice;
});
return subtotal;
}
function getConsumerOrderSubtotal(aecProducts) {
let subtotal = 0;
aecProducts.forEach(aecProduct => {
subtotal += aecProduct.consumerExtendedPrice;
});
return subtotal;
}
function getTotalQuantityReturned(aecProducts) {
let totalQuantityReturned = 0;
aecProducts.forEach(aecProduct => {
totalQuantityReturned += aecProduct.quantityReturned;
});
return totalQuantityReturned;
}
function getTotalQuantityCanceled(aecProducts) {
let totalQuantityCanceled = 0;
aecProducts.forEach(aecProduct => {
totalQuantityCanceled += aecProduct.quantityCanceled;
});
return totalQuantityCanceled;
}
function getTotalQuantityShipped(aecProducts) {
let totalQuantityShipped = 0;
aecProducts.forEach(aecProduct => {
totalQuantityShipped += aecProduct.quantityShipped;
});
return totalQuantityShipped;
}
/*
function getTotalQuantityBackordered(aecProducts) {
let totalQuantityBackordered = 0;
aecProducts.forEach(aecProduct => {
totalQuantityBackordered += aecProduct.quantityBackorded;
});
return totalQuantityBackordered;
}
*/
function getTotalQuantityOrdered(aecProducts) {
let totalQuantityOrdered = 0;
aecProducts.forEach(aecProduct => {
totalQuantityOrdered += aecProduct.quantityOrdered;
});
return totalQuantityOrdered;
}
function buildOrderTrailer(aecProducts, vendorShipDate) {
const totQauntityOrdered = getTotalQuantityOrdered(aecProducts);
const totQauntityShipped = getTotalQuantityShipped(aecProducts);
const totQauntityReturned = getTotalQuantityReturned(aecProducts);
const totQauntityCanceled = getTotalQuantityCanceled(aecProducts);
const type = '"T"';
const totalQuantityOrdered = `"${totQauntityOrdered}"`;
const totalQuantityShipped = `"${totQauntityShipped}"`;
const totalQuantityReturned = `"${totQauntityReturned}"`;
const totalItemLines = `"${aecProducts.length}"`;
let consumerOrderSubtotal = '"0"';
let consumerOrderTotal = '"0"';
if (totQauntityShipped !== 0 || totQauntityReturned !== 0) {
consumerOrderSubtotal = `"${formatMoney(getConsumerOrderSubtotal(aecProducts))}"`;
consumerOrderTotal = consumerOrderSubtotal;
}
let workOrderOrReturn = '""';
if (totQauntityShipped !== 0) {
workOrderOrReturn = '"32540063"'; // this value doesn't matter in our system
} else if (totQauntityReturned !== 0) {
workOrderOrReturn = '"JB04527432"'; // this value doesn't matter in our system
}
let invoice = '""';
if (totQauntityShipped !== 0) {
invoice = '"PLS41100280"'; // this value doesn't matter in our system
} else if (totQauntityReturned !== 0) {
invoice = '"RJB4549680"'; // this value doesn't matter in our system
}
let shipViaCode = '""';
if (totQauntityReturned === 0) {
shipViaCode = '"50"'; // this value doesn't matter in our system
}
let carrierTrackingNumber = '""';
if (totQauntityShipped !== 0) {
carrierTrackingNumber = '"9274899996315500307783"'; // this value doesn't matter in our system
}
let shipDate = '""';
if (totQauntityShipped !== 0 || totQauntityCanceled !== 0 || totQauntityReturned !== 0) {
shipDate = `"${formatDate(vendorShipDate)}"`;
}
let returnAuthCode = '""';
if (totQauntityReturned !== 0) {
returnAuthCode = '"EBX"'; // this value doesn't matter in our system
}
let arOrderSubtotal = '"0"'; // AEC accounts receivable
let arOrderShipping = '"0"';
let arOrderHandling = '"0"';
let arOrderTotal = '"0"';
if (totQauntityShipped !== 0) {
const aecOrderSubtotal = getAecOrderSubtotal(aecProducts);
const orderShipping = totQauntityShipped * aecUnitShippingPrice;
const orderHandling = totQauntityShipped * aecUnitHandlingPrice;
const orderTotal = aecOrderSubtotal + orderShipping + orderHandling;
arOrderSubtotal = `"${formatMoney(aecOrderSubtotal)}"`;
arOrderShipping = `"${formatMoney(orderShipping)}"`;
arOrderHandling = `"${formatMoney(orderHandling)}"`;
arOrderTotal = `"${formatMoney(orderTotal)}"`;
}
const part1 = `${type},${totalQuantityOrdered},${totalQuantityShipped},${totalQuantityReturned},`;
const part2 = `${totalItemLines},${consumerOrderSubtotal},${'"0","0","0"'},${consumerOrderTotal},`;
const part3 = `${workOrderOrReturn},${invoice},${shipViaCode},${carrierTrackingNumber},${'"0"'},`;
const part4 = `${shipDate},${returnAuthCode},${'""'},${arOrderSubtotal},${'"0"'},${arOrderShipping},`;
let part5 = `${arOrderHandling},${arOrderTotal}`;
if (totQauntityReturned !== 0) {
part5 += ',"PLS41100280"'; // this value doesn't matter in our system
}
return part1 + part2 + part3 + part4 + part5;
}
function buildOrderLineItems(aecProducts) {
const items = [];
aecProducts.forEach(aecProduct => {
const type = '"L"';
const orderingProductId = `"${formatProductId(aecProduct.orderingProductId)}"`;
const salesCode = '"01"'; // this value doesn't matter in our system
const aecProductId = `"${formatProductId(aecProduct.aecProductId)}"`;
const quantityOrdered = `"${aecProduct.quantityOrdered}"`;
const quantityShipped = `"${aecProduct.quantityShipped}"`;
const quantityReturned = `"${aecProduct.quantityReturned}"`;
const quantityBackordered = `"${aecProduct.quantityBackordered}"`;
const quantityCanceled = `"${aecProduct.quantityCanceled}"`;
const consumerUnitPrice = `"${formatMoney(aecProduct.consumerUnitPrice)}"`;
const consumerExtendedPrice = `"${formatMoney(aecProduct.consumerExtendedPrice)}"`;
const aecUnitPrice = `"${formatMoney(aecProduct.aecUnitPrice)}"`;
const aecExtendedPrice = `"${formatMoney(aecProduct.aecExtendedPrice)}"`;
const referenceId = '""';
const wmsId = '"12345678"'; // this value doesn't matter in our system
const part1 = `${type},${orderingProductId},${salesCode},${aecProductId},${quantityOrdered},`;
const part2 = `${quantityShipped},${quantityReturned},${consumerUnitPrice},`;
const part3 = `${consumerExtendedPrice},${aecUnitPrice},${aecExtendedPrice},`;
const part4 = `${referenceId},${wmsId},${quantityBackordered},${quantityCanceled}`;
const item = part1 + part2 + part3 + part4;
items.push(item);
});
return items;
}
function buildCustomerShipTo(aecShippingAddress) {
const type = '"S"';
const name = `"${aecShippingAddress.name}"`;
const address1 = `"${aecShippingAddress.address1}"`;
const address2 = `"${aecShippingAddress.address2}"`;
const city = `"${aecShippingAddress.city}"`;
const state = `"${aecShippingAddress.state}"`;
const postal = `"${aecShippingAddress.zip}"`;
const country = `"${aecShippingAddress.country}"`;
const part1 = `${type},${name},${address1},${address2},${city},${state},${postal},`;
const part2 = `${'"",'}${country}${',"",""'}`;
return part1 + part2;
}
function buildCustomerBillTo(aecBillingAddress) {
const type = '"B"';
const name = `"${aecBillingAddress.name}"`;
const address1 = `"${aecBillingAddress.address1}"`;
const address2 = `"${aecBillingAddress.address2}"`;
const city = `"${aecBillingAddress.city}"`;
const state = `"${aecBillingAddress.state}"`;
const postal = `"${aecBillingAddress.zip}"`;
const country = `"${aecBillingAddress.country}"`;
const part1 = `${type},${name},${address1},${address2},${city},${state},${postal},`;
const part2 = `${'"",'}${country}${',"","","","??","","","",""'}`;
return part1 + part2;
}
function buildOrderHeader({
vendorInvoiceDate,
vendorOrderId,
transactionDate,
purchaseOrderInternalId,
salesOrderExternalId,
}) {
const type = '"H"';
const orderType = '"S"';
const invoiceDate = `"${formatDate(vendorInvoiceDate)}"`;
const confirmationNumber = `"${vendorOrderId}"`;
const orderDate = `"${formatDate(transactionDate)}"`;
const retailerPoNumber = `"${purchaseOrderInternalId}"`;
const memo = '"F"';
const source = `"${salesOrderExternalId}"`;
const retailerReturnAuthNumber = '""'; // created in ROMS
const part1 = `${type},${orderType},${invoiceDate},${confirmationNumber},${orderDate},`;
const part2 = `${retailerPoNumber},${memo},${source},${retailerReturnAuthNumber}`;
return part1 + part2;
}
function getAecOrderFeedbackNumItems(aecOrderFeedback) {
return aecOrderFeedback.items.length;
}
function getAecOrderFeedbackNumLineItems(aecOrderFeedback) {
let totalNumberLineItems = getAecOrderFeedbackNumItems(aecOrderFeedback);
totalNumberLineItems += 4; // H, B, S, T
return totalNumberLineItems;
}
function getAecOrderFeedbacksNumItems(aecOrderFeedbacks) {
let totalNumberItems = 0;
aecOrderFeedbacks.forEach(aecOrderFeedback => {
totalNumberItems += getAecOrderFeedbackNumItems(aecOrderFeedback);
});
return totalNumberItems;
}
function getAecOrderFeedbacksNumLineItems(aecOrderFeedbacks) {
let totalNumberLineItems = 0;
aecOrderFeedbacks.forEach(aecOrderFeedback => {
totalNumberLineItems += getAecOrderFeedbackNumLineItems(aecOrderFeedback);
});
return totalNumberLineItems;
}
function buildTransmissionFileTrailer(aecOrderFeedbacks) {
const type = '"E"';
const totalNumberOrders = `"${aecOrderFeedbacks.length}"`;
const TotalNumberItems = `"${getAecOrderFeedbacksNumItems(aecOrderFeedbacks)}"`;
const TotalNumberLineItems = `"${getAecOrderFeedbacksNumLineItems(aecOrderFeedbacks) + 2}"`;
return `${type},${totalNumberOrders},${TotalNumberItems},${TotalNumberLineItems}`;
}
function buildTransmissionFileHeader(transmissionDate) {
const type = '"F"';
const transDate = `"${formatDate(transmissionDate)}"`;
const description = `"POSTED <BEGINNING> TO ${formatDate(transmissionDate)} By Batch #"`;
return `${type},${transDate},${description}`;
}
function writeFinalLineToStream(writeStream, line) {
const newLine = `${line}\n`;
writeStream.end(newLine);
}
function writeLineToStream(writeStream, line) {
const newLine = `${line}\n`;
writeStream.write(newLine);
}
function serializeAecTransmissionFileContainer(writeStream, aecTransmissionFileContainer) {
writeLineToStream(writeStream, aecTransmissionFileContainer.transmissionFileHeader);
aecTransmissionFileContainer.aecOrderFeedbacks.forEach(aecOrderFeedback => {
writeLineToStream(writeStream, aecOrderFeedback.orderHeader);
writeLineToStream(writeStream, aecOrderFeedback.customerBillTo);
writeLineToStream(writeStream, aecOrderFeedback.customerShipTo);
aecOrderFeedback.items.forEach(item => {
writeLineToStream(writeStream, item);
});
writeLineToStream(writeStream, aecOrderFeedback.orderTrailer);
});
writeFinalLineToStream(writeStream, aecTransmissionFileContainer.transmissionFileTrailer);
}
// *********** Public Functions ***********
function convertNetsuiteBillingAddressToAecBillingAddress(netsuiteBillingAddress) {
return {
name: netsuiteBillingAddress.addressee,
address1: netsuiteBillingAddress.address1,
address2: netsuiteBillingAddress.address2,
city: netsuiteBillingAddress.city,
state: netsuiteBillingAddress.state,
zip: netsuiteBillingAddress.zip,
country: convertCountryName(netsuiteBillingAddress.country),
};
}
function convertNetsuiteShippingAddressToAecShippingAddress(netsuiteShippingAddress) {
return {
name: netsuiteShippingAddress.addressee,
address1: netsuiteShippingAddress.address1,
address2: netsuiteShippingAddress.address2,
city: netsuiteShippingAddress.city,
state: netsuiteShippingAddress.state,
zip: netsuiteShippingAddress.zip,
country: convertCountryName(netsuiteShippingAddress.country),
};
}
function convertNetsuiteProductToAecProduct(netsuiteProduct, aecOrderAction) {
if (!isAecProduct(netsuiteProduct.name) && !isFyeProduct(netsuiteProduct.name)) {
throw new Error('Must be an AEC or FYE product id');
}
let quantityOrdered = 0;
let quantityShipped = 0;
let quantityReturned = 0;
let quantityBackordered = 0;
let quantityCanceled = 0;
if (aecOrderAction === AecOrderAction.Backorder) {
quantityOrdered = netsuiteProduct.quantity;
quantityBackordered = netsuiteProduct.quantity;
} else if (aecOrderAction === AecOrderAction.Ship) {
quantityOrdered = netsuiteProduct.quantity;
quantityShipped = netsuiteProduct.quantity;
} else if (aecOrderAction === AecOrderAction.Cancel) {
quantityOrdered = netsuiteProduct.quantity;
quantityCanceled = netsuiteProduct.quantity;
} else if (aecOrderAction === AecOrderAction.Return) {
quantityReturned = netsuiteProduct.quantity;
}
let consumerExtendedPrice = 0;
if (aecOrderAction === AecOrderAction.Ship || aecOrderAction === AecOrderAction.Return) {
consumerExtendedPrice = netsuiteProduct.amount * netsuiteProduct.quantity;
}
let aecUnitPrice = 0;
let aecExtendedPrice = 0;
if (!isFyeProduct(netsuiteProduct.name) && aecOrderAction === AecOrderAction.Ship) {
aecUnitPrice = calcAecUnitPrice(netsuiteProduct.amount);
aecExtendedPrice = aecUnitPrice * netsuiteProduct.quantity;
}
return {
orderingProductId: netsuiteProduct.name,
aecProductId: netsuiteProduct.name,
quantityOrdered,
quantityShipped,
quantityReturned,
quantityBackordered,
quantityCanceled,
consumerUnitPrice: netsuiteProduct.amount,
consumerExtendedPrice,
aecUnitPrice,
aecExtendedPrice,
orderAction: aecOrderAction !== undefined ? aecOrderAction : AecOrderAction.Backorder,
};
}
function convertNetsuiteProductsToAecProducts(netsuiteProducts, aecOrderAction) {
const aecProducts = [];
netsuiteProducts.forEach(netsuiteProduct => {
const aecProduct = convertNetsuiteProductToAecProduct(netsuiteProduct, aecOrderAction);
aecProducts.push(aecProduct);
});
return aecProducts;
}
function createAecOrderFeedback({
vendorInvoiceDate,
vendorOrderId,
transactionDate,
vendorShipDate,
purchaseOrderInternalId,
salesOrderExternalId,
aecBillingAddress,
aecShippingAddress,
aecProducts,
}) {
const aecOrderFeedback = {
orderHeader: buildOrderHeader({
vendorInvoiceDate,
vendorOrderId,
transactionDate,
purchaseOrderInternalId,
salesOrderExternalId,
}),
customerBillTo: buildCustomerBillTo(aecBillingAddress),
customerShipTo: buildCustomerShipTo(aecShippingAddress),
items: buildOrderLineItems(aecProducts),
orderTrailer: buildOrderTrailer(aecProducts, vendorShipDate),
};
return aecOrderFeedback;
}
function dumpAecOrderFeedback(aecOrderFeedback) {
console.log(aecOrderFeedback.orderHeader);
console.log(aecOrderFeedback.customerBillTo);
console.log(aecOrderFeedback.customerShipTo);
aecOrderFeedback.items.forEach(item => {
console.log(item);
});
console.log(aecOrderFeedback.orderTrailer);
}
function aecOrderFeedbacksEqual(aecOrderFeedback1, aecOrderFeedback2) {
if (aecOrderFeedback1.orderHeader !== aecOrderFeedback2.orderHeader) {
return false;
}
if (aecOrderFeedback1.customerBillTo !== aecOrderFeedback2.customerBillTo) {
return false;
}
if (aecOrderFeedback1.customerShipTo !== aecOrderFeedback2.customerShipTo) {
return false;
}
if (aecOrderFeedback1.items.length !== aecOrderFeedback2.items.length) {
return false;
}
let itemsEqual = true;
aecOrderFeedback1.items.forEach((aecOrderFeedbackItem1, index) => {
const aecOrderFeedbackItem2 = aecOrderFeedback2.items[index];
if (aecOrderFeedbackItem1 !== aecOrderFeedbackItem2) {
itemsEqual = false;
}
});
if (!itemsEqual) {
return false;
}
if (aecOrderFeedback1.orderTrailer !== aecOrderFeedback2.orderTrailer) {
return false;
}
return true;
}
function createAecTransmissionFileContainer(transmissionDate, aecOrderFeedbacks) {
const aecTransmissionFileContainer = {
transmissionFileHeader: buildTransmissionFileHeader(transmissionDate),
aecOrderFeedbacks,
transmissionFileTrailer: buildTransmissionFileTrailer(aecOrderFeedbacks),
};
return aecTransmissionFileContainer;
}
function dumpAecTransmissionFileContainer(aecTransmissionFileContainer) {
console.log(aecTransmissionFileContainer.transmissionFileHeader);
aecTransmissionFileContainer.aecOrderFeedbacks.forEach(aecOrderFeedback => {
dumpAecOrderFeedback(aecOrderFeedback);
});
console.log(aecTransmissionFileContainer.transmissionFileTrailer);
}
function aecTransmissionFileContainersEqual(aecTransmissionFileContainer1, aecTransmissionFileContainer2) {
if (aecTransmissionFileContainer1.transmissionFileHeader !== aecTransmissionFileContainer2.transmissionFileHeader) {
return false;
}
let aecOrderFeedbacksAreEqual = true;
aecTransmissionFileContainer1.aecOrderFeedbacks.forEach((aecOrderFeedback1, index) => {
if (!aecOrderFeedbacksEqual(aecOrderFeedback1, aecTransmissionFileContainer2.aecOrderFeedbacks[index])) {
aecOrderFeedbacksAreEqual = false;
}
});
if (!aecOrderFeedbacksAreEqual) {
return false;
}
if (aecTransmissionFileContainer1.transmissionFileTrailer !== aecTransmissionFileContainer2.transmissionFileTrailer) {
return false;
}
return true;
}
function generateAecTransmissionFileName() {
const date = new Date();
const aoft = 'AOFT';
const month = date.getMonth() + 1; // month is zero based
const formattedMonth = `0${month}`.slice(-2);
const day = date.getDate();
const formattedDay = `0${day}`.slice(-2);
const hours = date.getHours();
const formattedHours = `0${hours}`.slice(-2);
const minutes = date.getMinutes();
const formattedMinutes = `0${minutes}`.slice(-2);
const seconds = date.getSeconds();
const formattedSeconds = `0${seconds}`.slice(-2);
const milliSeconds = date.getMilliseconds();
const formattedMilliSeconds = `00${milliSeconds}`.slice(-3);
return `${aoft}${formattedMonth}${formattedDay}-${formattedHours}${formattedMinutes}${formattedSeconds}${formattedMilliSeconds}`;
}
async function createAecTransmissionFile(aecFile, aecTransmissionFileContainer) {
try {
const stream = fs.createWriteStream(aecFile);
return new Promise(resolve => {
stream.on('finish', () => {
resolve(true);
});
stream.on('error', () => {
resolve(false);
});
serializeAecTransmissionFileContainer(stream, aecTransmissionFileContainer);
});
} catch (err) {
deleteFile(aecFile); // clean up just in case the file got created
throw new Error(err);
}
}
module.exports = {
AecOrderAction,
convertNetsuiteBillingAddressToAecBillingAddress,
convertNetsuiteShippingAddressToAecShippingAddress,
convertNetsuiteProductToAecProduct,
convertNetsuiteProductsToAecProducts,
createAecOrderFeedback,
dumpAecOrderFeedback,
aecOrderFeedbacksEqual,
createAecTransmissionFileContainer,
dumpAecTransmissionFileContainer,
aecTransmissionFileContainersEqual,
generateAecTransmissionFileName,
createAecTransmissionFile,
};