MIF_E31222707/app/Models/ProductBatch.php

189 lines
5.2 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
use Modules\Product\Entities\Product;
use App\Models\Branch;
class ProductBatch extends Model
{
protected $fillable = [
'product_id',
'branch_id',
'batch_code',
'qty',
'unit_price',
'price',
'exp_date',
'purchase_id',
'created_by',
'updated_by'
];
protected $casts = [
'exp_date' => 'date',
'unit_price' => 'decimal:2',
'price' => 'decimal:2',
'qty' => 'integer'
];
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
public function branch(): BelongsTo
{
return $this->belongsTo(Branch::class);
}
/**
* Generate a unique batch code
*/
public static function generateBatchCode(): string
{
return 'BATCH-' . strtoupper(Str::random(8));
}
/**
* Get available stock for a product in a specific branch using FEFO/FIFO
*/
public static function getAvailableStock(int $productId, int $branchId)
{
return self::where('product_id', $productId)
->where('branch_id', $branchId)
->where('qty', '>', 0)
->orderBy('exp_date', 'asc')
->orderBy('created_at', 'asc')
->get();
}
/**
* Deduct stock from batches using FEFO/FIFO
*/
public static function deductStock(int $productId, int $branchId, int $qty)
{
$remainingQuantity = $qty;
$batches = self::getAvailableStock($productId, $branchId);
$usedBatches = [];
foreach ($batches as $batch) {
if ($remainingQuantity <= 0) break;
$deductAmount = min($remainingQuantity, $batch->qty);
$batch->qty -= $deductAmount;
$batch->save();
$usedBatches[] = [
'batch_id' => $batch->id,
'qty' => $deductAmount,
'unit_price' => $batch->unit_price,
'price' => $batch->price,
'exp_date' => $batch->exp_date,
];
$remainingQuantity -= $deductAmount;
}
if ($remainingQuantity > 0) {
throw new \Exception('Insufficient stock available');
}
return $usedBatches;
}
/**
* Add new stock to product batches
*/
public static function addStock(array $data)
{
// Rename expired_date to exp_date if it exists
if (isset($data['expired_date'])) {
$data['exp_date'] = $data['expired_date'];
unset($data['expired_date']);
}
// Rename purchase_price to unit_price if it exists
if (isset($data['purchase_price'])) {
$data['unit_price'] = $data['purchase_price'];
unset($data['purchase_price']);
}
// Generate batch code if not provided
if (empty($data['batch_code'])) {
$data['batch_code'] = self::generateBatchCode();
}
return self::create($data);
}
/**
* Get FIFO batch unit price (average from earliest batches)
*/
public static function getFifoBatchPrice(int $productId, int $branchId, int $qty = 1): float
{
$batches = self::where('product_id', $productId)
->where('branch_id', $branchId)
->where('qty', '>', 0)
->orderBy('exp_date', 'asc')
->orderBy('created_at', 'asc')
->get();
$remaining = $qty;
$totalPrice = 0;
foreach ($batches as $batch) {
if ($remaining <= 0) break;
$take = min($remaining, $batch->qty);
$totalPrice += $take * $batch->price;
$remaining -= $take;
}
if ($remaining > 0) {
throw new \Exception('Stok tidak cukup untuk menghitung harga FIFO');
}
return round($totalPrice / $qty, 2);
}
public static function getFifoBatch($product_id, $branch_id, $qty = 1)
{
return self::where('product_id', $product_id)
->where('branch_id', $branch_id)
->where('qty', '>=', $qty)
->orderBy('created_at') // FIFO
->first();
}
public static function transferToBranch(int $sourceBatchId, int $destinationBranchId, int $qty, int $userId): ProductBatch
{
$sourceBatch = self::lockForUpdate()->findOrFail($sourceBatchId);
if ($sourceBatch->qty < $qty) {
throw new \Exception("Stok tidak cukup pada batch {$sourceBatch->batch_code}");
}
// Kurangi stok dari batch asal
$sourceBatch->qty -= $qty;
$sourceBatch->save();
// Buat batch baru di cabang tujuan
return self::create([
'product_id' => $sourceBatch->product_id,
'batch_code' => $sourceBatch->batch_code,
'unit_price' => $sourceBatch->unit_price,
'price' => $sourceBatch->price,
'expired_date' => $sourceBatch->expired_date,
'purchase_id' => $sourceBatch->purchase_id,
'branch_id' => $destinationBranchId,
'qty' => $qty,
'created_by' => $userId,
'updated_by' => $userId,
]);
}
}