fixing bug di approval bertahap dokumen

This commit is contained in:
JokoPrasetio 2026-06-22 16:00:56 +07:00
parent f8c3816ca7
commit ca4dbf0486
8 changed files with 310 additions and 167 deletions

View File

@ -12,6 +12,7 @@ use App\Models\MasterKlasifikasi;
use App\Models\Notifkasi; use App\Models\Notifkasi;
use App\Models\SubUnitKerja; use App\Models\SubUnitKerja;
use App\Models\UnitKerja; use App\Models\UnitKerja;
use App\Models\DataUser;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -88,6 +89,41 @@ class PdfFpdiWithAlpha extends Fpdi
class DashboardController extends Controller class DashboardController extends Controller
{ {
private function attachDeleteRecommendationMeta($items, $viewerPegawaiId)
{
$collection = collect($items);
$recommenderIds = $collection->pluck('turt_by')
->filter(fn ($id) => !empty($id))
->map(fn ($id) => (int) $id)
->unique()
->values();
$recommenderNames = $recommenderIds->isEmpty()
? collect()
: DataUser::whereIn('id', $recommenderIds->all())->pluck('namalengkap', 'id');
return $collection->map(function ($item) use ($viewerPegawaiId, $recommenderNames) {
$viewerId = $viewerPegawaiId ? (int) $viewerPegawaiId : null;
$entryId = !empty($item->pegawai_id_entry) ? (int) $item->pegawai_id_entry : null;
$recommenderId = !empty($item->turt_by) ? (int) $item->turt_by : null;
$canViewRecommendation = (bool) ($item->hapus_turt ?? false)
&& !empty($item->note_hapus_turt)
&& $viewerId !== null
&& ($viewerId === $entryId || $viewerId === $recommenderId);
$item->delete_recommendation = $canViewRecommendation ? [
'visible' => true,
'note' => $item->note_hapus_turt,
'actor_name' => $recommenderNames->get($recommenderId) ?? 'TURT',
'actor_role' => 'TURT',
'created_at' => $item->turt_at,
] : null;
return $item;
});
}
// public function index(){ // public function index(){
// $katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get(); // $katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get();
// $klasifikasiDok = MasterKlasifikasi::where('statusenabled', true)->select('master_klasifikasi_directory_id', 'nama_klasifikasi_directory')->get(); // $klasifikasiDok = MasterKlasifikasi::where('statusenabled', true)->select('master_klasifikasi_directory_id', 'nama_klasifikasi_directory')->get();
@ -131,9 +167,7 @@ class DashboardController extends Controller
public function index(){ public function index(){
$katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get(); $katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get();
$authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai() $authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai()->where('isprimary', true)->first();
->where('isprimary', true)
->first();
// dd(auth()->user()?->dataUser); // dd(auth()->user()?->dataUser);
$authUnitKerja = $authMapping->objectunitkerjapegawaifk ?? null; $authUnitKerja = $authMapping->objectunitkerjapegawaifk ?? null;
$authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk ?? null; $authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk ?? null;
@ -287,6 +321,7 @@ class DashboardController extends Controller
$item->nama_kategori = $item->kategori->nama_kategori_directory ?? $item->kategori_hukum ?? null; $item->nama_kategori = $item->kategori->nama_kategori_directory ?? $item->kategori_hukum ?? null;
return $item; return $item;
}); });
$items = $this->attachDeleteRecommendationMeta($items, $pegawaiId);
$kategoriList = (clone $baseQuery)->get()->map(function($item){ $kategoriList = (clone $baseQuery)->get()->map(function($item){
if (!empty($item->kategori_hukum)) { if (!empty($item->kategori_hukum)) {
return ['id' => 'hukum:' . $item->kategori_hukum, 'label' => $item->kategori_hukum]; return ['id' => 'hukum:' . $item->kategori_hukum, 'label' => $item->kategori_hukum];
@ -1219,10 +1254,12 @@ class DashboardController extends Controller
$data = $query->orderBy('entry_at', 'desc') $data = $query->orderBy('entry_at', 'desc')
->paginate($perPage); ->paginate($perPage);
$pegawaiId = auth()->user()?->dataUser?->id;
$items = collect($data->items())->map(function($item){ $items = collect($data->items())->map(function($item){
$item->nama_kategori = $item->kategori->nama_kategori_directory ?? $item->kategori_hukum ?? null; $item->nama_kategori = $item->kategori->nama_kategori_directory ?? $item->kategori_hukum ?? null;
return $item; return $item;
}); });
$items = $this->attachDeleteRecommendationMeta($items, $pegawaiId);
$kategoriList = (clone $baseQuery)->get()->map(function($item){ $kategoriList = (clone $baseQuery)->get()->map(function($item){
if (!empty($item->kategori_hukum)) { if (!empty($item->kategori_hukum)) {
@ -1398,18 +1435,18 @@ class DashboardController extends Controller
]; ];
$fd = FileDirectory::create($payload); $fd = FileDirectory::create($payload);
$disk = Storage::disk('s3'); // $disk = Storage::disk('s3');
$imageName = $this->buildStoredFileName( $imageName = $this->buildStoredFileName(
$fd->nama_dokumen ?? null, $fd->nama_dokumen ?? null,
$fd->file_directory_id, $fd->file_directory_id,
$uploadedFile->getClientOriginalExtension() $uploadedFile->getClientOriginalExtension()
); );
$disk->putFileAs( // $disk->putFileAs(
$path, // $path,
$uploadedFile, // $uploadedFile,
$imageName, // $imageName,
['visibility' => 'private'] // ['visibility' => 'private']
); // );
$fd->update([ $fd->update([
'file' => $path . '/' . $imageName 'file' => $path . '/' . $imageName
]); ]);
@ -1933,14 +1970,14 @@ class DashboardController extends Controller
$needTurt->whereNotNull('master_kategori_directory_id') $needTurt->whereNotNull('master_kategori_directory_id')
->where(function ($pendingTurt) { ->where(function ($pendingTurt) {
$pendingTurt->whereNull('status_turt') $pendingTurt->whereNull('status_turt')
->orWhere('status_turt', 'rejected'); ->orWhereIn('status_turt', ['rejected Turt', 'revised Turt']);
}); });
}) })
->orWhere(function ($needMutu) { ->orWhere(function ($needMutu) {
$needMutu->where('is_akre', true) $needMutu->where('is_akre', true)
->where(function ($pendingMutu) { ->where(function ($pendingMutu) {
$pendingMutu->whereNull('status_mutu') $pendingMutu->whereNull('status_mutu')
->orWhere('status_mutu', 'rejected'); ->orWhereIn('status_mutu', ['rejected Mutu', 'revised Mutu']);
}); });
}); });
}); });
@ -1959,7 +1996,7 @@ class DashboardController extends Controller
$sub->whereNotNull('master_kategori_directory_id') $sub->whereNotNull('master_kategori_directory_id')
->where(function ($pendingTurt) { ->where(function ($pendingTurt) {
$pendingTurt->whereNull('status_turt') $pendingTurt->whereNull('status_turt')
->orWhere('status_turt', 'rejected'); ->orWhereIn('status_turt', ['rejected Turt', 'revised Turt']);
}); });
}); });
} }
@ -1969,7 +2006,7 @@ class DashboardController extends Controller
$sub->where('is_akre', true) $sub->where('is_akre', true)
->where(function ($pendingMutu) { ->where(function ($pendingMutu) {
$pendingMutu->whereNull('status_mutu') $pendingMutu->whereNull('status_mutu')
->orWhere('status_mutu', 'rejected'); ->orWhereIn('status_mutu', ['rejected Mutu', 'revised Mutu']);
}); });
}); });
} }
@ -2393,7 +2430,7 @@ class DashboardController extends Controller
$sub->whereNotNull('master_kategori_directory_id') $sub->whereNotNull('master_kategori_directory_id')
->where(function ($pendingTurt) { ->where(function ($pendingTurt) {
$pendingTurt->whereNull('status_turt') $pendingTurt->whereNull('status_turt')
->orWhere('status_turt', 'rejected'); ->orWhereIn('status_turt', ['revised Turt']);
}); });
}); });
} }
@ -2403,7 +2440,7 @@ class DashboardController extends Controller
$sub->where('is_akre', true) $sub->where('is_akre', true)
->where(function ($pendingMutu) { ->where(function ($pendingMutu) {
$pendingMutu->whereNull('status_mutu') $pendingMutu->whereNull('status_mutu')
->orWhere('status_mutu', 'rejected'); ->orWhereIn('status_mutu', ['revised Mutu']);
}); });
}); });
} }
@ -2846,7 +2883,7 @@ class DashboardController extends Controller
$q->whereNotNull('master_kategori_directory_id') $q->whereNotNull('master_kategori_directory_id')
->where(function ($pendingTurt) { ->where(function ($pendingTurt) {
$pendingTurt->whereNull('status_turt') $pendingTurt->whereNull('status_turt')
->orWhere('status_turt', 'rejected'); ->orWhereIn('status_turt', ['rejected Turt', 'revised Turt']);
}); });
} }
if ($isKomiteMutu) { if ($isKomiteMutu) {
@ -2855,7 +2892,7 @@ class DashboardController extends Controller
$sub->where('is_akre', true) $sub->where('is_akre', true)
->where(function ($pendingMutu) { ->where(function ($pendingMutu) {
$pendingMutu->whereNull('status_mutu') $pendingMutu->whereNull('status_mutu')
->orWhere('status_mutu', 'rejected'); ->orWhereIn('status_mutu', ['rejected Mutu', 'revised Mutu']);
}); });
}); });
} }
@ -2873,14 +2910,14 @@ class DashboardController extends Controller
$needTurt->whereNotNull('master_kategori_directory_id') $needTurt->whereNotNull('master_kategori_directory_id')
->where(function ($pendingTurt) { ->where(function ($pendingTurt) {
$pendingTurt->whereNull('status_turt') $pendingTurt->whereNull('status_turt')
->orWhere('status_turt', 'rejected'); ->orWhereIn('status_turt', ['rejected Turt', 'revised Turt']);
}); });
}) })
->orWhere(function ($needMutu) { ->orWhere(function ($needMutu) {
$needMutu->where('is_akre', true) $needMutu->where('is_akre', true)
->where(function ($pendingMutu) { ->where(function ($pendingMutu) {
$pendingMutu->whereNull('status_mutu') $pendingMutu->whereNull('status_mutu')
->orWhere('status_mutu', 'rejected'); ->orWhereIn('status_mutu', ['rejected Mutu', 'revised Mutu']);
}); });
}); });
}); });
@ -2912,14 +2949,14 @@ class DashboardController extends Controller
$needTurt->whereNotNull('master_kategori_directory_id') $needTurt->whereNotNull('master_kategori_directory_id')
->where(function ($pendingTurt) { ->where(function ($pendingTurt) {
$pendingTurt->whereNull('status_turt') $pendingTurt->whereNull('status_turt')
->orWhere('status_turt', 'rejected'); ->orWhere('status_turt', ['revised Turt', 'rejected Turt']);
}); });
}) })
->orWhere(function ($needMutu) { ->orWhere(function ($needMutu) {
$needMutu->where('is_akre', true) $needMutu->where('is_akre', true)
->where(function ($pendingMutu) { ->where(function ($pendingMutu) {
$pendingMutu->whereNull('status_mutu') $pendingMutu->whereNull('status_mutu')
->orWhere('status_mutu', 'rejected'); ->orWhereIn('status_mutu', ['revised Mutu', 'rejected Mutu']);
}); });
}); });
}); });
@ -3124,34 +3161,32 @@ class DashboardController extends Controller
} }
} }
$targetRoleNotifications = []; $targetRoleNotifications = [];
$resetMutuApproval = $data->status_mutu === 'rejected'; $resetMutuApproval = $data->status_mutu === 'rejected Mutu';
$resetTurtApproval = $data->status_turt === 'rejected'; $resetTurtApproval = $data->status_turt === 'rejected Turt';
$resetAtasanApproval = $data->status_action === 'rejected'; $resetAtasanApproval = $data->status_action === 'rejected';
// dd($resetTurtApproval, $resetMutuApproval);
if ($resetMutuApproval) { if ($resetMutuApproval) {
$payload['status_mutu'] = 'revised Mutu'; $payload['status_mutu'] = 'revised Mutu';
$payload['mutu_revision'] = null; // $payload['mutu_revision'] = null;
$payload['action_mutu_at'] = null; $payload['action_mutu_at'] = null;
$payload['action_mutu_by'] = null; $payload['action_mutu_by'] = null;
$targetRoleNotifications[] = 51; $targetRoleNotifications[] = 51;
} }
if ($resetTurtApproval) { if ($resetTurtApproval) {
$payload['status_turt'] = 'revised TURT'; $payload['status_turt'] = 'revised Turt';
$payload['turt_revision'] = null; // $payload['turt_revision'] = null;
$payload['action_turt_at'] = null; $payload['action_turt_at'] = null;
$payload['action_turt_by'] = null; $payload['action_turt_by'] = null;
$targetRoleNotifications[] = 22; $targetRoleNotifications[] = 22;
} }
if($resetAtasanApproval){ if($resetAtasanApproval){
$payload['revision'] = null; // $payload['revision'] = null;
$payload['status_action'] = 'revised'; $payload['status_action'] = 'revised';
$payload['action_at'] = null; $payload['action_at'] = null;
$payload['action_by'] = null; $payload['action_by'] = null;
} }
if (!empty($payload)) { if (!empty($payload)) {
$data->update($payload); $data->update($payload);
} }
@ -3940,14 +3975,11 @@ class DashboardController extends Controller
], 422); ], 422);
} }
$data->update([ $data->update([
'status_mutu' => 'rejected', 'status_mutu' => 'rejected Mutu',
'action_mutu_at' => now(), 'action_mutu_at' => now(),
'action_mutu_by' => auth()->user()->dataUser->id, 'action_mutu_by' => auth()->user()->dataUser->id,
'mutu_revision' => $revision, 'mutu_revision' => $revision,
'revision' => 'Komite Mutu: ' . $revision, 'revision' => 'Komite Mutu: ' . $revision
'status_action' => 'rejected',
'action_at' => now(),
'action_by' => auth()->user()->objectpegawaifk,
]); ]);
$parts = array_values(array_filter(explode('/', $data->file))); $parts = array_values(array_filter(explode('/', $data->file)));
@ -4020,14 +4052,11 @@ class DashboardController extends Controller
], 422); ], 422);
} }
$data->update([ $data->update([
'status_turt' => 'rejected', 'status_turt' => 'rejected Turt',
'action_turt_at' => now(), 'action_turt_at' => now(),
'action_turt_by' => auth()->user()->dataUser->id, 'action_turt_by' => auth()->user()->dataUser->id,
'turt_revision' => $revision, 'turt_revision' => $revision,
'revision' => 'TURT: ' . $revision, 'revision' => 'TURT: ' . $revision,
'status_action' => 'rejected',
'action_at' => now(),
'action_by' => auth()->user()->objectpegawaifk,
]); ]);
$parts = array_values(array_filter(explode('/', $data->file))); $parts = array_values(array_filter(explode('/', $data->file)));

View File

@ -66,30 +66,49 @@ document.addEventListener('DOMContentLoaded', () => {
} }
function approvalBadge(status, label){ function approvalBadge(status, label){
if (status === 'approved') return `<span class="badge bg-success">Approved ${label}</span>`; const normalized = String(status || '').toLowerCase();
if (status === 'rejected') return `<span class="badge bg-danger">Rejected ${label}</span>`; if (normalized === 'approved') return `<span class="badge bg-success">Approved ${label}</span>`;
if (normalized.startsWith('rejected')) return `<span class="badge bg-danger">Rejected ${label}</span>`;
if (normalized.startsWith('revised')) return `<span class="badge bg-info">Revised ${label}</span>`;
return `<span class="badge bg-warning text-dark">Pending ${label}</span>`; return `<span class="badge bg-warning text-dark">Pending ${label}</span>`;
} }
function getWorkflowStatusBadges(item){ function getWorkflowStatusBadges(item){
if (item?.status_mutu === 'rejected') return [`<span class="badge bg-danger">Rejected Mutu</span>`]; const actionStatus = String(item?.status_action || '').toLowerCase();
if (item?.status_turt === 'rejected') return [`<span class="badge bg-danger">Rejected TURT</span>`]; const mutuStatus = String(item?.status_mutu || '').toLowerCase();
if (item?.status_action === 'rejected') return [`<span class="badge bg-danger">Rejected Atasan</span>`]; const turtStatus = String(item?.status_turt || '').toLowerCase();
if (item?.status_action === 'revised') return [`<span class="badge bg-info">Revised</span>`];
if (item?.status_action !== 'approved') { if (actionStatus.startsWith('revised')) {
return [`<span class="badge bg-info">Revised</span>`];
}
if (actionStatus.startsWith('rejected')) {
return [`<span class="badge bg-danger">Rejected Atasan</span>`];
}
if (actionStatus !== 'approved') {
return [`<span class="badge bg-warning text-dark">Pending Atasan</span>`]; return [`<span class="badge bg-warning text-dark">Pending Atasan</span>`];
} }
const statuses = []; const statuses = [];
if (item?.is_akre && item?.status_mutu !== 'approved') {
if (item?.is_akre) {
if (mutuStatus.startsWith('rejected')) {
statuses.push(`<span class="badge bg-danger">Rejected Mutu</span>`);
} else if (mutuStatus !== 'approved') {
statuses.push(`<span class="badge bg-warning text-dark">Pending Mutu</span>`); 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) { if (item?.master_kategori_directory_id) {
if (turtStatus.startsWith('rejected')) {
statuses.push(`<span class="badge bg-danger">Rejected TURT</span>`);
} else if (turtStatus !== 'approved') {
statuses.push(`<span class="badge bg-warning text-dark">Pending TURT</span>`);
}
}
if (statuses.length > 0) {
return statuses; return statuses;
} }
@ -108,11 +127,18 @@ document.addEventListener('DOMContentLoaded', () => {
} }
function isRejected(item){ function isRejected(item){
return item?.status_action === 'rejected' || item?.status_mutu === 'rejected' || item?.status_turt === 'rejected'; return String(item?.status_action || '').toLowerCase().startsWith('rejected')
|| String(item?.status_mutu || '').toLowerCase().startsWith('rejected')
|| String(item?.status_turt || '').toLowerCase().startsWith('rejected');
} }
function isAtasanApproved(item){ function isAtasanApproved(item){
return item?.status_action === 'approved'; return String(item?.status_action || '').toLowerCase() === 'approved';
}
function canProcessRoleStatus(status){
const normalized = String(status || '').toLowerCase();
return normalized === '' || normalized.startsWith('revised');
} }
function canApproveAsAtasan(item){ function canApproveAsAtasan(item){
@ -123,6 +149,62 @@ document.addEventListener('DOMContentLoaded', () => {
return item?.can_edit_submission === true || item?.can_edit_submission === 1 || item?.can_edit_submission === '1'; return item?.can_edit_submission === true || item?.can_edit_submission === 1 || item?.can_edit_submission === '1';
} }
function hasRevisionInfo(item){
return !!(item?.revision || item?.mutu_revision || item?.turt_revision);
}
function collectRevisionNotes(item){
const notes = [];
const genericRevision = String(item?.revision || '').trim();
const mutuRevision = String(item?.mutu_revision || '').trim();
const turtRevision = String(item?.turt_revision || '').trim();
if (genericRevision) {
const lowerGeneric = genericRevision.toLowerCase();
if (!lowerGeneric.startsWith('komite mutu:') && !lowerGeneric.startsWith('turt:')) {
notes.push({ label: 'Atasan', text: genericRevision });
}
}
if (mutuRevision) {
notes.push({ label: 'Komite Mutu', text: mutuRevision });
}
if (turtRevision) {
notes.push({ label: 'TURT', text: turtRevision });
}
return notes;
}
function escapeHtml(value){
return String(value ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function buildRevisionNotesHtml(notes){
if (!notes.length) {
return '<div class="text-muted">Tidak ada catatan revisi.</div>';
}
return `
<div class="text-start" style="line-height:1.6;">
${notes.map(note => `
<div class="mb-2">
<div><strong>${escapeHtml(note.label)}</strong></div>
<div>${escapeHtml(note.text)}</div>
</div>
`).join('')}
</div>
`;
}
function getItemById(id){
return (tableState.data || []).find((row) => String(row.file_directory_id) === String(id));
}
function getSelectableIdsOnPage(){ function getSelectableIdsOnPage(){
return (tableState.data || []) return (tableState.data || [])
.filter((item) => !isRejected(item) && !isAtasanApproved(item) && canApproveAsAtasan(item)) .filter((item) => !isRejected(item) && !isAtasanApproved(item) && canApproveAsAtasan(item))
@ -169,6 +251,9 @@ document.addEventListener('DOMContentLoaded', () => {
const rejected = isRejected(item); const rejected = isRejected(item);
const atasanApproved = isAtasanApproved(item); const atasanApproved = isAtasanApproved(item);
const canAtasanApprove = canApproveAsAtasan(item); const canAtasanApprove = canApproveAsAtasan(item);
const atasanRejected = String(item?.status_action || '').toLowerCase().startsWith('rejected');
const canMutuAct = atasanApproved && isKomiteMutu && item.is_akre && canProcessRoleStatus(item.status_mutu);
const canTurtAct = atasanApproved && isTurt && item.master_kategori_directory_id && canProcessRoleStatus(item.status_turt);
const checked = selectedIds.has(id); const checked = selectedIds.has(id);
const actions = [ const actions = [
`<button class="btn btn-sm btn-primary" onclick="infoDok(this)" `<button class="btn btn-sm btn-primary" onclick="infoDok(this)"
@ -187,20 +272,14 @@ document.addEventListener('DOMContentLoaded', () => {
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-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>`); 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>`);
} }
if (atasanApproved && isKomiteMutu && item.is_akre && item.status_mutu !== 'approved') { if (canMutuAct) {
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-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>`); 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 (atasanApproved && isTurt && item.master_kategori_directory_id && item.status_turt !== 'approved') { if (canTurtAct) {
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-success" onclick="approvePendingRole('turt','${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('turt','${item.file_directory_id}','${item.nama_dokumen || ''}')"><i class="fa-solid fa-ban"></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 { } else {
if (!atasanApproved) { 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-success" onclick="approvePending('${item.file_directory_id}', '${item.nama_dokumen || ''}')"><i class="fa-solid fa-check"></i></button>`);
@ -208,9 +287,14 @@ document.addEventListener('DOMContentLoaded', () => {
} }
} }
if (rejected) { if (rejected && atasanRejected && !canMutuAct && !canTurtAct) {
actions.length = 1; actions.length = 1;
}
if (rejected) {
if (hasRevisionInfo(item)) {
actions.push(`<button class="btn btn-sm btn-info" onclick="infoRejectPending('${item.file_directory_id}')"><i class="fa-solid fa-circle-info"></i></button>`); actions.push(`<button class="btn btn-sm btn-info" onclick="infoRejectPending('${item.file_directory_id}')"><i class="fa-solid fa-circle-info"></i></button>`);
}
if (canEditSubmission(item)) { if (canEditSubmission(item)) {
actions.push(`<button class="btn btn-sm btn-primary" onclick="editRejectedFromPending('${item.file_directory_id}')"><i class="fa-solid fa-pen-to-square"></i></button>`); actions.push(`<button class="btn btn-sm btn-primary" onclick="editRejectedFromPending('${item.file_directory_id}')"><i class="fa-solid fa-pen-to-square"></i></button>`);
} }
@ -546,31 +630,11 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
} }
window.infoRejectRole = function(label, revision){
Swal.fire({
title: `Catatan ${label}`,
text: revision || 'Tidak ada catatan revisi.',
icon: 'info',
confirmButtonText: 'Tutup'
});
}
window.infoRejectPending = function(id){ window.infoRejectPending = function(id){
const item = getItemById(id); const item = getItemById(id);
const notes = [];
if (item?.revision) {
notes.push(`Atasan: ${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)}`);
}
Swal.fire({ Swal.fire({
title: 'Catatan Revisi', title: 'Catatan Revisi',
text: notes.length ? notes.join('\n\n') : 'Tidak ada catatan revisi.', html: buildRevisionNotesHtml(collectRevisionNotes(item)),
icon: 'info', icon: 'info',
confirmButtonText: 'Tutup' confirmButtonText: 'Tutup'
}); });

View File

@ -19,7 +19,6 @@ document.addEventListener('DOMContentLoaded', () => {
const selectedCountEl = document.getElementById('selectedCount'); const selectedCountEl = document.getElementById('selectedCount');
const bulkActionsEl = document.getElementById('pengajuanBulkActions'); const bulkActionsEl = document.getElementById('pengajuanBulkActions');
const deleteDataEl = document.getElementById('deleteData'); const deleteDataEl = document.getElementById('deleteData');
const pendingPersetujuanTabBadge = document.getElementById('pendingPersetujuanTabBadge');
const pendingPersetujuanNotice = document.getElementById('pendingPersetujuanNotice'); const pendingPersetujuanNotice = document.getElementById('pendingPersetujuanNotice');
const pendingPersetujuanNoticeCount = document.getElementById('pendingPersetujuanNoticeCount'); const pendingPersetujuanNoticeCount = document.getElementById('pendingPersetujuanNoticeCount');
const formCreate = document.getElementById('formFile'); const formCreate = document.getElementById('formFile');
@ -111,55 +110,137 @@ document.addEventListener('DOMContentLoaded', () => {
} }
function approvalBadge(status, label){ function approvalBadge(status, label){
if (status === 'approved') return `<span class="badge bg-success">Approved ${label}</span>`; const normalized = String(status || '').toLowerCase();
if (status === 'rejected') return `<span class="badge bg-danger">Rejected ${label}</span>`; if (normalized === 'approved') return `<span class="badge bg-success">Approved ${label}</span>`;
if (normalized.startsWith('rejected')) return `<span class="badge bg-danger">Rejected ${label}</span>`;
if (normalized.startsWith('revised')) return `<span class="badge bg-info">Revised ${label}</span>`;
return `<span class="badge bg-warning text-dark">Pending ${label}</span>`; return `<span class="badge bg-warning text-dark">Pending ${label}</span>`;
} }
function getPengajuanStatusBadge(item){ function hasRevisionInfo(item){
if (item?.status_mutu === 'rejected') { return !!(item?.revision || item?.mutu_revision || item?.turt_revision);
return `<span class="badge bg-danger">Rejected Mutu</span>`;
} }
if (item?.status_turt === 'rejected') {
return `<span class="badge bg-danger">Rejected TURT</span>`; function collectRevisionNotes(item){
const notes = [];
const genericRevision = String(item?.revision || '').trim();
const mutuRevision = String(item?.mutu_revision || '').trim();
const turtRevision = String(item?.turt_revision || '').trim();
if (genericRevision) {
const lowerGeneric = genericRevision.toLowerCase();
if (!lowerGeneric.startsWith('komite mutu:') && !lowerGeneric.startsWith('turt:')) {
notes.push({ label: 'Atasan/Bagian Terkait', text: genericRevision });
} }
if (item?.status_action === 'rejected') {
return `<span class="badge bg-danger">Rejected Atasan</span>`;
} }
if (item?.status_action === 'revised') { if (mutuRevision) {
notes.push({ label: 'Komite Mutu', text: mutuRevision });
}
if (turtRevision) {
notes.push({ label: 'TURT', text: turtRevision });
}
return notes;
}
function escapeHtml(value){
return String(value ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function buildRevisionNotesHtml(notes){
if (!notes.length) {
return '<div class="text-muted">Tidak ada catatan revisi.</div>';
}
return `
<div class="text-start" style="line-height:1.6;">
${notes.map(note => `
<div class="mb-2">
<div><strong>${escapeHtml(note.label)}</strong></div>
<div>${escapeHtml(note.text)}</div>
</div>
`).join('')}
</div>
`;
}
function isRejectedStatus(item){
return String(item?.status_action || '').toLowerCase().startsWith('rejected')
|| String(item?.status_mutu || '').toLowerCase().startsWith('rejected')
|| String(item?.status_turt || '').toLowerCase().startsWith('rejected');
}
function isApprovedStatus(status){
return String(status || '').toLowerCase() === 'approved';
}
function canProcessRoleStatus(status){
const normalized = String(status || '').toLowerCase();
return normalized === '' || normalized.startsWith('revised');
}
function getPengajuanStatusBadges(item){
const actionStatus = String(item?.status_action || '').toLowerCase();
const mutuStatus = String(item?.status_mutu || '').toLowerCase();
const turtStatus = String(item?.status_turt || '').toLowerCase();
if (actionStatus.startsWith('revised')) {
return `<span class="badge bg-info">Revised</span>`; return `<span class="badge bg-info">Revised</span>`;
} }
if (item?.status_action !== 'approved') { if (actionStatus.startsWith('rejected')) {
return `<span class="badge bg-danger">Rejected Atasan</span>`;
}
if (actionStatus !== 'approved') {
return `<span class="badge bg-warning text-dark">Pending Atasan</span>`; return `<span class="badge bg-warning text-dark">Pending Atasan</span>`;
} }
const pendingApprovals = []; const statuses = [];
if (item?.is_akre && item?.status_mutu !== 'approved') { if (item?.is_akre) {
pendingApprovals.push('Pending Mutu'); if (mutuStatus.startsWith('rejected mutu')) {
statuses.push(`<span class="badge bg-danger">Rejected Mutu</span>`);
} else if (mutuStatus === 'revised mutu') {
statuses.push(`<span class="badge bg-info text-white">Revisi Mutu</span>`);
} else if(mutuStatus === 'approved'){
statuses.push(`<span class="badge bg-success text-white">Approved Mutu</span>`);
}else{
statuses.push(`<span class="badge bg-warning text-dark">Pending Mutu</span>`);
} }
if (item?.master_kategori_directory_id && item?.status_turt !== 'approved') {
pendingApprovals.push('Pending TURT');
} }
if (pendingApprovals.length) { if (item?.master_kategori_directory_id) {
return pendingApprovals.map((label) => `<span class="badge bg-warning text-dark">${label}</span>`).join(' '); if (turtStatus.startsWith('rejected turt')) {
statuses.push(`<span class="badge bg-danger">Rejected TURT</span>`);
} else if (turtStatus === 'revised turt') {
statuses.push(`<span class="badge bg-info text-white">Revisi TURT</span>`);
} else if(turtStatus === 'approved'){
statuses.push(`<span class="badge bg-success text-white">Approved TURT</span>`);
} else{
statuses.push(`<span class="badge bg-warning text-dark">Pending TURT</span>`);
} }
if (item?.status_action === 'approved') { }
if (statuses.length > 0) {
return statuses.join(' ');
}
return `<span class="badge bg-success">Approved</span>`; return `<span class="badge bg-success">Approved</span>`;
} }
return `<span class="badge bg-warning text-dark">Pending Atasan</span>`;
}
function buildRow(item){ function buildRow(item){
let tanggal = item.entry_at ? formatTanggal(item.entry_at) : '-'; let tanggal = item.entry_at ? formatTanggal(item.entry_at) : '-';
let tanggalExp = item.tgl_expired ? formatTanggal(item.tgl_expired) : '-'; let tanggalExp = item.tgl_expired ? formatTanggal(item.tgl_expired) : '-';
let tanggalTerbit = item.tanggal_terbit ? formatTanggal(item.tanggal_terbit) : '-'; let tanggalTerbit = item.tanggal_terbit ? formatTanggal(item.tanggal_terbit) : '-';
const isApproved = item?.status_action === 'approved'; const isApproved = isApprovedStatus(item?.status_action);
const isRejected = item?.status_action === 'rejected' || item?.status_mutu === 'rejected' || item?.status_turt === 'rejected'; const isRejected = isRejectedStatus(item);
const showEdit = !isApproved || isRejected; const showEdit = isRejected;
const showInfo = isRejected; const showInfo = isRejected && hasRevisionInfo(item);
const id = String(item.file_directory_id); const id = String(item.file_directory_id);
const checked = selectedIds.has(id); const checked = selectedIds.has(id);
const aksi = ` const aksi = `
@ -195,7 +276,7 @@ document.addEventListener('DOMContentLoaded', () => {
</td> </td>
<td>${aksi}</td> <td>${aksi}</td>
<td>${item.no_dokumen || '-'}</td> <td>${item.no_dokumen || '-'}</td>
<td>${getPengajuanStatusBadge(item)}</td> <td><div class="d-flex gap-1 flex-wrap">${getPengajuanStatusBadges(item)}</div></td>
<td>${aksesBadge(item?.permission_file)}</td> <td>${aksesBadge(item?.permission_file)}</td>
<td><a href="#" class="file-link" <td><a href="#" class="file-link"
data-file="${item.file}" data-file="${item.file}"
@ -234,19 +315,16 @@ document.addEventListener('DOMContentLoaded', () => {
data-permission_file="${item.permission_file || '-'}"><i class="fa-solid fa-eye"></i></button>` data-permission_file="${item.permission_file || '-'}"><i class="fa-solid fa-eye"></i></button>`
]; ];
if (showMutu && item.status_mutu !== 'approved') { if (showMutu && canProcessRoleStatus(item.status_mutu)) {
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-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>`); 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') { if (showTurt && canProcessRoleStatus(item.status_turt)) {
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-success" onclick="approvePersetujuan('turt','${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('turt','${item.file_directory_id}','${item.nama_dokumen || ''}')"><i class="fa-solid fa-ban"></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) { if (hasRevisionInfo(item)) {
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>`); actions.push(`<button class="btn btn-sm btn-info" onclick="infoReject('${item.file_directory_id}')"><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 = []; const statusParts = [];
@ -499,10 +577,8 @@ document.addEventListener('DOMContentLoaded', () => {
function updatePendingPersetujuanIndicators(total){ function updatePendingPersetujuanIndicators(total){
const count = Number(total || 0); const count = Number(total || 0);
if (pendingPersetujuanTabBadge) { console.log(pendingPersetujuanNoticeCount);
pendingPersetujuanTabBadge.textContent = String(count);
pendingPersetujuanTabBadge.classList.toggle('d-none', count <= 0);
}
if (pendingPersetujuanNotice && pendingPersetujuanNoticeCount) { if (pendingPersetujuanNotice && pendingPersetujuanNoticeCount) {
pendingPersetujuanNoticeCount.textContent = String(count); pendingPersetujuanNoticeCount.textContent = String(count);
pendingPersetujuanNotice.classList.toggle('d-none', count <= 0); pendingPersetujuanNotice.classList.toggle('d-none', count <= 0);
@ -513,19 +589,14 @@ document.addEventListener('DOMContentLoaded', () => {
function fetchPendingPersetujuanCount(){ function fetchPendingPersetujuanCount(){
if (!(isKomiteMutu || isTurt || isAtasan)) return; if (!(isKomiteMutu || isTurt || isAtasan)) return;
const params = new URLSearchParams({ fetch('/data/count-pending', {
page: 1, headers: {
per_page: 1, 'Accept': 'application/json'
keyword: '', }
start_date: '', })
end_date: '',
mode: 'persetujuan'
});
fetch(`/datatable/pengajuan-file?${params.toString()}`)
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
updatePendingPersetujuanIndicators(data?.pagination?.total || 0); updatePendingPersetujuanIndicators(data?.count || 0);
}) })
.catch(() => { .catch(() => {
updatePendingPersetujuanIndicators(0); updatePendingPersetujuanIndicators(0);
@ -633,29 +704,9 @@ document.addEventListener('DOMContentLoaded', () => {
window.infoReject = function(id){ window.infoReject = function(id){
const item = getItemById(id); const item = getItemById(id);
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({ Swal.fire({
title: 'Catatan Revisi', title: 'Catatan Revisi',
text: revision, html: buildRevisionNotesHtml(collectRevisionNotes(item)),
icon: 'info',
confirmButtonText: 'Tutup'
});
}
window.infoRejectPersetujuan = function(label, revision){
Swal.fire({
title: `Catatan ${label}`,
text: revision || 'Tidak ada catatan revisi.',
icon: 'info', icon: 'info',
confirmButtonText: 'Tutup' confirmButtonText: 'Tutup'
}); });

View File

@ -660,6 +660,7 @@
function renderDeleteRecommendation(item){ function renderDeleteRecommendation(item){
const recommendation = item?.delete_recommendation; const recommendation = item?.delete_recommendation;
if (!recommendation?.visible || !recommendation?.note) return ''; if (!recommendation?.visible || !recommendation?.note) return '';
const actorRole = recommendation.actor_role || 'TURT'; const actorRole = recommendation.actor_role || 'TURT';
@ -1255,7 +1256,7 @@
data-input-target="expiredInput_${colCount}" data-input-target="expiredInput_${colCount}"
data-base-target="dateActive_${colCount}" data-base-target="dateActive_${colCount}"
data-preview-target="expiredPreview_${colCount}"> data-preview-target="expiredPreview_${colCount}">
<option value="">Tidak ada</option> <option value="">Selamanya</option>
<option value="1">1 Tahun</option> <option value="1">1 Tahun</option>
<option value="2">2 Tahun</option> <option value="2">2 Tahun</option>
<option value="3">3 Tahun</option> <option value="3">3 Tahun</option>

View File

@ -57,7 +57,7 @@
data-input-target="expiredInput_0" data-input-target="expiredInput_0"
data-base-target="dateActive_0" data-base-target="dateActive_0"
data-preview-target="expiredPreview_0"> data-preview-target="expiredPreview_0">
<option value="">Tidak ada</option> <option value="">Selamanya</option>
<option value="1">1 Tahun</option> <option value="1">1 Tahun</option>
<option value="2">2 Tahun</option> <option value="2">2 Tahun</option>
<option value="3">3 Tahun</option> <option value="3">3 Tahun</option>

View File

@ -58,7 +58,7 @@
<div class="small text-muted ms-md-auto" id="tableSummary"></div> <div class="small text-muted ms-md-auto" id="tableSummary"></div>
</div> </div>
<div id="tabPengajuan"> <div id="tabPengajuan">
<div class="table-responsive" style="max-height: 55vh; overflow-y:auto; overflow-x:auto;"> <div class="table-responsive" style="max-height: 75vh; overflow-y:auto; overflow-x:auto;">
<table class="table table-sm table-hover table-striped align-middle mb-0 pending-table" id="lastUpdatedTable"> <table class="table table-sm table-hover table-striped align-middle mb-0 pending-table" id="lastUpdatedTable">
<thead> <thead>
<tr> <tr>

View File

@ -135,7 +135,7 @@
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-2 mt-3" id="paginationPengajuan"></div> <div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-2 mt-3" id="paginationPengajuan"></div>
</div> </div>
<div id="tabHistory" class="d-none"> <div id="tabHistory" class="d-none">
<div class="table-responsive" style="max-height: 55vh; overflow-y:auto;"> <div class="table-responsive" style="max-height: 75vh; overflow-y:auto;">
<table class="table table-sm table-hover align-middle mb-0" id="tableHistory"> <table class="table table-sm table-hover align-middle mb-0" id="tableHistory">
<thead> <thead>
<tr> <tr>
@ -145,8 +145,6 @@
<th>Kategori</th> <th>Kategori</th>
<th>Unit</th> <th>Unit</th>
<th>Aksi</th> <th>Aksi</th>
<th>Tanggal Terbit</th>
<th>Tanggal Kedaluwarsa Dokumen</th>
<th>Tanggal Upload</th> <th>Tanggal Upload</th>
</tr> </tr>
</thead> </thead>

View File

@ -56,7 +56,7 @@
data-input-target="edit_tgl_expired" data-input-target="edit_tgl_expired"
data-base-target="edit_tanggal_terbit" data-base-target="edit_tanggal_terbit"
data-preview-target="edit_expired_preview"> data-preview-target="edit_expired_preview">
<option value="">Tidak ada</option> <option value="">Selamanya</option>
<option value="1">1 Tahun</option> <option value="1">1 Tahun</option>
<option value="2">2 Tahun</option> <option value="2">2 Tahun</option>
<option value="3">3 Tahun</option> <option value="3">3 Tahun</option>