Migrasi dari Excel ke ERP Kustom: Pemetaan Data, Validasi, dan Strategi Cutover

Foto oleh Unsplash

Foto oleh Unsplash
Bagi sebagian besar UKM Indonesia, titik awal proyek ERP adalah kumpulan file Excel dengan nama seperti 'Final_v3_FINAL_USE_THIS.xlsx'. Migrasi data ini ke sistem ERP baru adalah salah satu tugas yang paling diremehkan dan paling kritis dalam seluruh proyek.
Sebelum menulis satu baris kode migrasi, luangkan setidaknya satu minggu untuk mengaudit file Excel sumber. Audit harus menjawab: berapa banyak pelanggan unik, format tanggal apa yang digunakan, field apa yang wajib, nilai apa yang muncul di kolom status, dan referensi silang apa yang ada antar sheet.
Masalah kualitas data paling umum: NPWP disimpan sebagai teks dengan titik dan tanda hubung; tanggal dalam bahasa Indonesia ('15 Maret 2023') yang tidak bisa diurai library standar; catatan pelanggan duplikat dengan nama sedikit berbeda; jumlah mata uang dengan awalan 'Rp'; dan sel yang digabungkan.
Buat dokumen pemetaan yang menunjukkan setiap kolom Excel, field ERP targetnya, transformasi yang diperlukan, dan aturan validasi. Dokumen ini menjadi spesifikasi untuk skrip migrasi Anda dan kesepakatan antara tim teknis dan pemangku kepentingan bisnis.
Excel → Custom ERP Migration Pipeline
┌─────────────────┐
│ SOURCE: Excel │ Multiple sheets, ad-hoc formulas,
│ (.xlsx files) │ merged cells, inconsistent formats
└────────┬────────┘
│ Step 1: Extract & Parse
▼
┌─────────────────────────────────────┐
│ EXTRACT (Node.js / exceljs) │
│ • Read all sheets │
│ • Strip formatting, get raw values │
│ • Map columns → field names │
└────────────────┬────────────────────┘
│ Step 2: Validate
▼
┌─────────────────────────────────────┐
│ VALIDATE (Zod schemas) │
│ • Required fields present? │
│ • Date formats parseable? │
│ • FK references exist? (customer) │
│ • Duplicate invoice numbers? │
│ • Amount totals match? │
└──────┬──────────────────────┬───────┘
│ PASS │ FAIL
▼ ▼
┌─────────────┐ ┌─────────────────┐
│ TRANSFORM │ │ ERROR REPORT │
│ & LOAD │ │ (.xlsx output) │
│ PostgreSQL │ │ Row + reason │
└─────────────┘ └─────────────────┘
Step 3: Cutover
┌────────────────────────────────────┐
│ Parallel run: Excel + ERP (2 wk) │
│ → Reconcile totals daily │
│ → Hard cutover on month-end │
└────────────────────────────────────┘Jalankan skrip migrasi Anda terhadap file Excel nyata setidaknya tiga kali sebelum go-live: sekali di awal untuk baseline, sekali setelah pembersihan data, dan sekali pada hari go-live. Setiap run harus menghasilkan laporan error — tujuannya adalah mendorong laporan tersebut ke nol baris.
Kami menggunakan ExcelJS untuk membaca file Excel di Node.js — ia menangani sel yang digabungkan, teks kaya, nilai formula, dan semua keanehan file Excel dunia nyata. Zod menyediakan validasi skema dengan inferensi tipe TypeScript.
Skrip migrasi harus idempoten — Anda harus dapat menjalankannya beberapa kali tanpa membuat duplikat. Gunakan upsert TypeORM dengan kunci konflik unik. Bungkus bulk insert dalam transaksi database sehingga jika baris apapun gagal, seluruh batch di-rollback.
// NestJS: Excel import service using exceljs + Zod validation
import * as ExcelJS from 'exceljs';
import { z } from 'zod';
const CustomerRowSchema = z.object({
customerCode: z.string().min(1),
name: z.string().min(1),
taxId: z.string().regex(/^d{15,16}$/, 'NPWP must be 15-16 digits').optional(),
creditLimit: z.coerce.number().min(0),
paymentTerms: z.enum(['NET_7', 'NET_14', 'NET_30', 'NET_45', 'NET_60']),
phone: z.string().optional(),
email: z.string().email().optional(),
});
@Injectable()
export class ExcelImportService {
async importCustomers(buffer: Buffer): Promise<ImportResult> {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(buffer);
const sheet = workbook.getWorksheet('Customers');
const errors: ImportError[] = [];
const valid: CustomerRow[] = [];
const HEADER_ROW = 1;
sheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
if (rowNumber === HEADER_ROW) return; // skip header
const rawData = {
customerCode: row.getCell('A').text.trim(),
name: row.getCell('B').text.trim(),
taxId: row.getCell('C').text.trim() || undefined,
creditLimit: row.getCell('D').value,
paymentTerms: row.getCell('E').text.trim().toUpperCase(),
phone: row.getCell('F').text.trim() || undefined,
email: row.getCell('G').text.trim() || undefined,
};
const result = CustomerRowSchema.safeParse(rawData);
if (result.success) {
valid.push(result.data);
} else {
errors.push({
row: rowNumber,
data: rawData,
issues: result.error.issues.map(i => i.message),
});
}
});
// Bulk insert valid rows in a transaction
if (valid.length > 0) {
await this.dataSource.transaction(async manager => {
await manager.upsert(Customer, valid, ['customerCode']);
});
}
return { imported: valid.length, failed: errors.length, errors };
}
}Data historis hadir dalam dua jenis: catatan tertutup dan catatan terbuka. Catatan terbuka perlu diimpor dalam state yang tepat sehingga otomatisasi mengambilnya dengan benar. Kesalahan paling berbahaya adalah mengimpor invoice terlambat sebagai status DRAFT.
Jangan pernah menghapus file Excel sumber setelah migrasi. Arsipkan dalam penyimpanan read-only dan simpan setidaknya 12 bulan setelah go-live. Sebelum go-live, ambil dump database PostgreSQL lengkap dan simpan offline — jika sesuatu yang katastrofik terjadi dalam 48 jam pertama, Anda memerlukan titik rollback yang bersih.
Setelah migrasi awal, jalankan sistem Excel lama dan ERP baru secara paralel setidaknya 2 minggu sebelum hard cutover. Di akhir setiap minggu, rekonsiliasi total kunci antara kedua sistem. Setiap perbedaan memicu penyelidikan sebelum cutover.
Jadwalkan go-live untuk Jumat sore atau hari tenang di pertengahan bulan. Ekspor data Excel terakhir Kamis malam, jalankan skrip migrasi Jumat pagi, verifikasi jumlah catatan, jalankan rekonsiliasi, minta tanda tangan manajer keuangan, nonaktifkan pengeditan Excel, dan kirim pengumuman perusahaan.