done approval -> next testing

This commit is contained in:
JokoPrasetio 2026-06-18 17:19:44 +07:00
parent 0ee7b75460
commit 798455de16
10 changed files with 1167 additions and 125 deletions

View File

@ -17,6 +17,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Mockery\Matcher\Not;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use setasign\Fpdi\Fpdi;
@ -1330,20 +1331,36 @@ class DashboardController extends Controller
$isAtasan = MappingUnitKerjaPegawai::where('statusenabled', true)->where(function($q){
$q->where('objectatasanlangsungfk', auth()->user()?->dataUser?->id)->orWhere('objectpejabatpenilaifk', auth()->user()->objectpegawaifk);
})->exists();
$objectUnit = MappingUnitKerjaPegawai::query()
->where('statusenabled', true)
->where('objectpegawaifk', auth()->user()->objectpegawaifk)
->pluck('objectunitkerjapegawaifk')
->unique()
->values()
->all();
$isKomiteMutu = in_array(51, $objectUnit);
$isTurt = in_array(22, $objectUnit);
foreach ($datas as $index => $data) {
if(isset(explode('/', $data['id_unit_kerja'],2)[1])){
list($id_unit_kerja, $nama_unit_kerja) = explode('/', $data['id_unit_kerja'],2);
}else{
$nama_unit_kerja = UnitKerja::where('id', $data['id_unit_kerja'])->first()->name;
$id_unit_kerja = $data['id_unit_kerja'];
}
list($id_sub_unit_kerja, $nama_sub_unit_kerja) = explode('/', $data['id_sub_unit_kerja'],2);
if($data['master_kategori_directory_id']){
list($master_kategori_directory_id, $nama_kategori) = explode('/', $data['master_kategori_directory_id'],2);
}
if(isset($data['akre'])){
$path = $data['akre'];
}else{
if(isset($data['master_kategori_directory_id'])){
list($master_kategori_directory_id, $nama_kategori) = explode('/', $data['master_kategori_directory_id'],2);
$path = "{$nama_unit_kerja}/{$nama_sub_unit_kerja}/{$nama_kategori}";
}else{
$path = "{$nama_unit_kerja}/{$nama_sub_unit_kerja}";
}
}
$uploadedFile = request()->file("data.$index.file");
if(!$uploadedFile){
throw new \RuntimeException('File wajib diunggah pada segment ke-' . ($index+1));
@ -1351,8 +1368,14 @@ class DashboardController extends Controller
if (!$uploadedFile->isValid()) {
throw new \RuntimeException('Upload file gagal pada segment ke-' . ($index+1));
}
$status = $isAtasan ? 'approved' : null;
dd($data);
$requiresMutuApproval = isset($data['akre']);
$requiresTurtApproval = !empty($master_kategori_directory_id);
$mutuStatus = $isKomiteMutu && $requiresMutuApproval ? 'approved' : null;
$turtStatus = $isTurt && $requiresTurtApproval ? 'approved' : null;
$finalApproved = $isAtasan
&& (!$requiresMutuApproval || $mutuStatus === 'approved')
&& (!$requiresTurtApproval || $turtStatus === 'approved');
$status = $finalApproved ? 'approved' : null;
$payload = [
'id_unit_kerja' => $id_unit_kerja,
'id_sub_unit_kerja' => $id_sub_unit_kerja,
@ -1368,28 +1391,34 @@ class DashboardController extends Controller
'action_by' => $status && $status === "approved" ? auth()->user()->objectpegawaifk : null,
'action_at' => $status && $status === "approved" ? now() : null,
'is_akre' => isset($data['akre']),
'kategori_hukum' => isset($data['kategori_hukum']) ? $data['kategori_hukum'] : null
'kategori_hukum' => isset($data['kategori_hukum']) ? $data['kategori_hukum'] : null,
'status_mutu' => $mutuStatus,
'action_mutu_at' => $mutuStatus === 'approved' ? now() : null,
'action_mutu_by' => $mutuStatus === 'approved' ? auth()->user()->objectpegawaifk : null,
'status_turt' => $turtStatus,
'action_turt_at' => $turtStatus === 'approved' ? now() : null,
'action_turt_by' => $turtStatus === 'approved' ? auth()->user()->objectpegawaifk : null,
];
$fd = FileDirectory::create($payload);
$disk = Storage::disk('s3');
// $disk = Storage::disk('s3');
$imageName = $this->buildStoredFileName(
$fd->nama_dokumen ?? null,
$fd->file_directory_id,
$uploadedFile->getClientOriginalExtension()
);
$disk->putFileAs(
$path,
$uploadedFile,
$imageName,
['visibility' => 'private']
);
// $disk->putFileAs(
// $path,
// $uploadedFile,
// $imageName,
// ['visibility' => 'private']
// );
$fd->update([
'file' => $path . '/' . $imageName
]);
$docNumber = $fd->no_dokumen ? 'nomor '. $fd->no_dokumen : $data['nama_dokumen'];
if(!$isAtasan){
$uploaderName = auth()->user()?->dataUser?->namalengkap ?? 'Pengguna';
$docNumber = $fd->no_dokumen ? 'nomor '. $fd->no_dokumen : $imageName;
$payloadNotification = [
'created_at' => now(),
'text_notifikasi' => "Dokumen {$docNumber} memerlukan persetujuan. Diunggah oleh {$uploaderName}.",
@ -1412,15 +1441,40 @@ class DashboardController extends Controller
}
}
if($isAtasan){
if($fd->is_akre && $fd->status_mutu !== 'approved'){
$payloadNotificationMutu = [
'created_at' => now(),
'text_notifikasi' => "Dokumen {$docNumber} memerlukan persetujuan. Diunggah oleh " . auth()->user()->dataUser->namalengkap,
'url' => '/pengajuan-file',
'is_read' => false,
'unit_id' => 51,
];
Notifkasi::create($payloadNotificationMutu);
}
if($fd->master_kategori_directory_id && $fd->status_turt !== 'approved'){
$payloadNotificationTurt = [
'created_at' => now(),
'text_notifikasi' => "Dokumen {$docNumber} memerlukan persetujuan. Diunggah oleh " . auth()->user()->dataUser->namalengkap,
'url' => '/pengajuan-file',
'is_read' => false,
'unit_id' => 22,
];
Notifkasi::create($payloadNotificationTurt);
}
}
}
DB::connection('dbDirectory')->commit();
return response()->json([
'status' => true,
'message' => 'Data berhasil disimpan',
'status_action' => $isAtasan ? 'approved' : null
'status_action' => $status
], 200);
} catch (\Throwable $th) {
// dd($th);
dd($th);
DB::connection('dbDirectory')->rollback();
return response()->json([
'status' => false,
@ -1822,7 +1876,19 @@ class DashboardController extends Controller
}
public function pendingFile(){
return view('pendingFile.index', ['title' => 'Data Persetujuan']);
$objectUnit = MappingUnitKerjaPegawai::query()
->where('statusenabled', true)
->where('objectpegawaifk', auth()->user()->objectpegawaifk)
->pluck('objectunitkerjapegawaifk')
->unique()
->values()
->all();
return view('pendingFile.index', [
'title' => 'Data Persetujuan',
'isKomiteMutu' => in_array(51, $objectUnit),
'isTurt' => in_array(22, $objectUnit),
]);
}
public function dataPendingFile(){
@ -1840,18 +1906,80 @@ class DashboardController extends Controller
->unique()
->values()
->all();
$objectUnit = MappingUnitKerjaPegawai::query()
->where('statusenabled', true)
->where('objectpegawaifk', auth()->user()->objectpegawaifk)
->pluck('objectunitkerjapegawaifk')
->unique()
->values()
->all();
$keyword = request('keyword');
$query = FileDirectory::where('statusenabled', true)
->where(function($qsa){
$qsa->where('status_action', '!=', 'approved')->orWhereNull('status_action');
$isKomiteMutu = in_array(51, $objectUnit);
$isTurt = in_array(22, $objectUnit);
// 51 / Komite Mutu
// 22 / turt
$query = FileDirectory::where('statusenabled', true);
if ($isKomiteMutu || $isTurt) {
$query->where('status_action', 'approved')
->where(function ($q) use ($isKomiteMutu, $isTurt) {
if ($isTurt) {
$q->where(function ($sub) {
$sub->whereNotNull('master_kategori_directory_id')
->where(function ($pendingTurt) {
$pendingTurt->whereNull('status_turt')
->orWhere('status_turt', 'rejected');
});
});
}
if ($isKomiteMutu) {
$method = $isTurt ? 'orWhere' : 'where';
$q->{$method}(function ($sub) {
$sub->where('is_akre', true)
->where(function ($pendingMutu) {
$pendingMutu->whereNull('status_mutu')
->orWhere('status_mutu', 'rejected');
});
});
}
});
} else {
$query->where(function ($qsa) {
$qsa->where('status_action', '!=', 'approved')
->orWhereNull('status_action')
->orWhere(function ($approvedButPending) {
$approvedButPending->where('status_action', 'approved')
->where(function ($needFurtherApproval) {
$needFurtherApproval
->where(function ($needTurt) {
$needTurt->whereNotNull('master_kategori_directory_id')
->where(function ($pendingTurt) {
$pendingTurt->whereNull('status_turt')
->orWhere('status_turt', 'rejected');
});
})
->whereIn('pegawai_id_entry', $objectpegawaifk)
->when($keyword, function ($q) use ($keyword) {
->orWhere(function ($needMutu) {
$needMutu->where('is_akre', true)
->where(function ($pendingMutu) {
$pendingMutu->whereNull('status_mutu')
->orWhere('status_mutu', 'rejected');
});
});
});
});
});
}
$query->when($keyword, function ($q) use ($keyword) {
$q->where(function ($sub) use ($keyword) {
$sub->where('nama_dokumen', 'ILIKE', "%{$keyword}%")
->orWhere('no_dokumen', 'ILIKE', "%{$keyword}%");
});
});
if(!empty($objectpegawaifk) && !$isKomiteMutu && !$isTurt){
$query->whereIn('pegawai_id_entry', $objectpegawaifk);
}
if($start){
$query->whereDate('entry_at','>=',$start);
@ -1877,9 +2005,14 @@ class DashboardController extends Controller
'tanggal_terbit' => $item->tanggal_terbit,
'permission_file' => $item->permission_file,
'status_action' => $item->status_action,
'status_mutu' => $item->status_mutu,
'status_turt' => $item->status_turt,
'mutu_revision' => $item->mutu_revision,
'turt_revision' => $item->turt_revision,
'id_unit_kerja' => $item->id_unit_kerja,
'id_sub_unit_kerja' => $item->id_sub_unit_kerja,
'master_kategori_directory_id' => $item->master_kategori_directory_id
'master_kategori_directory_id' => $item->master_kategori_directory_id,
'is_akre' => $item->is_akre,
];
});
return response()->json([
@ -1942,6 +2075,29 @@ class DashboardController extends Controller
'action_type' => 'Approved Dokumen',
];
LogActivity::create($payloadLog);
if($data->is_akre){
$payloadNotificationMutu = [
'created_at' => now(),
'text_notifikasi' => "Dokumen {$docNumber} memerlukan persetujuan. Diunggah oleh {$data->pegawai_nama_entry}.",
'url' => '/pending-file',
'is_read' => false,
'unit_id' => 51,
];
Notifkasi::create($payloadNotificationMutu);
}
if($data->master_kategori_directory_id){
$payloadNotificationTurt = [
'created_at' => now(),
'text_notifikasi' => "Dokumen {$docNumber} memerlukan persetujuan. Diunggah oleh {$data->pegawai_nama_entry}.",
'url' => '/pending-file',
'is_read' => false,
'unit_id' => 22,
];
Notifkasi::create($payloadNotificationTurt);
}
DB::connection('dbDirectory')->commit();
return response()->json([
'status' => true,
@ -2224,7 +2380,16 @@ class DashboardController extends Controller
]);
}
$pegawaiId = auth()->user()?->dataUser?->id;
$pegawaiId = auth()->user()?->objectpegawaifk;
$objectUnit = MappingUnitKerjaPegawai::query()
->where('statusenabled', true)
->where('objectpegawaifk', auth()->user()->objectpegawaifk)
->pluck('objectunitkerjapegawaifk')
->unique()
->values()
->all();
$isKomiteMutu = in_array(51, $objectUnit);
$isTurt = in_array(22, $objectUnit);
if (!$pegawaiId) {
return response()->json([
'status' => false,
@ -2233,8 +2398,16 @@ class DashboardController extends Controller
'unread' => 0
], 404);
}
$query = Notifkasi::where('pegawai_id', $pegawaiId);
$query = Notifkasi::query();
$query->where(function($q) use($pegawaiId, $objectUnit, $isKomiteMutu, $isTurt){
if($isKomiteMutu || $isTurt){
$q->orWhere(function($sub) use($objectUnit){
$sub->whereNull('pegawai_id')->whereIn('unit_id', $objectUnit);
});
}else{
$q->where('pegawai_id', $pegawaiId);
}
});
$unread = (clone $query)->where('is_read', false)->count();
$items = $query->orderBy('created_at', 'desc')
->limit(20)
@ -2266,6 +2439,15 @@ class DashboardController extends Controller
}
$pegawaiId = auth()->user()?->dataUser?->id;
$objectUnit = MappingUnitKerjaPegawai::query()
->where('statusenabled', true)
->where('objectpegawaifk', auth()->user()->objectpegawaifk)
->pluck('objectunitkerjapegawaifk')
->unique()
->values()
->all();
$isKomiteMutu = in_array(51, $objectUnit);
$isTurt = in_array(22, $objectUnit);
if (!$pegawaiId) {
return response()->json([
'status' => false,
@ -2273,8 +2455,16 @@ class DashboardController extends Controller
], 404);
}
Notifkasi::where('pegawai_id', $pegawaiId)
->where('is_read', false)
$query = Notifkasi::query();
$query->where(function($q) use($pegawaiId, $isKomiteMutu, $isTurt, $objectUnit){
if($isKomiteMutu || $isTurt){
$q->orWhere(function($sub) use($objectUnit){
$sub->whereNull('pegawai_id')->whereIn('unit_id', $objectUnit);
});
}else{
$q->where('pegawai_id', $pegawaiId);
}
})->where('is_read', false)
->update(['is_read' => true]);
return response()->json([
@ -2515,9 +2705,20 @@ class DashboardController extends Controller
$katDok = MasterKategori::where('statusenabled', true)
->select('master_kategori_directory_id', 'nama_kategori_directory')
->get();
$objectUnit = MappingUnitKerjaPegawai::query()
->where('statusenabled', true)
->where('objectpegawaifk', auth()->user()->objectpegawaifk)
->pluck('objectunitkerjapegawaifk')
->unique()
->values()
->all();
$isKomiteMutu = in_array(51, $objectUnit);
$isTurt = in_array(22, $objectUnit);
return view('pengajuanFile.index', [
'title' => 'Data Persetujuan',
'katDok' => $katDok
'katDok' => $katDok,
'isKomiteMutu' => $isKomiteMutu,
'isTurt' => $isTurt
]);
}
@ -2527,21 +2728,87 @@ class DashboardController extends Controller
$start = request('start_date');
$end = request('end_date');
$isHistory = filter_var(request('history'), FILTER_VALIDATE_BOOLEAN);
$query = FileDirectory::with('kategori')->where('statusenabled', true)
->where('pegawai_id_entry', auth()->user()->objectpegawaifk)
$mode = (string) request('mode', 'pengajuan');
$pegawaiId = auth()->user()->objectpegawaifk;
$objectUnit = MappingUnitKerjaPegawai::query()
->where('statusenabled', true)
->where('objectpegawaifk', $pegawaiId)
->pluck('objectunitkerjapegawaifk')
->unique()
->values()
->all();
$isKomiteMutu = in_array(51, $objectUnit);
$isTurt = in_array(22, $objectUnit);
$query = FileDirectory::with('kategori')->where('statusenabled', true);
if ($mode === 'persetujuan' && ($isKomiteMutu || $isTurt)) {
$query->where(function ($q) use ($isKomiteMutu, $isTurt) {
if ($isTurt) {
$q->whereNotNull('master_kategori_directory_id')
->where(function ($pendingTurt) {
$pendingTurt->whereNull('status_turt')
->orWhere('status_turt', 'rejected');
});
}
if ($isKomiteMutu) {
$method = $isTurt ? 'orWhere' : 'where';
$q->{$method}(function ($sub) {
$sub->where('is_akre', true)
->where(function ($pendingMutu) {
$pendingMutu->whereNull('status_mutu')
->orWhere('status_mutu', 'rejected');
});
});
}
})->where('status_action', 'approved');
} else {
$query->where('pegawai_id_entry', $pegawaiId)
->when($isHistory, function($q){
$q->where('status_action', 'approved');
$q->where('status_action', 'approved')
->where(function ($sub) {
$sub->whereNull('master_kategori_directory_id')
->orWhere('status_turt', 'approved');
})
->where(function ($sub) {
$sub->where('is_akre', '!=', true)
->orWhereNull('is_akre')
->orWhere('status_mutu', 'approved');
});
}, function($q){
$q->where(function($sub){
$sub->where('status_action', '!=', 'approved')
->orWhereNull('status_action');
->orWhereNull('status_action')
->orWhere(function ($approvedButPending) {
$approvedButPending->where('status_action', 'approved')
->where(function ($needFurtherApproval) {
$needFurtherApproval
->where(function ($needTurt) {
$needTurt->whereNotNull('master_kategori_directory_id')
->where(function ($pendingTurt) {
$pendingTurt->whereNull('status_turt')
->orWhere('status_turt', 'rejected');
});
})
->orderBy('entry_at','desc');
->orWhere(function ($needMutu) {
$needMutu->where('is_akre', true)
->where(function ($pendingMutu) {
$pendingMutu->whereNull('status_mutu')
->orWhere('status_mutu', 'rejected');
});
});
});
});
});
});
}
$query->orderBy('entry_at','desc');
if($keyword){
$query->where(function($q) use ($keyword){
$q->where('file', 'ILIKE', "%{$keyword}%")
->orWhere('no_dokumen', 'ILIKE', "%{$keyword}%");
->orWhere('no_dokumen', 'ILIKE', "%{$keyword}%")
->orWhere('nama_dokumen', 'ILIKE', "%{$keyword}%");
});
}
if($start){
@ -2568,6 +2835,10 @@ class DashboardController extends Controller
'permission_file' => $item->permission_file,
'status_action' => $item->status_action,
'revision' => $item->revision,
'status_mutu' => $item->status_mutu,
'status_turt' => $item->status_turt,
'mutu_revision' => $item->mutu_revision,
'turt_revision' => $item->turt_revision,
'id_unit_kerja' => $item->id_unit_kerja,
'id_sub_unit_kerja' => $item->id_sub_unit_kerja,
'master_kategori_directory_id' => $item->master_kategori_directory_id,
@ -2727,39 +2998,70 @@ class DashboardController extends Controller
}
}
}
$targetRoleNotifications = [];
$resetMutuApproval = $data->status_mutu === 'rejected';
$resetTurtApproval = $data->status_turt === 'rejected';
if ($resetMutuApproval) {
$payload['status_mutu'] = null;
$payload['mutu_revision'] = null;
$payload['action_mutu_at'] = null;
$payload['action_mutu_by'] = null;
$targetRoleNotifications[] = 51;
}
if ($resetTurtApproval) {
$payload['status_turt'] = null;
$payload['turt_revision'] = null;
$payload['action_turt_at'] = null;
$payload['action_turt_by'] = null;
$targetRoleNotifications[] = 22;
}
$payload['revision'] = null;
$payload['status_action'] = 'revised';
$payload['action_at'] = null;
$payload['action_by'] = null;
if (!empty($payload)) {
$data->update($payload);
}
$data->refresh();
$mapping = MappingUnitKerjaPegawai::where('statusenabled', true)
->where('objectpegawaifk', auth()->user()?->dataUser?->id)
->first();
$parts = array_values(array_filter(explode('/', $data->file)));
$uploaderName = auth()->user()?->dataUser?->namalengkap ?? 'Pengguna';
$docNumber = $data->no_dokumen ? 'nomor '. $data->no_dokumen : end($parts);;
$payloadNotification = [
$notifText = "Dokumen {$docNumber} telah direvisi dan memerlukan persetujuan ulang. Direvisi oleh {$uploaderName}.";
$targetRoleNotifications = array_values(array_unique($targetRoleNotifications));
if (!empty($targetRoleNotifications)) {
foreach ($targetRoleNotifications as $unitId) {
Notifkasi::create([
'created_at' => now(),
'text_notifikasi' => "Dokumen {$docNumber} telah direvisi dan memerlukan persetujuan ulang. ". "Direvisi oleh {$uploaderName}.",
'text_notifikasi' => $notifText,
'url' => '/pengajuan-file',
'is_read' => false,
'unit_id' => $unitId,
]);
}
} else {
$pegawaiTargets = array_filter([
$mapping?->objectatasanlangsungfk,
$mapping?->objectpejabatpenilaifk,
]);
foreach (array_unique($pegawaiTargets) as $pegawaiId) {
Notifkasi::create([
'created_at' => now(),
'text_notifikasi' => $notifText,
'url' => '/pending-file',
'is_read' => false,
'pegawai_id' => $mapping?->objectatasanlangsungfk,
];
Notifkasi::create($payloadNotification);
if($mapping->objectpejabatpenilaifk){
$payloadNotification = [
'created_at' => now(),
'text_notifikasi' => "Dokumen {$docNumber} telah direvisi dan memerlukan persetujuan ulang. ". "Direvisi oleh {$uploaderName}.",
'url' => '/pending-file',
'is_read' => false,
'pegawai_id' => $mapping?->objectpejabatpenilaifk,
];
Notifkasi::create($payloadNotification);
'pegawai_id' => $pegawaiId,
]);
}
}
$payloadLog = [
@ -2771,7 +3073,7 @@ class DashboardController extends Controller
'statusenabled' => true,
'nama_dokumen' => $data->nama_dokumen ?? null,
'no_dokumen' => $data->no_dokumen ?? null,
'mod_change' => null,
'mod_change' => 'Reset approval: ' . (!empty($targetRoleNotifications) ? implode(',', $targetRoleNotifications) : 'atasan'),
'id_unit_kerja' => $data ? $data->id_unit_kerja : null,
'id_sub_unit_kerja' => $data ? $data->id_sub_unit_kerja : null,
'action_type' => 'Revisi Dokumen',
@ -3230,4 +3532,340 @@ class DashboardController extends Controller
return $result;
}
public function approvePendingFileMutu(string $id){
try {
DB::connection('dbDirectory')->beginTransaction();
if (!$this->hasApprovalRole(51)) {
return response()->json([
'status' => false,
'message' => 'Anda tidak memiliki akses approval Komite Mutu'
], 403);
}
$data = FileDirectory::where('file_directory_id', $id)->first();
if(!$data){
return response()->json([
'status' => false,
'message' => 'Data tidak ditemukan'
], 404);
}
if ($data->status_action !== 'approved') {
return response()->json([
'status' => false,
'message' => 'Dokumen belum disetujui atasan.'
], 422);
}
$data->update([
'status_mutu' => 'approved',
'action_mutu_at' => now(),
'action_mutu_by' => auth()->user()->objectpegawaifk,
]);
$this->syncFinalApprovalStatus($data->fresh());
$parts = array_values(array_filter(explode('/', $data->file)));
$actionName = auth()->user()?->dataUser?->namalengkap ?? 'Pengguna';
$docNumber = $data->no_dokumen ? 'nomor '. $data->no_dokumen : end($parts);
$payloadNotification = [
'created_at' => now(),
'text_notifikasi' => "Dokumen {$docNumber} telah disetujui oleh {$actionName} Komite Mutu.",
'url' => '/',
'is_read' => false,
'pegawai_id' => $data->pegawai_id_entry,
];
Notifkasi::create($payloadNotification);
$payloadLog = [
'file_directory_id' => $data->file_directory_id,
'pegawai_id_entry' => $data->pegawai_id_entry,
'pegawai_nama_entry' => $data->pegawai_nama_entry,
'entry_at' => now(),
'file' => $data->file,
'statusenabled' => true,
'nama_dokumen' => $data->nama_dokumen ?? null,
'no_dokumen' => $data->no_dokumen ?? null,
'mod_change' => null,
'id_unit_kerja' => $data ? $data->id_unit_kerja : null,
'id_sub_unit_kerja' => $data ? $data->id_sub_unit_kerja : null,
'action_type' => 'Approved Dokumen Mutu',
];
LogActivity::create($payloadLog);
DB::connection('dbDirectory')->commit();
return response()->json([
'status' => true,
'message' => 'File berhasil di-approve'
], 200);
} catch (\Throwable $th) {
DB::connection('dbDirectory')->rollBack();
return response()->json([
'status' => false,
'message' => $th->getMessage()
], 500);
}
}
public function approvePendingFileTurt(string $id){
try {
DB::connection('dbDirectory')->beginTransaction();
if (!$this->hasApprovalRole(22)) {
return response()->json([
'status' => false,
'message' => 'Anda tidak memiliki akses approval TURT'
], 403);
}
$data = FileDirectory::where('file_directory_id', $id)->first();
if(!$data){
return response()->json([
'status' => false,
'message' => 'Data tidak ditemukan'
], 404);
}
if ($data->status_action !== 'approved') {
return response()->json([
'status' => false,
'message' => 'Dokumen belum disetujui atasan.'
], 422);
}
$data->update([
'status_turt' => 'approved',
'action_turt_at' => now(),
'action_turt_by' => auth()->user()->objectpegawaifk,
]);
$this->syncFinalApprovalStatus($data->fresh());
$parts = array_values(array_filter(explode('/', $data->file)));
$actionName = auth()->user()?->dataUser?->namalengkap ?? 'Pengguna';
$docNumber = $data->no_dokumen ? 'nomor '. $data->no_dokumen : end($parts);
$payloadNotification = [
'created_at' => now(),
'text_notifikasi' => "Dokumen {$docNumber} telah disetujui oleh {$actionName}, Tim Kerja Tata Usaha dan Rumah Tangga.",
'url' => '/',
'is_read' => false,
'pegawai_id' => $data->pegawai_id_entry,
];
Notifkasi::create($payloadNotification);
$payloadLog = [
'file_directory_id' => $data->file_directory_id,
'pegawai_id_entry' => $data->pegawai_id_entry,
'pegawai_nama_entry' => $data->pegawai_nama_entry,
'entry_at' => now(),
'file' => $data->file,
'statusenabled' => true,
'nama_dokumen' => $data->nama_dokumen ?? null,
'no_dokumen' => $data->no_dokumen ?? null,
'mod_change' => null,
'id_unit_kerja' => $data ? $data->id_unit_kerja : null,
'id_sub_unit_kerja' => $data ? $data->id_sub_unit_kerja : null,
'action_type' => 'Approved Dokumen Turt',
];
LogActivity::create($payloadLog);
DB::connection('dbDirectory')->commit();
return response()->json([
'status' => true,
'message' => 'File berhasil di-approve'
], 200);
} catch (\Throwable $th) {
DB::connection('dbDirectory')->rollBack();
return response()->json([
'status' => false,
'message' => $th->getMessage()
], 500);
}
}
public function rejectPendingFileMutu(Request $request, string $id){
try {
DB::connection('dbDirectory')->beginTransaction();
if (!$this->hasApprovalRole(51)) {
return response()->json([
'status' => false,
'message' => 'Anda tidak memiliki akses approval Komite Mutu'
], 403);
}
$data = FileDirectory::where('file_directory_id', $id)->first();
if(!$data){
return response()->json([
'status' => false,
'message' => 'Data tidak ditemukan'
], 404);
}
if ($data->status_action !== 'approved') {
return response()->json([
'status' => false,
'message' => 'Dokumen belum disetujui atasan.'
], 422);
}
$revision = trim((string) $request->input('revision', ''));
if ($revision === '') {
return response()->json([
'status' => false,
'message' => 'Catatan revisi wajib diisi'
], 422);
}
$data->update([
'status_mutu' => 'rejected',
'action_mutu_at' => now(),
'action_mutu_by' => auth()->user()->dataUser->id,
'mutu_revision' => $revision,
'revision' => 'Komite Mutu: ' . $revision,
'status_action' => 'rejected',
'action_at' => now(),
'action_by' => auth()->user()->objectpegawaifk,
]);
$parts = array_values(array_filter(explode('/', $data->file)));
$actionName = auth()->user()?->dataUser?->namalengkap ?? 'Pengguna';
$docNumber = $data->no_dokumen ? 'nomor '. $data->no_dokumen : end($parts);
$payloadNotification = [
'created_at' => now(),
'text_notifikasi' => "Dokumen {$docNumber} telah ditolak oleh {$actionName}, Komite Mutu. ". "Silakan periksa dokumen untuk melakukan perbaikan.",
'url' => '/pengajuan-file',
'is_read' => false,
// 'pegawai_id' => $mapping?->objectatasanlangsungfk,
'pegawai_id' => $data->pegawai_id_entry,
];
Notifkasi::create($payloadNotification);
$payloadLog = [
'file_directory_id' => $data->file_directory_id,
'pegawai_id_entry' => $data->pegawai_id_entry,
'pegawai_nama_entry' => $data->pegawai_nama_entry,
'entry_at' => now(),
'file' => $data->file,
'statusenabled' => true,
'nama_dokumen' => $data->nama_dokumen ?? null,
'no_dokumen' => $data->no_dokumen ?? null,
'id_unit_kerja' => $data ? $data->id_unit_kerja : null,
'id_sub_unit_kerja' => $data ? $data->id_sub_unit_kerja : null,
'action_type' => 'Rejected Dokumen Mutu',
];
LogActivity::create($payloadLog);
DB::connection('dbDirectory')->commit();
return response()->json([
'status' => true,
'message' => 'File berhasil di-reject'
], 200);
} catch (\Throwable $th) {
DB::connection('dbDirectory')->rollBack();
return response()->json([
'status' => false,
'message' => $th->getMessage()
], 500);
}
}
public function rejectPendingFileTurt(Request $request, string $id){
try {
DB::connection('dbDirectory')->beginTransaction();
if (!$this->hasApprovalRole(22)) {
return response()->json([
'status' => false,
'message' => 'Anda tidak memiliki akses approval TURT'
], 403);
}
$data = FileDirectory::where('file_directory_id', $id)->first();
if(!$data){
return response()->json([
'status' => false,
'message' => 'Data tidak ditemukan'
], 404);
}
if ($data->status_action !== 'approved') {
return response()->json([
'status' => false,
'message' => 'Dokumen belum disetujui atasan.'
], 422);
}
$revision = trim((string) $request->input('revision', ''));
if ($revision === '') {
return response()->json([
'status' => false,
'message' => 'Catatan revisi wajib diisi'
], 422);
}
$data->update([
'status_turt' => 'rejected',
'action_turt_at' => now(),
'action_turt_by' => auth()->user()->dataUser->id,
'turt_revision' => $revision,
'revision' => 'TURT: ' . $revision,
'status_action' => 'rejected',
'action_at' => now(),
'action_by' => auth()->user()->objectpegawaifk,
]);
$parts = array_values(array_filter(explode('/', $data->file)));
$actionName = auth()->user()?->dataUser?->namalengkap ?? 'Pengguna';
$docNumber = $data->no_dokumen ? 'nomor '. $data->no_dokumen : end($parts);
$payloadNotification = [
'created_at' => now(),
'text_notifikasi' => "Dokumen {$docNumber} telah ditolak oleh {$actionName}, Tim Kerja Tata Usaha dan Rumah Tangga. ". "Silakan periksa dokumen untuk melakukan perbaikan.",
'url' => '/pengajuan-file',
'is_read' => false,
// 'pegawai_id' => $mapping?->objectatasanlangsungfk,
'pegawai_id' => $data->pegawai_id_entry,
];
Notifkasi::create($payloadNotification);
$payloadLog = [
'file_directory_id' => $data->file_directory_id,
'pegawai_id_entry' => $data->pegawai_id_entry,
'pegawai_nama_entry' => $data->pegawai_nama_entry,
'entry_at' => now(),
'file' => $data->file,
'statusenabled' => true,
'nama_dokumen' => $data->nama_dokumen ?? null,
'no_dokumen' => $data->no_dokumen ?? null,
'mod_change' => $revision,
'id_unit_kerja' => $data ? $data->id_unit_kerja : null,
'id_sub_unit_kerja' => $data ? $data->id_sub_unit_kerja : null,
'action_type' => 'Rejected Dokumen Turt',
];
LogActivity::create($payloadLog);
DB::connection('dbDirectory')->commit();
return response()->json([
'status' => true,
'message' => 'File berhasil di-reject'
], 200);
} catch (\Throwable $th) {
DB::connection('dbDirectory')->rollBack();
return response()->json([
'status' => false,
'message' => $th->getMessage()
], 500);
}
}
private function syncFinalApprovalStatus(FileDirectory $data): void
{
$mustApproveMutu = (bool) $data->is_akre;
$mustApproveTurt = !empty($data->master_kategori_directory_id);
if ($data->status_action !== 'approved') {
return;
}
$mutuApproved = !$mustApproveMutu || $data->status_mutu === 'approved';
$turtApproved = !$mustApproveTurt || $data->status_turt === 'approved';
if ($mutuApproved && $turtApproved) {
$data->update([
'status_action' => 'approved',
'action_at' => now(),
'action_by' => auth()->user()->objectpegawaifk,
]);
}
}
private function hasApprovalRole(int $unitId): bool
{
return MappingUnitKerjaPegawai::query()
->where('statusenabled', true)
->where('objectpegawaifk', auth()->user()->objectpegawaifk)
->where('objectunitkerjapegawaifk', $unitId)
->exists();
}
}

View File

@ -45,7 +45,7 @@ class User extends Authenticatable
'katasandi' => 'hashed',
];
}
protected $with = ['dataUser', 'masterPersetujuan', 'akses'];
protected $with = ['dataUser'];
public function dataUser(){
return $this->belongsTo(DataUser::class, 'objectpegawaifk', 'id')->select('id', 'namalengkap');
}

View File

@ -15904,14 +15904,14 @@ body {
border-radius: 20px;
}
.body-wrapper .body-wrapper-inner {
min-height: calc(100vh - 110px);
min-height: calc(130vh - 180px);
}
.body-wrapper .container-fluid, .body-wrapper .container-sm, .body-wrapper .container-md, .body-wrapper .container-lg, .body-wrapper .container-xl, .body-wrapper .container-xxl {
max-width: 1300px;
max-width: 1600px;
margin: 0 auto;
padding: 24px;
padding: 10px;
transition: 0.2s ease-in;
padding-top: 120px;
padding-top: 90px;
}
@media (max-width: 991.98px) {
.body-wrapper .container-fluid, .body-wrapper .container-sm, .body-wrapper .container-md, .body-wrapper .container-lg, .body-wrapper .container-xl, .body-wrapper .container-xxl {

View File

@ -19,6 +19,8 @@ document.addEventListener('DOMContentLoaded', () => {
const titleEl = document.getElementById('pendingTitle');
const tabPendingEl = document.getElementById('tabPengajuan');
const tabHistoryEl = document.getElementById('tabHistory');
const isKomiteMutu = !!window.isKomiteMutu;
const isTurt = !!window.isTurt;
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
const selectedIds = new Set();
@ -63,10 +65,35 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
function statusBadge(status){
if (status === 'rejected') return '<span class="badge bg-danger">Rejected</span>';
if (status === 'revised') return '<span class="badge bg-info">Revised</span>';
return '<span class="badge bg-warning text-dark">Pending</span>';
function approvalBadge(status, label){
if (status === 'approved') return `<span class="badge bg-success">Approved ${label}</span>`;
if (status === 'rejected') return `<span class="badge bg-danger">Rejected ${label}</span>`;
return `<span class="badge bg-warning text-dark">Pending ${label}</span>`;
}
function getWorkflowStatusBadges(item){
if (item?.status_mutu === 'rejected') return [`<span class="badge bg-danger">Rejected Mutu</span>`];
if (item?.status_turt === 'rejected') return [`<span class="badge bg-danger">Rejected TURT</span>`];
if (item?.status_action === 'rejected') return [`<span class="badge bg-danger">Rejected Atasan</span>`];
if (item?.status_action === 'revised') return [`<span class="badge bg-info">Revised</span>`];
if (item?.status_action !== 'approved') {
return [`<span class="badge bg-warning text-dark">Pending Atasan</span>`];
}
const statuses = [];
if (item?.is_akre && item?.status_mutu !== 'approved') {
statuses.push(`<span class="badge bg-warning text-dark">Pending Mutu</span>`);
}
if (item?.master_kategori_directory_id && item?.status_turt !== 'approved') {
statuses.push(`<span class="badge bg-warning text-dark">Pending TURT</span>`);
}
if (statuses.length) {
return statuses;
}
return [`<span class="badge bg-success">Approved</span>`];
}
function aksesBadge(akses){
@ -81,18 +108,22 @@ document.addEventListener('DOMContentLoaded', () => {
}
function isRejected(item){
return item?.status_action === 'rejected';
return item?.status_action === 'rejected' || item?.status_mutu === 'rejected' || item?.status_turt === 'rejected';
}
function isAtasanApproved(item){
return item?.status_action === 'approved';
}
function getSelectableIdsOnPage(){
return (tableState.data || [])
.filter((item) => !isRejected(item))
.filter((item) => !isRejected(item) && !isAtasanApproved(item))
.map((item) => String(item.file_directory_id));
}
function updateSelectAllState(){
if (!selectAllCheckbox) return;
if (tableState.mode === 'history') {
if (tableState.mode === 'history' || isKomiteMutu || isTurt) {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
selectAllCheckbox.disabled = true;
@ -115,8 +146,8 @@ document.addEventListener('DOMContentLoaded', () => {
function updateSelectionUI(){
const count = selectedIds.size;
if (selectedCountEl) selectedCountEl.textContent = String(count);
if (bulkApproveBtn) bulkApproveBtn.disabled = count === 0 || tableState.mode === 'history';
if (clearSelectionBtn) clearSelectionBtn.disabled = count === 0 || tableState.mode === 'history';
if (bulkApproveBtn) bulkApproveBtn.disabled = count === 0 || tableState.mode === 'history' || isKomiteMutu || isTurt;
if (clearSelectionBtn) clearSelectionBtn.disabled = count === 0 || tableState.mode === 'history' || isKomiteMutu || isTurt;
updateSelectAllState();
}
@ -126,10 +157,10 @@ document.addEventListener('DOMContentLoaded', () => {
const tanggalTerbit = item.tanggal_terbit ? formatTanggal(item.tanggal_terbit) : '-';
const id = String(item.file_directory_id);
const rejected = isRejected(item);
const atasanApproved = isAtasanApproved(item);
const checked = selectedIds.has(id);
const aksi = `
<div class="d-flex gap-1">
<button class="btn btn-sm btn-primary" onclick="infoDok(this)"
const actions = [
`<button class="btn btn-sm btn-primary" onclick="infoDok(this)"
data-file="${item.file}"
data-fileName="${item.nama_dokumen}"
data-id="${item.file_directory_id}"
@ -137,15 +168,34 @@ document.addEventListener('DOMContentLoaded', () => {
data-tanggal_terbit="${item.tanggal_terbit || '-'}"
data-permission_file="${item.permission_file || '-'}">
<i class="fa-solid fa-eye"></i>
</button>
<button class="btn btn-sm btn-success" onclick="approvePending('${item.file_directory_id}', '${item.nama_dokumen || ''}')">
<i class="fa-solid fa-check"></i>
</button>
<button class="btn btn-sm btn-danger" onclick="rejectPending('${item.file_directory_id}', '${item.nama_dokumen || ''}')">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
`;
</button>`
];
if (isKomiteMutu || isTurt) {
if (isKomiteMutu && item.is_akre && item.status_mutu !== 'approved') {
actions.push(`<button class="btn btn-sm btn-success" onclick="approvePendingRole('mutu','${item.file_directory_id}','${item.nama_dokumen || ''}')"><i class="fa-solid fa-check"></i></button>`);
actions.push(`<button class="btn btn-sm btn-danger" onclick="rejectPendingRole('mutu','${item.file_directory_id}','${item.nama_dokumen || ''}')"><i class="fa-solid fa-xmark"></i></button>`);
}
if (isTurt && item.status_turt !== 'approved') {
actions.push(`<button class="btn btn-sm btn-success" onclick="approvePendingRole('turt','${item.file_directory_id}','${item.nama_dokumen || ''}')"><i class="fa-solid fa-check-double"></i></button>`);
actions.push(`<button class="btn btn-sm btn-danger" onclick="rejectPendingRole('turt','${item.file_directory_id}','${item.nama_dokumen || ''}')"><i class="fa-solid fa-ban"></i></button>`);
}
if (item.status_mutu === 'rejected' && item.mutu_revision) {
actions.push(`<button class="btn btn-sm btn-info" onclick="infoRejectRole('Komite Mutu', ${JSON.stringify('' + (item.mutu_revision || ''))})"><i class="fa-solid fa-circle-info"></i></button>`);
}
if (item.status_turt === 'rejected' && item.turt_revision) {
actions.push(`<button class="btn btn-sm btn-info" onclick="infoRejectRole('TURT', ${JSON.stringify('' + (item.turt_revision || ''))})"><i class="fa-solid fa-circle-info"></i></button>`);
}
} else {
if (!atasanApproved) {
actions.push(`<button class="btn btn-sm btn-success" onclick="approvePending('${item.file_directory_id}', '${item.nama_dokumen || ''}')"><i class="fa-solid fa-check"></i></button>`);
actions.push(`<button class="btn btn-sm btn-danger" onclick="rejectPending('${item.file_directory_id}', '${item.nama_dokumen || ''}')"><i class="fa-solid fa-xmark"></i></button>`);
}
}
const statusContent = (isKomiteMutu || isTurt)
? getWorkflowStatusBadges(item).join(' ')
: getWorkflowStatusBadges(item).join(' ');
return `
<tr class="${checked ? 'table-active' : ''}">
<td class="text-center">
@ -153,11 +203,11 @@ document.addEventListener('DOMContentLoaded', () => {
class="form-check-input row-select"
data-id="${id}"
${checked ? 'checked' : ''}
${rejected ? 'disabled' : ''}>
${(rejected || atasanApproved || isKomiteMutu || isTurt) ? 'disabled' : ''}>
</td>
<td class="text-center">${rejected ? '' : aksi}</td>
<td class="text-center text-nowrap">${rejected ? '' : `<div class="d-flex justify-content-center align-items-center gap-1 flex-nowrap">${actions.join('')}</div>`}</td>
<td>${safeText(item.no_dokumen)}</td>
<td>${statusBadge(item?.status_action)}</td>
<td>${statusContent}</td>
<td>${aksesBadge(item?.permission_file)}</td>
<td><a href="#" class="file-link"
data-file="${item.file}"
@ -323,7 +373,7 @@ document.addEventListener('DOMContentLoaded', () => {
tabHistoryEl.classList.toggle('d-none', tableState.mode !== 'history');
}
if (pendingBulkActionsEl) {
pendingBulkActionsEl.classList.toggle('d-none', tableState.mode === 'history');
pendingBulkActionsEl.classList.toggle('d-none', tableState.mode === 'history' || isKomiteMutu || isTurt);
}
updateSelectionUI();
}
@ -473,6 +523,117 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
window.infoRejectRole = function(label, revision){
Swal.fire({
title: `Catatan ${label}`,
text: revision || 'Tidak ada catatan revisi.',
icon: 'info',
confirmButtonText: 'Tutup'
});
}
window.approvePendingRole = function(type, id, fileName){
const endpoint = type === 'mutu'
? `/pending-file/${id}/approve-mutu`
: `/pending-file/${id}/approve-turt`;
const label = type === 'mutu' ? 'Komite Mutu' : 'TURT';
Swal.fire({
title: `Approve ${label}?`,
text: fileName || 'Dokumen akan disetujui.',
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Approve',
cancelButtonText: 'Batal',
}).then((result) => {
if (!result.isConfirmed) return;
fetch(endpoint, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': csrfToken,
'Content-Type': 'application/json'
},
}).then(async(res) => {
const data = await res.json();
if (!res.ok || !data?.status) {
throw new Error(data?.message || 'Gagal approve.');
}
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: data.message || 'Dokumen disetujui.',
timer: 1500,
showConfirmButton: false
});
fetchData();
}).catch((err) => {
Swal.fire({
icon: 'error',
title: 'Gagal',
text: err.message || 'Terjadi kesalahan.'
});
});
});
}
window.rejectPendingRole = function(type, id, fileName){
const endpoint = type === 'mutu'
? `/pending-file/${id}/reject-mutu`
: `/pending-file/${id}/reject-turt`;
const label = type === 'mutu' ? 'Komite Mutu' : 'TURT';
Swal.fire({
title: `Reject ${label}?`,
text: fileName || 'Dokumen akan ditolak.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Reject',
cancelButtonText: 'Batal',
input: 'textarea',
inputLabel: 'Catatan',
inputPlaceholder: 'Tulis alasan atau catatan revisi...',
inputAttributes: {
'aria-label': 'Catatan'
},
preConfirm: (value) => {
const revision = (value || '').trim();
if (!revision) {
Swal.showValidationMessage('Catatan revisi wajib diisi.');
}
return revision;
}
}).then((result) => {
if (!result.isConfirmed) return;
fetch(endpoint, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ revision: result.value })
}).then(async(res) => {
const data = await res.json();
if (!res.ok || !data?.status) {
throw new Error(data?.message || 'Gagal reject.');
}
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: data.message || 'Dokumen ditolak.',
timer: 1500,
showConfirmButton: false
});
fetchData();
}).catch((err) => {
Swal.fire({
icon: 'error',
title: 'Gagal',
text: err.message || 'Terjadi kesalahan.'
});
});
});
}
window.infoDok = function(e){
let fileUrl =$(e).data('file');
let noDokumen = $(e).data('no_dokumen');

View File

@ -27,6 +27,8 @@ document.addEventListener('DOMContentLoaded', () => {
const editUnitSelect = $('#edit_id_unit_kerja');
const editSubUnitSelect = $('#edit_id_sub_unit_kerja');
const selectedIds = new Set();
const isKomiteMutu = !!window.isKomiteMutu;
const isTurt = !!window.isTurt;
if (pageSizeSelect) {
const initialSize = parseInt(pageSizeSelect.value);
@ -74,7 +76,7 @@ document.addEventListener('DOMContentLoaded', () => {
function updateSelectAllState(){
if (!selectAllCheckbox) return;
if (tableState.mode === 'history') {
if (tableState.mode === 'history' || tableState.mode === 'persetujuan') {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
selectAllCheckbox.disabled = true;
@ -98,18 +100,60 @@ document.addEventListener('DOMContentLoaded', () => {
function updateSelectionUI(){
const count = selectedIds.size;
if (selectedCountEl) selectedCountEl.textContent = String(count);
if (bulkDeleteBtn) bulkDeleteBtn.disabled = count === 0 || tableState.mode === 'history';
if (clearSelectionBtn) clearSelectionBtn.disabled = count === 0 || tableState.mode === 'history';
if (bulkDeleteBtn) bulkDeleteBtn.disabled = count === 0 || tableState.mode === 'history' || tableState.mode === 'persetujuan';
if (clearSelectionBtn) clearSelectionBtn.disabled = count === 0 || tableState.mode === 'history' || tableState.mode === 'persetujuan';
updateSelectAllState();
}
function approvalBadge(status, label){
if (status === 'approved') return `<span class="badge bg-success">Approved ${label}</span>`;
if (status === 'rejected') return `<span class="badge bg-danger">Rejected ${label}</span>`;
return `<span class="badge bg-warning text-dark">Pending ${label}</span>`;
}
function getPengajuanStatusBadge(item){
if (item?.status_mutu === 'rejected') {
return `<span class="badge bg-danger">Rejected Mutu</span>`;
}
if (item?.status_turt === 'rejected') {
return `<span class="badge bg-danger">Rejected TURT</span>`;
}
if (item?.status_action === 'rejected') {
return `<span class="badge bg-danger">Rejected Atasan</span>`;
}
if (item?.status_action === 'revised') {
return `<span class="badge bg-info">Revised</span>`;
}
if (item?.status_action !== 'approved') {
return `<span class="badge bg-warning text-dark">Pending Atasan</span>`;
}
const pendingApprovals = [];
if (item?.is_akre && item?.status_mutu !== 'approved') {
pendingApprovals.push('Pending Mutu');
}
if (item?.master_kategori_directory_id && item?.status_turt !== 'approved') {
pendingApprovals.push('Pending TURT');
}
if (pendingApprovals.length) {
return pendingApprovals.map((label) => `<span class="badge bg-warning text-dark">${label}</span>`).join(' ');
}
if (item?.status_action === 'approved') {
return `<span class="badge bg-success">Approved</span>`;
}
return `<span class="badge bg-warning text-dark">Pending Atasan</span>`;
}
function buildRow(item){
let tanggal = item.entry_at ? formatTanggal(item.entry_at) : '-';
let tanggalExp = item.tgl_expired ? formatTanggal(item.tgl_expired) : '-';
let tanggalTerbit = item.tanggal_terbit ? formatTanggal(item.tanggal_terbit) : '-';
const isApproved = item?.status_action === 'approved';
const isRejected = item?.status_action === 'rejected';
const showEdit = !isApproved;
const isRejected = item?.status_action === 'rejected' || item?.status_mutu === 'rejected' || item?.status_turt === 'rejected';
const showEdit = !isApproved || isRejected;
const showInfo = isRejected;
const id = String(item.file_directory_id);
const checked = selectedIds.has(id);
@ -146,18 +190,7 @@ document.addEventListener('DOMContentLoaded', () => {
</td>
<td>${aksi}</td>
<td>${item.no_dokumen || '-'}</td>
<td>
<button class="btn btn-sm
${item?.status_action === "approved" ? 'btn-success' :
item?.status_action === "rejected" ? 'btn-danger' :
item?.status_action === "revised" ? 'btn-info' :
'btn-warning'}">
${item?.status_action === "approved" ? 'Approved' :
item?.status_action === "rejected" ? 'Rejected' :
item?.status_action === "revised" ? 'Revised' :
'Pending'}
</button>
</td>
<td>${getPengajuanStatusBadge(item)}</td>
<td>${aksesBadge(item?.permission_file)}</td>
<td><a href="#" class="file-link"
data-file="${item.file}"
@ -180,6 +213,67 @@ document.addEventListener('DOMContentLoaded', () => {
`;
}
function buildRowPersetujuan(item){
const tanggal = item.entry_at ? formatTanggal(item.entry_at) : '-';
const tanggalExp = item.tgl_expired ? formatTanggal(item.tgl_expired) : '-';
const tanggalTerbit = item.tanggal_terbit ? formatTanggal(item.tanggal_terbit) : '-';
const showMutu = isKomiteMutu && !!item.is_akre;
const showTurt = isTurt;
const actions = [
`<button type="button" class="btn btn-sm btn-primary" onclick="infoDok(this)"
data-file="${item.file}"
data-fileName="${item.nama_dokumen}"
data-id="${item.file_directory_id}"
data-no_dokumen="${item.no_dokumen || '-'}"
data-tanggal_terbit="${item.tanggal_terbit || '-'}"
data-permission_file="${item.permission_file || '-'}"><i class="fa-solid fa-eye"></i></button>`
];
if (showMutu && item.status_mutu !== 'approved') {
actions.push(`<button class="btn btn-sm btn-success" onclick="approvePersetujuan('mutu','${item.file_directory_id}','${item.nama_dokumen || ''}')"><i class="fa-solid fa-check"></i></button>`);
actions.push(`<button class="btn btn-sm btn-danger" onclick="rejectPersetujuan('mutu','${item.file_directory_id}','${item.nama_dokumen || ''}')"><i class="fa-solid fa-xmark"></i></button>`);
}
if (showTurt && item.status_turt !== 'approved') {
actions.push(`<button class="btn btn-sm btn-success" onclick="approvePersetujuan('turt','${item.file_directory_id}','${item.nama_dokumen || ''}')"><i class="fa-solid fa-check-double"></i></button>`);
actions.push(`<button class="btn btn-sm btn-danger" onclick="rejectPersetujuan('turt','${item.file_directory_id}','${item.nama_dokumen || ''}')"><i class="fa-solid fa-ban"></i></button>`);
}
if (item.status_mutu === 'rejected' && item.mutu_revision) {
actions.push(`<button class="btn btn-sm btn-info" onclick="infoRejectPersetujuan('Komite Mutu', ${JSON.stringify('' + (item.mutu_revision || ''))})"><i class="fa-solid fa-circle-info"></i></button>`);
}
if (item.status_turt === 'rejected' && item.turt_revision) {
actions.push(`<button class="btn btn-sm btn-info" onclick="infoRejectPersetujuan('TURT', ${JSON.stringify('' + (item.turt_revision || ''))})"><i class="fa-solid fa-circle-info"></i></button>`);
}
const statusParts = [];
if (item.is_akre) {
statusParts.push(approvalBadge(item.status_mutu, 'Mutu'));
}
if (item.master_kategori_directory_id) {
statusParts.push(approvalBadge(item.status_turt, 'TURT'));
}
return `
<tr>
<td class="text-center">-</td>
<td><div class="d-flex gap-1 flex-wrap">${actions.join('')}</div></td>
<td>${item.no_dokumen || '-'}</td>
<td><div class="d-flex gap-1 flex-wrap">${statusParts.join('')}</div></td>
<td>${aksesBadge(item?.permission_file)}</td>
<td><a href="#" class="file-link"
data-file="${item.file}"
data-fileName="${item.nama_dokumen}"
data-id="${item.file_directory_id}"
data-no_dokumen="${item.no_dokumen || '-'}"
data-tanggal_terbit="${item.tanggal_terbit || '-'}"
data-permission_file="${item.permission_file || '-'}">${item.nama_dokumen}</a></td>
${item.is_akre ? `<td colspan="2" class="text-center">Dokumen akreditasi</td>` : `<td>${item.name_kategori || '-'}</td><td>${item.name_unit || '-'}</td>`}
<td class="text-nowrap">${tanggalTerbit}</td>
<td class="text-nowrap">${tanggalExp}</td>
<td class="text-nowrap">${tanggal}</td>
</tr>
`;
}
function buildHistoryRow(item){
const tanggal = item.entry_at ? formatTanggal(item.entry_at) : '-';
const tanggalExp = item.tgl_expired ? formatTanggal(item.tgl_expired) : '-';
@ -300,11 +394,14 @@ document.addEventListener('DOMContentLoaded', () => {
function renderTable(){
const isHistoryMode = tableState.mode === 'history';
const isPersetujuan = tableState.mode === 'persetujuan';
const activeState = isHistoryMode ? historyState : tableState;
const pageData = activeState.data || [];
const targetBody = isHistoryMode ? tbodyHistory : tbodyPengajuan;
const colSpan = isHistoryMode ? 9 : 11;
const rowBuilder = isHistoryMode ? buildHistoryRow : buildRow;
const rowBuilder = isHistoryMode ? buildHistoryRow : (isPersetujuan ? buildRowPersetujuan : buildRow);
if (pageData.length === 0) {
targetBody.innerHTML = `
<tr>
@ -363,17 +460,20 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
if (titleEl) {
titleEl.textContent = tableState.mode === 'history' ? 'Log History' : 'Data Pengajuan';
titleEl.textContent = tableState.mode === 'history'
? 'Log History'
: (tableState.mode === 'persetujuan' ? 'Data Persetujuan' : 'Data Pengajuan');
}
document.body.dataset.pengajuanMode = tableState.mode;
if (tabPengajuanEl && tabHistoryEl) {
tabPengajuanEl.classList.toggle('d-none', tableState.mode === 'history');
tabHistoryEl.classList.toggle('d-none', tableState.mode !== 'history');
}
if (bulkActionsEl) {
bulkActionsEl.classList.toggle('d-none', tableState.mode === 'history');
bulkActionsEl.classList.toggle('d-none', tableState.mode === 'history' || tableState.mode === 'persetujuan');
}
if (deleteDataEl && tableState.mode === 'history') {
if (deleteDataEl && (tableState.mode === 'history' || tableState.mode === 'persetujuan')) {
deleteDataEl.classList.add('d-none');
}
updateSelectionUI();
@ -457,7 +557,8 @@ document.addEventListener('DOMContentLoaded', () => {
per_page: activeState.pageSize,
keyword: activeState.search || '',
start_date: activeState.startDate || '',
end_date: activeState.endDate || ''
end_date: activeState.endDate || '',
mode: tableState.mode
});
const endpoint = tableState.mode === 'history'
? `/data/log-dokumen?${params.toString()}`
@ -478,7 +579,17 @@ document.addEventListener('DOMContentLoaded', () => {
window.infoReject = function(id){
const item = getItemById(id);
const revision = item?.revision ? String(item.revision) : 'Tidak ada catatan revisi.';
const notes = [];
if (item?.revision) {
notes.push(`Atasan/Bagian Terkait: ${String(item.revision)}`);
}
if (item?.mutu_revision) {
notes.push(`Komite Mutu: ${String(item.mutu_revision)}`);
}
if (item?.turt_revision) {
notes.push(`TURT: ${String(item.turt_revision)}`);
}
const revision = notes.length ? notes.join('\n\n') : 'Tidak ada catatan revisi.';
Swal.fire({
title: 'Catatan Revisi',
text: revision,
@ -486,6 +597,119 @@ document.addEventListener('DOMContentLoaded', () => {
confirmButtonText: 'Tutup'
});
}
window.infoRejectPersetujuan = function(label, revision){
Swal.fire({
title: `Catatan ${label}`,
text: revision || 'Tidak ada catatan revisi.',
icon: 'info',
confirmButtonText: 'Tutup'
});
}
window.approvePersetujuan = function(type, id, fileName){
const endpoint = type === 'mutu'
? `/pengajuan-file/${id}/approve-mutu`
: `/pengajuan-file/${id}/approve-turt`;
const label = type === 'mutu' ? 'Komite Mutu' : 'TURT';
Swal.fire({
title: `Approve ${label}?`,
text: fileName || 'Dokumen akan disetujui.',
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Approve',
cancelButtonText: 'Batal',
}).then((result) => {
if (!result.isConfirmed) return;
fetch(endpoint, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': csrfToken,
'Content-Type': 'application/json'
},
}).then(async(res) => {
const data = await res.json();
if (!res.ok || !data?.status) {
throw new Error(data?.message || 'Gagal approve.');
}
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: data.message || 'Dokumen disetujui.',
timer: 1500,
showConfirmButton: false
});
fetchData();
}).catch((err) => {
Swal.fire({
icon: 'error',
title: 'Gagal',
text: err.message || 'Terjadi kesalahan.'
});
});
});
}
window.rejectPersetujuan = function(type, id, fileName){
const endpoint = type === 'mutu'
? `/pengajuan-file/${id}/reject-mutu`
: `/pengajuan-file/${id}/reject-turt`;
const label = type === 'mutu' ? 'Komite Mutu' : 'TURT';
Swal.fire({
title: `Reject ${label}?`,
text: fileName || 'Dokumen akan ditolak.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Reject',
cancelButtonText: 'Batal',
input: 'textarea',
inputLabel: 'Catatan',
inputPlaceholder: 'Tulis alasan atau catatan revisi...',
inputAttributes: {
'aria-label': 'Catatan'
},
preConfirm: (value) => {
const revision = (value || '').trim();
if (!revision) {
Swal.showValidationMessage('Catatan revisi wajib diisi.');
}
return revision;
}
}).then((result) => {
if (!result.isConfirmed) return;
fetch(endpoint, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
revision: result.value
})
}).then(async(res) => {
const data = await res.json();
if (!res.ok || !data?.status) {
throw new Error(data?.message || 'Gagal reject.');
}
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: data.message || 'Dokumen ditolak.',
timer: 1500,
showConfirmButton: false
});
fetchData();
}).catch((err) => {
Swal.fire({
icon: 'error',
title: 'Gagal',
text: err.message || 'Terjadi kesalahan.'
});
});
});
}
window.infoDok = function(e){
let fileUrl = $(e).data('file');
let noDokumen = $(e).data('no_dokumen')

View File

@ -200,8 +200,8 @@
<span class="small text-muted">Tampilkan</span>
<select id="tablePageSize" class="form-select form-select-sm" style="width: 80px;">
<option value="5">5</option>
<option value="10" selected>10</option>
<option value="20">20</option>
<option value="10" >10</option>
<option value="20" selected>20</option>
<option value="50">50</option>
<option value="100">100</option>
</select>

View File

@ -198,8 +198,8 @@
<span class="small text-muted">Tampilkan</span>
<select id="tablePageSize" class="form-select form-select-sm" style="width: 80px;">
<option value="5">5</option>
<option value="10" selected>10</option>
<option value="20">20</option>
<option value="10">10</option>
<option value="20" selected>20</option>
<option value="50">50</option>
<option value="100">100</option>
</select>

View File

@ -114,5 +114,9 @@
</div>
</div>
@include('pendingFile.modal.view')
<script>
window.isKomiteMutu = @json($isKomiteMutu);
window.isTurt = @json($isTurt);
</script>
<script src="{{ ver('/js/pendingFile/index.js') }}"></script>
@endsection

View File

@ -46,6 +46,11 @@
<li class="nav-item">
<button class="nav-link active" type="button" data-mode="pengajuan">Data Pengajuan</button>
</li>
@if($isKomiteMutu || $isTurt)
<li class="nav-item">
<button class="nav-link" type="button" data-mode="persetujuan">Data Persetujuan</button>
</li>
@endif
<li class="nav-item">
<button class="nav-link" type="button" data-mode="history">Log History</button>
</li>
@ -151,6 +156,8 @@
@include('dataUnit.modal.create')
<script>
window.katDok = @json($katDok);
window.isKomiteMutu = @json($isKomiteMutu);
window.isTurt = @json($isTurt);
</script>
<script src="{{ ver('/js/pengajuanFile/index.js') }}"></script>
@endsection

View File

@ -71,6 +71,10 @@ Route::middleware(['auth:admin,web'])->group(function(){
Route::get('/pengajuan-file', [DashboardController::class, 'pengajuanFile']);
Route::get('/datatable/pengajuan-file', [DashboardController::class, 'dataPengajuanFile']);
Route::post('/pengajuan-file/{id}/update', [DashboardController::class, 'updatePengajuanFile']);
Route::post('/pengajuan-file/{id}/approve-mutu', [DashboardController::class, 'approvePendingFileMutu']);
Route::post('/pengajuan-file/{id}/approve-turt', [DashboardController::class, 'approvePendingFileTurt']);
Route::post('/pengajuan-file/{id}/reject-mutu', [DashboardController::class, 'rejectPendingFileMutu']);
Route::post('/pengajuan-file/{id}/reject-turt', [DashboardController::class, 'rejectPendingFileTurt']);
// Route::middleware(['master.persetujuan'])->group(function () {
Route::get('/pending-file', [DashboardController::class, 'pendingFile']);
@ -78,6 +82,10 @@ Route::middleware(['auth:admin,web'])->group(function(){
Route::post('/pending-file/{id}/approve', [DashboardController::class, 'approvePendingFile']);
Route::post('/pending-file/approve-multiple', [DashboardController::class, 'approvePendingFileMultiple']);
Route::post('/pending-file/{id}/reject', [DashboardController::class, 'rejectPendingFile']);
Route::post('/pending-file/{id}/approve-mutu', [DashboardController::class, 'approvePendingFileMutu']);
Route::post('/pending-file/{id}/approve-turt', [DashboardController::class, 'approvePendingFileTurt']);
Route::post('/pending-file/{id}/reject-mutu', [DashboardController::class, 'rejectPendingFileMutu']);
Route::post('/pending-file/{id}/reject-turt', [DashboardController::class, 'rejectPendingFileTurt']);
Route::get('/data/count-pending', [DashboardController::class, 'countDataPending']);
Route::get('/data/count-rejected', [DashboardController::class, 'countRejectedPengajuan']);
// });