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.
166 lines
4.8 KiB
TypeScript
166 lines
4.8 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { z } from "zod";
|
|
import { getCurrentUser } from "@/data/auth";
|
|
import {
|
|
countPemakaianGroupProduct,
|
|
deleteGroupProduct,
|
|
insertGroupProduct,
|
|
updateGroupProduct,
|
|
type GroupProductInput,
|
|
} from "@/data/group-product";
|
|
import {
|
|
logActionError,
|
|
type DeleteState,
|
|
type FormState,
|
|
} from "@/lib/server-action";
|
|
|
|
type GroupProductField = keyof typeof groupProductSchema.shape;
|
|
export type SimpanGroupProductState = FormState<GroupProductField> & {
|
|
kdStrip?: string;
|
|
};
|
|
|
|
export type HapusGroupProductState = DeleteState;
|
|
|
|
// Batas atas = rentang kolom `int` MariaDB.
|
|
const angka = z.coerce
|
|
.number({ error: "Harus berupa angka" })
|
|
.int("Harus bilangan bulat")
|
|
.min(0, "Tidak boleh negatif")
|
|
.max(2_000_000_000, "Maksimal 2.000.000.000");
|
|
|
|
const groupProductSchema = z.object({
|
|
namaStrip: z
|
|
.string()
|
|
.trim()
|
|
.min(1, "Nama wajib diisi")
|
|
.max(50, "Nama maksimal 50 karakter"),
|
|
murah1: angka,
|
|
murah2: angka,
|
|
sedang1: angka,
|
|
sedang2: angka,
|
|
mahal1: angka,
|
|
mahal2: angka,
|
|
benikMurah: angka,
|
|
benikSedang: angka,
|
|
benikMahal: angka,
|
|
kelBrg: z.string().trim().max(1, "Kelompok barang maksimal 1 karakter"),
|
|
kelJns: z.string().trim().max(1, "Kategori maksimal 1 karakter"),
|
|
persenTiwul: z.coerce
|
|
.number({ error: "Harus berupa angka" })
|
|
.min(0, "Tidak boleh negatif")
|
|
.max(100, "Maksimal 100"),
|
|
});
|
|
|
|
function emptyStringToNull(value: string): string | null {
|
|
return value === "" ? null : value;
|
|
}
|
|
|
|
// Kolom angka dikosongkan berarti 0, kecuali persen tiwul yang default-nya 0.025.
|
|
function angkaAtauDefault(value: string, def: string): string {
|
|
return value === "" ? def : value;
|
|
}
|
|
|
|
export async function simpanGroupProduct(
|
|
_prevState: SimpanGroupProductState,
|
|
formData: FormData,
|
|
): Promise<SimpanGroupProductState> {
|
|
const user = await getCurrentUser();
|
|
|
|
const fields = Object.keys(groupProductSchema.shape) as GroupProductField[];
|
|
const raw = Object.fromEntries(
|
|
fields.map((field) => [field, String(formData.get(field) ?? "")]),
|
|
) as Record<GroupProductField, string>;
|
|
|
|
const values: Record<GroupProductField, string> = {
|
|
...raw,
|
|
murah1: angkaAtauDefault(raw.murah1, "0"),
|
|
murah2: angkaAtauDefault(raw.murah2, "0"),
|
|
sedang1: angkaAtauDefault(raw.sedang1, "0"),
|
|
sedang2: angkaAtauDefault(raw.sedang2, "0"),
|
|
mahal1: angkaAtauDefault(raw.mahal1, "0"),
|
|
mahal2: angkaAtauDefault(raw.mahal2, "0"),
|
|
benikMurah: angkaAtauDefault(raw.benikMurah, "0"),
|
|
benikSedang: angkaAtauDefault(raw.benikSedang, "0"),
|
|
benikMahal: angkaAtauDefault(raw.benikMahal, "0"),
|
|
persenTiwul: angkaAtauDefault(raw.persenTiwul, "0.025"),
|
|
};
|
|
|
|
const parsed = groupProductSchema.safeParse(values);
|
|
if (!parsed.success) {
|
|
return {
|
|
ok: false,
|
|
fieldErrors: z.flattenError(parsed.error).fieldErrors,
|
|
values,
|
|
};
|
|
}
|
|
|
|
const input: GroupProductInput = {
|
|
...parsed.data,
|
|
kelBrg: emptyStringToNull(parsed.data.kelBrg),
|
|
};
|
|
const kdStrip = String(formData.get("kdStrip") ?? "").trim();
|
|
|
|
try {
|
|
if (kdStrip === "") {
|
|
const kdBaru = await insertGroupProduct(input);
|
|
revalidatePath("/master/group-product");
|
|
return { ok: true, kdStrip: kdBaru };
|
|
}
|
|
|
|
const updated = await updateGroupProduct(kdStrip, input);
|
|
if (!updated) {
|
|
return { ok: false, error: "Group product tidak ditemukan", values };
|
|
}
|
|
revalidatePath("/master/group-product");
|
|
return { ok: true, kdStrip };
|
|
} catch (error) {
|
|
logActionError(
|
|
"simpanGroupProduct",
|
|
{ userId: user.id, kdStrip: kdStrip || "(baru)", nama: input.namaStrip },
|
|
error,
|
|
);
|
|
return {
|
|
ok: false,
|
|
error: "Gagal menyimpan group product, coba simpan ulang",
|
|
values,
|
|
};
|
|
}
|
|
}
|
|
|
|
export async function hapusGroupProduct(
|
|
_prevState: HapusGroupProductState,
|
|
formData: FormData,
|
|
): Promise<HapusGroupProductState> {
|
|
const user = await getCurrentUser();
|
|
|
|
const kdStrip = String(formData.get("kdStrip") ?? "").trim();
|
|
if (kdStrip === "") {
|
|
return { ok: false, error: "Kode group product tidak ada" };
|
|
}
|
|
|
|
try {
|
|
const dipakai = await countPemakaianGroupProduct(kdStrip);
|
|
if (dipakai.barang > 0 || dipakai.subGroup > 0) {
|
|
return {
|
|
ok: false,
|
|
error: `Group product masih dipakai ${dipakai.barang} barang dan ${dipakai.subGroup} sub group product, tidak bisa dihapus`,
|
|
};
|
|
}
|
|
|
|
const deleted = await deleteGroupProduct(kdStrip);
|
|
if (!deleted) {
|
|
return { ok: false, error: "Group product tidak ditemukan" };
|
|
}
|
|
revalidatePath("/master/group-product");
|
|
return { ok: true };
|
|
} catch (error) {
|
|
logActionError("hapusGroupProduct", { userId: user.id, kdStrip }, error);
|
|
return {
|
|
ok: false,
|
|
error: "Gagal menghapus group product, coba hapus ulang",
|
|
};
|
|
}
|
|
}
|