diff --git a/.gitea/workflows/deploy-demo.yaml b/.gitea/workflows/deploy-demo.yaml index 872927a..0ad7c11 100644 --- a/.gitea/workflows/deploy-demo.yaml +++ b/.gitea/workflows/deploy-demo.yaml @@ -34,7 +34,6 @@ jobs: docker run --rm -v /workspace/mama/star-cloud:/target busybox mkdir -p /target/docker/emqx cat docker/emqx/acl.conf | docker run -i --rm -v /workspace/mama/star-cloud:/target busybox sh -c "cat > /target/docker/emqx/acl.conf" - - name: Step 1 - Build & Deploy Containers run: | WWWGROUP=1000 WWWUSER=1000 docker compose -f compose.yaml up -d --build --wait diff --git a/Star_Cloud_April_Report.pdf b/Star_Cloud_April_Report.pdf deleted file mode 100644 index dba89f2..0000000 Binary files a/Star_Cloud_April_Report.pdf and /dev/null differ diff --git a/Star_Cloud_April_Report.pptx b/Star_Cloud_April_Report.pptx deleted file mode 100644 index 786e75a..0000000 Binary files a/Star_Cloud_April_Report.pptx and /dev/null differ diff --git a/app/Models/Machine/Machine.php b/app/Models/Machine/Machine.php index fcdf0c7..526b1ba 100644 --- a/app/Models/Machine/Machine.php +++ b/app/Models/Machine/Machine.php @@ -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); + } }); } diff --git a/app/Models/Product/Product.php b/app/Models/Product/Product.php index e9fa4c5..e7163d4 100644 --- a/app/Models/Product/Product.php +++ b/app/Models/Product/Product.php @@ -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. diff --git a/app/Services/Machine/MachineService.php b/app/Services/Machine/MachineService.php index ba43106..dbb084d 100644 --- a/app/Services/Machine/MachineService.php +++ b/app/Services/Machine/MachineService.php @@ -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. *