/** *@NApiVersion 2.x *@NScriptType restlet */ define(['N/record', 'N/search', 'N/error', 'N/log', 'N/format', 'N/runtime'], function (record, search, error, log, format, runtime) { // Logging utility - will be initialized in post() var logs = []; var addLog; function initLogging() { logs = []; addLog = function(level, message, details) { var logEntry = { timestamp: new Date().toISOString(), level: level, message: message, details: details || null }; logs.push(logEntry); if (level === 'error') { log.error(message, details); } else if (level === 'audit') { log.audit(message, details); } else { log.debug(message, details); } }; } function findById(type, id) { return record.load({ type: type, id: id, isDynamic: true }); } function isControlField(fieldId, sublistId) { var controlFields = ['label', 'defaultbilling', 'defaultshipping', 'isresidential', 'isinactive']; return controlFields.indexOf(fieldId.toLowerCase()) !== -1; } function processFieldValue(field, value) { if (!field) { if (typeof value === 'string') { var lowerValue = value.toLowerCase(); if (lowerValue === 't' || lowerValue === 'f' || lowerValue === 'true' || lowerValue === 'false') { return value === 't' || value === 'T' || value === 'true' || value === 'True' || value === true; } // Try to parse as date using N/format var datePattern = /^\d{1,2}\/\d{1,2}\/\d{4}$/; if (datePattern.test(value)) { try { return format.parse({ value: value, type: format.Type.DATE }); } catch (e) { return value; } } } return value; } if (field.type === 'date') { if (value) { try { return format.parse({ value: value, type: format.Type.DATE }); } catch (e) { log.debug('Error parsing date', {value: value, error: e.message}); return null; } } else { return null; } } else if (field.type === 'checkbox') { return value === 't' || value === 'T' || value === 'true' || value === 'True' || value === true; } else { // For non-numeric field types, coerce number values to strings. // CsvHelper or JSON parsing can convert string values like // "29524481704" into JS numbers, which breaks text fields. if (typeof value === 'number' && field.type !== 'integer' && field.type !== 'float' && field.type !== 'currency' && field.type !== 'percent') { return String(value); } return value; } } function processSublist(rec, sublistId, items) { log.debug('Processing sublist', {sublistId: sublistId, itemCount: items ? items.length : 0}); try { var lineCount = rec.getLineCount({sublistId: sublistId}); for (var j = lineCount - 1; j >= 0; j--) { rec.removeLine({sublistId: sublistId, line: j}); } log.debug('Cleared existing lines', lineCount); for (var i = 0; i < items.length; i++) { var item = items[i]; log.debug('Processing line ' + i, { sublistId: sublistId, itemKeys: Object.keys(item) }); rec.selectNewLine({sublistId: sublistId}); var controlFields = {}; var subrecordFields = {}; for (var fieldId in item) { if (item.hasOwnProperty(fieldId)) { var value = item[fieldId]; if (value && typeof value === 'object' && !Array.isArray(value)) { subrecordFields[fieldId] = value; } else if (isControlField(fieldId, sublistId)) { controlFields[fieldId] = value; } else { controlFields[fieldId] = value; } } } log.debug('Field separation', { line: i, controlFields: Object.keys(controlFields), subrecordFields: Object.keys(subrecordFields) }); var fieldsSet = 0; for (var ctrlField in controlFields) { if (controlFields.hasOwnProperty(ctrlField)) { var ctrlValue = controlFields[ctrlField]; try { var processedValue = processFieldValue(null, ctrlValue); rec.setCurrentSublistValue({ sublistId: sublistId, fieldId: ctrlField, value: processedValue }); fieldsSet++; log.debug('Set control field', { fieldId: ctrlField, value: processedValue, line: i }); } catch (fieldError) { log.debug('Could not set control field ' + ctrlField, { error: fieldError.message, value: ctrlValue, line: i }); } } } log.debug('Control fields set on line ' + i, fieldsSet); for (var subrecordFieldId in subrecordFields) { if (subrecordFields.hasOwnProperty(subrecordFieldId)) { var subrecordData = subrecordFields[subrecordFieldId]; try { log.debug('Processing subrecord on current line', { line: i, subrecordFieldId: subrecordFieldId, dataKeys: Object.keys(subrecordData), fullData: JSON.stringify(subrecordData) }); var subrecord = rec.getCurrentSublistSubrecord({ sublistId: sublistId, fieldId: subrecordFieldId }); if (subrecord) { var subFieldsSet = 0; for (var subFieldId in subrecordData) { if (subrecordData.hasOwnProperty(subFieldId)) { var subValue = subrecordData[subFieldId]; try { var subField = null; try { subField = subrecord.getField({ fieldId: subFieldId }); } catch (getFieldError) { log.debug('Could not get field metadata for ' + subFieldId, getFieldError.message); } var processedSubValue = processFieldValue(subField, subValue); subrecord.setValue({ fieldId: subFieldId, value: processedSubValue }); subFieldsSet++; log.debug('Set subrecord value', { subrecordField: subrecordFieldId, fieldId: subFieldId, fieldType: subField ? subField.type : 'unknown', originalValue: subValue, processedValue: processedSubValue, line: i }); } catch (fieldError) { log.error('Could not set subrecord field ' + subFieldId, { error: fieldError.message, value: subValue, line: i, stack: fieldError.stack }); } } } log.debug('Subrecord fields set', { subrecordField: subrecordFieldId, fieldsSet: subFieldsSet, line: i }); } else { log.error('Could not get subrecord', { sublistId: sublistId, fieldId: subrecordFieldId, line: i }); } } catch (subrecordError) { log.error('Error processing subrecord', { sublistId: sublistId, line: i, fieldId: subrecordFieldId, error: subrecordError.message, stack: subrecordError.stack }); } } } rec.commitLine({sublistId: sublistId}); log.debug('Committed line ' + i); } log.debug('Sublist processing complete', { sublistId: sublistId, linesProcessed: items.length }); } catch (e) { log.error('Error processing sublist ' + sublistId, { error: e.message, stack: e.stack }); throw e; } } function post(context) { var startTime = new Date().getTime(); initLogging(); context.errors = []; var successCount = 0; var savedRecordIds = []; addLog('audit', 'Bulk Import Started', { recordType: context.recordType, recordCount: context.records ? context.records.length : 0, continueOnError: context.continueOnError || false }); try { for (var i = 0; i < context.records.length; i++) { var r = context.records[i]; var rec = null; try { if (!!r.id) { rec = findById(context.recordType, r.id); addLog('debug', 'Loaded existing record', { id: rec.id, index: i }); } else { rec = record.create({ type: context.recordType, isDynamic: true }); addLog('debug', 'Creating new record', { index: i }); } for (var field in r) { if (field !== 'id') { if (Array.isArray(r[field])) { addLog('debug', 'Processing sublist', { fieldId: field, itemCount: r[field].length, index: i }); try { processSublist(rec, field, r[field]); } catch (sublistError) { addLog('error', 'Error processing sublist', { fieldId: field, error: sublistError.message, index: i }); throw sublistError; } } else { var f = rec.getField({fieldId: field}); if (f) { var processedValue = processFieldValue(f, r[field]); rec.setValue(field, processedValue); } else { var list = rec.getSublist({sublistId: field}) if (list) { addLog('debug', 'Processing sublist via JSON parse (legacy)', { fieldId: field, index: i }); var items = JSON.parse(r[field]); processSublist(rec, field, items); } } } } } var savedId = rec.save(); successCount++; savedRecordIds.push({ index: i, id: savedId, action: r.id ? 'updated' : 'created' }); addLog('debug', 'Record saved successfully', { id: savedId, index: i, action: r.id ? 'updated' : 'created' }); } catch (e) { context.errors.push({ message: e.message, index: i }); addLog('error', 'Failed to process record', { index: i, error: e.message }); if (!context.continueOnError) { addLog('debug', 'Stopping due to error (continueOnError=false)'); break; } } } var currentScript = runtime.getCurrentScript(); addLog('audit', 'Bulk Import Completed', { totalRecords: context.records ? context.records.length : 0, successCount: successCount, errorCount: context.errors.length, duration: new Date().getTime() - startTime, remainingUsage: currentScript.getRemainingUsage() }); context.records = null; context.success = context.errors.length === 0; context.error = JSON.stringify(context.errors); context.successCount = successCount; context.savedRecords = savedRecordIds; context.logs = logs; context.duration = new Date().getTime() - startTime; context.governanceRemaining = currentScript.getRemainingUsage(); return context; } catch (e) { addLog('error', 'Bulk Import Failed', { error: e.message, stack: e.stack }); context.records = null; context.success = false; context.error = e.message; context.logs = logs; context.duration = new Date().getTime() - startTime; return context; } } return { post: post } });