praakreditasi/app/Http/Controllers/PitStopController.php

2252 lines
98 KiB
PHP

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpFoundation\StreamedResponse;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use App\Models\MasterPitStopPraAkre;
use App\Models\PraAkre;
use App\Models\KaryawanLuar;
use Barryvdh\DomPDF\Facade\Pdf;
class PitStopController extends Controller
{
private function dtInt($value, $default = 0)
{
$n = is_numeric($value) ? (int) $value : (int) $default;
return $n < 0 ? (int) $default : $n;
}
private function streamXlsx(string $filename, callable $buildSpreadsheet): StreamedResponse
{
$response = new StreamedResponse(function () use ($buildSpreadsheet) {
$spreadsheet = new Spreadsheet();
$buildSpreadsheet($spreadsheet);
$writer = new Xlsx($spreadsheet);
$writer->save('php://output');
});
$response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
$response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"');
return $response;
}
public function pitstop(){
$masterPitStop = MasterPitStopPraAkre::where('statusenabled', true)->orderBy('id', 'asc')->get();
$data = [
'title' => 'PitStop',
'masterPitStop' => $masterPitStop
];
return view('pitstop.index', $data);
}
public function progressUnit()
{
$totalSteps = (int) MasterPitStopPraAkre::where('statusenabled', true)->count();
return view('pitstop.progress_unit', [
'title' => 'Monitoring Pra Akreditasi',
'totalSteps' => $totalSteps,
]);
}
public function progressUnitDetail(Request $request, $unit_id)
{
$unitId = (int) $unit_id;
$unit = DB::connection('pgsql')
->table('public.unitkerjapegawai_m as ukp')
->where('ukp.statusenabled', true)
->where('ukp.id', $unitId)
->select(['ukp.id', 'ukp.name'])
->first();
abort_if(!$unit, 404);
$totalSteps = (int) MasterPitStopPraAkre::where('statusenabled', true)->count();
return view('pitstop.progress_unit_detail', [
'title' => 'Monitoring Pra Akreditasi - ' . ($unit->name ?? 'Unit'),
'unit' => $unit,
'totalSteps' => $totalSteps,
]);
}
public function progressExternalDetail(Request $request, $tipe = null)
{
// NOTE:
// - `/{tipe}` breaks for values containing "/" because many servers/frameworks decode `%2F` early.
// - Support both route param and query param (?tipe=...) for backward compatibility.
if ($tipe === null) {
$tipe = $request->query('tipe');
}
$tipe = urldecode((string) $tipe);
$tipe = trim($tipe);
abort_if($tipe === '', 404);
$totalSteps = (int) MasterPitStopPraAkre::where('statusenabled', true)->count();
$masterPitStop = MasterPitStopPraAkre::where('statusenabled', true)->get();
return view('pitstop.progress_external_detail', [
'title' => 'Monitoring Karyawan Luar - ' . $tipe,
'tipe' => $tipe,
'totalSteps' => $totalSteps,
'masterPitStop' => $masterPitStop,
]);
}
public function dataProgress(Request $request)
{
$draw = $this->dtInt($request->input('draw'), 1);
$start = $this->dtInt($request->input('start'), 0);
$length = $this->dtInt($request->input('length'), 10);
if ($length < 1) $length = 10;
if ($length > 200) $length = 200;
$search = trim((string) data_get($request->all(), 'search.value', ''));
$totalSteps = (int) MasterPitStopPraAkre::where('statusenabled', true)->count();
$perPegawai = DB::connection('pgsql')
->table('public.mappegawaijabatantounitkerja_m as mp')
->join('public.pegawai_m as pg', 'pg.id', '=', 'mp.objectpegawaifk')
->join('public.unitkerjapegawai_m as ukp', 'ukp.id', '=', 'mp.objectunitkerjapegawaifk')
->leftJoin('public.praakre as p', 'p.pegawai_id', '=', 'pg.id')
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->where('mp.statusenabled', true)
->where('mp.isprimary', true)
->where('pg.statusenabled', true)
->where('ukp.statusenabled', true)
// ->whereNotIn('pg.kategorypegawai', ['11'])
->select([
DB::raw('ukp.id as unit_id'),
DB::raw("coalesce(ukp.name, '-') as unit_name"),
DB::raw('pg.id as pegawai_id'),
DB::raw("count(distinct m.id) as lulus_count"),
])
->groupBy('ukp.id', 'ukp.name', 'pg.id');
$unitAgg = DB::connection('pgsql')
->query()
->fromSub($perPegawai, 't')
->select([
't.unit_id',
't.unit_name',
DB::raw('count(*) as total_pegawai'),
DB::raw('sum(case when t.lulus_count >= ' . $totalSteps . ' then 1 else 0 end) as pegawai_selesai'),
DB::raw('sum(t.lulus_count) as total_step_lulus'),
DB::raw('avg(t.lulus_count) as avg_step_lulus'),
])
->groupBy('t.unit_id', 't.unit_name');
$recordsTotal = DB::connection('pgsql')
->query()
->fromSub($unitAgg, 'u')
->count();
$filteredQuery = DB::connection('pgsql')->query()->fromSub($unitAgg, 'u');
if ($search !== '') {
$filteredQuery->where('u.unit_name', 'ILIKE', '%' . $search . '%');
}
$recordsFiltered = (clone $filteredQuery)->count();
$rows = $filteredQuery
->orderByDesc('pegawai_selesai')
->orderByDesc('avg_step_lulus')
->orderBy('unit_name')
->offset($start)
->limit($length)
->get();
$data = $rows->map(function ($r) use ($totalSteps) {
$totalPegawai = (int) ($r->total_pegawai ?? 0);
$pegawaiSelesai = (int) ($r->pegawai_selesai ?? 0);
$selesaiPct = $totalPegawai > 0 ? round(($pegawaiSelesai / $totalPegawai) * 100, 1) : 0;
return [
'unit_id' => (int) $r->unit_id,
'unit_name' => (string) ($r->unit_name ?? '-'),
'total_pegawai' => $totalPegawai,
'pegawai_selesai' => $pegawaiSelesai,
'selesai_pct' => $selesaiPct,
'total_step_lulus' => (int) ($r->total_step_lulus ?? 0),
];
})->values();
return response()->json([
'draw' => $draw,
'recordsTotal' => $recordsTotal,
'recordsFiltered' => $recordsFiltered,
'data' => $data,
]);
}
public function dataProgressExternal(Request $request){
$draw = $this->dtInt($request->input('draw'), 1);
$start = $this->dtInt($request->input('start'), 0);
$length = $this->dtInt($request->input('length'), 10);
if ($length < 1) $length = 10;
if ($length > 200) $length = 200;
$search = trim((string) data_get($request->all(), 'search.value', ''));
$totalSteps = (int) MasterPitStopPraAkre::where('statusenabled', true)->count();
$perPegawai = DB::connection('pgsql')
->table('public.pegawai_luar_pl as pl')
->leftJoin('public.praakre as p', function ($join) {
$join->on('p.pegawai_id', '=', 'pl.id')
->where('p.tipe_karyawan', 'luar');
})
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->select([
DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-') as tipe"),
DB::raw('pl.id as pegawai_id'),
DB::raw("count(distinct m.id) as lulus_count"),
])
->groupBy(DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-')"), 'pl.id');
$tipeAgg = DB::connection('pgsql')
->query()
->fromSub($perPegawai, 't')
->select([
't.tipe',
DB::raw('count(*) as total_pegawai'),
DB::raw('sum(case when t.lulus_count >= ' . $totalSteps . ' then 1 else 0 end) as pegawai_selesai'),
DB::raw('sum(t.lulus_count) as total_step_lulus'),
DB::raw('avg(t.lulus_count) as avg_step_lulus'),
])
->groupBy('t.tipe');
$recordsTotal = DB::connection('pgsql')
->query()
->fromSub($tipeAgg, 'u')
->count();
$filteredQuery = DB::connection('pgsql')->query()->fromSub($tipeAgg, 'u');
if ($search !== '') {
$filteredQuery->where('u.tipe', 'ILIKE', '%' . $search . '%');
}
$recordsFiltered = (clone $filteredQuery)->count();
$rows = $filteredQuery
->orderByDesc('pegawai_selesai')
->orderByDesc('avg_step_lulus')
->orderBy('tipe')
->offset($start)
->limit($length)
->get();
$data = $rows->map(function ($r) use ($totalSteps) {
$totalPegawai = (int) ($r->total_pegawai ?? 0);
$pegawaiSelesai = (int) ($r->pegawai_selesai ?? 0);
$selesaiPct = $totalPegawai > 0 ? round(($pegawaiSelesai / $totalPegawai) * 100, 1) : 0;
return [
'tipe' => (string) ($r->tipe ?? '-'),
'total_pegawai' => $totalPegawai,
'pegawai_selesai' => $pegawaiSelesai,
'selesai_pct' => $selesaiPct,
'total_step_lulus' => (int) ($r->total_step_lulus ?? 0),
'avg_step_lulus' => $totalSteps > 0 ? round((float) ($r->avg_step_lulus ?? 0), 2) : 0,
];
})->values();
return response()->json([
'draw' => $draw,
'recordsTotal' => $recordsTotal,
'recordsFiltered' => $recordsFiltered,
'data' => $data,
]);
}
public function dataProgressExternalDetail(Request $request)
{
$validator = Validator::make($request->all(), [
'tipe' => 'required|string|max:100',
'limit' => 'nullable|integer|min:1|max:500',
]);
if ($validator->fails()) {
return response()->json([
'error' => 1,
'message' => 'Validasi gagal.',
'errors' => $validator->errors(),
], 422);
}
$tipe = trim((string) $validator->validated()['tipe']);
$limit = (int) ($validator->validated()['limit'] ?? 200);
$totalSteps = (int) MasterPitStopPraAkre::where('statusenabled', true)->count();
$rows = DB::connection('pgsql')
->table('public.pegawai_luar_pl as pl')
->leftJoin('public.praakre as p', function ($join) {
$join->on('p.pegawai_id', '=', 'pl.id')
->where('p.tipe_karyawan', 'luar');
})
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->where(DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-')"), '=', $tipe)
->select([
'pl.id',
'pl.nama',
'pl.nik',
DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-') as tipe"),
DB::raw("count(distinct m.id) filter (where p.status='lulus') as lulus_count"),
])
->groupBy('pl.id', 'pl.nama', 'pl.nik', DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-')"))
->orderBy('pl.nama')
->limit($limit)
->get();
$data = $rows->map(function ($r) use ($totalSteps) {
$lulus = (int) ($r->lulus_count ?? 0);
$selesai = $totalSteps > 0 ? ($lulus >= $totalSteps ? 1 : 0) : 0;
$pct = $totalSteps > 0 ? round(($lulus / $totalSteps) * 100, 1) : 0;
return [
'id' => (int) $r->id,
'nama' => (string) ($r->nama ?? '-'),
'nik' => (string) ($r->nik ?? '-'),
'tipe' => (string) ($r->tipe ?? '-'),
'lulus_count' => $lulus,
'pct' => $pct,
'selesai' => $selesai,
];
})->values();
return response()->json([
'error' => 0,
'data' => $data,
'meta' => [
'tipe' => $tipe,
'total_steps' => $totalSteps,
'count' => $data->count(),
],
]);
}
public function monitoringPdf(Request $request)
{
$unitId = $request->query('unit_id');
$unitId = is_null($unitId) || $unitId === '' ? null : (int) $unitId;
$totalSteps = (int) MasterPitStopPraAkre::where('statusenabled', true)->count();
$generatedAt = now();
// Default: ringkasan per unit (tanpa list pegawai) supaya tidak memakan memory besar.
if (is_null($unitId)) {
$perPegawai = DB::connection('pgsql')
->table('public.mappegawaijabatantounitkerja_m as mp')
->join('public.pegawai_m as pg', 'pg.id', '=', 'mp.objectpegawaifk')
->join('public.unitkerjapegawai_m as ukp', 'ukp.id', '=', 'mp.objectunitkerjapegawaifk')
->leftJoin('public.praakre as p', 'p.pegawai_id', '=', 'pg.id')
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
// ->whereNotIn('pg.kategorypegawai', ['11'])
->where('mp.statusenabled', true)
->where('mp.isprimary', true)
->where('pg.statusenabled', true)
->where('pg.kedudukanfk', 1)
->where('ukp.statusenabled', true)
->select([
DB::raw('ukp.id as unit_id'),
DB::raw("coalesce(ukp.name, '-') as unit_name"),
DB::raw('pg.id as pegawai_id'),
DB::raw("count(distinct m.id) as lulus_count"),
])
->groupBy('ukp.id', 'ukp.name', 'pg.id');
$unitAgg = DB::connection('pgsql')
->query()
->fromSub($perPegawai, 't')
->select([
't.unit_id',
't.unit_name',
DB::raw('count(*) as total_pegawai'),
DB::raw('sum(case when t.lulus_count >= ' . $totalSteps . ' then 1 else 0 end) as pegawai_selesai'),
])
->groupBy('t.unit_id', 't.unit_name');
$rows = DB::connection('pgsql')
->query()
->fromSub($unitAgg, 'u')
->orderByDesc('pegawai_selesai')
->orderByDesc('total_pegawai')
->orderBy('unit_name')
->get();
$units = $rows->map(function ($r) {
$totalPegawai = (int) ($r->total_pegawai ?? 0);
$pegawaiSelesai = (int) ($r->pegawai_selesai ?? 0);
$pct = $totalPegawai > 0 ? round(($pegawaiSelesai / $totalPegawai) * 100, 1) : 0;
return (object) [
'unit_id' => (int) $r->unit_id,
'unit_name' => (string) ($r->unit_name ?? '-'),
'total_pegawai' => $totalPegawai,
'pegawai_selesai' => $pegawaiSelesai,
'pct' => $pct,
'pegawai' => collect(),
];
});
$pdf = Pdf::loadView('pitstop.monitoring_pdf', [
'title' => 'Monitoring Pra Akreditasi',
'generatedAt' => $generatedAt,
'totalSteps' => $totalSteps,
'units' => $units,
'isSummaryOnly' => true,
])->setPaper('a4', 'landscape');
$filename = 'monitoring-pra-akreditasi-ringkasan-' . $generatedAt->format('Ymd-Hi') . '.pdf';
return $pdf->stream($filename);
}
// Mode detail: PDF per unit (lebih aman untuk memory).
$unit = DB::connection('pgsql')
->table('public.unitkerjapegawai_m as ukp')
->where('ukp.statusenabled', true)
->where('ukp.id', $unitId)
->select(['ukp.id', 'ukp.name'])
->first();
abort_if(!$unit, 404);
$perPegawai = DB::connection('pgsql')
->table('public.mappegawaijabatantounitkerja_m as mp')
->join('public.pegawai_m as pg', 'pg.id', '=', 'mp.objectpegawaifk')
->join('public.unitkerjapegawai_m as ukp', 'ukp.id', '=', 'mp.objectunitkerjapegawaifk')
->leftJoin('public.praakre as p', 'p.pegawai_id', '=', 'pg.id')
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->where('mp.statusenabled', true)
->where('mp.isprimary', true)
->where('pg.statusenabled', true)
->where('pg.kedudukanfk', 1)
->where('ukp.statusenabled', true)
->where('ukp.id', $unitId)
// ->whereNotIn('pg.kategorypegawai', ['11'])
->select([
DB::raw('ukp.id as unit_id'),
DB::raw("coalesce(ukp.name, '-') as unit_name"),
DB::raw('pg.id as pegawai_id'),
DB::raw("coalesce(pg.namalengkap, '-') as nama"),
DB::raw("coalesce(pg.nip_pns, '-') as nip_pns"),
DB::raw("count(distinct m.id) as lulus_count"),
DB::raw("coalesce((
select string_agg(ms.nama, ', ' order by ms.id)
from public.masterpitstop ms
left join public.praakre px
on px.pegawai_id = pg.id
and px.masterpitstop_id = ms.id::text
where ms.statusenabled = true
and px.id is null
), '') as belum_selesai_steps"),
])
->groupBy('ukp.id', 'ukp.name', 'pg.id', 'pg.namalengkap', 'pg.nip_pns');
$rows = DB::connection('pgsql')
->query()
->fromSub($perPegawai, 't')
->orderBy('t.nama')
->get();
$totalPegawai = $rows->count();
$pegawaiSelesai = $rows->filter(function ($r) use ($totalSteps) {
return (int) ($r->lulus_count ?? 0) >= $totalSteps && $totalSteps > 0;
})->count();
$pct = $totalPegawai > 0 ? round(($pegawaiSelesai / $totalPegawai) * 100, 1) : 0;
$pegawai = $rows->map(function ($r) use ($totalSteps) {
$lulus = (int) ($r->lulus_count ?? 0);
$pctPegawai = $totalSteps > 0 ? round(($lulus / $totalSteps) * 100, 1) : 0;
$belum = trim((string) ($r->belum_selesai_steps ?? ''));
return (object) [
'pegawai_id' => (int) $r->pegawai_id,
'nama' => (string) ($r->nama ?? '-'),
'nip_pns' => (string) ($r->nip_pns ?? '-'),
'total_steps' => $totalSteps,
'lulus_steps' => $lulus,
'pct' => $pctPegawai,
'belum_selesai_steps' => $belum,
];
})->values();
$units = collect([
(object) [
'unit_id' => (int) $unit->id,
'unit_name' => (string) ($unit->name ?? '-'),
'total_pegawai' => $totalPegawai,
'pegawai_selesai' => $pegawaiSelesai,
'pct' => $pct,
'pegawai' => $pegawai,
],
]);
$pdf = Pdf::loadView('pitstop.monitoring_pdf', [
'title' => 'Monitoring Pra Akreditasi',
'generatedAt' => $generatedAt,
'totalSteps' => $totalSteps,
'units' => $units,
'isSummaryOnly' => false,
])->setPaper('a4', 'landscape');
$filename = 'monitoring-pra-akreditasi-unit-' . $unitId . '-' . $generatedAt->format('Ymd-Hi') . '.pdf';
return $pdf->stream($filename);
}
public function monitoringPdfExternal(Request $request)
{
$tipe = $request->query('tipe');
$tipe = is_null($tipe) ? null : trim((string) $tipe);
if ($tipe === '') $tipe = null;
$totalSteps = (int) MasterPitStopPraAkre::where('statusenabled', true)->count();
$generatedAt = now();
// Summary: per tipe (tanpa list pegawai)
if (is_null($tipe)) {
$perPegawai = DB::connection('pgsql')
->table('public.pegawai_luar_pl as pl')
->leftJoin('public.praakre as p', function ($join) {
$join->on('p.pegawai_id', '=', 'pl.id')
->where('p.tipe_karyawan', 'luar');
})
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->select([
DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-') as tipe"),
DB::raw('pl.id as pegawai_id'),
DB::raw("count(distinct m.id) as lulus_count"),
])
->groupBy(DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-')"), 'pl.id');
$tipeAgg = DB::connection('pgsql')
->query()
->fromSub($perPegawai, 't')
->select([
't.tipe',
DB::raw('count(*) as total_pegawai'),
DB::raw('sum(case when t.lulus_count >= ' . $totalSteps . ' then 1 else 0 end) as pegawai_selesai'),
])
->groupBy('t.tipe')
->orderByDesc('pegawai_selesai')
->orderByDesc('total_pegawai')
->orderBy('tipe')
->get();
$types = $tipeAgg->map(function ($r) {
return (object) [
'tipe' => (string) ($r->tipe ?? '-'),
'total_pegawai' => (int) ($r->total_pegawai ?? 0),
'pegawai_selesai' => (int) ($r->pegawai_selesai ?? 0),
'pegawai' => collect(),
];
});
$pdf = Pdf::loadView('pitstop.monitoring_pdf_external', [
'title' => 'Monitoring Karyawan Luar',
'generatedAt' => $generatedAt,
'totalSteps' => $totalSteps,
'types' => $types,
'isSummaryOnly' => true,
])->setPaper('a4', 'landscape');
$filename = 'monitoring-karyawan-luar-ringkasan-' . $generatedAt->format('Ymd-Hi') . '.pdf';
return $pdf->stream($filename);
}
// Detail per tipe
$perPegawai = DB::connection('pgsql')
->table('public.pegawai_luar_pl as pl')
->leftJoin('public.praakre as p', function ($join) {
$join->on('p.pegawai_id', '=', 'pl.id')
->where('p.tipe_karyawan', 'luar');
})
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->where(DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-')"), '=', $tipe)
->select([
'pl.id',
DB::raw("coalesce(pl.nama, '-') as nama"),
DB::raw("coalesce(pl.nik, '-') as nik"),
DB::raw("count(distinct m.id) as lulus_count"),
DB::raw("coalesce((
select string_agg(ms.nama, ', ' order by ms.id)
from public.masterpitstop ms
left join public.praakre px
on px.pegawai_id = pl.id
and px.tipe_karyawan = 'luar'
and px.masterpitstop_id = ms.id::text
where ms.statusenabled = true
and px.id is null
), '') as belum_steps"),
])
->groupBy('pl.id', 'pl.nama', 'pl.nik')
->orderBy('pl.nama')
->get();
$pegawai = $perPegawai->map(function ($r) use ($totalSteps) {
return (object) [
'nama' => (string) ($r->nama ?? '-'),
'nik' => (string) ($r->nik ?? '-'),
'lulus_steps' => (int) ($r->lulus_count ?? 0),
'total_steps' => $totalSteps,
'belum_selesai_steps' => (string) ($r->belum_steps ?? ''),
];
});
$totalPegawai = (int) $pegawai->count();
$pegawaiSelesai = $pegawai->filter(function ($p) {
return ((int) ($p->total_steps ?? 0)) > 0 && ((int) ($p->lulus_steps ?? 0)) >= ((int) ($p->total_steps ?? 0));
})->count();
$types = collect([
(object) [
'tipe' => $tipe,
'total_pegawai' => $totalPegawai,
'pegawai_selesai' => $pegawaiSelesai,
'pegawai' => $pegawai,
],
]);
$pdf = Pdf::loadView('pitstop.monitoring_pdf_external', [
'title' => 'Monitoring Karyawan Luar - ' . $tipe,
'generatedAt' => $generatedAt,
'totalSteps' => $totalSteps,
'types' => $types,
'isSummaryOnly' => false,
])->setPaper('a4', 'landscape');
$filename = 'monitoring-karyawan-luar-' . preg_replace('/[^a-zA-Z0-9_-]+/', '-', $tipe) . '-' . $generatedAt->format('Ymd-Hi') . '.pdf';
return $pdf->download($filename);
}
public function monitoringExcel(Request $request)
{
$unitId = $request->query('unit_id');
$unitId = is_null($unitId) || $unitId === '' ? null : (int) $unitId;
$totalSteps = (int) MasterPitStopPraAkre::where('statusenabled', true)->count();
$generatedAt = now();
// Summary per unit
if (is_null($unitId)) {
$perPegawai = DB::connection('pgsql')
->table('public.mappegawaijabatantounitkerja_m as mp')
->join('public.pegawai_m as pg', 'pg.id', '=', 'mp.objectpegawaifk')
->join('public.unitkerjapegawai_m as ukp', 'ukp.id', '=', 'mp.objectunitkerjapegawaifk')
->leftJoin('public.praakre as p', 'p.pegawai_id', '=', 'pg.id')
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->where('mp.statusenabled', true)
->where('mp.isprimary', true)
->where('pg.statusenabled', true)
->where('pg.kedudukanfk', 1)
->where('ukp.statusenabled', true)
// ->whereNotIn('pg.kategorypegawai', ['11'])
->select([
DB::raw('ukp.id as unit_id'),
DB::raw("coalesce(ukp.name, '-') as unit_name"),
DB::raw('pg.id as pegawai_id'),
DB::raw("count(distinct m.id) as lulus_count"),
])
->groupBy('ukp.id', 'ukp.name', 'pg.id');
$unitAgg = DB::connection('pgsql')
->query()
->fromSub($perPegawai, 't')
->select([
't.unit_id',
't.unit_name',
DB::raw('count(*) as total_pegawai'),
DB::raw('sum(case when t.lulus_count >= ' . $totalSteps . ' then 1 else 0 end) as pegawai_selesai'),
])
->groupBy('t.unit_id', 't.unit_name')
->orderBy('t.unit_name')
->get();
// Rekap karyawan (all unit)
$pegawaiRecap = DB::connection('pgsql')
->table('public.mappegawaijabatantounitkerja_m as mp')
->join('public.pegawai_m as pg', 'pg.id', '=', 'mp.objectpegawaifk')
->join('public.unitkerjapegawai_m as ukp', 'ukp.id', '=', 'mp.objectunitkerjapegawaifk')
->leftJoin('public.praakre as p', function ($join) {
$join->on('p.pegawai_id', '=', 'pg.id')
->where(function ($q) {
$q->whereNull('p.tipe_karyawan')
->orWhere('p.tipe_karyawan', 'internal');
});
})
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->where('mp.statusenabled', true)
->where('mp.isprimary', true)
->where('pg.statusenabled', true)
->where('pg.kedudukanfk', 1)
->where('ukp.statusenabled', true)
// ->whereNotIn('pg.kategorypegawai', ['11'])
->select([
DB::raw('ukp.id as unit_id'),
DB::raw("coalesce(ukp.name, '-') as unit_name"),
'pg.id',
DB::raw("coalesce(pg.namalengkap, '-') as nama"),
DB::raw("coalesce(pg.nip_pns, '-') as nip_pns"),
DB::raw("count(distinct m.id) as lulus_count"),
DB::raw("coalesce((
select string_agg(ms.nama, ', ' order by ms.id)
from public.masterpitstop ms
left join public.praakre px
on px.pegawai_id = pg.id
and (px.tipe_karyawan is null or px.tipe_karyawan = 'internal')
and px.masterpitstop_id = ms.id::text
where ms.statusenabled = true
and px.id is null
), '') as belum_steps"),
])
->groupBy('ukp.id', 'ukp.name', 'pg.id', 'pg.namalengkap', 'pg.nip_pns')
->orderBy('ukp.name')
->orderBy('pg.namalengkap')
->get();
$pegawaiIds = collect($pegawaiRecap)->pluck('id')->filter()->values()->all();
$praakreDetailAll = collect();
if (count($pegawaiIds) > 0) {
$praakreDetailAll = DB::connection('pgsql')
->table('public.praakre as p')
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->leftJoin('public.pegawai_m as pen', function ($join) {
$join->on(DB::raw('pen.id::text'), '=', 'p.action_at');
})
->whereIn('p.pegawai_id', $pegawaiIds)
->where(function ($q) {
$q->whereNull('p.tipe_karyawan')
->orWhere('p.tipe_karyawan', 'internal');
})
->whereNotNull('m.id')
->select([
'p.pegawai_id',
DB::raw("coalesce(m.nama, '-') as pitstop_nama"),
'p.nilai',
DB::raw("coalesce(pen.namalengkap, '-') as penilai_nama"),
])
->orderBy('p.pegawai_id')
->orderBy('m.id')
->orderBy('p.id')
->get();
}
$filename = 'monitoring-pra-akreditasi-ringkasan-' . $generatedAt->format('Ymd-Hi') . '.xlsx';
return $this->streamXlsx($filename, function (Spreadsheet $spreadsheet) use ($unitAgg, $pegawaiRecap, $praakreDetailAll, $totalSteps) {
// Sheet 1: Rekap Unit
$unitSheet = $spreadsheet->getActiveSheet();
$unitSheet->setTitle('Rekap Unit');
$headers = ['Nama Unit', 'Total Pitstop', 'Total Karyawan', 'Karyawan Selesai'];
foreach ($headers as $i => $h) {
$unitSheet->setCellValueByColumnAndRow($i + 1, 1, $h);
}
$unitSheet->getStyle('A1:D1')->getFont()->setBold(true);
$row = 2;
foreach ($unitAgg as $r) {
$unitSheet->setCellValueByColumnAndRow(1, $row, (string) ($r->unit_name ?? '-'));
$unitSheet->setCellValueByColumnAndRow(2, $row, (int) $totalSteps);
$unitSheet->setCellValueByColumnAndRow(3, $row, (int) ($r->total_pegawai ?? 0));
$unitSheet->setCellValueByColumnAndRow(4, $row, (int) ($r->pegawai_selesai ?? 0));
$row++;
}
foreach (range('A', 'D') as $col) {
$unitSheet->getColumnDimension($col)->setAutoSize(true);
}
// Sheet 2: Rekap Karyawan
$pegawaiSheet = $spreadsheet->createSheet();
$pegawaiSheet->setTitle('Rekap Karyawan');
$pegawaiHeaders = ['Nama Unit', 'Nama Karyawan', 'NIP', 'Lulus Pitstop', 'Total Pitstop', 'Belum Dikerjakan'];
foreach ($pegawaiHeaders as $i => $h) {
$pegawaiSheet->setCellValueByColumnAndRow($i + 1, 1, $h);
}
$pegawaiSheet->getStyle('A1:F1')->getFont()->setBold(true);
$pegawaiRow = 2;
foreach ($pegawaiRecap as $r) {
$pegawaiSheet->setCellValueByColumnAndRow(1, $pegawaiRow, (string) ($r->unit_name ?? '-'));
$pegawaiSheet->setCellValueByColumnAndRow(2, $pegawaiRow, (string) ($r->nama ?? '-'));
$pegawaiSheet->setCellValueExplicitByColumnAndRow(3, $pegawaiRow, (string) ($r->nip_pns ?? '-'), DataType::TYPE_STRING);
$pegawaiSheet->setCellValueByColumnAndRow(4, $pegawaiRow, (int) ($r->lulus_count ?? 0));
$pegawaiSheet->setCellValueByColumnAndRow(5, $pegawaiRow, (int) $totalSteps);
$pegawaiSheet->setCellValueByColumnAndRow(6, $pegawaiRow, (string) ($r->belum_steps ?? ''));
$pegawaiRow++;
}
foreach (range('A', 'F') as $col) {
$pegawaiSheet->getColumnDimension($col)->setAutoSize(true);
}
// Sheet 3: Detail PraAkre Karyawan
$detailSheet = $spreadsheet->createSheet();
$detailSheet->setTitle('Detail PraAkre');
$detailHeaders = ['Nama Unit', 'Nama Karyawan', 'NIP', 'Pitstop', 'Nilai', 'Penilai'];
foreach ($detailHeaders as $i => $h) {
$detailSheet->setCellValueByColumnAndRow($i + 1, 1, $h);
}
$detailSheet->getStyle('A1:F1')->getFont()->setBold(true);
$pegawaiById = collect($pegawaiRecap)->keyBy('id');
$detailRow = 2;
$lastPegawaiId = null;
foreach ($praakreDetailAll as $d) {
if (!is_null($lastPegawaiId) && (string) $lastPegawaiId !== (string) $d->pegawai_id) {
$detailSheet->getStyle('A' . $detailRow . ':F' . $detailRow)
->applyFromArray([
'borders' => [
'top' => [
'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN,
'color' => ['argb' => 'FF999999'],
],
],
'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
'startColor' => ['argb' => 'FF1E88E5'],
],
]);
$detailRow++;
}
$pg = $pegawaiById->get($d->pegawai_id);
$detailSheet->setCellValueByColumnAndRow(1, $detailRow, (string) data_get($pg, 'unit_name', '-'));
$detailSheet->setCellValueByColumnAndRow(2, $detailRow, (string) data_get($pg, 'nama', '-'));
$detailSheet->setCellValueExplicitByColumnAndRow(3, $detailRow, (string) data_get($pg, 'nip_pns', '-'), DataType::TYPE_STRING);
$detailSheet->setCellValueByColumnAndRow(4, $detailRow, (string) ($d->pitstop_nama ?? '-'));
$detailSheet->setCellValueByColumnAndRow(5, $detailRow, (string) ($d->nilai ?? ''));
$detailSheet->setCellValueByColumnAndRow(6, $detailRow, (string) ($d->penilai_nama ?? '-'));
$detailRow++;
$lastPegawaiId = $d->pegawai_id;
}
foreach (range('A', 'F') as $col) {
$detailSheet->getColumnDimension($col)->setAutoSize(true);
}
$spreadsheet->setActiveSheetIndex(0);
});
}
// Detail per unit
$unit = DB::connection('pgsql')
->table('public.unitkerjapegawai_m as ukp')
->where('ukp.statusenabled', true)
->where('ukp.id', $unitId)
->select(['ukp.id', 'ukp.name'])
->first();
abort_if(!$unit, 404);
$pegawaiRows = DB::connection('pgsql')
->table('public.mappegawaijabatantounitkerja_m as mp')
->join('public.pegawai_m as pg', 'pg.id', '=', 'mp.objectpegawaifk')
->join('public.unitkerjapegawai_m as ukp', 'ukp.id', '=', 'mp.objectunitkerjapegawaifk')
->leftJoin('public.praakre as p', function ($join) {
$join->on('p.pegawai_id', '=', 'pg.id')
->where(function ($q) {
$q->whereNull('p.tipe_karyawan')
->orWhere('p.tipe_karyawan', 'internal');
});
})
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->where('mp.statusenabled', true)
->where('mp.isprimary', true)
->where('pg.statusenabled', true)
->where('pg.kedudukanfk', 1)
->where('ukp.statusenabled', true)
->where('ukp.id', $unitId)
// ->whereNotIn('pg.kategorypegawai', ['11'])
->select([
'pg.id',
DB::raw("coalesce(pg.namalengkap, '-') as nama"),
DB::raw("coalesce(pg.nip_pns, '-') as nip_pns"),
DB::raw("count(distinct m.id) as lulus_count"),
DB::raw("coalesce((
select string_agg(ms.nama, ', ' order by ms.id)
from public.masterpitstop ms
left join public.praakre px
on px.pegawai_id = pg.id
and (px.tipe_karyawan is null or px.tipe_karyawan = 'internal')
and px.masterpitstop_id = ms.id::text
where ms.statusenabled = true
and px.id is null
), '') as belum_steps"),
])
->groupBy('pg.id', 'pg.namalengkap', 'pg.nip_pns')
->orderBy('pg.namalengkap')
->get();
$pegawaiIds = collect($pegawaiRows)->pluck('id')->filter()->values()->all();
$praakreRows = collect();
if (count($pegawaiIds) > 0) {
$praakreRows = DB::connection('pgsql')
->table('public.praakre as p')
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->leftJoin('public.pegawai_m as pen', function ($join) {
$join->on(DB::raw('pen.id::text'), '=', 'p.action_at');
})
->whereIn('p.pegawai_id', $pegawaiIds)
->where(function ($q) {
$q->whereNull('p.tipe_karyawan')
->orWhere('p.tipe_karyawan', 'internal');
})
->whereNotNull('m.id')
->select([
'p.pegawai_id',
DB::raw("coalesce(m.nama, '-') as pitstop_nama"),
'p.nilai',
DB::raw("coalesce(pen.namalengkap, '-') as penilai_nama"),
])
->orderBy('p.pegawai_id')
->orderBy('m.id')
->orderBy('p.id')
->get();
}
$filename = 'monitoring-pra-akreditasi-unit-' . (int) $unitId . '-' . $generatedAt->format('Ymd-Hi') . '.xlsx';
return $this->streamXlsx($filename, function (Spreadsheet $spreadsheet) use ($pegawaiRows, $praakreRows, $unit, $totalSteps) {
// Sheet 1: Rekap
$rekapSheet = $spreadsheet->getActiveSheet();
$rekapSheet->setTitle('Rekap');
$rekapHeaders = ['Nama Unit', 'Nama Karyawan', 'NIP', 'Lulus Pitstop', 'Total Pitstop', 'Belum Dikerjakan'];
foreach ($rekapHeaders as $i => $h) {
$rekapSheet->setCellValueByColumnAndRow($i + 1, 1, $h);
}
$rekapSheet->getStyle('A1:F1')->getFont()->setBold(true);
$rekapRow = 2;
foreach ($pegawaiRows as $pg) {
$rekapSheet->setCellValueByColumnAndRow(1, $rekapRow, (string) ($unit->name ?? '-'));
$rekapSheet->setCellValueByColumnAndRow(2, $rekapRow, (string) ($pg->nama ?? '-'));
$rekapSheet->setCellValueExplicitByColumnAndRow(3, $rekapRow, (string) ($pg->nip_pns ?? '-'), DataType::TYPE_STRING);
$rekapSheet->setCellValueByColumnAndRow(4, $rekapRow, (int) ($pg->lulus_count ?? 0));
$rekapSheet->setCellValueByColumnAndRow(5, $rekapRow, (int) $totalSteps);
$rekapSheet->setCellValueByColumnAndRow(6, $rekapRow, (string) ($pg->belum_steps ?? ''));
$rekapRow++;
}
foreach (range('A', 'F') as $col) {
$rekapSheet->getColumnDimension($col)->setAutoSize(true);
}
// Sheet 2: Detail
$detailSheet = $spreadsheet->createSheet();
$detailSheet->setTitle('Detail PraAkre');
$detailHeaders = ['Nama Karyawan', 'NIP', 'Pitstop', 'Nilai', 'Penilai'];
foreach ($detailHeaders as $i => $h) {
$detailSheet->setCellValueByColumnAndRow($i + 1, 1, $h);
}
$detailSheet->getStyle('A1:E1')->getFont()->setBold(true);
$pegawaiById = collect($pegawaiRows)->keyBy('id');
$detailRow = 2;
$lastPegawaiId = null;
foreach ($praakreRows as $d) {
if (!is_null($lastPegawaiId) && (string) $lastPegawaiId !== (string) $d->pegawai_id) {
$detailSheet->getStyle('A' . $detailRow . ':E' . $detailRow)
->applyFromArray([
'borders' => [
'top' => [
'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN,
'color' => ['argb' => 'FF999999'],
],
],
'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
'startColor' => ['argb' => 'FF1E88E5'],
],
]);
$detailRow++;
}
$pg = $pegawaiById->get($d->pegawai_id);
$detailSheet->setCellValueByColumnAndRow(1, $detailRow, (string) data_get($pg, 'nama', '-'));
$detailSheet->setCellValueExplicitByColumnAndRow(2, $detailRow, (string) data_get($pg, 'nip_pns', '-'), DataType::TYPE_STRING);
$detailSheet->setCellValueByColumnAndRow(3, $detailRow, (string) ($d->pitstop_nama ?? '-'));
$detailSheet->setCellValueByColumnAndRow(4, $detailRow, (string) ($d->nilai ?? ''));
$detailSheet->setCellValueByColumnAndRow(5, $detailRow, (string) ($d->penilai_nama ?? '-'));
$detailRow++;
$lastPegawaiId = $d->pegawai_id;
}
foreach (range('A', 'E') as $col) {
$detailSheet->getColumnDimension($col)->setAutoSize(true);
}
// default active sheet
$spreadsheet->setActiveSheetIndex(0);
});
}
public function monitoringExcelExternal(Request $request)
{
$tipe = $request->query('tipe');
$tipe = is_null($tipe) ? null : trim((string) $tipe);
if ($tipe === '') $tipe = null;
$totalSteps = (int) MasterPitStopPraAkre::where('statusenabled', true)->count();
$generatedAt = now();
// Summary per tipe
if (is_null($tipe)) {
$perPegawai = DB::connection('pgsql')
->table('public.pegawai_luar_pl as pl')
->leftJoin('public.praakre as p', function ($join) {
$join->on('p.pegawai_id', '=', 'pl.id')
->where('p.tipe_karyawan', 'luar');
})
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->select([
DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-') as tipe"),
DB::raw('pl.id as pegawai_id'),
DB::raw("count(distinct m.id) as lulus_count"),
])
->groupBy(DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-')"), 'pl.id');
$tipeAgg = DB::connection('pgsql')
->query()
->fromSub($perPegawai, 't')
->select([
't.tipe',
DB::raw('count(*) as total_pegawai'),
DB::raw('sum(case when t.lulus_count >= ' . $totalSteps . ' then 1 else 0 end) as pegawai_selesai'),
])
->groupBy('t.tipe')
->orderBy('t.tipe')
->get();
// Rekap karyawan external (all tipe)
$pegawaiRecap = DB::connection('pgsql')
->table('public.pegawai_luar_pl as pl')
->leftJoin('public.praakre as p', function ($join) {
$join->on('p.pegawai_id', '=', 'pl.id')
->where('p.tipe_karyawan', 'luar');
})
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->select([
DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-') as tipe"),
'pl.id',
DB::raw("coalesce(pl.nama, '-') as nama"),
DB::raw("coalesce(pl.nik, '-') as nik"),
DB::raw("count(distinct m.id) as lulus_count"),
DB::raw("coalesce((
select string_agg(ms.nama, ', ' order by ms.id)
from public.masterpitstop ms
left join public.praakre px
on px.pegawai_id = pl.id
and px.tipe_karyawan = 'luar'
and px.masterpitstop_id = ms.id::text
where ms.statusenabled = true
and px.id is null
), '') as belum_steps"),
])
->groupBy(DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-')"), 'pl.id', 'pl.nama', 'pl.nik')
->orderBy(DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-')"))
->orderBy('pl.nama')
->get();
$pegawaiIds = collect($pegawaiRecap)->pluck('id')->filter()->values()->all();
$praakreDetailAll = collect();
if (count($pegawaiIds) > 0) {
$praakreDetailAll = DB::connection('pgsql')
->table('public.praakre as p')
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->leftJoin('public.pegawai_m as pen', function ($join) {
$join->on(DB::raw('pen.id::text'), '=', 'p.action_at');
})
->whereIn('p.pegawai_id', $pegawaiIds)
->where('p.tipe_karyawan', 'luar')
->whereNotNull('m.id')
->select([
'p.pegawai_id',
DB::raw("coalesce(m.nama, '-') as pitstop_nama"),
'p.nilai',
DB::raw("coalesce(pen.namalengkap, '-') as penilai_nama"),
])
->orderBy('p.pegawai_id')
->orderBy('m.id')
->orderBy('p.id')
->get();
}
$filename = 'monitoring-karyawan-luar-ringkasan-' . $generatedAt->format('Ymd-Hi') . '.xlsx';
return $this->streamXlsx($filename, function (Spreadsheet $spreadsheet) use ($tipeAgg, $pegawaiRecap, $praakreDetailAll, $totalSteps) {
// Sheet 1: Rekap Unit/Tipe
$unitSheet = $spreadsheet->getActiveSheet();
$unitSheet->setTitle('Rekap Unit');
$headers = ['Unit', 'Total Pitstop', 'Total Karyawan External', 'Karyawan External Selesai'];
foreach ($headers as $i => $h) {
$unitSheet->setCellValueByColumnAndRow($i + 1, 1, $h);
}
$unitSheet->getStyle('A1:D1')->getFont()->setBold(true);
$row = 2;
foreach ($tipeAgg as $r) {
$unitSheet->setCellValueByColumnAndRow(1, $row, (string) ($r->tipe ?? '-'));
$unitSheet->setCellValueByColumnAndRow(2, $row, (int) $totalSteps);
$unitSheet->setCellValueByColumnAndRow(3, $row, (int) ($r->total_pegawai ?? 0));
$unitSheet->setCellValueByColumnAndRow(4, $row, (int) ($r->pegawai_selesai ?? 0));
$row++;
}
foreach (range('A', 'D') as $col) {
$unitSheet->getColumnDimension($col)->setAutoSize(true);
}
// Sheet 2: Rekap Karyawan
$pegawaiSheet = $spreadsheet->createSheet();
$pegawaiSheet->setTitle('Rekap Karyawan');
$pegawaiHeaders = ['Unit', 'Nama', 'NIK', 'Lulus Pitstop', 'Total Pitstop', 'Belum Dikerjakan'];
foreach ($pegawaiHeaders as $i => $h) {
$pegawaiSheet->setCellValueByColumnAndRow($i + 1, 1, $h);
}
$pegawaiSheet->getStyle('A1:F1')->getFont()->setBold(true);
$pegawaiRow = 2;
foreach ($pegawaiRecap as $r) {
$pegawaiSheet->setCellValueByColumnAndRow(1, $pegawaiRow, (string) ($r->tipe ?? '-'));
$pegawaiSheet->setCellValueByColumnAndRow(2, $pegawaiRow, (string) ($r->nama ?? '-'));
$pegawaiSheet->setCellValueExplicitByColumnAndRow(3, $pegawaiRow, (string) ($r->nik ?? '-'), DataType::TYPE_STRING);
$pegawaiSheet->setCellValueByColumnAndRow(4, $pegawaiRow, (int) ($r->lulus_count ?? 0));
$pegawaiSheet->setCellValueByColumnAndRow(5, $pegawaiRow, (int) $totalSteps);
$pegawaiSheet->setCellValueByColumnAndRow(6, $pegawaiRow, (string) ($r->belum_steps ?? ''));
$pegawaiRow++;
}
foreach (range('A', 'F') as $col) {
$pegawaiSheet->getColumnDimension($col)->setAutoSize(true);
}
// Sheet 3: Detail PraAkre
$detailSheet = $spreadsheet->createSheet();
$detailSheet->setTitle('Detail PraAkre');
$detailHeaders = ['Unit', 'Nama', 'NIK', 'Pitstop', 'Nilai', 'Penilai'];
foreach ($detailHeaders as $i => $h) {
$detailSheet->setCellValueByColumnAndRow($i + 1, 1, $h);
}
$detailSheet->getStyle('A1:F1')->getFont()->setBold(true);
$pegawaiById = collect($pegawaiRecap)->keyBy('id');
$detailRow = 2;
$lastPegawaiId = null;
foreach ($praakreDetailAll as $d) {
if (!is_null($lastPegawaiId) && (string) $lastPegawaiId !== (string) $d->pegawai_id) {
$detailSheet->getStyle('A' . $detailRow . ':F' . $detailRow)
->applyFromArray([
'borders' => [
'top' => [
'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN,
'color' => ['argb' => 'FF999999'],
],
],
'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
'startColor' => ['argb' => 'FF1E88E5'],
],
]);
$detailRow++;
}
$pg = $pegawaiById->get($d->pegawai_id);
$detailSheet->setCellValueByColumnAndRow(1, $detailRow, (string) data_get($pg, 'tipe', '-'));
$detailSheet->setCellValueByColumnAndRow(2, $detailRow, (string) data_get($pg, 'nama', '-'));
$detailSheet->setCellValueExplicitByColumnAndRow(3, $detailRow, (string) data_get($pg, 'nik', '-'), DataType::TYPE_STRING);
$detailSheet->setCellValueByColumnAndRow(4, $detailRow, (string) ($d->pitstop_nama ?? '-'));
$detailSheet->setCellValueByColumnAndRow(5, $detailRow, (string) ($d->nilai ?? ''));
$detailSheet->setCellValueByColumnAndRow(6, $detailRow, (string) ($d->penilai_nama ?? '-'));
$detailRow++;
$lastPegawaiId = $d->pegawai_id;
}
foreach (range('A', 'F') as $col) {
$detailSheet->getColumnDimension($col)->setAutoSize(true);
}
$spreadsheet->setActiveSheetIndex(0);
});
}
// Detail per tipe
$rows = DB::connection('pgsql')
->table('public.pegawai_luar_pl as pl')
->leftJoin('public.praakre as p', function ($join) {
$join->on('p.pegawai_id', '=', 'pl.id')
->where('p.tipe_karyawan', 'luar');
})
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->where(DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-')"), '=', $tipe)
->select([
'pl.id',
DB::raw("coalesce(pl.nama, '-') as nama"),
DB::raw("coalesce(pl.nik, '-') as nik"),
DB::raw("count(distinct m.id) as lulus_count"),
DB::raw("coalesce((
select string_agg(ms.nama, ', ' order by ms.id)
from public.masterpitstop ms
left join public.praakre px
on px.pegawai_id = pl.id
and px.tipe_karyawan = 'luar'
and px.masterpitstop_id = ms.id::text
where ms.statusenabled = true
and px.id is null
), '') as belum_steps"),
])
->groupBy('pl.id', 'pl.nama', 'pl.nik')
->orderBy('pl.nama')
->get();
$pegawaiIds = collect($rows)->pluck('id')->filter()->values()->all();
$praakreRows = collect();
if (count($pegawaiIds) > 0) {
$praakreRows = DB::connection('pgsql')
->table('public.praakre as p')
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->leftJoin('public.pegawai_m as pen', function ($join) {
$join->on(DB::raw('pen.id::text'), '=', 'p.action_at');
})
->whereIn('p.pegawai_id', $pegawaiIds)
->where('p.tipe_karyawan', 'luar')
->whereNotNull('m.id')
->select([
'p.pegawai_id',
DB::raw("coalesce(m.nama, '-') as pitstop_nama"),
'p.nilai',
DB::raw("coalesce(pen.namalengkap, '-') as penilai_nama"),
])
->orderBy('p.pegawai_id')
->orderBy('m.id')
->orderBy('p.id')
->get();
}
$filename = 'monitoring-karyawan-luar-' . preg_replace('/[^a-zA-Z0-9_-]+/', '-', (string) $tipe) . '-' . $generatedAt->format('Ymd-Hi') . '.xlsx';
return $this->streamXlsx($filename, function (Spreadsheet $spreadsheet) use ($rows, $praakreRows, $tipe, $totalSteps) {
// Sheet 1: Rekap
$rekapSheet = $spreadsheet->getActiveSheet();
$rekapSheet->setTitle('Rekap');
$rekapHeaders = ['Unit', 'Nama', 'NIK', 'Lulus Pitstop', 'Total Pitstop', 'Belum Dikerjakan'];
foreach ($rekapHeaders as $i => $h) {
$rekapSheet->setCellValueByColumnAndRow($i + 1, 1, $h);
}
$rekapSheet->getStyle('A1:F1')->getFont()->setBold(true);
$rekapRow = 2;
foreach ($rows as $r) {
$rekapSheet->setCellValueByColumnAndRow(1, $rekapRow, (string) $tipe);
$rekapSheet->setCellValueByColumnAndRow(2, $rekapRow, (string) ($r->nama ?? '-'));
$rekapSheet->setCellValueExplicitByColumnAndRow(3, $rekapRow, (string) ($r->nik ?? '-'), DataType::TYPE_STRING);
$rekapSheet->setCellValueByColumnAndRow(4, $rekapRow, (int) ($r->lulus_count ?? 0));
$rekapSheet->setCellValueByColumnAndRow(5, $rekapRow, (int) $totalSteps);
$rekapSheet->setCellValueByColumnAndRow(6, $rekapRow, (string) ($r->belum_steps ?? ''));
$rekapRow++;
}
foreach (range('A', 'F') as $col) {
$rekapSheet->getColumnDimension($col)->setAutoSize(true);
}
// Sheet 2: Detail
$detailSheet = $spreadsheet->createSheet();
$detailSheet->setTitle('Detail PraAkre');
$detailHeaders = ['Unit', 'Nama', 'NIK', 'Pitstop', 'Nilai', 'Penilai'];
foreach ($detailHeaders as $i => $h) {
$detailSheet->setCellValueByColumnAndRow($i + 1, 1, $h);
}
$detailSheet->getStyle('A1:F1')->getFont()->setBold(true);
$pegawaiById = collect($rows)->keyBy('id');
$detailRow = 2;
$lastPegawaiId = null;
foreach ($praakreRows as $d) {
if (!is_null($lastPegawaiId) && (string) $lastPegawaiId !== (string) $d->pegawai_id) {
$detailSheet->getStyle('A' . $detailRow . ':F' . $detailRow)
->applyFromArray([
'borders' => [
'top' => [
'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN,
'color' => ['argb' => 'FF999999'],
],
],
'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
'startColor' => ['argb' => 'FF1E88E5'],
],
]);
$detailRow++;
}
$pg = $pegawaiById->get($d->pegawai_id);
$detailSheet->setCellValueByColumnAndRow(1, $detailRow, (string) $tipe);
$detailSheet->setCellValueByColumnAndRow(2, $detailRow, (string) data_get($pg, 'nama', '-'));
$detailSheet->setCellValueExplicitByColumnAndRow(3, $detailRow, (string) data_get($pg, 'nik', '-'), DataType::TYPE_STRING);
$detailSheet->setCellValueByColumnAndRow(4, $detailRow, (string) ($d->pitstop_nama ?? '-'));
$detailSheet->setCellValueByColumnAndRow(5, $detailRow, (string) ($d->nilai ?? ''));
$detailSheet->setCellValueByColumnAndRow(6, $detailRow, (string) ($d->penilai_nama ?? '-'));
$detailRow++;
$lastPegawaiId = $d->pegawai_id;
}
foreach (range('A', 'F') as $col) {
$detailSheet->getColumnDimension($col)->setAutoSize(true);
}
$spreadsheet->setActiveSheetIndex(0);
});
}
public function dataProgressUnit(Request $request, $unit_id)
{
$unitId = (int) $unit_id;
$draw = $this->dtInt($request->input('draw'), 1);
$start = $this->dtInt($request->input('start'), 0);
$length = $this->dtInt($request->input('length'), 10);
if ($length < 1) $length = 10;
if ($length > 200) $length = 200;
$search = trim((string) data_get($request->all(), 'search.value', ''));
$totalSteps = (int) MasterPitStopPraAkre::where('statusenabled', true)->count();
$baseNoSearch = DB::connection('pgsql')
->table('public.mappegawaijabatantounitkerja_m as mp')
->join('public.pegawai_m as pg', 'pg.id', '=', 'mp.objectpegawaifk')
->join('public.unitkerjapegawai_m as ukp', 'ukp.id', '=', 'mp.objectunitkerjapegawaifk')
->leftJoin('public.praakre as p', 'p.pegawai_id', '=', 'pg.id')
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->where('mp.statusenabled', true)
->where('mp.isprimary', true)
->where('pg.statusenabled', true)
->where('pg.kedudukanfk', 1)
->where('ukp.statusenabled', true)
->where('ukp.id', $unitId)
// ->whereNotIn('pg.kategorypegawai', ['11'])
->select([
'pg.id',
DB::raw("coalesce(pg.namalengkap, '-') as nama"),
DB::raw("coalesce(pg.nip_pns, '-') as nip_pns"),
DB::raw("count(distinct m.id) as lulus_count"),
DB::raw("coalesce((
select count(*)
from public.masterpitstop ms
left join public.praakre px
on px.pegawai_id = pg.id
and px.masterpitstop_id = ms.id::text
where ms.statusenabled = true
and px.id is null
), 0) as belum_count"),
DB::raw("coalesce((
select string_agg(ms.nama, ', ' order by ms.id)
from public.masterpitstop ms
left join public.praakre px
on px.pegawai_id = pg.id
and px.masterpitstop_id = ms.id::text
where ms.statusenabled = true
and px.id is null
), '') as belum_steps"),
])
->groupBy('pg.id', 'pg.namalengkap', 'pg.nip_pns');
$base = clone $baseNoSearch;
if ($search !== '') {
$base->where('pg.namalengkap', 'ILIKE', '%' . $search . '%');
}
$recordsTotal = DB::connection('pgsql')->query()->fromSub($baseNoSearch, 't')->count();
$recordsFiltered = $search === ''
? $recordsTotal
: DB::connection('pgsql')->query()->fromSub($base, 't')->count();
$rows = $base
->orderByDesc('lulus_count')
->orderBy('pg.namalengkap')
->offset($start)
->limit($length)
->get();
$data = $rows->map(function ($r) use ($totalSteps) {
$lulus = (int) ($r->lulus_count ?? 0);
$pct = $totalSteps > 0 ? round(($lulus / $totalSteps) * 100, 1) : 0;
return [
'id' => (int) $r->id,
'nama' => (string) ($r->nama ?? '-'),
'nip_pns' => (string) ($r->nip_pns ?? '-'),
'lulus_count' => $lulus,
'pct' => $pct,
'selesai' => $totalSteps > 0 ? ($lulus >= $totalSteps ? 1 : 0) : 0,
'belum_count' => (int) ($r->belum_count ?? 0),
'belum_steps' => (string) ($r->belum_steps ?? ''),
];
})->values();
return response()->json([
'draw' => $draw,
'recordsTotal' => $recordsTotal,
'recordsFiltered' => $recordsFiltered,
'data' => $data,
]);
}
public function dataProgressExternalByTipe(Request $request, $tipe = null)
{
// Support both route param and query param (?tipe=...) so tipe containing "/" works reliably.
if ($tipe === null) {
$tipe = $request->query('tipe');
}
$draw = $this->dtInt($request->input('draw'), 1);
$start = $this->dtInt($request->input('start'), 0);
$length = $this->dtInt($request->input('length'), 10);
if ($length < 1) $length = 10;
if ($length > 200) $length = 200;
$search = trim((string) data_get($request->all(), 'search.value', ''));
$tipe = urldecode((string) $tipe);
$tipe = trim($tipe);
if ($tipe === '') {
return response()->json([
'draw' => $draw,
'recordsTotal' => 0,
'recordsFiltered' => 0,
'data' => [],
]);
}
$totalSteps = (int) MasterPitStopPraAkre::where('statusenabled', true)->count();
$baseNoSearch = DB::connection('pgsql')
->table('public.pegawai_luar_pl as pl')
->leftJoin('public.praakre as p', function ($join) {
$join->on('p.pegawai_id', '=', 'pl.id')
->where('p.tipe_karyawan', 'luar');
})
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->where(DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-')"), '=', $tipe)
->select([
'pl.id',
DB::raw("coalesce(pl.nama, '-') as nama"),
DB::raw("coalesce(pl.nik, '-') as nik"),
DB::raw("count(distinct m.id) as lulus_count"),
DB::raw("coalesce((
select count(*)
from public.masterpitstop ms
left join public.praakre px
on px.pegawai_id = pl.id
and px.tipe_karyawan = 'luar'
and px.masterpitstop_id = ms.id::text
where ms.statusenabled = true
and px.id is null
), 0) as belum_count"),
DB::raw("coalesce((
select string_agg(ms.nama, ', ' order by ms.id)
from public.masterpitstop ms
left join public.praakre px
on px.pegawai_id = pl.id
and px.tipe_karyawan = 'luar'
and px.masterpitstop_id = ms.id::text
where ms.statusenabled = true
and px.id is null
), '') as belum_steps"),
])
->groupBy('pl.id', 'pl.nama', 'pl.nik');
$base = clone $baseNoSearch;
if ($search !== '') {
$base->where(function ($q) use ($search) {
$q->where('pl.nama', 'ILIKE', '%' . $search . '%')
->orWhere('pl.nik', 'ILIKE', '%' . $search . '%');
});
}
$recordsTotal = DB::connection('pgsql')->query()->fromSub($baseNoSearch, 't')->count();
$recordsFiltered = $search === ''
? $recordsTotal
: DB::connection('pgsql')->query()->fromSub($base, 't')->count();
$rows = $base
->orderByDesc('lulus_count')
->orderBy('pl.nama')
->offset($start)
->limit($length)
->get();
$data = $rows->map(function ($r) use ($totalSteps) {
$lulus = (int) ($r->lulus_count ?? 0);
$pct = $totalSteps > 0 ? round(($lulus / $totalSteps) * 100, 1) : 0;
return [
'id' => (int) $r->id,
'nama' => (string) ($r->nama ?? '-'),
'nik' => (string) ($r->nik ?? '-'),
'lulus_count' => $lulus,
'pct' => $pct,
'selesai' => $totalSteps > 0 ? ($lulus >= $totalSteps ? 1 : 0) : 0,
'belum_count' => (int) ($r->belum_count ?? 0),
'belum_steps' => (string) ($r->belum_steps ?? ''),
];
})->values();
return response()->json([
'draw' => $draw,
'recordsTotal' => $recordsTotal,
'recordsFiltered' => $recordsFiltered,
'data' => $data,
]);
}
public function dataProgressAllKaryawan(Request $request)
{
$draw = $this->dtInt($request->input('draw'), 1);
$start = $this->dtInt($request->input('start'), 0);
$length = $this->dtInt($request->input('length'), 10);
if ($length < 1) $length = 10;
if ($length > 200) $length = 200;
$search = trim((string) data_get($request->all(), 'search.value', ''));
$totalSteps = (int) MasterPitStopPraAkre::where('statusenabled', true)->count();
// ── Internal ────────────────────────────────────────────────────
$internal = DB::connection('pgsql')
->table('public.mappegawaijabatantounitkerja_m as mp')
->join('public.pegawai_m as pg', 'pg.id', '=', 'mp.objectpegawaifk')
->join('public.unitkerjapegawai_m as ukp', 'ukp.id', '=', 'mp.objectunitkerjapegawaifk')
->leftJoin('public.praakre as p', 'p.pegawai_id', '=', 'pg.id')
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->where('mp.statusenabled', true)
->where('mp.isprimary', true)
->where('pg.statusenabled', true)
->where('pg.kedudukanfk', 1)
->where('ukp.statusenabled', true)
->select([
DB::raw("'internal' as tipe_karyawan"),
'pg.id',
DB::raw("coalesce(pg.namalengkap, '-') as nama"),
DB::raw("coalesce(pg.nip_pns, '-') as identitas"),
DB::raw("coalesce(ukp.name, '-') as unit_name"),
DB::raw("count(distinct m.id) as lulus_count"),
])
->groupBy('pg.id', 'pg.namalengkap', 'pg.nip_pns', 'ukp.name');
// ── External ────────────────────────────────────────────────────
$external = DB::connection('pgsql')
->table('public.pegawai_luar_pl as pl')
->leftJoin('public.praakre as p', function ($join) {
$join->on('p.pegawai_id', '=', 'pl.id')
->where('p.tipe_karyawan', 'luar');
})
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->select([
DB::raw("'luar' as tipe_karyawan"),
'pl.id',
DB::raw("coalesce(pl.nama, '-') as nama"),
DB::raw("coalesce(pl.nik, '-') as identitas"),
DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-') as unit_name"),
DB::raw("count(distinct m.id) as lulus_count"),
])
->groupBy('pl.id', 'pl.nama', 'pl.nik', DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-')"));
// ── Union & Base ─────────────────────────────────────────────────
$union = $internal->unionAll($external);
$base = DB::connection('pgsql')->query()->fromSub($union, 'u');
if ($search !== '') {
$base->where(function ($q) use ($search) {
$q->where('u.nama', 'ILIKE', "%{$search}%")
->orWhere('u.identitas', 'ILIKE', "%{$search}%")
->orWhere('u.unit_name', 'ILIKE', "%{$search}%")
->orWhere('u.tipe_karyawan','ILIKE', "%{$search}%");
});
}
// ── Count — eksekusi SEKALI ──────────────────────────────────────
$recordsFiltered = (clone $base)->count();
$recordsTotal = $search === '' ? $recordsFiltered
: DB::connection('pgsql')->query()->fromSub($union, 'u')->count();
// ── Fetch halaman ini saja ───────────────────────────────────────
$rows = (clone $base)
->select('u.*')
->orderByDesc('lulus_count')
->orderBy('nama')
->offset($start)
->limit($length)
->get();
// ── Batch query belum_steps (hanya untuk baris di halaman ini) ───
$allSteps = DB::connection('pgsql')
->table('public.masterpitstop')
->where('statusenabled', true)
->orderBy('id')
->pluck('nama', 'id'); // [id => nama]
$internalIds = $rows->where('tipe_karyawan', 'internal')->pluck('id')->toArray();
$luarIds = $rows->where('tipe_karyawan', 'luar')->pluck('id')->toArray();
// Step yang sudah lulus, di-group per pegawai_id
$lulusInternal = collect();
$lulusLuar = collect();
if (!empty($internalIds)) {
$lulusInternal = DB::connection('pgsql')
->table('public.praakre as p')
->join('public.masterpitstop as ms', DB::raw('ms.id::text'), '=', 'p.masterpitstop_id')
->whereIn('p.pegawai_id', $internalIds)
->where('ms.statusenabled', true)
->select('p.pegawai_id', 'ms.id as step_id')
->get()
->groupBy('pegawai_id');
}
if (!empty($luarIds)) {
$lulusLuar = DB::connection('pgsql')
->table('public.praakre as p')
->join('public.masterpitstop as ms', DB::raw('ms.id::text'), '=', 'p.masterpitstop_id')
->whereIn('p.pegawai_id', $luarIds)
->where('p.tipe_karyawan', 'luar')
->where('ms.statusenabled', true)
->select('p.pegawai_id', 'ms.id as step_id')
->get()
->groupBy('pegawai_id');
}
// ── Map hasil ────────────────────────────────────────────────────
$data = $rows->map(function ($r) use ($totalSteps, $lulusInternal, $lulusLuar, $allSteps) {
$isLuar = $r->tipe_karyawan === 'luar';
$lulusMap = $isLuar ? ($lulusLuar->get($r->id) ?? collect())
: ($lulusInternal->get($r->id) ?? collect());
$lulusIds = $lulusMap->pluck('step_id')->toArray();
$belumSteps = $allSteps
->filter(fn($nama, $id) => !in_array($id, $lulusIds))
->implode(', ');
$lulus = (int) ($r->lulus_count ?? 0);
$belum = max(0, $totalSteps - $lulus);
return [
'tipe_karyawan' => (string) ($r->tipe_karyawan ?? 'internal'),
'id' => (int) ($r->id ?? 0),
'nama' => (string) ($r->nama ?? '-'),
'identitas' => (string) ($r->identitas ?? '-'),
'unit_name' => (string) ($r->unit_name ?? '-'),
'lulus_count' => $lulus,
'pct' => $totalSteps > 0 ? round(($lulus / $totalSteps) * 100, 1) : 0,
'selesai' => $totalSteps > 0 && $lulus >= $totalSteps ? 1 : 0,
'belum_count' => $belum,
'belum_steps' => $belumSteps,
];
})->values();
return response()->json([
'draw' => $draw,
'recordsTotal' => $recordsTotal,
'recordsFiltered' => $recordsFiltered,
'data' => $data,
]);
}
public function progressDetail(Request $request)
{
$validator = Validator::make($request->all(), [
'pegawai_id' => 'required|integer|exists:pgsql.public.pegawai_m,id',
]);
if ($validator->fails()) {
return response()->json([
'error' => 1,
'message' => 'Validasi gagal.',
'errors' => $validator->errors(),
], 422);
}
$pegawaiId = (int) $validator->validated()['pegawai_id'];
$rows = DB::connection('pgsql')
->table('public.praakre as p')
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->leftJoin('public.pegawai_m as pen', function ($join) {
$join->on(DB::raw('pen.id::text'), '=', 'p.action_at');
})
->where('p.pegawai_id', $pegawaiId)
// ->where('p.status', 'lulus')
->whereNotNull('m.id')
->select([
'p.id',
'p.masterpitstop_id',
DB::raw("coalesce(m.nama, '-') as step_nama"),
'p.nilai',
DB::raw("coalesce(pen.namalengkap, '-') as penilai_nama"),
DB::raw("to_char(p.created_at, 'DD-MM-YYYY HH24:MI') as created_at"),
])
->orderByDesc('p.id')
->limit(500)
->get();
return response()->json([
'error' => 0,
'data' => $rows,
]);
}
public function progressDetailExternal(Request $request)
{
$validator = Validator::make($request->all(), [
'pegawai_id' => 'required|integer|exists:pgsql.public.pegawai_luar_pl,id',
]);
if ($validator->fails()) {
return response()->json([
'error' => 1,
'message' => 'Validasi gagal.',
'errors' => $validator->errors(),
], 422);
}
$pegawaiId = (int) $validator->validated()['pegawai_id'];
$rows = DB::connection('pgsql')
->table('public.praakre as p')
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->leftJoin('public.pegawai_m as pen', function ($join) {
$join->on(DB::raw('pen.id::text'), '=', 'p.action_at');
})
->where('p.tipe_karyawan', 'luar')
->where('p.pegawai_id', $pegawaiId)
->whereNotNull('m.id')
->select([
'p.id',
'p.masterpitstop_id',
DB::raw("coalesce(m.nama, '-') as step_nama"),
'p.nilai',
DB::raw("coalesce(pen.namalengkap, '-') as penilai_nama"),
DB::raw("to_char(p.created_at, 'DD-MM-YYYY HH24:MI') as created_at"),
])
->orderByDesc('p.id')
->limit(500)
->get();
return response()->json([
'error' => 0,
'data' => $rows,
]);
}
public function pegawaiSteps(Request $request)
{
$validator = Validator::make($request->all(), [
'pegawai_id' => 'required|integer|exists:pgsql.public.pegawai_m,id',
]);
if ($validator->fails()) {
return response()->json([
'error' => 1,
'message' => 'Validasi gagal.',
'errors' => $validator->errors(),
], 422);
}
$pegawaiId = (int) $validator->validated()['pegawai_id'];
$lockedSteps = PraAkre::where('pegawai_id', $pegawaiId)
// ->where('status', 'lulus')
->distinct()
->pluck('masterpitstop_id');
return response()->json([
'error' => 0,
'data' => [
'locked_steps' => $lockedSteps,
],
]);
}
public function pegawaiStepsExternal(Request $request)
{
$validator = Validator::make($request->all(), [
'pegawai_id' => 'required|integer|exists:pgsql.public.pegawai_luar_pl,id',
]);
if ($validator->fails()) {
return response()->json([
'error' => 1,
'message' => 'Validasi gagal.',
'errors' => $validator->errors(),
], 422);
}
$pegawaiId = (int) $validator->validated()['pegawai_id'];
$lockedSteps = PraAkre::where('pegawai_id', $pegawaiId)
->where('tipe_karyawan', 'luar')
// ->where('status', 'lulus')
->distinct()
->pluck('masterpitstop_id');
return response()->json([
'error' => 0,
'data' => [
'locked_steps' => $lockedSteps,
],
]);
}
public function submit(Request $request)
{
$tipeKaryawan = (string) $request->input('tipe_karyawan', 'internal');
$tipeKaryawan = $tipeKaryawan === 'luar' ? 'luar' : 'internal';
$validator = Validator::make($request->all(), [
'karyawan_id' => ['required', 'integer'],
'step' => 'required',
// 'status' => 'required|in:lulus,tidak_lulus',
'unit_id' => 'nullable|string|max:15',
'tipe_karyawan' => 'nullable|in:internal,luar',
'nilai' => 'required'
]);
if ($validator->fails()) {
return response()->json([
'error' => 1,
'message' => 'Validasi gagal.',
'errors' => $validator->errors(),
], 422);
}
$payload = $validator->validated();
$payload['tipe_karyawan'] = $tipeKaryawan;
$pegawaiId = (int) $payload['karyawan_id'];
$masterPitstopId = (string) $payload['step'];
if ($tipeKaryawan === 'luar') {
$exists = DB::connection('pgsql')
->table('public.pegawai_luar_pl')
->where('id', $pegawaiId)
->exists();
if (!$exists) {
return response()->json([
'error' => 1,
'message' => 'Karyawan luar tidak ditemukan.',
], 422);
}
} else {
$exists = DB::connection('pgsql')
->table('public.pegawai_m')
->where('id', $pegawaiId)
->exists();
if (!$exists) {
return response()->json([
'error' => 1,
'message' => 'Karyawan tidak ditemukan.',
], 422);
}
}
$alreadyPassed = PraAkre::where('pegawai_id', $pegawaiId)
->where('tipe_karyawan', $tipeKaryawan)
->where('masterpitstop_id', $masterPitstopId)
// ->where('status', 'lulus')
->exists();
if ($alreadyPassed) {
return response()->json([
'error' => 1,
'message' => 'Pitstop ini sudah terkunci.',
], 409);
}
$row = PraAkre::create([
'pegawai_id' => $pegawaiId,
'masterpitstop_id' => $masterPitstopId,
'unit_id' => $tipeKaryawan === 'luar' ? null : ($payload['unit_id'] ?? null),
// 'status' => $payload['status'],
'nilai' => $payload['nilai'],
'tipe_karyawan' => $payload['tipe_karyawan'],
'action_at' => auth()->user()->objectpegawaifk
,
'created_at' => now(),
]);
return response()->json([
'error' => 0,
'message' => 'Data berhasil disimpan.',
'data' => [
'id' => $row->id,
'pegawai_id' => $row->pegawai_id,
'masterpitstop_id' => $row->masterpitstop_id,
'status' => $row->status,
],
]);
}
public function progressDetailGhost(Request $request)
{
$validator = Validator::make($request->all(), [
'pegawai_id' => 'required|integer|exists:pgsql.public.pegawai_m,id',
]);
if ($validator->fails()) {
return response()->json([
'error' => 1,
'message' => 'Validasi gagal.',
'errors' => $validator->errors(),
], 422);
}
$pegawaiId = (int) $validator->validated()['pegawai_id'];
$rows = DB::connection('pgsql')
->table('public.praakre as p')
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->leftJoin('public.pegawai_m as pen', function ($join) {
$join->on(DB::raw('pen.id::text'), '=', 'p.action_at');
})
->where('p.pegawai_id', $pegawaiId)
->whereNotNull('m.id')
->select([
'p.id',
'p.masterpitstop_id',
DB::raw("coalesce(m.nama, '-') as step_nama"),
'p.nilai',
DB::raw("coalesce(pen.namalengkap, '-') as penilai_nama"),
DB::raw("to_char(p.created_at, 'DD-MM-YYYY HH24:MI') as created_at"),
])
->orderByDesc('p.id')
->limit(500)
->get();
return response()->json([
'error' => 0,
'data' => $rows,
]);
}
public function progressDetailExternalGhost(Request $request)
{
$validator = Validator::make($request->all(), [
'pegawai_id' => 'required|integer|exists:pgsql.public.pegawai_luar_pl,id',
]);
if ($validator->fails()) {
return response()->json([
'error' => 1,
'message' => 'Validasi gagal.',
'errors' => $validator->errors(),
], 422);
}
$pegawaiId = (int) $validator->validated()['pegawai_id'];
$rows = DB::connection('pgsql')
->table('public.praakre as p')
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->leftJoin('public.pegawai_m as pen', function ($join) {
$join->on(DB::raw('pen.id::text'), '=', 'p.action_at');
})
->where('p.tipe_karyawan', 'luar')
->where('p.status', 'lulus')
->where('p.pegawai_id', $pegawaiId)
->whereNotNull('m.id')
->select([
'p.id',
'p.masterpitstop_id',
DB::raw("coalesce(m.nama, '-') as step_nama"),
'p.status',
'p.nilai',
DB::raw("coalesce(pen.namalengkap, '-') as penilai_nama"),
DB::raw("to_char(p.created_at, 'DD-MM-YYYY HH24:MI') as created_at"),
])
->orderByDesc('p.id')
->limit(500)
->get();
return response()->json([
'error' => 0,
'data' => $rows,
]);
}
public function dataProgressAllKaryawanGhost(Request $request)
{
$draw = $this->dtInt($request->input('draw'), 1);
$start = $this->dtInt($request->input('start'), 0);
$length = $this->dtInt($request->input('length'), 10);
if ($length < 1) $length = 10;
if ($length > 200) $length = 200;
$search = trim((string) data_get($request->all(), 'search.value', ''));
$totalSteps = (int) MasterPitStopPraAkre::where('statusenabled', true)->count();
// ----------------------------------------------------------------
// Subquery: belum_count & belum_steps dihitung SEKALI via CTE
// Hindari correlated subquery per-baris
// ----------------------------------------------------------------
$belumCte = DB::connection('pgsql')
->table('public.masterpitstop as ms')
->where('ms.statusenabled', true)
->select([
'ms.id as ms_id',
'ms.nama as ms_nama',
]);
// ----------------------------------------------------------------
// Internal query — ganti correlated subquery dengan LEFT JOIN agregasi
// ----------------------------------------------------------------
$internal = DB::connection('pgsql')
->table('public.mappegawaijabatantounitkerja_m as mp')
->join('public.pegawai_m as pg', 'pg.id', '=', 'mp.objectpegawaifk')
->join('public.unitkerjapegawai_m as ukp', 'ukp.id', '=', 'mp.objectunitkerjapegawaifk')
->leftJoin('public.praakre as p', function ($join) {
$join->on('p.pegawai_id', '=', 'pg.id')
->where('p.tipe_karyawan', '!=', 'luar'); // pastikan hanya internal
})
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->where('mp.statusenabled', true)
->where('mp.isprimary', true)
->where('pg.statusenabled', true)
->where('pg.kedudukanfk', 1)
->where('ukp.statusenabled', true)
->select([
DB::raw("'internal' as tipe_karyawan"),
'pg.id',
DB::raw("coalesce(pg.namalengkap, '-') as nama"),
DB::raw("coalesce(ukp.name, '-') as unit_name"),
DB::raw("count(distinct m.id) as lulus_count"),
// belum_count = totalSteps - lulus (dihitung di PHP, bukan subquery)
DB::raw("0 as belum_count"), // placeholder, dihitung di PHP
DB::raw("'' as belum_steps"), // placeholder, isi jika memang dibutuhkan
])
->groupBy('pg.id', 'pg.namalengkap', 'pg.nip_pns', 'ukp.name');
// ----------------------------------------------------------------
// External query
// ----------------------------------------------------------------
$external = DB::connection('pgsql')
->table('public.pegawai_luar_pl as pl')
->leftJoin('public.praakre as p', function ($join) {
$join->on('p.pegawai_id', '=', 'pl.id')
->where('p.tipe_karyawan', 'luar');
})
->leftJoin('public.masterpitstop as m', function ($join) {
$join->on(DB::raw('m.id::text'), '=', 'p.masterpitstop_id')
->where('m.statusenabled', true);
})
->select([
DB::raw("'luar' as tipe_karyawan"),
'pl.id',
DB::raw("coalesce(pl.nama, '-') as nama"),
DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-') as unit_name"),
DB::raw("count(distinct m.id) as lulus_count"),
DB::raw("0 as belum_count"),
DB::raw("'' as belum_steps"),
])
->groupBy('pl.id', 'pl.nama', 'pl.nik', DB::raw("coalesce(nullif(btrim(pl.tipe), ''), '-')"));
// ----------------------------------------------------------------
// Union & base query — hanya SATU kali wrap
// ----------------------------------------------------------------
$union = $internal->unionAll($external);
$base = DB::connection('pgsql')
->query()
->fromSub($union, 'u');
if ($search !== '') {
$base->where(function ($q) use ($search) {
$q->where('u.nama', 'ILIKE', "%{$search}%")
->orWhere('u.unit_name', 'ILIKE', "%{$search}%")
->orWhere('u.tipe_karyawan','ILIKE', "%{$search}%");
});
}
// ----------------------------------------------------------------
// Hitung total SEKALI dengan conditional aggregate
// Hindari eksekusi query count dua kali
// ----------------------------------------------------------------
$counts = (clone $base)
->selectRaw('count(*) as total')
->first();
$recordsFiltered = (int) ($counts->total ?? 0);
$recordsTotal = $search === ''
? $recordsFiltered
: (int) DB::connection('pgsql')->query()->fromSub($union, 'u')->count();
// ----------------------------------------------------------------
// Ambil data dengan pagination
// ----------------------------------------------------------------
$rows = (clone $base)
->select('u.*')
->orderByDesc('lulus_count')
->orderBy('nama')
->offset($start)
->limit($length)
->get();
// ----------------------------------------------------------------
// belum_steps: hanya ambil untuk baris yang ditampilkan saja (bukan semua)
// Jauh lebih efisien daripada subquery per-baris
// ----------------------------------------------------------------
$pegawaiIds = $rows->where('tipe_karyawan', 'internal')->pluck('id')->toArray();
$luarIds = $rows->where('tipe_karyawan', 'luar')->pluck('id')->toArray();
// Ambil step yang sudah lulus per pegawai (hanya untuk baris di halaman ini)
$lulusInternal = collect();
$lulusLuar = collect();
if (!empty($pegawaiIds)) {
$lulusInternal = DB::connection('pgsql')
->table('public.praakre as p')
->join('public.masterpitstop as ms', DB::raw('ms.id::text'), '=', 'p.masterpitstop_id')
->whereIn('p.pegawai_id', $pegawaiIds)
->where('ms.statusenabled', true)
->select('p.pegawai_id', 'ms.id as step_id')
->get()
->groupBy('pegawai_id');
}
if (!empty($luarIds)) {
$lulusLuar = DB::connection('pgsql')
->table('public.praakre as p')
->join('public.masterpitstop as ms', DB::raw('ms.id::text'), '=', 'p.masterpitstop_id')
->whereIn('p.pegawai_id', $luarIds)
->where('p.tipe_karyawan', 'luar')
->where('ms.statusenabled', true)
->select('p.pegawai_id', 'ms.id as step_id')
->get()
->groupBy('pegawai_id');
}
// Semua step aktif
$allSteps = DB::connection('pgsql')
->table('public.masterpitstop')
->where('statusenabled', true)
->orderBy('id')
->pluck('nama', 'id');
// ----------------------------------------------------------------
// Map hasil
// ----------------------------------------------------------------
$data = $rows->map(function ($r) use ($totalSteps, $lulusInternal, $lulusLuar, $allSteps) {
$isLuar = $r->tipe_karyawan === 'luar';
$lulusMap = $isLuar
? ($lulusLuar->get($r->id) ?? collect())
: ($lulusInternal->get($r->id) ?? collect());
$lulusStepIds = $lulusMap->pluck('step_id')->toArray();
$belumSteps = $allSteps
->filter(fn($nama, $id) => !in_array($id, $lulusStepIds))
->values()
->implode(', ');
$lulus = (int) ($r->lulus_count ?? 0);
$belum = $totalSteps - $lulus;
$pct = $totalSteps > 0 ? round(($lulus / $totalSteps) * 100, 1) : 0;
return [
'tipe_karyawan' => (string) ($r->tipe_karyawan ?? 'internal'),
'id' => (int) ($r->id ?? 0),
'nama' => (string) ($r->nama ?? '-'),
'unit_name' => (string) ($r->unit_name ?? '-'),
'lulus_count' => $lulus,
'pct' => $pct,
'selesai' => $totalSteps > 0 ? ($lulus >= $totalSteps ? 1 : 0) : 0,
'belum_count' => max(0, $belum),
'belum_steps' => $belumSteps,
];
})->values();
return response()->json([
'draw' => $draw,
'recordsTotal' => $recordsTotal,
'recordsFiltered' => $recordsFiltered,
'data' => $data,
]);
}
}