854 lines
34 KiB
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;
|
|
}
|
|
}
|
|
} |