699 lines
20 KiB
TypeScript
699 lines
20 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"
|
|
|
|
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"
|
|
}
|
|
})
|
|
return data
|
|
}, {
|
|
detail: {
|
|
summary: "List Kategori Pelayanan Surat",
|
|
description: `tool untuk mendapatkan list kategori pelayanan surat beserta syaratnya untuk memenuhi syarat dokumen sesuai kategori yg dipilih saat melakukan 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`
|
|
}
|
|
})
|
|
|
|
|
|
// --- PELAYANAN SURAT ---
|
|
.get("/", async ({ query }) => {
|
|
const { phone } = query
|
|
const data = await prisma.pelayananAjuan.findMany({
|
|
orderBy: {
|
|
createdAt: "asc"
|
|
},
|
|
where: {
|
|
isActive: true,
|
|
Warga: {
|
|
phone
|
|
}
|
|
}
|
|
})
|
|
return data
|
|
}, {
|
|
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: {
|
|
OR: [
|
|
{
|
|
noPengajuan: id
|
|
},
|
|
{
|
|
id: id
|
|
}
|
|
]
|
|
},
|
|
select: {
|
|
id: true,
|
|
noPengajuan: true,
|
|
status: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
CategoryPelayanan: {
|
|
select: {
|
|
name: true,
|
|
dataText: true,
|
|
syaratDokumen: true,
|
|
}
|
|
},
|
|
Warga: {
|
|
select: {
|
|
name: true,
|
|
phone: true,
|
|
_count: {
|
|
select: {
|
|
Pengaduan: true,
|
|
PelayananAjuan: true,
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
})
|
|
|
|
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 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;
|
|
}[];
|
|
|
|
const dataSyaratFix = dataSyarat.map((item) => {
|
|
const desc = syaratDokumen.find((v) => v.name == item.jenis)?.desc
|
|
return {
|
|
id: item.id,
|
|
jenis: desc,
|
|
value: item.value,
|
|
}
|
|
})
|
|
|
|
const dataTextFix = dataText.map((item) => {
|
|
const desc = data?.CategoryPelayanan?.dataText.find((v) => v == item.jenis)
|
|
return {
|
|
id: item.id,
|
|
jenis: item.jenis,
|
|
value: item.value,
|
|
}
|
|
})
|
|
|
|
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 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,
|
|
}
|
|
|
|
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",
|
|
description: `tool untuk mendapatkan detail ajuan pelayanan surat`,
|
|
tags: ["mcp"]
|
|
}
|
|
})
|
|
.post("/create", async ({ body, headers }) => {
|
|
const { kategoriId, dataText, syaratDokumen } = 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(noTelepon)) {
|
|
return { success: false, message: 'nomor telepon tidak valid, harap masukkan nomor yang benar' }
|
|
}
|
|
|
|
const nomorHP = normalizePhoneNumber({ phone: noTelepon })
|
|
const dataWarga = await prisma.warga.upsert({
|
|
where: {
|
|
phone: nomorHP
|
|
},
|
|
create: {
|
|
name: namaWarga,
|
|
phone: nomorHP,
|
|
},
|
|
update: {
|
|
name: namaWarga,
|
|
},
|
|
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) {
|
|
console.log('syarat dokumen', item)
|
|
const jenisFix = (category?.syaratDokumen as Array<{ name: string; desc: string }>)
|
|
?.find((v) => v.name === item.jenis || v.desc === item.jenis);
|
|
console.log('jenis fix', jenisFix)
|
|
dataInsertSyaratDokumen.push({
|
|
idPengajuanLayanan: pengaduan.id,
|
|
idCategory: idCategoryFix,
|
|
jenis: item.jenis,
|
|
value: item.value,
|
|
})
|
|
}
|
|
|
|
for (const item of dataText) {
|
|
console.log('dataitem', item)
|
|
const jenisFix = category?.dataText.find((v) => v.toLowerCase() == item.jenis.toLowerCase())
|
|
console.log('data text fix', jenisFix)
|
|
dataInsertDataText.push({
|
|
idPengajuanLayanan: pengaduan.id,
|
|
idCategory: idCategoryFix,
|
|
jenis: item.jenis,
|
|
value: item.value,
|
|
})
|
|
}
|
|
|
|
console.log('datainsertsyaratdokumen', dataInsertSyaratDokumen)
|
|
console.log('datainsertdatatext', dataInsertDataText)
|
|
|
|
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' }
|
|
}, {
|
|
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"
|
|
}),
|
|
// namaWarga: t.String({
|
|
// description: "Nama warga",
|
|
// examples: ["Budi Santoso"],
|
|
// error: "Nama warga harus diisi"
|
|
// }),
|
|
|
|
// noTelepon: t.String({
|
|
// error: "Nomor telepon harus diisi",
|
|
// examples: ["08123456789", "+628123456789"],
|
|
// description: "Nomor telepon warga pelapor"
|
|
// }),
|
|
|
|
dataText: t.Array(
|
|
t.Object({
|
|
jenis: 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: [
|
|
[
|
|
{ jenis: "nama", value: "Budi Santoso" },
|
|
{ jenis: "jenis kelamin", value: "Laki-laki" },
|
|
{ jenis: "tempat tanggal lahir", value: "Denpasar, 28 Februari 1990" },
|
|
{ jenis: "negara", value: "Indonesia" },
|
|
{ jenis: "agama", value: "Islam" },
|
|
{ jenis: "status perkawinan", value: "Belum menikah" },
|
|
{ jenis: "alamat", value: "Jl. Mawar No. 10" },
|
|
{ jenis: "pekerjaan", value: "Karyawan Swasta" },
|
|
{ jenis: "jenis usaha", value: "usaha makanan" },
|
|
{ jenis: "alamat usaha", value: "Jl. Melati No. 21" },
|
|
]
|
|
],
|
|
error: "dataText harus berupa array"
|
|
}
|
|
),
|
|
|
|
syaratDokumen: t.Array(
|
|
t.Object({
|
|
jenis: 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: [
|
|
[
|
|
{ jenis: "pengantar kelian", value: "pengantar_kelurahan_budi.png" },
|
|
{ jenis: "ktp/kk", value: "kk_budi.png" },
|
|
{ jenis: "foto lokasi", value: "foto_lokasi_budi.png" }
|
|
]
|
|
],
|
|
error: "syaratDokumen 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`,
|
|
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' }
|
|
}
|
|
|
|
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,
|
|
}
|
|
})
|
|
|
|
if (status === "selesai") {
|
|
await createSurat({ idPengajuan: pengajuan.id, idCategory: pengajuan.idCategory, idWarga: pengajuan.idWarga, noSurat })
|
|
}
|
|
|
|
return { success: true, message: 'pengajuan surat sudah diperbarui' }
|
|
}, {
|
|
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`,
|
|
tags: ["mcp"]
|
|
}
|
|
})
|
|
.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`,
|
|
}
|
|
})
|
|
|
|
export default PelayananRoute
|