Script For Sending Payment Reminder Emails (Map/Reduce)

The requirement is to send a payment reminder email if the customer is not done payment after 1 hour of sales order creation from the website.

We need to consider the following conditions for sending the payment reminder email:

Condition For sending Email: 

The following conditions need to be true. 

  1. The payment confirmation checkbox(a custom field created for checking payment status) should be unchecked (False) for the corresponding sales order. 
  1. The sales order creation date will be today. 
  1. The source of Sale order creation will be the Web. 
  1. Sales orders were created one hour ago. 
  1. Payment Reminder Email checkbox is False 

If all these conditions are true then an email will send to the customer. 

Script:

Note: This script uses an email template (for payment reminder) for sending emails.

The script uses a search with filters as the above-mentioned conditions and uses the ‘reduce’ function for sending emails.

/**
 * @NApiVersion 2.1
 * @NScriptType MapReduceScript
 */
/*****************************************************************************************************************
 * Author: Jobin & Jismi
 * Client Name: Nickolls and Perks Limited
 * Date Created : 28-Feb-2023
 * Created By: JJ0147
 * Script Description:The script will check the sales orders with no payment record and sends payment reminder email to
 *                    corresponding customers.
 *
 *****************************************************************************************************************/
define(['N/https', 'N/record', 'N/search', 'N/email', 'N/render' , 'N/runtime'],
    /**
     * @param{https} https
     * @param{record} record
     * @param{search} search
     * @param{nEmail} nEmail
     * @param{render} render
     * @param{runtime} runtime
     */
    (https, record, search, nEmail, render, runtime) => {
        /**
         * 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
         */

        const getInputData = (inputContext) => {
            try {
                log.debug("Inside getInputData");
                var salesorderSearchObj = search.create({
                    type: "salesorder",
                    filters:
                        [
                            ["type","anyof","SalesOrd"],
                            "AND",
                            ["source","anyof","NLWebStore"],
                            "AND",
                            ["datecreated","onorbefore","hoursago1"],
                            "AND",
                            ["datecreated","within","today"],
                            "AND",
                            ["terms","anyof","14"],
                            "AND",
                            ["custbody_payment_reminder_sent","is","F"],
                            "AND",
                            ["custbody_payment_confirmed_checkbox","is","F"],
                            "AND",
                            ["mainline","is","T"],
                            "AND",
                            ["applyingtransaction","anyof","@NONE@"]
                        ],
                    columns:
                        [
                            search.createColumn({name: "tranid", label: "Document Number"}),
                            search.createColumn({name: "terms", label: "Terms"}),
                            search.createColumn({name: "source", label: "Source"}),
                            search.createColumn({name: "custbody_payment_confirmed_checkbox", label: "Payment_Confirmation"}),
                            search.createColumn({name: "custbody_payment_reminder_sent", label: "Payment reminder email"}),
                            search.createColumn({name: "datecreated", label: "Date Created"}),
                            search.createColumn({name: "email", label: "Email"})
                        ]
                });
                var searchResultCount = salesorderSearchObj.runPaged().count;
                log.debug("salesorderSearchObj result count",searchResultCount);
                let salesOrderListResult = [];
                salesorderSearchObj.run().each(function(result){
                    // .run().each has a limit of 4,000 results
                    salesOrderListResult.push(result);
                    return true;
                });
                log.debug("salesOrderListResult", salesOrderListResult);
                return salesOrderListResult;

            } catch (err) {
                log.debug("Error in scheduled script execute function", err);
            }
        }


        /**
         * 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
         */
        const reduce = (reduceContext) => {
            try {
                log.debug("Inside reduce22");
                let salesorderSearchObj = JSON.parse(reduceContext.values);//dataObj is obtained by returning values from getInputData() stage
                log.debug("salesorderSearchObj", salesorderSearchObj);
                let salesOrderInternalId = salesorderSearchObj.id;
                let payment_check = salesorderSearchObj.values.custbody_payment_confirmed_checkbox
                let email_check = salesorderSearchObj.values.custbody_payment_reminder_sent
                let customer_emailid = salesorderSearchObj.values.email
                let tranID = salesorderSearchObj.values.tranid
                let tran_date = salesorderSearchObj.values.datecreated

                log.debug("payment_check", payment_check);
                log.debug("email_check", email_check);
                log.debug("customer_emailid", customer_emailid);
                log.debug("tranID", tranID);
                log.debug("tran_date", tran_date);
                log.debug("salesOrderInternalId", salesOrderInternalId);
                var transactionNumber = parseInt(salesOrderInternalId);
                log.debug("transactionNumber", transactionNumber);

                let salesOrderRecordObj = record.load({type: record.Type.SALES_ORDER, id: salesOrderInternalId});
                log.debug("salesOrderRecordObj", salesOrderRecordObj);

                if((payment_check=== false)&&(email_check===false)) {
                    log.debug("if true");
                    // Load the email template record
                    var emailTemplate = record.load({
                        type: record.Type.EMAIL_TEMPLATE,
                        id:435 // Replace TEMPLATE_ID with the internal ID of your email template
                    }); // Set the recipient email address
                    log.debug("emailTemplate77",emailTemplate)
                    var mergeResult = render.mergeEmail({
                        templateId:435,
                        entity: {
                            type: 'employee',
                            id:118
                        },
                        transactionId: transactionNumber
                    });
                    log.debug("mergeResult.body", mergeResult.body)
                    var recipientEmail = customer_emailid; // Set the email subject
                    var emailSubject = 'Payment Reminder'; // Create the email merge record
                    nEmail.send({
                        author:118,
                        recipients: recipientEmail,
                        subject: emailSubject,
                        body: mergeResult.body,
                        relatedRecords: {transactionId: transactionNumber}
                    });
                    //setting value fof email send checkbox true
                    salesOrderRecordObj.setValue({
                        fieldId: 'custbody_payment_reminder_sent',
                        value: true
                    });
                    salesOrderRecordObj.save({
                        enableSourcing: true,
                        ignoreMandatoryFields: true
                    });

                }
            } catch (err) {
                log.debug("Error in reduce", err);
            }
        }

        return {getInputData, reduce}
    });

Leave a comment

Your email address will not be published. Required fields are marked *