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.
205 lines
5.5 KiB
TypeScript
205 lines
5.5 KiB
TypeScript
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<GroupProduct, "kdStrip">;
|
|
|
|
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<string> {
|
|
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<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 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<boolean> {
|
|
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<PemakaianGroupProduct> {
|
|
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<boolean> {
|
|
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<GroupProductOption[]> =>
|
|
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<GroupProductPage> {
|
|
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,
|
|
};
|
|
}
|