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.

149 lines
4.2 KiB
TypeScript

"use server";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import { getCurrentUser } from "@/data/auth";
import {
SubGroupProductDitolakError,
countPemakaianSubGroupProduct,
deleteSubGroupProduct,
insertSubGroupProduct,
updateSubGroupProduct,
} from "@/data/sub-group-product";
import {
logActionError,
type DeleteState,
type FormState,
} from "@/lib/server-action";
type SubGroupProductField = keyof typeof subGroupProductSchema.shape;
export type SimpanSubGroupProductState = FormState<SubGroupProductField> & {
id?: number;
};
export type HapusSubGroupProductState = DeleteState;
const subGroupProductSchema = z.object({
kodeKedProduk: z
.string()
.trim()
.min(1, "Kode wajib diisi")
.max(1, "Kode maksimal 1 karakter"),
kodeStrip: z
.string()
.trim()
.min(1, "Group product wajib dipilih")
.max(2, "Kode group product maksimal 2 karakter"),
kedProduk: z
.string()
.trim()
.min(1, "Nama wajib diisi")
.max(50, "Nama maksimal 50 karakter"),
});
// Edit hanya mengubah nama; kode dikunci (lihat SubGroupProductUpdate).
const updateSchema = subGroupProductSchema.pick({ kedProduk: true });
export async function simpanSubGroupProduct(
_prevState: SimpanSubGroupProductState,
formData: FormData,
): Promise<SimpanSubGroupProductState> {
const user = await getCurrentUser();
const fields = Object.keys(
subGroupProductSchema.shape,
) as SubGroupProductField[];
const values = Object.fromEntries(
fields.map((field) => [field, String(formData.get(field) ?? "")]),
) as Record<SubGroupProductField, string>;
const idRaw = String(formData.get("id") ?? "").trim();
const id = idRaw === "" ? null : Number.parseInt(idRaw, 10);
if (id !== null && !Number.isSafeInteger(id)) {
return { ok: false, error: "Sub group product tidak valid", values };
}
try {
if (id === null) {
const parsed = subGroupProductSchema.safeParse(values);
if (!parsed.success) {
return {
ok: false,
fieldErrors: z.flattenError(parsed.error).fieldErrors,
values,
};
}
const idBaru = await insertSubGroupProduct(parsed.data);
revalidatePath("/master/sub-group-product");
return { ok: true, id: idBaru };
}
const parsed = updateSchema.safeParse(values);
if (!parsed.success) {
return {
ok: false,
fieldErrors: z.flattenError(parsed.error).fieldErrors,
values,
};
}
const updated = await updateSubGroupProduct(id, parsed.data);
if (!updated) {
return { ok: false, error: "Sub group product tidak ditemukan", values };
}
revalidatePath("/master/sub-group-product");
return { ok: true, id };
} catch (error) {
// Penolakan aturan bisnis adalah validasi biasa, bukan error tak terduga, jadi tidak di-log.
if (error instanceof SubGroupProductDitolakError) {
return { ok: false, error: error.message, values };
}
logActionError(
"simpanSubGroupProduct",
{ userId: user.id, id: id ?? "(baru)", ...values },
error,
);
return {
ok: false,
error: "Gagal menyimpan sub group product, coba simpan ulang",
values,
};
}
}
export async function hapusSubGroupProduct(
_prevState: HapusSubGroupProductState,
formData: FormData,
): Promise<HapusSubGroupProductState> {
const user = await getCurrentUser();
const id = Number.parseInt(String(formData.get("id") ?? ""), 10);
if (!Number.isSafeInteger(id)) {
return { ok: false, error: "Sub group product tidak valid" };
}
try {
const dipakai = await countPemakaianSubGroupProduct(id);
if (dipakai > 0) {
return {
ok: false,
error: `Sub group product masih dipakai ${dipakai} barang, tidak bisa dihapus`,
};
}
const deleted = await deleteSubGroupProduct(id);
if (!deleted) {
return { ok: false, error: "Sub group product tidak ditemukan" };
}
revalidatePath("/master/sub-group-product");
return { ok: true };
} catch (error) {
logActionError("hapusSubGroupProduct", { userId: user.id, id }, error);
return {
ok: false,
error: "Gagal menghapus sub group product, coba hapus ulang",
};
}
}