You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

177 lines
4.9 KiB
TypeScript

import "server-only";
import { asc, desc, like, or, eq } from "drizzle-orm";
import { amigoDb } from "@/db/amigo";
import { pelanggan } from "@/db/amigo/schema";
import { PER_PAGE } from "@/lib/pagination";
import { escapeLike } from "@/lib/utils";
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;
};
// kdCustomer dibuat sistem, nilaiBon diisi transaksi BON; keduanya bukan isian form.
export type PelangganInput = Omit<Pelanggan, "kdCustomer" | "nilaiBon">;
// 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(),
);
}
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<string> {
// 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<boolean> {
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<boolean> {
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<PelangganPage> {
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<string> {
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")}`;
}