Merge pull request 'nico/4-mar-26/realiasasi-apbdes' (#73) from nico/4-mar-26/realiasasi-apbdes into staggingweb
Reviewed-on: #73
This commit is contained in:
@@ -1,26 +1,33 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = Prisma.BeritaGetPayload<{
|
||||
select: {
|
||||
judul: true;
|
||||
deskripsi: true;
|
||||
content: true;
|
||||
kategoriBeritaId: true;
|
||||
imageId: true;
|
||||
};
|
||||
}>;
|
||||
type FormCreate = {
|
||||
judul: string;
|
||||
deskripsi: string;
|
||||
content: string;
|
||||
kategoriBeritaId: string;
|
||||
imageId: string; // Featured image
|
||||
imageIds?: string[]; // Multiple images for gallery
|
||||
linkVideo?: string; // YouTube link
|
||||
};
|
||||
|
||||
async function beritaCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
await prisma.berita.create({
|
||||
data: {
|
||||
data: {
|
||||
content: body.content,
|
||||
deskripsi: body.deskripsi,
|
||||
imageId: body.imageId,
|
||||
judul: body.judul,
|
||||
kategoriBeritaId: body.kategoriBeritaId,
|
||||
// Connect multiple images if provided
|
||||
linkVideo: body.linkVideo,
|
||||
images: body.imageIds && body.imageIds.length > 0
|
||||
? {
|
||||
connect: body.imageIds.map((id) => ({ id })),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export default async function handler(
|
||||
where: { id },
|
||||
include: {
|
||||
image: true,
|
||||
images: true,
|
||||
kategoriBerita: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -21,6 +21,8 @@ const Berita = new Elysia({ prefix: "/berita", tags: ["Desa/Berita"] })
|
||||
imageId: t.String(),
|
||||
content: t.String(),
|
||||
kategoriBeritaId: t.Union([t.String(), t.Null()]),
|
||||
imageIds: t.Array(t.String()),
|
||||
linkVideo: t.Optional(t.String()),
|
||||
}),
|
||||
})
|
||||
.get("/find-first", beritaFindFirst)
|
||||
@@ -39,6 +41,8 @@ const Berita = new Elysia({ prefix: "/berita", tags: ["Desa/Berita"] })
|
||||
imageId: t.String(),
|
||||
content: t.String(),
|
||||
kategoriBeritaId: t.Union([t.String(), t.Null()]),
|
||||
imageIds: t.Array(t.String()),
|
||||
linkVideo: t.Optional(t.String()),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -4,52 +4,48 @@ import { Prisma } from "@prisma/client";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
type FormUpdate = Prisma.BeritaGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
judul: true;
|
||||
deskripsi: true;
|
||||
content: true;
|
||||
kategoriBeritaId: true;
|
||||
imageId: true;
|
||||
};
|
||||
}>;
|
||||
type FormUpdate = {
|
||||
id: string;
|
||||
judul: string;
|
||||
deskripsi: string;
|
||||
content: string;
|
||||
kategoriBeritaId: string;
|
||||
imageId: string; // Featured image
|
||||
imageIds?: string[]; // Multiple images for gallery
|
||||
linkVideo?: string; // YouTube link
|
||||
};
|
||||
|
||||
async function beritaUpdate(context: Context) {
|
||||
try {
|
||||
const id = context.params?.id as string; // ambil dari URL
|
||||
const id = context.params?.id as string;
|
||||
const body = (await context.body) as Omit<FormUpdate, "id">;
|
||||
|
||||
const {
|
||||
judul,
|
||||
deskripsi,
|
||||
content,
|
||||
kategoriBeritaId,
|
||||
imageId,
|
||||
} = body;
|
||||
|
||||
|
||||
const { judul, deskripsi, content, kategoriBeritaId, imageId, imageIds, linkVideo } = body;
|
||||
|
||||
if (!id) {
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, message: "ID tidak boleh kosong" }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||
{ status: 400, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const existing = await prisma.berita.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
image: true,
|
||||
images: true, // Include gallery images
|
||||
kategoriBerita: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
if (!existing) {
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, message: "Berita tidak ditemukan" }),
|
||||
{ status: 404, headers: { 'Content-Type': 'application/json' } }
|
||||
{ status: 404, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Delete old featured image if changed
|
||||
if (existing.imageId && existing.imageId !== imageId) {
|
||||
const oldImage = existing.image;
|
||||
if (oldImage) {
|
||||
@@ -64,35 +60,60 @@ async function beritaUpdate(context: Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Build update data
|
||||
const updateData: Prisma.BeritaUpdateInput = {
|
||||
judul,
|
||||
deskripsi,
|
||||
content,
|
||||
kategoriBerita: kategoriBeritaId ? { connect: { id: kategoriBeritaId } } : { disconnect: true },
|
||||
image: imageId ? { connect: { id: imageId } } : { disconnect: true },
|
||||
linkVideo,
|
||||
};
|
||||
|
||||
// Handle multiple images update
|
||||
if (imageIds !== undefined) {
|
||||
// Disconnect all existing images first
|
||||
updateData.images = {
|
||||
set: [],
|
||||
};
|
||||
|
||||
// Connect new images if provided
|
||||
if (imageIds.length > 0) {
|
||||
updateData.images = {
|
||||
...updateData.images,
|
||||
connect: imageIds.map((id) => ({ id })),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const updated = await prisma.berita.update({
|
||||
where: { id },
|
||||
data: {
|
||||
judul,
|
||||
deskripsi,
|
||||
content,
|
||||
kategoriBeritaId: kategoriBeritaId || null,
|
||||
imageId,
|
||||
data: updateData,
|
||||
include: {
|
||||
image: true,
|
||||
images: true,
|
||||
kategoriBerita: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: "Berita berhasil diupdate",
|
||||
data: updated,
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error updating berita:", error);
|
||||
console.error("Error updating berita:", error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat mengupdate berita",
|
||||
}),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||
{ status: 500, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
}
|
||||
}
|
||||
export default beritaUpdate;
|
||||
export default beritaUpdate;
|
||||
|
||||
@@ -2,7 +2,7 @@ import Elysia from "elysia";
|
||||
import Berita from "./berita";
|
||||
import Pengumuman from "./pengumuman";
|
||||
import ProfileDesa from "./profile/profile_desa";
|
||||
import PotensiDesa from "./potensi";
|
||||
import PotensiDesa from "./potensi";
|
||||
import GalleryFoto from "./gallery/foto";
|
||||
import GalleryVideo from "./gallery/video";
|
||||
import LayananDesa from "./layanan";
|
||||
@@ -12,6 +12,7 @@ import KategoriBerita from "./berita/kategori-berita";
|
||||
import KategoriPengumuman from "./pengumuman/kategori-pengumuman";
|
||||
import MantanPerbekel from "./profile/profile-mantan-perbekel";
|
||||
import AjukanPermohonan from "./layanan/ajukan_permohonan";
|
||||
import Musik from "./musik";
|
||||
|
||||
|
||||
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
||||
@@ -28,6 +29,7 @@ const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
||||
.use(KategoriBerita)
|
||||
.use(KategoriPengumuman)
|
||||
.use(AjukanPermohonan)
|
||||
|
||||
.use(Musik)
|
||||
|
||||
|
||||
export default Desa;
|
||||
|
||||
37
src/app/api/[[...slugs]]/_lib/desa/musik/create.ts
Normal file
37
src/app/api/[[...slugs]]/_lib/desa/musik/create.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
type FormCreate = {
|
||||
judul: string;
|
||||
artis: string;
|
||||
deskripsi?: string;
|
||||
durasi: string;
|
||||
audioFileId: string;
|
||||
coverImageId: string;
|
||||
genre?: string;
|
||||
tahunRilis?: number | null;
|
||||
};
|
||||
|
||||
async function musikCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
await prisma.musikDesa.create({
|
||||
data: {
|
||||
judul: body.judul,
|
||||
artis: body.artis,
|
||||
deskripsi: body.deskripsi,
|
||||
durasi: body.durasi,
|
||||
audioFileId: body.audioFileId,
|
||||
coverImageId: body.coverImageId,
|
||||
genre: body.genre,
|
||||
tahunRilis: body.tahunRilis,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Sukses menambahkan musik",
|
||||
};
|
||||
}
|
||||
|
||||
export default musikCreate;
|
||||
54
src/app/api/[[...slugs]]/_lib/desa/musik/del.ts
Normal file
54
src/app/api/[[...slugs]]/_lib/desa/musik/del.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
import path from "path";
|
||||
|
||||
const musikDelete = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
|
||||
const musik = await prisma.musikDesa.findUnique({
|
||||
where: { id },
|
||||
include: { audioFile: true, coverImage: true },
|
||||
});
|
||||
|
||||
if (!musik) return { status: 404, body: "Musik tidak ditemukan" };
|
||||
|
||||
// 1. HAPUS MUSIK DULU
|
||||
await prisma.musikDesa.delete({ where: { id } });
|
||||
|
||||
// 2. HAPUS FILE AUDIO (jika ada)
|
||||
if (musik.audioFile) {
|
||||
try {
|
||||
const fs = await import("fs/promises");
|
||||
const filePath = path.join(musik.audioFile.path, musik.audioFile.name);
|
||||
await fs.unlink(filePath);
|
||||
|
||||
await prisma.fileStorage.delete({
|
||||
where: { id: musik.audioFile.id },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error deleting audio file:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. HAPUS FILE COVER (jika ada)
|
||||
if (musik.coverImage) {
|
||||
try {
|
||||
const fs = await import("fs/promises");
|
||||
const filePath = path.join(musik.coverImage.path, musik.coverImage.name);
|
||||
await fs.unlink(filePath);
|
||||
|
||||
await prisma.fileStorage.delete({
|
||||
where: { id: musik.coverImage.id },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error deleting cover image:", error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Musik dan file terkait berhasil dihapus",
|
||||
};
|
||||
};
|
||||
|
||||
export default musikDelete;
|
||||
66
src/app/api/[[...slugs]]/_lib/desa/musik/find-by-id.ts
Normal file
66
src/app/api/[[...slugs]]/_lib/desa/musik/find-by-id.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function findMusikById(request: Request) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const id = url.pathname.split("/").pop();
|
||||
|
||||
if (!id) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "ID tidak valid",
|
||||
}),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const data = await prisma.musikDesa.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
audioFile: true,
|
||||
coverImage: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "Musik tidak ditemukan",
|
||||
}),
|
||||
{
|
||||
status: 404,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: "Success fetch musik by ID",
|
||||
data,
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Error fetching musik by ID:", e);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "Gagal mengambil musik: " + (e instanceof Error ? e.message : 'Unknown error'),
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
69
src/app/api/[[...slugs]]/_lib/desa/musik/find-many.ts
Normal file
69
src/app/api/[[...slugs]]/_lib/desa/musik/find-many.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/desa/musik/find-many.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function musikFindMany(context: Context) {
|
||||
// Ambil parameter dari query
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const genre = (context.query.genre as string) || '';
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Filter berdasarkan genre (jika ada)
|
||||
if (genre) {
|
||||
where.genre = {
|
||||
equals: genre,
|
||||
mode: 'insensitive'
|
||||
};
|
||||
}
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ judul: { contains: search, mode: 'insensitive' } },
|
||||
{ artis: { contains: search, mode: 'insensitive' } },
|
||||
{ deskripsi: { contains: search, mode: 'insensitive' } },
|
||||
{ genre: { contains: search, mode: 'insensitive' } }
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// Ambil data dan total count secara paralel
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.musikDesa.findMany({
|
||||
where,
|
||||
include: {
|
||||
audioFile: true,
|
||||
coverImage: true,
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.musikDesa.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil ambil data musik dengan pagination",
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di findMany paginated:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data musik",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default musikFindMany;
|
||||
47
src/app/api/[[...slugs]]/_lib/desa/musik/index.ts
Normal file
47
src/app/api/[[...slugs]]/_lib/desa/musik/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import musikFindMany from "./find-many";
|
||||
import musikCreate from "./create";
|
||||
import musikDelete from "./del";
|
||||
import musikUpdate from "./updt";
|
||||
import findMusikById from "./find-by-id";
|
||||
|
||||
const Musik = new Elysia({ prefix: "/musik", tags: ["Desa/Musik"] })
|
||||
.get("/find-many", musikFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await findMusikById(new Request(context.request));
|
||||
return response;
|
||||
})
|
||||
.post("/create", musikCreate, {
|
||||
body: t.Object({
|
||||
judul: t.String(),
|
||||
artis: t.String(),
|
||||
deskripsi: t.Optional(t.String()),
|
||||
durasi: t.String(),
|
||||
audioFileId: t.String(),
|
||||
coverImageId: t.String(),
|
||||
genre: t.Optional(t.String()),
|
||||
tahunRilis: t.Optional(t.Number()),
|
||||
}),
|
||||
})
|
||||
.delete("/delete/:id", musikDelete)
|
||||
.put(
|
||||
"/:id",
|
||||
async (context) => {
|
||||
const response = await musikUpdate(context);
|
||||
return response;
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
judul: t.String(),
|
||||
artis: t.String(),
|
||||
deskripsi: t.Optional(t.String()),
|
||||
durasi: t.String(),
|
||||
audioFileId: t.String(),
|
||||
coverImageId: t.String(),
|
||||
genre: t.Optional(t.String()),
|
||||
tahunRilis: t.Optional(t.Number()),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
export default Musik;
|
||||
65
src/app/api/[[...slugs]]/_lib/desa/musik/updt.ts
Normal file
65
src/app/api/[[...slugs]]/_lib/desa/musik/updt.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
type FormUpdate = {
|
||||
judul: string;
|
||||
artis: string;
|
||||
deskripsi?: string;
|
||||
durasi: string;
|
||||
audioFileId: string;
|
||||
coverImageId: string;
|
||||
genre?: string;
|
||||
tahunRilis?: number | null;
|
||||
};
|
||||
|
||||
async function musikUpdate(context: Context) {
|
||||
const { id } = context.params as { id: string };
|
||||
const body = context.body as FormUpdate;
|
||||
|
||||
try {
|
||||
const existing = await prisma.musikDesa.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return {
|
||||
status: 404,
|
||||
body: {
|
||||
success: false,
|
||||
message: "Musik tidak ditemukan",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const updated = await prisma.musikDesa.update({
|
||||
where: { id },
|
||||
data: {
|
||||
judul: body.judul,
|
||||
artis: body.artis,
|
||||
deskripsi: body.deskripsi,
|
||||
durasi: body.durasi,
|
||||
audioFileId: body.audioFileId,
|
||||
coverImageId: body.coverImageId,
|
||||
genre: body.genre,
|
||||
tahunRilis: body.tahunRilis,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Musik berhasil diupdate",
|
||||
data: updated,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error updating musik:", error);
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat mengupdate musik",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default musikUpdate;
|
||||
@@ -22,9 +22,10 @@ const fileStorageCreate = async (context: Context) => {
|
||||
if (!UPLOAD_DIR) return { status: 500, body: "UPLOAD_DIR is not defined" };
|
||||
|
||||
const isImage = file.type.startsWith("image/");
|
||||
const category = isImage ? "image" : "document";
|
||||
const isAudio = file.type.startsWith("audio/");
|
||||
const category = isImage ? "image" : isAudio ? "audio" : "document";
|
||||
|
||||
const pathName = category === "image" ? "images" : "documents";
|
||||
const pathName = category === "image" ? "images" : category === "audio" ? "audio" : "documents";
|
||||
const rootPath = path.join(UPLOAD_DIR, pathName);
|
||||
await fs.mkdir(rootPath, { recursive: true });
|
||||
|
||||
@@ -54,6 +55,11 @@ const fileStorageCreate = async (context: Context) => {
|
||||
// Simpan metadata untuk versi desktop sebagai default
|
||||
finalName = desktopName;
|
||||
finalMimeType = "image/webp";
|
||||
} else if (isAudio) {
|
||||
// Simpan file audio tanpa kompresi
|
||||
const ext = file.name.split(".").pop() || "mp3";
|
||||
finalName = `${finalName}.${ext}`;
|
||||
await fs.writeFile(path.join(rootPath, finalName), buffer);
|
||||
} else {
|
||||
// Jika file adalah PDF, simpan tanpa kompresi
|
||||
if (file.type === "application/pdf") {
|
||||
|
||||
@@ -8,15 +8,15 @@ type APBDesItemInput = {
|
||||
kode: string;
|
||||
uraian: string;
|
||||
anggaran: number;
|
||||
realisasi: number;
|
||||
selisih: number;
|
||||
persentase: number;
|
||||
level: number;
|
||||
tipe?: string | null;
|
||||
};
|
||||
|
||||
type FormCreate = {
|
||||
tahun: number;
|
||||
name?: string;
|
||||
deskripsi?: string;
|
||||
jumlah?: string;
|
||||
imageId: string;
|
||||
fileId: string;
|
||||
items: APBDesItemInput[];
|
||||
@@ -24,8 +24,7 @@ type FormCreate = {
|
||||
|
||||
export default async function apbdesCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
// Log the incoming request for debugging
|
||||
|
||||
console.log('Incoming request body:', JSON.stringify(body, null, 2));
|
||||
|
||||
try {
|
||||
@@ -43,33 +42,39 @@ export default async function apbdesCreate(context: Context) {
|
||||
throw new Error('At least one item is required');
|
||||
}
|
||||
|
||||
// 1. Buat APBDes + items (tanpa parentId dulu)
|
||||
// 1. Buat APBDes + items dengan auto-calculate fields
|
||||
const created = await prisma.$transaction(async (prisma) => {
|
||||
const apbdes = await prisma.aPBDes.create({
|
||||
data: {
|
||||
tahun: body.tahun,
|
||||
name: `APBDes Tahun ${body.tahun}`,
|
||||
name: body.name || `APBDes Tahun ${body.tahun}`,
|
||||
deskripsi: body.deskripsi,
|
||||
jumlah: body.jumlah,
|
||||
imageId: body.imageId,
|
||||
fileId: body.fileId,
|
||||
},
|
||||
});
|
||||
|
||||
// Create items in a batch
|
||||
// Create items dengan auto-calculate totalRealisasi=0, selisih, persentase
|
||||
const items = await Promise.all(
|
||||
body.items.map(item => {
|
||||
// Create a new object with only the fields that exist in the APBDesItem model
|
||||
body.items.map(async item => {
|
||||
const anggaran = item.anggaran;
|
||||
const totalRealisasi = 0; // Belum ada realisasi saat create
|
||||
const selisih = anggaran - totalRealisasi; // Sisa anggaran (positif = belum digunakan)
|
||||
const persentase = anggaran > 0 ? (totalRealisasi / anggaran) * 100 : 0;
|
||||
|
||||
const itemData = {
|
||||
kode: item.kode,
|
||||
uraian: item.uraian,
|
||||
anggaran: item.anggaran,
|
||||
realisasi: item.realisasi,
|
||||
selisih: item.selisih,
|
||||
persentase: item.persentase,
|
||||
anggaran: anggaran,
|
||||
level: item.level,
|
||||
tipe: item.tipe, // ✅ sertakan, biar null
|
||||
tipe: item.tipe || null,
|
||||
totalRealisasi,
|
||||
selisih,
|
||||
persentase,
|
||||
apbdesId: apbdes.id,
|
||||
};
|
||||
|
||||
|
||||
return prisma.aPBDesItem.create({
|
||||
data: itemData,
|
||||
select: { id: true, kode: true },
|
||||
@@ -84,20 +89,27 @@ export default async function apbdesCreate(context: Context) {
|
||||
// 2. Isi parentId berdasarkan kode
|
||||
await assignParentIdsToApbdesItems(created.items);
|
||||
|
||||
// 3. Ambil ulang data lengkap untuk response
|
||||
// 3. Ambil ulang data lengkap untuk response (include realisasiItems)
|
||||
const result = await prisma.aPBDes.findUnique({
|
||||
where: { id: created.id },
|
||||
include: {
|
||||
image: true,
|
||||
file: true,
|
||||
items: {
|
||||
where: { isActive: true },
|
||||
orderBy: { kode: 'asc' },
|
||||
include: {
|
||||
realisasiItems: {
|
||||
where: { isActive: true },
|
||||
orderBy: { tanggal: 'asc' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log('APBDes created successfully:', JSON.stringify(result, null, 2));
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil membuat APBDes",
|
||||
@@ -105,7 +117,6 @@ export default async function apbdesCreate(context: Context) {
|
||||
};
|
||||
} catch (innerError) {
|
||||
console.error('Error in post-creation steps:', innerError);
|
||||
// Even if post-creation steps fail, we still return success since the main record was created
|
||||
return {
|
||||
success: true,
|
||||
message: "APBDes berhasil dibuat, tetapi ada masalah dengan pemrosesan tambahan",
|
||||
@@ -115,13 +126,12 @@ export default async function apbdesCreate(context: Context) {
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Error creating APBDes:", error);
|
||||
|
||||
// Log the full error for debugging
|
||||
|
||||
if (error.code) console.error('Prisma error code:', error.code);
|
||||
if (error.meta) console.error('Prisma error meta:', error.meta);
|
||||
|
||||
|
||||
const errorMessage = error.message || 'Unknown error';
|
||||
|
||||
|
||||
context.set.status = 500;
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -21,7 +21,7 @@ export default async function apbdesFindMany(context: Context) {
|
||||
|
||||
try {
|
||||
const where: any = { isActive: true };
|
||||
|
||||
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ name: { contains: search, mode: "insensitive" } },
|
||||
@@ -47,7 +47,16 @@ export default async function apbdesFindMany(context: Context) {
|
||||
include: {
|
||||
image: true,
|
||||
file: true,
|
||||
items: true,
|
||||
items: {
|
||||
where: { isActive: true },
|
||||
orderBy: { kode: "asc" },
|
||||
include: {
|
||||
realisasiItems: {
|
||||
where: { isActive: true },
|
||||
orderBy: { tanggal: 'asc' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.aPBDes.count({ where }),
|
||||
|
||||
@@ -2,15 +2,9 @@ import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function apbdesFindUnique(context: Context) {
|
||||
// ✅ Parse URL secara manual
|
||||
const url = new URL(context.request.url);
|
||||
const pathSegments = url.pathname.split('/').filter(Boolean);
|
||||
|
||||
console.log("🔍 DEBUG INFO:");
|
||||
console.log("- Full URL:", context.request.url);
|
||||
console.log("- Pathname:", url.pathname);
|
||||
console.log("- Path segments:", pathSegments);
|
||||
|
||||
|
||||
// Expected: ['api', 'landingpage', 'apbdes', 'ID']
|
||||
if (pathSegments.length < 4) {
|
||||
context.set.status = 400;
|
||||
@@ -20,9 +14,9 @@ export default async function apbdesFindUnique(context: Context) {
|
||||
debug: { pathSegments }
|
||||
};
|
||||
}
|
||||
|
||||
if (pathSegments[0] !== 'api' ||
|
||||
pathSegments[1] !== 'landingpage' ||
|
||||
|
||||
if (pathSegments[0] !== 'api' ||
|
||||
pathSegments[1] !== 'landingpage' ||
|
||||
pathSegments[2] !== 'apbdes') {
|
||||
context.set.status = 400;
|
||||
return {
|
||||
@@ -31,9 +25,9 @@ export default async function apbdesFindUnique(context: Context) {
|
||||
debug: { pathSegments }
|
||||
};
|
||||
}
|
||||
|
||||
const id = pathSegments[3]; // ✅ ID ada di index ke-3
|
||||
|
||||
|
||||
const id = pathSegments[3];
|
||||
|
||||
if (!id || id.trim() === '') {
|
||||
context.set.status = 400;
|
||||
return {
|
||||
@@ -48,11 +42,17 @@ export default async function apbdesFindUnique(context: Context) {
|
||||
include: {
|
||||
items: {
|
||||
where: { isActive: true },
|
||||
orderBy: { kode: 'asc' }
|
||||
orderBy: { kode: 'asc' },
|
||||
include: {
|
||||
realisasiItems: {
|
||||
where: { isActive: true },
|
||||
orderBy: { tanggal: 'asc' },
|
||||
},
|
||||
},
|
||||
},
|
||||
image: true,
|
||||
file: true
|
||||
}
|
||||
file: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result || !result.isActive) {
|
||||
|
||||
@@ -5,17 +5,17 @@ import apbdesDelete from "./del";
|
||||
import apbdesFindMany from "./findMany";
|
||||
import apbdesFindUnique from "./findUnique";
|
||||
import apbdesUpdate from "./updt";
|
||||
import realisasiCreate from "./realisasi/create";
|
||||
import realisasiUpdate from "./realisasi/update";
|
||||
import realisasiDelete from "./realisasi/delete";
|
||||
|
||||
// Definisikan skema untuk item APBDes
|
||||
// Definisikan skema untuk item APBDes (tanpa realisasi field)
|
||||
const ApbdesItemSchema = t.Object({
|
||||
kode: t.String(),
|
||||
uraian: t.String(),
|
||||
anggaran: t.Number(),
|
||||
realisasi: t.Number(),
|
||||
selisih: t.Number(),
|
||||
persentase: t.Number(),
|
||||
level: t.Number(),
|
||||
tipe: t.Optional(t.Union([t.String(), t.Null()])) // misal: "pendapatan" atau "belanja"
|
||||
tipe: t.Optional(t.Union([t.String(), t.Null()])), // "pendapatan" | "belanja" | "pembiayaan" | null
|
||||
});
|
||||
|
||||
const APBDes = new Elysia({
|
||||
@@ -26,33 +26,72 @@ const APBDes = new Elysia({
|
||||
// ✅ Find all (dengan query opsional: page, limit, tahun)
|
||||
.get("/findMany", apbdesFindMany)
|
||||
|
||||
// ✅ Find by ID
|
||||
// ✅ Find by ID (include realisasiItems)
|
||||
.get("/:id", apbdesFindUnique)
|
||||
|
||||
// ✅ Create
|
||||
// ✅ Create APBDes dengan items (tanpa realisasi)
|
||||
.post("/create", apbdesCreate, {
|
||||
body: t.Object({
|
||||
tahun: t.Number(),
|
||||
name: t.Optional(t.String()),
|
||||
deskripsi: t.Optional(t.String()),
|
||||
jumlah: t.Optional(t.String()),
|
||||
imageId: t.String(),
|
||||
fileId: t.String(),
|
||||
items: t.Array(ApbdesItemSchema),
|
||||
}),
|
||||
})
|
||||
|
||||
// ✅ Update
|
||||
// ✅ Update APBDes dengan items (tanpa realisasi)
|
||||
.put("/:id", apbdesUpdate, {
|
||||
params: t.Object({ id: t.String() }),
|
||||
body: t.Object({
|
||||
tahun: t.Number(),
|
||||
name: t.Optional(t.String()),
|
||||
deskripsi: t.Optional(t.String()),
|
||||
jumlah: t.Optional(t.String()),
|
||||
imageId: t.String(),
|
||||
fileId: t.String(),
|
||||
items: t.Array(ApbdesItemSchema),
|
||||
}),
|
||||
})
|
||||
|
||||
// ✅ Delete
|
||||
// ✅ Delete APBDes
|
||||
.delete("/del/:id", apbdesDelete, {
|
||||
params: t.Object({ id: t.String() }),
|
||||
})
|
||||
|
||||
// =========================================
|
||||
// REALISASI ENDPOINTS
|
||||
// =========================================
|
||||
|
||||
// ✅ Create realisasi untuk item tertentu
|
||||
.post("/:itemId/realisasi", realisasiCreate, {
|
||||
params: t.Object({ itemId: t.String() }),
|
||||
body: t.Object({
|
||||
kode: t.String(),
|
||||
jumlah: t.Number(),
|
||||
tanggal: t.String(),
|
||||
keterangan: t.Optional(t.String()),
|
||||
buktiFileId: t.Optional(t.String()),
|
||||
}),
|
||||
})
|
||||
|
||||
// ✅ Update realisasi
|
||||
.put("/realisasi/:realisasiId", realisasiUpdate, {
|
||||
params: t.Object({ realisasiId: t.String() }),
|
||||
body: t.Object({
|
||||
kode: t.Optional(t.String()),
|
||||
jumlah: t.Optional(t.Number()),
|
||||
tanggal: t.Optional(t.String()),
|
||||
keterangan: t.Optional(t.String()),
|
||||
buktiFileId: t.Optional(t.String()),
|
||||
}),
|
||||
})
|
||||
|
||||
// ✅ Delete realisasi
|
||||
.delete("/realisasi/:realisasiId", realisasiDelete, {
|
||||
params: t.Object({ realisasiId: t.String() }),
|
||||
});
|
||||
|
||||
export default APBDes;
|
||||
@@ -0,0 +1,84 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type RealisasiCreateBody = {
|
||||
kode: string;
|
||||
jumlah: number;
|
||||
tanggal: string; // ISO format
|
||||
keterangan?: string;
|
||||
buktiFileId?: string;
|
||||
};
|
||||
|
||||
export default async function realisasiCreate(context: Context) {
|
||||
const { itemId } = context.params as { itemId: string };
|
||||
const body = context.body as RealisasiCreateBody;
|
||||
|
||||
console.log('Creating realisasi:', JSON.stringify(body, null, 2));
|
||||
|
||||
try {
|
||||
// 1. Pastikan APBDesItem ada
|
||||
const item = await prisma.aPBDesItem.findUnique({
|
||||
where: { id: itemId },
|
||||
});
|
||||
|
||||
if (!item) {
|
||||
context.set.status = 404;
|
||||
return {
|
||||
success: false,
|
||||
message: "Item APBDes tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Create realisasi item
|
||||
const realisasi = await prisma.realisasiItem.create({
|
||||
data: {
|
||||
apbdesItemId: itemId,
|
||||
kode: body.kode,
|
||||
jumlah: body.jumlah,
|
||||
tanggal: new Date(body.tanggal),
|
||||
keterangan: body.keterangan,
|
||||
buktiFileId: body.buktiFileId,
|
||||
},
|
||||
});
|
||||
|
||||
// 3. Update totalRealisasi, selisih, persentase di APBDesItem
|
||||
const allRealisasi = await prisma.realisasiItem.findMany({
|
||||
where: { apbdesItemId: itemId, isActive: true },
|
||||
select: { jumlah: true },
|
||||
});
|
||||
|
||||
const totalRealisasi = allRealisasi.reduce((sum, r) => sum + r.jumlah, 0);
|
||||
const selisih = item.anggaran - totalRealisasi; // Sisa anggaran (positif = belum digunakan)
|
||||
const persentase = item.anggaran > 0 ? (totalRealisasi / item.anggaran) * 100 : 0;
|
||||
|
||||
await prisma.aPBDesItem.update({
|
||||
where: { id: itemId },
|
||||
data: {
|
||||
totalRealisasi,
|
||||
selisih,
|
||||
persentase,
|
||||
},
|
||||
});
|
||||
|
||||
// 4. Return response
|
||||
return {
|
||||
success: true,
|
||||
message: "Realisasi berhasil ditambahkan",
|
||||
data: realisasi,
|
||||
meta: {
|
||||
totalRealisasi,
|
||||
selisih,
|
||||
persentase,
|
||||
},
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error("Error creating realisasi:", error);
|
||||
context.set.status = 500;
|
||||
return {
|
||||
success: false,
|
||||
message: `Gagal menambahkan realisasi: ${error.message}`,
|
||||
error: process.env.NODE_ENV === 'development' ? error : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function realisasiDelete(context: Context) {
|
||||
const { realisasiId } = context.params as { realisasiId: string };
|
||||
|
||||
console.log('Deleting realisasi:', realisasiId);
|
||||
|
||||
try {
|
||||
// 1. Pastikan realisasi ada
|
||||
const existing = await prisma.realisasiItem.findUnique({
|
||||
where: { id: realisasiId },
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
context.set.status = 404;
|
||||
return {
|
||||
success: false,
|
||||
message: "Realisasi tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
const apbdesItemId = existing.apbdesItemId;
|
||||
|
||||
// 2. Soft delete realisasi (set isActive = false)
|
||||
await prisma.realisasiItem.update({
|
||||
where: { id: realisasiId },
|
||||
data: {
|
||||
isActive: false,
|
||||
deletedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
// 3. Recalculate totalRealisasi, selisih, persentase di APBDesItem
|
||||
const allRealisasi = await prisma.realisasiItem.findMany({
|
||||
where: { apbdesItemId, isActive: true },
|
||||
select: { jumlah: true },
|
||||
});
|
||||
|
||||
const item = await prisma.aPBDesItem.findUnique({
|
||||
where: { id: apbdesItemId },
|
||||
});
|
||||
|
||||
if (item) {
|
||||
const totalRealisasi = allRealisasi.reduce((sum, r) => sum + r.jumlah, 0);
|
||||
const selisih = item.anggaran - totalRealisasi; // Sisa anggaran (positif = belum digunakan)
|
||||
const persentase = item.anggaran > 0 ? (totalRealisasi / item.anggaran) * 100 : 0;
|
||||
|
||||
await prisma.aPBDesItem.update({
|
||||
where: { id: apbdesItemId },
|
||||
data: {
|
||||
totalRealisasi,
|
||||
selisih,
|
||||
persentase,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Realisasi berhasil dihapus",
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error("Error deleting realisasi:", error);
|
||||
context.set.status = 500;
|
||||
return {
|
||||
success: false,
|
||||
message: `Gagal menghapus realisasi: ${error.message}`,
|
||||
error: process.env.NODE_ENV === 'development' ? error : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type RealisasiUpdateBody = {
|
||||
kode?: string;
|
||||
jumlah?: number;
|
||||
tanggal?: string;
|
||||
keterangan?: string;
|
||||
buktiFileId?: string;
|
||||
};
|
||||
|
||||
export default async function realisasiUpdate(context: Context) {
|
||||
const { realisasiId } = context.params as { realisasiId: string };
|
||||
const body = context.body as RealisasiUpdateBody;
|
||||
|
||||
console.log('Updating realisasi:', JSON.stringify(body, null, 2));
|
||||
|
||||
try {
|
||||
// 1. Pastikan realisasi ada
|
||||
const existing = await prisma.realisasiItem.findUnique({
|
||||
where: { id: realisasiId },
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
context.set.status = 404;
|
||||
return {
|
||||
success: false,
|
||||
message: "Realisasi tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Update realisasi
|
||||
const updated = await prisma.realisasiItem.update({
|
||||
where: { id: realisasiId },
|
||||
data: {
|
||||
...(body.kode !== undefined && { kode: body.kode }),
|
||||
...(body.jumlah !== undefined && { jumlah: body.jumlah }),
|
||||
...(body.tanggal !== undefined && { tanggal: new Date(body.tanggal) }),
|
||||
...(body.keterangan !== undefined && { keterangan: body.keterangan }),
|
||||
...(body.buktiFileId !== undefined && { buktiFileId: body.buktiFileId }),
|
||||
},
|
||||
});
|
||||
|
||||
// 3. Recalculate totalRealisasi, selisih, persentase di APBDesItem
|
||||
const allRealisasi = await prisma.realisasiItem.findMany({
|
||||
where: { apbdesItemId: existing.apbdesItemId, isActive: true },
|
||||
select: { jumlah: true },
|
||||
});
|
||||
|
||||
const item = await prisma.aPBDesItem.findUnique({
|
||||
where: { id: existing.apbdesItemId },
|
||||
});
|
||||
|
||||
if (item) {
|
||||
const totalRealisasi = allRealisasi.reduce((sum, r) => sum + r.jumlah, 0);
|
||||
const selisih = item.anggaran - totalRealisasi; // Sisa anggaran (positif = belum digunakan)
|
||||
const persentase = item.anggaran > 0 ? (totalRealisasi / item.anggaran) * 100 : 0;
|
||||
|
||||
await prisma.aPBDesItem.update({
|
||||
where: { id: existing.apbdesItemId },
|
||||
data: {
|
||||
totalRealisasi,
|
||||
selisih,
|
||||
persentase,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Realisasi berhasil diperbarui",
|
||||
data: updated,
|
||||
meta: {
|
||||
totalRealisasi: allRealisasi.reduce((sum, r) => sum + r.jumlah, 0),
|
||||
},
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error("Error updating realisasi:", error);
|
||||
context.set.status = 500;
|
||||
return {
|
||||
success: false,
|
||||
message: `Gagal memperbarui realisasi: ${error.message}`,
|
||||
error: process.env.NODE_ENV === 'development' ? error : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,15 @@ type APBDesItemInput = {
|
||||
kode: string;
|
||||
uraian: string;
|
||||
anggaran: number;
|
||||
realisasi: number;
|
||||
selisih: number;
|
||||
persentase: number;
|
||||
level: number;
|
||||
tipe?: string | null;
|
||||
};
|
||||
|
||||
type FormUpdateBody = {
|
||||
tahun: number;
|
||||
name?: string;
|
||||
deskripsi?: string;
|
||||
jumlah?: string;
|
||||
imageId: string;
|
||||
fileId: string;
|
||||
items: APBDesItemInput[];
|
||||
@@ -38,25 +38,32 @@ export default async function apbdesUpdate(context: Context) {
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Hapus semua item lama
|
||||
// 2. Hapus semua item lama (cascade akan menghapus realisasiItems juga)
|
||||
await prisma.aPBDesItem.deleteMany({
|
||||
where: { apbdesId: id },
|
||||
});
|
||||
|
||||
// 3. Buat item baru tanpa parentId terlebih dahulu
|
||||
// 3. Buat item baru dengan auto-calculate fields
|
||||
await prisma.aPBDesItem.createMany({
|
||||
data: body.items.map((item) => ({
|
||||
apbdesId: id,
|
||||
kode: item.kode,
|
||||
uraian: item.uraian,
|
||||
anggaran: item.anggaran,
|
||||
realisasi: item.realisasi,
|
||||
selisih: item.anggaran - item.realisasi,
|
||||
persentase: item.anggaran > 0 ? (item.realisasi / item.anggaran) * 100 : 0,
|
||||
level: item.level,
|
||||
tipe: item.tipe || null,
|
||||
isActive: true,
|
||||
})),
|
||||
data: body.items.map((item) => {
|
||||
const anggaran = item.anggaran;
|
||||
const totalRealisasi = 0; // Reset karena items baru
|
||||
const selisih = anggaran - totalRealisasi; // Sisa anggaran (positif = belum digunakan)
|
||||
const persentase = anggaran > 0 ? (totalRealisasi / anggaran) * 100 : 0;
|
||||
|
||||
return {
|
||||
apbdesId: id,
|
||||
kode: item.kode,
|
||||
uraian: item.uraian,
|
||||
anggaran: anggaran,
|
||||
level: item.level,
|
||||
tipe: item.tipe || null,
|
||||
totalRealisasi,
|
||||
selisih,
|
||||
persentase,
|
||||
isActive: true,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
// 4. Dapatkan semua item yang baru dibuat untuk mendapatkan ID-nya
|
||||
@@ -66,12 +73,11 @@ export default async function apbdesUpdate(context: Context) {
|
||||
});
|
||||
|
||||
// 5. Update parentId untuk setiap item
|
||||
// Pastikan allItems memiliki tipe yang benar
|
||||
const itemsForParentUpdate = allItems.map(item => ({
|
||||
id: item.id,
|
||||
kode: item.kode,
|
||||
}));
|
||||
|
||||
|
||||
await assignParentIdsToApbdesItems(itemsForParentUpdate);
|
||||
|
||||
// 6. Update data APBDes
|
||||
@@ -79,18 +85,27 @@ export default async function apbdesUpdate(context: Context) {
|
||||
where: { id },
|
||||
data: {
|
||||
tahun: body.tahun,
|
||||
name: body.name || `APBDes Tahun ${body.tahun}`,
|
||||
deskripsi: body.deskripsi,
|
||||
jumlah: body.jumlah,
|
||||
imageId: body.imageId,
|
||||
fileId: body.fileId,
|
||||
},
|
||||
});
|
||||
|
||||
// 5. Ambil data lengkap untuk response
|
||||
// 7. Ambil data lengkap untuk response (include realisasiItems)
|
||||
const result = await prisma.aPBDes.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
items: {
|
||||
where: { isActive: true },
|
||||
orderBy: { kode: 'asc' }
|
||||
orderBy: { kode: 'asc' },
|
||||
include: {
|
||||
realisasiItems: {
|
||||
where: { isActive: true },
|
||||
orderBy: { tanggal: 'asc' },
|
||||
},
|
||||
},
|
||||
},
|
||||
image: true,
|
||||
file: true,
|
||||
|
||||
Reference in New Issue
Block a user