Files
CKB/app/Services/StockService.php

288 lines
11 KiB
PHP

<?php
namespace App\Services;
use App\Models\Stock;
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
* @param int $workQuantity
* @return array
*/
public function checkStockAvailability($workId, $dealerId, $workQuantity = 1)
{
$work = Work::with('products')->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;
}
}