feat(ekonomi): implement UMKM module with CRUD API and Dashboard analytics
This commit is contained in:
@@ -11,6 +11,10 @@ import DemografiPekerjaan from "./demografi-pekerjaan";
|
||||
import JumlahPengangguran from "./jumlah-pengangguran";
|
||||
import PendapatanAsliDesa from "./pendapatan-asli-desa";
|
||||
import StrukturOrganisasi from "./struktur-bumdes";
|
||||
import Umkm from "./umkm";
|
||||
import ProdukUmkm from "./umkm/produk";
|
||||
import PenjualanProduk from "./umkm/penjualan";
|
||||
import UmkmDashboard from "./umkm/dashboard";
|
||||
|
||||
const Ekonomi = new Elysia({
|
||||
prefix: "/ekonomi",
|
||||
@@ -21,6 +25,10 @@ const Ekonomi = new Elysia({
|
||||
.use(LowonganKerja)
|
||||
.use(ProgramKemiskinan)
|
||||
.use(StrukturOrganisasi)
|
||||
.use(Umkm)
|
||||
.use(ProdukUmkm)
|
||||
.use(PenjualanProduk)
|
||||
.use(UmkmDashboard)
|
||||
.use(GrafikUsiaKerjaYangMenganggur)
|
||||
.use(GrafikMenganggurBerdasarkanPendidikan)
|
||||
.use(JumlahPendudukMiskin)
|
||||
|
||||
35
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/create.ts
Normal file
35
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/create.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function umkmCreate(context: Context) {
|
||||
const body = context.body as any;
|
||||
|
||||
try {
|
||||
const data = await prisma.umkm.create({
|
||||
data: {
|
||||
nama: body.nama,
|
||||
pemilik: body.pemilik,
|
||||
deskripsi: body.deskripsi,
|
||||
alamat: body.alamat,
|
||||
kontak: body.kontak,
|
||||
imageId: body.imageId,
|
||||
kategoriId: body.kategoriId,
|
||||
isActive: body.isActive ?? true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil membuat UMKM baru",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di umkmCreate:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal membuat UMKM baru",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default umkmCreate;
|
||||
@@ -0,0 +1,72 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function umkmDashboardDetailPenjualan(context: Context) {
|
||||
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')}`;
|
||||
|
||||
try {
|
||||
// Ambil semua produk yang punya penjualan bulan ini atau bulan lalu
|
||||
const [produkSkrg, produkLalu, allProduks] = await Promise.all([
|
||||
prisma.penjualanProduk.groupBy({
|
||||
by: ['produkId'],
|
||||
where: { periode, deletedAt: null },
|
||||
_sum: { totalNilai: true, jumlah: true }
|
||||
}),
|
||||
prisma.penjualanProduk.groupBy({
|
||||
by: ['produkId'],
|
||||
where: { periode: periodeLalu, deletedAt: null },
|
||||
_sum: { totalNilai: true }
|
||||
}),
|
||||
prisma.produkUmkm.findMany({
|
||||
where: { deletedAt: null },
|
||||
select: { id: true, nama: true, stok: true }
|
||||
})
|
||||
]);
|
||||
|
||||
const data = allProduks.map(p => {
|
||||
const skrgRaw = produkSkrg.find(s => s.produkId === p.id)?._sum || { totalNilai: 0, jumlah: 0 };
|
||||
const laluRaw = produkLalu.find(l => l.produkId === p.id)?._sum || { totalNilai: 0 };
|
||||
|
||||
const skrg = {
|
||||
totalNilai: skrgRaw.totalNilai || 0,
|
||||
jumlah: skrgRaw.jumlah || 0
|
||||
};
|
||||
const lalu = {
|
||||
totalNilai: laluRaw.totalNilai || 0
|
||||
};
|
||||
|
||||
let trend = "stable";
|
||||
if (skrg.totalNilai > lalu.totalNilai) trend = "up";
|
||||
if (skrg.totalNilai < lalu.totalNilai) trend = "down";
|
||||
|
||||
let statusStok = "Aman";
|
||||
if (p.stok < 5) statusStok = "Rendah";
|
||||
else if (p.stok < 20) statusStok = "Menipis";
|
||||
|
||||
return {
|
||||
namaProduk: p.nama,
|
||||
penjualanBulanIni: skrg.totalNilai,
|
||||
penjualanBulanLalu: lalu.totalNilai,
|
||||
trend,
|
||||
volume: skrg.jumlah,
|
||||
stok: p.stok,
|
||||
statusStok
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di umkmDashboardDetailPenjualan:", e);
|
||||
return { success: false, message: "Gagal mengambil detail penjualan dashboard" };
|
||||
}
|
||||
}
|
||||
|
||||
export default umkmDashboardDetailPenjualan;
|
||||
@@ -0,0 +1,16 @@
|
||||
import Elysia from "elysia";
|
||||
import umkmDashboardKpi from "./kpi";
|
||||
import umkmDashboardRingSummary from "./ringSummary";
|
||||
import umkmDashboardTopProduk from "./topProduk";
|
||||
import umkmDashboardDetailPenjualan from "./detailPenjualan";
|
||||
|
||||
const UmkmDashboard = new Elysia({
|
||||
prefix: "/umkm/dashboard",
|
||||
tags: ["Ekonomi/UMKM Dashboard"],
|
||||
})
|
||||
.get("/kpi", umkmDashboardKpi)
|
||||
.get("/ringkasan-penjualan", umkmDashboardRingSummary)
|
||||
.get("/top-produk", umkmDashboardTopProduk)
|
||||
.get("/detail-penjualan", umkmDashboardDetailPenjualan);
|
||||
|
||||
export default UmkmDashboard;
|
||||
49
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/kpi.ts
Normal file
49
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/kpi.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
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')}`;
|
||||
|
||||
try {
|
||||
const [umkmAktif, totalUmkm, omzetBulanan, kategoriTerbanyak] = 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 }
|
||||
}),
|
||||
prisma.umkm.groupBy({
|
||||
by: ['kategoriId'],
|
||||
_count: { _all: true },
|
||||
orderBy: { _count: { kategoriId: 'desc' } },
|
||||
take: 1
|
||||
})
|
||||
]);
|
||||
|
||||
// Ambil nama kategori jika ada
|
||||
let kategoriNama = "-";
|
||||
if (kategoriTerbanyak.length > 0) {
|
||||
const kat = await prisma.kategoriProduk.findUnique({
|
||||
where: { id: kategoriTerbanyak[0].kategoriId },
|
||||
select: { nama: true }
|
||||
});
|
||||
kategoriNama = kat?.nama || "-";
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
umkmAktif,
|
||||
totalUmkm,
|
||||
omzetBulanan: omzetBulanan._sum.totalNilai || 0,
|
||||
kategoriTerbanyak: kategoriNama
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di umkmDashboardKpi:", e);
|
||||
return { success: false, message: "Gagal mengambil data KPI dashboard" };
|
||||
}
|
||||
}
|
||||
|
||||
export default umkmDashboardKpi;
|
||||
@@ -0,0 +1,52 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function umkmDashboardRingSummary(context: Context) {
|
||||
const periode = (context.query.periode as string) ||
|
||||
`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`;
|
||||
|
||||
// Hitung periode bulan lalu
|
||||
const date = new Date(periode + "-01");
|
||||
date.setMonth(date.getMonth() - 1);
|
||||
const periodeLalu = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
|
||||
|
||||
try {
|
||||
const [penjualanSkrg, penjualanLalu, produkAktif, totalTransaksi] = await Promise.all([
|
||||
prisma.penjualanProduk.aggregate({
|
||||
where: { periode, deletedAt: null },
|
||||
_sum: { totalNilai: true }
|
||||
}),
|
||||
prisma.penjualanProduk.aggregate({
|
||||
where: { periode: periodeLalu, deletedAt: null },
|
||||
_sum: { totalNilai: true }
|
||||
}),
|
||||
prisma.produkUmkm.count({ where: { isActive: true, deletedAt: null } }),
|
||||
prisma.penjualanProduk.count({ where: { periode, deletedAt: null } })
|
||||
]);
|
||||
|
||||
const skrg = penjualanSkrg._sum.totalNilai || 0;
|
||||
const lalu = penjualanLalu._sum.totalNilai || 0;
|
||||
|
||||
let persentasePerubahan = 0;
|
||||
if (lalu > 0) {
|
||||
persentasePerubahan = ((skrg - lalu) / lalu) * 100;
|
||||
} else if (skrg > 0) {
|
||||
persentasePerubahan = 100;
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
totalPenjualan: skrg,
|
||||
persentasePerubahan: Math.round(persentasePerubahan * 100) / 100,
|
||||
produkAktif,
|
||||
totalTransaksi
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di umkmDashboardRingSummary:", e);
|
||||
return { success: false, message: "Gagal mengambil ringkasan dashboard" };
|
||||
}
|
||||
}
|
||||
|
||||
export default umkmDashboardRingSummary;
|
||||
@@ -0,0 +1,42 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function umkmDashboardTopProduk(context: Context) {
|
||||
const periode = (context.query.periode as string) ||
|
||||
`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`;
|
||||
|
||||
try {
|
||||
const topPenjualan = await prisma.penjualanProduk.groupBy({
|
||||
by: ['produkId'],
|
||||
where: { periode, deletedAt: null },
|
||||
_sum: { totalNilai: true, jumlah: true },
|
||||
orderBy: { _sum: { totalNilai: 'desc' } },
|
||||
take: 3
|
||||
});
|
||||
|
||||
const data = await Promise.all(topPenjualan.map(async (item) => {
|
||||
const produk = await prisma.produkUmkm.findUnique({
|
||||
where: { id: item.produkId },
|
||||
include: { umkm: true }
|
||||
});
|
||||
|
||||
return {
|
||||
namaProduk: produk?.nama || "Unknown",
|
||||
namaUmkm: produk?.umkm.nama || "Unknown",
|
||||
totalPenjualan: item._sum.totalNilai || 0,
|
||||
jumlahTerjual: item._sum.jumlah || 0,
|
||||
growth: 0 // logic growth bisa ditambah jika diperlukan
|
||||
};
|
||||
}));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di umkmDashboardTopProduk:", e);
|
||||
return { success: false, message: "Gagal mengambil top produk" };
|
||||
}
|
||||
}
|
||||
|
||||
export default umkmDashboardTopProduk;
|
||||
31
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/del.ts
Normal file
31
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/del.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function umkmDelete(context: Context) {
|
||||
const id = context.params.id;
|
||||
|
||||
try {
|
||||
// Soft delete
|
||||
const data = await prisma.umkm.update({
|
||||
where: { id },
|
||||
data: {
|
||||
deletedAt: new Date(),
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil menghapus UMKM",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di umkmDelete:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal menghapus UMKM",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default umkmDelete;
|
||||
59
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/findMany.ts
Normal file
59
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/findMany.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function umkmFindMany(context: Context) {
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const kategoriId = context.query.kategoriId as string | undefined;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where: any = { deletedAt: null };
|
||||
|
||||
if (kategoriId) {
|
||||
where.kategoriId = kategoriId;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ nama: { contains: search, mode: 'insensitive' } },
|
||||
{ pemilik: { contains: search, mode: 'insensitive' } },
|
||||
{ alamat: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.umkm.findMany({
|
||||
where,
|
||||
include: {
|
||||
image: true,
|
||||
kategori: true,
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.umkm.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengambil data UMKM",
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di umkmFindMany:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data UMKM",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default umkmFindMany;
|
||||
31
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/findManyAll.ts
Normal file
31
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/findManyAll.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
async function umkmFindManyAll() {
|
||||
try {
|
||||
const data = await prisma.umkm.findMany({
|
||||
where: {
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
nama: true,
|
||||
},
|
||||
orderBy: { nama: 'asc' },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengambil semua UMKM aktif",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di umkmFindManyAll:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data UMKM",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default umkmFindManyAll;
|
||||
41
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/findUnique.ts
Normal file
41
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/findUnique.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function umkmFindUnique(context: Context) {
|
||||
const id = context.params.id;
|
||||
|
||||
try {
|
||||
const data = await prisma.umkm.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
image: true,
|
||||
kategori: true,
|
||||
produk: {
|
||||
where: { deletedAt: null },
|
||||
include: { image: true }
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
message: "UMKM tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengambil detail UMKM",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di umkmFindUnique:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil detail UMKM",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default umkmFindUnique;
|
||||
53
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/index.ts
Normal file
53
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/index.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import umkmCreate from "./create";
|
||||
import umkmDelete from "./del";
|
||||
import umkmFindMany from "./findMany";
|
||||
import umkmFindManyAll from "./findManyAll";
|
||||
import umkmFindUnique from "./findUnique";
|
||||
import umkmUpdate from "./updt";
|
||||
|
||||
const Umkm = new Elysia({
|
||||
prefix: "/umkm",
|
||||
tags: ["Ekonomi/UMKM"],
|
||||
})
|
||||
.get("/find-many", umkmFindMany)
|
||||
.get("/find-many-all", umkmFindManyAll)
|
||||
.get("/:id", umkmFindUnique, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
})
|
||||
.post("/create", umkmCreate, {
|
||||
body: t.Object({
|
||||
nama: t.String(),
|
||||
pemilik: t.String(),
|
||||
kategoriId: t.String(),
|
||||
deskripsi: t.Optional(t.String()),
|
||||
alamat: t.Optional(t.String()),
|
||||
kontak: t.Optional(t.String()),
|
||||
imageId: t.Optional(t.String()),
|
||||
isActive: t.Optional(t.Boolean()),
|
||||
}),
|
||||
})
|
||||
.put("/:id", umkmUpdate, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
body: t.Object({
|
||||
nama: t.String(),
|
||||
pemilik: t.String(),
|
||||
kategoriId: t.String(),
|
||||
deskripsi: t.Optional(t.String()),
|
||||
alamat: t.Optional(t.String()),
|
||||
kontak: t.Optional(t.String()),
|
||||
imageId: t.Optional(t.String()),
|
||||
isActive: t.Boolean(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", umkmDelete, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
});
|
||||
|
||||
export default Umkm;
|
||||
@@ -0,0 +1,56 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function penjualanProdukCreate(context: Context) {
|
||||
const body = context.body as any;
|
||||
const tanggal = body.tanggal ? new Date(body.tanggal) : new Date();
|
||||
|
||||
// Format periode YYYY-MM
|
||||
const periode = `${tanggal.getFullYear()}-${String(tanggal.getMonth() + 1).padStart(2, '0')}`;
|
||||
|
||||
const totalNilai = body.jumlah * body.hargaSatuan;
|
||||
|
||||
try {
|
||||
// Gunakan transaction untuk update stok produk
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
// 1. Catat penjualan
|
||||
const penjualan = await tx.penjualanProduk.create({
|
||||
data: {
|
||||
produkId: body.produkId,
|
||||
jumlah: body.jumlah,
|
||||
hargaSatuan: body.hargaSatuan,
|
||||
totalNilai: totalNilai,
|
||||
tanggal: tanggal,
|
||||
periode: periode,
|
||||
isActive: body.isActive ?? true,
|
||||
},
|
||||
});
|
||||
|
||||
// 2. Update stok produk
|
||||
await tx.produkUmkm.update({
|
||||
where: { id: body.produkId },
|
||||
data: {
|
||||
stok: {
|
||||
decrement: body.jumlah
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return penjualan;
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mencatat penjualan produk",
|
||||
data: result,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di penjualanProdukCreate:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mencatat penjualan produk",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default penjualanProdukCreate;
|
||||
53
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/penjualan/del.ts
Normal file
53
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/penjualan/del.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function penjualanProdukDelete(context: Context) {
|
||||
const id = context.params.id;
|
||||
|
||||
try {
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
// 1. Ambil data penjualan
|
||||
const data = await tx.penjualanProduk.findUnique({
|
||||
where: { id },
|
||||
select: { jumlah: true, produkId: true }
|
||||
});
|
||||
|
||||
if (!data) throw new Error("Data penjualan tidak ditemukan");
|
||||
|
||||
// 2. Soft delete
|
||||
const deleted = await tx.penjualanProduk.update({
|
||||
where: { id },
|
||||
data: {
|
||||
deletedAt: new Date(),
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
|
||||
// 3. Kembalikan stok produk
|
||||
await tx.produkUmkm.update({
|
||||
where: { id: data.produkId },
|
||||
data: {
|
||||
stok: {
|
||||
increment: data.jumlah
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return deleted;
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil menghapus data penjualan dan mengembalikan stok",
|
||||
data: result,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di penjualanProdukDelete:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal menghapus data penjualan",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default penjualanProdukDelete;
|
||||
@@ -0,0 +1,58 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function penjualanProdukFindMany(context: Context) {
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const produkId = context.query.produkId as string | undefined;
|
||||
const periode = context.query.periode as string | undefined; // YYYY-MM
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where: any = { deletedAt: null };
|
||||
|
||||
if (produkId) {
|
||||
where.produkId = produkId;
|
||||
}
|
||||
|
||||
if (periode) {
|
||||
where.periode = periode;
|
||||
}
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.penjualanProduk.findMany({
|
||||
where,
|
||||
include: {
|
||||
produk: {
|
||||
include: {
|
||||
umkm: true
|
||||
}
|
||||
}
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { tanggal: 'desc' },
|
||||
}),
|
||||
prisma.penjualanProduk.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengambil data penjualan produk",
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di penjualanProdukFindMany:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data penjualan produk",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default penjualanProdukFindMany;
|
||||
@@ -0,0 +1,40 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function penjualanProdukFindUnique(context: Context) {
|
||||
const id = context.params.id;
|
||||
|
||||
try {
|
||||
const data = await prisma.penjualanProduk.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
produk: {
|
||||
include: {
|
||||
umkm: true
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data penjualan tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengambil detail penjualan produk",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di penjualanProdukFindUnique:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil detail penjualan produk",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default penjualanProdukFindUnique;
|
||||
@@ -0,0 +1,45 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import penjualanProdukCreate from "./create";
|
||||
import penjualanProdukDelete from "./del";
|
||||
import penjualanProdukFindMany from "./findMany";
|
||||
import penjualanProdukFindUnique from "./findUnique";
|
||||
import penjualanProdukUpdate from "./updt";
|
||||
|
||||
const PenjualanProduk = new Elysia({
|
||||
prefix: "/umkm/penjualan",
|
||||
tags: ["Ekonomi/UMKM Penjualan"],
|
||||
})
|
||||
.get("/find-many", penjualanProdukFindMany)
|
||||
.get("/:id", penjualanProdukFindUnique, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
})
|
||||
.post("/create", penjualanProdukCreate, {
|
||||
body: t.Object({
|
||||
produkId: t.String(),
|
||||
jumlah: t.Number(),
|
||||
hargaSatuan: t.Number(),
|
||||
tanggal: t.Optional(t.String()),
|
||||
isActive: t.Optional(t.Boolean()),
|
||||
}),
|
||||
})
|
||||
.put("/:id", penjualanProdukUpdate, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
body: t.Object({
|
||||
produkId: t.String(),
|
||||
jumlah: t.Number(),
|
||||
hargaSatuan: t.Number(),
|
||||
tanggal: t.Optional(t.String()),
|
||||
isActive: t.Boolean(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", penjualanProdukDelete, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
});
|
||||
|
||||
export default PenjualanProduk;
|
||||
83
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/penjualan/updt.ts
Normal file
83
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/penjualan/updt.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function penjualanProdukUpdate(context: Context) {
|
||||
const body = context.body as any;
|
||||
const id = context.params.id;
|
||||
const tanggal = body.tanggal ? new Date(body.tanggal) : new Date();
|
||||
const periode = `${tanggal.getFullYear()}-${String(tanggal.getMonth() + 1).padStart(2, '0')}`;
|
||||
const totalNilai = body.jumlah * body.hargaSatuan;
|
||||
|
||||
try {
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
// 1. Ambil data lama untuk hitung selisih stok
|
||||
const oldData = await tx.penjualanProduk.findUnique({
|
||||
where: { id },
|
||||
select: { jumlah: true, produkId: true }
|
||||
});
|
||||
|
||||
if (!oldData) throw new Error("Data penjualan tidak ditemukan");
|
||||
|
||||
// 2. Update penjualan
|
||||
const updated = await tx.penjualanProduk.update({
|
||||
where: { id },
|
||||
data: {
|
||||
produkId: body.produkId,
|
||||
jumlah: body.jumlah,
|
||||
hargaSatuan: body.hargaSatuan,
|
||||
totalNilai: totalNilai,
|
||||
tanggal: tanggal,
|
||||
periode: periode,
|
||||
isActive: body.isActive,
|
||||
},
|
||||
});
|
||||
|
||||
// 3. Update stok jika produk sama, sesuaikan selisih
|
||||
if (oldData.produkId === body.produkId) {
|
||||
const diff = body.jumlah - oldData.jumlah;
|
||||
await tx.produkUmkm.update({
|
||||
where: { id: body.produkId },
|
||||
data: {
|
||||
stok: {
|
||||
decrement: diff
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Jika produk berubah, kembalikan stok lama dan kurangi stok baru
|
||||
await tx.produkUmkm.update({
|
||||
where: { id: oldData.produkId },
|
||||
data: {
|
||||
stok: {
|
||||
increment: oldData.jumlah
|
||||
}
|
||||
}
|
||||
});
|
||||
await tx.produkUmkm.update({
|
||||
where: { id: body.produkId },
|
||||
data: {
|
||||
stok: {
|
||||
decrement: body.jumlah
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return updated;
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil memperbarui data penjualan",
|
||||
data: result,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di penjualanProdukUpdate:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal memperbarui data penjualan",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default penjualanProdukUpdate;
|
||||
34
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/create.ts
Normal file
34
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/create.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function produkUmkmCreate(context: Context) {
|
||||
const body = context.body as any;
|
||||
|
||||
try {
|
||||
const data = await prisma.produkUmkm.create({
|
||||
data: {
|
||||
nama: body.nama,
|
||||
harga: body.harga,
|
||||
stok: body.stok ?? 0,
|
||||
deskripsi: body.deskripsi,
|
||||
umkmId: body.umkmId,
|
||||
imageId: body.imageId,
|
||||
isActive: body.isActive ?? true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil membuat produk UMKM baru",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di produkUmkmCreate:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal membuat produk UMKM baru",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default produkUmkmCreate;
|
||||
31
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/del.ts
Normal file
31
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/del.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function produkUmkmDelete(context: Context) {
|
||||
const id = context.params.id;
|
||||
|
||||
try {
|
||||
// Soft delete
|
||||
const data = await prisma.produkUmkm.update({
|
||||
where: { id },
|
||||
data: {
|
||||
deletedAt: new Date(),
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil menghapus produk UMKM",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di produkUmkmDelete:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal menghapus produk UMKM",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default produkUmkmDelete;
|
||||
@@ -0,0 +1,64 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function produkUmkmFindMany(context: Context) {
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const umkmId = context.query.umkmId as string | undefined;
|
||||
const kategoriId = context.query.kategoriId as string | undefined;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where: any = { deletedAt: null };
|
||||
|
||||
if (umkmId) {
|
||||
where.umkmId = umkmId;
|
||||
}
|
||||
|
||||
if (kategoriId) {
|
||||
where.umkm = {
|
||||
kategoriId: kategoriId
|
||||
};
|
||||
}
|
||||
|
||||
if (search) {
|
||||
where.nama = { contains: search, mode: 'insensitive' };
|
||||
}
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.produkUmkm.findMany({
|
||||
where,
|
||||
include: {
|
||||
image: true,
|
||||
umkm: {
|
||||
include: { kategori: true }
|
||||
}
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.produkUmkm.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengambil data produk UMKM",
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di produkUmkmFindMany:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data produk UMKM",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default produkUmkmFindMany;
|
||||
@@ -0,0 +1,42 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function produkUmkmFindUnique(context: Context) {
|
||||
const id = context.params.id;
|
||||
|
||||
try {
|
||||
const data = await prisma.produkUmkm.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
image: true,
|
||||
umkm: true,
|
||||
penjualan: {
|
||||
where: { deletedAt: null },
|
||||
orderBy: { tanggal: 'desc' },
|
||||
take: 10
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Produk UMKM tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengambil detail produk UMKM",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di produkUmkmFindUnique:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil detail produk UMKM",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default produkUmkmFindUnique;
|
||||
49
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/index.ts
Normal file
49
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/index.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import produkUmkmCreate from "./create";
|
||||
import produkUmkmDelete from "./del";
|
||||
import produkUmkmFindMany from "./findMany";
|
||||
import produkUmkmFindUnique from "./findUnique";
|
||||
import produkUmkmUpdate from "./updt";
|
||||
|
||||
const ProdukUmkm = new Elysia({
|
||||
prefix: "/umkm/produk",
|
||||
tags: ["Ekonomi/UMKM Produk"],
|
||||
})
|
||||
.get("/find-many", produkUmkmFindMany)
|
||||
.get("/:id", produkUmkmFindUnique, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
})
|
||||
.post("/create", produkUmkmCreate, {
|
||||
body: t.Object({
|
||||
nama: t.String(),
|
||||
harga: t.Number(),
|
||||
umkmId: t.String(),
|
||||
stok: t.Optional(t.Number()),
|
||||
deskripsi: t.Optional(t.String()),
|
||||
imageId: t.Optional(t.String()),
|
||||
isActive: t.Optional(t.Boolean()),
|
||||
}),
|
||||
})
|
||||
.put("/:id", produkUmkmUpdate, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
body: t.Object({
|
||||
nama: t.String(),
|
||||
harga: t.Number(),
|
||||
umkmId: t.String(),
|
||||
stok: t.Number(),
|
||||
deskripsi: t.Optional(t.String()),
|
||||
imageId: t.Optional(t.String()),
|
||||
isActive: t.Boolean(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", produkUmkmDelete, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
});
|
||||
|
||||
export default ProdukUmkm;
|
||||
36
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/updt.ts
Normal file
36
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/updt.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function produkUmkmUpdate(context: Context) {
|
||||
const body = context.body as any;
|
||||
const id = context.params.id;
|
||||
|
||||
try {
|
||||
const data = await prisma.produkUmkm.update({
|
||||
where: { id },
|
||||
data: {
|
||||
nama: body.nama,
|
||||
harga: body.harga,
|
||||
stok: body.stok,
|
||||
deskripsi: body.deskripsi,
|
||||
umkmId: body.umkmId,
|
||||
imageId: body.imageId,
|
||||
isActive: body.isActive,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil memperbarui produk UMKM",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di produkUmkmUpdate:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal memperbarui produk UMKM",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default produkUmkmUpdate;
|
||||
37
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/updt.ts
Normal file
37
src/app/api/[[...slugs]]/_lib/ekonomi/umkm/updt.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function umkmUpdate(context: Context) {
|
||||
const body = context.body as any;
|
||||
const id = context.params.id;
|
||||
|
||||
try {
|
||||
const data = await prisma.umkm.update({
|
||||
where: { id },
|
||||
data: {
|
||||
nama: body.nama,
|
||||
pemilik: body.pemilik,
|
||||
deskripsi: body.deskripsi,
|
||||
alamat: body.alamat,
|
||||
kontak: body.kontak,
|
||||
imageId: body.imageId,
|
||||
kategoriId: body.kategoriId,
|
||||
isActive: body.isActive,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil memperbarui data UMKM",
|
||||
data,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di umkmUpdate:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal memperbarui data UMKM",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default umkmUpdate;
|
||||
Reference in New Issue
Block a user