SIPDAM/samooapk/app/Http/Controllers/Api/PenugasanApiController.php

854 lines
34 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Penugasan;
use App\Models\Teknisi;
use App\Models\TarifPekerjaan;
use App\Models\TimTeknisiPenugasan;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Carbon\Carbon;
use Exception;
class PenugasanApiController extends Controller
{
/**
* GET - Daftar penugasan untuk teknisi (via mobile)
*/
public function index(Request $request)
{
try {
$idTeknisi = $request->input('id_teknisi');
if (!$idTeknisi) {
return response()->json([
'success' => false,
'message' => 'ID Teknisi tidak ditemukan'
], 401);
}
$query = Penugasan::with(['teknisi', 'tarif', 'timTeknisi.teknisi', 'items.tarif'])
->where(function ($q) use ($idTeknisi) {
$q->where('id_teknisi', $idTeknisi)
->orWhereHas('timTeknisi', function ($sq) use ($idTeknisi) {
$sq->where('id_teknisi', $idTeknisi);
});
});
if ($request->filled('status')) {
$query->where('status_pekerjaan', $request->status);
}
if ($request->filled('jenis_pekerjaan')) {
$query->where('jenis_pekerjaan', $request->jenis_pekerjaan);
}
if ($request->filled('tanggal_mulai')) {
$query->whereDate('tanggal_diberikan', '>=', $request->tanggal_mulai);
}
if ($request->filled('tanggal_akhir')) {
$query->whereDate('tanggal_diberikan', '<=', $request->tanggal_akhir);
}
$penugasan = $query->orderBy('tanggal_diberikan', 'desc')->paginate(15);
$penugasan->getCollection()->transform(function ($item) {
$namaTim = $item->timTeknisi->map(function ($tt) {
return $tt->teknisi->nama ?? 'N/A';
})->implode(', ');
$item->nama_tim = !empty($namaTim) ? $namaTim : ($item->teknisi->nama ?? 'N/A');
if ($item->teknisi) {
$item->teknisi->nama = $item->nama_tim;
}
$item->foto_surat_url = $item->foto_surat_url;
$item->foto_sebelum_url = $item->foto_sebelum_url;
$item->foto_sesudah_url = $item->foto_sesudah_url;
$item->label_jenis_pekerjaan = $item->label_jenis_pekerjaan;
return $item;
});
return response()->json([
'success' => true,
'message' => 'Data penugasan berhasil diambil',
'data' => $penugasan
]);
} catch (Exception $e) {
return response()->json([
'success' => false,
'message' => 'Gagal mengambil data: ' . $e->getMessage()
], 500);
}
}
/**
* GET - Detail penugasan
*/
public function show($id)
{
try {
$penugasan = Penugasan::with(['teknisi', 'tarif', 'timTeknisi.teknisi', 'items.tarif'])
->findOrFail($id);
$teamMembers = $penugasan->timTeknisi->map(function ($tt) {
return $tt->teknisi->nama ?? null;
})->filter()->unique()->values();
$namaTim = $teamMembers->implode(', ');
$data = $penugasan->toArray();
$fullTeamNames = !empty($namaTim) ? $namaTim : ($penugasan->teknisi->nama ?? 'N/A');
if (isset($data['teknisi'])) {
$data['teknisi']['nama'] = $fullTeamNames;
}
$prefix = !empty($namaTim) ? "[Tim: $namaTim] " : "";
$data['catatan_admin'] = $prefix . ($penugasan->catatan_admin ?? '');
$data['instruksi_tambahan'] = $data['catatan_admin'];
$data['foto_surat_url'] = $penugasan->foto_surat_url;
$data['foto_sebelum_url'] = $penugasan->foto_sebelum_url;
$data['foto_sesudah_url'] = $penugasan->foto_sesudah_url;
$data['label_jenis_pekerjaan'] = $penugasan->label_jenis_pekerjaan;
$data['is_garansi_aktif'] = $penugasan->isGaransiAktif();
$data['sisa_hari_garansi'] = $penugasan->getSisaHariGaransi();
return response()->json([
'success' => true,
'message' => 'Detail penugasan berhasil diambil',
'data' => $data
]);
} catch (Exception $e) {
return response()->json([
'success' => false,
'message' => 'Data tidak ditemukan: ' . $e->getMessage()
], 404);
}
}
/**
* POST - Teknisi melengkapi detail pekerjaan via mobile (pertama kali)
*/
public function lengkapiDetail(Request $request, $id)
{
try {
$penugasan = Penugasan::findOrFail($id);
$validator = Validator::make($request->all(), [
'items' => 'required|array|min:1',
'items.*.jenis_pekerjaan' => 'required|string',
'items.*.dimensi_pipa' => 'nullable',
'items.*.jarak_meter' => 'nullable|numeric',
'items.*.jumlah_unit' => 'nullable|integer',
'items.*.jumlah_titik' => 'nullable|integer',
'items.*.pakai_pipa_besi' => 'nullable',
'items.*.jenis_pengangkatan' => 'nullable',
'detail_pekerjaan' => 'nullable|string',
'tanggal_mulai' => 'required|date',
'tim_teknisi' => 'nullable|array',
'foto_sebelum' => 'nullable|file|max:10240',
'foto_sesudah' => 'nullable|file|max:10240',
'foto_sebelum_base64' => 'nullable|string',
'foto_sesudah_base64' => 'nullable|string',
]);
if ($validator->fails()) {
\Illuminate\Support\Facades\Log::error('Validation Fail in lengkapiDetail', $validator->errors()->toArray());
return response()->json([
'success' => false,
'message' => 'Validasi gagal',
'errors' => $validator->errors()
], 422);
}
DB::beginTransaction();
\App\Models\PenugasanItem::where('id_penugasan', $penugasan->id_penugasan)->delete();
$fotoSebelum = $penugasan->foto_sebelum;
if ($request->hasFile('foto_sebelum')) {
$fotoSebelum = $request->file('foto_sebelum')->store('penugasan/foto-sebelum', 'public');
} elseif ($request->foto_sebelum_base64) {
$fotoSebelum = $this->storeBase64($request->foto_sebelum_base64, 'penugasan/foto-sebelum');
}
$fotoSesudah = $penugasan->foto_sesudah;
if ($request->hasFile('foto_sesudah')) {
$fotoSesudah = $request->file('foto_sesudah')->store('penugasan/foto-sesudah', 'public');
} elseif ($request->foto_sesudah_base64) {
$fotoSesudah = $this->storeBase64($request->foto_sesudah_base64, 'penugasan/foto-sesudah');
}
$totalNilaiPenugasan = 0;
$hasSR = false;
foreach ($request->items as $itemData) {
$tarif = TarifPekerjaan::where('jenis_pekerjaan', $itemData['jenis_pekerjaan'])
->where('is_active', true);
if (!empty($itemData['dimensi_pipa'])) {
$tarif->where('dimensi_pipa', $itemData['dimensi_pipa']);
}
if (isset($itemData['pakai_pipa_besi'])) {
$tarif->where('pakai_pipa_besi', $itemData['pakai_pipa_besi']);
}
$tarif = $tarif->first();
$nilaiItem = $this->hitungNilaiItem($tarif, $itemData);
$totalNilaiPenugasan += $nilaiItem;
\App\Models\PenugasanItem::create([
'id_penugasan' => $penugasan->id_penugasan,
'id_tarif' => $tarif ? $tarif->id_tarif : null,
'jenis_pekerjaan' => $itemData['jenis_pekerjaan'],
'dimensi_pipa' => $itemData['dimensi_pipa'] ?? null,
'jarak_meter' => $itemData['jarak_meter'] ?? null,
'jumlah_unit' => $itemData['jumlah_unit'] ?? null,
'jumlah_titik' => $itemData['jumlah_titik'] ?? null,
'pakai_pipa_besi' => $itemData['pakai_pipa_besi'] ?? null,
'jenis_pengangkatan' => $itemData['jenis_pengangkatan'] ?? null,
'total_nilai_pekerjaan' => $nilaiItem,
]);
if ($itemData['jenis_pekerjaan'] === 'sr') $hasSR = true;
}
$firstItem = $request->items[0];
$penugasan->update([
'jenis_pekerjaan' => $firstItem['jenis_pekerjaan'],
'dimensi_pipa' => $firstItem['dimensi_pipa'] ?? $penugasan->dimensi_pipa,
'jarak_meter' => $firstItem['jarak_meter'] ?? $penugasan->jarak_meter,
'jumlah_unit' => $firstItem['jumlah_unit'] ?? $penugasan->jumlah_unit,
'pakai_pipa_besi' => array_key_exists('pakai_pipa_besi', $firstItem)
? $firstItem['pakai_pipa_besi']
: $penugasan->pakai_pipa_besi,
'status_pekerjaan' => $penugasan->status_pekerjaan === 'belum_mulai'
? 'dalam_proses'
: $penugasan->status_pekerjaan,
'total_nilai_pekerjaan' => $totalNilaiPenugasan,
'detail_pekerjaan' => $request->has('detail_pekerjaan')
? $request->detail_pekerjaan
: $penugasan->detail_pekerjaan,
'tanggal_mulai' => $request->tanggal_mulai,
'foto_sebelum' => $fotoSebelum,
'foto_sesudah' => $fotoSesudah,
]);
if ($hasSR) {
$penugasan->setGaransiMeteranAir($request->tanggal_mulai);
$penugasan->save();
}
if ($request->filled('tim_teknisi')) {
foreach ($request->tim_teknisi as $idTeknisiTambahan) {
$exists = TimTeknisiPenugasan::where('id_penugasan', $penugasan->id_penugasan)
->where('id_teknisi', $idTeknisiTambahan)
->exists();
if (!$exists) {
TimTeknisiPenugasan::create([
'id_penugasan' => $penugasan->id_penugasan,
'id_teknisi' => $idTeknisiTambahan,
'status_kehadiran' => 'hadir',
]);
}
}
}
DB::commit();
$penugasan->load(['teknisi', 'tarif', 'timTeknisi.teknisi', 'items.tarif']);
return response()->json([
'success' => true,
'message' => 'Detail pekerjaan berhasil dilengkapi!',
'data' => $penugasan
]);
} catch (Exception $e) {
DB::rollBack();
return response()->json([
'success' => false,
'message' => 'Gagal melengkapi detail: ' . $e->getMessage()
], 500);
}
}
/**
* PUT - Update / edit detail pekerjaan yang sudah diisi sebelumnya (teknisi via mobile)
*/
public function updateDetail(Request $request, $id)
{
try {
$penugasan = Penugasan::with(['timTeknisi'])->findOrFail($id);
$validator = Validator::make($request->all(), [
'id_teknisi' => 'required|integer',
'items' => 'required|array|min:1',
'items.*.id_penugasan_item' => 'nullable|integer',
'items.*.jenis_pekerjaan' => 'required|string',
'items.*.dimensi_pipa' => 'nullable',
'items.*.jarak_meter' => 'nullable|numeric',
'items.*.jumlah_unit' => 'nullable|integer',
'items.*.jumlah_titik' => 'nullable|integer',
'items.*.pakai_pipa_besi' => 'nullable',
'items.*.jenis_pengangkatan' => 'nullable',
'detail_pekerjaan' => 'nullable|string',
'tanggal_mulai' => 'nullable|date',
'tim_teknisi' => 'nullable|array',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validasi gagal',
'errors' => $validator->errors()
], 422);
}
$idTeknisiEditor = $request->id_teknisi;
$isAssigned = ($penugasan->id_teknisi == $idTeknisiEditor) ||
$penugasan->timTeknisi->pluck('id_teknisi')->contains($idTeknisiEditor);
if (!$isAssigned) {
return response()->json([
'success' => false,
'message' => 'Anda tidak berwenang mengedit penugasan ini'
], 403);
}
DB::beginTransaction();
\App\Models\PenugasanItem::where('id_penugasan', $penugasan->id_penugasan)->delete();
$hasSR = false;
$processedItemIds = [];
foreach ($request->items as $itemData) {
$tarifQuery = TarifPekerjaan::where('jenis_pekerjaan', $itemData['jenis_pekerjaan'])
->where('is_active', true);
if (!empty($itemData['dimensi_pipa'])) {
$tarifQuery->where('dimensi_pipa', $itemData['dimensi_pipa']);
}
if (isset($itemData['pakai_pipa_besi'])) {
$tarifQuery->where('pakai_pipa_besi', $itemData['pakai_pipa_besi']);
}
$tarif = $tarifQuery->first();
$nilaiItem = $this->hitungNilaiItem($tarif, $itemData);
$created = \App\Models\PenugasanItem::create([
'id_penugasan' => $penugasan->id_penugasan,
'id_tarif' => $tarif ? $tarif->id_tarif : null,
'jenis_pekerjaan' => $itemData['jenis_pekerjaan'],
'dimensi_pipa' => $itemData['dimensi_pipa'] ?? null,
'jarak_meter' => $itemData['jarak_meter'] ?? null,
'jumlah_unit' => $itemData['jumlah_unit'] ?? null,
'jumlah_titik' => $itemData['jumlah_titik'] ?? null,
'pakai_pipa_besi' => $itemData['pakai_pipa_besi'] ?? null,
'jenis_pengangkatan' => $itemData['jenis_pengangkatan'] ?? null,
'total_nilai_pekerjaan' => $nilaiItem,
]);
$processedItemIds[] = $created->id_penugasan_item;
if ($itemData['jenis_pekerjaan'] === 'sr') $hasSR = true;
}
if ($request->has('detail_pekerjaan')) {
$penugasan->detail_pekerjaan = $request->detail_pekerjaan;
}
if ($request->filled('tanggal_mulai')) {
$penugasan->tanggal_mulai = $request->tanggal_mulai;
}
if ($request->filled('tim_teknisi')) {
foreach ($request->tim_teknisi as $idTeknisiTambahan) {
$exists = TimTeknisiPenugasan::where('id_penugasan', $penugasan->id_penugasan)
->where('id_teknisi', $idTeknisiTambahan)
->exists();
if (!$exists) {
TimTeknisiPenugasan::create([
'id_penugasan' => $penugasan->id_penugasan,
'id_teknisi' => $idTeknisiTambahan,
'status_kehadiran' => 'hadir',
]);
}
}
}
$total = \App\Models\PenugasanItem::where('id_penugasan', $penugasan->id_penugasan)
->sum('total_nilai_pekerjaan');
$firstItem = $request->items[0];
$penugasan->total_nilai_pekerjaan = $total;
$penugasan->jenis_pekerjaan = $firstItem['jenis_pekerjaan'] ?? $penugasan->jenis_pekerjaan;
$penugasan->dimensi_pipa = $firstItem['dimensi_pipa'] ?? $penugasan->dimensi_pipa;
$penugasan->jarak_meter = $firstItem['jarak_meter'] ?? $penugasan->jarak_meter;
$penugasan->jumlah_unit = $firstItem['jumlah_unit'] ?? $penugasan->jumlah_unit;
$penugasan->pakai_pipa_besi = array_key_exists('pakai_pipa_besi', $firstItem)
? $firstItem['pakai_pipa_besi']
: $penugasan->pakai_pipa_besi;
$statusBolehDiubah = ['belum_mulai'];
if (in_array($penugasan->status_pekerjaan, $statusBolehDiubah)) {
$penugasan->status_pekerjaan = 'dalam_proses';
}
$penugasan->save();
if ($hasSR) {
$penugasan->setGaransiMeteranAir($penugasan->tanggal_mulai ?? null);
$penugasan->save();
}
DB::commit();
$penugasan->load(['teknisi', 'tarif', 'timTeknisi.teknisi', 'items.tarif']);
return response()->json([
'success' => true,
'message' => 'Detail pekerjaan berhasil diperbarui!',
'data' => $penugasan
]);
} catch (Exception $e) {
DB::rollBack();
return response()->json([
'success' => false,
'message' => 'Gagal memperbarui detail: ' . $e->getMessage()
], 500);
}
}
/**
* POST - Tambah rincian pekerjaan baru di tengah progres
*/
public function addItem(Request $request, $id)
{
try {
$penugasan = Penugasan::findOrFail($id);
$validator = Validator::make($request->all(), [
'jenis_pekerjaan' => 'required|string',
'dimensi_pipa' => 'nullable',
'jarak_meter' => 'nullable|numeric',
'jumlah_unit' => 'nullable|integer',
'jumlah_titik' => 'nullable|integer',
'pakai_pipa_besi' => 'nullable',
'jenis_pengangkatan' => 'nullable',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validasi gagal',
'errors' => $validator->errors()
], 422);
}
DB::beginTransaction();
$tarif = TarifPekerjaan::where('jenis_pekerjaan', $request->jenis_pekerjaan)
->where('is_active', true);
if ($request->filled('dimensi_pipa')) {
$tarif->where('dimensi_pipa', $request->dimensi_pipa);
}
if ($request->has('pakai_pipa_besi')) {
$tarif->where('pakai_pipa_besi', $request->pakai_pipa_besi);
}
$tarif = $tarif->first();
$nilaiItem = $this->hitungNilaiItem($tarif, $request->all());
\App\Models\PenugasanItem::create([
'id_penugasan' => $penugasan->id_penugasan,
'id_tarif' => $tarif ? $tarif->id_tarif : null,
'jenis_pekerjaan' => $request->jenis_pekerjaan,
'dimensi_pipa' => $request->dimensi_pipa,
'jarak_meter' => $request->jarak_meter,
'jumlah_unit' => $request->jumlah_unit,
'jumlah_titik' => $request->jumlah_titik,
'pakai_pipa_besi' => $request->pakai_pipa_besi,
'jenis_pengangkatan' => $request->jenis_pengangkatan,
'total_nilai_pekerjaan' => $nilaiItem,
]);
$total = \App\Models\PenugasanItem::where('id_penugasan', $penugasan->id_penugasan)
->sum('total_nilai_pekerjaan');
$penugasan->total_nilai_pekerjaan = $total;
$penugasan->save();
DB::commit();
return response()->json([
'success' => true,
'message' => 'Rincian pekerjaan berhasil ditambahkan!',
'data' => [
'nilai_item' => $nilaiItem,
'total_baru' => $penugasan->total_nilai_pekerjaan
]
]);
} catch (Exception $e) {
DB::rollBack();
return response()->json([
'success' => false,
'message' => 'Gagal menambah rincian: ' . $e->getMessage()
], 500);
}
}
/**
* PUT - Update status pekerjaan
*
* ✅ FIX: Ketika status diubah menjadi 'selesai', semua anggota tim
* otomatis dicatat status_kehadiran = 'hadir' di tabel tim_teknisi_penugasans.
* Ini memastikan semua anggota tim terhitung gajinya meskipun
* bukan dia yang menerima/menceklis tugas di awal.
*/
public function updateStatus(Request $request, $id)
{
try {
$penugasan = Penugasan::findOrFail($id);
$validator = Validator::make($request->all(), [
'status_pekerjaan' => 'required|in:belum_mulai,dalam_proses,selesai,dibatalkan',
'tanggal_diselesaikan' => 'required_if:status_pekerjaan,selesai|nullable|date',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validasi gagal',
'errors' => $validator->errors()
], 422);
}
DB::beginTransaction();
$updateData = ['status_pekerjaan' => $request->status_pekerjaan];
if ($request->status_pekerjaan === 'selesai') {
$updateData['tanggal_diselesaikan'] = $request->tanggal_diselesaikan ?? now();
// ✅ FIX: Pastikan semua anggota tim tercatat hadir
// sehingga gaji semua anggota tim terhitung dengan benar
TimTeknisiPenugasan::where('id_penugasan', $penugasan->id_penugasan)
->update(['status_kehadiran' => 'hadir']);
// Recalculate total_nilai_pekerjaan if it's empty or zero
if (empty($penugasan->total_nilai_pekerjaan) || $penugasan->total_nilai_pekerjaan <= 0) {
$penugasan->load(['items.tarif']);
$totalNilai = 0;
if ($penugasan->items && $penugasan->items->count() > 0) {
foreach ($penugasan->items as $item) {
$itemTotal = (float) $item->total_nilai_pekerjaan;
if ($itemTotal <= 0) {
$tarif = $item->tarif;
if (!$tarif) {
$tarifQuery = TarifPekerjaan::where('jenis_pekerjaan', $item->jenis_pekerjaan)
->where('is_active', true);
if ($item->dimensi_pipa) {
$tarifQuery->where('dimensi_pipa', $item->dimensi_pipa);
}
$tarif = $tarifQuery->first();
}
$itemTotal = $this->hitungNilaiItem($tarif, $item->toArray());
if ($itemTotal > 0) {
$item->update(['total_nilai_pekerjaan' => $itemTotal]);
}
}
$totalNilai += $itemTotal;
}
}
if ($totalNilai <= 0 && $penugasan->jenis_pekerjaan) {
$tarif = $penugasan->tarif;
if (!$tarif) {
$tarifQuery = TarifPekerjaan::where('jenis_pekerjaan', $penugasan->jenis_pekerjaan)
->where('is_active', true);
if ($penugasan->dimensi_pipa) {
$tarifQuery->where('dimensi_pipa', $penugasan->dimensi_pipa);
}
$tarif = $tarifQuery->first();
}
if ($tarif) {
if ($penugasan->jarak_meter > 0 && $tarif->tarif_per_meter) {
$totalNilai = (float) $tarif->tarif_per_meter * (float) $penugasan->jarak_meter;
} elseif ($penugasan->jumlah_unit > 0 && $tarif->tarif_per_unit) {
$totalNilai = (float) $tarif->tarif_per_unit * (int) $penugasan->jumlah_unit;
} elseif ($penugasan->jumlah_titik > 0 && $tarif->tarif_per_unit) {
$totalNilai = (float) $tarif->tarif_per_unit * (int) $penugasan->jumlah_titik;
} else {
$totalNilai = (float) ($tarif->tarif_per_unit ?? $tarif->tarif_per_meter ?? 0);
}
}
}
if ($totalNilai > 0) {
$updateData['total_nilai_pekerjaan'] = $totalNilai;
}
}
}
$penugasan->update($updateData);
DB::commit();
return response()->json([
'success' => true,
'message' => 'Status pekerjaan berhasil diupdate!',
'data' => $penugasan
]);
} catch (Exception $e) {
DB::rollBack();
return response()->json([
'success' => false,
'message' => 'Gagal update status: ' . $e->getMessage()
], 500);
}
}
/**
* POST - Upload foto sebelum/sesudah
*/
public function uploadFoto(Request $request, $id)
{
try {
$penugasan = Penugasan::findOrFail($id);
\Illuminate\Support\Facades\Log::info('Upload Foto Request Received', [
'id_penugasan' => $id,
'tipe_foto' => $request->tipe_foto,
'has_file' => $request->hasFile('foto'),
]);
$validator = Validator::make($request->all(), [
'tipe_foto' => 'required|in:sebelum,sesudah',
'foto' => 'nullable|image|mimes:jpeg,png,jpg|max:10240',
'sebelum_base64' => 'nullable|string',
'sesudah_base64' => 'nullable|string',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validasi gagal',
'errors' => $validator->errors()
], 422);
}
$tipeFoto = $request->tipe_foto;
$fotoPath = null;
if ($request->hasFile('foto')) {
$fotoPath = $request->file('foto')->store("penugasan/foto-{$tipeFoto}", 'public');
} elseif ($request->input($tipeFoto . '_base64')) {
$fotoPath = $this->storeBase64($request->input($tipeFoto . '_base64'), "penugasan/foto-{$tipeFoto}");
}
if (!$fotoPath) {
return response()->json([
'success' => false,
'message' => 'Tidak ada foto yang diupload'
], 422);
}
if ($tipeFoto === 'sebelum') {
$penugasan->foto_sebelum = $fotoPath;
} else {
$penugasan->foto_sesudah = $fotoPath;
}
$penugasan->save();
return response()->json([
'success' => true,
'message' => "Foto {$tipeFoto} berhasil diupload!",
'data' => [
'foto_url' => asset("storage/{$fotoPath}"),
'foto_path' => $fotoPath
]
]);
} catch (Exception $e) {
return response()->json([
'success' => false,
'message' => 'Gagal upload foto: ' . $e->getMessage()
], 500);
}
}
/**
* GET - Tarif berdasarkan jenis pekerjaan
*/
public function getTarifByJenis(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'jenis_pekerjaan' => 'required|in:sr,pengembangan_jaringan_pipa,pengangkatan,pemasangan_gate_valve,gali_urug,perbaikan_jaringan_pipa,pengecatan_pipa_besi,penyempurnaan_jaringan_pipa',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Jenis pekerjaan tidak valid',
'errors' => $validator->errors()
], 422);
}
$tarifs = TarifPekerjaan::where('jenis_pekerjaan', $request->jenis_pekerjaan)
->where('is_active', true)
->get();
return response()->json([
'success' => true,
'message' => 'Data tarif berhasil diambil',
'data' => $tarifs
]);
} catch (Exception $e) {
return response()->json([
'success' => false,
'message' => 'Gagal mengambil data tarif: ' . $e->getMessage()
], 500);
}
}
/**
* GET - Statistik penugasan teknisi
*/
public function statistik(Request $request)
{
try {
$idTeknisi = $request->input('id_teknisi');
if (!$idTeknisi) {
return response()->json([
'success' => false,
'message' => 'ID Teknisi tidak ditemukan'
], 401);
}
$baseQuery = Penugasan::where(function ($q) use ($idTeknisi) {
$q->where('id_teknisi', $idTeknisi)
->orWhereHas('timTeknisi', function ($sq) use ($idTeknisi) {
$sq->where('id_teknisi', $idTeknisi);
});
});
$statistik = [
'total_penugasan' => (clone $baseQuery)->count(),
'belum_mulai' => (clone $baseQuery)->where('status_pekerjaan', 'belum_mulai')->count(),
'dalam_proses' => (clone $baseQuery)->where('status_pekerjaan', 'dalam_proses')->count(),
'selesai' => (clone $baseQuery)->where('status_pekerjaan', 'selesai')->count(),
'menunggu_detail' => (clone $baseQuery)->whereNull('jenis_pekerjaan')->count(),
'detail_lengkap' => (clone $baseQuery)->whereNotNull('jenis_pekerjaan')->count(),
];
return response()->json([
'success' => true,
'message' => 'Statistik berhasil diambil',
'data' => $statistik
]);
} catch (Exception $e) {
return response()->json([
'success' => false,
'message' => 'Gagal mengambil statistik: ' . $e->getMessage()
], 500);
}
}
/**
* GET - Daftar teknisi aktif untuk tambah tim
*/
public function getTeknisiList()
{
try {
$teknisi = Teknisi::where('status', 'aktif')
->orderBy('nama')
->get(['id_teknisi', 'nama', 'no_telepon', 'spesialisasi']);
return response()->json([
'success' => true,
'message' => 'Daftar teknisi berhasil diambil',
'data' => $teknisi
]);
} catch (Exception $e) {
return response()->json([
'success' => false,
'message' => 'Gagal mengambil data teknisi: ' . $e->getMessage()
], 500);
}
}
// ===================================
// PRIVATE HELPER
// ===================================
private function hitungNilaiItem($tarif, array $data): float
{
if (!$tarif) return 0;
if ($tarif->tarif_per_meter && !empty($data['jarak_meter'])) {
return (float)$tarif->tarif_per_meter * (float)$data['jarak_meter'];
}
if ($tarif->tarif_per_unit && !empty($data['jumlah_unit'])) {
return (float)$tarif->tarif_per_unit * (int)$data['jumlah_unit'];
}
if ($tarif->tarif_per_unit && !empty($data['jumlah_titik'])) {
return (float)$tarif->tarif_per_unit * (int)$data['jumlah_titik'];
}
return (float)($tarif->tarif_per_unit ?? 0);
}
private function storeBase64($base64String, $folder)
{
try {
if (preg_match("/^data:image\/(\w+);base64,/", $base64String, $type)) {
$base64String = substr($base64String, strpos($base64String, ",") + 1);
$type = strtolower($type[1]);
} else {
$type = "jpg";
}
$image = base64_decode($base64String);
if ($image === false) return null;
$fileName = \Illuminate\Support\Str::random(40) . "." . $type;
$path = $folder . "/" . $fileName;
\Illuminate\Support\Facades\Storage::disk("public")->put($path, $image);
return $path;
} catch (Exception $e) {
\Illuminate\Support\Facades\Log::error("Base64 Store Error: " . $e->getMessage());
return null;
}
}
}