import "server-only"; import { asc, desc, like, or, eq } from "drizzle-orm"; import { amigoDb } from "@/db/amigo"; import { pelanggan } from "@/db/amigo/schema"; export type Pelanggan = { kdCustomer: string; nama: string; alias: string; ktp: string; alamat: string; wilayah: string | null; telp: string | null; ponsel: string | null; email: string | null; jenisCustomer: string | null; upline: string | null; btsBon: number | null; nilaiBon: number | null; }; export type PelangganPage = { rows: Pelanggan[]; hasNext: boolean; }; const PER_PAGE = 25; // kdCustomer dibuat sistem, nilaiBon diisi transaksi BON; keduanya bukan isian form. export type PelangganInput = Omit; // Tanggal hari ini di zona toko (WIB) sebagai `yyyy-mm-dd`; toISOString() memakai UTC // sehingga jam 00:00-07:00 tersimpan sebagai tanggal kemarin. function todayWib(): string { return new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Jakarta" }).format( new Date(), ); } // `\`, `%`, dan `_` adalah karakter khusus LIKE (escape default MariaDB `\`). function escapeLike(value: string): string { return value.replace(/[\\%_]/g, "\\$&"); } function isKdCustomerExist(error: unknown): boolean { const e = error as { errno?: number; code?: string; cause?: { errno?: number; code?: string }; }; return ( e?.errno === 1062 || e?.code === "ER_DUP_ENTRY" || e?.cause?.errno === 1062 || e?.cause?.code === "ER_DUP_ENTRY" ); } export async function insertPelanggan(input: PelangganInput): Promise { // MyISAM tidak punya transaksi; baca max + insert bisa balapan dengan PHP lama, // jadi kalau kode sudah dipakai proses lain, ulangi baca max + insert. for (let attempt = 0; attempt < 3; attempt++) { const kdCustomer = await generateKodePelanggan(); try { await amigoDb.insert(pelanggan).values({ kd_customer: kdCustomer, alias: input.alias, ktp: input.ktp, // PHP lama tidak mengisi `member`, jadi selalu tanggal nol. member: "0000-00-00", nama: input.nama, alamat: input.alamat, wilayah: input.wilayah, telp: input.telp, ponsel: input.ponsel, email: input.email, upline: input.upline, jenis_customer: input.jenisCustomer, bts_bon: input.btsBon, tgl_entry: todayWib(), }); return kdCustomer; } catch (error) { if (!isKdCustomerExist(error) || attempt === 2) throw error; } } throw new Error("Kode pelanggan baru sedang dipakai proses lain"); } export async function updatePelanggan( kdCustomer: string, input: PelangganInput, ): Promise { const [res] = await amigoDb .update(pelanggan) .set({ alias: input.alias, ktp: input.ktp, nama: input.nama, alamat: input.alamat, wilayah: input.wilayah, telp: input.telp, ponsel: input.ponsel, email: input.email, upline: input.upline, jenis_customer: input.jenisCustomer, bts_bon: input.btsBon, }) .where(eq(pelanggan.kd_customer, kdCustomer)); return res.affectedRows > 0; } export async function deletePelanggan(kdCustomer: string): Promise { const [res] = await amigoDb .delete(pelanggan) .where(eq(pelanggan.kd_customer, kdCustomer)); return res.affectedRows > 0; } export async function listPelanggan( search?: string, page = 1, ): Promise { const q = search?.trim(); const pattern = q ? `%${escapeLike(q)}%` : ""; const rows = await amigoDb .select({ kdCustomer: pelanggan.kd_customer, nama: pelanggan.nama, alias: pelanggan.alias, email: pelanggan.email, alamat: pelanggan.alamat, telp: pelanggan.telp, ponsel: pelanggan.ponsel, jenisCustomer: pelanggan.jenis_customer, btsBon: pelanggan.bts_bon, nilaiBon: pelanggan.nilai_bon, ktp: pelanggan.ktp, wilayah: pelanggan.wilayah, upline: pelanggan.upline, }) .from(pelanggan) .where( q ? or( like(pelanggan.nama, pattern), like(pelanggan.kd_customer, pattern), ) : undefined, ) // kd_customer sebagai pengurut kedua: nama bisa sama, tanpa itu urutan // antar-query tidak stabil dan baris bisa dobel/hilang di batas halaman. .orderBy(asc(pelanggan.nama), asc(pelanggan.kd_customer)) .limit(PER_PAGE + 1) .offset((page - 1) * PER_PAGE); return { rows: rows.slice(0, PER_PAGE), hasNext: rows.length > PER_PAGE, }; } // Kode pelanggan = "A8" + 6 digit urut, pola yang sama dengan aplikasi lama // (old-source/ajaxpelanggan.php ambil_kode). export async function generateKodePelanggan(): Promise { const [row] = await amigoDb .select({ kdCustomer: pelanggan.kd_customer }) .from(pelanggan) .orderBy(desc(pelanggan.kd_customer)) .limit(1); const urut = Number.parseInt(row?.kdCustomer.slice(2) ?? "0", 10) + 1; return `A8${String(urut).padStart(6, "0")}`; }