fix(umkm): dashboard backend - jumlahKategoriTerbanyak, kategoriAktif, growth, trendPersen, mode=week - bump to 0.1.40
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,49 +1,86 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
function getWeekRange(offsetWeeks = 0) {
|
||||||
|
const now = new Date();
|
||||||
|
const day = now.getDay();
|
||||||
|
const diffToMonday = day === 0 ? -6 : 1 - day;
|
||||||
|
|
||||||
|
const monday = new Date(now);
|
||||||
|
monday.setDate(now.getDate() + diffToMonday + offsetWeeks * 7);
|
||||||
|
monday.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const sunday = new Date(monday);
|
||||||
|
sunday.setDate(monday.getDate() + 6);
|
||||||
|
sunday.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
return { start: monday, end: sunday };
|
||||||
|
}
|
||||||
|
|
||||||
async function umkmDashboardDetailPenjualan(context: Context) {
|
async function umkmDashboardDetailPenjualan(context: Context) {
|
||||||
const periode = (context.query.periode as string) ||
|
const mode = context.query.mode as string | undefined;
|
||||||
`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`;
|
const isWeek = mode === "week";
|
||||||
|
|
||||||
|
const periode =
|
||||||
|
(context.query.periode as string) ||
|
||||||
|
`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, "0")}`;
|
||||||
|
|
||||||
const date = new Date(periode + "-01");
|
const date = new Date(periode + "-01");
|
||||||
date.setMonth(date.getMonth() - 1);
|
date.setMonth(date.getMonth() - 1);
|
||||||
const periodeLalu = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
|
const periodeLalu = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
|
||||||
|
|
||||||
|
const kategoriId = context.query.kategoriId as string | undefined;
|
||||||
|
const umkmId = context.query.umkmId as string | undefined;
|
||||||
|
|
||||||
|
const whereSkrg = isWeek
|
||||||
|
? { createdAt: { gte: getWeekRange(0).start, lte: getWeekRange(0).end }, deletedAt: null }
|
||||||
|
: { periode, deletedAt: null };
|
||||||
|
|
||||||
|
const whereLalu = isWeek
|
||||||
|
? { createdAt: { gte: getWeekRange(-1).start, lte: getWeekRange(-1).end }, deletedAt: null }
|
||||||
|
: { periode: periodeLalu, deletedAt: null };
|
||||||
|
|
||||||
|
// Filter produk berdasarkan kategori dan/atau UMKM
|
||||||
|
const produkFilter: Record<string, unknown> = { deletedAt: null };
|
||||||
|
if (kategoriId) produkFilter.kategoriProdukId = kategoriId;
|
||||||
|
if (umkmId) produkFilter.umkmId = umkmId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Ambil semua produk yang punya penjualan bulan ini atau bulan lalu
|
|
||||||
const [produkSkrg, produkLalu, allProduks] = await Promise.all([
|
const [produkSkrg, produkLalu, allProduks] = await Promise.all([
|
||||||
prisma.penjualanProduk.groupBy({
|
prisma.penjualanProduk.groupBy({
|
||||||
by: ['produkId'],
|
by: ["produkId"],
|
||||||
where: { periode, deletedAt: null },
|
where: whereSkrg,
|
||||||
_sum: { totalNilai: true, jumlah: true }
|
_sum: { totalNilai: true, jumlah: true },
|
||||||
}),
|
}),
|
||||||
prisma.penjualanProduk.groupBy({
|
prisma.penjualanProduk.groupBy({
|
||||||
by: ['produkId'],
|
by: ["produkId"],
|
||||||
where: { periode: periodeLalu, deletedAt: null },
|
where: whereLalu,
|
||||||
_sum: { totalNilai: true }
|
_sum: { totalNilai: true },
|
||||||
}),
|
}),
|
||||||
// Use PasarDesa
|
|
||||||
prisma.pasarDesa.findMany({
|
prisma.pasarDesa.findMany({
|
||||||
where: { deletedAt: null },
|
where: produkFilter,
|
||||||
select: { id: true, nama: true, stok: true }
|
select: { id: true, nama: true, stok: true },
|
||||||
})
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const data = allProduks.map(p => {
|
const data = allProduks.map((p) => {
|
||||||
const skrgRaw = produkSkrg.find(s => s.produkId === p.id)?._sum || { totalNilai: 0, jumlah: 0 };
|
const skrgRaw = produkSkrg.find((s) => s.produkId === p.id)?._sum || {};
|
||||||
const laluRaw = produkLalu.find(l => l.produkId === p.id)?._sum || { totalNilai: 0 };
|
const laluRaw = produkLalu.find((l) => l.produkId === p.id)?._sum || {};
|
||||||
|
|
||||||
const skrg = {
|
const nilaiSkrg = (skrgRaw as any).totalNilai || 0;
|
||||||
totalNilai: (skrgRaw as any).totalNilai || 0,
|
const nilaiLalu = (laluRaw as any).totalNilai || 0;
|
||||||
jumlah: (skrgRaw as any).jumlah || 0
|
const jumlah = (skrgRaw as any).jumlah || 0;
|
||||||
};
|
|
||||||
const lalu = {
|
|
||||||
totalNilai: (laluRaw as any).totalNilai || 0
|
|
||||||
};
|
|
||||||
|
|
||||||
let trend = "stable";
|
let trend = "stable";
|
||||||
if (skrg.totalNilai > lalu.totalNilai) trend = "up";
|
if (nilaiSkrg > nilaiLalu) trend = "up";
|
||||||
if (skrg.totalNilai < lalu.totalNilai) trend = "down";
|
if (nilaiSkrg < nilaiLalu) trend = "down";
|
||||||
|
|
||||||
|
let trendPersen = 0;
|
||||||
|
if (nilaiLalu > 0) {
|
||||||
|
trendPersen = Math.round(((nilaiSkrg - nilaiLalu) / nilaiLalu) * 10000) / 100;
|
||||||
|
} else if (nilaiSkrg > 0) {
|
||||||
|
trendPersen = 100;
|
||||||
|
}
|
||||||
|
|
||||||
let statusStok = "Aman";
|
let statusStok = "Aman";
|
||||||
if (p.stok < 5) statusStok = "Rendah";
|
if (p.stok < 5) statusStok = "Rendah";
|
||||||
@@ -51,19 +88,17 @@ async function umkmDashboardDetailPenjualan(context: Context) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
namaProduk: p.nama,
|
namaProduk: p.nama,
|
||||||
penjualanBulanIni: skrg.totalNilai,
|
penjualanBulanIni: nilaiSkrg,
|
||||||
penjualanBulanLalu: lalu.totalNilai,
|
penjualanBulanLalu: nilaiLalu,
|
||||||
trend,
|
trend,
|
||||||
volume: skrg.jumlah,
|
trendPersen,
|
||||||
|
volume: jumlah,
|
||||||
stok: p.stok,
|
stok: p.stok,
|
||||||
statusStok
|
statusStok,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return { success: true, data };
|
||||||
success: true,
|
|
||||||
data
|
|
||||||
};
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error di umkmDashboardDetailPenjualan:", e);
|
console.error("Error di umkmDashboardDetailPenjualan:", e);
|
||||||
return { success: false, message: "Gagal mengambil detail penjualan dashboard" };
|
return { success: false, message: "Gagal mengambil detail penjualan dashboard" };
|
||||||
|
|||||||
@@ -1,44 +1,55 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
function getWeekRange(offsetWeeks = 0) {
|
||||||
|
const now = new Date();
|
||||||
|
const day = now.getDay();
|
||||||
|
const diffToMonday = day === 0 ? -6 : 1 - day;
|
||||||
|
|
||||||
|
const monday = new Date(now);
|
||||||
|
monday.setDate(now.getDate() + diffToMonday + offsetWeeks * 7);
|
||||||
|
monday.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const sunday = new Date(monday);
|
||||||
|
sunday.setDate(monday.getDate() + 6);
|
||||||
|
sunday.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
return { start: monday, end: sunday };
|
||||||
|
}
|
||||||
|
|
||||||
async function umkmDashboardKpi(context: Context) {
|
async function umkmDashboardKpi(context: Context) {
|
||||||
|
const mode = context.query.mode as string | undefined;
|
||||||
|
const isWeek = mode === "week";
|
||||||
|
|
||||||
const periode =
|
const periode =
|
||||||
(context.query.periode as string) ||
|
(context.query.periode as string) ||
|
||||||
`${new Date().getFullYear()}-${String(
|
`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, "0")}`;
|
||||||
new Date().getMonth() + 1
|
|
||||||
).padStart(2, "0")}`;
|
const whereClause = isWeek
|
||||||
|
? { createdAt: { gte: getWeekRange(0).start, lte: getWeekRange(0).end }, deletedAt: null }
|
||||||
|
: { periode, deletedAt: null };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// KPI utama
|
|
||||||
const [umkmAktif, totalUmkm, omzetBulanan] = await Promise.all([
|
const [umkmAktif, totalUmkm, omzetBulanan] = await Promise.all([
|
||||||
prisma.umkm.count({
|
prisma.umkm.count({ where: { isActive: true, deletedAt: null } }),
|
||||||
where: { isActive: true, deletedAt: null },
|
prisma.umkm.count({ where: { deletedAt: null } }),
|
||||||
}),
|
|
||||||
prisma.umkm.count({
|
|
||||||
where: { deletedAt: null },
|
|
||||||
}),
|
|
||||||
prisma.penjualanProduk.aggregate({
|
prisma.penjualanProduk.aggregate({
|
||||||
where: { periode, deletedAt: null },
|
where: whereClause,
|
||||||
_sum: { totalNilai: true },
|
_sum: { totalNilai: true },
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// =========================
|
// Cari kategori dengan penjualan terbanyak
|
||||||
// 1. Cari kategori dari penjualan
|
|
||||||
// =========================
|
|
||||||
const salesByCategory = await prisma.penjualanProduk.findMany({
|
const salesByCategory = await prisma.penjualanProduk.findMany({
|
||||||
where: { periode, deletedAt: null },
|
where: whereClause,
|
||||||
select: {
|
select: {
|
||||||
jumlah: true,
|
jumlah: true,
|
||||||
produk: {
|
produk: { select: { kategoriProdukId: true } },
|
||||||
select: {
|
|
||||||
kategoriProdukId: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let kategoriNama = "-";
|
let kategoriNama = "-";
|
||||||
|
let topCategoryId: string | null = null;
|
||||||
|
|
||||||
if (salesByCategory.length > 0) {
|
if (salesByCategory.length > 0) {
|
||||||
const categoryCounts: Record<string, number> = {};
|
const categoryCounts: Record<string, number> = {};
|
||||||
@@ -46,15 +57,10 @@ async function umkmDashboardKpi(context: Context) {
|
|||||||
for (const sale of salesByCategory) {
|
for (const sale of salesByCategory) {
|
||||||
const catId = sale.produk.kategoriProdukId;
|
const catId = sale.produk.kategoriProdukId;
|
||||||
if (!catId) continue;
|
if (!catId) continue;
|
||||||
|
categoryCounts[catId] = (categoryCounts[catId] || 0) + sale.jumlah;
|
||||||
categoryCounts[catId] =
|
|
||||||
(categoryCounts[catId] || 0) + sale.jumlah;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cari kategori dengan penjualan tertinggi
|
|
||||||
let topCategoryId: string | null = null;
|
|
||||||
let maxSales = 0;
|
let maxSales = 0;
|
||||||
|
|
||||||
for (const [id, count] of Object.entries(categoryCounts)) {
|
for (const [id, count] of Object.entries(categoryCounts)) {
|
||||||
if (count > maxSales) {
|
if (count > maxSales) {
|
||||||
maxSales = count;
|
maxSales = count;
|
||||||
@@ -67,34 +73,36 @@ async function umkmDashboardKpi(context: Context) {
|
|||||||
where: { id: topCategoryId },
|
where: { id: topCategoryId },
|
||||||
select: { nama: true },
|
select: { nama: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
kategoriNama = kategori?.nama || "-";
|
kategoriNama = kategori?.nama || "-";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================
|
// Fallback: kategori dari UMKM terbanyak
|
||||||
// 2. Fallback (kalau tidak ada penjualan)
|
|
||||||
// =========================
|
|
||||||
if (kategoriNama === "-") {
|
if (kategoriNama === "-") {
|
||||||
const kategoriTerbanyakUmkm = await prisma.umkm.groupBy({
|
const kategoriTerbanyakUmkm = await prisma.umkm.groupBy({
|
||||||
by: ["kategoriId"],
|
by: ["kategoriId"],
|
||||||
_count: { _all: true },
|
_count: { _all: true },
|
||||||
orderBy: {
|
orderBy: { _count: { kategoriId: "desc" } },
|
||||||
_count: { kategoriId: "desc" },
|
|
||||||
},
|
|
||||||
take: 1,
|
take: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (kategoriTerbanyakUmkm.length > 0) {
|
if (kategoriTerbanyakUmkm.length > 0) {
|
||||||
|
topCategoryId = kategoriTerbanyakUmkm[0].kategoriId;
|
||||||
const kategori = await prisma.kategoriProdukUmkm.findUnique({
|
const kategori = await prisma.kategoriProdukUmkm.findUnique({
|
||||||
where: { id: kategoriTerbanyakUmkm[0].kategoriId },
|
where: { id: topCategoryId },
|
||||||
select: { nama: true },
|
select: { nama: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
kategoriNama = kategori?.nama || "-";
|
kategoriNama = kategori?.nama || "-";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hitung jumlah produk dalam kategori terbanyak
|
||||||
|
const jumlahKategoriTerbanyak = topCategoryId
|
||||||
|
? await prisma.pasarDesa.count({
|
||||||
|
where: { kategoriProdukId: topCategoryId, deletedAt: null },
|
||||||
|
})
|
||||||
|
: 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
@@ -102,15 +110,13 @@ async function umkmDashboardKpi(context: Context) {
|
|||||||
totalUmkm,
|
totalUmkm,
|
||||||
omzetBulanan: omzetBulanan._sum.totalNilai || 0,
|
omzetBulanan: omzetBulanan._sum.totalNilai || 0,
|
||||||
kategoriTerbanyak: kategoriNama,
|
kategoriTerbanyak: kategoriNama,
|
||||||
|
jumlahKategoriTerbanyak,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error di umkmDashboardKpi:", e);
|
console.error("Error di umkmDashboardKpi:", e);
|
||||||
return {
|
return { success: false, message: "Gagal mengambil data KPI dashboard" };
|
||||||
success: false,
|
|
||||||
message: "Gagal mengambil data KPI dashboard",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default umkmDashboardKpi;
|
export default umkmDashboardKpi;
|
||||||
|
|||||||
@@ -1,35 +1,63 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
function getWeekRange(offsetWeeks = 0) {
|
||||||
|
const now = new Date();
|
||||||
|
const day = now.getDay();
|
||||||
|
const diffToMonday = day === 0 ? -6 : 1 - day;
|
||||||
|
|
||||||
|
const monday = new Date(now);
|
||||||
|
monday.setDate(now.getDate() + diffToMonday + offsetWeeks * 7);
|
||||||
|
monday.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const sunday = new Date(monday);
|
||||||
|
sunday.setDate(monday.getDate() + 6);
|
||||||
|
sunday.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
return { start: monday, end: sunday };
|
||||||
|
}
|
||||||
|
|
||||||
async function umkmDashboardRingSummary(context: Context) {
|
async function umkmDashboardRingSummary(context: Context) {
|
||||||
const periode = (context.query.periode as string) ||
|
const mode = context.query.mode as string | undefined;
|
||||||
`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`;
|
const isWeek = mode === "week";
|
||||||
|
|
||||||
|
const periode =
|
||||||
|
(context.query.periode as string) ||
|
||||||
|
`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, "0")}`;
|
||||||
|
|
||||||
// Hitung periode bulan lalu
|
// Hitung periode bulan lalu
|
||||||
const date = new Date(periode + "-01");
|
const date = new Date(periode + "-01");
|
||||||
date.setMonth(date.getMonth() - 1);
|
date.setMonth(date.getMonth() - 1);
|
||||||
const periodeLalu = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
|
const periodeLalu = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
|
||||||
|
|
||||||
|
const whereSkrg = isWeek
|
||||||
|
? { createdAt: { gte: getWeekRange(0).start, lte: getWeekRange(0).end }, deletedAt: null }
|
||||||
|
: { periode, deletedAt: null };
|
||||||
|
|
||||||
|
const whereLalu = isWeek
|
||||||
|
? { createdAt: { gte: getWeekRange(-1).start, lte: getWeekRange(-1).end }, deletedAt: null }
|
||||||
|
: { periode: periodeLalu, deletedAt: null };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [penjualanSkrg, penjualanLalu, produkAktif, totalTransaksi] = await Promise.all([
|
const [penjualanSkrg, penjualanLalu, kategoriAktif, totalTransaksi] = await Promise.all([
|
||||||
prisma.penjualanProduk.aggregate({
|
prisma.penjualanProduk.aggregate({
|
||||||
where: { periode, deletedAt: null },
|
where: whereSkrg,
|
||||||
_sum: { totalNilai: true }
|
_sum: { totalNilai: true },
|
||||||
}),
|
}),
|
||||||
prisma.penjualanProduk.aggregate({
|
prisma.penjualanProduk.aggregate({
|
||||||
where: { periode: periodeLalu, deletedAt: null },
|
where: whereLalu,
|
||||||
_sum: { totalNilai: true }
|
_sum: { totalNilai: true },
|
||||||
}),
|
}),
|
||||||
// Count from PasarDesa
|
// Hitung jumlah kategori aktif
|
||||||
prisma.pasarDesa.count({
|
prisma.kategoriProdukUmkm.count({
|
||||||
where: { isActive: true, deletedAt: null }
|
where: { isActive: true, deletedAt: null },
|
||||||
}),
|
}),
|
||||||
prisma.penjualanProduk.count({ where: { periode, deletedAt: null } })
|
prisma.penjualanProduk.count({ where: whereSkrg }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const skrg = penjualanSkrg._sum.totalNilai || 0;
|
const skrg = penjualanSkrg._sum.totalNilai || 0;
|
||||||
const lalu = penjualanLalu._sum.totalNilai || 0;
|
const lalu = penjualanLalu._sum.totalNilai || 0;
|
||||||
|
|
||||||
let persentasePerubahan = 0;
|
let persentasePerubahan = 0;
|
||||||
if (lalu > 0) {
|
if (lalu > 0) {
|
||||||
persentasePerubahan = ((skrg - lalu) / lalu) * 100;
|
persentasePerubahan = ((skrg - lalu) / lalu) * 100;
|
||||||
@@ -42,9 +70,9 @@ async function umkmDashboardRingSummary(context: Context) {
|
|||||||
data: {
|
data: {
|
||||||
totalPenjualan: skrg,
|
totalPenjualan: skrg,
|
||||||
persentasePerubahan: Math.round(persentasePerubahan * 100) / 100,
|
persentasePerubahan: Math.round(persentasePerubahan * 100) / 100,
|
||||||
produkAktif,
|
kategoriAktif,
|
||||||
totalTransaksi
|
totalTransaksi,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error di umkmDashboardRingSummary:", e);
|
console.error("Error di umkmDashboardRingSummary:", e);
|
||||||
|
|||||||
@@ -1,39 +1,91 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
function getWeekRange(offsetWeeks = 0) {
|
||||||
|
const now = new Date();
|
||||||
|
const day = now.getDay();
|
||||||
|
const diffToMonday = day === 0 ? -6 : 1 - day;
|
||||||
|
|
||||||
|
const monday = new Date(now);
|
||||||
|
monday.setDate(now.getDate() + diffToMonday + offsetWeeks * 7);
|
||||||
|
monday.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const sunday = new Date(monday);
|
||||||
|
sunday.setDate(monday.getDate() + 6);
|
||||||
|
sunday.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
return { start: monday, end: sunday };
|
||||||
|
}
|
||||||
|
|
||||||
async function umkmDashboardTopProduk(context: Context) {
|
async function umkmDashboardTopProduk(context: Context) {
|
||||||
const periode = (context.query.periode as string) ||
|
const mode = context.query.mode as string | undefined;
|
||||||
`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`;
|
const isWeek = mode === "week";
|
||||||
|
|
||||||
|
const periode =
|
||||||
|
(context.query.periode as string) ||
|
||||||
|
`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, "0")}`;
|
||||||
|
|
||||||
|
const date = new Date(periode + "-01");
|
||||||
|
date.setMonth(date.getMonth() - 1);
|
||||||
|
const periodeLalu = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
|
||||||
|
|
||||||
|
const whereSkrg = isWeek
|
||||||
|
? { createdAt: { gte: getWeekRange(0).start, lte: getWeekRange(0).end }, deletedAt: null }
|
||||||
|
: { periode, deletedAt: null };
|
||||||
|
|
||||||
|
const whereLalu = isWeek
|
||||||
|
? { createdAt: { gte: getWeekRange(-1).start, lte: getWeekRange(-1).end }, deletedAt: null }
|
||||||
|
: { periode: periodeLalu, deletedAt: null };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const topPenjualan = await prisma.penjualanProduk.groupBy({
|
const topPenjualan = await prisma.penjualanProduk.groupBy({
|
||||||
by: ['produkId'],
|
by: ["produkId"],
|
||||||
where: { periode, deletedAt: null },
|
where: whereSkrg,
|
||||||
_sum: { totalNilai: true, jumlah: true },
|
_sum: { totalNilai: true, jumlah: true },
|
||||||
orderBy: { _sum: { totalNilai: 'desc' } },
|
orderBy: { _sum: { totalNilai: "desc" } },
|
||||||
take: 3
|
take: 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await Promise.all(topPenjualan.map(async (item) => {
|
// Ambil penjualan periode lalu untuk produk yang sama
|
||||||
// Find from PasarDesa now
|
const produkIds = topPenjualan.map((p) => p.produkId);
|
||||||
const produk = await prisma.pasarDesa.findUnique({
|
const penjualanLalu = await prisma.penjualanProduk.groupBy({
|
||||||
where: { id: item.produkId },
|
by: ["produkId"],
|
||||||
include: { umkm: true }
|
where: { ...whereLalu, produkId: { in: produkIds } },
|
||||||
});
|
_sum: { totalNilai: true },
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
const laluMap = new Map(
|
||||||
namaProduk: produk?.nama || "Unknown",
|
penjualanLalu.map((p) => [p.produkId, p._sum.totalNilai || 0])
|
||||||
namaUmkm: produk?.umkm?.nama || "Unknown",
|
);
|
||||||
totalPenjualan: item._sum.totalNilai || 0,
|
|
||||||
jumlahTerjual: item._sum.jumlah || 0,
|
|
||||||
growth: 0
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
const data = await Promise.all(
|
||||||
success: true,
|
topPenjualan.map(async (item) => {
|
||||||
data
|
const produk = await prisma.pasarDesa.findUnique({
|
||||||
};
|
where: { id: item.produkId },
|
||||||
|
include: { umkm: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalSkrg = item._sum.totalNilai || 0;
|
||||||
|
const totalLalu = laluMap.get(item.produkId) || 0;
|
||||||
|
|
||||||
|
let growth = 0;
|
||||||
|
if (totalLalu > 0) {
|
||||||
|
growth = Math.round(((totalSkrg - totalLalu) / totalLalu) * 10000) / 100;
|
||||||
|
} else if (totalSkrg > 0) {
|
||||||
|
growth = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
namaProduk: produk?.nama || "Unknown",
|
||||||
|
namaUmkm: produk?.umkm?.nama || "Unknown",
|
||||||
|
totalPenjualan: totalSkrg,
|
||||||
|
jumlahTerjual: item._sum.jumlah || 0,
|
||||||
|
growth,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return { success: true, data };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error di umkmDashboardTopProduk:", e);
|
console.error("Error di umkmDashboardTopProduk:", e);
|
||||||
return { success: false, message: "Gagal mengambil top produk" };
|
return { success: false, message: "Gagal mengambil top produk" };
|
||||||
|
|||||||
Reference in New Issue
Block a user