import "server-only"; import { cache } from "react"; import { asc, count, desc, eq, like, or } from "drizzle-orm"; import { amigoDb } from "@/db/amigo"; import { barang, kedalaman_produk, strip } from "@/db/amigo/schema"; import { PER_PAGE } from "@/lib/pagination"; import { escapeLike } from "@/lib/utils"; export type GroupProduct = { kdStrip: string; namaStrip: string; murah1: number; murah2: number; sedang1: number; sedang2: number; mahal1: number; mahal2: number; benikMurah: number; benikSedang: number; benikMahal: number; kelBrg: string | null; kelJns: string; persenTiwul: number; }; export type GroupProductPage = { rows: GroupProduct[]; hasNext: boolean; }; // kdStrip dibuat sistem, bukan isian form. export type GroupProductInput = Omit; function isKdStripExist(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" ); } // Kode = 2 digit urut dari kode tertinggi ("09" -> "10"); semua kode existing numerik. // Sengaja otomatis, padahal PHP lama mengisinya manual (`ajaxstrip.php`). async function generateKodeStrip(): Promise { const [row] = await amigoDb .select({ kdStrip: strip.kd_strip }) .from(strip) .orderBy(desc(strip.kd_strip)) .limit(1); const urut = Number.parseInt(row?.kdStrip ?? "", 10); return String(Number.isNaN(urut) ? 0 : urut + 1).padStart(2, "0"); } export async function insertGroupProduct( input: GroupProductInput, ): 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 kdStrip = await generateKodeStrip(); try { await amigoDb.insert(strip).values({ kd_strip: kdStrip, nama_strip: input.namaStrip, murah1: input.murah1, murah2: input.murah2, sedang1: input.sedang1, sedang2: input.sedang2, mahal1: input.mahal1, mahal2: input.mahal2, benik_murah: input.benikMurah, benik_sedang: input.benikSedang, benik_mahal: input.benikMahal, kel_brg: input.kelBrg, kel_jns: input.kelJns, persen_tiwul: input.persenTiwul, }); return kdStrip; } catch (error) { if (!isKdStripExist(error) || attempt === 2) throw error; } } throw new Error("Kode group product baru sedang dipakai proses lain"); } export async function updateGroupProduct( kdStrip: string, input: GroupProductInput, ): Promise { const [res] = await amigoDb .update(strip) .set({ nama_strip: input.namaStrip, murah1: input.murah1, murah2: input.murah2, sedang1: input.sedang1, sedang2: input.sedang2, mahal1: input.mahal1, mahal2: input.mahal2, benik_murah: input.benikMurah, benik_sedang: input.benikSedang, benik_mahal: input.benikMahal, kel_brg: input.kelBrg, kel_jns: input.kelJns, persen_tiwul: input.persenTiwul, }) .where(eq(strip.kd_strip, kdStrip)); return res.affectedRows > 0; } export type PemakaianGroupProduct = { barang: number; subGroup: number; }; export async function countPemakaianGroupProduct( kdStrip: string, ): Promise { const [[barangRow], [subGroupRow]] = await Promise.all([ amigoDb .select({ total: count() }) .from(barang) .where(eq(barang.kode_strip, kdStrip)), amigoDb .select({ total: count() }) .from(kedalaman_produk) .where(eq(kedalaman_produk.kode_strip, kdStrip)), ]); return { barang: barangRow?.total ?? 0, subGroup: subGroupRow?.total ?? 0 }; } export async function deleteGroupProduct(kdStrip: string): Promise { const [res] = await amigoDb.delete(strip).where(eq(strip.kd_strip, kdStrip)); return res.affectedRows > 0; } export type GroupProductOption = { kdStrip: string; namaStrip: string; }; // Untuk select di form sub group product; jumlah group sedikit, jadi tanpa pagination. // `cache`: dipanggil beberapa komponen di satu render, query cukup sekali per request. export const listGroupProductOptions = cache( async (): Promise => amigoDb .select({ kdStrip: strip.kd_strip, namaStrip: strip.nama_strip }) .from(strip) .orderBy(asc(strip.kd_strip)), ); export async function listGroupProduct( search?: string, page = 1, ): Promise { const q = search?.trim(); const pattern = q ? `%${escapeLike(q)}%` : ""; const rows = await amigoDb .select({ kdStrip: strip.kd_strip, namaStrip: strip.nama_strip, murah1: strip.murah1, murah2: strip.murah2, sedang1: strip.sedang1, sedang2: strip.sedang2, mahal1: strip.mahal1, mahal2: strip.mahal2, benikMurah: strip.benik_murah, benikSedang: strip.benik_sedang, benikMahal: strip.benik_mahal, kelBrg: strip.kel_brg, kelJns: strip.kel_jns, persenTiwul: strip.persen_tiwul, }) .from(strip) .where( q ? or(like(strip.nama_strip, pattern), like(strip.kd_strip, pattern)) : undefined, ) .orderBy(asc(strip.kd_strip)) .limit(PER_PAGE + 1) .offset((page - 1) * PER_PAGE); return { rows: rows.slice(0, PER_PAGE), hasNext: rows.length > PER_PAGE, }; }