diff --git a/scripts/seed-local-superadmin.mjs b/scripts/seed-local-superadmin.mjs new file mode 100644 index 0000000..887b37b --- /dev/null +++ b/scripts/seed-local-superadmin.mjs @@ -0,0 +1,66 @@ +import { randomBytes, scryptSync } from "node:crypto"; +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); + +const roles = [ + { code: "ADMIN", name: "Administrator" }, + { code: "OWNER", name: "Owner" }, + { code: "PURCHASING", name: "Purchasing" }, + { code: "WAREHOUSE", name: "Warehouse" }, + { code: "QC", name: "Quality Control" }, + { code: "SALES", name: "Sales" }, + { code: "SYSTEM_ADMIN", name: "System Admin" } +]; + +function hashPassword(password) { + const salt = randomBytes(16).toString("hex"); + const derived = scryptSync(password, salt, 64).toString("hex"); + return `${salt}:${derived}`; +} + +async function main() { + for (const role of roles) { + await prisma.role.upsert({ + where: { code: role.code }, + update: { name: role.name }, + create: role + }); + } + + const role = await prisma.role.findUniqueOrThrow({ + where: { code: "SYSTEM_ADMIN" } + }); + + await prisma.user.upsert({ + where: { email: "wirabasalamah@gmail.com" }, + update: { + roleId: role.id, + username: "wirabasalamah", + name: "Wira Basalamah", + passwordHash: hashPassword("password"), + emailVerifiedAt: new Date(), + status: "ACTIVE" + }, + create: { + roleId: role.id, + username: "wirabasalamah", + email: "wirabasalamah@gmail.com", + name: "Wira Basalamah", + passwordHash: hashPassword("password"), + emailVerifiedAt: new Date(), + status: "ACTIVE" + } + }); +} + +main() + .then(async () => { + console.log("Seeded SYSTEM_ADMIN user: wirabasalamah@gmail.com"); + await prisma.$disconnect(); + }) + .catch(async (error) => { + console.error(error); + await prisma.$disconnect(); + process.exit(1); + }); diff --git a/src/app/api/v1/receipts/[id]/route.ts b/src/app/api/v1/receipts/[id]/route.ts index 1fd1951..8ff4f73 100644 --- a/src/app/api/v1/receipts/[id]/route.ts +++ b/src/app/api/v1/receipts/[id]/route.ts @@ -23,7 +23,14 @@ const receiptDetailInclude = { warehouseLocation: true } }, - lots: true + lots: { + include: { + grade: true, + unit: true, + warehouse: true, + warehouseLocation: true + } + } } as const; export async function GET(request: Request, context: RouteContext) { diff --git a/src/app/api/v1/receipts/route.ts b/src/app/api/v1/receipts/route.ts index d010bb4..ab4fb97 100644 --- a/src/app/api/v1/receipts/route.ts +++ b/src/app/api/v1/receipts/route.ts @@ -21,7 +21,14 @@ const receiptDetailInclude = { warehouseLocation: true } }, - lots: true + lots: { + include: { + grade: true, + unit: true, + warehouse: true, + warehouseLocation: true + } + } } as const; export async function GET(request: Request) { @@ -60,12 +67,8 @@ export async function POST(request: Request) { const purchase = await prisma.purchase.findUnique({ where: { id: purchaseId }, include: { - _count: { - select: { - receipts: true, - lots: true - } - } + receipts: { select: { id: true, receiptNo: true } }, + lots: { select: { id: true } } } }); @@ -73,16 +76,13 @@ export async function POST(request: Request) { return NextResponse.json({ message: "Purchase not found" }, { status: 404 }); } - if (purchase.status !== "SUBMITTED") { - return NextResponse.json( - { message: "Purchase harus diajukan sebelum dibuatkan receipt." }, - { status: 409 } - ); + if (purchase.status === "CANCELLED") { + return NextResponse.json({ message: "Purchase yang dibatalkan tidak bisa dibuatkan receipt" }, { status: 422 }); } - if (purchase._count.receipts > 0 || purchase._count.lots > 0) { + if (purchase.receipts.length > 0 || purchase.lots.length > 0) { return NextResponse.json( - { message: "Purchase sudah memiliki receipt/lot." }, + { message: `Purchase sudah memiliki receipt/lot ${purchase.receipts[0]?.receiptNo ?? ""}`.trim() }, { status: 409 } ); } diff --git a/src/features/purchases/components/purchases-client.tsx b/src/features/purchases/components/purchases-client.tsx index a4b99c9..3ec9f55 100644 --- a/src/features/purchases/components/purchases-client.tsx +++ b/src/features/purchases/components/purchases-client.tsx @@ -15,9 +15,7 @@ import type { EmployeeRecord, GradeRecord, ProfitShareSchemeRecord, - UnitRecord, - WarehouseLocationRecord, - WarehouseRecord + UnitRecord } from "@/types/master-data"; import type { PurchaseDetail, PurchaseListItem } from "@/types/purchase"; @@ -51,8 +49,6 @@ type PurchaseForm = { above_average_ratio_percent: string; mk_share_percent: string; non_mk_share_percent: string; - warehouse_id: string; - warehouse_location_id: string; modal_beli: string; modal_barang: string; modal_masuk: string; @@ -107,8 +103,6 @@ const emptyForm = (): PurchaseForm => ({ above_average_ratio_percent: "", mk_share_percent: "", non_mk_share_percent: "", - warehouse_id: "", - warehouse_location_id: "", modal_beli: "", modal_barang: "", modal_masuk: "", @@ -187,8 +181,6 @@ export function PurchasesClient({ currencyCode }: PurchasesClientProps) { const [profitShareSchemes, setProfitShareSchemes] = useState([]); const [couriers, setCouriers] = useState([]); const [employees, setEmployees] = useState([]); - const [warehouses, setWarehouses] = useState([]); - const [locations, setLocations] = useState([]); const [form, setForm] = useState(emptyForm); const [editingId, setEditingId] = useState(null); const [selectedPurchase, setSelectedPurchase] = useState(null); @@ -209,8 +201,6 @@ export function PurchasesClient({ currencyCode }: PurchasesClientProps) { profitShareSchemesRes, couriersRes, employeesRes, - warehousesRes, - locationsRes, gradesRes ] = await Promise.all([ fetch("/api/v1/purchases?purchase_type=REGULAR", { cache: "no-store" }), @@ -219,8 +209,6 @@ export function PurchasesClient({ currencyCode }: PurchasesClientProps) { fetch("/api/v1/profit-share-schemes", { cache: "no-store" }), fetch("/api/v1/couriers", { cache: "no-store" }), fetch("/api/v1/employees", { cache: "no-store" }), - fetch("/api/v1/warehouses", { cache: "no-store" }), - fetch("/api/v1/warehouse-locations", { cache: "no-store" }), fetch("/api/v1/grades", { cache: "no-store" }) ]); @@ -233,11 +221,6 @@ export function PurchasesClient({ currencyCode }: PurchasesClientProps) { ); const couriersPayload = await parseJsonResponse<{ data: CourierRecord[] }>(couriersRes, "Memuat daftar kurir"); const employeesPayload = await parseJsonResponse<{ data: EmployeeRecord[] }>(employeesRes, "Memuat daftar pegawai"); - const warehousesPayload = await parseJsonResponse<{ data: WarehouseRecord[] }>(warehousesRes, "Memuat gudang"); - const locationsPayload = await parseJsonResponse<{ data: WarehouseLocationRecord[] }>( - locationsRes, - "Memuat lokasi gudang" - ); const gradesPayload = await parseJsonResponse<{ data: GradeRecord[] }>(gradesRes, "Memuat grade"); if ( @@ -247,8 +230,6 @@ export function PurchasesClient({ currencyCode }: PurchasesClientProps) { !profitShareSchemesRes.ok || !couriersRes.ok || !employeesRes.ok || - !warehousesRes.ok || - !locationsRes.ok || !gradesRes.ok ) { throw new Error(dict.purchases.loadingError); @@ -260,8 +241,6 @@ export function PurchasesClient({ currencyCode }: PurchasesClientProps) { setProfitShareSchemes(profitShareSchemesPayload.data); setCouriers(couriersPayload.data); setEmployees(employeesPayload.data); - setWarehouses(warehousesPayload.data); - setLocations(locationsPayload.data); setGrades(gradesPayload.data); } catch (err) { setError(err instanceof Error ? err.message : dict.purchases.loadingError); @@ -331,8 +310,6 @@ export function PurchasesClient({ currencyCode }: PurchasesClientProps) { setEditingId(detail.id); const defaultUnitId = detail.lines[0]?.unit.id ?? units[0]?.id ?? ""; - const defaultWarehouseId = detail.lines[0]?.warehouse?.id ?? ""; - const defaultWarehouseLocationId = detail.lines[0]?.warehouse_location?.id ?? ""; const beratBeli = detail.analysis?.weight_buy ?? detail.lines.reduce((sum, line) => sum + line.qty_ordered, 0); const beratMasuk = detail.analysis?.weight_received ?? beratBeli; const derivedModalBarang = detail.lines.reduce( @@ -356,8 +333,6 @@ export function PurchasesClient({ currencyCode }: PurchasesClientProps) { above_average_ratio_percent: detail.above_average_ratio_percent?.toString() ?? "", mk_share_percent: detail.mk_share_percent?.toString() ?? "", non_mk_share_percent: detail.non_mk_share_percent?.toString() ?? "", - warehouse_id: defaultWarehouseId, - warehouse_location_id: defaultWarehouseLocationId, modal_beli: String(modalBeli), modal_barang: String(modalBarang), modal_masuk: detail.analysis?.modal_masuk?.toString() ?? "", @@ -447,8 +422,8 @@ export function PurchasesClient({ currencyCode }: PurchasesClientProps) { const beratBeliKgPayload = convertQtyToKg(beratBeliPayload, selectedUnit?.code); const modalBeliPayload = beratBeliKgPayload > 0 ? totalModalBeliValue / beratBeliKgPayload : 0; const payload = { - warehouse_id: form.warehouse_id || "", - warehouse_location_id: form.warehouse_location_id || null, + warehouse_id: "", + warehouse_location_id: null, modal_beli: modalBeliPayload, modal_barang: modalBarangValue, modal_masuk: modalMasuk ?? 0, @@ -485,8 +460,8 @@ export function PurchasesClient({ currencyCode }: PurchasesClientProps) { unit_cost: 0, mal_unit_price: numberFromForm(line.mal_unit_price), classification_status: "FINAL", - warehouse_id: form.warehouse_id || "", - warehouse_location_id: form.warehouse_location_id || null, + warehouse_id: "", + warehouse_location_id: null, notes: line.notes || "" })), cost_lines: normalizedCostLines.map((costLine) => ({ @@ -869,21 +844,14 @@ export function PurchasesClient({ currencyCode }: PurchasesClientProps) { }); }, [barangAtasRata2Value, computedMkSharePercent, computedNonMkSharePercent, kadarMasukValue]); - const locationOptions = useMemo( - () => locations.filter((location) => location.warehouse_id === form.warehouse_id), - [form.warehouse_id, locations] - ); - const masterReady = grades.length > 0 && units.length > 0 && - employees.length > 0 && - warehouses.length > 0; + employees.length > 0; const missingMasterLabels = [ grades.length === 0 ? "grade" : null, units.length === 0 ? "unit" : null, - employees.length === 0 ? "pegawai penerima" : null, - warehouses.length === 0 ? "gudang" : null + employees.length === 0 ? "pegawai penerima" : null ].filter((value): value is string => Boolean(value)); const agentMap = useMemo(() => { @@ -1112,29 +1080,6 @@ export function PurchasesClient({ currencyCode }: PurchasesClientProps) { -
- - setForm((current) => ({ - ...current, - warehouse_id: value, - warehouse_location_id: "" - })) - } - options={warehouses.map((warehouse) => ({ value: warehouse.id, label: `${warehouse.code} - ${warehouse.name}` }))} - placeholder={dict.receipts.chooseWarehouse} - /> - setForm((current) => ({ ...current, warehouse_location_id: value }))} - options={locationOptions.map((location) => ({ value: location.id, label: `${location.code} - ${location.name}` }))} - placeholder={dict.purchases.optional} - /> -
-