create stock and stock logs

This commit is contained in:
2025-06-10 18:38:06 +07:00
parent 1a2ddb59d4
commit 51079aa567
36 changed files with 1621 additions and 311 deletions

View File

@@ -10,17 +10,108 @@ class Opname extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = ['dealer_id','opname_date','user_id','note'];
protected $fillable = [
'dealer_id',
'opname_date',
'user_id',
'note',
'status',
'approved_by',
'approved_at',
'rejection_note'
];
public function dealer(){
protected $casts = [
'approved_at' => 'datetime'
];
protected static function booted()
{
static::updated(function ($opname) {
// Jika status berubah menjadi approved
if ($opname->isDirty('status') && $opname->status === 'approved') {
// Update stock untuk setiap detail opname
foreach ($opname->details as $detail) {
$stock = Stock::firstOrCreate(
[
'product_id' => $detail->product_id,
'dealer_id' => $opname->dealer_id
],
['quantity' => 0]
);
// Update stock dengan physical_stock dari opname
$stock->updateStock(
$detail->physical_stock,
$opname,
"Stock adjustment from approved opname #{$opname->id}"
);
}
}
});
}
public function dealer()
{
return $this->belongsTo(Dealer::class);
}
public function details(){
public function details()
{
return $this->hasMany(OpnameDetail::class);
}
public function user(){
public function user()
{
return $this->belongsTo(User::class);
}
public function approver()
{
return $this->belongsTo(User::class, 'approved_by');
}
// Method untuk approve opname
public function approve(User $approver)
{
if ($this->status !== 'pending') {
throw new \Exception('Only pending opnames can be approved');
}
$this->status = 'approved';
$this->approved_by = $approver->id;
$this->approved_at = now();
$this->save();
return $this;
}
// Method untuk reject opname
public function reject(User $rejector, string $note)
{
if ($this->status !== 'pending') {
throw new \Exception('Only pending opnames can be rejected');
}
$this->status = 'rejected';
$this->approved_by = $rejector->id;
$this->approved_at = now();
$this->rejection_note = $note;
$this->save();
return $this;
}
// Method untuk submit opname untuk approval
public function submit()
{
if ($this->status !== 'draft') {
throw new \Exception('Only draft opnames can be submitted');
}
$this->status = 'pending';
$this->save();
return $this;
}
}

View File

@@ -19,4 +19,14 @@ class Product extends Model
public function opnameDetails(){
return $this->hasMany(OpnameDetail::class);
}
public function stocks(){
return $this->hasMany(Stock::class);
}
// Helper method untuk mendapatkan total stock saat ini
public function getCurrentTotalStockAttribute()
{
return $this->stocks()->sum('quantity');
}
}

56
app/Models/Stock.php Normal file
View File

@@ -0,0 +1,56 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Stock extends Model
{
use HasFactory;
protected $fillable = [
'product_id',
'dealer_id',
'quantity'
];
public function product()
{
return $this->belongsTo(Product::class);
}
public function dealer()
{
return $this->belongsTo(Dealer::class);
}
public function stockLogs()
{
return $this->hasMany(StockLog::class);
}
// Method untuk mengupdate stock
public function updateStock($newQuantity, $source, $description = null)
{
$previousQuantity = $this->quantity;
$quantityChange = $newQuantity - $previousQuantity;
$this->quantity = $newQuantity;
$this->save();
// Buat log perubahan
StockLog::create([
'stock_id' => $this->id,
'source_type' => get_class($source),
'source_id' => $source->id,
'previous_quantity' => $previousQuantity,
'new_quantity' => $newQuantity,
'quantity_change' => $quantityChange,
'description' => $description,
'user_id' => auth()->id()
]);
return $this;
}
}

70
app/Models/StockLog.php Normal file
View File

@@ -0,0 +1,70 @@
<?php
namespace App\Models;
use App\Enums\StockChangeType;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class StockLog extends Model
{
use HasFactory;
protected $fillable = [
'stock_id',
'source_type',
'source_id',
'previous_quantity',
'new_quantity',
'quantity_change',
'change_type',
'description',
'user_id'
];
protected $casts = [
'change_type' => StockChangeType::class,
'previous_quantity' => 'decimal:2',
'new_quantity' => 'decimal:2',
'quantity_change' => 'decimal:2'
];
protected static function booted()
{
static::creating(function ($stockLog) {
// Hitung quantity_change
$stockLog->quantity_change = $stockLog->new_quantity - $stockLog->previous_quantity;
// Tentukan change_type berdasarkan quantity_change
if ($stockLog->quantity_change == 0) {
// Jika quantity sama persis (tanpa toleransi)
$stockLog->change_type = StockChangeType::NO_CHANGE;
} else if ($stockLog->quantity_change > 0) {
$stockLog->change_type = StockChangeType::INCREASE;
} else {
$stockLog->change_type = StockChangeType::DECREASE;
}
});
}
public function stock()
{
return $this->belongsTo(Stock::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
public function source()
{
return $this->morphTo();
}
// Helper method untuk mendapatkan label change_type
public function getChangeTypeLabelAttribute()
{
return $this->change_type->label();
}
}