/*
* transformFunction:
*
* This function transforms a set of invoice records into the desired EDI JSON format.
*
* The function is passed one ‘options’ argument with the following fields:
* ‘record’ – array [] representing a record set.
* ‘settings’ – all custom settings in scope for the transform currently running.
*
* The function returns the transformed EDI JSON object.
* Throwing an exception will return an error for the record.
*/
function transform(options) {
// Helper function to format dates as DDMMYY
// Helper function to format dates as YYMMDD
function formatDateDDMMYY(dateStr) {
if (!dateStr) return ”; // Return empty string if dateStr is falsy
const [ month, day, year] = dateStr.split(‘/’); // Split the input DD/MM/YYYY
if ( !month ||!day || !year) return ”; // Return empty string if any component is missing
return `${year.slice(-2)}${month.padStart(2, ‘0’)}${day.padStart(2, ‘0’)}`; // Format as YYMMDD
}
function formatDateYYYYMMDD(dateStr) {
if (!dateStr) return ”; // Return empty string if dateStr is falsy
const [month, day, year] = dateStr.split(‘/’); // Split date into components
if (!month || !day || !year) return ”; // Return empty string if any component is missing
// Pad month and day with leading zeros if necessary
const paddedMonth = month.padStart(2, ‘0’);
const paddedDay = day.padStart(2, ‘0’);
return `${year}${paddedMonth}${paddedDay}`; // Return formatted date as YYYYMMDD
}
// Helper function to format time as HHMM in 24-hour format
function formattedTime(timeStr, period) {
if (!timeStr || !period) return ”; // Return empty string if timeStr or period is falsy
let [hours, minutes] = timeStr.split(‘:’);
// Convert 12-hour time to 24-hour time
if (period.toLowerCase() === ‘pm’ && parseInt(hours) < 12) {
hours = (parseInt(hours) + 12).toString().padStart(2, ‘0’);
} else if (period.toLowerCase() === ‘am’ && hours === ’12’) {
hours = ’00’;
}
return `${hours}${minutes}`;
}
// Helper function to extract and format date and time as per EDI standard
function formatDateTime(dateTimeStr) {
if (!dateTimeStr) return { date: ”, time: ” }; // Return empty if dateTimeStr is falsy
const [datePart, timePart, period] = dateTimeStr.split(/[s:]+/);
if (!datePart || !timePart || !period) return { date: ”, time: ” };
return {
date: formatDateDDMMYY(datePart), // Use the updated date formatting function
time: formattedTime(timePart, period)
};
}
// Helper function to remove % symbol from a tax rate string
function removePercentageSymbol(value) {
if (!value) return ”; // Return empty string if value is falsy
return value.replace(‘%’, ”); // Remove % symbol
}
// Helper function to adjust the customer name if it exceeds 35 characters
function adjustCustomerName(name) {
if (!name) return ”; // Return empty string if the name is falsy
if (name.length <= 35) return name; // Return the original name if within the limit
return `${name.slice(0, 35)}:${name.slice(35)}`; // Insert ‘:’ at the 35th character
}
// Helper function to combine shipDate and time
function combineDateAndTime(updatedInvoiceDate, time) {
if (!updatedInvoiceDate || !time) return ”; // Return empty string if any value is falsy
return `${updatedInvoiceDate}${time}`; // Concatenate the date and time
}
// Helper function to generate a message reference number
function generateMessageReference(invoiceNumber, invoiceDate) {
const formattedDate = formatDateDDMMYY(invoiceDate);
return `${invoiceNumber}-${formattedDate}`; // Format: InvoiceNumber-DDMMYY
}
try {
// Ensure the record is an array (recordSet)
const recordSet = Array.isArray(options.record) ? options.record : [options.record];
if (recordSet.length === 0) {
throw new Error(‘Record set is empty.’);
}
// Identify the first record in the set, assuming it has the main invoice details
const mainRecord = recordSet[0];
// Retrieve necessary fields from the input data
const invoiceNumber = mainRecord[“Document Number”] || ”;
const poNumber = mainRecord[“PO/Cheque Number”] || ”;
const invoiceDate = mainRecord[“Date”] || ”; // Invoice date in DD/MM/YYYY
const updatedInvoiceDate = formatDateYYYYMMDD(invoiceDate);
const shipDate = mainRecord[“ShipDate”] || ”;
const time = “1012”;
//const documentDateTime = mainRecord[“Document date”] || ”; // Document date and time in DD/MM/YYYY HH:MM AM/PM
// Extract the formatted document date and time using our helper function
const { date: formattedDocumentDate, time: formattedDocumentTime } = formatDateTime(invoiceDate);
const numberOfItems = recordSet.length;
const taxRate = removePercentageSymbol(mainRecord[“Tax Rate”]);
// Prepare NAD Segment for the Buyer (Customer)
const nadSegments = [
{
“Party Qualifier”: “BY”, // “BY” indicates Buyer
“Party Identification Code”: mainRecord[“StoreCode”] || ”, // Unique Store Code
“Name”: mainRecord[“ShipToName”] || ”, // ShipTo Name
“Customer Ref number”: mainRecord[“StoreCode”] || ”, // Store Code as Reference
“Code List Qualifier”: “160”,
“Code List Responsible Agency”: “92”,
“Street”: mainRecord[“ShipToAddress1″] || ”, // ShipTo Address Line 1
“City”: mainRecord[“ShipToCity”] || ”, // ShipTo City
“State”: mainRecord[“ShipToState”] || ”, // ShipTo State
“Postal Code”: mainRecord[“ShipToZip”] || ”, // ShipTo Zip
“Country”: mainRecord[“ShipToCountry”] || ” // ShipTo Country
}
];
// Prepare DTM segments (Date/Time/Period)
const dtmSegments = [
{
“DATE/TIME/PERIOD”: {
“Date qualifier 1″:”137”,
“Date qualifier 2″:”2”,
“Date/time/period qualifier”: “17”, // Invoice date qualifier
“Date/time/period”: combineDateAndTime(updatedInvoiceDate,time),
“Date/time/period 2”: shipDate,
“Date/time/period format qualifier”: “203”,
“Date/time/period format qualifier 2”: “102”
}
},
];
// Prepare Segment Group 1 (References)
const segmentGroup1 = [
{
“REFERENCE”: {
“Reference qualifier”: “ON”, // Unique Invoice Reference
“Reference number”: invoiceNumber
},
“DTM”: [
{
“DATE/TIME/PERIOD”: {
“Date/time/period qualifier”: “35”, // Document creation date/time qualifier
“Date/time/period”: formattedDocumentDate,
“Date/time/period format qualifier”: “102”
}
}
]
}
];
// Prepare Segment Group 25 (Line Items)
const segmentGroup25 = recordSet.map((item, index) => {
// Format the amount for each line item
const amount = parseFloat(item[“Amount”] || “0”).toFixed(2); // Parse and ensure two decimal places
const taxRate = removePercentageSymbol(item[“Tax Rate”] || ”);
return {
“LINE ITEM NUMBER”: (index + 1).toString(),
“ITEM NUMBER IDENTIFICATION”: {
“Item number”: item[“Item”] || ”,
“Item number type, coded”: “SRV”
},
“QTY”: [
{
“QUANTITY DETAILS”: {
“Quantity qualifier”: “113”,
“Quantity”: item[“Quantity”] || “0”,
“Measure unit qualifier”: “EA” || ”
}
}
],
“PIA”: [
{
“LINE ITEM NUMBER”: (index + 1).toString(),
“ITEM NUMBER IDENTIFICATION”: {
“Item number”: item[“Item”] || ”,
“Item number type, coded”: “IN”
},
“PRODUCT ID. FUNCTION QUALIFIER”: “5”
}
],
“IMD”:[{
“Item Description”: item[“ItemDescription”],
}],
“MONETARY AMOUNT”: {
“Monetary amount type qualifier”: “128”,
“Monetary amount”: amount, // Use parsed and formatted amount
},
“PRICE INFORMATION”: {
“Price qualifier”: “AAA”,
“Price”: item[“UnitPrice”] || “0.00”,
“Price type, coded”: “CT”,
“Price type qualifier”: “EA”
},
“DUTY/TAX/FEE”: {
“DUTY/TAX/FEE FUNCTION QUALIFIER”: “7”,
“Duty/tax/fee type, coded”: item[“Tax”] || ”,
“Duty/tax/fee rate”: amount, // Use parsed and formatted amount
“Duty/Tax/fee/ Rate”: taxRate || “0”
}
};
});
// Prepare Separate MOA Segment for Tax Total
const taxTotalSegment = {
“MOA”: {
“code”: “124”,
“rate”: parseFloat(mainRecord[“Tax Total”] || “0”).toFixed(2)
}
};
// Prepare Segment Group 48 (Transaction Total)
const segmentGroup48 = [
{
“TRANSACTION TOTAL”: {
“Monetary amount type qualifier”: “124”,
“Monetary amount”: parseFloat(mainRecord[“Transaction Total”]).toFixed(2),
}
}
];
// Function to count the total number of item lines
function getTotalItemLines(recordSet) {
return recordSet.length; // Count the number of line items
}
const totalItemLines = getTotalItemLines(segmentGroup25);
// Generate the message reference number using the new function
const messageReferenceNumber = generateMessageReference(invoiceNumber, invoiceDate);
// Construct the final EDI JSON object
const transformedRecord = {
“SYNTAX IDENTIFIER”: {
“Syntax identifier”: “UNOC”,
“Syntax version number”: “3”
},
“INTERCHANGE SENDER”: {
“Sender identification”: “OXTL”,
“Partner identification code qualifier”: “ZZZ”
},
“INTERCHANGE RECIPIENT”: {
“Recipient identification”: mainRecord.StoreCode,
“Partner identification code qualifier”: “ZZZ”
},
“DATE/TIME OF PREPARATION”: {
“Date of preparation”: formatDateDDMMYY(invoiceDate),
“Time of preparation”: “1012” // Now dynamically fetched and formatted from input
},
“Interchange control reference”: “115883”,
“Communications agreement ID”: “EANCOM”,
“MESSAGE REFERENCE NUMBER”: messageReferenceNumber, // Updated
“MESSAGE IDENTIFIER”: {
“Message type identifier”: “ORDRSP”,
“Message type version number”: “D”,
“Message type release number”: “01B”,
“Controlling agency”: “UN”,
“Association assigned code”: “EAN008”
},
“DOCUMENT/MESSAGE NAME”: {
“Document/message name, coded”: “231”
},
“DOCUMENT/MESSAGE NUMBER”: poNumber,
“DTM”: dtmSegments,
“NAD”: nadSegments, // Added the NAD segment for buyer/customer details
“INVOICE NUMBER”: {
“Invoice number”: invoiceNumber
},
“Segment Group 1”: segmentGroup1,
“Segment Group 25”: segmentGroup25,
“Tax Total MOA”: taxTotalSegment,
“Segment Group 48”: segmentGroup48,
“SECTION IDENTIFICATION”: “S”,
“CNT”: [
{
“CONTROL”: {
“Control qualifier”: “2”,
“Control value”: totalItemLines.toString() // Total number of item lines
}
}
],
“Number of Items”:numberOfItems,
“NUMBER OF SEGMENTS IN A MESSAGE”: (10 + (numberOfItems * 6)+2).toString(), // Adjust based on actual segment count
“MESSAGE REFERENCE NUMBER(UNT020)”: messageReferenceNumber, // Updated
“Interchange control count”: “1”,
“Interchange control reference(UNZ020)”: “73920002”
};
// Return the transformed EDI JSON object
return transformedRecord;
} catch (error) {
// Throw an exception with the error message
throw new Error(`Transformation Error: ${error.message}`);
}
}