define([‘N/record’, ‘N/search’],
function (record, search) {
“use strict”;
const LEAVE_PROJECT = ‘26088’;
const PROJECT_ALLOCATION_IDS = [“31442”, “31433”, “31443”];
/**
* Fetch Employee Work Calendar ID.
* @param {string} employeeId – Employee internal ID.
* @returns {string|null} Work calendar ID.
*/
function getEmployeeWorkCalendar(employeeId) {
try {
let employeeData = search.lookupFields({
type: search.Type.EMPLOYEE,
id: employeeId,
columns: [‘workcalendar’]
});
return employeeData.workcalendar?.[0]?.value || null;
} catch (e) {
log.error(‘Error fetching Employee Work Calendar’, e);
return null;
}
}
/**
* Fetch Work Calendar Hours Per Day.
* @param {string} calendarId – Work calendar internal ID.
* @returns {number} Work hours per day.
*/
function getWorkCalendarHours(calendarId) {
try {
let workCalendarData = search.lookupFields({
type: “workcalendar”,
id: calendarId,
columns: [‘workhoursperday’]
});
return parseFloat(workCalendarData.workhoursperday) || 0;
} catch (e) {
log.error(‘Error fetching Work Calendar Hours’, e);
return 0;
}
}
/**
* Fetch Project Allocations.
* @returns {Array} List of project allocations.
*/
function getProjectAllocations() {
try {
let projectAllocSearch = search.create({
type: “customrecord_jj_project_alloc”,
filters: [
[“isinactive”, “is”, “F”], // Only active records
“AND”,
[“internalid”, “anyof”, PROJECT_ALLOCATION_IDS], // Specific project allocation IDs
“AND”,
[“custrecord_jj_project”, “noneof”, LEAVE_PROJECT] // Exclude specific project
],
columns: [
search.createColumn({ name: “internalid” }),
search.createColumn({ name: “custrecord_jj_project_period” }),
search.createColumn({ name: “custrecord_jj_project” }),
search.createColumn({ name: “custrecord_jj_time_tracked_project” }),
search.createColumn({
name: “custrecord_jj_payroll_emp”,
join: “CUSTRECORD_JJ_PROJECT_PERIOD”
})
]
});
let results = [];
projectAllocSearch.run().each(function (result) {
results.push({
id: result.getValue({ name: “internalid” }),
projectPeriod: result.getValue({ name: “custrecord_jj_project_period” }), // Payroll ID
projectId: result.getValue({ name: “custrecord_jj_project” }),
trackedHours: parseFloat(result.getValue({ name: “custrecord_jj_time_tracked_project” }) || 0),
payrollEmpId: result.getValue({
name: “custrecord_jj_payroll_emp”,
join: “CUSTRECORD_JJ_PROJECT_PERIOD”
})
});
return true;
});
log.debug(“Project Allocations Retrieved”, results);
return results;
} catch (e) {
log.error(‘Error in getProjectAllocations’, e);
return e;
}
}
/**
* Defines the function that is executed at the beginning of the map/reduce process and generates the input data.
* @param {Object} inputContext
* @param {boolean} inputContext.isRestarted – Indicates whether the current invocation of this function is the first
* invocation (if true, the current invocation is not the first invocation and this function has been restarted)
* @param {Object} inputContext.ObjectRef – Object that references the input data
* @typedef {Object} ObjectRef
* @property {string|number} ObjectRef.id – Internal ID of the record instance that contains the input data
* @property {string} ObjectRef.type – Type of the record instance that contains the input data
* @returns {Array|Object|Search|ObjectRef|File|Query} The input data to use in the map/reduce process
* @since 2015.2
*/
function getInputData() {
try {
log.debug(‘Fetching Project Allocations’, ‘Starting to retrieve project allocation records…’);
return getProjectAllocations();
} catch (e) {
log.error(‘Error in getInputData’, e);
return e;
}
}
/**
* Defines the function that is executed when the map entry point is triggered. This entry point is triggered automatically
* when the associated getInputData stage is complete. This function is applied to each key-value pair in the provided
* context.
* @param {Object} mapContext – Data collection containing the key-value pairs to process in the map stage. This parameter
* is provided automatically based on the results of the getInputData stage.
* @param {Iterator} mapContext.errors – Serialized errors that were thrown during previous attempts to execute the map
* function on the current key-value pair
* @param {number} mapContext.executionNo – Number of times the map function has been executed on the current key-value
* pair
* @param {boolean} mapContext.isRestarted – Indicates whether the current invocation of this function is the first
* invocation (if true, the current invocation is not the first invocation and this function has been restarted)
* @param {string} mapContext.key – Key to be processed during the map stage
* @param {string} mapContext.value – Value to be processed during the map stage
* @since 2015.2
*/
function map(context) {
try {
let result = JSON.parse(context.value);
let projectAllocationId = result.id;
let trackedHours = result.trackedHours;
let payrollId = result.projectPeriod; // This is the Payroll ID
let payrollEmpId = result.payrollEmpId; // Already retrieved in getProjectAllocations()
log.debug(‘Calculated Tracked Hours’, {
projectAllocationId: projectAllocationId,
payrollId: payrollId,
trackedHours: trackedHours,
payrollEmpId: payrollEmpId
});
context.write({
key: payrollId,
value: {
projectAllocationId: projectAllocationId,
payrollEmpId: payrollEmpId,
totalTrackedHours: trackedHours
}
});
} catch (e) {
log.error(‘Error in map’, e);
return e;
}
}
/**
* Defines the function that is executed when the reduce entry point is triggered. This entry point is triggered
* automatically when the associated map stage is complete. This function is applied to each group in the provided context.
* @param {Object} reduceContext – Data collection containing the groups to process in the reduce stage. This parameter is
* provided automatically based on the results of the map stage.
* @param {Iterator} reduceContext.errors – Serialized errors that were thrown during previous attempts to execute the
* reduce function on the current group
* @param {number} reduceContext.executionNo – Number of times the reduce function has been executed on the current group
* @param {boolean} reduceContext.isRestarted – Indicates whether the current invocation of this function is the first
* invocation (if true, the current invocation is not the first invocation and this function has been restarted)
* @param {string} reduceContext.key – Key to be processed during the reduce stage
* @param {List<String>} reduceContext.values – All values associated with a unique key that was passed to the reduce stage
* for processing
* @since 2015.2
*/
function reduce(context) {
try {
let payrollId = context.key;
let totalTrackedHoursForEmployee = 0;
let payrollEmpId = null;
context.values.forEach(function (value) {
let parsedValue = JSON.parse(value);
totalTrackedHoursForEmployee += parsedValue.totalTrackedHours;
payrollEmpId = parsedValue.payrollEmpId;
});
if (!payrollEmpId) {
log.error(‘Payroll Employee ID Not Found’, { payrollId });
return;
}
// Fetch Employee’s Work Calendar & Min Work Hours
let workCalendarId = getEmployeeWorkCalendar(payrollEmpId);
let minWorkHours = workCalendarId ? getWorkCalendarHours(workCalendarId) : 0; // Default to 0 hours if not found
if (minWorkHours > 0) {
totalTrackedHoursForEmployee = Math.ceil(totalTrackedHoursForEmployee / minWorkHours) * minWorkHours;
}
log.debug(‘Reducing Employee Data’, {
payrollEmpId: payrollEmpId,
payrollId: payrollId,
totalTrackedHoursForEmployee: totalTrackedHoursForEmployee,
minWorkHours: minWorkHours
});
// Update Payroll Record
record.submitFields({
type: ‘customrecord_jj_payroll_details’,
id: payrollId,
values: {
‘custrecord_jj_time_tracked’: totalTrackedHoursForEmployee
},
options: { ignoreMandatoryFields: true }
});
log.debug(‘Updated Payroll Details’, {
payrollEmpId: payrollEmpId,
payrollId: payrollId,
totalTrackedHoursForEmployee: totalTrackedHoursForEmployee
});
} catch (e) {
log.error(‘Error in reduce’, e);
return e;
}
}
/**
* Defines the function that is executed when the summarize entry point is triggered. This entry point is triggered
* automatically when the associated reduce stage is complete. This function is applied to the entire result set.
* @param {Object} summaryContext – Statistics about the execution of a map/reduce script
* @param {number} summaryContext.concurrency – Maximum concurrency number when executing parallel tasks for the map/reduce
* script
* @param {Date} summaryContext.dateCreated – The date and time when the map/reduce script began running
* @param {boolean} summaryContext.isRestarted – Indicates whether the current invocation of this function is the first
* invocation (if true, the current invocation is not the first invocation and this function has been restarted)
* @param {Iterator} summaryContext.output – Serialized keys and values that were saved as output during the reduce stage
* @param {number} summaryContext.seconds – Total seconds elapsed when running the map/reduce script
* @param {number} summaryContext.usage – Total number of governance usage units consumed when running the map/reduce
* script
* @param {number} summaryContext.yields – Total number of yields when running the map/reduce script
* @param {Object} summaryContext.inputSummary – Statistics about the input stage
* @param {Object} summaryContext.mapSummary – Statistics about the map stage
* @param {Object} summaryContext.reduceSummary – Statistics about the reduce stage
* @since 2015.2
*/
function summarize(summary) {
try {
log.audit(‘Map/Reduce Execution Summary’, summary);
if (summary.error) {
log.error(‘Execution Error’, summary.error);
}
} catch (e) {
log.error(‘Error in summarize’, e);
return e;
}
}
return {
getInputData: getInputData,
map: map,
reduce: reduce,
summarize: summarize
};
});
1. Constants Definition
- Defines a specific Leave Project ID (
LEAVE_PROJECT = '26088'). - Specifies a list of Project Allocation IDs to be processed.
2. Fetching Employee Work Calendar Details
- Retrieves the Work Calendar ID for a given employee.
- Fetches the Work Hours Per Day from the Work Calendar record.
3. Fetching Project Allocations
- Searches for active Project Allocations that match specific criteria:
- Project allocation IDs from the predefined list.
- Excludes leave projects (i.e., projects related to employee leaves).
- Retrieves relevant details:
- Project Allocation ID
- Payroll ID (Project Period)
- Project ID
- Tracked Time
- Employee ID from Payroll
4. Map/Reduce Execution Flow
a) Get Input Data (getInputData)
- Calls the function to retrieve project allocation records.
- Logs the retrieval process.
b) Map Phase (map)
- Extracts project allocation details from the input data.
- Logs details like Project Allocation ID, Payroll ID, Tracked Hours, and Payroll Employee ID.
- Writes data to be processed in the reduce phase, grouped by Payroll ID.
c) Reduce Phase (reduce)
- Aggregates Total Tracked Hours for each Payroll ID.
- Fetches the employee’s Work Calendar ID and Work Hours Per Day.
- Adjusts total tracked hours based on minimum work hours for accurate payroll tracking.
- Logs the computed values.
- Updates the Payroll Details Record with the new tracked hours.
d) Summarize Phase (summarize)
- Logs the execution summary of the script.
- Captures any errors encountered during execution.