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
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")}`;
|
|
}
|