find($workId); if (!$work) { return [ 'available' => true, 'message' => 'Pekerjaan tidak ditemukan, tapi transaksi diizinkan', 'details' => [] ]; } $stockDetails = []; foreach ($work->products as $product) { $requiredQuantity = $product->pivot->quantity_required * $workQuantity; $availableStock = $product->getStockByDealer($dealerId); $stockDetails[] = [ 'product_id' => $product->id, 'product_name' => $product->name, 'required_quantity' => $requiredQuantity, 'available_stock' => $availableStock, 'is_available' => true // Always true - allow negative stock ]; } return [ 'available' => true, // Always return true - allow negative stock 'message' => 'Stock tersedia (negative stock allowed)', 'details' => $stockDetails ]; } /** * Reduce stock when work transaction is completed * * @param Transaction $transaction * @return bool * @throws Exception */ public function reduceStockForTransaction(Transaction $transaction) { try { return DB::transaction(function () use ($transaction) { $work = $transaction->work; if (!$work) { // If work not found, just return true to allow transaction to proceed return true; } $work->load('products'); if ($work->products->isEmpty()) { // No products required for this work, return true return true; } 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) { 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 ]); } // 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}) - 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; } } /** * Restore stock when work transaction is cancelled/reversed * * @param Transaction $transaction * @return bool * @throws Exception */ public function restoreStockForTransaction(Transaction $transaction) { return DB::transaction(function () use ($transaction) { $work = $transaction->work; if (!$work) { throw new Exception('Work not found for transaction'); } $work->load('products'); if ($work->products->isEmpty()) { return true; } foreach ($work->products as $product) { $restoreQuantity = $product->pivot->quantity_required * $transaction->qty; $stock = Stock::where('product_id', $product->id) ->where('dealer_id', $transaction->dealer_id) ->first(); if (!$stock) { // Create new stock record if doesn't exist $stock = Stock::create([ 'product_id' => $product->id, 'dealer_id' => $transaction->dealer_id, 'quantity' => 0 ]); } // Restore stock $newQuantity = $stock->quantity + $restoreQuantity; $stock->updateStock( $newQuantity, $transaction, "Stock restored from cancelled work: {$work->name} (Transaction #{$transaction->id})" ); } return true; }); } /** * Get stock usage prediction for a work * * @param int $workId * @param int $quantity * @return array */ public function getStockUsagePrediction($workId, $quantity = 1) { $work = Work::with('products')->find($workId); if (!$work) { return []; } $predictions = []; foreach ($work->products as $product) { $totalRequired = $product->pivot->quantity_required * $quantity; $predictions[] = [ 'product_id' => $product->id, 'product_name' => $product->name, 'product_code' => $product->code, 'unit' => $product->unit, 'quantity_per_work' => $product->pivot->quantity_required, 'total_quantity_needed' => $totalRequired, 'notes' => $product->pivot->notes ]; } return $predictions; } }