Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The NetSuite Pro

The NetSuite Pro Logo The NetSuite Pro Logo

The NetSuite Pro Navigation

  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • NetSuite Customization
    • NetSuite Integration
    • NetSuite Advanced PDF Templates
    • NetSuite Reporting & Analytics Guide
    • Real-World NetSuite Examples
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask A Question
  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • NetSuite Customization
    • NetSuite Integration
    • NetSuite Advanced PDF Templates
    • NetSuite Reporting & Analytics Guide
    • Real-World NetSuite Examples
  • Blog
  • Contact Us
Home/ Real-World NetSuite Examples/Credit Memo Auto-Approval by Role & Amount

Credit Memo Auto-Approval by Role & Amount

πŸ’Ό Business problem

Finance spends time clicking Approve on lots of tiny, low-risk credit memos (e.g., shipping adjustments, price-match pennies). We want to auto-approve when safe, and leave the rest for manual review.


🧠 Approach

  • User Event (beforeSubmit) on Credit Memo:
    • If memo total ≀ threshold (e.g., $100) and created by/edited by an allowed role, set approvalstatus = Approved.
    • Optional: approve only when reason is in an allow-list (custom field or memo text match).
  • Scheduled / Map-Reduce (optional): bulk-approve existing Pending Approval credit memos that meet the same criteria.

If your account doesn’t use Credit Memo Approval Routing, the field may be absent. Script will safely no-op.


πŸ”§ Prereqs & parameters

Create deployment parameters so Finance can tune without code changes:

  • custscript_cm_amount_threshold (Number) β€” e.g., 100
  • custscript_cm_whitelist_roles (Text CSV of role IDs) β€” e.g., 3,1042
  • custscript_cm_reason_field (Text; body field ID or blank) β€” e.g., custbody_credit_reason
  • custscript_cm_reason_allow_csv (Text CSV of allowed reason values or keywords) β€” e.g., Shipping Adj,Price Match,Rounding
  • custscript_cm_require_memo (Checkbox) β€” require non-empty Memo to auto-approve (T/F)

Optional body field (nice to have):

  • custbody_cm_auto_approval_note (Text) β€” store why it was approved.

🧩 User Event (SuiteScript 2.1) β€” auto-approve on save

/**
 * @NApiVersion 2.1
 * @NScriptType UserEventScript
 * Title: UE | Credit Memo Auto-Approval (role + amount + reason)
 */
define(['N/runtime','N/log'], (runtime, log) => {

  const P_AMT  = 'custscript_cm_amount_threshold';
  const P_ROLES = 'custscript_cm_whitelist_roles';
  const P_REASON_FIELD = 'custscript_cm_reason_field';
  const P_REASON_ALLOW = 'custscript_cm_reason_allow_csv';
  const P_REQUIRE_MEMO = 'custscript_cm_require_memo';

  const F_APPROVAL = 'approvalstatus'; // 1=Pending Approval, 2=Approved, 3=Rejected (commonly)
  const F_MEMO = 'memo';
  const F_AUDIT = 'custbody_cm_auto_approval_note'; // optional

  const beforeSubmit = (ctx) => {
    const rec = ctx.newRecord;
    if (rec.type !== 'creditmemo') return;
    // Only on create/copy/edit; skip others
    if (![ctx.UserEventType.CREATE, ctx.UserEventType.COPY, ctx.UserEventType.EDIT].includes(ctx.type)) return;

    try {
      // If approval field doesn't exist (no approval routing), gracefully skip
      if (!hasField(rec, F_APPROVAL)) return;

      // Already approved or rejected? Respect current state.
      const currentStatus = safeGet(rec, F_APPROVAL);
      if (String(currentStatus) === '2' || String(currentStatus) === '3') return;

      const user = runtime.getCurrentUser();
      const roleId = String(user.role);

      const amtThreshold = toNum(param(P_AMT), 0);
      const whitelist = parseCsv(param(P_ROLES)); // role IDs as strings
      const reasonField = (param(P_REASON_FIELD) || '').trim();
      const allowCsv = parseCsv(param(P_REASON_ALLOW)).map(x => x.toUpperCase());
      const requireMemo = (param(P_REQUIRE_MEMO) === 'T');

      const total = toNum(safeGet(rec, 'total'), 0);
      const memo = (safeGet(rec, F_MEMO) || '').trim();

      // Role check
      const roleOk = whitelist.length === 0 ? true : whitelist.includes(roleId);

      // Amount check
      const amountOk = amtThreshold <= 0 ? true : total <= amtThreshold;

      // Reason check (optional)
      let reasonOk = true;
      if (reasonField || allowCsv.length > 0) {
        const reasonVal = ((reasonField && hasField(rec, reasonField)) ? safeGet(rec, reasonField) : memo) || '';
        if (allowCsv.length > 0) {
          const hay = reasonVal.toString().toUpperCase();
          reasonOk = allowCsv.some(needle => hay.indexOf(needle) > -1);
        }
      }

      // Memo required?
      const memoOk = requireMemo ? memo.length > 0 : true;

      if (roleOk && amountOk && reasonOk && memoOk) {
        // Approve
        safeSet(rec, F_APPROVAL, 2);
        if (hasField(rec, F_AUDIT)) {
          const note = `Auto-approved on save: role ${roleId}, total ${total}, reason OK: ${reasonOk}`;
          safeSet(rec, F_AUDIT, note);
        }
      } else {
        // Leave pending; optionally annotate
        if (hasField(rec, F_AUDIT)) {
          const why = [
            roleOk ? null : 'role not allowed',
            amountOk ? null : `amount>${amtThreshold}`,
            reasonOk ? null : 'reason not allowed',
            memoOk ? null : 'memo required'
          ].filter(Boolean).join(', ');
          if (why) safeSet(rec, F_AUDIT, `Stayed Pending: ${why}`);
        }
      }

    } catch (e) {
      log.error('CM Auto-Approval UE error', e);
    }
  };

  // ------------ helpers ------------
  function param(name){ return runtime.getCurrentScript().getParameter({ name }) || ''; }
  function hasField(rec, fieldId){
    try { rec.getValue({ fieldId }); return true; } catch (_e){ return false; }
  }
  function safeGet(rec, fieldId){
    try { return rec.getValue({ fieldId }); } catch (_e){ return null; }
  }
  function safeSet(rec, fieldId, value){
    try { rec.setValue({ fieldId, value }); } catch (_e){}
  }
  function toNum(v, d){ const n = Number(String(v).replace(/[^\d.-]/g,'')); return isFinite(n) ? n : d; }
  function parseCsv(s){ return (s||'').split(',').map(x=>x.trim()).filter(Boolean); }

  return { beforeSubmit };
});

(Optional) Nightly bulk auto-approval (Saved Search + MR)

Use a saved search for Pending Approval credit memos under the threshold (and any other filters you like), then approve them in bulk.

Saved Search (Transaction)

  • Type = Credit Memo
  • Status = Pending Approval
  • Amount (Net or Total) ≀ {threshold}
  • (Optional) Memo contains any of: Shipping Adj / Price Match / Rounding
  • (Optional) Created By Role in (your whitelisted roles) β€” if you track via a custom field or system notes search

Map/Reduce (outline)

  • getInputData() loads the search
  • In reduce(), call record.submitFields({ type: 'creditmemo', id, values: { approvalstatus: 2 } })
  • Wrap with try/catch; log each approval

βœ… Testing checklist

  1. Turn on approval routing for credit memos (if you use it).
  2. Set parameters: threshold 100, roles 3,1042, reasons Shipping Adj,Price Match.
  3. Create a CM for $25 with memo β€œShipping adj – damaged box” as an allowed role β†’ auto-approved.
  4. Create a CM for $250 (over threshold) β†’ stays Pending Approval.
  5. Create a CM for $10 but memo is blank and β€œRequire Memo” = T β†’ stays Pending.
  6. Temporarily remove your role from whitelist β†’ stays Pending.
  7. (Optional) Run the MR on historical items β†’ confirms bulk approvals.

πŸ”’ Enhancements

  • Maintain an Allowlist of Items (e.g., shipping charge item) by internal IDs; approve only when all lines are in the allowlist.
  • Set an upper daily limit per role (track totals in a custom record).
  • Email Finance when a CM is auto-approved above a smaller β€œnotify” threshold (e.g., > $50).

πŸ“Œ Summary

ControlEffect
Role + Amount thresholdAuto-approve low-risk memos instantly
Reason allow-listPrevents accidental approvals
Nightly MRCleans up existing pending queue
Share
  • Facebook

Leave a ReplyCancel reply

Sidebar

Ask A Question

Stats

  • Questions 6
  • Answers 6
  • Best Answers 0
  • Users 2
  • Popular
  • Answers
  • Rocky

    Issue in running a client script in NetSuite SuiteScript 2.0 ...

    • 1 Answer
  • admin

    How can I send an email with an attachment in ...

    • 1 Answer
  • admin

    How do I avoid SSS_USAGE_LIMIT_EXCEEDED in a Map/Reduce script?

    • 1 Answer
  • admin
    admin added an answer The issue is usually caused by following Wrong script file… September 14, 2025 at 10:33 pm
  • admin
    admin added an answer Steps to send an Invoice PDF by email: define(['N/email', 'N/render',… August 28, 2025 at 3:05 am
  • admin
    admin added an answer This error means your script hit NetSuite’s governance usage limit… August 28, 2025 at 3:02 am

Top Members

Rocky

Rocky

  • 1 Question
  • 22 Points
Begginer
admin

admin

  • 5 Questions
  • 2 Points

Trending Tags

clientscript netsuite scripting suitescript

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

© 2025 The NetSuite Pro. All Rights Reserved