1315 lines
38 KiB
TypeScript
1315 lines
38 KiB
TypeScript
import Elysia, { t } from "elysia"
|
|
import type { StatusPengaduan } from "generated/prisma"
|
|
import { createSurat } from "../lib/create-surat"
|
|
import { getLastUpdated } from "../lib/get-last-updated"
|
|
import { generateNoPengajuanSurat } from "../lib/no-pengajuan-surat"
|
|
import { isValidPhone, normalizePhoneNumber } from "../lib/normalizePhone"
|
|
import { prisma } from "../lib/prisma"
|
|
import { toSlug } from "../lib/slug_converter"
|
|
|
|
|
|
const PelayananRoute = new Elysia({
|
|
prefix: "pelayanan",
|
|
tags: ["pelayanan"],
|
|
})
|
|
|
|
// --- KATEGORI PELAYANAN ---
|
|
.get("/category", async () => {
|
|
const data = await prisma.categoryPelayanan.findMany({
|
|
where: {
|
|
isActive: true
|
|
},
|
|
orderBy: {
|
|
name: "asc"
|
|
},
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
syaratDokumen: true,
|
|
dataPelengkap: true,
|
|
}
|
|
})
|
|
|
|
const dataFix = data.map(item => ({
|
|
...item,
|
|
link: `${process.env.BUN_PUBLIC_BASE_URL}/darmasaba/surat?jenis=${toSlug(item.name)}`
|
|
}));
|
|
|
|
return dataFix
|
|
}, {
|
|
detail: {
|
|
summary: "List Kategori Pelayanan Surat",
|
|
description: `tool untuk mendapatkan list kategori pelayanan surat dan juga berisi link form pengajuan surat`,
|
|
tags: ["mcp"]
|
|
}
|
|
})
|
|
.post("/category/create", async ({ body }) => {
|
|
const { name, syaratDokumen, dataText } = body
|
|
|
|
await prisma.categoryPelayanan.create({
|
|
data: {
|
|
name,
|
|
syaratDokumen,
|
|
dataText,
|
|
}
|
|
})
|
|
|
|
return { success: true, message: 'kategori pelayanan surat sudah dibuat' }
|
|
}, {
|
|
body: t.Object({
|
|
name: t.String({ minLength: 1, error: "name harus diisi" }),
|
|
syaratDokumen: t.Any(),
|
|
dataText: t.Any(),
|
|
}),
|
|
detail: {
|
|
summary: "buat kategori pelayanan surat",
|
|
description: `tool untuk membuat kategori pelayanan surat`
|
|
}
|
|
})
|
|
.post("/category/update", async ({ body }) => {
|
|
const { id, name, syaratDokumen, dataText } = body
|
|
|
|
await prisma.categoryPelayanan.update({
|
|
where: {
|
|
id,
|
|
},
|
|
data: {
|
|
name,
|
|
syaratDokumen,
|
|
dataText,
|
|
}
|
|
})
|
|
|
|
return { success: true, message: 'kategori pelayanan surat sudah diperbarui' }
|
|
}, {
|
|
body: t.Object({
|
|
id: t.String({ minLength: 1, error: "id harus diisi" }),
|
|
name: t.String({ minLength: 1, error: "name harus diisi" }),
|
|
syaratDokumen: t.Any(),
|
|
dataText: t.Any(),
|
|
}),
|
|
detail: {
|
|
summary: "update kategori pelayanan surat",
|
|
description: `tool untuk update kategori pelayanan surat`
|
|
}
|
|
})
|
|
.post("/category/delete", async ({ body }) => {
|
|
const { id } = body
|
|
|
|
await prisma.categoryPelayanan.update({
|
|
where: {
|
|
id,
|
|
},
|
|
data: {
|
|
isActive: false
|
|
}
|
|
})
|
|
|
|
return { success: true, message: 'kategori pelayanan surat sudah dihapus' }
|
|
}, {
|
|
body: t.Object({
|
|
id: t.String({ minLength: 1, error: "id harus diisi" }),
|
|
}),
|
|
detail: {
|
|
summary: "delete kategori pelayanan surat",
|
|
description: `tool untuk delete kategori pelayanan surat`
|
|
}
|
|
})
|
|
.get("/category/detail", async ({ query }) => {
|
|
const { id } = query
|
|
const data = await prisma.categoryPelayanan.findUnique({
|
|
where: {
|
|
id
|
|
}
|
|
})
|
|
|
|
if (!data) {
|
|
return;
|
|
}
|
|
|
|
const dataPelengkap: { key: string }[] = Array.isArray(data.dataPelengkap)
|
|
? data.dataPelengkap.filter(
|
|
(v): v is { key: string } =>
|
|
typeof v === "object" &&
|
|
v !== null &&
|
|
"key" in v &&
|
|
typeof (v as any).key === "string"
|
|
)
|
|
: [];
|
|
|
|
const syaratDokumen: { name: string }[] = Array.isArray(data.syaratDokumen)
|
|
? data.syaratDokumen.filter(
|
|
(v): v is { name: string } =>
|
|
typeof v === "object" &&
|
|
v !== null &&
|
|
"name" in v &&
|
|
typeof (v as any).name === "string"
|
|
)
|
|
: [];
|
|
|
|
|
|
return {
|
|
id: data.id,
|
|
name: data.name,
|
|
dataPelengkap,
|
|
syaratDokumen,
|
|
};
|
|
}, {
|
|
query: t.Object({
|
|
id: t.String({ minLength: 1, error: "id harus diisi" }),
|
|
}),
|
|
detail: {
|
|
summary: "Detail Kategori Pelayanan Surat by ID",
|
|
description: `tool untuk mendapatkan detail kategori pelayanan surat berdasarkan id`,
|
|
}
|
|
})
|
|
|
|
|
|
// --- PELAYANAN SURAT ---
|
|
.get("/", async ({ query, headers }) => {
|
|
// const { phone } = query
|
|
const phone = headers['x-phone'] || ""
|
|
const data = await prisma.pelayananAjuan.findMany({
|
|
orderBy: {
|
|
createdAt: "asc"
|
|
},
|
|
where: {
|
|
isActive: true,
|
|
Warga: {
|
|
phone
|
|
}
|
|
},
|
|
select: {
|
|
noPengajuan: true,
|
|
status: true,
|
|
createdAt: true,
|
|
CategoryPelayanan: {
|
|
select: {
|
|
name: true
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
const dataFix = data.map((item) => {
|
|
return {
|
|
noPengajuan: item.noPengajuan,
|
|
status: item.status,
|
|
category: item.CategoryPelayanan.name,
|
|
createdAt: item.createdAt.toLocaleDateString("id-ID", { day: "numeric", month: "long", year: "numeric" }),
|
|
}
|
|
})
|
|
|
|
return dataFix
|
|
|
|
}, {
|
|
// query: t.Object({
|
|
// phone: t.String({ minLength: 1, error: "phone harus diisi" }),
|
|
// }),
|
|
detail: {
|
|
summary: "List Ajuan Pelayanan Surat by Phone",
|
|
description: `tool untuk mendapatkan list ajuan pelayanan surat`,
|
|
tags: ["mcp"]
|
|
}
|
|
})
|
|
.get("/detail", async ({ query }) => {
|
|
const { id } = query
|
|
const data = await prisma.pelayananAjuan.findFirst({
|
|
where: {
|
|
id: id
|
|
},
|
|
select: {
|
|
id: true,
|
|
noPengajuan: true,
|
|
status: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
CategoryPelayanan: {
|
|
select: {
|
|
name: true,
|
|
syaratDokumen: true,
|
|
dataPelengkap: true
|
|
}
|
|
},
|
|
Warga: {
|
|
select: {
|
|
name: true,
|
|
phone: true,
|
|
_count: {
|
|
select: {
|
|
Pengaduan: true,
|
|
PelayananAjuan: true,
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
})
|
|
|
|
if (!data) {
|
|
const datafix = {
|
|
pengajuan: {},
|
|
history: [],
|
|
warga: {},
|
|
syaratDokumen: [],
|
|
dataText: [],
|
|
}
|
|
return datafix
|
|
}
|
|
|
|
const dataSurat = await prisma.suratPelayanan.findFirst({
|
|
where: {
|
|
idPengajuanLayanan: data?.id,
|
|
isActive: true
|
|
},
|
|
select: {
|
|
id: true,
|
|
idCategory: true,
|
|
file: true
|
|
}
|
|
})
|
|
|
|
const dataSyarat = await prisma.syaratDokumenPelayanan.findMany({
|
|
where: {
|
|
idPengajuanLayanan: data?.id,
|
|
isActive: true
|
|
},
|
|
select: {
|
|
id: true,
|
|
jenis: true,
|
|
value: true,
|
|
}
|
|
})
|
|
|
|
const dataText = await prisma.dataTextPelayanan.findMany({
|
|
where: {
|
|
idPengajuanLayanan: data?.id,
|
|
isActive: true
|
|
},
|
|
select: {
|
|
id: true,
|
|
value: true,
|
|
jenis: true,
|
|
}
|
|
})
|
|
const syaratDokumen = (data?.CategoryPelayanan?.syaratDokumen ?? []) as {
|
|
name: string;
|
|
desc: string;
|
|
key: string;
|
|
}[];
|
|
|
|
const dataSyaratFix = dataSyarat.map((item) => {
|
|
const desc = syaratDokumen.find((v) => v.key == item.jenis)?.desc
|
|
return {
|
|
id: item.id,
|
|
jenis: desc,
|
|
value: item.value,
|
|
}
|
|
})
|
|
|
|
const dataTextCategory = (data?.CategoryPelayanan?.dataPelengkap ?? []) as {
|
|
type: string;
|
|
options?: {
|
|
label: string,
|
|
value: string
|
|
}[]; name: string;
|
|
desc: string;
|
|
key: string;
|
|
}[];
|
|
|
|
const refMap = new Map(
|
|
dataTextCategory.map((v, i) => [
|
|
v.key,
|
|
{ ...v, order: i }
|
|
])
|
|
);
|
|
|
|
const dataTextFix = dataText
|
|
.map((item) => {
|
|
const ref = refMap.get(item.jenis);
|
|
const nama = dataTextCategory.find((v) => v.key == item.jenis)?.name
|
|
return {
|
|
id: item.id,
|
|
jenis: nama,
|
|
key: ref?.key,
|
|
value: item.value,
|
|
type: ref?.type ?? "",
|
|
options: ref?.options ?? [],
|
|
order: ref?.order ?? Infinity,
|
|
};
|
|
})
|
|
.sort((a, b) => a.order - b.order)
|
|
.map(({ order, ...rest }) => rest); // hapus order
|
|
|
|
|
|
const dataHistory = await prisma.historyPelayanan.findMany({
|
|
where: {
|
|
idPengajuanLayanan: data?.id,
|
|
},
|
|
select: {
|
|
id: true,
|
|
deskripsi: true,
|
|
status: true,
|
|
createdAt: true,
|
|
idUser: true,
|
|
User: {
|
|
select: {
|
|
name: true,
|
|
}
|
|
}
|
|
},
|
|
orderBy: {
|
|
createdAt: "desc"
|
|
}
|
|
})
|
|
|
|
const dataHistoryFix = dataHistory.map((item) => {
|
|
return {
|
|
id: item.id,
|
|
deskripsi: item.deskripsi,
|
|
status: item.status,
|
|
createdAt: item.createdAt,
|
|
idUser: item.idUser,
|
|
nameUser: item.User?.name,
|
|
}
|
|
})
|
|
|
|
const warga = {
|
|
name: data?.Warga?.name,
|
|
phone: data?.Warga?.phone,
|
|
pengaduan: data?.Warga?._count.Pengaduan,
|
|
pelayanan: data?.Warga?._count.PelayananAjuan,
|
|
}
|
|
|
|
const dataPengajuan = {
|
|
id: data?.id,
|
|
noPengajuan: data?.noPengajuan,
|
|
category: data?.CategoryPelayanan.name,
|
|
status: data?.status,
|
|
createdAt: data?.createdAt,
|
|
updatedAt: data?.updatedAt,
|
|
idSurat: dataSurat?.id,
|
|
fileSurat: dataSurat?.file,
|
|
}
|
|
|
|
const datafix = {
|
|
pengajuan: dataPengajuan,
|
|
history: dataHistoryFix,
|
|
warga: warga,
|
|
syaratDokumen: dataSyaratFix,
|
|
dataText: dataTextFix,
|
|
}
|
|
|
|
return datafix
|
|
}, {
|
|
query: t.Object({
|
|
id: t.String({ minLength: 1, error: "id harus diisi" }),
|
|
}),
|
|
detail: {
|
|
summary: "Detail Ajuan Pelayanan Surat by ID",
|
|
description: `tool untuk mendapatkan detail ajuan pelayanan surat berdasarkan id`,
|
|
}
|
|
})
|
|
.post("/create", async ({ body, headers }) => {
|
|
const { kategoriId, dataPelengkap, syaratDokumen, nama, phone } = body
|
|
// const namaWarga = headers['x-user'] || ""
|
|
// const noTelepon = headers['x-phone'] || ""
|
|
const noPengajuan = await generateNoPengajuanSurat()
|
|
let idCategoryFix = kategoriId
|
|
let idWargaFix = ""
|
|
|
|
const category = await prisma.categoryPelayanan.findUnique({
|
|
where: {
|
|
id: kategoriId,
|
|
}
|
|
})
|
|
|
|
if (!category) {
|
|
const cariCategory = await prisma.categoryPelayanan.findFirst({
|
|
where: {
|
|
name: kategoriId,
|
|
}
|
|
})
|
|
|
|
if (!cariCategory) {
|
|
return { success: false, message: 'kategori pelayanan surat tidak ditemukan' }
|
|
} else {
|
|
idCategoryFix = cariCategory.id
|
|
}
|
|
|
|
}
|
|
|
|
if (!isValidPhone(phone)) {
|
|
return { success: false, message: 'nomor telepon tidak valid, harap masukkan nomor yang benar' }
|
|
}
|
|
|
|
const nomorHP = normalizePhoneNumber({ phone: phone })
|
|
const dataWarga = await prisma.warga.upsert({
|
|
where: {
|
|
phone: nomorHP
|
|
},
|
|
create: {
|
|
name: nama,
|
|
phone: nomorHP,
|
|
},
|
|
update: {
|
|
name: nama,
|
|
},
|
|
select: {
|
|
id: true
|
|
}
|
|
})
|
|
|
|
idWargaFix = dataWarga.id
|
|
|
|
const pengaduan = await prisma.pelayananAjuan.create({
|
|
data: {
|
|
noPengajuan,
|
|
idCategory: idCategoryFix,
|
|
idWarga: idWargaFix,
|
|
},
|
|
select: {
|
|
id: true,
|
|
}
|
|
})
|
|
|
|
if (!pengaduan.id) {
|
|
return { success: false, message: 'gagal membuat pengajuan surat' }
|
|
}
|
|
|
|
let dataInsertSyaratDokumen = []
|
|
let dataInsertDataText = []
|
|
|
|
for (const item of syaratDokumen) {
|
|
dataInsertSyaratDokumen.push({
|
|
idPengajuanLayanan: pengaduan.id,
|
|
idCategory: idCategoryFix,
|
|
jenis: item.key,
|
|
value: item.value,
|
|
})
|
|
}
|
|
|
|
for (const item of dataPelengkap) {
|
|
dataInsertDataText.push({
|
|
idPengajuanLayanan: pengaduan.id,
|
|
idCategory: idCategoryFix,
|
|
jenis: item.key,
|
|
value: item.value,
|
|
})
|
|
}
|
|
|
|
|
|
await prisma.syaratDokumenPelayanan.createMany({
|
|
data: dataInsertSyaratDokumen,
|
|
})
|
|
|
|
await prisma.dataTextPelayanan.createMany({
|
|
data: dataInsertDataText,
|
|
})
|
|
|
|
|
|
await prisma.historyPelayanan.create({
|
|
data: {
|
|
idPengajuanLayanan: pengaduan.id,
|
|
deskripsi: "Pengajuan surat dibuat",
|
|
}
|
|
})
|
|
|
|
return { success: true, message: 'Pengajuan layanan surat sudah dibuat dengan nomer ' + noPengajuan + ', nomer ini akan digunakan untuk mengakses pengajuan ini', noPengajuan }
|
|
}, {
|
|
body: t.Object({
|
|
kategoriId: t.String({
|
|
description: "ID atau nama kategori pelayanan surat yang dipilih. Jika berupa nama, sistem akan mencocokkan secara otomatis.",
|
|
examples: ["skusaha"],
|
|
error: "ID kategori harus diisi"
|
|
}),
|
|
nama: t.String({
|
|
description: "Nama warga",
|
|
examples: ["Budi Santoso"],
|
|
error: "Nama warga harus diisi"
|
|
}),
|
|
phone: t.String({
|
|
error: "Nomor telepon harus diisi",
|
|
examples: ["08123456789", "+628123456789"],
|
|
description: "Nomor telepon warga pelapor"
|
|
}),
|
|
|
|
dataPelengkap: t.Array(
|
|
t.Object({
|
|
key: t.String({
|
|
description: "Jenis field yang dibutuhkan oleh kategori pelayanan. Biasanya dinamis.",
|
|
examples: ["nama", "jenis kelamin", "tempat tanggal lahir", "negara", "agama", "status perkawinan", "alamat", "pekerjaan", "jenis usaha", "alamat usaha"],
|
|
error: "jenis harus diisi"
|
|
}),
|
|
value: t.String({
|
|
description: "Isi atau nilai dari jenis field terkait.",
|
|
examples: ["Budi Santoso", "Laki-laki", "Denpasar, 28 Februari 1990", "Indonesia", "Islam", "Belum menikah", "Jl. Mawar No. 10", "Karyawan Swasta", "usaha makanan", "Jl. Melati No. 21"],
|
|
error: "value harus diisi"
|
|
}),
|
|
}),
|
|
{
|
|
description: "Kumpulan data text dinamis sesuai kategori layanan.",
|
|
examples: [
|
|
[
|
|
{ key: "nama", value: "Budi Santoso" },
|
|
{ key: "jenis kelamin", value: "Laki-laki" },
|
|
{ key: "tempat tanggal lahir", value: "Denpasar, 28 Februari 1990" },
|
|
{ key: "negara", value: "Indonesia" },
|
|
{ key: "agama", value: "Islam" },
|
|
{ key: "status perkawinan", value: "Belum menikah" },
|
|
{ key: "alamat", value: "Jl. Mawar No. 10" },
|
|
{ key: "pekerjaan", value: "Karyawan Swasta" },
|
|
{ key: "jenis usaha", value: "usaha makanan" },
|
|
{ key: "alamat usaha", value: "Jl. Melati No. 21" },
|
|
]
|
|
],
|
|
error: "Data Pelengkap harus berupa array"
|
|
}
|
|
),
|
|
|
|
syaratDokumen: t.Array(
|
|
t.Object({
|
|
key: t.String({
|
|
description: "Jenis dokumen persyaratan yang diminta oleh kategori layanan.",
|
|
examples: ["ktp", "kk", "surat_pengantar_rt"],
|
|
error: "jenis harus diisi"
|
|
}),
|
|
value: t.String({
|
|
description: "Nama file atau identifier file dokumen yang diupload.",
|
|
examples: ["ktp_budi.png", "kk_budi.png"],
|
|
error: "value harus diisi"
|
|
}),
|
|
}),
|
|
{
|
|
description: "Kumpulan dokumen yang wajib diupload sesuai persyaratan layanan.",
|
|
examples: [
|
|
[
|
|
{ key: "pengantar kelian", value: "pengantar_kelurahan_budi.png" },
|
|
{ key: "ktp/kk", value: "kk_budi.png" },
|
|
{ key: "foto lokasi", value: "foto_lokasi_budi.png" }
|
|
]
|
|
],
|
|
error: "Syarat Dokumen harus berupa array"
|
|
}
|
|
),
|
|
}),
|
|
detail: {
|
|
summary: "Buat Pengajuan Pelayanan Surat",
|
|
description: `tool untuk membuat pengajuan pelayanan surat dengan syarat dokumen serta data text sesuai kategori pelayanan surat yang dipilih`,
|
|
}
|
|
})
|
|
.post("/detail-data", async ({ body }) => {
|
|
const { nomerPengajuan } = body
|
|
const data = await prisma.pelayananAjuan.findFirst({
|
|
where: {
|
|
noPengajuan: {
|
|
equals: nomerPengajuan,
|
|
mode: "insensitive"
|
|
}
|
|
},
|
|
select: {
|
|
id: true,
|
|
noPengajuan: true,
|
|
status: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
CategoryPelayanan: {
|
|
select: {
|
|
name: true,
|
|
dataPelengkap: true,
|
|
syaratDokumen: true,
|
|
}
|
|
},
|
|
Warga: {
|
|
select: {
|
|
name: true,
|
|
phone: true,
|
|
_count: {
|
|
select: {
|
|
Pengaduan: true,
|
|
PelayananAjuan: true,
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
})
|
|
|
|
if (!data) {
|
|
return {
|
|
success: false,
|
|
message: "Data tidak ditemukan",
|
|
pengajuan: {},
|
|
history: [],
|
|
warga: {},
|
|
syaratDokumen: [],
|
|
dataPelengkap: [],
|
|
}
|
|
}
|
|
|
|
const dataSurat = await prisma.suratPelayanan.findFirst({
|
|
where: {
|
|
idPengajuanLayanan: data?.id,
|
|
isActive: true
|
|
},
|
|
select: {
|
|
id: true,
|
|
idCategory: true,
|
|
}
|
|
})
|
|
|
|
const dataSyarat = await prisma.syaratDokumenPelayanan.findMany({
|
|
where: {
|
|
idPengajuanLayanan: data?.id,
|
|
isActive: true
|
|
},
|
|
select: {
|
|
id: true,
|
|
jenis: true,
|
|
value: true,
|
|
}
|
|
})
|
|
|
|
const dataPelengkap = await prisma.dataTextPelayanan.findMany({
|
|
where: {
|
|
idPengajuanLayanan: data?.id,
|
|
isActive: true
|
|
},
|
|
select: {
|
|
id: true,
|
|
value: true,
|
|
jenis: true,
|
|
}
|
|
})
|
|
const syaratDokumen = (data?.CategoryPelayanan?.syaratDokumen ?? []) as {
|
|
name: string;
|
|
desc: string;
|
|
key: string;
|
|
}[];
|
|
|
|
const dataSyaratFix = dataSyarat.map((item) => {
|
|
const desc = syaratDokumen.find((v) => v.key == item.jenis)?.desc
|
|
const name = syaratDokumen.find((v) => v.key == item.jenis)?.name
|
|
return {
|
|
id: item.id,
|
|
key: item.jenis,
|
|
value: item.value,
|
|
name: name ?? '',
|
|
desc: desc ?? ''
|
|
}
|
|
})
|
|
|
|
const dataPelengkapList = (data?.CategoryPelayanan?.dataPelengkap ?? []) as {
|
|
type: string;
|
|
options?: {
|
|
label: string,
|
|
value: string
|
|
}[];
|
|
name: string;
|
|
desc: string;
|
|
key: string;
|
|
}[];
|
|
|
|
const refMap = new Map(
|
|
dataPelengkapList.map((v, i) => [
|
|
v.key,
|
|
{ ...v, order: i }
|
|
])
|
|
);
|
|
|
|
const dataTextFix = dataPelengkap
|
|
.map((item) => {
|
|
const ref = refMap.get(item.jenis);
|
|
|
|
return {
|
|
id: item.id,
|
|
key: item.jenis,
|
|
value: item.value,
|
|
desc: ref?.desc ?? "",
|
|
name: ref?.name ?? "",
|
|
type: ref?.type ?? "",
|
|
options: ref?.options ?? [],
|
|
order: ref?.order ?? Infinity,
|
|
};
|
|
})
|
|
.sort((a, b) => a.order - b.order)
|
|
.map(({ order, ...rest }) => rest); // hapus order
|
|
|
|
|
|
const dataHistory = await prisma.historyPelayanan.findMany({
|
|
where: {
|
|
idPengajuanLayanan: data?.id,
|
|
},
|
|
select: {
|
|
id: true,
|
|
deskripsi: true,
|
|
status: true,
|
|
createdAt: true,
|
|
idUser: true,
|
|
User: {
|
|
select: {
|
|
name: true,
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
const alasanDitolak = await prisma.historyPelayanan.findFirst({
|
|
where: {
|
|
idPengajuanLayanan: data?.id,
|
|
status: "ditolak"
|
|
},
|
|
select: {
|
|
keteranganAlasan: true,
|
|
},
|
|
orderBy: {
|
|
createdAt: "desc"
|
|
}
|
|
})
|
|
|
|
const dataHistoryFix = dataHistory.map((item) => {
|
|
return {
|
|
id: item.id,
|
|
deskripsi: item.deskripsi,
|
|
status: item.status,
|
|
createdAt: item.createdAt,
|
|
idUser: item.idUser,
|
|
nameUser: item.User?.name,
|
|
}
|
|
})
|
|
|
|
const warga = {
|
|
name: data?.Warga?.name,
|
|
phone: data?.Warga?.phone,
|
|
pengaduan: data?.Warga?._count.Pengaduan,
|
|
pelayanan: data?.Warga?._count.PelayananAjuan,
|
|
}
|
|
|
|
const dataPengajuan = {
|
|
id: data?.id,
|
|
noPengajuan: data?.noPengajuan,
|
|
category: data?.CategoryPelayanan.name,
|
|
status: data?.status,
|
|
createdAt: data?.createdAt,
|
|
updatedAt: data?.updatedAt,
|
|
idSurat: dataSurat?.id,
|
|
alasan: alasanDitolak?.keteranganAlasan,
|
|
}
|
|
|
|
const datafix = {
|
|
success: true,
|
|
message: 'sukses',
|
|
pengajuan: dataPengajuan,
|
|
linkUpdate: `${process.env.BUN_PUBLIC_BASE_URL}/darmasaba/update-data-surat?pengajuan=${data.noPengajuan}`,
|
|
history: dataHistoryFix,
|
|
warga: warga,
|
|
syaratDokumen: dataSyaratFix,
|
|
dataPelengkap: dataTextFix,
|
|
}
|
|
|
|
return datafix
|
|
|
|
}, {
|
|
body: t.Object({
|
|
nomerPengajuan: t.String({
|
|
description: "Nomor pengajuan pelayanan surat yang ingin diakses.",
|
|
examples: ["PS-101225-001", "PS-101225-002"],
|
|
error: "Nomor pengajuan harus diisi"
|
|
})
|
|
}),
|
|
detail: {
|
|
summary: "Detail Pengajuan Pelayanan Surat By Nomor Pengajuan",
|
|
description: `tool untuk mendapatkan detail pengajuan pelayanan surat berdasarkan nomor pengajuan`,
|
|
tags: ["mcp"]
|
|
}
|
|
})
|
|
.post("/update-status", async ({ body }) => {
|
|
const { id, status, keterangan, idUser, noSurat } = body
|
|
let deskripsi = ""
|
|
|
|
|
|
const pengajuan = await prisma.pelayananAjuan.update({
|
|
where: {
|
|
id,
|
|
},
|
|
data: {
|
|
status: status as StatusPengaduan,
|
|
},
|
|
select: {
|
|
id: true,
|
|
idCategory: true,
|
|
idWarga: true,
|
|
}
|
|
})
|
|
|
|
if (!pengajuan) {
|
|
return { success: false, message: 'gagal update status pengajuan surat', linkUpdate: '', idSurat: '' }
|
|
}
|
|
|
|
const dataPengajuan = await prisma.pelayananAjuan.findUnique({
|
|
where: { id: pengajuan.id },
|
|
select: { noPengajuan: true }
|
|
});
|
|
|
|
if (status === "diterima") {
|
|
deskripsi = "Pengajuan surat diterima"
|
|
} else if (status === "ditolak") {
|
|
deskripsi = "Pengajuan surat ditolak dengan keterangan " + keterangan
|
|
} else if (status === "selesai") {
|
|
deskripsi = "Pengajuan surat disetujui"
|
|
}
|
|
|
|
await prisma.historyPelayanan.create({
|
|
data: {
|
|
idPengajuanLayanan: pengajuan.id,
|
|
deskripsi: deskripsi,
|
|
status: status as StatusPengaduan,
|
|
idUser,
|
|
keteranganAlasan: keterangan,
|
|
}
|
|
})
|
|
|
|
|
|
let idSurat = "";
|
|
if (status === "selesai") {
|
|
const result = await createSurat({ idPengajuan: pengajuan.id, idCategory: pengajuan.idCategory, idWarga: pengajuan.idWarga, noSurat })
|
|
idSurat = result.idSurat ?? "";
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
message: 'pengajuan surat sudah diperbarui',
|
|
linkUpdate: status == "ditolak" ? `${process.env.BUN_PUBLIC_BASE_URL}/darmasaba/update-data-surat?pengajuan=${dataPengajuan?.noPengajuan}` : '',
|
|
idSurat: idSurat,
|
|
}
|
|
}, {
|
|
body: t.Object({
|
|
id: t.String({ minLength: 1, error: "id harus diisi" }),
|
|
status: t.String({ minLength: 1, error: "status harus diisi" }),
|
|
keterangan: t.String({ optional: true }),
|
|
idUser: t.String({ optional: true }),
|
|
noSurat: t.String({ optional: true }),
|
|
}),
|
|
detail: {
|
|
summary: "Update Status Pengajuan Pelayanan Surat",
|
|
description: `tool untuk update status pengajuan pelayanan surat`,
|
|
}
|
|
})
|
|
.post("/update", async ({ body }) => {
|
|
const { id, syaratDokumen, dataPelengkap } = body
|
|
let dataUpdate = []
|
|
|
|
const pengajuan = await prisma.pelayananAjuan.findUnique({
|
|
where: {
|
|
id
|
|
}
|
|
})
|
|
|
|
if (!pengajuan) {
|
|
return { success: false, message: 'data pengajuan surat tidak ditemukan' }
|
|
}
|
|
|
|
|
|
if (pengajuan.status != "ditolak" && pengajuan.status != "antrian") {
|
|
return { success: false, message: 'pengajuan surat tidak dapat diupdate karena status ' + pengajuan.status }
|
|
}
|
|
|
|
if (dataPelengkap && dataPelengkap.length > 0) {
|
|
for (const item of dataPelengkap) {
|
|
dataUpdate.push(item.key)
|
|
|
|
const upd = await prisma.dataTextPelayanan.update({
|
|
where: {
|
|
id: item.id
|
|
},
|
|
data: {
|
|
value: item.value,
|
|
}
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
const category = await prisma.categoryPelayanan.findUnique({
|
|
where: {
|
|
id: pengajuan.idCategory,
|
|
}
|
|
})
|
|
|
|
type SyaratDokumen = {
|
|
desc: string;
|
|
name: string;
|
|
};
|
|
|
|
const syarat = category?.syaratDokumen as SyaratDokumen[] | undefined
|
|
|
|
|
|
if (syaratDokumen && syaratDokumen.length > 0) {
|
|
for (const item of syaratDokumen) {
|
|
dataUpdate.push(item.key)
|
|
const upd = await prisma.syaratDokumenPelayanan.update({
|
|
where: {
|
|
id: item.id
|
|
},
|
|
data: {
|
|
value: item.value,
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
const keys = dataUpdate.join(", ");
|
|
|
|
if (pengajuan.status == "ditolak") {
|
|
const updStatus = await prisma.pelayananAjuan.update({
|
|
where: {
|
|
id: pengajuan.id,
|
|
},
|
|
data: {
|
|
status: "antrian",
|
|
}
|
|
})
|
|
}
|
|
|
|
const history = await prisma.historyPelayanan.create({
|
|
data: {
|
|
idPengajuanLayanan: pengajuan.id,
|
|
deskripsi: `Pengajuan surat diupdate oleh warga (data yg diupdate: ${keys})`,
|
|
status: "antrian",
|
|
}
|
|
})
|
|
|
|
|
|
return { success: true, message: 'pengajuan surat sudah diperbarui' }
|
|
|
|
}, {
|
|
body: t.Object({
|
|
id: t.String({
|
|
error: "id harus diisi",
|
|
description: "ID yang ingin diupdate"
|
|
}),
|
|
dataPelengkap: t.Optional(t.Array(
|
|
t.Object({
|
|
id: t.String({
|
|
description: "ID Data Pelengkap.",
|
|
error: "id harus diisi"
|
|
}),
|
|
key: t.String({
|
|
description: "Key field yang dibutuhkan oleh kategori pelayanan. Biasanya dinamis.",
|
|
examples: ["nama", "jenis kelamin", "tempat tanggal lahir", "negara", "agama", "status perkawinan", "alamat", "pekerjaan", "jenis usaha", "alamat usaha"],
|
|
error: "key harus diisi"
|
|
}),
|
|
value: t.String({
|
|
description: "Isi atau nilai dari jenis field terkait.",
|
|
examples: ["Budi Santoso", "Laki-laki", "Denpasar, 28 Februari 1990", "Indonesia", "Islam", "Belum menikah", "Jl. Mawar No. 10", "Karyawan Swasta", "usaha makanan", "Jl. Melati No. 21"],
|
|
error: "value harus diisi"
|
|
}),
|
|
}),
|
|
{
|
|
description: "Kumpulan data text dinamis sesuai kategori layanan.",
|
|
examples: [
|
|
[
|
|
{ id: "1", key: "nama", value: "Budi Santoso" },
|
|
{ id: "2", key: "jenis kelamin", value: "Laki-laki" },
|
|
{ id: "3", key: "tempat tanggal lahir", value: "Denpasar, 28 Februari 1990" },
|
|
{ id: "4", key: "negara", value: "Indonesia" },
|
|
{ id: "5", key: "agama", value: "Islam" },
|
|
{ id: "6", key: "status perkawinan", value: "Belum menikah" },
|
|
{ id: "7", key: "alamat", value: "Jl. Mawar No. 10" },
|
|
{ id: "8", key: "pekerjaan", value: "Karyawan Swasta" },
|
|
{ id: "9", key: "jenis usaha", value: "usaha makanan" },
|
|
{ id: "10", key: "alamat usaha", value: "Jl. Melati No. 21" },
|
|
]
|
|
],
|
|
}
|
|
)),
|
|
syaratDokumen: t.Optional(t.Array(
|
|
t.Object({
|
|
id: t.String({
|
|
description: "ID syarat dokumen",
|
|
error: "id harus diisi"
|
|
}),
|
|
key: t.String({
|
|
description: "Key dokumen persyaratan yang diminta oleh kategori layanan.",
|
|
examples: ["ktp", "kk", "surat_pengantar_rt"],
|
|
error: "key harus diisi"
|
|
}),
|
|
value: t.String({
|
|
description: "Nama file atau identifier file dokumen yang diupload.",
|
|
examples: ["ktp_budi.png", "kk_budi.png"],
|
|
error: "value harus diisi"
|
|
}),
|
|
}),
|
|
{
|
|
description: "Kumpulan dokumen yang wajib diupload sesuai persyaratan layanan.",
|
|
examples: [
|
|
[
|
|
{ id: "1", key: "pengantar kelian", value: "pengantar_kelurahan_budi.png" },
|
|
{ id: "2", key: "ktp/kk", value: "kk_budi.png" },
|
|
{ id: "3", key: "foto lokasi", value: "foto_lokasi_budi.png" }
|
|
]
|
|
],
|
|
}
|
|
)),
|
|
}),
|
|
detail: {
|
|
summary: "Update Data Pengajuan Pelayanan Surat",
|
|
description: `tool untuk update data pengajuan pelayanan surat`,
|
|
}
|
|
})
|
|
.post("/update-data-pelengkap", async ({ body }) => {
|
|
const { id, value, jenis, idUser } = body
|
|
|
|
const dataPelengkap = await prisma.dataTextPelayanan.findUnique({
|
|
where: {
|
|
id
|
|
},
|
|
select: {
|
|
idPengajuanLayanan: true,
|
|
PelayananAjuan: {
|
|
select: {
|
|
status: true
|
|
}
|
|
}
|
|
}
|
|
|
|
})
|
|
|
|
if (!dataPelengkap) {
|
|
return { success: false, message: 'data pelengkap surat tidak ditemukan' }
|
|
}
|
|
|
|
const upd = await prisma.dataTextPelayanan.update({
|
|
where: {
|
|
id
|
|
},
|
|
data: {
|
|
value: value,
|
|
}
|
|
})
|
|
|
|
const history = await prisma.historyPelayanan.create({
|
|
data: {
|
|
idPengajuanLayanan: dataPelengkap.idPengajuanLayanan,
|
|
deskripsi: `Pengajuan surat diupdate oleh user (data yg diupdate: ${jenis})`,
|
|
status: dataPelengkap.PelayananAjuan.status,
|
|
idUser
|
|
}
|
|
})
|
|
|
|
|
|
return { success: true, message: 'data pelengkap surat sudah diperbarui' }
|
|
|
|
}, {
|
|
body: t.Object({
|
|
id: t.String({
|
|
error: "id harus diisi",
|
|
description: "ID yang ingin diupdate"
|
|
}),
|
|
value: t.String({
|
|
error: "value harus diisi",
|
|
description: "Value yang ingin diupdate"
|
|
}),
|
|
jenis: t.String({
|
|
error: "jenis harus diisi",
|
|
description: "Jenis data yang ingin diupdate"
|
|
}),
|
|
idUser: t.String({
|
|
error: "idUser harus diisi",
|
|
description: "ID user yang melakukan update"
|
|
})
|
|
}),
|
|
detail: {
|
|
summary: "Update Data Pelengkap Pengajuan Pelayanan Surat oleh user admin",
|
|
description: `tool untuk update data pelengkap pengajuan pelayanan surat oleh user admin`,
|
|
}
|
|
})
|
|
.get("/list", async ({ query }) => {
|
|
const { take, page, search, status } = query
|
|
const skip = !page ? 0 : (Number(page) - 1) * (!take ? 10 : Number(take))
|
|
|
|
let where: any = {
|
|
isActive: true,
|
|
OR: [
|
|
{
|
|
CategoryPelayanan: {
|
|
name: {
|
|
contains: search ?? "",
|
|
mode: "insensitive"
|
|
},
|
|
},
|
|
},
|
|
{
|
|
noPengajuan: {
|
|
contains: search ?? "",
|
|
mode: "insensitive"
|
|
},
|
|
},
|
|
{
|
|
Warga: {
|
|
phone: {
|
|
contains: search ?? "",
|
|
mode: "insensitive"
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Warga: {
|
|
name: {
|
|
contains: search ?? "",
|
|
mode: "insensitive"
|
|
},
|
|
},
|
|
}
|
|
]
|
|
}
|
|
|
|
if (status && status !== "semua") {
|
|
where = {
|
|
...where,
|
|
status: status
|
|
}
|
|
}
|
|
|
|
|
|
const totalData = await prisma.pelayananAjuan.count({
|
|
where
|
|
});
|
|
|
|
const data = await prisma.pelayananAjuan.findMany({
|
|
skip,
|
|
take: !take ? 10 : Number(take),
|
|
orderBy: {
|
|
createdAt: "desc"
|
|
},
|
|
where,
|
|
select: {
|
|
id: true,
|
|
noPengajuan: true,
|
|
status: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
CategoryPelayanan: {
|
|
select: {
|
|
name: true
|
|
}
|
|
},
|
|
Warga: {
|
|
select: {
|
|
name: true,
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
const dataFix = data.map((item) => {
|
|
return {
|
|
noPengajuan: item.noPengajuan,
|
|
id: item.id,
|
|
category: item.CategoryPelayanan.name,
|
|
warga: item.Warga.name,
|
|
status: item.status,
|
|
createdAt: item.createdAt.toISOString(),
|
|
updatedAt: 'terakhir diperbarui ' + getLastUpdated(item.updatedAt),
|
|
}
|
|
})
|
|
|
|
const dataReturn = {
|
|
data: dataFix,
|
|
total: totalData,
|
|
page: Number(page) || 1,
|
|
pageSize: !take ? 10 : Number(take),
|
|
totalPages: Math.ceil(totalData / (!take ? 10 : Number(take)))
|
|
}
|
|
|
|
return dataReturn
|
|
}, {
|
|
query: t.Object({
|
|
take: t.String({ optional: true }),
|
|
page: t.String({ optional: true }),
|
|
search: t.String({ optional: true }),
|
|
status: t.String({ optional: true }),
|
|
}),
|
|
detail: {
|
|
summary: "List Pengajuan Pelayanan Surat Warga",
|
|
description: `tool untuk mendapatkan list pengajuan pelayanan surat warga`,
|
|
}
|
|
})
|
|
.get("/count", async ({ query }) => {
|
|
const counts = await prisma.pelayananAjuan.groupBy({
|
|
by: ['status'],
|
|
where: {
|
|
isActive: true,
|
|
},
|
|
_count: {
|
|
status: true,
|
|
},
|
|
});
|
|
|
|
const grouped = Object.fromEntries(
|
|
counts.map(c => [c.status, c._count.status])
|
|
);
|
|
|
|
const total = await prisma.pelayananAjuan.count({
|
|
where: { isActive: true },
|
|
});
|
|
|
|
return {
|
|
antrian: grouped?.antrian || 0,
|
|
diterima: grouped?.diterima || 0,
|
|
dikerjakan: grouped?.dikerjakan || 0,
|
|
ditolak: grouped?.ditolak || 0,
|
|
selesai: grouped?.selesai || 0,
|
|
semua: total,
|
|
};
|
|
}, {
|
|
detail: {
|
|
summary: "Jumlah Pengajuan Pelayanan Surat Warga",
|
|
description: `tool untuk mendapatkan jumlah pengajuan pelayanan surat warga`,
|
|
}
|
|
})
|
|
.post("/get-no-pengajuan", async ({ body }) => {
|
|
const { phone, noPengajuan } = body;
|
|
|
|
if (!isValidPhone(phone)) {
|
|
return { success: false, message: 'nomor telepon tidak valid, harap masukkan nomor yang benar' }
|
|
}
|
|
|
|
const nomorHP = normalizePhoneNumber({ phone: phone })
|
|
const data = await prisma.pelayananAjuan.findMany({
|
|
where: {
|
|
noPengajuan: noPengajuan,
|
|
Warga: {
|
|
phone: nomorHP
|
|
}
|
|
},
|
|
select: {
|
|
id: true,
|
|
noPengajuan: true,
|
|
status: true,
|
|
createdAt: true,
|
|
}
|
|
});
|
|
|
|
if (data.length == 0) {
|
|
return { success: false, message: 'Data pengajuan tidak ditemukan' };
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
nomer: noPengajuan
|
|
};
|
|
|
|
}, {
|
|
body: t.Object({
|
|
phone: t.String({ minLength: 1, error: "Nomor telepon harus diisi" }),
|
|
noPengajuan: t.String({ minLength: 1, error: "Nomor pengajuan harus diisi" }),
|
|
}),
|
|
detail: {
|
|
summary: "Cek Nomor Pengajuan Surat",
|
|
description: `tool untuk memeriksa apakah nomor pengajuan surat valid dan terkait dengan nomor telepon warga. Jika valid, mengembalikan nomor pengajuan.`,
|
|
}
|
|
})
|
|
|
|
export default PelayananRoute
|