Skip to main content
BlogERP

Mengotomatiskan Piutang Usaha dalam ERP Kustom: Pengingat, Laporan Aging, dan Pelacakan Pembayaran

Cara membangun sistem AR yang sepenuhnya otomatis dengan NestJS dan PostgreSQL — dari pembuatan invoice dan email pengingat terjadwal hingga laporan aging dan alur eskalasi keterlambatan.

Matthews Wong

Software Engineer

Juni 20259 menit baca
Mengotomatiskan Piutang Usaha dalam ERP Kustom: Pengingat, Laporan Aging, dan Pelacakan Pembayaran

Foto oleh Unsplash

Ringkasan Utama

Mengotomatiskan piutang usaha di ERP kustom bermuara pada empat pekerjaan yang saling terhubung: kirim setiap invoice beserta tautan pembayaran, jalankan jadwal pengingat yang memperhitungkan zona waktu dari T-7 sampai T+30, kelompokkan invoice terbuka ke dalam laporan aging, dan pantau DSO. Dengan begitu, penagihan lewat spreadsheet dan WhatsApp berubah menjadi konfigurasi, dan tim keuangan tinggal menangani pengecualian.

Di banyak UKM Indonesia yang pernah saya tangani, pengelolaan piutang usaha dilakukan dengan kombinasi spreadsheet Excel dan follow-up WhatsApp manual. Tim keuangan mencatat siapa berutang berapa, mengirim pengingat satu per satu, dan menghabiskan berjam-jam setiap bulan untuk mencocokkan bukti pembayaran dengan invoice yang belum lunas. Ketika saya membangun modul AR untuk ERP kustom klien sebagai bagian dari pekerjaan konsultasi ERP lepas saya, tujuannya adalah mengotomatiskan seluruh siklus piutang — dari pembuatan invoice hingga eskalasi keterlambatan — sehingga tim keuangan bisa fokus pada pengecualian, bukan follow-up rutin.

Siklus Hidup AR: Apa yang Perlu Diotomatiskan

Otomatisasi AR yang lengkap mencakup empat tahap: pembuatan dan pengiriman invoice, pengingat pembayaran otomatis pada interval yang ditentukan, deteksi dan eskalasi keterlambatan, dan pencatatan pembayaran dengan rekonsiliasi.

Pembuatan Invoice dan Pengiriman PDF

Langkah pertama adalah membuat invoice PDF yang profesional dan mengirimkannya ke pelanggan melalui email. Dalam implementasi NestJS yang saya bangun, saya memakai Puppeteer untuk me-render template HTML menjadi PDF — cara ini memberi kontrol desain penuh tanpa keterbatasan library PDF. Template HTML menarik data dari entitas invoice dan di-render lewat instance Chrome headless. PDF yang dihasilkan disimpan di Google Cloud Storage dan tautan unduhannya disertakan di email pengiriman. Bagi penjual PKP, invoice ini bukan faktur pajak: Pasal 26 PP 44/2022 juga mewajibkan Faktur Pajak dibuat saat penyerahan melalui sistem DJP (Coretax sejak 1 Januari 2025), sehingga catatan AR menyimpan nomornya.

Desain Jadwal Pengingat

Jadwal pengingat dapat dikonfigurasi per syarat pembayaran pelanggan. Untuk pelanggan NET_30: pengingat ramah di T-7, pengingat lebih tegas di T-3, pemberitahuan keterlambatan di T+0, tindak lanjut di T+7, dan notifikasi credit hold di T+30 keterlambatan.

Accounts Receivable Automation Flow

  Invoice Created
       │
       ▼
  ┌──────────────────┐
  │  Generate PDF    │◄── NestJS PDF service (Puppeteer)
  │  & Send Email    │──► Customer inbox
  └────────┬─────────┘
           │
           ▼
  ┌──────────────────┐
  │  Due Date T-7    │──► Reminder email #1 (automated)
  └────────┬─────────┘
           │
           ▼
  ┌──────────────────┐
  │  Due Date T-3    │──► Reminder email #2 (escalation tone)
  └────────┬─────────┘
           │
           ▼
  ┌──────────────────┐      ┌─────────────────────┐
  │  Due Date T+0    │─────►│  Mark as OVERDUE     │
  └────────┬─────────┘      │  Notify AR Manager   │
           │                └─────────────────────┘
           ▼
  ┌──────────────────┐
  │  T+7 Overdue     │──► Manual follow-up flag
  │  T+30 Overdue    │──► Credit hold trigger
  └──────────────────┘

Selalu sertakan tautan pembayaran langsung di email pengingat. Dalam pekerjaan konsultasi ERP lepas saya, saya mengintegrasikan payment gateway lokal (Midtrans): pelanggan membayar dari email lewat virtual account, QRIS, e-wallet, atau kartu, dan Midtrans mengirim notifikasi ke ERP, sehingga tidak ada yang perlu mencocokkan transfer berdasarkan nomor referensi. Midtrans hanya menerima setiap order_id satu kali, jadi buat satu tautan per invoice dan pakai ulang di setiap pengingat.

Mengimplementasikan Scheduler di NestJS

Modul @nestjs/schedule di NestJS menyediakan scheduler berbasis cron yang berjalan di dalam proses aplikasi. Saya mengaturnya agar menjalankan job pengingat AR setiap pagi pukul 08.00 WIB (UTC+7) dengan memberikan timeZone: 'Asia/Jakarta' pada decorator @Cron; tanpa opsi itu, library cron di bawahnya memakai waktu lokal server. Job ini mencari invoice yang jatuh tempo dalam 7, 3, dan 0 hari, serta invoice yang sudah terlambat. Untuk setiap invoice yang cocok, job mengirim template email yang sesuai, memperbarui status invoice, dan mencatat tindakan itu ke audit trail.

Pembuatan Laporan Aging

Laporan aging mengkategorikan semua invoice yang belum dibayar berdasarkan seberapa terlambatnya: lancar (belum jatuh tempo), 1–30 hari, 31–60 hari, 61–90 hari, dan 90+ hari. Saya membuat laporan ini secara on-demand dan juga pra-komputasi snapshot harian untuk performa.

// NestJS: AR automated reminder scheduler
import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, LessThanOrEqual, In } from 'typeorm';
import { Invoice } from './invoice.entity';
import { EmailService } from '../email/email.service';
import { addDays, startOfDay } from 'date-fns';

@Injectable()
export class ArReminderService {
  private readonly logger = new Logger(ArReminderService.name);

  constructor(
    @InjectRepository(Invoice)
    private invoiceRepo: Repository<Invoice>,
    private emailService: EmailService,
  ) {}

  @Cron(CronExpression.EVERY_DAY_AT_8AM, { timeZone: 'Asia/Jakarta' })
  async sendReminders() {
    const today = startOfDay(new Date());

    // T-7: first reminder
    const upcomingInvoices = await this.invoiceRepo.find({
      where: {
        dueDate: addDays(today, 7),
        status: In(['SENT', 'PARTIALLY_PAID']),
      },
      relations: ['customer'],
    });

    for (const invoice of upcomingInvoices) {
      await this.emailService.sendArReminder(invoice, 'REMINDER_7_DAYS');
      this.logger.log(`Sent T-7 reminder for invoice ${invoice.invoiceNumber}`);
    }

    // T+0: mark overdue
    const overdueInvoices = await this.invoiceRepo.find({
      where: {
        dueDate: LessThanOrEqual(today),
        status: In(['SENT', 'PARTIALLY_PAID']),
      },
      relations: ['customer'],
    });

    for (const invoice of overdueInvoices) {
      await this.invoiceRepo.update(invoice.id, { status: 'OVERDUE' });
      await this.emailService.sendArReminder(invoice, 'OVERDUE_NOTICE');
    }

    this.logger.log(
      `Processed ${upcomingInvoices.length} reminders, ${overdueInvoices.length} overdue`
    );
  }
}

Pencatatan Pembayaran dan Rekonsiliasi

Ketika pelanggan melakukan pembayaran, staf keuangan mencatatnya di ERP terhadap invoice. Sistem mendukung pembayaran parsial. Ketika invoice mencapai saldo nol, secara otomatis beralih ke status DIBAYAR dan kuitansi dikirim melalui email ke pelanggan.

Berhati-hatilah dengan penanganan zona waktu di scheduler. Indonesia memiliki tiga zona waktu — WIB (UTC+7), WITA (UTC+8), dan WIT (UTC+9) — sementara waktu server sering memakai UTC. Cron yang dimaksudkan untuk pukul 08.00 tetapi berjalan dalam UTC akan menyala pukul 15.00 WIB, 16.00 WITA, dan 17.00 WIT, menjelang akhir jam kerja pelanggan, dan antara pukul 17.00 hingga tengah malam UTC, tanggal UTC sudah tertinggal satu hari dari WIB. Selalu simpan tanggal jatuh tempo sebagai DATE (bukan TIMESTAMP) di PostgreSQL dan konversikan secara eksplisit ke zona waktu pelanggan saat menghitung status keterlambatan.

Pelaporan dan Integrasi Dashboard

Modul AR berkontribusi ke dashboard keuangan dengan tiga metrik utama: Days Sales Outstanding (DSO), tingkat penagihan per periode, dan jumlah terlambat per tier pelanggan. DSO yang meningkat menandakan masalah penagihan sebelum menjadi krisis arus kas.

Hasil Setelah 6 Bulan Produksi

Setelah sistem AR otomatis berjalan di produksi untuk satu klien selama enam bulan, waktu tim keuangan untuk follow-up manual jauh berkurang, lebih sedikit invoice yang lewat jatuh tempo, dan waktu penagihan menjadi lebih singkat. Persentase dari satu klien tidak bisa dipindahkan begitu saja ke bisnis Anda, jadi catat DSO, porsi invoice yang dibayar terlambat, dan jam follow-up mingguan sebelum go-live, lalu bandingkan. Keuntungan terbesarnya bukan otomatisasi itu sendiri, melainkan visibilitas — manajer keuangan dapat melihat gambaran piutang secara real time, bukan menyusunnya manual di Excel setiap akhir bulan.

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Mengapa menggunakan Puppeteer untuk pembuatan PDF invoice alih-alih library PDF khusus?

Puppeteer me-render template HTML melalui instance Chrome tanpa kepala (headless), sehingga memberikan kontrol desain penuh tanpa keterbatasan tata letak library PDF khusus. PDF yang dihasilkan disimpan di Google Cloud Storage dan tautan unduhan disertakan dalam email pengiriman ke pelanggan.

Mengapa setiap email pengingat sebaiknya menyertakan tautan pembayaran?

Tautan memungkinkan pelanggan membayar langsung dari email, bukan lewat transfer bank manual yang kemudian harus dicocokkan tim keuangan berdasarkan nomor referensi. Dengan Midtrans, pelanggan memilih metode di halaman pembayaran yang disediakan Midtrans, tempat virtual account, QRIS, e-wallet, dan kartu bisa diaktifkan, dan sistem merchant menerima notifikasi begitu pembayaran selesai. Karena Midtrans hanya menerima setiap order_id satu kali, sistem membuat satu tautan per invoice dan memakainya ulang di setiap pengingat.

Bagaimana jadwal pengingat untuk pelanggan NET_30 dan bagaimana nada komunikasinya berubah seiring waktu?

Untuk pelanggan NET_30, jadwalnya berjalan: pengingat ramah di T-7, pengingat lebih tegas di T-3, pemberitahuan keterlambatan di T+0, tindak lanjut di T+7, dan notifikasi credit hold di T+30 keterlambatan. Nada email berubah dari ramah menjadi formal, dan email T+30 menyalin (CC) manajer akun penjualan untuk mendorong intervensi manual.

Mengapa penanganan zona waktu sangat penting untuk scheduler AR, dan apa pendekatan yang direkomendasikan?

Indonesia memiliki tiga zona waktu — WIB (UTC+7), WITA (UTC+8), dan WIT (UTC+9) — sementara server sering berjalan dalam UTC. @nestjs/schedule menjalankan job menurut waktu lokal server kecuali decorator @Cron diberi timeZone, sehingga job yang dimaksudkan untuk pukul 08.00 WIB di server UTC sebenarnya menyala pukul 15.00 WIB. Atur timeZone: 'Asia/Jakarta', simpan tanggal jatuh tempo sebagai DATE alih-alih TIMESTAMP, dan konversikan ke zona waktu pelanggan saat menghitung status keterlambatan.

Apa yang berubah setelah sistem AR otomatis berjalan di produksi?

Selama enam bulan di produksi untuk satu klien, waktu tim keuangan untuk follow-up manual jauh berkurang, lebih sedikit invoice yang terlambat, dan waktu penagihan menjadi lebih singkat. Keuntungan terbesarnya adalah visibilitas real-time atas seluruh piutang, menggantikan penyusunan manual di Excel setiap akhir bulan. Persentase dari satu klien tidak bisa dipindahkan ke bisnis lain, jadi ukur DSO, porsi pembayaran terlambat, dan jam follow-up sebelum go-live, lalu bandingkan sesudahnya.