[PROMOTE] 晉升 dev 變更至 demo 環境
1. [FEAT] 實作機台貨道類型與商品容量上限的雙向連動同步機制,包含 Product、Machine 模型更新監聽與 MachineService 重構。 2. 刪除根目錄的 4 月報告暫存檔案 (Star_Cloud_April_Report.pdf/pptx)。
This commit is contained in:
commit
97fb828a9b
Binary file not shown.
Binary file not shown.
@ -43,6 +43,17 @@ class Machine extends Model
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($machine->wasChanged([
|
||||
'is_spring_slot_1_10',
|
||||
'is_spring_slot_11_20',
|
||||
'is_spring_slot_21_30',
|
||||
'is_spring_slot_31_40',
|
||||
'is_spring_slot_41_50',
|
||||
'is_spring_slot_51_60',
|
||||
])) {
|
||||
app(\App\Services\Machine\MachineService::class)->syncMachineSlotMaxStock($machine);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -5,11 +5,21 @@ namespace App\Models\Product;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Services\Machine\MachineService;
|
||||
use App\Traits\TenantScoped;
|
||||
|
||||
class Product extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes, TenantScoped;
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::updated(function (Product $product) {
|
||||
if ($product->wasChanged(['track_limit', 'spring_limit'])) {
|
||||
app(MachineService::class)->syncProductSlotMaxStock($product);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include active products.
|
||||
|
||||
@ -7,6 +7,7 @@ use App\Models\Machine\MachineLog;
|
||||
use App\Models\Machine\MachineSlot;
|
||||
use App\Models\Machine\MachineStockMovement;
|
||||
use App\Models\Machine\RemoteCommand;
|
||||
use App\Models\Product\Product;
|
||||
use App\Models\Transaction\Order;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@ -14,6 +15,15 @@ use Carbon\Carbon;
|
||||
|
||||
class MachineService
|
||||
{
|
||||
private const SPRING_SLOT_FLAGS = [
|
||||
[1, 10, 'is_spring_slot_1_10'],
|
||||
[11, 20, 'is_spring_slot_11_20'],
|
||||
[21, 30, 'is_spring_slot_21_30'],
|
||||
[31, 40, 'is_spring_slot_31_40'],
|
||||
[41, 50, 'is_spring_slot_41_50'],
|
||||
[51, 60, 'is_spring_slot_51_60'],
|
||||
];
|
||||
|
||||
/**
|
||||
* B013: 硬體狀態代碼對照表 (Hardware Status Code Mapping)
|
||||
*
|
||||
@ -278,15 +288,12 @@ class MachineService
|
||||
})?->id;
|
||||
}
|
||||
|
||||
// 根據貨道類型自動決定上限 (Auto-calculate max_stock based on slot type)
|
||||
// 若未提供 type,預設為 '1' (履帶/Track)
|
||||
$slotType = $slotData['type'] ?? $existingSlot->type ?? '1';
|
||||
// 根據機台貨道機制設定決定上限與貨道類型 (1: 履帶, 2: 彈簧)
|
||||
$slotType = $this->resolveSlotType($machine, $slotNo);
|
||||
if ($actualProductId) {
|
||||
$product = $products->find($actualProductId);
|
||||
if ($product) {
|
||||
// 1: 履帶, 2: 彈簧
|
||||
$calculatedMaxStock = ($slotType == '1') ? $product->track_limit : $product->spring_limit;
|
||||
$slotData['capacity'] = $calculatedMaxStock ?? $slotData['capacity'] ?? null;
|
||||
$slotData['capacity'] = $this->calculateSlotMaxStock($product, $machine, $slotNo);
|
||||
}
|
||||
}
|
||||
|
||||
@ -307,22 +314,26 @@ class MachineService
|
||||
// 檢查是否有實質變動 (Check for actual changes)
|
||||
$isStockChanged = ($oldStock !== $newStock);
|
||||
$isProductChanged = ($oldProductId != $actualProductId); // 這裡用鬆散比對以處理 null/0 的情況
|
||||
$isSlotConfigChanged = (string) $existingSlot->type !== (string) $updateData['type']
|
||||
|| (int) $existingSlot->max_stock !== (int) $updateData['max_stock'];
|
||||
|
||||
if ($isStockChanged || $isProductChanged) {
|
||||
if ($isStockChanged || $isProductChanged || $isSlotConfigChanged) {
|
||||
$existingSlot->update($updateData);
|
||||
$existingSlot->refresh();
|
||||
|
||||
$delta = $newStock - $oldStock;
|
||||
$note = $isProductChanged ? "movement.note.product_changed_and_adjusted" : "movement.note.replenishment_correction";
|
||||
|
||||
$this->recordStockMovement(
|
||||
$existingSlot,
|
||||
$delta,
|
||||
MachineStockMovement::TYPE_ADJUSTMENT,
|
||||
null,
|
||||
$note,
|
||||
['slot_no' => $existingSlot->slot_no, 'old' => $oldStock, 'new' => $newStock, 'old_product_id' => $oldProductId, 'new_product_id' => $actualProductId]
|
||||
);
|
||||
if ($isStockChanged || $isProductChanged) {
|
||||
$delta = $newStock - $oldStock;
|
||||
$note = $isProductChanged ? "movement.note.product_changed_and_adjusted" : "movement.note.replenishment_correction";
|
||||
|
||||
$this->recordStockMovement(
|
||||
$existingSlot,
|
||||
$delta,
|
||||
MachineStockMovement::TYPE_ADJUSTMENT,
|
||||
null,
|
||||
$note,
|
||||
['slot_no' => $existingSlot->slot_no, 'old' => $oldStock, 'new' => $newStock, 'old_product_id' => $oldProductId, 'new_product_id' => $actualProductId]
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$newSlot = $machine->slots()->create(array_merge($updateData, ['slot_no' => $slotNo]));
|
||||
@ -378,6 +389,68 @@ class MachineService
|
||||
});
|
||||
}
|
||||
|
||||
public function syncProductSlotMaxStock(Product $product): void
|
||||
{
|
||||
MachineSlot::query()
|
||||
->where('product_id', $product->id)
|
||||
->with(['machine' => fn($query) => $query->withoutGlobalScopes()])
|
||||
->chunkById(500, function ($slots) use ($product) {
|
||||
foreach ($slots as $slot) {
|
||||
if (!$slot->machine) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$slot->update([
|
||||
'type' => $this->resolveSlotType($slot->machine, $slot->slot_no),
|
||||
'max_stock' => $this->calculateSlotMaxStock($product, $slot->machine, $slot->slot_no),
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function syncMachineSlotMaxStock(Machine $machine): void
|
||||
{
|
||||
$machine->slots()
|
||||
->with(['product' => fn($query) => $query->withoutGlobalScopes()])
|
||||
->chunkById(500, function ($slots) use ($machine) {
|
||||
foreach ($slots as $slot) {
|
||||
if (!$slot->product) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$slot->update([
|
||||
'type' => $this->resolveSlotType($machine, $slot->slot_no),
|
||||
'max_stock' => $this->calculateSlotMaxStock($slot->product, $machine, $slot->slot_no),
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function calculateSlotMaxStock(Product $product, Machine $machine, string|int|null $slotNo): int
|
||||
{
|
||||
return $this->isSpringSlot($machine, $slotNo)
|
||||
? (int) $product->spring_limit
|
||||
: (int) $product->track_limit;
|
||||
}
|
||||
|
||||
public function resolveSlotType(Machine $machine, string|int|null $slotNo): string
|
||||
{
|
||||
return $this->isSpringSlot($machine, $slotNo) ? '2' : '1';
|
||||
}
|
||||
|
||||
public function isSpringSlot(Machine $machine, string|int|null $slotNo): bool
|
||||
{
|
||||
$slotNumber = (int) $slotNo;
|
||||
|
||||
foreach (self::SPRING_SLOT_FLAGS as [$start, $end, $field]) {
|
||||
if ($slotNumber >= $start && $slotNumber <= $end) {
|
||||
return (bool) $machine->{$field};
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update machine slot stock, expiry, and batch.
|
||||
*
|
||||
|
||||
Loading…
Reference in New Issue
Block a user