To update document number of historical transactions,
- First, we need to check the “Allow Override” checkbox in the set up auto generated number page
define(['N/record', 'N/search', 'N/format'],
function (record, search, format) {
"use strict";
const TYPE_TRANSFER_ORDER = 48;
const TYPE_INV_ADJUSTMENT = 11;
const TYPE_VEND_RETURN_AUTH = 43;
const TYPE_VEND_PAYMENT = 18;
const TYPE_VEND_PREPAYMENT = 79;
const TYPE_VEND_PREPAYMENT_APP = 80;
const TYPE_INVOICE = 7;
const TYPE_BILL = 17;
const TYPE_PAYMENT = 9;
const TYPE_DEPOSIT = 4;
/**
* Generates a document number based on the record details, transaction type, fiscal year, and prefix values.
* Updates the relevant fields and locks/unlocks child records as required.
*
* @param {Object} newRecord - The NetSuite record object for which the document number is generated.
* @param {string} transactionType - The type of transaction (e.g., 'check', 'inboundshipment', 'vendorprepaymentapplication').
*/
function generateDocumentNumber(transactionType, tranDate, subsidiary) {
try {
let fiscalYear = getFiscalYearNew(tranDate);
let parentRecord = fetchParentRecord(transactionType);
let childRecord;
if (parentRecord && parentRecord.internalId) {
childRecord = fetchChildRecord(subsidiary, parentRecord.internalId, fiscalYear);
} else {
return {};
}
let fieldValues;
if ((childRecord.isLocked) == false) {
fieldValues = { 'custrecord_jj_sub_rec_locked': true };
submitFieldsToRecord('customrecord_jj_sub_doc_number_config', childRecord.internalId, fieldValues);
fieldValues = { 'custrecord_jj_total_rec_count': Number(parentRecord.numberOfRecords) + 1 };
submitFieldsToRecord('customrecord_jj_document_number_config', parentRecord.internalId, fieldValues)
let documentNumber = (parentRecord.prefixValue) + '-' + (childRecord.prefixValue) + '-' + ((fiscalYear).toString().slice(-2)) + '-' + padNumber(Number(childRecord.sequenceNumber) + 1, 5);
fieldValues = { 'custrecord_jj_seq_numbers': padNumber(Number(childRecord.sequenceNumber) + 1, 5), 'custrecord_jj_sub_rec_locked': false };
submitFieldsToRecord('customrecord_jj_sub_doc_number_config', childRecord.internalId, fieldValues)
return documentNumber;
}
} catch (e) {
log.error('Error @generateDocumentNumber', e);
return '';
}
}
/**
* Updates the specified fields of a record with the given values.
*
* @param {string} transactionType - The type of the record (e.g., sales order, invoice).
* @param {number} recordId - The internal ID of the record to update.
* @param {Array<string>} fieldIds - An array of field IDs to update.
* @param {Object} values - An object containing the field values to set.
*/
function submitFieldsToRecord(recordType, recordId, fieldValues) {
try {
record.submitFields({
type: recordType,
id: recordId,
values: fieldValues,
options: {
enablesourcing: true,
ignoreMandatoryFields: true
}
});
} catch (e) {
log.error('Error @submitFieldsToRecord', e);
}
}
/**
* Retrieves the last two digits of the fiscal year from a given transaction date.
*
* @param {string} tranDate - The transaction date in string format.
* @returns {string|null} - The last two digits of the fiscal year, or null if an error occurs.
*/
function getFiscalYearNew(tranDate) {
try {
let dateOBJ = format.parse({
value: tranDate,
type: format.Type.DATE
});
let year = dateOBJ.getFullYear();
return year;
} catch (e) {
log.error('Error @getFiscalYearNew', e);
return '';
}
}
/**
* Retrieves the corresponding transaction type ID based on the given transaction type.
*
* @param {string} transactionType - The type of transaction (e.g., 'transferorder', 'check').
* @returns {number} - The transaction type ID, or null if the transaction type is not recognized.
*/
function getTransactionTypeId(transactionType) {
try {
switch (transactionType) {
case 'transferorder':
return TYPE_TRANSFER_ORDER;
case 'inventoryadjustment':
return TYPE_INV_ADJUSTMENT;
case 'vendorreturnauthorization':
return TYPE_VEND_RETURN_AUTH;
case 'vendorpayment':
return TYPE_VEND_PAYMENT;
case 'vendorprepaymentapplication':
return TYPE_VEND_PREPAYMENT_APP;
case 'vendorprepayment':
return TYPE_VEND_PREPAYMENT;
case 'invoice':
return TYPE_INVOICE;
case 'vendorbill':
return TYPE_BILL;
case 'customerpayment':
return TYPE_PAYMENT;
case 'deposit':
return TYPE_DEPOSIT;
default:
return null;
}
} catch (e) {
log.error('Error @ getTransactionTypeId', e);
return '';
}
}
/**
* Fetches the parent record based on the transaction type and optional payment type.
*
* @param {string} transactionType - The type of the transaction to filter records.
* @param {string} [paymentType] - (Optional) The payment type to refine the filter further.
* @returns {Object|null} - An object containing record details, or null if no records are found or an error occurs.
*/
function fetchParentRecord(transactionType) {
try {
let transactionTypeId = getTransactionTypeId(transactionType);
if (!transactionTypeId) {
return null;
}
let filters = [
["custrecord_jj_transaction_type", "anyof", transactionTypeId]
];
let parentSearch = search.create({
type: 'customrecord_jj_document_number_config',
filters: filters,
columns: [
search.createColumn({ name: "internalid" }),
search.createColumn({ name: "custrecord_jj_transaction_type" }),
search.createColumn({ name: "custrecord_jj_doc_prefix" }),
search.createColumn({ name: "custrecord_jj_total_rec_count" })
]
});
let pagedData = parentSearch.runPaged({ pageSize: 1000 });
let resultObject = null;
outerLoop:
for (let i = 0; i < pagedData.pageRanges.length; i++) {
let page = pagedData.fetch({ index: i });
for (let j = 0; j < page.data.length; j++) {
let result = page.data[j];
resultObject = {
internalId: result.getValue({ name: "internalid" }),
transactionType: result.getValue({ name: "custrecord_jj_transaction_type" }),
prefixValue: result.getValue({ name: "custrecord_jj_doc_prefix" }),
numberOfRecords: result.getValue({ name: "custrecord_jj_total_rec_count" }) || 0
};
break outerLoop;
}
}
return resultObject || {};
} catch (e) {
log.error('Error @ fetchParentRecord', e);
return {};
}
}
/**
* Fetches the child record based on the given subsidiary and parent record ID.
*
* @param {string} subsidiary - The subsidiary ID to filter child records.
* @param {string} parentRecordId - The internal ID of the parent record to filter child records.
* @returns {Object|null} - An object containing child record details, or null if no records are found or an error occurs.
*/
function fetchChildRecord(subsidiary, parentRecordId, fiscalYear) {
try {
let childSearch = search.create({
type: 'customrecord_jj_sub_doc_number_config',
filters: [
["custrecord_jj_parent_doc_config", "anyof", parentRecordId],
"AND",
["custrecord_jj_doc_subsidiary", "anyof", subsidiary]
],
columns: [
search.createColumn({ name: "internalid" }),
search.createColumn({ name: "custrecord_jj_doc_subsidiary" }),
search.createColumn({ name: "custrecord_jj_sub_code" }),
search.createColumn({ name: "custrecord_jj_parent_doc_config" }),
search.createColumn({ name: "custrecord_jj_sub_rec_locked" }),
search.createColumn({ name: "custrecord_jj_seq_numbers" }),
search.createColumn({ name: "custrecord_jj_doc_fiscal_year" })
]
});
let pagedData = childSearch.runPaged({ pageSize: 1000 });
let matchedRecord = null;
let fallbackPrefix = null;
outerLoop:
for (let i = 0; i < pagedData.pageRanges.length; i++) {
let page = pagedData.fetch({ index: i });
for (let j = 0; j < page.data.length; j++) {
let record = page.data[j];
let fiscalYearInResult = record.getValue({ name: "custrecord_jj_doc_fiscal_year" });
if (!fallbackPrefix) {
fallbackPrefix = record.getValue({ name: "custrecord_jj_sub_code" });
}
if (fiscalYearInResult === String(fiscalYear)) {
matchedRecord = record;
break outerLoop;
}
}
}
if (matchedRecord) {
return {
internalId: matchedRecord.getValue({ name: "internalid" }),
subsidiary: matchedRecord.getValue({ name: "custrecord_jj_doc_subsidiary" }),
prefixValue: matchedRecord.getValue({ name: "custrecord_jj_sub_code" }),
parentRecord: matchedRecord.getValue({ name: "custrecord_jj_parent_doc_config" }),
isLocked: matchedRecord.getValue({ name: "custrecord_jj_sub_rec_locked" }),
sequenceNumber: matchedRecord.getValue({ name: "custrecord_jj_seq_numbers" }),
fiscalYear: matchedRecord.getValue({ name: "custrecord_jj_doc_fiscal_year" })
};
}
return {};
} catch (e) {
log.error('Error @ fetchChildRecord', e);
return {};
}
}
/**
* Pads a given number with leading zeros to match the specified length.
*
* @param {number} number - The number to be padded.
* @param {number} length - The desired total length of the output string.
* @returns {string} - The padded number as a string, or an empty string if an error occurs.
*/
function padNumber(number, length) {
try {
return String(number).padStart(length, '0');
} catch (e) {
log.error("error @ padNumber", e);
return '';
}
}
/**
* Retrieves input data (transactions to process) for the Map/Reduce script.
* Fetches transactions created on or after January 1, 2025, for multiple record types.
*
* @returns {Object} - The search result set containing transaction records to be processed.
*/
function getInputData() {
try {
let filters = [
["type", "anyof", "TrnfrOrd"],
"AND",
["trandate", "onorafter", "01-Jan-2025"],
"AND",
["mainline", "is", "T"],
"AND",
["custbody_jj_doc_num_updated", "is", "F"]
];
let transactionSearch = search.create({
type: "transaction",
settings: [
{ name: "consolidationtype", value: "ACCTTYPE" },
{ name: "includeperiodendtransactions", value: "F" }
],
filters: filters,
columns: [
search.createColumn({ name: "subsidiary", label: "Subsidiary" }),
search.createColumn({ name: "type", label: "Type" }),
search.createColumn({ name: "trandate", label: "Date" }),
search.createColumn({ name: "internalid", label: "Internal ID" })
]
});
let pagedData = transactionSearch.runPaged({ pageSize: 1000 });
log.debug("count", pagedData);
let allResults = [];
pagedData.pageRanges.forEach(function (pageRange) {
let page = pagedData.fetch({ index: pageRange.index });
page.data.forEach(function (result) {
allResults.push(result);
});
});
return allResults;
} catch (e) {
log.error('Error @getInputData', e);
return [];
}
}
/**
* map - Processes each transaction record to generate and set a new document number (`tranid`) for each transaction.
*
* @param {Object} context - The context object that contains the data for the current iteration.
* @param {string} context.value - The transaction data to process.
*/
function map(context) {
try {
let searchResult = JSON.parse(context.value);
let recordId = searchResult.id;
let transactionType = searchResult.recordType;
let subsidiary = searchResult.values.subsidiary?.[0]?.value;
let tranDate = searchResult.values.trandate;
log.debug("recordId", recordId);
log.debug("transactionType", transactionType);
log.debug("subsidiary", subsidiary);
log.debug("tranDate", tranDate);
let documentNumber;
switch (transactionType) {
case 'transferorder':
case 'inventoryadjustment':
case 'vendorreturnauthorization':
case 'vendorpayment':
case 'vendorprepayment':
case 'vendorprepaymentapplication':
case 'vendorbill':
case 'deposit':
case 'invoice':
case 'customerpayment':
documentNumber = generateDocumentNumber(transactionType, tranDate, subsidiary);
break;
default:
break;
}
log.debug("documentNumber", documentNumber)
let fieldValues;
if (documentNumber) {
fieldValues = { 'tranid': documentNumber,
'custbody_jj_doc_num_updated': true
};
submitFieldsToRecord(transactionType, recordId, fieldValues);
}
} catch (e) {
log.error('Error in map function', e);
}
}
/**
* @description Summarize function for handling errors, and summarizing the execution.
* @param {Object} summary - Summary object for MapReduce script execution
*/
function summarize(summary) {
try {
log.audit({
title: 'MapReduce Script Summary',
details: `Processed ${summary.successCount} records, and encountered ${summary.errorCount} errors.`
});
} catch (e) {
log.error('Error @summarize', e);
}
}
return {
getInputData: getInputData,
map: map,
summarize: summarize
};
});