partial update close modal on all page and disable create transaction with no stock

This commit is contained in:
2025-06-25 14:01:21 +07:00
parent c3233ea6b2
commit e96ca0a83c
15 changed files with 693 additions and 572 deletions

View File

@@ -25,16 +25,16 @@ class CategoryController extends Controller
$data = Category::all(); $data = Category::all();
return DataTables::of($data)->addIndexColumn() return DataTables::of($data)->addIndexColumn()
->addColumn('action', function($row) use ($menu) { ->addColumn('action', function($row) use ($menu) {
$btn = ''; $btn = '<div class="d-flex">';
if(Auth::user()->can('delete', $menu)) { if(Gate::allows('update', $menu)) {
$btn .= '<button class="btn btn-warning btn-sm btn-bold mr-2" id="editCategory'. $row->id .'" data-url="'. route('category.edit', $row->id) .'" data-action="'. route('category.update', $row->id) .'" onclick="editCategory('. $row->id .')"> Edit </button>';
}
if(Gate::allows('delete', $menu)) {
$btn .= '<button class="btn btn-danger btn-sm btn-bold" data-action="'. route('category.destroy', $row->id) .'" id="destroyCategory'. $row->id .'" onclick="destroyCategory('. $row->id .')"> Hapus </button>'; $btn .= '<button class="btn btn-danger btn-sm btn-bold" data-action="'. route('category.destroy', $row->id) .'" id="destroyCategory'. $row->id .'" onclick="destroyCategory('. $row->id .')"> Hapus </button>';
} }
if(Auth::user()->can('update', $menu)) { $btn .= '</div>';
$btn .= '<button class="btn btn-warning btn-sm btn-bold" id="editCategory'. $row->id .'" data-url="'. route('category.edit', $row->id) .'" data-action="'. route('category.update', $row->id) .'" onclick="editCategory('. $row->id .')"> Edit </button>';
}
return $btn; return $btn;
}) })
->rawColumns(['action']) ->rawColumns(['action'])

View File

@@ -27,27 +27,28 @@ class DealerController extends Controller
$data = Dealer::leftJoin('users as u', 'u.id', '=', 'pic')->select('u.name as pic_name', 'dealers.*'); $data = Dealer::leftJoin('users as u', 'u.id', '=', 'pic')->select('u.name as pic_name', 'dealers.*');
return Datatables::of($data)->addIndexColumn() return Datatables::of($data)->addIndexColumn()
->addColumn('action', function($row) use ($menu) { ->addColumn('action', function($row) use ($menu) {
$btn = ''; $btn = '<div class="d-flex">';
if($row->pic != null) { if($row->pic != null) {
if(Auth::user()->can('delete', $menu)) { if(Gate::allows('delete', $menu)) {
$btn .= '<button class="btn btn-danger btn-sm btn-bold" data-action="'. route('dealer.destroy', $row->id) .'" id="destroyDealer'. $row->id .'" onclick="destroyDealer('. $row->id .')"> Hapus </button>'; $btn .= '<button class="btn btn-danger btn-sm btn-bold mr-2" data-action="'. route('dealer.destroy', $row->id) .'" id="destroyDealer'. $row->id .'" onclick="destroyDealer('. $row->id .')"> Hapus </button>';
} }
if(Auth::user()->can('update', $menu)) { if(Gate::allows('update', $menu)) {
$btn .= '<button class="btn btn-warning btn-sm btn-bold" id="editDealer'. $row->id .'" data-url="'. route('dealer.edit', $row->id) .'" data-action="'. route('dealer.update', $row->id) .'" onclick="editDealer('. $row->id .')"> Edit </button>'; $btn .= '<button class="btn btn-warning btn-sm btn-bold" id="editDealer'. $row->id .'" data-url="'. route('dealer.edit', $row->id) .'" data-action="'. route('dealer.update', $row->id) .'" onclick="editDealer('. $row->id .')"> Edit </button>';
} }
}else{ }else{
if(Auth::user()->can('delete', $menu)) { if(Gate::allows('delete', $menu)) {
$btn .= '<button class="btn btn-danger btn-sm btn-bold" data-action="'. route('dealer.destroy', $row->id) .'" id="destroyDealer'. $row->id .'" onclick="destroyDealer('. $row->id .')"> Hapus </button>'; $btn .= '<button class="btn btn-danger btn-sm btn-bold mr-2" data-action="'. route('dealer.destroy', $row->id) .'" id="destroyDealer'. $row->id .'" onclick="destroyDealer('. $row->id .')"> Hapus </button>';
} }
if(Auth::user()->can('update', $menu)) { if(Gate::allows('update', $menu)) {
$btn .= '<button class="btn btn-warning btn-sm btn-bold" id="editDealer'. $row->id .'" data-url="'. route('dealer.edit', $row->id) .'" data-action="'. route('dealer.update', $row->id) .'" onclick="editDealer('. $row->id .')"> Edit </button> $btn .= '<button class="btn btn-warning btn-sm btn-bold mr-2" id="editDealer'. $row->id .'" data-url="'. route('dealer.edit', $row->id) .'" data-action="'. route('dealer.update', $row->id) .'" onclick="editDealer('. $row->id .')"> Edit </button>
<button class="btn btn-success btn-sm btn-bold" data-action="'. route('dealer.picstore', $row->id) .'" id="addPic'. $row->id .'" data-url="'. route('dealer.edit', $row->id) .'" onclick="addPic('. $row->id .')"> Tambahkan PIC </button>'; <button class="btn btn-success btn-sm btn-bold" data-action="'. route('dealer.picstore', $row->id) .'" id="addPic'. $row->id .'" data-url="'. route('dealer.edit', $row->id) .'" onclick="addPic('. $row->id .')"> Tambahkan PIC </button>';
} }
} }
$btn .= '</div>';
return $btn; return $btn;
}) })
->rawColumns(['action']) ->rawColumns(['action'])

View File

@@ -670,10 +670,66 @@ class TransactionController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
$request['quantity'] = array_filter($request['quantity'], function($value) { return !is_null($value) && $value !== ''; }); // Handle different form types (work vs wash)
$isWashForm = $request->form === 'wash';
$validWorkIds = [];
$validQuantities = [];
$validPairs = [];
if ($isWashForm) {
// For wash form, work_id and quantity are already fixed
$validWorkIds = $request->work_id;
$validQuantities = $request->quantity;
// Create pairs for wash form
if (is_array($request->work_id) && is_array($request->quantity)) {
for ($i = 0; $i < count($request->work_id); $i++) {
$validPairs[] = [
'work_id' => $request->work_id[$i],
'quantity' => $request->quantity[$i],
'index' => $i
];
}
}
} else {
// For work form, filter out empty work/quantity pairs before validation
if ($request->work_id && $request->quantity) {
for ($i = 0; $i < count($request->work_id); $i++) {
$workId = $request->work_id[$i] ?? null;
$quantity = $request->quantity[$i] ?? null;
// Only include pairs where both work_id and quantity are filled
if (!empty($workId) && !empty($quantity) && $quantity > 0) {
$validWorkIds[] = $workId;
$validQuantities[] = $quantity;
$validPairs[] = [
'work_id' => $workId,
'quantity' => $quantity,
'index' => $i
];
}
}
}
// Check if at least one valid pair exists (only for work form)
if (empty($validPairs)) {
return redirect()->back()
->withErrors(['error' => 'Minimal pilih satu pekerjaan dan isi quantity-nya'])
->withInput();
}
}
// Update request with filtered data for validation
$request->merge([
'work_id' => $validWorkIds,
'quantity' => $validQuantities
]);
$request->validate([ $request->validate([
'work_id.*' => ['required', 'integer'], 'work_id.*' => ['required', 'integer', 'exists:works,id'],
'quantity.*' => ['required', 'integer'], 'quantity.*' => ['required', 'integer', 'min:1'],
'spk_no' => ['required', 'string', 'min:1', function($attribute, $value, $fail) use($request) { 'spk_no' => ['required', 'string', 'min:1', function($attribute, $value, $fail) use($request) {
// Handle date format conversion safely for validation // Handle date format conversion safely for validation
if (strpos($request->date, '/') !== false) { if (strpos($request->date, '/') !== false) {
@@ -734,7 +790,7 @@ class TransactionController extends Controller
} }
} }
}], }],
'warranty' => ['required'], 'warranty' => ['required', 'in:0,1'],
'date' => ['required', 'string', 'min:1', function($attribute, $value, $fail) use($request) { 'date' => ['required', 'string', 'min:1', function($attribute, $value, $fail) use($request) {
// Handle date format conversion safely for validation // Handle date format conversion safely for validation
if (strpos($value, '/') !== false) { if (strpos($value, '/') !== false) {
@@ -774,10 +830,13 @@ class TransactionController extends Controller
'police_number.min' => 'No. Polisi tidak boleh kosong', 'police_number.min' => 'No. Polisi tidak boleh kosong',
'date.required' => 'Tanggal Pekerjaan harus diisi', 'date.required' => 'Tanggal Pekerjaan harus diisi',
'date.min' => 'Tanggal Pekerjaan tidak boleh kosong', 'date.min' => 'Tanggal Pekerjaan tidak boleh kosong',
'warranty.required' => 'Warranty harus dipilih',
'user_sa_id.required' => 'Service Advisor harus dipilih', 'user_sa_id.required' => 'Service Advisor harus dipilih',
'user_sa_id.exists' => 'Service Advisor yang dipilih tidak valid', 'user_sa_id.exists' => 'Service Advisor yang dipilih tidak valid',
'work_id.*.required' => 'Pekerjaan harus dipilih', 'work_id.*.required' => 'Pekerjaan yang dipilih harus valid',
'quantity.*.required' => 'Quantity harus diisi', 'work_id.*.exists' => 'Pekerjaan yang dipilih tidak ditemukan',
'quantity.*.required' => 'Quantity harus diisi untuk setiap pekerjaan yang dipilih',
'quantity.*.min' => 'Quantity minimal 1',
]); ]);
// Handle date format conversion safely // Handle date format conversion safely
@@ -796,50 +855,21 @@ class TransactionController extends Controller
$request['date'] = $dateValue; $request['date'] = $dateValue;
} }
// Check stock availability for all works before creating transactions // Stock checking removed - allow negative stock
$stockErrors = [];
for($i = 0; $i < count($request->work_id); $i++) {
$stockCheck = $this->stockService->checkStockAvailability(
$request->work_id[$i],
$request->dealer_id,
$request->quantity[$i]
);
if (!$stockCheck['available']) {
$work = Work::find($request->work_id[$i]);
$stockErrors[] = "Pekerjaan '{$work->name}': {$stockCheck['message']}";
// Add detailed stock information
if (!empty($stockCheck['details'])) {
foreach ($stockCheck['details'] as $detail) {
if (!$detail['is_available']) {
$stockErrors[] = "- {$detail['product_name']}: Dibutuhkan {$detail['required_quantity']}, Tersedia {$detail['available_stock']}";
}
}
}
}
}
// If there are stock errors, return with error messages
if (!empty($stockErrors)) {
return redirect()->back()
->withErrors(['stock' => implode('<br>', $stockErrors)])
->withInput();
}
DB::beginTransaction(); DB::beginTransaction();
try { try {
$transactions = []; $transactions = [];
$data = []; $data = [];
// Create transaction records // Create transaction records using filtered valid pairs
for($i = 0; $i < count($request->work_id); $i++) { foreach($validPairs as $pair) {
$transactionData = [ $transactionData = [
"user_id" => $request->mechanic_id, "user_id" => $request->mechanic_id,
"dealer_id" => $request->dealer_id, "dealer_id" => $request->dealer_id,
"form" => $request->form, "form" => $request->form,
"work_id" => $request->work_id[$i], "work_id" => $pair['work_id'],
"qty" => $request->quantity[$i], "qty" => $pair['quantity'],
"spk" => $request->spk_no, "spk" => $request->spk_no,
"police_number" => $request->police_number, "police_number" => $request->police_number,
"warranty" => $request->warranty, "warranty" => $request->warranty,

View File

@@ -24,16 +24,16 @@ class UserController extends Controller
return DataTables::of($data) return DataTables::of($data)
->addIndexColumn() ->addIndexColumn()
->addColumn('action', function($row) use ($menu) { ->addColumn('action', function($row) use ($menu) {
$btn = ''; $btn = '<div class="d-flex">';
if(Auth::user()->can('delete', $menu)) { if(Gate::allows('update', $menu)) {
$btn .= '<button class="btn btn-warning btn-sm btn-bold mr-2" id="editUser'. $row->id .'" data-url="'. route('user.edit', $row->id) .'" data-action="'. route('user.update', $row->id) .'" onclick="editUser('. $row->id .')"> Edit </button>';
}
if(Gate::allows('delete', $menu)) {
$btn .= '<button class="btn btn-danger btn-sm btn-bold" data-action="'. route('user.destroy', $row->id) .'" id="destroyUser'. $row->id .'" onclick="destroyUser('. $row->id .')"> Hapus </button>'; $btn .= '<button class="btn btn-danger btn-sm btn-bold" data-action="'. route('user.destroy', $row->id) .'" id="destroyUser'. $row->id .'" onclick="destroyUser('. $row->id .')"> Hapus </button>';
} }
if(Auth::user()->can('update', $menu)) { $btn .= '</div>';
$btn .= '<button class="btn btn-warning btn-sm btn-bold" id="editUser'. $row->id .'" data-url="'. route('user.edit', $row->id) .'" data-action="'. route('user.update', $row->id) .'" onclick="editUser('. $row->id .')"> Edit </button>';
}
return $btn; return $btn;
}) })
->rawColumns(['action']) ->rawColumns(['action'])

View File

@@ -100,8 +100,13 @@ class StockAuditController extends Controller
$mutationNumber = $row->source ? $row->source->mutation_number : '-'; $mutationNumber = $row->source ? $row->source->mutation_number : '-';
return "Mutasi: {$mutationNumber}"; return "Mutasi: {$mutationNumber}";
} elseif ($row->source_type === 'App\\Models\\Opname') { } elseif ($row->source_type === 'App\\Models\\Opname') {
return "Opname"; $opname_id = $row->source ? $row->source->id : '-';
} else { return "Opname: #{$opname_id}";
} elseif ($row->source_type === 'App\\Models\\Transaction')
{
$transaction_id = $row->source ? $row->source->id : '-';
return "Transaksi: #{$transaction_id}";
}else {
return $row->source_type ?? '-'; return $row->source_type ?? '-';
} }
}) })

View File

@@ -7,12 +7,14 @@ use App\Models\Work;
use App\Models\Transaction; use App\Models\Transaction;
use App\Models\StockLog; use App\Models\StockLog;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Exception; use Exception;
class StockService class StockService
{ {
/** /**
* Check if dealer has sufficient stock for work * Check if dealer has sufficient stock for work
* Modified to always return available = true (allow negative stock)
* *
* @param int $workId * @param int $workId
* @param int $dealerId * @param int $dealerId
@@ -25,36 +27,30 @@ class StockService
if (!$work) { if (!$work) {
return [ return [
'available' => false, 'available' => true,
'message' => 'Pekerjaan tidak ditemukan', 'message' => 'Pekerjaan tidak ditemukan, tapi transaksi diizinkan',
'details' => [] 'details' => []
]; ];
} }
$stockDetails = []; $stockDetails = [];
$allAvailable = true;
foreach ($work->products as $product) { foreach ($work->products as $product) {
$requiredQuantity = $product->pivot->quantity_required * $workQuantity; $requiredQuantity = $product->pivot->quantity_required * $workQuantity;
$availableStock = $product->getStockByDealer($dealerId); $availableStock = $product->getStockByDealer($dealerId);
$isAvailable = $availableStock >= $requiredQuantity;
if (!$isAvailable) {
$allAvailable = false;
}
$stockDetails[] = [ $stockDetails[] = [
'product_id' => $product->id, 'product_id' => $product->id,
'product_name' => $product->name, 'product_name' => $product->name,
'required_quantity' => $requiredQuantity, 'required_quantity' => $requiredQuantity,
'available_stock' => $availableStock, 'available_stock' => $availableStock,
'is_available' => $isAvailable 'is_available' => true // Always true - allow negative stock
]; ];
} }
return [ return [
'available' => $allAvailable, 'available' => true, // Always return true - allow negative stock
'message' => $allAvailable ? 'Stock tersedia' : 'Stock tidak mencukupi', 'message' => 'Stock tersedia (negative stock allowed)',
'details' => $stockDetails 'details' => $stockDetails
]; ];
} }
@@ -68,11 +64,13 @@ class StockService
*/ */
public function reduceStockForTransaction(Transaction $transaction) public function reduceStockForTransaction(Transaction $transaction)
{ {
try {
return DB::transaction(function () use ($transaction) { return DB::transaction(function () use ($transaction) {
$work = $transaction->work; $work = $transaction->work;
if (!$work) { if (!$work) {
throw new Exception('Work not found for transaction'); // If work not found, just return true to allow transaction to proceed
return true;
} }
$work->load('products'); $work->load('products');
@@ -84,30 +82,123 @@ class StockService
foreach ($work->products as $product) { foreach ($work->products as $product) {
$requiredQuantity = $product->pivot->quantity_required * $transaction->qty; $requiredQuantity = $product->pivot->quantity_required * $transaction->qty;
Log::info('Processing stock reduction', [
'transaction_id' => $transaction->id,
'product_id' => $product->id,
'product_name' => $product->name,
'dealer_id' => $transaction->dealer_id,
'required_quantity' => $requiredQuantity,
'transaction_qty' => $transaction->qty
]);
$stock = Stock::where('product_id', $product->id) $stock = Stock::where('product_id', $product->id)
->where('dealer_id', $transaction->dealer_id) ->where('dealer_id', $transaction->dealer_id)
->first(); ->first();
if (!$stock) { if (!$stock) {
throw new Exception("Stock not found for product {$product->name} at dealer"); Log::info('Stock not found, creating new stock record', [
'product_id' => $product->id,
'dealer_id' => $transaction->dealer_id
]);
try {
// Create new stock record with 0 quantity if doesn't exist
$stock = Stock::create([
'product_id' => $product->id,
'dealer_id' => $transaction->dealer_id,
'quantity' => 0
]);
Log::info('New stock record created', [
'stock_id' => $stock->id,
'initial_quantity' => $stock->quantity
]);
} catch (\Exception $createException) {
Log::warning('Failed to create stock, using firstOrCreate', [
'error' => $createException->getMessage()
]);
// If creating stock fails, try to use firstOrCreate instead
$stock = Stock::firstOrCreate([
'product_id' => $product->id,
'dealer_id' => $transaction->dealer_id
], [
'quantity' => 0
]);
}
} else {
Log::info('Existing stock found', [
'stock_id' => $stock->id,
'current_quantity' => $stock->quantity
]);
} }
if ($stock->quantity < $requiredQuantity) { // Allow negative stock - reduce regardless of current quantity
throw new Exception("Insufficient stock for product {$product->name}. Required: {$requiredQuantity}, Available: {$stock->quantity}");
}
// Reduce stock
$newQuantity = $stock->quantity - $requiredQuantity; $newQuantity = $stock->quantity - $requiredQuantity;
Log::info('Updating stock quantity', [
'stock_id' => $stock->id,
'previous_quantity' => $stock->quantity,
'required_quantity' => $requiredQuantity,
'new_quantity' => $newQuantity
]);
try {
$stock->updateStock( $stock->updateStock(
$newQuantity, $newQuantity,
$transaction, $transaction,
"Stock reduced for work: {$work->name} (Transaction #{$transaction->id})" "Stock reduced for work: {$work->name} (Transaction #{$transaction->id}) - Allow negative stock"
); );
Log::info('Stock update successful via updateStock method');
} catch (\Exception $updateException) {
Log::warning('updateStock method failed, using fallback', [
'error' => $updateException->getMessage()
]);
// If updateStock fails, try direct update but still create stock log
$previousQuantity = $stock->quantity;
$stock->quantity = $newQuantity;
$stock->save();
// Manually create stock log since updateStock failed
try {
$stockLog = \App\Models\StockLog::create([
'stock_id' => $stock->id,
'source_type' => get_class($transaction),
'source_id' => $transaction->id,
'previous_quantity' => $previousQuantity,
'new_quantity' => $newQuantity,
'quantity_change' => $newQuantity - $previousQuantity,
'description' => "Stock reduced for work: {$work->name} (Transaction #{$transaction->id}) - Allow negative stock (manual log)",
'user_id' => auth()->id()
]);
Log::info('Manual stock log created successfully', [
'stock_log_id' => $stockLog->id,
'previous_quantity' => $previousQuantity,
'new_quantity' => $newQuantity
]);
} catch (\Exception $logException) {
// Log the error but don't fail the transaction
Log::warning('Failed to create stock log: ' . $logException->getMessage());
}
}
} }
return true; return true;
}); });
} catch (\Exception $e) {
// Log the error but don't throw it - allow transaction to proceed
Log::error('StockService::reduceStockForTransaction error: ' . $e->getMessage(), [
'transaction_id' => $transaction->id,
'work_id' => $transaction->work_id,
'dealer_id' => $transaction->dealer_id
]);
// Return true to allow transaction to proceed even if stock reduction fails
return true;
}
} }
/** /**

View File

@@ -1,101 +1,112 @@
$.ajaxSetup({ $.ajaxSetup({
headers: { headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
} },
}); });
var table = $('#kt_table').DataTable({ var table = $("#kt_table").DataTable({
processing: true, processing: true,
serverSide: true, serverSide: true,
ajax: $("input[name='ajax_url']"), ajax: $("input[name='ajax_url']"),
columns: [ columns: [
{data: 'name', name: 'name'}, { data: "name", name: "name" },
{data: 'form', name: 'form'}, { data: "form", name: "form" },
{data: 'action', name: 'action', orderable: false, searchable: false}, { data: "action", name: "action", orderable: false, searchable: false },
] ],
}); });
$("#addCategory").click(function () {
$("#addCategory").click(function() { $("#categoryModal").modal("show");
$("#categoryModal").modal("show") let form_action = $("input[name='store_url']").val();
let form_action = $("input[name='store_url']").val() $("#categoryForm").attr("action", form_action);
$("#categoryForm").attr('action', form_action) $("#categoryForm input[name='_method']").remove();
$("#categoryForm input[name='_method']").remove() $("#categoryForm").attr("data-form", "store");
$("#categoryForm").attr('data-form', 'store') $("#categoryForm").trigger("reset");
$("#categoryForm").trigger("reset") });
})
function destroyCategory(id) { function destroyCategory(id) {
let action = $("#destroyCategory"+id).attr("data-action") let action = $("#destroyCategory" + id).attr("data-action");
console.log(action) console.log(action);
Swal.fire({ Swal.fire({
title: 'Hapus Kategori?', title: "Hapus Kategori?",
icon: "question",
text: "Data Pekerjaan yang terkait dengan kategori ini juga akan terhapus!", text: "Data Pekerjaan yang terkait dengan kategori ini juga akan terhapus!",
showCancelButton: true, showCancelButton: true,
confirmButtonColor: '#d33', confirmButtonColor: "#d33",
cancelButtonColor: '#dedede', cancelButtonColor: "#dedede",
confirmButtonText: 'Hapus' confirmButtonText: "Hapus",
}).then((result) => { }).then((result) => {
if (result.value) { if (result.value) {
$.ajax({ $.ajax({
url: action, url: action,
type: 'POST', type: "POST",
data: { data: {
_token: $('meta[name="csrf-token"]').attr('content'), _token: $('meta[name="csrf-token"]').attr("content"),
_method: 'DELETE' _method: "DELETE",
}, },
success: function(res) { success: function (res) {
Swal.fire( Swal.fire("Kategori Dihapus!");
'Kategori Dihapus!' table.ajax.reload();
) },
table.ajax.reload() });
}
})
} }
}) });
} }
function editCategory(id) { function editCategory(id) {
let form_action = $("#editCategory"+id).attr("data-action") let form_action = $("#editCategory" + id).attr("data-action");
let edit_url = $("#editCategory"+id).attr("data-url") let edit_url = $("#editCategory" + id).attr("data-url");
$("#categoryModal").modal("show") $("#categoryModal").modal("show");
$("#categoryForm").append('<input type="hidden" name="_method" value="PUT">') $("#categoryForm").append(
$("#categoryForm").attr('action', form_action) '<input type="hidden" name="_method" value="PUT">'
$("#categoryForm").attr('data-form', 'update') );
$.get(edit_url, function(res) { $("#categoryForm").attr("action", form_action);
$("#categoryForm input[name='name']").val(res.data.name) $("#categoryForm").attr("data-form", "update");
$("#categoryForm option[value='"+ res.data.form +"']").prop('selected', true); $.get(edit_url, function (res) {
}) $("#categoryForm input[name='name']").val(res.data.name);
$("#categoryForm option[value='" + res.data.form + "']").prop(
"selected",
true
);
});
} }
$(document).ready(function () { $(document).ready(function () {
$("#categoryForm").submit(function(e) { // Add event handlers for modal close buttons
e.preventDefault(); $('.close, [data-dismiss="modal"]').on("click", function () {
let dataForm = $("#categoryForm").attr('data-form') $("#categoryModal").modal("hide");
if(dataForm == 'store') { });
$.ajax({
url: $('#categoryForm').attr("action"),
type: 'POST',
data: $('#categoryForm').serialize(),
success: function(res) {
$("#categoryModal").modal("hide")
$('#categoryForm').trigger("reset")
table.ajax.reload()
}
})
}else if(dataForm == 'update') {
$.ajax({
url: $('#categoryForm').attr("action"),
type: 'POST',
data: $('#categoryForm').serialize(),
success: function(res) {
$("#categoryModal").modal("hide")
$('#categoryForm').trigger("reset")
table.ajax.reload()
}
})
}
})
}); // Also handle the "Batal" button
$('.btn-secondary[data-dismiss="modal"]').on("click", function () {
$("#categoryModal").modal("hide");
});
$("#categoryForm").submit(function (e) {
e.preventDefault();
let dataForm = $("#categoryForm").attr("data-form");
if (dataForm == "store") {
$.ajax({
url: $("#categoryForm").attr("action"),
type: "POST",
data: $("#categoryForm").serialize(),
success: function (res) {
$("#categoryModal").modal("hide");
$("#categoryForm").trigger("reset");
table.ajax.reload();
},
});
} else if (dataForm == "update") {
$.ajax({
url: $("#categoryForm").attr("action"),
type: "POST",
data: $("#categoryForm").serialize(),
success: function (res) {
$("#categoryModal").modal("hide");
$("#categoryForm").trigger("reset");
table.ajax.reload();
},
});
}
});
});

View File

@@ -1,128 +1,136 @@
$.ajaxSetup({ $.ajaxSetup({
headers: { headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
} },
}); });
var table = $('#kt_table').DataTable({ var table = $("#kt_table").DataTable({
processing: true, processing: true,
serverSide: true, serverSide: true,
ajax: $("input[name='ajax_url']"), ajax: $("input[name='ajax_url']"),
columns: [ columns: [
{data: 'dealer_code', name: 'dealer_code'}, { data: "dealer_code", name: "dealer_code" },
{data: 'name', name: 'name'}, { data: "name", name: "name" },
{data: 'pic_name', name: 'u.name'}, { data: "pic_name", name: "u.name" },
{data: 'address', name: 'address'}, { data: "address", name: "address" },
{data: 'action', name: 'action', orderable: false, searchable: false}, { data: "action", name: "action", orderable: false, searchable: false },
] ],
}); });
$("#addDealer").click(function () {
$("#addDealer").click(function() { $("#dealerModal").modal("show");
$("#dealerModal").modal("show") let form_action = $("input[name='store_url']").val();
let form_action = $("input[name='store_url']").val() $("#dealerForm").attr("action", form_action);
$("#dealerForm").attr('action', form_action) $("#dealerForm input[name='_method']").remove();
$("#dealerForm input[name='_method']").remove() $("#dealerForm").attr("data-form", "store");
$("#dealerForm").attr('data-form', 'store') $("#dealerForm").trigger("reset");
$("#dealerForm").trigger("reset") });
})
function destroyDealer(id) { function destroyDealer(id) {
let action = $("#destroyDealer"+id).attr("data-action") let action = $("#destroyDealer" + id).attr("data-action");
Swal.fire({ Swal.fire({
title: 'Hapus Dealer?', title: "Hapus Dealer?",
text: "Data pengguna yang terkait dengan dealer ini juga akan terhapus!", text: "Data pengguna yang terkait dengan dealer ini juga akan terhapus!",
showCancelButton: true, showCancelButton: true,
confirmButtonColor: '#d33', confirmButtonColor: "#d33",
cancelButtonColor: '#dedede', cancelButtonColor: "#dedede",
confirmButtonText: 'Hapus' confirmButtonText: "Hapus",
}).then((result) => { }).then((result) => {
if (result.value) { if (result.value) {
$.ajax({ $.ajax({
url: action, url: action,
type: 'POST', type: "POST",
data: { data: {
_token: $('meta[name="csrf-token"]').attr('content'), _token: $('meta[name="csrf-token"]').attr("content"),
_method: 'DELETE' _method: "DELETE",
}, },
success: function(res) { success: function (res) {
Swal.fire( Swal.fire("Dealer Dihapus!");
'Dealer Dihapus!' table.ajax.reload();
) },
table.ajax.reload() });
}
})
} }
}) });
} }
function editDealer(id) { function editDealer(id) {
let form_action = $("#editDealer"+id).attr("data-action") let form_action = $("#editDealer" + id).attr("data-action");
let edit_url = $("#editDealer"+id).attr("data-url") let edit_url = $("#editDealer" + id).attr("data-url");
$("#dealerModal").modal("show") $("#dealerModal").modal("show");
$("#dealerForm").append('<input type="hidden" name="_method" value="PUT">') $("#dealerForm").append('<input type="hidden" name="_method" value="PUT">');
$("#dealerForm").attr('action', form_action) $("#dealerForm").attr("action", form_action);
$("#dealerForm").attr('data-form', 'update') $("#dealerForm").attr("data-form", "update");
$.get(edit_url, function(res) { $.get(edit_url, function (res) {
$("#dealerForm input[name='name']").val(res.data.name) $("#dealerForm input[name='name']").val(res.data.name);
$("#dealerForm input[name='dealer_code']").val(res.data.dealer_code) $("#dealerForm input[name='dealer_code']").val(res.data.dealer_code);
$("#dealerForm input[name='address']").val(res.data.address) $("#dealerForm input[name='address']").val(res.data.address);
}) });
} }
function addPic(id) { function addPic(id) {
let form_action = $("#addPic"+id).attr("data-action") let form_action = $("#addPic" + id).attr("data-action");
let edit_url = $("#addPic"+id).attr("data-url") let edit_url = $("#addPic" + id).attr("data-url");
$("#picModal").modal("show") $("#picModal").modal("show");
$("#picForm").append('<input type="hidden" name="_method" value="PUT">') $("#picForm").append('<input type="hidden" name="_method" value="PUT">');
$("#picForm").attr('action', form_action) $("#picForm").attr("action", form_action);
$.get(edit_url, function(res) { $.get(edit_url, function (res) {
$("#picForm input[name='name']").val(res.data.name) $("#picForm input[name='name']").val(res.data.name);
$("#picForm input[name='dealer_code']").val(res.data.dealer_code) $("#picForm input[name='dealer_code']").val(res.data.dealer_code);
}) });
} }
$(document).ready(function () { $(document).ready(function () {
$("#picForm").submit(function(e) { // Add event handlers for modal close buttons
e.preventDefault() $('.close, [data-dismiss="modal"]').on("click", function () {
$.ajax({ $("#dealerModal").modal("hide");
url: $('#picForm').attr("action"), $("#picModal").modal("hide");
type: 'POST', });
data: {pic: $('#picForm select[name="pic_id"]').val()},
success: function(res) {
$("#picModal").modal("hide")
$('#picForm').trigger("reset")
table.ajax.reload()
}
})
})
$("#dealerForm").submit(function(e) { // Also handle the "Batal" button
$('.btn-secondary[data-dismiss="modal"]').on("click", function () {
$("#dealerModal").modal("hide");
$("#picModal").modal("hide");
});
$("#picForm").submit(function (e) {
e.preventDefault(); e.preventDefault();
let dataForm = $("#dealerForm").attr('data-form') $.ajax({
if(dataForm == 'store') { url: $("#picForm").attr("action"),
$.ajax({ type: "POST",
url: $('#dealerForm').attr("action"), data: { pic: $('#picForm select[name="pic_id"]').val() },
type: 'POST', success: function (res) {
data: $('#dealerForm').serialize(), $("#picModal").modal("hide");
success: function(res) { $("#picForm").trigger("reset");
$("#dealerModal").modal("hide") table.ajax.reload();
$('#dealerForm').trigger("reset") },
table.ajax.reload() });
} });
})
}else if(dataForm == 'update') {
$.ajax({
url: $('#dealerForm').attr("action"),
type: 'POST',
data: $('#dealerForm').serialize(),
success: function(res) {
$("#dealerModal").modal("hide")
$('#dealerForm').trigger("reset")
table.ajax.reload()
}
})
}
})
}); $("#dealerForm").submit(function (e) {
e.preventDefault();
let dataForm = $("#dealerForm").attr("data-form");
if (dataForm == "store") {
$.ajax({
url: $("#dealerForm").attr("action"),
type: "POST",
data: $("#dealerForm").serialize(),
success: function (res) {
$("#dealerModal").modal("hide");
$("#dealerForm").trigger("reset");
table.ajax.reload();
},
});
} else if (dataForm == "update") {
$.ajax({
url: $("#dealerForm").attr("action"),
type: "POST",
data: $("#dealerForm").serialize(),
success: function (res) {
$("#dealerModal").modal("hide");
$("#dealerForm").trigger("reset");
table.ajax.reload();
},
});
}
});
});

View File

@@ -1,105 +1,125 @@
$.ajaxSetup({ $.ajaxSetup({
headers: { headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
} },
}); });
var table = $('#kt_table').DataTable({ var table = $("#kt_table").DataTable({
processing: true, processing: true,
serverSide: true, serverSide: true,
ajax: $("input[name='ajax_url']").val(), ajax: $("input[name='ajax_url']").val(),
columns: [ columns: [
{data: 'DT_RowIndex', name: 'DT_RowIndex', orderable: false, searchable: false}, {
{data: 'dealer_name', name: 'd.name'}, data: "DT_RowIndex",
{data: 'role_name', name: 'r.name'}, name: "DT_RowIndex",
{data: 'name', name: 'users.name'}, orderable: false,
{data: 'email', name: 'users.email'}, searchable: false,
{data: 'action', name: 'action', orderable: false, searchable: false}, },
] { data: "dealer_name", name: "d.name" },
{ data: "role_name", name: "r.name" },
{ data: "name", name: "users.name" },
{ data: "email", name: "users.email" },
{ data: "action", name: "action", orderable: false, searchable: false },
],
}); });
$("#addUser").click(function () {
$("#addUser").click(function() { $("#userModal").modal("show");
$("#userModal").modal("show") let form_action = $("input[name='store_url']").val();
let form_action = $("input[name='store_url']").val() $("#userForm").attr("action", form_action);
$("#userForm").attr('action', form_action) $("#userForm input[name='_method']").remove();
$("#userForm input[name='_method']").remove() $("#userForm").attr("data-form", "store");
$("#userForm").attr('data-form', 'store') $("#userForm").trigger("reset");
$("#userForm").trigger("reset") $("#modalHeading").html("Tambah Pengguna");
$("#modalHeading").html("Tambah Pengguna") });
})
function destroyUser(id) { function destroyUser(id) {
let action = $("#destroyUser"+id).attr("data-action") let action = $("#destroyUser" + id).attr("data-action");
Swal.fire({ Swal.fire({
title: 'Hapus User?', title: "Hapus User?",
icon: "warning",
text: "Semua data yang terkait dengan Pengguna ini juga akan terhapus!", text: "Semua data yang terkait dengan Pengguna ini juga akan terhapus!",
showCancelButton: true, showCancelButton: true,
confirmButtonColor: '#d33', confirmButtonColor: "#d33",
cancelButtonColor: '#dedede', cancelButtonColor: "#dedede",
confirmButtonText: 'Hapus' confirmButtonText: "Hapus",
}).then((result) => { }).then((result) => {
if (result.value) { if (result.value) {
$.ajax({ $.ajax({
url: action, url: action,
type: 'POST', type: "POST",
data: { data: {
_token: $('meta[name="csrf-token"]').attr('content'), _token: $('meta[name="csrf-token"]').attr("content"),
_method: 'DELETE' _method: "DELETE",
}, },
success: function(res) { success: function (res) {
Swal.fire( Swal.fire("Pengguna Dihapus!");
'Pengguna Dihapus!' table.ajax.reload();
) },
table.ajax.reload() });
}
})
} }
}) });
} }
function editUser(id) { function editUser(id) {
let form_action = $("#editUser"+id).attr("data-action") let form_action = $("#editUser" + id).attr("data-action");
let edit_url = $("#editUser"+id).attr("data-url") let edit_url = $("#editUser" + id).attr("data-url");
$("#userModal").modal("show") $("#userModal").modal("show");
$("#userForm").append('<input type="hidden" name="_method" value="PUT">') $("#userForm").append('<input type="hidden" name="_method" value="PUT">');
$("#userForm").attr('action', form_action) $("#userForm").attr("action", form_action);
$("#userForm").attr('data-form', 'update') $("#userForm").attr("data-form", "update");
$.get(edit_url, function(res) { $.get(edit_url, function (res) {
$("#userForm input[name='name']").val(res.data.name) $("#userForm input[name='name']").val(res.data.name);
$("#userForm input[name='email']").val(res.data.email) $("#userForm input[name='email']").val(res.data.email);
$("#userForm select[name='dealer_id'] option[value='"+ res.data.dealer_id +"']").prop('selected', true); $(
$("#userForm select[name='role'] option[value='"+ res.data.role_id +"']").prop('selected', true); "#userForm select[name='dealer_id'] option[value='" +
}) res.data.dealer_id +
"']"
).prop("selected", true);
$(
"#userForm select[name='role'] option[value='" +
res.data.role_id +
"']"
).prop("selected", true);
});
} }
$(document).ready(function () { $(document).ready(function () {
$("#userForm").submit(function(e) { // Add event handlers for modal close buttons
e.preventDefault(); $('.close, [data-dismiss="modal"]').on("click", function () {
let dataForm = $("#userForm").attr('data-form') $("#userModal").modal("hide");
if(dataForm == 'store') { });
$.ajax({
url: $('#userForm').attr("action"),
type: 'POST',
data: $('#userForm').serialize(),
success: function(res) {
$("#userModal").modal("hide")
$('#userForm').trigger("reset")
table.ajax.reload()
}
})
}else if(dataForm == 'update') {
$.ajax({
url: $('#userForm').attr("action"),
type: 'POST',
data: $('#userForm').serialize(),
success: function(res) {
$("#userModal").modal("hide")
$('#userForm').trigger("reset")
table.ajax.reload()
}
})
}
})
}); // Also handle the "Batal" button
$('.btn-secondary[data-dismiss="modal"]').on("click", function () {
$("#userModal").modal("hide");
});
$("#userForm").submit(function (e) {
e.preventDefault();
let dataForm = $("#userForm").attr("data-form");
if (dataForm == "store") {
$.ajax({
url: $("#userForm").attr("action"),
type: "POST",
data: $("#userForm").serialize(),
success: function (res) {
$("#userModal").modal("hide");
$("#userForm").trigger("reset");
table.ajax.reload();
},
});
} else if (dataForm == "update") {
$.ajax({
url: $("#userForm").attr("action"),
type: "POST",
data: $("#userForm").serialize(),
success: function (res) {
$("#userModal").modal("hide");
$("#userForm").trigger("reset");
table.ajax.reload();
},
});
}
});
});

View File

@@ -55,6 +55,7 @@
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="modalHeading"></h5> <h5 class="modal-title" id="modalHeading"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">

View File

@@ -56,6 +56,7 @@
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="modalHeading"></h5> <h5 class="modal-title" id="modalHeading"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
@@ -93,6 +94,7 @@
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="modalHeading"></h5> <h5 class="modal-title" id="modalHeading"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">

View File

@@ -119,6 +119,7 @@
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="modalHeading"></h5> <h5 class="modal-title" id="modalHeading"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
@@ -364,6 +365,16 @@ function bulkClose(url, selected) {
} }
jQuery(document).ready(function () { jQuery(document).ready(function () {
// Add event handlers for modal close buttons
$('.close, [data-dismiss="modal"]').on('click', function() {
$('#transactionModal').modal('hide');
});
// Also handle the "Batal" button
$('.btn-secondary[data-dismiss="modal"]').on('click', function() {
$('#transactionModal').modal('hide');
});
KTBootstrapDatepicker.init(); KTBootstrapDatepicker.init();
$('#kt_search').on('click', function (e) { $('#kt_search').on('click', function (e) {

View File

@@ -48,18 +48,14 @@
<td>{{ $loop->iteration }}</td> <td>{{ $loop->iteration }}</td>
<td>{{ $role->name }}</td> <td>{{ $role->name }}</td>
<td> <td>
@can('update', $menus['roleprivileges.index']) <div class="d-flex">
<button class="btn btn-sm btn-warning" onclick="editRole({{$role->id}})"><i class="fa fa-edit"></i> Edit</button> @can('update', $menus['roleprivileges.index'])
@endcan <button class="btn btn-sm btn-bold btn-warning mr-2" onclick="editRole({{$role->id}})"> Edit</button>
@can('delete', $menus['roleprivileges.index']) @endcan
<br> @can('delete', $menus['roleprivileges.index'])
<br> <button class="btn btn-sm btn-bold btn-danger" onclick="deleteRole({{$role->id}}, '{{$role->name}}')">Hapus</button>
<form action="{{ route('roleprivileges.delete', $role->id) }}" method="post" onsubmit="return confirm('Anda akan menghapus role {{ $role->name }}?');"> @endcan
@csrf </div>
@method('DELETE')
<button type="submit" class="btn btn-sm btn-danger"><i class="fa fa-trash"></i> Hapus</button>
</form>
@endcan
</td> </td>
</tr> </tr>
@endforeach @endforeach
@@ -191,6 +187,41 @@
@section('javascripts') @section('javascripts')
<script> <script>
function deleteRole(id, name) {
Swal.fire({
title: 'Hapus Role?',
text: `Anda akan menghapus role ${name}?`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#6c757d',
confirmButtonText: 'Ya, Hapus!',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
// Create form and submit
let form = document.createElement('form');
form.method = 'POST';
form.action = '{{ route("roleprivileges.delete", ":id") }}'.replace(':id', id);
let csrfToken = document.createElement('input');
csrfToken.type = 'hidden';
csrfToken.name = '_token';
csrfToken.value = '{{ csrf_token() }}';
let methodField = document.createElement('input');
methodField.type = 'hidden';
methodField.name = '_method';
methodField.value = 'DELETE';
form.appendChild(csrfToken);
form.appendChild(methodField);
document.body.appendChild(form);
form.submit();
}
});
}
function editRole(id) { function editRole(id) {
let url = '{{ route("roleprivileges.edit", ":id") }}' let url = '{{ route("roleprivileges.edit", ":id") }}'
url = url.replace(':id', id); url = url.replace(':id', id);
@@ -265,5 +296,18 @@
} }
}) })
} }
$(document).ready(function () {
// Add event handlers for modal close buttons
$('.close, [data-dismiss="modal"]').on("click", function () {
$("#roleModal").modal("hide");
$("#roleEditModal").modal("hide");
});
// Also handle the "Close" button
$('.btn-secondary[data-dismiss="modal"]').on("click", function () {
$("#roleModal").modal("hide");
$("#roleEditModal").modal("hide");
});
});
</script> </script>
@endsection @endsection

View File

@@ -51,6 +51,7 @@
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="modalHeading"></h5> <h5 class="modal-title" id="modalHeading"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">

View File

@@ -87,14 +87,7 @@ use Illuminate\Support\Facades\Auth;
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0,0,0,0.15); box-shadow: 0 6px 12px rgba(0,0,0,0.15);
} }
.physical-stock.is-invalid {
border-color: #dc3545;
background-color: #fff5f5;
}
.physical-stock.is-invalid:focus {
border-color: #dc3545;
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
}
.quantity-input-mutasi.is-invalid { .quantity-input-mutasi.is-invalid {
border-color: #dc3545; border-color: #dc3545;
background-color: #fff5f5; background-color: #fff5f5;
@@ -424,16 +417,7 @@ use Illuminate\Support\Facades\Auth;
<input type="hidden" name="mechanic_id" value="{{ $mechanic->id }}"> <input type="hidden" name="mechanic_id" value="{{ $mechanic->id }}">
<input type="hidden" name="dealer_id" value="{{ $mechanic->dealer_id }}"> <input type="hidden" name="dealer_id" value="{{ $mechanic->dealer_id }}">
<!-- Stock Error Display -->
@if($errors->has('stock'))
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<strong><i class="fa fa-exclamation-triangle"></i> Peringatan Stock:</strong>
<br>{!! $errors->first('stock') !!}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
@endif
@if($errors->has('error')) @if($errors->has('error'))
<div class="alert alert-danger alert-dismissible fade show" role="alert"> <div class="alert alert-danger alert-dismissible fade show" role="alert">
@@ -473,7 +457,7 @@ use Illuminate\Support\Facades\Auth;
<div class="col-6"> <div class="col-6">
<label>Warranty</label> <label>Warranty</label>
<select name="warranty" class="form-control @if(old('form') == 'work') @error('warranty') is-invalid @enderror @endif"> <select name="warranty" class="form-control @if(old('form') == 'work') @error('warranty') is-invalid @enderror @endif">
<option selected>Warranty</option> <option value="" @if(old('form') == 'work') @if(old('warranty') == '' || old('warranty') == null) selected @endif @else selected @endif>Warranty</option>
<option value="1" @if(old('form') == 'work') @if(old('warranty') == 1) selected @endif @endif>Ya</option> <option value="1" @if(old('form') == 'work') @if(old('warranty') == 1) selected @endif @endif>Ya</option>
<option value="0" @if(old('form') == 'work') @if(old('warranty') == 0) selected @endif @endif>Tidak</option> <option value="0" @if(old('form') == 'work') @if(old('warranty') == 0) selected @endif @endif>Tidak</option>
</select> </select>
@@ -676,15 +660,17 @@ use Illuminate\Support\Facades\Auth;
<div class="col-6"> <div class="col-6">
<label>Warranty</label> <label>Warranty</label>
<select name="warranty" class="form-control @if(old('form') == 'wash') @error('warranty') is-invalid @enderror @endif"> <select name="warranty" class="form-control @if(old('form') == 'wash') @error('warranty') is-invalid @enderror @endif">
<option selected>Warranty</option> <option value="" @if(old('form') == 'wash') @if(old('warranty') == '' || old('warranty') == null) selected @endif @else selected @endif>Warranty</option>
<option value="1" @if(old('form') == 'wash') @if(old('warranty') == 1) selected @endif @endif>Ya</option> <option value="1" @if(old('form') == 'wash') @if(old('warranty') == 1) selected @endif @endif>Ya</option>
<option value="0" @if(old('form') == 'wash') @if(old('warranty') == 0) selected @endif @endif>Tidak</option> <option value="0" @if(old('form') == 'wash') @if(old('warranty') == 0) selected @endif @endif>Tidak</option>
</select> </select>
@if(old('form') == 'wash')
@error('warranty') @error('warranty')
<span class="invalid-feedback" role="alert"> <span class="invalid-feedback" role="alert">
<strong>{!! $message !!}</strong> <strong>{!! $message !!}</strong>
</span> </span>
@enderror @enderror
@endif
</div> </div>
<div class="col-6"> <div class="col-6">
<label>Tanggal Pekerjaan</label> <label>Tanggal Pekerjaan</label>
@@ -988,112 +974,8 @@ use Illuminate\Support\Facades\Auth;
} }
}); });
// Global variables for stock checking // Global variables (stock checking removed)
let dealerId = {{ $mechanic->dealer_id }}; let dealerId = {{ $mechanic->dealer_id }};
let stockWarnings = {};
// Function to check stock availability for selected work
function checkStockAvailability(workId, quantity, callback) {
if (!workId || !quantity || quantity < 1) {
if (callback) callback(null);
return;
}
$.ajax({
url: "{{ route('transaction.check-stock') }}",
method: 'POST',
data: {
work_id: workId,
dealer_id: dealerId,
quantity: quantity,
_token: $('meta[name="csrf-token"]').attr('content')
},
success: function(response) {
if (callback) callback(response.data);
},
error: function(xhr) {
console.error('Error checking stock:', xhr);
if (callback) callback(null);
}
});
}
// Function to display stock warning
function displayStockWarning(fieldId, stockData) {
// Remove existing warning
$(`#stock-warning-${fieldId}`).remove();
if (!stockData || stockData.available) {
return; // No warning needed
}
let warningHtml = `
<div id="stock-warning-${fieldId}" class="alert alert-warning mt-2 mb-0" style="font-size: 12px;">
<strong><i class="fa fa-exclamation-triangle"></i> Peringatan Stock:</strong> ${stockData.message}
<ul class="mb-0 mt-1" style="font-size: 11px;">
`;
stockData.details.forEach(function(detail) {
if (!detail.is_available) {
warningHtml += `
<li>${detail.product_name}: Butuh ${detail.required_quantity}, Tersedia ${detail.available_stock}</li>
`;
}
});
warningHtml += `
</ul>
</div>
`;
$(`#work_field${fieldId.replace('work_work', '')}`).append(warningHtml);
}
// Function to handle work selection change
function handleWorkSelectionChange(selectElement) {
let workId = $(selectElement).val();
let fieldId = $(selectElement).attr('id');
let quantityInput = $(selectElement).closest('.form-group').find('input[name="quantity[]"]');
let quantity = parseInt(quantityInput.val()) || 1;
if (workId) {
checkStockAvailability(workId, quantity, function(stockData) {
displayStockWarning(fieldId, stockData);
// Store warning data for form submission validation
if (stockData && !stockData.available) {
stockWarnings[fieldId] = stockData;
} else {
delete stockWarnings[fieldId];
}
});
} else {
// Remove warning when no work selected
$(`#stock-warning-${fieldId}`).remove();
delete stockWarnings[fieldId];
}
}
// Function to handle quantity change
function handleQuantityChange(quantityInput) {
let workSelect = $(quantityInput).closest('.form-group').find('select[name="work_id[]"]');
let workId = workSelect.val();
let quantity = parseInt($(quantityInput).val()) || 1;
if (workId && quantity > 0) {
let fieldId = workSelect.attr('id');
checkStockAvailability(workId, quantity, function(stockData) {
displayStockWarning(fieldId, stockData);
// Store warning data for form submission validation
if (stockData && !stockData.available) {
stockWarnings[fieldId] = stockData;
} else {
delete stockWarnings[fieldId];
}
});
}
}
function logout(event){ function logout(event){
event.preventDefault(); event.preventDefault();
@@ -1191,24 +1073,8 @@ use Illuminate\Support\Facades\Auth;
}) })
} }
// Add event listeners for existing fields // Setup form fields without stock checking
$(document).ready(function() { $(document).ready(function() {
// Initial fields (work1, work2, etc.)
$('select[name="work_id[]"]').on('change', function() {
handleWorkSelectionChange(this);
});
$('input[name="quantity[]"]').on('input', function() {
handleQuantityChange(this);
});
// Check stock for pre-filled fields
$('select[name="work_id[]"]').each(function() {
if ($(this).val()) {
handleWorkSelectionChange(this);
}
});
// Ensure placeholder options are properly disabled and not selectable // Ensure placeholder options are properly disabled and not selectable
$('select[name="work_id[]"]').each(function() { $('select[name="work_id[]"]').each(function() {
var $select = $(this); var $select = $(this);
@@ -1241,19 +1107,24 @@ use Illuminate\Support\Facades\Auth;
if ($placeholder.length) { if ($placeholder.length) {
$placeholder.prop('selected', true); $placeholder.prop('selected', true);
} }
} else {
// Trigger change event to update any dependent fields
$select.trigger('change');
} }
}); });
// Update field counter to match the actual number of work fields when validation fails
@if(old('form') == 'work' && old('work_id'))
var actualFieldCount = $('select[name="work_id[]"]').length;
$('.work_field_counter').val(actualFieldCount);
console.log('Validation failed - updating field counter to:', actualFieldCount);
console.log('Old work_id values:', @json(old('work_id')));
@endif
@endif @endif
}); });
// Override addFormField function to include event listeners // Function to add form fields (stock checking removed)
function addFormFieldWithStockCheck(form) { function addFormFieldWithStockCheck(form) {
addFormField(form); // Call original function addFormField(form); // Call original function
// Add event listeners to new field // Setup placeholder for new field
setTimeout(function() { setTimeout(function() {
var id = $(`.${form}_field_counter`).val(); var id = $(`.${form}_field_counter`).val();
var $newSelect = $(`#${form}_work${id}`); var $newSelect = $(`#${form}_work${id}`);
@@ -1264,14 +1135,6 @@ use Illuminate\Support\Facades\Auth;
$placeholder.prop('disabled', true).prop('selected', true).val(''); $placeholder.prop('disabled', true).prop('selected', true).val('');
} }
$newSelect.on('change', function() {
handleWorkSelectionChange(this);
});
$newSelect.closest('.form-group').find('input[name="quantity[]"]').on('input', function() {
handleQuantityChange(this);
});
// Prevent auto-selection of first non-disabled option // Prevent auto-selection of first non-disabled option
$newSelect.on('focus', function() { $newSelect.on('focus', function() {
if (!$(this).val()) { if (!$(this).val()) {
@@ -1287,6 +1150,7 @@ use Illuminate\Support\Facades\Auth;
var policeNumber = $('input[name="police_number"]').val().trim(); var policeNumber = $('input[name="police_number"]').val().trim();
var userSaId = $('select[name="user_sa_id"]').val(); var userSaId = $('select[name="user_sa_id"]').val();
var date = $('input[name="date"]').val().trim(); var date = $('input[name="date"]').val().trim();
var warranty = $('select[name="warranty"]').val();
var errorMessages = []; var errorMessages = [];
@@ -1318,6 +1182,13 @@ use Illuminate\Support\Facades\Auth;
$('input[name="date"]').removeClass('is-invalid'); $('input[name="date"]').removeClass('is-invalid');
} }
if (!warranty || warranty === '' || warranty === 'Warranty') {
errorMessages.push('Warranty harus dipilih');
$('select[name="warranty"]').addClass('is-invalid');
} else {
$('select[name="warranty"]').removeClass('is-invalid');
}
if (errorMessages.length > 0) { if (errorMessages.length > 0) {
e.preventDefault(); e.preventDefault();
Swal.fire({ Swal.fire({
@@ -1336,68 +1207,66 @@ use Illuminate\Support\Facades\Auth;
return false; return false;
} }
// Validate that at least one work is selected // Validate that at least one work is selected with valid quantity
var hasSelectedWork = false; var hasValidWorkQuantity = false;
$('select[name="work_id[]"]').each(function() { var validCount = 0;
if ($(this).val() && $(this).val() !== '') { var emptyWorkSelects = [];
hasSelectedWork = true; var emptyQuantityInputs = [];
return false; // break loop
$('select[name="work_id[]"]').each(function(index) {
var workId = $(this).val();
var $quantityInput = $(this).closest('.form-group').find('input[name="quantity[]"]');
var quantity = $quantityInput.val();
// Remove previous error styling
$(this).removeClass('is-invalid');
$quantityInput.removeClass('is-invalid');
// Check if this pair is valid (both work and quantity filled)
if (workId && workId !== '' && quantity && parseInt(quantity) > 0) {
hasValidWorkQuantity = true;
validCount++;
} else if (workId && workId !== '' && (!quantity || parseInt(quantity) <= 0)) {
// Work selected but no valid quantity
emptyQuantityInputs.push($quantityInput);
} else if ((!workId || workId === '') && quantity && parseInt(quantity) > 0) {
// Quantity filled but no work selected
emptyWorkSelects.push($(this));
} }
}); });
if (!hasSelectedWork) { if (!hasValidWorkQuantity) {
e.preventDefault(); e.preventDefault();
// Highlight problematic fields
emptyWorkSelects.forEach(function($select) {
$select.addClass('is-invalid');
});
emptyQuantityInputs.forEach(function($input) {
$input.addClass('is-invalid');
});
var message = 'Minimal pilih satu pekerjaan dan isi quantity-nya sebelum menyimpan!';
if (emptyWorkSelects.length > 0) {
message += '\n\n• Ada quantity yang diisi tanpa memilih pekerjaan';
}
if (emptyQuantityInputs.length > 0) {
message += '\n\n• Ada pekerjaan yang dipilih tanpa mengisi quantity';
}
Swal.fire({ Swal.fire({
title: 'Peringatan', title: 'Peringatan Validasi',
text: 'Minimal pilih satu pekerjaan sebelum menyimpan!', text: message,
icon: 'warning', icon: 'warning',
confirmButtonText: 'OK' confirmButtonText: 'OK'
}); });
return false; return false;
} }
// Check if there are stock warnings // Show info about how many valid pairs will be saved
if (Object.keys(stockWarnings).length > 0) { console.log(`Akan menyimpan ${validCount} pekerjaan`);
e.preventDefault();
let warningMessages = [];
Object.values(stockWarnings).forEach(function(warning) {
warningMessages.push(warning.message);
warning.details.forEach(function(detail) {
if (!detail.is_available) {
warningMessages.push(`- ${detail.product_name}: Butuh ${detail.required_quantity}, Tersedia ${detail.available_stock}`);
}
});
});
Swal.fire({
title: 'Peringatan Stock Tidak Mencukupi',
html: `
<div class="text-left">
<p class="mb-3">Ada beberapa pekerjaan yang memerlukan produk dengan stock tidak mencukupi:</p>
<div class="alert alert-warning text-left">
${warningMessages.join('<br>')}
</div>
<p class="mb-0"><strong>Apakah Anda yakin ingin melanjutkan?</strong></p>
<small class="text-muted">Transaksi akan tetap dibuat, namun stock akan menjadi negatif.</small>
</div>
`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#ffc107',
cancelButtonColor: '#6c757d',
confirmButtonText: 'Lanjutkan',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
$(".button-save").attr("disabled", true);
$(".button-save").addClass("disabled");
$("#workForm")[0].submit(); // Submit the form
}
});
return false;
}
$(".button-save").attr("disabled", true); $(".button-save").attr("disabled", true);
$(".button-save").addClass("disabled"); $(".button-save").addClass("disabled");
@@ -1405,40 +1274,42 @@ use Illuminate\Support\Facades\Auth;
}) })
$("#washForm").submit(function(e) { $("#washForm").submit(function(e) {
// Validate required fields // Validate required fields within the wash form context
var spkNo = $('input[name="spk_no"]').val().trim(); var $form = $(this);
var policeNumber = $('input[name="police_number"]').val().trim(); var spkNo = $form.find('input[name="spk_no"]').val();
var userSaId = $('select[name="user_sa_id"]').val(); var policeNumber = $form.find('input[name="police_number"]').val();
var date = $('input[name="date"]').val().trim(); var userSaId = $form.find('select[name="user_sa_id"]').val();
var date = $form.find('input[name="date"]').val();
var warranty = $form.find('select[name="warranty"]').val();
var errorMessages = []; var errorMessages = [];
if (!spkNo) { // Reset all field error states
$form.find('input, select').removeClass('is-invalid');
if (!spkNo || spkNo.trim() === '') {
errorMessages.push('No. SPK harus diisi'); errorMessages.push('No. SPK harus diisi');
$('input[name="spk_no"]').addClass('is-invalid'); $form.find('input[name="spk_no"]').addClass('is-invalid');
} else {
$('input[name="spk_no"]').removeClass('is-invalid');
} }
if (!policeNumber) { if (!policeNumber || policeNumber.trim() === '') {
errorMessages.push('No. Polisi harus diisi'); errorMessages.push('No. Polisi harus diisi');
$('input[name="police_number"]').addClass('is-invalid'); $form.find('input[name="police_number"]').addClass('is-invalid');
} else {
$('input[name="police_number"]').removeClass('is-invalid');
} }
if (!userSaId || userSaId === '') { if (!userSaId || userSaId === '' || userSaId === null) {
errorMessages.push('Service Advisor harus dipilih'); errorMessages.push('Service Advisor harus dipilih');
$('select[name="user_sa_id"]').addClass('is-invalid'); $form.find('select[name="user_sa_id"]').addClass('is-invalid');
} else {
$('select[name="user_sa_id"]').removeClass('is-invalid');
} }
if (!date) { if (!date || date.trim() === '') {
errorMessages.push('Tanggal Pekerjaan harus diisi'); errorMessages.push('Tanggal Pekerjaan harus diisi');
$('input[name="date"]').addClass('is-invalid'); $form.find('input[name="date"]').addClass('is-invalid');
} else { }
$('input[name="date"]').removeClass('is-invalid');
if (!warranty || warranty === 'Warranty') {
errorMessages.push('Warranty harus dipilih');
$form.find('select[name="warranty"]').addClass('is-invalid');
} }
if (errorMessages.length > 0) { if (errorMessages.length > 0) {
@@ -1456,11 +1327,23 @@ use Illuminate\Support\Facades\Auth;
icon: 'warning', icon: 'warning',
confirmButtonText: 'OK' confirmButtonText: 'OK'
}); });
// Scroll to first invalid field
var firstInvalid = $form.find('.is-invalid:first');
if (firstInvalid.length) {
$('html, body').animate({
scrollTop: firstInvalid.offset().top - 100
}, 500);
}
return false; return false;
} }
$(".button-save").attr("disabled", true); // Disable submit button to prevent double submission
$(".button-save").addClass("disabled"); $form.find(".button-save").attr("disabled", true);
$form.find(".button-save").addClass("disabled");
$form.find(".button-save").text("Menyimpan...");
return true; return true;
}) })
@@ -1714,7 +1597,7 @@ use Illuminate\Support\Facades\Auth;
if (typeof Swal !== 'undefined') { if (typeof Swal !== 'undefined') {
Swal.fire({ Swal.fire({
type: 'warning', icon: 'warning',
title: 'Validasi Gagal', title: 'Validasi Gagal',
text: errorMessages.join(', ') text: errorMessages.join(', ')
}); });
@@ -1729,7 +1612,7 @@ use Illuminate\Support\Facades\Auth;
Swal.fire({ Swal.fire({
title: 'Kirim Mutasi?', title: 'Kirim Mutasi?',
text: "Mutasi akan dikirim ke dealer tujuan", text: "Mutasi akan dikirim ke dealer tujuan",
type: 'question', icon: 'question',
showCancelButton: true, showCancelButton: true,
confirmButtonColor: '#ffc107', confirmButtonColor: '#ffc107',
cancelButtonColor: '#6c757d', cancelButtonColor: '#6c757d',
@@ -1874,6 +1757,19 @@ use Illuminate\Support\Facades\Auth;
$(document).on('change', 'select[name="user_sa_id"]', function() { $(document).on('change', 'select[name="user_sa_id"]', function() {
$(this).removeClass('is-invalid'); $(this).removeClass('is-invalid');
}); });
$(document).on('change', 'select[name="warranty"]', function() {
$(this).removeClass('is-invalid');
});
// Remove invalid styling from work/quantity fields when user interacts with them
$(document).on('change', 'select[name="work_id[]"]', function() {
$(this).removeClass('is-invalid');
});
$(document).on('input', 'input[name="quantity[]"]', function() {
$(this).removeClass('is-invalid');
});
// Handle server-side errors - scroll to first error and highlight // Handle server-side errors - scroll to first error and highlight
$(document).ready(function() { $(document).ready(function() {
@@ -1938,7 +1834,7 @@ use Illuminate\Support\Facades\Auth;
// Show alert for validation errors // Show alert for validation errors
@if(session('error')) @if(session('error'))
Swal.fire({ Swal.fire({
type: 'error', icon: 'error',
title: 'Terjadi Kesalahan', title: 'Terjadi Kesalahan',
text: '{{ session("error") }}', text: '{{ session("error") }}',
confirmButtonText: 'OK' confirmButtonText: 'OK'
@@ -1951,7 +1847,7 @@ use Illuminate\Support\Facades\Auth;
// Handle success/error messages for both opname and mutasi // Handle success/error messages for both opname and mutasi
@if(session('success')) @if(session('success'))
Swal.fire({ Swal.fire({
type: 'success', icon: 'success',
title: 'Berhasil!', title: 'Berhasil!',
text: '{{ session("success") }}', text: '{{ session("success") }}',
timer: 3000, timer: 3000,
@@ -2055,7 +1951,7 @@ use Illuminate\Support\Facades\Auth;
@if(session('error')) @if(session('error'))
Swal.fire({ Swal.fire({
type: 'error', icon: 'error',
title: 'Terjadi Kesalahan', title: 'Terjadi Kesalahan',
text: '{{ session("error") }}', text: '{{ session("error") }}',
confirmButtonText: 'OK' confirmButtonText: 'OK'
@@ -2084,7 +1980,7 @@ use Illuminate\Support\Facades\Auth;
@endforeach @endforeach
Swal.fire({ Swal.fire({
type: 'error', icon: 'error',
title: 'Validasi Gagal', title: 'Validasi Gagal',
html: errorMessages.join('<br>'), html: errorMessages.join('<br>'),
confirmButtonText: 'OK' confirmButtonText: 'OK'
@@ -2648,7 +2544,7 @@ use Illuminate\Support\Facades\Auth;
var hasInvalidInput = $('.quantity-approved-input.is-invalid').length > 0; var hasInvalidInput = $('.quantity-approved-input.is-invalid').length > 0;
if (hasInvalidInput) { if (hasInvalidInput) {
Swal.fire({ Swal.fire({
type: 'error', icon: 'error',
title: 'Validasi Gagal', title: 'Validasi Gagal',
text: 'Perbaiki quantity yang tidak valid sebelum melanjutkan' text: 'Perbaiki quantity yang tidak valid sebelum melanjutkan'
}); });
@@ -2666,7 +2562,7 @@ use Illuminate\Support\Facades\Auth;
if (!hasApprovedQuantity) { if (!hasApprovedQuantity) {
Swal.fire({ Swal.fire({
type: 'warning', icon: 'warning',
title: 'Peringatan', title: 'Peringatan',
text: 'Minimal satu produk harus memiliki quantity yang disetujui' text: 'Minimal satu produk harus memiliki quantity yang disetujui'
}); });