feat(ekonomi): refactor umkm module with sales delete, stock validation, and ordering system
This commit is contained in:
@@ -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;
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user