[FEAT] 售完即清空貨道效期與批號
1. recordDispense 扣庫存後,若 stock 歸 0 則一併清空 expiry_date 與 batch_no,避免舊批效期殘留:日後僅補 stock 未改效期時,update_inventory 不會把過期日下發、誤將新批貨鎖成「暫不販售」,與機台端「賣完清效期」對稱 2. 新增 ExpiryClearOnSelloutMysqlTest 驗證售完清效期行為 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fcd0588686
commit
e0532a539f
@ -305,6 +305,17 @@ class TransactionService
|
|||||||
if ($slot) {
|
if ($slot) {
|
||||||
$slot->decrement('stock');
|
$slot->decrement('stock');
|
||||||
|
|
||||||
|
// 售完(庫存歸 0)即清空效期與批號,避免舊批貨效期殘留。
|
||||||
|
// 場景:日後管理員只補 stock 未改 expiry_date 時,update_inventory 才不會
|
||||||
|
// 把過期日一起下發、把新批貨誤鎖成「暫不販售」。與機台端「賣完清效期」對稱。
|
||||||
|
// 純加法:僅在仍有殘留值時才寫,不影響既有扣庫存行為。
|
||||||
|
if ($slot->stock <= 0 && ($slot->expiry_date !== null || $slot->batch_no !== null)) {
|
||||||
|
$slot->update([
|
||||||
|
'expiry_date' => null,
|
||||||
|
'batch_no' => null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$type = \App\Models\Machine\MachineStockMovement::TYPE_SALE;
|
$type = \App\Models\Machine\MachineStockMovement::TYPE_SALE;
|
||||||
|
|
||||||
$this->machineService->recordStockMovement(
|
$this->machineService->recordStockMovement(
|
||||||
|
|||||||
101
tests/Feature/Transaction/ExpiryClearOnSelloutMysqlTest.php
Normal file
101
tests/Feature/Transaction/ExpiryClearOnSelloutMysqlTest.php
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Transaction;
|
||||||
|
|
||||||
|
use App\Models\Machine\Machine;
|
||||||
|
use App\Models\System\Company;
|
||||||
|
use App\Services\Transaction\TransactionService;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 售完(庫存歸 0)自動清空效期/批號 —— 需 MySQL(recordDispense 內部寫
|
||||||
|
* machine_stock_movements,其 type enum 為 MySQL-only DDL,sqlite :memory: 無法驗)。執行:
|
||||||
|
*
|
||||||
|
* ./vendor/bin/sail test -c phpunit.mysql.xml --filter ExpiryClearOnSelloutMysqlTest
|
||||||
|
*
|
||||||
|
* 在預設 sqlite 套件中自動 skip,不影響主套件綠燈。
|
||||||
|
*
|
||||||
|
* 動機:機台端「賣完清效期」已做,但 B009 補貨上報不回傳 expiry,後台若殘留舊效期,
|
||||||
|
* 日後管理員只補 stock 未改效期 → update_inventory 把過期日下發 → 新批貨被誤鎖。
|
||||||
|
* 故後台扣庫存歸 0 時亦須清效期,與機台端對稱。
|
||||||
|
*/
|
||||||
|
class ExpiryClearOnSelloutMysqlTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
private Company $company;
|
||||||
|
private TransactionService $service;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
if (DB::connection()->getDriverName() !== 'mysql') {
|
||||||
|
$this->markTestSkipped('需 MySQL:machine_stock_movements.type enum 為 MySQL-only DDL。');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->company = Company::create(['name' => 'Test Company']);
|
||||||
|
$this->service = app(TransactionService::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function dispense(string $serial, string $slotNo): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'serial_no' => $serial,
|
||||||
|
'slot_no' => $slotNo,
|
||||||
|
'product_id' => 101,
|
||||||
|
'amount' => 50,
|
||||||
|
'dispense_status' => 1,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 庫存賣到 0 → 效期與批號被清空。 */
|
||||||
|
public function test_clears_expiry_and_batch_when_stock_hits_zero(): void
|
||||||
|
{
|
||||||
|
$serial = '1234567890';
|
||||||
|
$machine = Machine::factory()->create([
|
||||||
|
'company_id' => $this->company->id,
|
||||||
|
'serial_no' => $serial,
|
||||||
|
]);
|
||||||
|
$slot = $machine->slots()->create([
|
||||||
|
'slot_no' => '1',
|
||||||
|
'stock' => 1,
|
||||||
|
'max_stock' => 10,
|
||||||
|
'expiry_date' => '2030-12-31',
|
||||||
|
'batch_no' => 'B-001',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->service->recordDispense($this->dispense($serial, '1'));
|
||||||
|
|
||||||
|
$fresh = $slot->fresh();
|
||||||
|
$this->assertEquals(0, $fresh->stock, '庫存應扣為 0');
|
||||||
|
$this->assertNull($fresh->expiry_date, '售完應清空效期');
|
||||||
|
$this->assertNull($fresh->batch_no, '售完應清空批號');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 庫存尚未歸 0 → 效期與批號保留(同批貨還在販售)。 */
|
||||||
|
public function test_keeps_expiry_when_stock_remains(): void
|
||||||
|
{
|
||||||
|
$serial = '1234567891';
|
||||||
|
$machine = Machine::factory()->create([
|
||||||
|
'company_id' => $this->company->id,
|
||||||
|
'serial_no' => $serial,
|
||||||
|
]);
|
||||||
|
$slot = $machine->slots()->create([
|
||||||
|
'slot_no' => '2',
|
||||||
|
'stock' => 5,
|
||||||
|
'max_stock' => 10,
|
||||||
|
'expiry_date' => '2030-12-31',
|
||||||
|
'batch_no' => 'B-001',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->service->recordDispense($this->dispense($serial, '2'));
|
||||||
|
|
||||||
|
$fresh = $slot->fresh();
|
||||||
|
$this->assertEquals(4, $fresh->stock, '庫存應扣為 4');
|
||||||
|
$this->assertEquals('2030-12-31', $fresh->expiry_date->format('Y-m-d'), '未售完應保留效期');
|
||||||
|
$this->assertEquals('B-001', $fresh->batch_no, '未售完應保留批號');
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user