# Automating Accounts Receivable in a Custom ERP: Reminders, Aging Reports, and Payment Tracking

> How to build a fully automated AR system with NestJS and PostgreSQL — from invoice generation and scheduled reminder emails to aging reports and overdue escalation workflows.

URL: https://www.matthewswong.com/en/blog/accounts-receivable-automation-erp/

- Author: Matthews Wong (https://www.matthewswong.com/en/about/)
- Published: 2025-06-10
- Updated: 2026-10-13
- Category: ERP

In many of the Indonesian SMEs I have worked with, accounts receivable management is handled through a combination of Excel spreadsheets and manual WhatsApp follow-ups. The finance team tracks who owes what, sends reminders by hand, and spends hours each month reconciling payment receipts against outstanding invoices. When I built the AR module for a client's custom ERP as part of my freelance ERP consulting work, the goal was to automate the entire receivables lifecycle — from invoice generation to overdue escalation — so the finance team could focus on exceptions rather than routine follow-up.

## The AR Lifecycle: What Needs to Be Automated

A complete AR automation covers four stages: invoice creation and delivery, automated payment reminders at defined intervals, overdue detection and escalation, and payment recording with reconciliation. Each stage produces events that trigger the next, creating a self-running loop that only requires human intervention for exceptions — disputed invoices, partial payments that don't match, or customers who need a custom payment arrangement.

### Invoice Generation and PDF Delivery

The first step is generating a professional PDF invoice and delivering it to the customer via email. In the NestJS implementation I built, I use Puppeteer to render an HTML template to PDF — this gives full design control without the limitations of PDF libraries. The HTML template pulls data from the invoice entity and renders it via a headless Chrome instance. The resulting PDF is stored in Google Cloud Storage and a download link is included in the delivery email. For a PKP seller this is not the tax invoice: PP 44/2022 Article 26 also requires a Faktur Pajak at delivery, issued through DJP's systems (Coretax since 1 January 2025), so the AR record stores its number.

### Reminder Schedule Design

The reminder schedule is configurable per customer payment term. For a NET_30 customer: a friendly reminder at T-7, a firmer reminder at T-3, an overdue notice at T+0, a follow-up at T+7, and a credit hold notification at T+30 overdue. Different email templates are used at each stage — the tone escalates from friendly to formal, and the T+30 email copies the sales account manager to prompt manual intervention.

> Always include a direct payment link in reminder emails. In my freelance ERP consulting work, I integrated a local payment gateway (Midtrans): customers pay from the email by virtual account, QRIS, e-wallet or card, and Midtrans notifies the ERP, so nobody matches transfers by reference number. Midtrans accepts each order_id only once, so create one link per invoice and reuse it in every reminder.

## Implementing the Scheduler in NestJS

NestJS's @nestjs/schedule module provides a cron-based scheduler that runs inside the application process. I set it up to run the AR reminder job every morning at 8 AM WIB (UTC+7) by passing timeZone: 'Asia/Jakarta' to the @Cron decorator; without it, the underlying cron library uses the server's local time. The job queries for invoices due in 7 days, 3 days, and 0 days, and for invoices already overdue. For each matched invoice, it sends the appropriate email template, updates the invoice status, and logs the action to the audit trail.

### Aging Report Generation

The aging report categorizes all outstanding invoices by how overdue they are: current (not yet due), 1–30 days, 31–60 days, 61–90 days, and 90+ days. I generate this report on demand (real-time query) and also pre-compute a daily snapshot stored in a summary table for performance. The PostgreSQL query uses CASE WHEN on the date difference between today and the due date to bucket each invoice.

## Payment Recording and Reconciliation

When a customer makes a payment, finance staff records it in the ERP against the invoice. The system supports partial payments — an invoice can have multiple payment records until it's fully settled. When an invoice reaches zero balance, it automatically transitions to PAID status and a receipt is emailed to the customer. For bank transfers, reconciliation is semi-automated via bank statement import.

> **Timezone Warning**

> Be careful with timezone handling in the scheduler. Indonesia has three time zones — WIB (UTC+7), WITA (UTC+8) and WIT (UTC+9) — and server time is often UTC. A cron meant for 08:00 that runs on UTC fires at 15:00 WIB, 16:00 WITA and 17:00 WIT, late in the customer's working day, and between 17:00 and midnight UTC the UTC date is already a day behind WIB. Always store due dates as DATE (not TIMESTAMP) in PostgreSQL and explicitly convert to the customer's timezone when computing overdue status.

## Reporting and Dashboard Integration

Beyond the aging report, the AR module feeds into the finance dashboard with three key metrics: Days Sales Outstanding (DSO), collection rate by period, and overdue amount by customer tier. DSO is the most important metric — it tells you how many days on average it takes to collect payment after invoicing. A rising DSO signals a collections problem before it becomes a cash flow crisis.

## Results After 6 Months of Production

After running the automated AR system in production for a client for six months, the finance team spent far less time on manual follow-up, fewer invoices slipped past their due date, and collection time shortened. One client's percentages will not transfer to your business, so record DSO, the share of invoices paid late and weekly follow-up hours before go-live, then compare. The biggest win was not the automation itself but the visibility — finance managers could see the full AR picture in real time rather than compiling it manually in Excel at month-end.

## Sources & Further Reading

- NestJS Docs — Task Scheduling with @nestjs/schedule — https://docs.nestjs.com/techniques/task-scheduling
- TypeORM — Official Documentation for PostgreSQL integration — https://www.typeorm.io/
- Midtrans — Create Payment Link API reference — https://docs.midtrans.com/reference/create-payment-link

> **Key takeaway:** Automating accounts receivable in a custom ERP comes down to four linked jobs: send every invoice with a payment link, run a time-zone-aware reminder schedule from T-7 to T+30, bucket open invoices into an aging report, and track DSO. That turns spreadsheet and WhatsApp chasing into configuration, leaving the finance team only the exceptions.

## Source links

- https://docs.nestjs.com/techniques/task-scheduling
- https://www.typeorm.io/
- https://docs.midtrans.com/reference/create-payment-link
- https://github.com/kelektiv/node-cron
- https://peraturan.bpk.go.id/Details/232656/pp-no-44-tahun-2022
- https://www.pajak.go.id/en/node/113499

## FAQ

### Why use Puppeteer for PDF invoice generation instead of a dedicated PDF library?

Puppeteer renders an HTML template via a headless Chrome instance, giving full design control without the layout limitations of dedicated PDF libraries. The resulting PDF is stored in Google Cloud Storage and a download link is included in the delivery email.

### Why should every reminder email include a payment link?

A link lets the customer pay straight from the email instead of making a manual bank transfer that finance then has to match by reference number. With Midtrans the customer picks a method on the hosted payment page, where virtual accounts, QRIS, e-wallets and cards can be enabled, and the merchant's system is notified when the payment completes. Because Midtrans accepts each order_id only once, the system creates one link per invoice and reuses it in every reminder.

### What is the reminder schedule for a NET_30 customer and how does the tone change over time?

For a NET_30 customer the schedule runs: a friendly reminder at T-7, a firmer reminder at T-3, an overdue notice at T+0, a follow-up at T+7, and a credit hold notification at T+30 overdue. The email tone escalates from friendly to formal, and the T+30 email copies the sales account manager to prompt manual intervention.

### Why is timezone handling critical for the AR scheduler, and what is the recommended approach?

Indonesia spans three time zones — WIB (UTC+7), WITA (UTC+8) and WIT (UTC+9) — while servers often run on UTC. @nestjs/schedule fires in the server's local time unless the @Cron decorator is given a timeZone, so a job meant for 08:00 WIB on a UTC server actually fires at 15:00 WIB. Set timeZone: 'Asia/Jakarta', store due dates as DATE rather than TIMESTAMP, and convert to the customer's time zone when computing overdue status.

### What changed after the automated AR system went into production?

Over six months in production for a client, the finance team spent far less time on manual follow-up, fewer invoices went overdue, and collection time shortened. The biggest gain was real-time visibility into the full receivables picture, replacing a manual month-end Excel compilation. One client's percentages will not transfer to another business, so measure DSO, the late-payment share and follow-up hours before go-live and compare afterwards.

---

- HTML version: https://www.matthewswong.com/en/blog/accounts-receivable-automation-erp/
- Bahasa Indonesia: https://www.matthewswong.com/id/blog/accounts-receivable-automation-erp/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
