feat(生產/庫存): 實作生產管理模組與批號追溯功能
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 53s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped

This commit is contained in:
2026-01-21 17:19:36 +08:00
parent fc20c6d813
commit 1ae21febb5
17 changed files with 1753 additions and 33 deletions

View File

@@ -0,0 +1,120 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class ProductionOrder extends Model
{
use HasFactory, LogsActivity;
protected $fillable = [
'code',
'product_id',
'output_batch_number',
'output_box_count',
'output_quantity',
'warehouse_id',
'production_date',
'expiry_date',
'user_id',
'status',
'remark',
];
protected $casts = [
'production_date' => 'date:Y-m-d',
'expiry_date' => 'date:Y-m-d',
'output_quantity' => 'decimal:2',
];
/**
* 成品商品
*/
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
/**
* 入庫倉庫
*/
public function warehouse(): BelongsTo
{
return $this->belongsTo(Warehouse::class);
}
/**
* 操作人員
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* 生產工單明細 (BOM)
*/
public function items(): HasMany
{
return $this->hasMany(ProductionOrderItem::class);
}
/**
* 活動日誌設定
*/
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logAll()
->logOnlyDirty()
->dontSubmitEmptyLogs();
}
/**
* 活動日誌快照
*/
public function tapActivity(\Spatie\Activitylog\Contracts\Activity $activity, string $eventName)
{
$properties = $activity->properties;
$attributes = $properties['attributes'] ?? [];
$snapshot = $properties['snapshot'] ?? [];
// 快照關鍵名稱
$snapshot['production_code'] = $this->code;
$snapshot['product_name'] = $this->product ? $this->product->name : null;
$snapshot['warehouse_name'] = $this->warehouse ? $this->warehouse->name : null;
$snapshot['user_name'] = $this->user ? $this->user->name : null;
$properties['attributes'] = $attributes;
$properties['snapshot'] = $snapshot;
$activity->properties = $properties;
}
/**
* 產生生產單號
*/
public static function generateCode(): string
{
$date = now()->format('Ymd');
$prefix = "PRO-{$date}-";
$lastOrder = static::where('code', 'like', "{$prefix}%")
->orderByDesc('code')
->first();
if ($lastOrder) {
$lastNumber = (int) substr($lastOrder->code, -3);
$nextNumber = str_pad($lastNumber + 1, 3, '0', STR_PAD_LEFT);
} else {
$nextNumber = '001';
}
return $prefix . $nextNumber;
}
}