feat(ekonomi): refactor umkm module with sales delete, stock validation, and ordering system

This commit is contained in:
2026-04-24 16:57:43 +08:00
parent 187e3a2115
commit cd7425292c
21 changed files with 561 additions and 248 deletions

View File

@@ -2,33 +2,97 @@ import prisma from "@/lib/prisma";
import { Context } from "elysia";
async function umkmDashboardKpi(context: Context) {
const periode = (context.query.periode as string) ||
`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`;
const periode =
(context.query.periode as string) ||
`${new Date().getFullYear()}-${String(
new Date().getMonth() + 1
).padStart(2, "0")}`;
try {
const [umkmAktif, totalUmkm, omzetBulanan, kategoriTerbanyak] = await Promise.all([
prisma.umkm.count({ where: { isActive: true, deletedAt: null } }),
prisma.umkm.count({ where: { deletedAt: null } }),
// KPI utama
const [umkmAktif, totalUmkm, omzetBulanan] = await Promise.all([
prisma.umkm.count({
where: { isActive: true, deletedAt: null },
}),
prisma.umkm.count({
where: { deletedAt: null },
}),
prisma.penjualanProduk.aggregate({
where: { periode, deletedAt: null },
_sum: { totalNilai: true }
_sum: { totalNilai: true },
}),
prisma.umkm.groupBy({
by: ['kategoriId'],
_count: { _all: true },
orderBy: { _count: { kategoriId: 'desc' } },
take: 1
})
]);
// Ambil nama kategori jika ada
// =========================
// 1. Cari kategori dari penjualan
// =========================
const salesByCategory = await prisma.penjualanProduk.findMany({
where: { periode, deletedAt: null },
select: {
jumlah: true,
produk: {
select: {
kategoriProdukId: true,
},
},
},
});
let kategoriNama = "-";
if (kategoriTerbanyak.length > 0) {
const kat = await prisma.kategoriProduk.findUnique({
where: { id: kategoriTerbanyak[0].kategoriId },
select: { nama: true }
if (salesByCategory.length > 0) {
const categoryCounts: Record<string, number> = {};
for (const sale of salesByCategory) {
const catId = sale.produk.kategoriProdukId;
if (!catId) continue;
categoryCounts[catId] =
(categoryCounts[catId] || 0) + sale.jumlah;
}
// cari kategori dengan penjualan tertinggi
let topCategoryId: string | null = null;
let maxSales = 0;
for (const [id, count] of Object.entries(categoryCounts)) {
if (count > maxSales) {
maxSales = count;
topCategoryId = id;
}
}
if (topCategoryId) {
const kategori = await prisma.kategoriProduk.findUnique({
where: { id: topCategoryId },
select: { nama: true },
});
kategoriNama = kategori?.nama || "-";
}
}
// =========================
// 2. Fallback (kalau tidak ada penjualan)
// =========================
if (kategoriNama === "-") {
const kategoriTerbanyakUmkm = await prisma.umkm.groupBy({
by: ["kategoriId"],
_count: { _all: true },
orderBy: {
_count: { kategoriId: "desc" },
},
take: 1,
});
kategoriNama = kat?.nama || "-";
if (kategoriTerbanyakUmkm.length > 0) {
const kategori = await prisma.kategoriProduk.findUnique({
where: { id: kategoriTerbanyakUmkm[0].kategoriId },
select: { nama: true },
});
kategoriNama = kategori?.nama || "-";
}
}
return {
@@ -37,13 +101,16 @@ async function umkmDashboardKpi(context: Context) {
umkmAktif,
totalUmkm,
omzetBulanan: omzetBulanan._sum.totalNilai || 0,
kategoriTerbanyak: kategoriNama
}
kategoriTerbanyak: kategoriNama,
},
};
} catch (e) {
console.error("Error di umkmDashboardKpi:", e);
return { success: false, message: "Gagal mengambil data KPI dashboard" };
return {
success: false,
message: "Gagal mengambil data KPI dashboard",
};
}
}
export default umkmDashboardKpi;
export default umkmDashboardKpi;

View File

@@ -13,7 +13,21 @@ async function penjualanProdukCreate(context: Context) {
try {
// Gunakan transaction untuk update stok produk (PasarDesa)
const result = await prisma.$transaction(async (tx) => {
// 1. Catat penjualan (relasi ke PasarDesa)
// 1. Validasi stok produk
const produk = await tx.pasarDesa.findUnique({
where: { id: body.produkId },
select: { stok: true }
});
if (!produk) {
throw new Error("Produk tidak ditemukan");
}
if (produk.stok < body.jumlah) {
throw new Error(`Stok tidak mencukupi. Tersedia: ${produk.stok}`);
}
// 2. Catat penjualan (relasi ke PasarDesa)
const penjualan = await tx.penjualanProduk.create({
data: {
produkId: body.produkId,
@@ -26,7 +40,7 @@ async function penjualanProdukCreate(context: Context) {
},
});
// 2. Update stok di model PasarDesa
// 3. Update stok di model PasarDesa
await tx.pasarDesa.update({
where: { id: body.produkId },
data: {