fix form create update postcheck and precheck

This commit is contained in:
2025-09-19 20:44:28 +07:00
parent cab0d2e9a8
commit db4c586535
22 changed files with 4977 additions and 224 deletions

View File

@@ -55,7 +55,6 @@ class AdminController extends Controller
} }
$ajax_url = route('dashboard_data').'?month='.$month.'&year='.$year.'&dealer='.$dealer; $ajax_url = route('dashboard_data').'?month='.$month.'&year='.$year.'&dealer='.$dealer;
// dd($ajax_url);
return view('dashboard', compact('month','year', 'ajax_url', 'dealer', 'dealer_datas')); return view('dashboard', compact('month','year', 'ajax_url', 'dealer', 'dealer_datas'));
} }
@@ -134,7 +133,6 @@ class AdminController extends Controller
$dealer_work_trx = DB::statement("PREPARE stmt FROM @sql"); $dealer_work_trx = DB::statement("PREPARE stmt FROM @sql");
$dealer_work_trx = DB::select(DB::raw("EXECUTE stmt")); $dealer_work_trx = DB::select(DB::raw("EXECUTE stmt"));
DB::statement('DEALLOCATE PREPARE stmt'); DB::statement('DEALLOCATE PREPARE stmt');
// DD($dealer_work_trx);
$theads = ['DEALER']; $theads = ['DEALER'];
$dealer_names = []; $dealer_names = [];
$dealer_trx = []; $dealer_trx = [];
@@ -165,7 +163,6 @@ class AdminController extends Controller
$dealer_names[] = $dealer_work->DEALER; $dealer_names[] = $dealer_work->DEALER;
} }
// dd($dealer_trx);
$dealer_trx = array_values($dealer_trx); $dealer_trx = array_values($dealer_trx);
$dealer = $request->dealer; $dealer = $request->dealer;
$month = $request->month; $month = $request->month;
@@ -319,7 +316,6 @@ class AdminController extends Controller
$prev_mth_end = $prev_mth[0].'-'.$prev_mth[1].'-'.date('t', strtotime($prev_mth_start)); $prev_mth_end = $prev_mth[0].'-'.$prev_mth[1].'-'.date('t', strtotime($prev_mth_start));
} }
// dd($prev_mth_end);
$yesterday_month_trx = Transaction::where('work_id', $work1->id)->where('dealer_id', $dealer->id)->whereDate('date', '>=', $prev_mth_start)->whereDate('date', '<=', $prev_mth_end)->sum('qty'); $yesterday_month_trx = Transaction::where('work_id', $work1->id)->where('dealer_id', $dealer->id)->whereDate('date', '>=', $prev_mth_start)->whereDate('date', '<=', $prev_mth_end)->sum('qty');
if(array_key_exists($work1->id, $prev_month_trxs_total)) { if(array_key_exists($work1->id, $prev_month_trxs_total)) {
@@ -528,16 +524,12 @@ class AdminController extends Controller
// $month_trxs_total = array_values($month_trxs_total); // $month_trxs_total = array_values($month_trxs_total);
// $yesterday_month_trxs_total = array_values($yesterday_month_trxs_total); // $yesterday_month_trxs_total = array_values($yesterday_month_trxs_total);
// dd(["month_trxs_total" => $month_trxs_total, "yesterday_month_trxs_total" => $yesterday_month_trxs_total, "works" => $works->toArray()]);
// dd($month_trxs_total);
// dd($yesterday_month_trxs_total);
$final_month_trxs_total = []; $final_month_trxs_total = [];
$final_yesterday_month_trxs_total = []; $final_yesterday_month_trxs_total = [];
foreach($works as $work1) { foreach($works as $work1) {
$final_month_trxs_total[$work1->id] = array_key_exists($work1->id, $month_trxs_total) ? $month_trxs_total[$work1->id] : 0; $final_month_trxs_total[$work1->id] = array_key_exists($work1->id, $month_trxs_total) ? $month_trxs_total[$work1->id] : 0;
$final_yesterday_month_trxs_total[$work1->id] = $yesterday_month_trxs_total[$work1->id]; $final_yesterday_month_trxs_total[$work1->id] = $yesterday_month_trxs_total[$work1->id];
} }
// dd([$final_month_trxs_total, $final_yesterday_month_trxs_total]);
$month_trxs_total = array_values($final_month_trxs_total); $month_trxs_total = array_values($final_month_trxs_total);
$yesterday_month_trxs_total = array_values($final_yesterday_month_trxs_total); $yesterday_month_trxs_total = array_values($final_yesterday_month_trxs_total);
$totals = []; $totals = [];

View File

@@ -93,7 +93,6 @@ class ApiController extends Controller
$prev_mth_end = $prev_mth[0].'-'.$prev_mth[1].'-'.date('t'); $prev_mth_end = $prev_mth[0].'-'.$prev_mth[1].'-'.date('t');
} }
// dd($prev_mth_end);
$yesterday_month_trx = Transaction::where('work_id', $work1->id)->where('dealer_id', $id)->whereDate('date', '>=', $prev_mth_start)->whereDate('date', '<=', $prev_mth_end)->sum('qty'); $yesterday_month_trx = Transaction::where('work_id', $work1->id)->where('dealer_id', $id)->whereDate('date', '>=', $prev_mth_start)->whereDate('date', '<=', $prev_mth_end)->sum('qty');
if(array_key_exists($work1->id, $yesterday_month_trxs_total)) { if(array_key_exists($work1->id, $yesterday_month_trxs_total)) {
@@ -153,7 +152,6 @@ class ApiController extends Controller
$final_month_trxs_total[$work1->id] = $month_trxs_total[$work1->id]; $final_month_trxs_total[$work1->id] = $month_trxs_total[$work1->id];
$final_yesterday_month_trxs_total[$work1->id] = $yesterday_month_trxs_total[$work1->id]; $final_yesterday_month_trxs_total[$work1->id] = $yesterday_month_trxs_total[$work1->id];
} }
// dd([$final_month_trxs_total, $final_yesterday_month_trxs_total]);
$month_trxs_total = array_values($final_month_trxs_total); $month_trxs_total = array_values($final_month_trxs_total);
$yesterday_month_trxs_total = array_values($final_yesterday_month_trxs_total); $yesterday_month_trxs_total = array_values($final_yesterday_month_trxs_total);

View File

@@ -472,7 +472,6 @@ class ReportController extends Controller
$sa_names = json_encode($sa_names); $sa_names = json_encode($sa_names);
$trx_data = json_encode(array_values($trx_data)); $trx_data = json_encode(array_values($trx_data));
// dd($trx_data);
$work_count = count($works); $work_count = count($works);
$month = $request->month; $month = $request->month;
$dealer_id = $request->dealer; $dealer_id = $request->dealer;
@@ -707,7 +706,24 @@ class ReportController extends Controller
->leftJoin('works as w', 'w.id', '=', 'transactions.work_id') ->leftJoin('works as w', 'w.id', '=', 'transactions.work_id')
->leftJoin('categories as cat', 'cat.id', '=', 'w.category_id') ->leftJoin('categories as cat', 'cat.id', '=', 'w.category_id')
->leftJoin('dealers as d', 'd.id', '=', 'transactions.dealer_id') ->leftJoin('dealers as d', 'd.id', '=', 'transactions.dealer_id')
->select('transactions.id', 'transactions.status', 'transactions.user_id as user_id', 'transactions.user_sa_id as user_sa_id', 'users.name as username', 'sa.name as sa_name', 'cat.name as category_name', 'w.name as workname', 'transactions.qty as qty', 'transactions.date as date', 'transactions.police_number as police_number', 'transactions.warranty as warranty', 'transactions.spk as spk', 'transactions.dealer_id', 'd.name as dealer_name'); ->leftJoin('prechecks as pre', 'pre.transaction_id', '=', 'transactions.id')
->leftJoin('postchecks as post', 'post.transaction_id', '=', 'transactions.id')
->select(
'transactions.id',
'transactions.status',
'users.name as username',
'sa.name as sa_name',
'cat.name as category_name',
'w.name as workname',
'transactions.qty as qty',
'transactions.date as date',
'transactions.police_number as police_number',
'transactions.warranty as warranty',
'transactions.spk as spk',
'd.name as dealer_name',
DB::raw('pre.id as precheck_id'),
DB::raw('post.id as postcheck_id')
);
// Filter by allowed dealers based on user role // Filter by allowed dealers based on user role
if($allowedDealers->count() > 0) { if($allowedDealers->count() > 0) {
@@ -747,22 +763,68 @@ class ReportController extends Controller
$data->orderBy('date', 'DESC'); $data->orderBy('date', 'DESC');
return DataTables::of($data)->addIndexColumn() return DataTables::of($data)->addIndexColumn()
->addColumn('action', function($row) use ($menu) { ->addColumn('action', function($row) use ($menu) {
$btn = '<div class="d-flex justify-content-center">'; $btn = '<div class="d-flex justify-content-center align-items-center flex-wrap">';
if($row->status == 1) { // Jika status closed
if(Gate::allows('delete', $menu)) { if ($row->status == 1) {
$btn .= ' <button class="btn btn-danger btn-sm btn-bold mr-2" data-action="'. route('report.transaction.destroy', $row->id) .'" id="destroyTransaction'. $row->id .'" onclick="destroyTransaction('. $row->id .')"> Hapus </button>'; if (Gate::allows('delete', $menu)) {
} $btn .= '<button class="btn btn-danger btn-sm font-weight-bold mr-2 mt-2"
$btn .= '<span class="badge badge-success">Closed</span>'; data-action="'. route('report.transaction.destroy', $row->id) .'"
}else{ id="destroyTransaction'. $row->id .'"
if(Gate::allows('delete', $menu)) { onclick="destroyTransaction('. $row->id .')">
$btn .= '<button class="btn btn-danger btn-sm btn-bold mr-2" data-action="'. route('report.transaction.destroy', $row->id) .'" id="destroyTransaction'. $row->id .'" onclick="destroyTransaction('. $row->id .')"> Hapus </button>'; Hapus
</button>';
} }
if(Gate::allows('update', $menu)) { // Badge Closed rapi
$btn .= '<button class="btn btn-info btn-sm btn-bold mr-2" data-url="'. route('report.transaction.edit', $row->id) .'" data-action="'. route('report.transaction.update', $row->id) .'" onclick="editTransaction('. $row->id .')" id="editTransaction'. $row->id .'"> Edit </button> $btn .= '<span class="btn btn-success btn-sm font-weight-bold px-3 py-2 mr-2 mt-2 disabled"
<button class="btn btn-warning btn-sm btn-bold" id="closeTransaction'. $row->id .'" data-url="'. route('report.transaction.close', $row->id) .'" onclick="closeTransaction('. $row->id .')"> Close </button>'; style="pointer-events: none; cursor: default;">
Closed
</span>';
} else {
if (Gate::allows('delete', $menu)) {
$btn .= '<button class="btn btn-danger btn-sm font-weight-bold mr-2 mt-2"
data-action="'. route('report.transaction.destroy', $row->id) .'"
id="destroyTransaction'. $row->id .'"
onclick="destroyTransaction('. $row->id .')">
Hapus
</button>';
} }
if (Gate::allows('update', $menu)) {
$btn .= '<button class="btn btn-info btn-sm font-weight-bold mr-2 mt-2"
data-url="'. route('report.transaction.edit', $row->id) .'"
data-action="'. route('report.transaction.update', $row->id) .'"
onclick="editTransaction('. $row->id .')"
id="editTransaction'. $row->id .'">
Edit
</button>';
$btn .= '<button class="btn btn-warning btn-sm font-weight-bold mr-2 mt-2"
id="closeTransaction'. $row->id .'"
data-url="'. route('report.transaction.close', $row->id) .'"
onclick="closeTransaction('. $row->id .')">
Close
</button>';
}
}
if ($row->precheck_id) {
$btn .= '<button class="btn btn-primary btn-sm font-weight-bold action-print mr-2 mt-2"
data-type="precheck"
data-id="'. $row->id .'"
data-url="'. route('report.transaction.precheck.print', $row->id) .'">
Pre Check
</button>';
}
if ($row->postcheck_id) {
$btn .= '<button class="btn btn-success btn-sm font-weight-bold action-print mr-2 mt-2"
data-type="postcheck"
data-id="'. $row->id .'"
data-url="'. route('report.transaction.postcheck.print', $row->id) .'">
Post Check
</button>';
} }
$btn .= '</div>'; $btn .= '</div>';

View File

@@ -15,6 +15,9 @@ use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use App\Models\Precheck;
use App\Models\Postcheck;
use Illuminate\Support\Facades\Log;
use Exception; use Exception;
class TransactionController extends Controller class TransactionController extends Controller
@@ -519,7 +522,6 @@ class TransactionController extends Controller
$works_count = count($works); $works_count = count($works);
$share = $month_trxs; $share = $month_trxs;
$month = $request->month; $month = $request->month;
dd($share);
return view('transaction.recap', compact('month_trxs_total', 'yesterday_month_trxs_total', 'month', 'trx_data', 'sa_names', 'works', 'works_count', 'trxs', 'month_trxs','dealer', 'share', 'mechanic')); return view('transaction.recap', compact('month_trxs_total', 'yesterday_month_trxs_total', 'month', 'trx_data', 'sa_names', 'works', 'works_count', 'trxs', 'month_trxs','dealer', 'share', 'mechanic'));
} }
@@ -568,7 +570,6 @@ class TransactionController extends Controller
$prev_mth_end = $prev_mth[0].'-'.$prev_mth[1].'-'.date('t'); $prev_mth_end = $prev_mth[0].'-'.$prev_mth[1].'-'.date('t');
} }
// dd($prev_mth_end);
$yesterday_month_trx = Transaction::whereNull('deleted_at')->where('work_id', $work1->id)->where('dealer_id', $id)->whereDate('date', '>=', $prev_mth_start)->whereDate('date', '<=', $prev_mth_end)->sum('qty'); $yesterday_month_trx = Transaction::whereNull('deleted_at')->where('work_id', $work1->id)->where('dealer_id', $id)->whereDate('date', '>=', $prev_mth_start)->whereDate('date', '<=', $prev_mth_end)->sum('qty');
if(array_key_exists($work1->id, $yesterday_month_trxs_total)) { if(array_key_exists($work1->id, $yesterday_month_trxs_total)) {
@@ -678,15 +679,12 @@ class TransactionController extends Controller
} }
} }
// dd($works);
// dd([$month_trxs_total, $yesterday_month_trxs_total]);
$final_month_trxs_total = []; $final_month_trxs_total = [];
$final_yesterday_month_trxs_total = []; $final_yesterday_month_trxs_total = [];
foreach($works as $work1) { foreach($works as $work1) {
$final_month_trxs_total[$work1->id] = $month_trxs_total[$work1->id]; $final_month_trxs_total[$work1->id] = $month_trxs_total[$work1->id];
$final_yesterday_month_trxs_total[$work1->id] = $yesterday_month_trxs_total[$work1->id]; $final_yesterday_month_trxs_total[$work1->id] = $yesterday_month_trxs_total[$work1->id];
} }
// dd([$final_month_trxs_total, $final_yesterday_month_trxs_total]);
$month_trxs_total = array_values($final_month_trxs_total); $month_trxs_total = array_values($final_month_trxs_total);
$yesterday_month_trxs_total = array_values($final_yesterday_month_trxs_total); $yesterday_month_trxs_total = array_values($final_yesterday_month_trxs_total);
@@ -994,19 +992,31 @@ class TransactionController extends Controller
public function update(Request $request, $id) public function update(Request $request, $id)
{ {
Transaction::find($id)->update([ $request->validate([
'spk' => 'required|string|max:255',
'date' => 'required|date',
'police_number' => 'required|string|max:255',
'work_id' => 'required|exists:works,id',
'qty' => 'required|integer|min:1',
'warranty' => 'required|in:0,1',
'user_sa_id' => 'required|exists:users,id',
]);
$transaction = Transaction::findOrFail($id);
$transaction->update([
"spk" => $request->spk, "spk" => $request->spk,
"date" => $request->date, "date" => $request->date,
"police_number" => $request->police_number, "police_number" => $request->police_number,
"work_id" => $request->work_id, "work_id" => $request->work_id,
"qty" => $request->qty, "qty" => $request->qty,
"warranty" => $request->warranty, "warranty" => $request->warranty,
"user_sa_id" => $request->sa_id, "user_sa_id" => $request->user_sa_id,
]); ]);
$response = [ $response = [
"status" => 200, "status" => 200,
"message" => "Data updated successfully" "message" => "Transaksi berhasil diperbarui"
]; ];
return response()->json($response); return response()->json($response);
@@ -1147,6 +1157,8 @@ class TransactionController extends Controller
'sa_name' => $transaction->sa_name, 'sa_name' => $transaction->sa_name,
'status' => $this->getStatusBadge($transaction->status), 'status' => $this->getStatusBadge($transaction->status),
'action' => $this->getActionButtons($transaction), 'action' => $this->getActionButtons($transaction),
'action_precheck' => $this->getActionButtonsPrecheck($transaction),
'action_postcheck' => $this->getActionButtonsPostcheck($transaction),
'claimed_at' => $transaction->claimed_at, 'claimed_at' => $transaction->claimed_at,
'claimed_by' => $transaction->claimed_by 'claimed_by' => $transaction->claimed_by
]; ];
@@ -1259,41 +1271,111 @@ class TransactionController extends Controller
{ {
$buttons = ''; $buttons = '';
// Only show buttons for mechanics // Edit button - show for all users (not just mechanics)
$buttons .= '<button class="btn btn-sm btn-warning mr-1"
data-action="' . route('transaction.update', $transaction->id) . '"
data-url="' . route('transaction.edit', $transaction->id) . '"
onclick="editTransaction(' . $transaction->id . ')"
id="editTransaction' . $transaction->id . '"
title="Edit Transaksi"
style="font-size: 11px; padding: 4px 8px;">
<i class="fas fa-edit"></i> Edit
</button>';
// Delete button - show for all users
$buttons .= '<button class="btn btn-sm btn-danger mr-1"
onclick="deleteTransaction(' . $transaction->id . ')"
title="Hapus Transaksi"
style="font-size: 11px; padding: 4px 8px;">
<i class="fas fa-trash"></i> Hapus
</button>';
// Only show claim buttons for mechanics
if (Auth::user()->role_id == 3) { if (Auth::user()->role_id == 3) {
// Claim button - show only if not claimed yet // Claim button - show only if not claimed yet
if (empty($transaction->claimed_at) && empty($transaction->claimed_by)) { if (empty($transaction->claimed_at) && empty($transaction->claimed_by)) {
$buttons .= '<button class="btn btn-sm btn-success mr-1" onclick="claimTransaction(' . $transaction->id . ')" title="Klaim Pekerjaan">'; $buttons .= '<button class="btn btn-sm btn-success mr-1" onclick="claimTransaction(' . $transaction->id . ')" title="Klaim Pekerjaan" style="font-size: 11px; padding: 4px 8px;">';
$buttons .= 'Klaim'; $buttons .= '<i class="fas fa-hand-paper"></i> Klaim';
$buttons .= '</button>'; $buttons .= '</button>';
} else { } else {
if($transaction->claimed_by == Auth::user()->id) { if ($transaction->claimed_by == Auth::user()->id) {
// Check if precheck exists $precheck = Precheck::where('transaction_id', $transaction->id)->first();
$precheck = \App\Models\Precheck::where('transaction_id', $transaction->id)->first(); $postcheck = Postcheck::where('transaction_id', $transaction->id)->first();
if (!$precheck) {
$buttons .= '<a href="/transaction/prechecks/' . $transaction->id . '" class="btn btn-sm btn-warning mr-1" title="Precheck">'; if ($precheck && $postcheck) {
$buttons .= 'Precheck'; $buttons .= '<span class="badge badge-success" style="font-size: 10px;"><i class="fas fa-check"></i> Selesai</span>';
$buttons .= '</a>';
} else {
// Check if postcheck exists
$postcheck = \App\Models\Postcheck::where('transaction_id', $transaction->id)->first();
if (!$postcheck) {
$buttons .= '<a href="/transaction/postchecks/' . $transaction->id . '" class="btn btn-sm btn-info mr-1" title="Postcheck">';
$buttons .= 'Postcheck';
$buttons .= '</a>';
} else {
$buttons .= '<span class="badge badge-success">Selesai</span>';
} }
} }
} $buttons .= '<span class="badge badge-info" style="font-size: 10px;"><i class="fas fa-check-circle"></i> Sudah Diklaim</span>';
$buttons .= '<span class="badge badge-info">Sudah Diklaim</span>';
} }
} }
return $buttons; return $buttons;
} }
private function getActionButtonsPrecheck($transaction)
{
$buttons = '';
if (Auth::user()->role_id == 3) {
$precheck = Precheck::where('transaction_id', $transaction->id)->first();
if ($precheck) {
$buttons .= '<a href="' . route('prechecks.edit', [$transaction->id, $precheck->id]) . '"
class="btn btn-sm btn-warning mr-1" title="Edit Precheck" style="font-size: 11px; padding: 4px 8px;">
<i class="fas fa-edit"></i> Edit
</a>';
$buttons .= '<a href="' . route('prechecks.print', $transaction->id) . '"
class="btn btn-sm btn-primary mr-1" title="Lihat Precheck" target="_blank" style="font-size: 11px; padding: 4px 8px;">
<i class="fas fa-eye"></i> Lihat
</a>';
} else {
if (empty($transaction->claimed_at) && empty($transaction->claimed_by)) {
$buttons .= '<span class="badge badge-danger" style="font-size: 10px;">Transaksi Belum Diklaim</span>';
}else{
$buttons .= '<a href="' . route('prechecks.create', $transaction->id) . '"
class="btn btn-sm btn-success mr-1" title="Tambah Precheck" style="font-size: 11px; padding: 4px 8px;">
<i class="fas fa-plus"></i> Tambah
</a>';
}
}
}
return $buttons;
}
private function getActionButtonsPostcheck($transaction)
{
$buttons = '';
if (Auth::user()->role_id == 3) {
$postcheck = Postcheck::where('transaction_id', $transaction->id)->first();
$precheck = Precheck::where('transaction_id', $transaction->id)->first();
if($precheck){
if ($postcheck) {
$buttons .= '<a href="' . route('postchecks.edit', [$transaction->id, $postcheck->id]) . '"
class="btn btn-sm btn-warning mr-1" title="Edit Postcheck" style="font-size: 11px; padding: 4px 8px;">
<i class="fas fa-edit"></i> Edit
</a>';
$buttons .= '<a href="' . route('postchecks.print', $transaction->id) . '"
class="btn btn-sm btn-primary mr-1" title="Lihat Postcheck" target="_blank" style="font-size: 11px; padding: 4px 8px;">
<i class="fas fa-eye"></i> Lihat
</a>';
} else {
$buttons .= '<a href="' . route('postchecks.create', $transaction->id) . '"
class="btn btn-sm btn-success mr-1" title="Tambah Postcheck" style="font-size: 11px; padding: 4px 8px;">
<i class="fas fa-plus"></i> Tambah
</a>';
}
}else{
$buttons .= '<span class="badge badge-danger" style="font-size: 10px;">Precheck Belum Disimpan</span>';
}
}
return $buttons;
}
/** /**
* Get KPI data for AJAX refresh * Get KPI data for AJAX refresh
*/ */

View File

@@ -11,14 +11,14 @@ use Illuminate\Support\Facades\Log;
class PostchecksController extends Controller class PostchecksController extends Controller
{ {
public function index(Transaction $transaction) public function create(Transaction $transaction)
{ {
$acConditions = Postcheck::getAcConditionOptions(); $acConditions = Postcheck::getAcConditionOptions();
$blowerConditions = Postcheck::getBlowerConditionOptions(); $blowerConditions = Postcheck::getBlowerConditionOptions();
$evaporatorConditions = Postcheck::getEvaporatorConditionOptions(); $evaporatorConditions = Postcheck::getEvaporatorConditionOptions();
$compressorConditions = Postcheck::getCompressorConditionOptions(); $compressorConditions = Postcheck::getCompressorConditionOptions();
return view('transaction.postchecks', compact( return view('transaction.postchecks.create', compact(
'transaction', 'transaction',
'acConditions', 'acConditions',
'blowerConditions', 'blowerConditions',
@@ -62,76 +62,15 @@ class PostchecksController extends Controller
'compressor_condition' => $request->compressor_condition, 'compressor_condition' => $request->compressor_condition,
'postcheck_notes' => $request->postcheck_notes, 'postcheck_notes' => $request->postcheck_notes,
]; ];
// Handle file uploads securely
// Handle file uploads
$imageFields = [ $imageFields = [
'front_image', 'cabin_temperature_image', 'ac_image', 'front_image', 'cabin_temperature_image', 'ac_image',
'blower_image', 'evaporator_image' 'blower_image', 'evaporator_image'
]; ];
foreach ($imageFields as $field) { foreach ($imageFields as $field) {
if ($request->hasFile($field) && $request->file($field)->isValid()) { $storedPath = $this->processImageUpload($request, $field, $transaction);
try { if ($storedPath) {
$file = $request->file($field); $data[$field] = $storedPath;
// Generate unique filename with transaction ID
$filename = time() . '_' . uniqid() . '_' . $transaction->id . '_' . $field . '.' . $file->getClientOriginalExtension();
// Create directory path: transactions/{transaction_id}/postcheck/
$directory = 'transactions/' . $transaction->id . '/postcheck';
// Ensure base storage directory exists
$this->ensureStorageDirectoryExists();
// Ensure transactions directory exists
if (!Storage::disk('public')->exists('transactions')) {
Storage::disk('public')->makeDirectory('transactions', 0755, true);
Log::info('Created transactions directory');
}
// Ensure transaction ID directory exists
$transactionDir = 'transactions/' . $transaction->id;
if (!Storage::disk('public')->exists($transactionDir)) {
Storage::disk('public')->makeDirectory($transactionDir, 0755, true);
Log::info('Created transaction directory: ' . $transactionDir);
}
// Ensure postcheck directory exists
if (!Storage::disk('public')->exists($directory)) {
Storage::disk('public')->makeDirectory($directory, 0755, true);
Log::info('Created postcheck directory: ' . $directory);
}
// Store file in organized directory structure
$path = $file->storeAs($directory, $filename, 'public');
// Store file path
$data[$field] = $path;
// Store metadata
$data[$field . '_metadata'] = [
'original_name' => $file->getClientOriginalName(),
'size' => $file->getSize(),
'mime_type' => $file->getMimeType(),
'uploaded_at' => now()->toISOString(),
'transaction_id' => $transaction->id,
'filename' => $filename,
];
Log::info('File uploaded successfully: ' . $path);
} catch (\Exception $e) {
// Log error for debugging
Log::error('File upload failed: ' . $e->getMessage(), [
'field' => $field,
'file' => $file->getClientOriginalName(),
'transaction_id' => $transaction->id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return back()->withErrors(['error' => 'Gagal mengupload file: ' . $field . '. Error: ' . $e->getMessage()]);
}
} }
} }
@@ -144,6 +83,91 @@ class PostchecksController extends Controller
} }
} }
public function edit(Transaction $transaction, Postcheck $postcheck)
{
$acConditions = Postcheck::getAcConditionOptions();
$blowerConditions = Postcheck::getBlowerConditionOptions();
$evaporatorConditions = Postcheck::getEvaporatorConditionOptions();
$compressorConditions = Postcheck::getCompressorConditionOptions();
return view('transaction.postchecks.edit', compact(
'transaction',
'postcheck',
'acConditions',
'blowerConditions',
'evaporatorConditions',
'compressorConditions'
));
}
public function update(Request $request, Transaction $transaction, Postcheck $postcheck)
{
$request->validate([
'kilometer' => 'required|numeric|min:0',
'pressure_high' => 'required|numeric|min:0',
'pressure_low' => 'nullable|numeric|min:0',
'cabin_temperature' => 'nullable|numeric',
'cabin_temperature_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
'ac_condition' => 'nullable|in:' . implode(',', Postcheck::getAcConditionOptions()),
'ac_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
'blower_condition' => 'nullable|in:' . implode(',', Postcheck::getBlowerConditionOptions()),
'blower_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
'evaporator_condition' => 'nullable|in:' . implode(',', Postcheck::getEvaporatorConditionOptions()),
'evaporator_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
'compressor_condition' => 'nullable|in:' . implode(',', Postcheck::getCompressorConditionOptions()),
'postcheck_notes' => 'nullable|string',
'front_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
]);
$updateData = [
'kilometer' => $request->kilometer,
'pressure_high' => $request->pressure_high,
'pressure_low' => $request->pressure_low,
'cabin_temperature' => $request->cabin_temperature,
'ac_condition' => $request->ac_condition,
'blower_condition' => $request->blower_condition,
'evaporator_condition' => $request->evaporator_condition,
'compressor_condition' => $request->compressor_condition,
'postcheck_notes' => $request->postcheck_notes,
];
$imageFields = [
'front_image', 'cabin_temperature_image', 'ac_image',
'blower_image', 'evaporator_image'
];
foreach ($imageFields as $field) {
$newPath = $this->processImageUpload($request, $field, $transaction);
if ($newPath) {
// delete old file if exists
if ($postcheck->{$field}) {
$this->deleteIfExists($postcheck->{$field});
}
$updateData[$field] = $newPath;
}
}
try {
$postcheck->update($updateData);
return redirect()->route('transaction')->with('success', 'Postcheck berhasil diperbarui');
} catch (\Exception $e) {
Log::error('Postcheck update failed: ' . $e->getMessage());
return back()->withErrors(['error' => 'Gagal memperbarui data postcheck. Silakan coba lagi.']);
}
}
public function print($transaction_id)
{
try {
$postcheck = Postcheck::where('transaction_id', $transaction_id)->firstOrFail();
return view('transaction.postchecks.print', compact('postcheck'));
} catch (\Exception $e) {
Log::error('Error printing postcheck: ' . $e->getMessage());
return back()->with('error', 'Gagal membuka halaman print postcheck.');
}
}
/** /**
* Ensure the base storage directory exists * Ensure the base storage directory exists
*/ */
@@ -185,4 +209,69 @@ class PostchecksController extends Controller
rmdir($testDir); rmdir($testDir);
Log::info('Storage directory is properly configured: ' . $storagePath); Log::info('Storage directory is properly configured: ' . $storagePath);
} }
/**
* Securely process image upload to prevent RCE.
* - Only allows jpeg and png
* - Generates safe filename
* - Validates actual image content using getimagesize
*/
private function processImageUpload(Request $request, string $field, Transaction $transaction): ?string
{
if (!($request->hasFile($field) && $request->file($field)->isValid())) {
return null;
}
$file = $request->file($field);
// Double-check mime type from PHP, disallow svg/gif
$allowedMimes = ['image/jpeg' => 'jpg', 'image/png' => 'png'];
$mime = $file->getMimeType();
if (!array_key_exists($mime, $allowedMimes)) {
throw new \RuntimeException('Tipe file tidak diperbolehkan');
}
// Verify it's a real image by reading dimensions
$imageInfo = @getimagesize($file->getRealPath());
if ($imageInfo === false) {
throw new \RuntimeException('File bukan gambar yang valid');
}
// Prepare directory
$directory = 'transactions/' . $transaction->id . '/postcheck';
$this->ensureStorageDirectoryExists();
if (!Storage::disk('public')->exists('transactions')) {
Storage::disk('public')->makeDirectory('transactions', 0755, true);
}
if (!Storage::disk('public')->exists('transactions/' . $transaction->id)) {
Storage::disk('public')->makeDirectory('transactions/' . $transaction->id, 0755, true);
}
if (!Storage::disk('public')->exists($directory)) {
Storage::disk('public')->makeDirectory($directory, 0755, true);
}
// Safe filename
$ext = $allowedMimes[$mime];
$filename = time() . '_' . bin2hex(random_bytes(6)) . '_' . $transaction->id . '_' . $field . '.' . $ext;
// Store
$path = $file->storeAs($directory, $filename, 'public');
Log::info('Secure image stored', ['field' => $field, 'path' => $path]);
return $path;
}
/**
* Delete a file from public storage if it exists
*/
private function deleteIfExists(string $path): void
{
try {
if ($path && Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
} catch (\Throwable $e) {
Log::warning('Failed to delete old image', ['path' => $path, 'error' => $e->getMessage()]);
}
}
} }

View File

@@ -11,14 +11,14 @@ use Illuminate\Support\Facades\Log;
class PrechecksController extends Controller class PrechecksController extends Controller
{ {
public function index(Transaction $transaction) public function create(Transaction $transaction)
{ {
$acConditions = Precheck::getAcConditionOptions(); $acConditions = Precheck::getAcConditionOptions();
$blowerConditions = Precheck::getBlowerConditionOptions(); $blowerConditions = Precheck::getBlowerConditionOptions();
$evaporatorConditions = Precheck::getEvaporatorConditionOptions(); $evaporatorConditions = Precheck::getEvaporatorConditionOptions();
$compressorConditions = Precheck::getCompressorConditionOptions(); $compressorConditions = Precheck::getCompressorConditionOptions();
return view('transaction.prechecks', compact( return view('transaction.prechecks.create', compact(
'transaction', 'transaction',
'acConditions', 'acConditions',
'blowerConditions', 'blowerConditions',
@@ -74,6 +74,11 @@ class PrechecksController extends Controller
try { try {
$file = $request->file($field); $file = $request->file($field);
// Enhanced security validation
if (!$this->isValidImageFile($file)) {
return back()->withErrors(['error' => 'File tidak valid atau berbahaya: ' . $field]);
}
// Generate unique filename with transaction ID // Generate unique filename with transaction ID
$filename = time() . '_' . uniqid() . '_' . $transaction->id . '_' . $field . '.' . $file->getClientOriginalExtension(); $filename = time() . '_' . uniqid() . '_' . $transaction->id . '_' . $field . '.' . $file->getClientOriginalExtension();
@@ -144,6 +149,158 @@ class PrechecksController extends Controller
} }
} }
public function edit(Transaction $transaction, Precheck $precheck)
{
$acConditions = Precheck::getAcConditionOptions();
$blowerConditions = Precheck::getBlowerConditionOptions();
$evaporatorConditions = Precheck::getEvaporatorConditionOptions();
$compressorConditions = Precheck::getCompressorConditionOptions();
return view('transaction.prechecks.edit', compact(
'transaction',
'precheck',
'acConditions',
'blowerConditions',
'evaporatorConditions',
'compressorConditions'
));
}
public function update(Request $request, Transaction $transaction, Precheck $precheck)
{
$request->validate([
'kilometer' => 'required|numeric|min:0',
'pressure_high' => 'required|numeric|min:0',
'pressure_low' => 'nullable|numeric|min:0',
'cabin_temperature' => 'nullable|numeric',
'cabin_temperature_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
'ac_condition' => 'nullable|in:' . implode(',', Precheck::getAcConditionOptions()),
'ac_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
'blower_condition' => 'nullable|in:' . implode(',', Precheck::getBlowerConditionOptions()),
'blower_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
'evaporator_condition' => 'nullable|in:' . implode(',', Precheck::getEvaporatorConditionOptions()),
'evaporator_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
'compressor_condition' => 'nullable|in:' . implode(',', Precheck::getCompressorConditionOptions()),
'precheck_notes' => 'nullable|string',
'front_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
]);
$data = [
'kilometer' => $request->kilometer,
'pressure_high' => $request->pressure_high,
'pressure_low' => $request->pressure_low,
'cabin_temperature' => $request->cabin_temperature,
'ac_condition' => $request->ac_condition,
'blower_condition' => $request->blower_condition,
'evaporator_condition' => $request->evaporator_condition,
'compressor_condition' => $request->compressor_condition,
'precheck_notes' => $request->precheck_notes,
];
// Handle file uploads with security validation
$imageFields = [
'front_image', 'cabin_temperature_image', 'ac_image',
'blower_image', 'evaporator_image'
];
foreach ($imageFields as $field) {
if ($request->hasFile($field) && $request->file($field)->isValid()) {
try {
$file = $request->file($field);
// Enhanced security validation
if (!$this->isValidImageFile($file)) {
return back()->withErrors(['error' => 'File tidak valid atau berbahaya: ' . $field]);
}
// Generate unique filename with transaction ID
$filename = time() . '_' . uniqid() . '_' . $transaction->id . '_' . $field . '.' . $file->getClientOriginalExtension();
// Create directory path: transactions/{transaction_id}/precheck/
$directory = 'transactions/' . $transaction->id . '/precheck';
// Ensure base storage directory exists
$this->ensureStorageDirectoryExists();
// Ensure transactions directory exists
if (!Storage::disk('public')->exists('transactions')) {
Storage::disk('public')->makeDirectory('transactions', 0755, true);
Log::info('Created transactions directory');
}
// Ensure transaction ID directory exists
$transactionDir = 'transactions/' . $transaction->id;
if (!Storage::disk('public')->exists($transactionDir)) {
Storage::disk('public')->makeDirectory($transactionDir, 0755, true);
Log::info('Created transaction directory: ' . $transactionDir);
}
// Ensure precheck directory exists
if (!Storage::disk('public')->exists($directory)) {
Storage::disk('public')->makeDirectory($directory, 0755, true);
Log::info('Created precheck directory: ' . $directory);
}
// Delete old file if exists
if ($precheck->$field && Storage::disk('public')->exists($precheck->$field)) {
Storage::disk('public')->delete($precheck->$field);
}
// Store file in organized directory structure
$path = $file->storeAs($directory, $filename, 'public');
// Store file path
$data[$field] = $path;
// Store metadata
$data[$field . '_metadata'] = [
'original_name' => $file->getClientOriginalName(),
'size' => $file->getSize(),
'mime_type' => $file->getMimeType(),
'uploaded_at' => now()->toISOString(),
'transaction_id' => $transaction->id,
'filename' => $filename,
];
Log::info('File uploaded successfully: ' . $path);
} catch (\Exception $e) {
// Log error for debugging
Log::error('File upload failed: ' . $e->getMessage(), [
'field' => $field,
'file' => $file->getClientOriginalName(),
'transaction_id' => $transaction->id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return back()->withErrors(['error' => 'Gagal mengupload file: ' . $field . '. Error: ' . $e->getMessage()]);
}
}
}
try {
$precheck->update($data);
return redirect()->route('transaction')->with('success', 'Precheck berhasil diperbarui');
} catch (\Exception $e) {
Log::error('Precheck update failed: ' . $e->getMessage());
return back()->withErrors(['error' => 'Gagal memperbarui data precheck. Silakan coba lagi.']);
}
}
public function print($transaction_id)
{
try {
$precheck = Precheck::where('transaction_id', $transaction_id)->firstOrFail();
return view('transaction.prechecks.print', compact('precheck'));
} catch (\Exception $e) {
Log::error('Error printing precheck: ' . $e->getMessage());
return back()->with('error', 'Gagal membuka halaman print precheck.');
}
}
/** /**
* Ensure the base storage directory exists * Ensure the base storage directory exists
*/ */
@@ -185,4 +342,138 @@ class PrechecksController extends Controller
rmdir($testDir); rmdir($testDir);
Log::info('Storage directory is properly configured: ' . $storagePath); Log::info('Storage directory is properly configured: ' . $storagePath);
} }
/**
* Enhanced security validation for image files to prevent RCE attacks
*
* @param \Illuminate\Http\UploadedFile $file
* @return bool
*/
private function isValidImageFile($file)
{
try {
// 1. Check file extension (whitelist approach)
$allowedExtensions = ['jpg', 'jpeg', 'png'];
$extension = strtolower($file->getClientOriginalExtension());
if (!in_array($extension, $allowedExtensions)) {
Log::warning('Invalid file extension: ' . $extension, [
'filename' => $file->getClientOriginalName(),
'user_id' => auth()->id()
]);
return false;
}
// 2. Check MIME type
$allowedMimeTypes = [
'image/jpeg',
'image/jpg',
'image/png'
];
$mimeType = $file->getMimeType();
if (!in_array($mimeType, $allowedMimeTypes)) {
Log::warning('Invalid MIME type: ' . $mimeType, [
'filename' => $file->getClientOriginalName(),
'user_id' => auth()->id()
]);
return false;
}
// 3. Verify file is actually an image using getimagesize
$imageInfo = @getimagesize($file->getPathname());
if ($imageInfo === false) {
Log::warning('File is not a valid image: ' . $file->getClientOriginalName(), [
'user_id' => auth()->id()
]);
return false;
}
// 4. Check image dimensions (prevent extremely large images)
$maxWidth = 5000;
$maxHeight = 5000;
if ($imageInfo[0] > $maxWidth || $imageInfo[1] > $maxHeight) {
Log::warning('Image dimensions too large: ' . $imageInfo[0] . 'x' . $imageInfo[1], [
'filename' => $file->getClientOriginalName(),
'user_id' => auth()->id()
]);
return false;
}
// 5. Check file size (max 20MB)
$maxSize = 20 * 1024 * 1024; // 20MB
if ($file->getSize() > $maxSize) {
Log::warning('File size too large: ' . $file->getSize(), [
'filename' => $file->getClientOriginalName(),
'user_id' => auth()->id()
]);
return false;
}
// 6. Check for suspicious content in filename
$filename = $file->getClientOriginalName();
$suspiciousPatterns = [
'<?php', '<?=', '<script', 'javascript:', 'data:', 'vbscript:',
'..', '~', '$', '`', '|', '&', ';', '(', ')', '{', '}',
'exec', 'system', 'shell_exec', 'passthru', 'eval'
];
foreach ($suspiciousPatterns as $pattern) {
if (stripos($filename, $pattern) !== false) {
Log::warning('Suspicious filename pattern detected: ' . $pattern, [
'filename' => $filename,
'user_id' => auth()->id()
]);
return false;
}
}
// 7. Additional security: Check file header (magic bytes)
$handle = fopen($file->getPathname(), 'rb');
if ($handle) {
$header = fread($handle, 8);
fclose($handle);
// Check for valid image headers
$validHeaders = [
"\xFF\xD8\xFF", // JPEG
"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", // PNG
];
$isValidHeader = false;
foreach ($validHeaders as $validHeader) {
if (substr($header, 0, strlen($validHeader)) === $validHeader) {
$isValidHeader = true;
break;
}
}
if (!$isValidHeader) {
Log::warning('Invalid file header detected', [
'filename' => $filename,
'user_id' => auth()->id(),
'header' => bin2hex($header)
]);
return false;
}
}
Log::info('File validation passed', [
'filename' => $filename,
'size' => $file->getSize(),
'mime_type' => $mimeType,
'user_id' => auth()->id()
]);
return true;
} catch (\Exception $e) {
Log::error('File validation error: ' . $e->getMessage(), [
'filename' => $file->getClientOriginalName(),
'user_id' => auth()->id(),
'error' => $e->getMessage()
]);
return false;
}
}
} }

View File

@@ -20,7 +20,6 @@ class adminRole
{ {
// check if user can access admin area // check if user can access admin area
$user = Privilege::join('menus AS m', 'm.id', '=', 'privileges.menu_id')->where('m.link', 'adminarea')->where('role_id', Auth::user()->role_id)->where('view', 1)->get(); $user = Privilege::join('menus AS m', 'm.id', '=', 'privileges.menu_id')->where('m.link', 'adminarea')->where('role_id', Auth::user()->role_id)->where('view', 1)->get();
// dd($user);
if (!$user) { if (!$user) {
abort(404); abort(404);
} }

View File

@@ -75,7 +75,7 @@ class Postcheck extends Model
*/ */
public function getFrontImageUrlAttribute() public function getFrontImageUrlAttribute()
{ {
return $this->front_image ? Storage::disk('public')->url($this->front_image) : null; return $this->front_image ? asset('storage/' . ltrim($this->front_image, '/')) : null;
} }
/** /**
@@ -83,7 +83,7 @@ class Postcheck extends Model
*/ */
public function getCabinTemperatureImageUrlAttribute() public function getCabinTemperatureImageUrlAttribute()
{ {
return $this->cabin_temperature_image ? Storage::disk('public')->url($this->cabin_temperature_image) : null; return $this->cabin_temperature_image ? asset('storage/' . ltrim($this->cabin_temperature_image, '/')) : null;
} }
/** /**
@@ -91,7 +91,7 @@ class Postcheck extends Model
*/ */
public function getAcImageUrlAttribute() public function getAcImageUrlAttribute()
{ {
return $this->ac_image ? Storage::disk('public')->url($this->ac_image) : null; return $this->ac_image ? asset('storage/' . ltrim($this->ac_image, '/')) : null;
} }
/** /**
@@ -99,7 +99,7 @@ class Postcheck extends Model
*/ */
public function getBlowerImageUrlAttribute() public function getBlowerImageUrlAttribute()
{ {
return $this->blower_image ? Storage::disk('public')->url($this->blower_image) : null; return $this->blower_image ? asset('storage/' . ltrim($this->blower_image, '/')) : null;
} }
/** /**
@@ -107,7 +107,7 @@ class Postcheck extends Model
*/ */
public function getEvaporatorImageUrlAttribute() public function getEvaporatorImageUrlAttribute()
{ {
return $this->evaporator_image ? Storage::disk('public')->url($this->evaporator_image) : null; return $this->evaporator_image ? asset('storage/' . ltrim($this->evaporator_image, '/')) : null;
} }
/** /**

View File

@@ -75,7 +75,7 @@ class Precheck extends Model
*/ */
public function getFrontImageUrlAttribute() public function getFrontImageUrlAttribute()
{ {
return $this->front_image ? Storage::disk('public')->url($this->front_image) : null; return $this->front_image ? asset('storage/' . ltrim($this->front_image, '/')) : null;
} }
/** /**
@@ -83,7 +83,7 @@ class Precheck extends Model
*/ */
public function getCabinTemperatureImageUrlAttribute() public function getCabinTemperatureImageUrlAttribute()
{ {
return $this->cabin_temperature_image ? Storage::disk('public')->url($this->cabin_temperature_image) : null; return $this->cabin_temperature_image ? asset('storage/' . ltrim($this->cabin_temperature_image, '/')) : null;
} }
/** /**
@@ -91,7 +91,7 @@ class Precheck extends Model
*/ */
public function getAcImageUrlAttribute() public function getAcImageUrlAttribute()
{ {
return $this->ac_image ? Storage::disk('public')->url($this->ac_image) : null; return $this->ac_image ? asset('storage/' . ltrim($this->ac_image, '/')) : null;
} }
/** /**
@@ -99,7 +99,7 @@ class Precheck extends Model
*/ */
public function getBlowerImageUrlAttribute() public function getBlowerImageUrlAttribute()
{ {
return $this->blower_image ? Storage::disk('public')->url($this->blower_image) : null; return $this->blower_image ? asset('storage/' . ltrim($this->blower_image, '/')) : null;
} }
/** /**
@@ -107,7 +107,7 @@ class Precheck extends Model
*/ */
public function getEvaporatorImageUrlAttribute() public function getEvaporatorImageUrlAttribute()
{ {
return $this->evaporator_image ? Storage::disk('public')->url($this->evaporator_image) : null; return $this->evaporator_image ? asset('storage/' . ltrim($this->evaporator_image, '/')) : null;
} }
/** /**

View File

@@ -1,28 +1,5 @@
{ {
"/js/app.js": "/js/app.js", "/js/app.js": "/js/app.js",
"/js/vendor.js": "/js/vendor.js", "/js/vendor.js": "/js/vendor.js",
"/js/warehouse_management/product_categories/index.js": "/js/warehouse_management/product_categories/index.js", "/css/app.css": "/css/app.css"
"/js/warehouse_management/products/index.js": "/js/warehouse_management/products/index.js",
"/js/warehouse_management/opnames/index.js": "/js/warehouse_management/opnames/index.js",
"/js/warehouse_management/opnames/create.js": "/js/warehouse_management/opnames/create.js",
"/js/warehouse_management/opnames/detail.js": "/js/warehouse_management/opnames/detail.js",
"/js/warehouse_management/mutations/index.js": "/js/warehouse_management/mutations/index.js",
"/js/warehouse_management/mutations/create.js": "/js/warehouse_management/mutations/create.js",
"/js/warehouse_management/stock_audit/index.js": "/js/warehouse_management/stock_audit/index.js",
"/css/app.css": "/css/app.css",
"/js/vendor/jquery.dataTables.min.js": "/js/vendor/jquery.dataTables.min.js",
"/js/vendor/dataTables.bootstrap4.min.js": "/js/vendor/dataTables.bootstrap4.min.js",
"/js/vendor/dataTables.fixedColumns.min.js": "/js/vendor/dataTables.fixedColumns.min.js",
"/js/vendor/sweetalert2.min.js": "/js/vendor/sweetalert2.min.js",
"/js/vendor/chart.umd.js": "/js/vendor/chart.umd.js",
"/js/vendor/chartjs-plugin-datalabels.min.js": "/js/vendor/chartjs-plugin-datalabels.min.js",
"/css/vendor/dataTables.bootstrap4.min.css": "/css/vendor/dataTables.bootstrap4.min.css",
"/css/vendor/fixedColumns.bootstrap4.min.css": "/css/vendor/fixedColumns.bootstrap4.min.css",
"/css/vendor/sweetalert2.min.css": "/css/vendor/sweetalert2.min.css",
"/js/cdn/dataTables.bootstrap4.min.js": "/js/cdn/dataTables.bootstrap4.min.js",
"/js/cdn/dataTables.fixedColumns.min.js": "/js/cdn/dataTables.fixedColumns.min.js",
"/js/cdn/jquery.dataTables.min.js": "/js/cdn/jquery.dataTables.min.js",
"/css/dataTables.bootstrap4.min.css": "/css/dataTables.bootstrap4.min.css",
"/css/fixedColumns.bootstrap4.min.css": "/css/fixedColumns.bootstrap4.min.css",
"/css/google-font.css": "/css/google-font.css"
} }

View File

@@ -223,7 +223,14 @@ var table = $('#kt_table').DataTable({
return `<input type="checkbox" name="selected[]" value="${data}" />`; return `<input type="checkbox" name="selected[]" value="${data}" />`;
} }
}, },
{data: 'date', name: 'transactions.date'}, {
data: 'date',
name: 'transactions.date',
render: function (data, type, row) {
if (!data) return '';
return data.split(' ')[0]; // ambil bagian sebelum spasi
}
},
{data: 'dealer_name', name: 'd.name'}, {data: 'dealer_name', name: 'd.name'},
{data: 'username', name: 'users.name'}, {data: 'username', name: 'users.name'},
{data: 'sa_name', name: 'sa.name'}, {data: 'sa_name', name: 'sa.name'},
@@ -415,6 +422,14 @@ jQuery(document).ready(function () {
}) })
}) })
$(document).on("click", ".action-print", function () {
let type = $(this).data("type");
let id = $(this).data("id");
let url = $(this).data("url");
window.open(url, "_blank");
});
}) })
</script> </script>

View File

@@ -320,7 +320,7 @@ use Illuminate\Support\Facades\Auth;
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
} }
/* Claim tab specific styling */ /* Claim tab specific styling - Enhanced and Fixed */
#claimTransactionsTable { #claimTransactionsTable {
font-size: 14px; font-size: 14px;
} }
@@ -352,6 +352,186 @@ use Illuminate\Support\Facades\Auth;
font-weight: 600; font-weight: 600;
} }
/* Fixed column widths for claim table */
#claimTransactionsTable th:nth-child(1),
#claimTransactionsTable td:nth-child(1) {
width: 12% !important;
min-width: 100px !important;
} /* Tanggal */
#claimTransactionsTable th:nth-child(2),
#claimTransactionsTable td:nth-child(2) {
width: 10% !important;
min-width: 80px !important;
} /* SPK */
#claimTransactionsTable th:nth-child(3),
#claimTransactionsTable td:nth-child(3) {
width: 12% !important;
min-width: 100px !important;
} /* No Polisi */
#claimTransactionsTable th:nth-child(4),
#claimTransactionsTable td:nth-child(4) {
width: 20% !important;
min-width: 150px !important;
} /* Pekerjaan */
#claimTransactionsTable th:nth-child(5),
#claimTransactionsTable td:nth-child(5) {
width: 8% !important;
min-width: 60px !important;
} /* Qty */
#claimTransactionsTable th:nth-child(6),
#claimTransactionsTable td:nth-child(6) {
width: 15% !important;
min-width: 120px !important;
} /* Service Advisor */
#claimTransactionsTable th:nth-child(7),
#claimTransactionsTable td:nth-child(7) {
width: 10% !important;
min-width: 80px !important;
} /* Status */
#claimTransactionsTable th:nth-child(8),
#claimTransactionsTable td:nth-child(8) {
width: 8% !important;
min-width: 80px !important;
} /* Aksi */
#claimTransactionsTable th:nth-child(9),
#claimTransactionsTable td:nth-child(9) {
width: 8% !important;
min-width: 100px !important;
} /* Pre Check */
#claimTransactionsTable th:nth-child(10),
#claimTransactionsTable td:nth-child(10) {
width: 8% !important;
min-width: 100px !important;
} /* Post Check */
/* Action column specific styling - Enhanced */
#claimTransactionsTable td:nth-child(8),
#claimTransactionsTable td:nth-child(9),
#claimTransactionsTable td:nth-child(10) {
text-align: center !important;
vertical-align: middle !important;
padding: 8px 4px !important;
}
/* Button alignment in action columns - Fixed */
#claimTransactionsTable td:nth-child(8) .btn,
#claimTransactionsTable td:nth-child(9) .btn,
#claimTransactionsTable td:nth-child(10) .btn {
margin: 1px 2px !important;
vertical-align: middle !important;
display: inline-block !important;
}
/* Specific button styling for different types */
#claimTransactionsTable .btn-success {
background-color: #28a745 !important;
border-color: #28a745 !important;
color: white !important;
}
#claimTransactionsTable .btn-warning {
background-color: #ffc107 !important;
border-color: #ffc107 !important;
color: #212529 !important;
}
#claimTransactionsTable .btn-primary {
background-color: #007bff !important;
border-color: #007bff !important;
color: white !important;
}
#claimTransactionsTable .btn-danger {
background-color: #dc3545 !important;
border-color: #dc3545 !important;
color: white !important;
}
/* Badge styling fixes */
#claimTransactionsTable .badge-warning {
background-color: #ffc107 !important;
color: #212529 !important;
}
#claimTransactionsTable .badge-success {
background-color: #28a745 !important;
color: white !important;
}
#claimTransactionsTable .badge-info {
background-color: #17a2b8 !important;
color: white !important;
}
#claimTransactionsTable .badge-danger {
background-color: #dc3545 !important;
color: white !important;
}
/* DataTable specific fixes */
#claimTransactionsTable_wrapper {
overflow-x: auto !important;
}
#claimTransactionsTable_wrapper .dataTables_scrollHead {
overflow: hidden !important;
}
#claimTransactionsTable_wrapper .dataTables_scrollBody {
overflow-x: auto !important;
}
/* Responsive adjustments for smaller screens */
@media (max-width: 768px) {
#claimTransactionsTable {
font-size: 12px !important;
}
#claimTransactionsTable th,
#claimTransactionsTable td {
padding: 8px 4px !important;
}
#claimTransactionsTable .btn {
font-size: 10px !important;
padding: 3px 6px !important;
min-width: 50px !important;
}
#claimTransactionsTable .badge {
font-size: 9px !important;
padding: 3px 6px !important;
}
/* Adjust column widths for mobile */
#claimTransactionsTable th:nth-child(4),
#claimTransactionsTable td:nth-child(4) {
width: 25% !important;
min-width: 120px !important;
} /* Pekerjaan - wider on mobile */
}
/* Additional fixes for DataTable rendering */
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter,
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_processing,
.dataTables_wrapper .dataTables_paginate {
color: #333 !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
padding: 0.5em 1em !important;
margin: 0 2px !important;
border-radius: 4px !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.current {
background: #007bff !important;
color: white !important;
border: 1px solid #007bff !important;
}
/* Tab claim specific styling */ /* Tab claim specific styling */
#form-claim { #form-claim {
background-color: #fff; background-color: #fff;
@@ -737,9 +917,6 @@ use Illuminate\Support\Facades\Auth;
<input type="hidden" name="category" value="work"> <input type="hidden" name="category" value="work">
<div class="work_multirow"> <div class="work_multirow">
@if (old('work_id') && old('form') == 'work') @if (old('work_id') && old('form') == 'work')
{{-- @php
dd($errors->all());
@endphp --}}
<input type="hidden" class="work_field_counter" value="{{ count(old('work_id')) }}"> <input type="hidden" class="work_field_counter" value="{{ count(old('work_id')) }}">
@for ($i = 0; $i < count(old('work_id')); $i++) @for ($i = 0; $i < count(old('work_id')); $i++)
<div class="form-group row" id="work_field{{ $i+1 }}"> <div class="form-group row" id="work_field{{ $i+1 }}">
@@ -957,20 +1134,21 @@ use Illuminate\Support\Facades\Auth;
<h6 class="mb-3">Daftar Pekerjaan yang Dapat Diklaim</h6> <h6 class="mb-3">Daftar Pekerjaan yang Dapat Diklaim</h6>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-bordered table-hover" id="claimTransactionsTable"> <table class="table table-bordered table-hover" id="claimTransactionsTable">
<thead class="thead-light"> <thead>
<tr> <tr>
<th width="10%">Tanggal</th> <th>Tanggal</th>
<th width="15%">No. SPK</th> <th>SPK</th>
<th width="15%">No. Polisi</th> <th>No Polisi</th>
<th width="20%">Pekerjaan</th> <th>Pekerjaan</th>
<th width="10%">Qty</th> <th>Qty</th>
<th width="15%">Service Advisor</th> <th>Service Advisor</th>
<th width="10%">Status</th> <th>Status</th>
<th width="5%">Aksi</th> <th>Aksi</th>
<th>Pre Check</th>
<th>Post Check</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<!-- Data will be loaded via DataTables AJAX -->
</tbody> </tbody>
</table> </table>
</div> </div>
@@ -1180,6 +1358,73 @@ use Illuminate\Support\Facades\Auth;
</div> </div>
</div> </div>
<!-- Modal Edit Transaksi -->
<div class="modal fade" id="editTransactionModal" tabindex="-1" role="dialog" aria-labelledby="editTransactionModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editTransactionModalLabel">Edit Transaksi</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form id="editTransactionForm" action="" method="POST">
@csrf
@method('PUT')
<input type="hidden" name="transaction_id" value="">
<input type="hidden" name="from_transaction_page" value="1">
<div class="modal-body">
<div class="form-group">
<label>No. SPK</label>
<input type="text" class="form-control" name="spk" placeholder="No. SPK" required>
</div>
<div class="form-group">
<label>Tanggal</label>
<input type="text" class="form-control" name="date" id="edit-transaction-date" placeholder="YYYY-MM-DD" required>
</div>
<div class="form-group">
<label>No. Polisi</label>
<input type="text" class="form-control" name="police_number" placeholder="No. Polisi" required>
</div>
<div class="form-group">
<label>Pekerjaan</label>
<select class="form-control" name="work_id" required>
<option value="" disabled>Pilih Pekerjaan</option>
@foreach ($work_works as $work)
<option value="{{ $work->id }}">{{ $work->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label>Qty</label>
<input type="number" class="form-control" name="qty" min="1" placeholder="Qty" required>
</div>
<div class="form-group">
<label>Warranty</label>
<select class="form-control" name="warranty" required>
<option value="1">Ya</option>
<option value="0">Tidak</option>
</select>
</div>
<div class="form-group">
<label>Service Advisor</label>
<select class="form-control" name="user_sa_id" required>
<option value="">Pilih Service Advisor</option>
@foreach ($user_sas as $user_sa)
<option value="{{ $user_sa->id }}">{{ $user_sa->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Tutup</button>
<button type="submit" class="btn btn-success" id="editTransactionButton">Simpan</button>
</div>
</form>
</div>
</div>
</div>
<!-- Modal Detail Mutasi --> <!-- Modal Detail Mutasi -->
<div class="modal fade" id="mutationDetailModal" tabindex="-1" role="dialog" aria-labelledby="mutationDetailModalLabel"> <div class="modal fade" id="mutationDetailModal" tabindex="-1" role="dialog" aria-labelledby="mutationDetailModalLabel">
<div class="modal-dialog modal-xl" role="document"> <div class="modal-dialog modal-xl" role="document">
@@ -1866,6 +2111,11 @@ use Illuminate\Support\Facades\Auth;
autoclose: true, autoclose: true,
todayHighlight: true todayHighlight: true
}); });
$("#edit-transaction-date").datepicker({
format: 'yyyy-mm-dd',
autoclose: true,
todayHighlight: true
});
$("#date-opname").datepicker({ $("#date-opname").datepicker({
format: 'yyyy-mm-dd', format: 'yyyy-mm-dd',
autoclose: true, autoclose: true,
@@ -2508,19 +2758,21 @@ use Illuminate\Support\Facades\Auth;
} }
}, },
columns: [ columns: [
{data: 'date', name: 'date'}, {data: 'date', name: 'date', title: 'Tanggal'},
{data: 'spk', name: 'spk'}, {data: 'spk', name: 'spk', title: 'SPK'},
{data: 'police_number', name: 'police_number'}, {data: 'police_number', name: 'police_number', title: 'No Polisi'},
{data: 'work_name', name: 'work_name'}, {data: 'work_name', name: 'work_name', title: 'Pekerjaan'},
{data: 'qty', name: 'qty'}, {data: 'qty', name: 'qty', title: 'Qty'},
{data: 'sa_name', name: 'sa_name'}, {data: 'sa_name', name: 'sa_name', title: 'Service Advisor'},
{data: 'status', name: 'status', orderable: false}, {data: 'status', name: 'status', title: 'Status', orderable: false},
{data: 'action', name: 'action', orderable: false, searchable: false} {data: 'action', name: 'action', title: 'Aksi', orderable: false, searchable: false},
{data: 'action_precheck', name: 'action_precheck', title: 'Pre Check', orderable: false, searchable: false},
{data: 'action_postcheck', name: 'action_postcheck', title: 'Post Check', orderable: false, searchable: false},
], ],
pageLength: 10, pageLength: 10,
responsive: true, responsive: true,
scrollX: true, scrollX: true,
order: [[0, 'desc']], // Sort by date descending order: [[0, 'desc']]
}); });
} }
@@ -2934,49 +3186,71 @@ use Illuminate\Support\Facades\Auth;
} }
function editTransaction(transactionId) { function editTransaction(transactionId) {
// Redirect to edit page or show edit modal // Load transaction data and show modal with form
window.location.href = '{{ route("transaction.edit", ":id") }}'.replace(':id', transactionId); $('#editTransactionModal').modal('show');
const url = '{{ route("transaction.edit", ":id") }}'.replace(':id', transactionId);
const action = '{{ route("transaction.update", ":id") }}'.replace(':id', transactionId);
// Reset form
const form = document.getElementById('editTransactionForm');
form.reset();
form.action = action;
form.querySelector('input[name="transaction_id"]').value = transactionId;
// Fetch data
$.get(url, function(response) {
if (response && response.status === 200 && response.data) {
const trx = response.data;
form.querySelector('input[name="spk"]').value = trx.spk || '';
form.querySelector('input[name="date"]').value = trx.date || '';
form.querySelector('input[name="police_number"]').value = trx.police_number || '';
form.querySelector('select[name="work_id"]').value = trx.work_id || '';
form.querySelector('input[name="qty"]').value = trx.qty || 1;
form.querySelector('select[name="warranty"]').value = String(trx.warranty ?? '0');
form.querySelector('select[name="user_sa_id"]').value = trx.user_sa_id || '';
} else {
Swal.fire({ icon: 'error', title: 'Error', text: 'Gagal memuat data transaksi' });
}
}).fail(function() {
Swal.fire({ icon: 'error', title: 'Error', text: 'Terjadi kesalahan saat memuat data' });
});
} }
function deleteTransaction(transactionId) { function deleteTransaction(transactionId) {
Swal.fire({ Swal.fire({
title: 'Konfirmasi Hapus', title: 'Konfirmasi Hapus',
text: 'Apakah Anda yakin ingin menghapus transaksi ini?', html: '<div class="text-left">Tindakan ini tidak dapat dibatalkan.<br>Hapus transaksi ini?</div>',
icon: 'warning', icon: 'warning',
showCancelButton: true, showCancelButton: true,
confirmButtonColor: '#d33', confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6', cancelButtonColor: '#6c757d',
confirmButtonText: 'Ya, Hapus!', confirmButtonText: 'Ya, Hapus',
cancelButtonText: 'Batal' cancelButtonText: 'Batal'
}).then((result) => { }).then((result) => {
if (result.isConfirmed) { if (result.isConfirmed) {
// Send delete request
$.ajax({ $.ajax({
url: '{{ route("transaction.destroy", ":id") }}'.replace(':id', transactionId), url: '{{ route("transaction.destroy", ":id") }}'.replace(':id', transactionId),
method: 'DELETE', method: 'POST',
data: { data: {
_method: 'DELETE',
_token: '{{ csrf_token() }}' _token: '{{ csrf_token() }}'
}, },
success: function(response) { success: function(response) {
Swal.fire({ Swal.fire({
icon: 'success', icon: 'success',
title: 'Berhasil!', title: 'Berhasil',
text: 'Transaksi berhasil dihapus', text: response && response.message ? response.message : 'Transaksi berhasil dihapus',
timer: 2000, timer: 1800,
showConfirmButton: false showConfirmButton: false
}).then(() => { }).then(() => {
// Refresh the table
if (claimTransactionsTable) { if (claimTransactionsTable) {
claimTransactionsTable.ajax.reload(); claimTransactionsTable.ajax.reload(null, false);
} }
}); });
}, },
error: function() { error: function(xhr) {
Swal.fire({ const msg = (xhr.responseJSON && xhr.responseJSON.message) ? xhr.responseJSON.message : 'Gagal menghapus transaksi';
icon: 'error', Swal.fire({ icon: 'error', title: 'Error', text: msg });
title: 'Error',
text: 'Gagal menghapus transaksi'
});
} }
}); });
} }
@@ -3274,6 +3548,51 @@ use Illuminate\Support\Facades\Auth;
initializePhysicalStock(); initializePhysicalStock();
handleServerSideErrors(); handleServerSideErrors();
initializeTabs(); initializeTabs();
// Handle edit transaction form submit via AJAX
$(document).on('submit', '#editTransactionForm', function(e) {
e.preventDefault();
const form = this;
const action = form.action;
const formData = $(form).serialize();
// Disable button to prevent double submit
const $btn = $('#editTransactionButton');
$btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Menyimpan...');
$.ajax({
url: action,
method: 'POST',
data: formData,
success: function(response) {
$('#editTransactionModal').modal('hide');
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: (response && response.message) ? response.message : 'Transaksi berhasil diperbarui',
timer: 1800,
showConfirmButton: false
}).then(() => {
if (claimTransactionsTable) {
claimTransactionsTable.ajax.reload(null, false);
}
});
},
error: function(xhr) {
let msg = 'Gagal memperbarui transaksi';
if (xhr.responseJSON && xhr.responseJSON.errors) {
const errors = xhr.responseJSON.errors;
msg = Object.values(errors).flat().join('\n');
} else if (xhr.responseJSON && xhr.responseJSON.message) {
msg = xhr.responseJSON.message;
}
Swal.fire({ icon: 'error', title: 'Error', text: msg });
},
complete: function() {
$btn.prop('disabled', false).text('Simpan');
}
});
});
}); });
// Handle sub-tab switching for transaksi tabs // Handle sub-tab switching for transaksi tabs

View File

@@ -7,7 +7,6 @@
<a href="{{ route('transaction') }}" class="mt-4 btn btn-danger"><i class="fa fa-chevron-left"></i> Kembali</a> <a href="{{ route('transaction') }}" class="mt-4 btn btn-danger"><i class="fa fa-chevron-left"></i> Kembali</a>
</div> </div>
<div class="col-8"> <div class="col-8">
{{-- <h5 class="text-center mt-4">Cipta Kreasi Baru</h5> --}}
<a href="/"><img src="{{ asset('logo-ckb.png') }}" style="width: 100%" alt="LOGO CKB"></a> <a href="/"><img src="{{ asset('logo-ckb.png') }}" style="width: 100%" alt="LOGO CKB"></a>
</div> </div>
</div> </div>
@@ -294,17 +293,6 @@
$string .= "*TOTAL: ".$total_qty." Unit*\n\n"; $string .= "*TOTAL: ".$total_qty." Unit*\n\n";
$overall_total += $total_qty; $overall_total += $total_qty;
// Remove monthly data display since this is daily report
// if (isset($trx['total_body']) && is_array($trx['total_body'])) {
// foreach ($trx['total_body'] as $total) {
// $string .= $total;
// }
// }
// if (isset($trx['total_total'])) {
// $string .= $trx['total_total']."\n\n";
// }
} }
} else { } else {
$string .= "*Tidak ada data transaksi hari ini*\n\n"; $string .= "*Tidak ada data transaksi hari ini*\n\n";
@@ -385,6 +373,18 @@
<script> <script>
$("#kt-table").DataTable() $("#kt-table").DataTable()
function precheck(id) {
let url = $("#precheck"+id).attr("data-url")
console.log(url)
// window.open(url, "_blank")
}
function postcheck(id) {
let url = $("#postcheck"+id).attr("data-url")
console.log(url)
// window.open(url, "_blank")
}
// Check if Web Share API is supported // Check if Web Share API is supported
function isWebShareSupported() { function isWebShareSupported() {
return navigator.share && typeof navigator.share === 'function'; return navigator.share && typeof navigator.share === 'function';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
@extends('layouts.frontapp')
@section('content')
@include('transaction.postchecks._form')
@endsection

View File

@@ -0,0 +1,5 @@
@extends('layouts.frontapp')
@section('content')
@include('transaction.postchecks._form')
@endsection

View File

@@ -0,0 +1,397 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Print Postcheck - {{ $postcheck->spk_number ?? '-' }}</title>
<style>
@media print {
@page {
margin: 0.5in;
size: A4;
}
body {
-webkit-print-color-adjust: exact;
color-adjust: exact;
}
.no-print {
display: none !important;
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
font-size: 12px;
line-height: 1.4;
color: #333;
background: white;
}
.container {
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.header {
border-bottom: 3px solid #2c5282;
padding-bottom: 20px;
margin-bottom: 30px;
}
.company-info {
text-align: center;
margin-bottom: 15px;
}
.company-name {
font-size: 24px;
font-weight: bold;
color: #2c5282;
margin-bottom: 5px;
}
.company-tagline {
font-size: 14px;
color: #666;
margin-bottom: 10px;
}
.document-title {
text-align: center;
font-size: 18px;
font-weight: bold;
color: #2c5282;
text-transform: uppercase;
letter-spacing: 1px;
}
.info-section {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
flex-wrap: wrap;
}
.info-box {
flex: 1;
min-width: 250px;
margin-right: 20px;
}
.info-box:last-child {
margin-right: 0;
}
.info-title {
font-weight: bold;
color: #2c5282;
margin-bottom: 10px;
font-size: 14px;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 5px;
}
.info-item {
display: flex;
justify-content: space-between;
padding: 4px 0;
border-bottom: 1px dotted #e2e8f0;
}
.info-label {
font-weight: 600;
color: #4a5568;
width: 45%;
}
.info-value {
color: #2d3748;
width: 50%;
text-align: right;
}
.section-title {
font-size: 16px;
font-weight: bold;
color: #2c5282;
margin: 20px 0 10px;
text-transform: uppercase;
}
.data-table {
width: 100%;
border-collapse: collapse;
border: 1px solid #e2e8f0;
margin-bottom: 20px;
}
.data-table th {
background-color: #2c5282;
color: white;
padding: 12px 8px;
text-align: left;
font-weight: bold;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.data-table td {
padding: 10px 8px;
border-bottom: 1px solid #e2e8f0;
vertical-align: top;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 15px;
}
.card {
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 12px;
}
.image-box {
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 10px;
text-align: center;
background: #f7fafc;
}
.image-box img {
max-width: 100%;
max-height: 240px;
border-radius: 4px;
}
.notes-section {
background-color: #f7fafc;
border-left: 4px solid #2c5282;
padding: 15px;
margin: 20px 0;
}
.footer {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e2e8f0;
}
.signatures {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-top: 20px;
}
.signature-box {
text-align: center;
}
.signature-line {
margin-top: 60px;
border-top: 1px solid #2d3748;
padding-top: 4px;
font-size: 12px;
color: #2d3748;
}
.print-button {
position: fixed;
top: 20px;
right: 20px;
background-color: #2c5282;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.print-button:hover {
background-color: #2a4365;
}
.text-right { text-align: right; }
.text-center { text-align: center; }
</style>
</head>
<body>
<button class="print-button no-print" onclick="window.print()">Print</button>
<div class="container">
<div class="header">
<div class="company-info">
<div class="company-name">PT. CIPTA KREASI BARU</div>
<div class="company-tagline">Postcheck Kendaraan</div>
</div>
<div class="document-title">Dokumen Postcheck</div>
</div>
<div class="info-section">
<div class="info-box">
<div class="info-title">Informasi Transaksi</div>
<div class="info-item">
<span class="info-label">No. Polisi</span>
<span class="info-value">{{ $postcheck->police_number ?? $postcheck->transaction->police_number ?? '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">No. SPK</span>
<span class="info-value">{{ $postcheck->spk_number ?? $postcheck->transaction->spk ?? '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">Tanggal Postcheck</span>
<span class="info-value">{{ optional($postcheck->postcheck_at)->format('d F Y H:i') }}</span>
</div>
<div class="info-item">
<span class="info-label">Dibuat oleh</span>
<span class="info-value">{{ $postcheck->postcheckBy->name ?? '-' }}</span>
</div>
</div>
<div class="info-box">
<div class="info-title">Parameter Utama</div>
<div class="info-item">
<span class="info-label">Kilometer</span>
<span class="info-value">{{ number_format((float)($postcheck->kilometer ?? 0), 2) }}</span>
</div>
<div class="info-item">
<span class="info-label">Tekanan High</span>
<span class="info-value">{{ number_format((float)($postcheck->pressure_high ?? 0), 2) }}</span>
</div>
<div class="info-item">
<span class="info-label">Tekanan Low</span>
<span class="info-value">{{ number_format((float)($postcheck->pressure_low ?? 0), 2) }}</span>
</div>
<div class="info-item">
<span class="info-label">Suhu Kabin</span>
<span class="info-value">{{ isset($postcheck->cabin_temperature) ? number_format((float)$postcheck->cabin_temperature, 2) : '-' }}</span>
</div>
</div>
</div>
<div class="section-title">Kondisi Komponen</div>
<table class="data-table">
<thead>
<tr>
<th style="width: 35%;">Komponen</th>
<th style="width: 25%;">Status Pekerjaan</th>
</tr>
</thead>
<tbody>
<tr>
<td>AC</td>
<td>{{ $postcheck->ac_condition ?? '-' }}</td>
</tr>
<tr>
<td>Blower</td>
<td>{{ $postcheck->blower_condition ?? '-' }}</td>
</tr>
<tr>
<td>Evaporator</td>
<td>{{ $postcheck->evaporator_condition ?? '-' }}</td>
</tr>
<tr>
<td>Compressor</td>
<td>{{ $postcheck->compressor_condition ?? '-' }}</td>
</tr>
</tbody>
</table>
<div class="section-title">Dokumentasi Foto</div>
<div class="grid">
<div class="card">
<div class="info-title" style="margin-bottom:8px;">Depan</div>
<div class="image-box">
@if($postcheck->front_image_url || $postcheck->front_image)
<img src="{{ $postcheck->front_image_url ?? asset('storage/' . ltrim($postcheck->front_image, '/')) }}" alt="Foto Depan">
@else
<div>Tidak ada gambar</div>
@endif
</div>
</div>
<div class="card">
<div class="info-title" style="margin-bottom:8px;">Suhu Kabin</div>
<div class="image-box">
@if($postcheck->cabin_temperature_image_url || $postcheck->cabin_temperature_image)
<img src="{{ $postcheck->cabin_temperature_image_url ?? asset('storage/' . ltrim($postcheck->cabin_temperature_image, '/')) }}" alt="Foto Suhu Kabin">
@else
<div>Tidak ada gambar</div>
@endif
</div>
</div>
<div class="card">
<div class="info-title" style="margin-bottom:8px;">AC</div>
<div class="image-box">
@if($postcheck->ac_image_url || $postcheck->ac_image)
<img src="{{ $postcheck->ac_image_url ?? asset('storage/' . ltrim($postcheck->ac_image, '/')) }}" alt="Foto AC">
@else
<div>Tidak ada gambar</div>
@endif
</div>
</div>
<div class="card">
<div class="info-title" style="margin-bottom:8px;">Blower</div>
<div class="image-box">
@if($postcheck->blower_image_url || $postcheck->blower_image)
<img src="{{ $postcheck->blower_image_url ?? asset('storage/' . ltrim($postcheck->blower_image, '/')) }}" alt="Foto Blower">
@else
<div>Tidak ada gambar</div>
@endif
</div>
</div>
<div class="card">
<div class="info-title" style="margin-bottom:8px;">Evaporator</div>
<div class="image-box">
@if($postcheck->evaporator_image_url || $postcheck->evaporator_image)
<img src="{{ $postcheck->evaporator_image_url ?? asset('storage/' . ltrim($postcheck->evaporator_image, '/')) }}" alt="Foto Evaporator">
@else
<div>Tidak ada gambar</div>
@endif
</div>
</div>
</div>
@if($postcheck->postcheck_notes)
<div class="notes-section">
<div class="info-title" style="border:0; padding:0; margin:0 0 6px 0;">Catatan</div>
<div>{{ $postcheck->postcheck_notes }}</div>
</div>
@endif
<div class="footer">
<div class="text-center" style="color:#666; font-size: 11px;">
Dicetak pada: {{ now()->format('d F Y H:i:s') }} | Sistem Postcheck PT. Cipta Kreasi Baru
</div>
</div>
</div>
<script>
document.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 'p') {
e.preventDefault();
window.print();
}
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
@extends('layouts.frontapp')
@section('content')
@include('transaction.prechecks._form')
@endsection

View File

@@ -0,0 +1,5 @@
@extends('layouts.frontapp')
@section('content')
@include('transaction.prechecks._form')
@endsection

View File

@@ -0,0 +1,397 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Print Precheck - {{ $precheck->spk_number ?? '-' }}</title>
<style>
@media print {
@page {
margin: 0.5in;
size: A4;
}
body {
-webkit-print-color-adjust: exact;
color-adjust: exact;
}
.no-print {
display: none !important;
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
font-size: 12px;
line-height: 1.4;
color: #333;
background: white;
}
.container {
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.header {
border-bottom: 3px solid #2c5282;
padding-bottom: 20px;
margin-bottom: 30px;
}
.company-info {
text-align: center;
margin-bottom: 15px;
}
.company-name {
font-size: 24px;
font-weight: bold;
color: #2c5282;
margin-bottom: 5px;
}
.company-tagline {
font-size: 14px;
color: #666;
margin-bottom: 10px;
}
.document-title {
text-align: center;
font-size: 18px;
font-weight: bold;
color: #2c5282;
text-transform: uppercase;
letter-spacing: 1px;
}
.info-section {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
flex-wrap: wrap;
}
.info-box {
flex: 1;
min-width: 250px;
margin-right: 20px;
}
.info-box:last-child {
margin-right: 0;
}
.info-title {
font-weight: bold;
color: #2c5282;
margin-bottom: 10px;
font-size: 14px;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 5px;
}
.info-item {
display: flex;
justify-content: space-between;
padding: 4px 0;
border-bottom: 1px dotted #e2e8f0;
}
.info-label {
font-weight: 600;
color: #4a5568;
width: 45%;
}
.info-value {
color: #2d3748;
width: 50%;
text-align: right;
}
.section-title {
font-size: 16px;
font-weight: bold;
color: #2c5282;
margin: 20px 0 10px;
text-transform: uppercase;
}
.data-table {
width: 100%;
border-collapse: collapse;
border: 1px solid #e2e8f0;
margin-bottom: 20px;
}
.data-table th {
background-color: #2c5282;
color: white;
padding: 12px 8px;
text-align: left;
font-weight: bold;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.data-table td {
padding: 10px 8px;
border-bottom: 1px solid #e2e8f0;
vertical-align: top;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 15px;
}
.card {
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 12px;
}
.image-box {
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 10px;
text-align: center;
background: #f7fafc;
}
.image-box img {
max-width: 100%;
max-height: 240px;
border-radius: 4px;
}
.notes-section {
background-color: #f7fafc;
border-left: 4px solid #2c5282;
padding: 15px;
margin: 20px 0;
}
.footer {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e2e8f0;
}
.signatures {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-top: 20px;
}
.signature-box {
text-align: center;
}
.signature-line {
margin-top: 60px;
border-top: 1px solid #2d3748;
padding-top: 4px;
font-size: 12px;
color: #2d3748;
}
.print-button {
position: fixed;
top: 20px;
right: 20px;
background-color: #2c5282;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.print-button:hover {
background-color: #2a4365;
}
.text-right { text-align: right; }
.text-center { text-align: center; }
</style>
</head>
<body>
<button class="print-button no-print" onclick="window.print()">Print</button>
<div class="container">
<div class="header">
<div class="company-info">
<div class="company-name">PT. CIPTA KREASI BARU</div>
<div class="company-tagline">Precheck Kendaraan</div>
</div>
<div class="document-title">Dokumen Precheck</div>
</div>
<div class="info-section">
<div class="info-box">
<div class="info-title">Informasi Transaksi</div>
<div class="info-item">
<span class="info-label">No. Polisi</span>
<span class="info-value">{{ $precheck->police_number ?? $precheck->transaction->police_number ?? '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">No. SPK</span>
<span class="info-value">{{ $precheck->spk_number ?? $precheck->transaction->spk ?? '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">Tanggal Precheck</span>
<span class="info-value">{{ optional($precheck->precheck_at)->format('d F Y H:i') }}</span>
</div>
<div class="info-item">
<span class="info-label">Dibuat oleh</span>
<span class="info-value">{{ $precheck->precheckBy->name ?? '-' }}</span>
</div>
</div>
<div class="info-box">
<div class="info-title">Parameter Utama</div>
<div class="info-item">
<span class="info-label">Kilometer</span>
<span class="info-value">{{ number_format((float)($precheck->kilometer ?? 0), 2) }}</span>
</div>
<div class="info-item">
<span class="info-label">Tekanan High</span>
<span class="info-value">{{ number_format((float)($precheck->pressure_high ?? 0), 2) }}</span>
</div>
<div class="info-item">
<span class="info-label">Tekanan Low</span>
<span class="info-value">{{ number_format((float)($precheck->pressure_low ?? 0), 2) }}</span>
</div>
<div class="info-item">
<span class="info-label">Suhu Kabin</span>
<span class="info-value">{{ isset($precheck->cabin_temperature) ? number_format((float)$precheck->cabin_temperature, 2) : '-' }}</span>
</div>
</div>
</div>
<div class="section-title">Kondisi Komponen</div>
<table class="data-table">
<thead>
<tr>
<th style="width: 35%;">Komponen</th>
<th style="width: 25%;">Kondisi</th>
</tr>
</thead>
<tbody>
<tr>
<td>AC</td>
<td>{{ $precheck->ac_condition ?? '-' }}</td>
</tr>
<tr>
<td>Blower</td>
<td>{{ $precheck->blower_condition ?? '-' }}</td>
</tr>
<tr>
<td>Evaporator</td>
<td>{{ $precheck->evaporator_condition ?? '-' }}</td>
</tr>
<tr>
<td>Compressor</td>
<td>{{ $precheck->compressor_condition ?? '-' }}</td>
</tr>
</tbody>
</table>
<div class="section-title">Dokumentasi Foto</div>
<div class="grid">
<div class="card">
<div class="info-title" style="margin-bottom:8px;">Depan</div>
<div class="image-box">
@if($precheck->front_image_url || $precheck->front_image)
<img src="{{ $precheck->front_image_url ?? asset('storage/' . ltrim($precheck->front_image, '/')) }}" alt="Foto Depan">
@else
<div>Tidak ada gambar</div>
@endif
</div>
</div>
<div class="card">
<div class="info-title" style="margin-bottom:8px;">Suhu Kabin</div>
<div class="image-box">
@if($precheck->cabin_temperature_image_url || $precheck->cabin_temperature_image)
<img src="{{ $precheck->cabin_temperature_image_url ?? asset('storage/' . ltrim($precheck->cabin_temperature_image, '/')) }}" alt="Foto Suhu Kabin">
@else
<div>Tidak ada gambar</div>
@endif
</div>
</div>
<div class="card">
<div class="info-title" style="margin-bottom:8px;">AC</div>
<div class="image-box">
@if($precheck->ac_image_url || $precheck->ac_image)
<img src="{{ $precheck->ac_image_url ?? asset('storage/' . ltrim($precheck->ac_image, '/')) }}" alt="Foto AC">
@else
<div>Tidak ada gambar</div>
@endif
</div>
</div>
<div class="card">
<div class="info-title" style="margin-bottom:8px;">Blower</div>
<div class="image-box">
@if($precheck->blower_image_url || $precheck->blower_image)
<img src="{{ $precheck->blower_image_url ?? asset('storage/' . ltrim($precheck->blower_image, '/')) }}" alt="Foto Blower">
@else
<div>Tidak ada gambar</div>
@endif
</div>
</div>
<div class="card">
<div class="info-title" style="margin-bottom:8px;">Evaporator</div>
<div class="image-box">
@if($precheck->evaporator_image_url || $precheck->evaporator_image)
<img src="{{ $precheck->evaporator_image_url ?? asset('storage/' . ltrim($precheck->evaporator_image, '/')) }}" alt="Foto Evaporator">
@else
<div>Tidak ada gambar</div>
@endif
</div>
</div>
</div>
@if($precheck->precheck_notes)
<div class="notes-section">
<div class="info-title" style="border:0; padding:0; margin:0 0 6px 0;">Catatan</div>
<div>{{ $precheck->precheck_notes }}</div>
</div>
@endif
<div class="footer">
<div class="text-center" style="color:#666; font-size: 11px;">
Dicetak pada: {{ now()->format('d F Y H:i:s') }} | Sistem Precheck PT. Cipta Kreasi Baru
</div>
</div>
</div>
<script>
document.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 'p') {
e.preventDefault();
window.print();
}
});
</script>
</body>
</html>

View File

@@ -176,13 +176,25 @@ Route::group(['middleware' => 'auth'], function() {
Route::get('/transaction/get-claim-transactions', [TransactionController::class, 'getClaimTransactions'])->name('transaction.get-claim-transactions'); Route::get('/transaction/get-claim-transactions', [TransactionController::class, 'getClaimTransactions'])->name('transaction.get-claim-transactions');
Route::post('/transaction/claim/{id}', [TransactionController::class, 'claim'])->name('transaction.claim'); Route::post('/transaction/claim/{id}', [TransactionController::class, 'claim'])->name('transaction.claim');
// Prechecks Routes Route::prefix('transaction/{transaction}')->group(function () {
Route::get('/transaction/prechecks/{transaction}', [PrechecksController::class, 'index'])->name('prechecks.index'); // Prechecks
Route::post('/transaction/prechecks/{transaction}', [PrechecksController::class, 'store'])->name('prechecks.store'); Route::prefix('prechecks')->name('prechecks.')->group(function () {
Route::get('create', [PrechecksController::class, 'create'])->name('create');
Route::post('store', [PrechecksController::class, 'store'])->name('store');
Route::get('{precheck}/edit', [PrechecksController::class, 'edit'])->name('edit');
Route::put('{precheck}', [PrechecksController::class, 'update'])->name('update');
Route::get('print', [PrechecksController::class, 'print'])->name('print');
});
// Postchecks Routes // Postchecks
Route::get('/transaction/postchecks/{transaction}', [PostchecksController::class, 'index'])->name('postchecks.index'); Route::prefix('postchecks')->name('postchecks.')->group(function () {
Route::post('/transaction/postchecks/{transaction}', [PostchecksController::class, 'store'])->name('postchecks.store'); Route::get('create', [PostchecksController::class, 'create'])->name('create');
Route::post('store', [PostchecksController::class, 'store'])->name('store');
Route::get('{postcheck}/edit', [PostchecksController::class, 'edit'])->name('edit');
Route::put('{postcheck}', [PostchecksController::class, 'update'])->name('update');
Route::get('print', [PostchecksController::class, 'print'])->name('print');
});
});
}); });
// KPI Data Route - accessible to all authenticated users // KPI Data Route - accessible to all authenticated users
@@ -259,6 +271,9 @@ Route::group(['middleware' => 'auth'], function() {
Route::get('/report/transaction_dealer/export', [ReportController::class, 'dealer_export'])->name('report.transaction_dealer.export'); Route::get('/report/transaction_dealer/export', [ReportController::class, 'dealer_export'])->name('report.transaction_dealer.export');
Route::get('/report/transaction_sa/export', [ReportController::class, 'sa_export'])->name('report.transaction_sa.export'); Route::get('/report/transaction_sa/export', [ReportController::class, 'sa_export'])->name('report.transaction_sa.export');
Route::get('/report/transaction_dealer', [ReportController::class, 'transaction_dealer'])->name('report.transaction_dealer'); Route::get('/report/transaction_dealer', [ReportController::class, 'transaction_dealer'])->name('report.transaction_dealer');
Route::get('report/transaction/precheck/{transaction_id}/print', [PrechecksController::class, 'print'])->name('report.transaction.precheck.print');
Route::get('report/transaction/postcheck/{transaction_id}/print', [PostchecksController::class, 'print'])->name('report.transaction.postcheck.print');
}); });
Route::prefix('warehouse')->group(function () { Route::prefix('warehouse')->group(function () {