partial update close modal on all page and disable create transaction with no stock
This commit is contained in:
@@ -25,16 +25,16 @@ class CategoryController extends Controller
|
||||
$data = Category::all();
|
||||
return DataTables::of($data)->addIndexColumn()
|
||||
->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>';
|
||||
}
|
||||
|
||||
if(Auth::user()->can('update', $menu)) {
|
||||
$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>';
|
||||
}
|
||||
|
||||
$btn .= '</div>';
|
||||
return $btn;
|
||||
})
|
||||
->rawColumns(['action'])
|
||||
|
||||
@@ -27,27 +27,28 @@ class DealerController extends Controller
|
||||
$data = Dealer::leftJoin('users as u', 'u.id', '=', 'pic')->select('u.name as pic_name', 'dealers.*');
|
||||
return Datatables::of($data)->addIndexColumn()
|
||||
->addColumn('action', function($row) use ($menu) {
|
||||
$btn = '';
|
||||
$btn = '<div class="d-flex">';
|
||||
if($row->pic != null) {
|
||||
|
||||
if(Auth::user()->can('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>';
|
||||
if(Gate::allows('delete', $menu)) {
|
||||
$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>';
|
||||
}
|
||||
}else{
|
||||
if(Auth::user()->can('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>';
|
||||
if(Gate::allows('delete', $menu)) {
|
||||
$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)) {
|
||||
$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>
|
||||
if(Gate::allows('update', $menu)) {
|
||||
$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>';
|
||||
}
|
||||
}
|
||||
|
||||
$btn .= '</div>';
|
||||
return $btn;
|
||||
})
|
||||
->rawColumns(['action'])
|
||||
|
||||
@@ -670,10 +670,66 @@ class TransactionController extends Controller
|
||||
|
||||
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([
|
||||
'work_id.*' => ['required', 'integer'],
|
||||
'quantity.*' => ['required', 'integer'],
|
||||
'work_id.*' => ['required', 'integer', 'exists:works,id'],
|
||||
'quantity.*' => ['required', 'integer', 'min:1'],
|
||||
'spk_no' => ['required', 'string', 'min:1', function($attribute, $value, $fail) use($request) {
|
||||
// Handle date format conversion safely for validation
|
||||
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) {
|
||||
// Handle date format conversion safely for validation
|
||||
if (strpos($value, '/') !== false) {
|
||||
@@ -774,10 +830,13 @@ class TransactionController extends Controller
|
||||
'police_number.min' => 'No. Polisi tidak boleh kosong',
|
||||
'date.required' => 'Tanggal Pekerjaan harus diisi',
|
||||
'date.min' => 'Tanggal Pekerjaan tidak boleh kosong',
|
||||
'warranty.required' => 'Warranty harus dipilih',
|
||||
'user_sa_id.required' => 'Service Advisor harus dipilih',
|
||||
'user_sa_id.exists' => 'Service Advisor yang dipilih tidak valid',
|
||||
'work_id.*.required' => 'Pekerjaan harus dipilih',
|
||||
'quantity.*.required' => 'Quantity harus diisi',
|
||||
'work_id.*.required' => 'Pekerjaan yang dipilih harus valid',
|
||||
'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
|
||||
@@ -796,50 +855,21 @@ class TransactionController extends Controller
|
||||
$request['date'] = $dateValue;
|
||||
}
|
||||
|
||||
// Check stock availability for all works before creating transactions
|
||||
$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();
|
||||
}
|
||||
// Stock checking removed - allow negative stock
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$transactions = [];
|
||||
$data = [];
|
||||
|
||||
// Create transaction records
|
||||
for($i = 0; $i < count($request->work_id); $i++) {
|
||||
// Create transaction records using filtered valid pairs
|
||||
foreach($validPairs as $pair) {
|
||||
$transactionData = [
|
||||
"user_id" => $request->mechanic_id,
|
||||
"dealer_id" => $request->dealer_id,
|
||||
"form" => $request->form,
|
||||
"work_id" => $request->work_id[$i],
|
||||
"qty" => $request->quantity[$i],
|
||||
"work_id" => $pair['work_id'],
|
||||
"qty" => $pair['quantity'],
|
||||
"spk" => $request->spk_no,
|
||||
"police_number" => $request->police_number,
|
||||
"warranty" => $request->warranty,
|
||||
|
||||
@@ -24,16 +24,16 @@ class UserController extends Controller
|
||||
return DataTables::of($data)
|
||||
->addIndexColumn()
|
||||
->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>';
|
||||
}
|
||||
|
||||
if(Auth::user()->can('update', $menu)) {
|
||||
$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>';
|
||||
}
|
||||
|
||||
$btn .= '</div>';
|
||||
return $btn;
|
||||
})
|
||||
->rawColumns(['action'])
|
||||
|
||||
@@ -100,8 +100,13 @@ class StockAuditController extends Controller
|
||||
$mutationNumber = $row->source ? $row->source->mutation_number : '-';
|
||||
return "Mutasi: {$mutationNumber}";
|
||||
} elseif ($row->source_type === 'App\\Models\\Opname') {
|
||||
return "Opname";
|
||||
} else {
|
||||
$opname_id = $row->source ? $row->source->id : '-';
|
||||
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 ?? '-';
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,12 +7,14 @@ use App\Models\Work;
|
||||
use App\Models\Transaction;
|
||||
use App\Models\StockLog;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Exception;
|
||||
|
||||
class StockService
|
||||
{
|
||||
/**
|
||||
* Check if dealer has sufficient stock for work
|
||||
* Modified to always return available = true (allow negative stock)
|
||||
*
|
||||
* @param int $workId
|
||||
* @param int $dealerId
|
||||
@@ -25,36 +27,30 @@ class StockService
|
||||
|
||||
if (!$work) {
|
||||
return [
|
||||
'available' => false,
|
||||
'message' => 'Pekerjaan tidak ditemukan',
|
||||
'available' => true,
|
||||
'message' => 'Pekerjaan tidak ditemukan, tapi transaksi diizinkan',
|
||||
'details' => []
|
||||
];
|
||||
}
|
||||
|
||||
$stockDetails = [];
|
||||
$allAvailable = true;
|
||||
|
||||
foreach ($work->products as $product) {
|
||||
$requiredQuantity = $product->pivot->quantity_required * $workQuantity;
|
||||
$availableStock = $product->getStockByDealer($dealerId);
|
||||
|
||||
$isAvailable = $availableStock >= $requiredQuantity;
|
||||
if (!$isAvailable) {
|
||||
$allAvailable = false;
|
||||
}
|
||||
|
||||
$stockDetails[] = [
|
||||
'product_id' => $product->id,
|
||||
'product_name' => $product->name,
|
||||
'required_quantity' => $requiredQuantity,
|
||||
'available_stock' => $availableStock,
|
||||
'is_available' => $isAvailable
|
||||
'is_available' => true // Always true - allow negative stock
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'available' => $allAvailable,
|
||||
'message' => $allAvailable ? 'Stock tersedia' : 'Stock tidak mencukupi',
|
||||
'available' => true, // Always return true - allow negative stock
|
||||
'message' => 'Stock tersedia (negative stock allowed)',
|
||||
'details' => $stockDetails
|
||||
];
|
||||
}
|
||||
@@ -68,11 +64,13 @@ class StockService
|
||||
*/
|
||||
public function reduceStockForTransaction(Transaction $transaction)
|
||||
{
|
||||
try {
|
||||
return DB::transaction(function () use ($transaction) {
|
||||
$work = $transaction->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');
|
||||
@@ -84,30 +82,123 @@ class StockService
|
||||
|
||||
foreach ($work->products as $product) {
|
||||
$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)
|
||||
->where('dealer_id', $transaction->dealer_id)
|
||||
->first();
|
||||
|
||||
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) {
|
||||
throw new Exception("Insufficient stock for product {$product->name}. Required: {$requiredQuantity}, Available: {$stock->quantity}");
|
||||
}
|
||||
|
||||
// Reduce stock
|
||||
// Allow negative stock - reduce regardless of current quantity
|
||||
$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(
|
||||
$newQuantity,
|
||||
$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;
|
||||
});
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user