[PROMOTE] feat/expiry-clear-on-sellout → demo:售完清效期測試驗收
1. recordDispense 售完(stock=0)即清空貨道 expiry_date 與 batch_no,避免舊批效期殘留誤鎖新批貨 2. 新增 ExpiryClearOnSelloutMysqlTest 驗收測試 3. 同步併入 main 既有提交:每機台商品定價 + 補貨單/機台列表 RWD 修正(fcd0588、c5a3f99) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> # Conflicts: # app/Services/Transaction/TransactionService.php
This commit is contained in:
commit
d1c7940db9
@ -308,6 +308,17 @@ class TransactionService
|
||||
if ((int) $slot->stock > 0) {
|
||||
$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;
|
||||
|
||||
$this->machineService->recordStockMovement(
|
||||
|
||||
15
lang/en.json
15
lang/en.json
@ -765,6 +765,7 @@
|
||||
"Failed to get invoice print URL": "Failed to get invoice print URL",
|
||||
"Failed to load permissions": "Failed to load permissions",
|
||||
"Failed to load preview": "Failed to load preview",
|
||||
"Failed to load pricing": "Failed to load pricing",
|
||||
"Failed to load tab content": "Failed to load tab content",
|
||||
"Failed to resolve issues.": "Failed to resolve issues.",
|
||||
"Failed to save permissions.": "Failed to save permissions.",
|
||||
@ -897,6 +898,7 @@
|
||||
"Generate Replenishment Order": "Generate Replenishment Order",
|
||||
"Generate and manage one-time pickup codes for customers": "Generate and manage one-time pickup codes for customers",
|
||||
"Gift Definitions": "Gift Definitions",
|
||||
"Global": "Global",
|
||||
"Global roles accessible by all administrators.": "Global roles accessible by all administrators.",
|
||||
"Go Authorize": "Go Authorize",
|
||||
"Go Back": "Go Back",
|
||||
@ -1020,6 +1022,7 @@
|
||||
"Latitude": "Latitude",
|
||||
"Lease": "Lease",
|
||||
"Leave blank to deploy immediately, or set a future time to schedule a background release.": "Leave blank to deploy immediately, or set a future time to schedule a background release.",
|
||||
"Leave blank to use the global product price. Member price will be capped at the machine selling price.": "Leave blank to use the global product price. Member price will be capped at the machine selling price.",
|
||||
"Leave empty for permanent code": "Leave empty for permanent code",
|
||||
"Leave empty or 0 for permanent code": "Leave empty or 0 for permanent code",
|
||||
"Leave empty to inherit company settings": "Leave empty to inherit company settings",
|
||||
@ -1105,6 +1108,8 @@
|
||||
"Machine Model Settings": "Machine Model Settings",
|
||||
"Machine Name": "Machine Name",
|
||||
"Machine Permissions": "Machine Permissions",
|
||||
"Machine Price": "Machine Price",
|
||||
"Machine Pricing": "Machine Pricing",
|
||||
"Machine Reboot": "Machine Reboot",
|
||||
"Machine Registry": "Machine Registry",
|
||||
"Machine Replenishment": "Machine Replenishment",
|
||||
@ -1123,6 +1128,7 @@
|
||||
"Machine Stock Movements": "Machine Stock Movements",
|
||||
"Machine System Settings": "Machine System Settings",
|
||||
"Machine Utilization": "Machine Utilization",
|
||||
"Machine company changed": "Machine company changed",
|
||||
"Machine created successfully.": "Machine created successfully.",
|
||||
"Machine deleted successfully.": "Machine deleted successfully.",
|
||||
"Machine has reconnected to the server and resumed normal heartbeat.": "Machine has reconnected to the server and resumed normal heartbeat.",
|
||||
@ -1135,6 +1141,8 @@
|
||||
"Machine normal": "Machine normal",
|
||||
"Machine not found": "Machine not found",
|
||||
"Machine permissions updated successfully.": "Machine permissions updated successfully.",
|
||||
"Machine pricing saved and sync command pushed.": "Machine pricing saved and sync command pushed.",
|
||||
"Machine pricing updated": "Machine pricing updated",
|
||||
"Machine settings updated successfully.": "Machine settings updated successfully.",
|
||||
"Machine temperature exceeds limit": "Machine temperature exceeds limit, purchase suspended",
|
||||
"Machine to Warehouse": "Machine to Warehouse",
|
||||
@ -1338,6 +1346,7 @@
|
||||
"No maintenance records found": "No maintenance records found",
|
||||
"No matching logs found": "No matching logs found",
|
||||
"No matching machines": "No matching machines",
|
||||
"No matching products": "No matching products",
|
||||
"No materials available": "No materials available",
|
||||
"No movement records found": "No movement records found",
|
||||
"No movements found": "No movements found",
|
||||
@ -1348,9 +1357,11 @@
|
||||
"No pickup codes found": "No pickup codes found",
|
||||
"No product data matching search criteria": "No product data matching search criteria",
|
||||
"No product record found": "No product record found",
|
||||
"No products available for this company": "No products available for this company",
|
||||
"No products found": "No products found",
|
||||
"No products found in this warehouse": "No products found in this warehouse",
|
||||
"No products found matching your criteria.": "No products found matching your criteria.",
|
||||
"No products on this machine yet": "No products on this machine yet",
|
||||
"No records found": "No records found",
|
||||
"No related detail found": "No related detail found",
|
||||
"No remark": "No remark",
|
||||
@ -1827,6 +1838,7 @@
|
||||
"Sales Today": "Sales Today",
|
||||
"Sales revenue minus product cost net value": "Sales revenue minus product cost net value",
|
||||
"Save": "Save",
|
||||
"Save & Sync": "Save & Sync",
|
||||
"Save Changes": "Save Changes",
|
||||
"Save Config": "Save Config",
|
||||
"Save Material": "Save Material",
|
||||
@ -1834,6 +1846,8 @@
|
||||
"Save Settings": "Save Settings",
|
||||
"Save Version": "Save Version",
|
||||
"Save error:": "Save error:",
|
||||
"Save failed": "Save failed",
|
||||
"Saved": "Saved",
|
||||
"Saved.": "Saved.",
|
||||
"Saving...": "Saving...",
|
||||
"Scale level and access control": "Scale level and access control",
|
||||
@ -2590,6 +2604,7 @@
|
||||
"of items": "items",
|
||||
"optional": "optional",
|
||||
"orders": "orders",
|
||||
"overrides set": "overrides set",
|
||||
"pending": "Pending",
|
||||
"permissions": "Permission Settings",
|
||||
"permissions.accounts": "Account Management",
|
||||
|
||||
15
lang/ja.json
15
lang/ja.json
@ -765,6 +765,7 @@
|
||||
"Failed to get invoice print URL": "領収書印刷URLの取得に失敗しました",
|
||||
"Failed to load permissions": "権限の読み込みに失敗しました",
|
||||
"Failed to load preview": "プレビューの読み込みに失敗しました",
|
||||
"Failed to load pricing": "価格設定の読み込みに失敗しました",
|
||||
"Failed to load tab content": "タブ内容の読み込みに失敗しました",
|
||||
"Failed to resolve issues.": "異常の解消に失敗しました。",
|
||||
"Failed to save permissions.": "権限の保存に失敗しました。",
|
||||
@ -897,6 +898,7 @@
|
||||
"Generate Replenishment Order": "補充伝票生成",
|
||||
"Generate and manage one-time pickup codes for customers": "顧客用のワンタイム受取コードを生成・管理",
|
||||
"Gift Definitions": "ギフト設定",
|
||||
"Global": "グローバル価格",
|
||||
"Global roles accessible by all administrators.": "全管理者がアクセス可能なグローバルロール。",
|
||||
"Go Authorize": "先に授権する",
|
||||
"Go Back": "戻る",
|
||||
@ -1020,6 +1022,7 @@
|
||||
"Latitude": "緯度",
|
||||
"Lease": "リース",
|
||||
"Leave blank to deploy immediately, or set a future time to schedule a background release.": "空欄のままにすると即時配信されます。未来の日時を指定すると背景で自動予約配信されます。",
|
||||
"Leave blank to use the global product price. Member price will be capped at the machine selling price.": "空欄の場合はグローバル商品価格を使用します。会員価格はマシン販売価格を上限に自動調整されます。",
|
||||
"Leave empty for permanent code": "空欄で無期限コードになります",
|
||||
"Leave empty or 0 for permanent code": "空欄または0で無期限コードになります",
|
||||
"Leave empty to inherit company settings": "空白のままにすると会社のグローバル設定を継承します",
|
||||
@ -1105,6 +1108,8 @@
|
||||
"Machine Model Settings": "機器モデル設定",
|
||||
"Machine Name": "機器名",
|
||||
"Machine Permissions": "機器権限",
|
||||
"Machine Price": "マシン価格",
|
||||
"Machine Pricing": "マシン価格設定",
|
||||
"Machine Reboot": "機器アプリ再起動",
|
||||
"Machine Registry": "機器リスト",
|
||||
"Machine Replenishment": "機器補充伝票",
|
||||
@ -1123,6 +1128,7 @@
|
||||
"Machine Stock Movements": "機器在庫変動履歴",
|
||||
"Machine System Settings": "機器システム設定",
|
||||
"Machine Utilization": "機器稼働率",
|
||||
"Machine company changed": "マシンの所属会社が変更されました",
|
||||
"Machine created successfully.": "機器が正常に作成されました。",
|
||||
"Machine deleted successfully.": "機器が正常に削除されました。",
|
||||
"Machine has reconnected to the server and resumed normal heartbeat.": "マシンがサーバーに再接続し、通常のハートビートを再開しました。",
|
||||
@ -1135,6 +1141,8 @@
|
||||
"Machine normal": "デバイス正常",
|
||||
"Machine not found": "機器が見つかりません",
|
||||
"Machine permissions updated successfully.": "機器権限が正常に更新されました。",
|
||||
"Machine pricing saved and sync command pushed.": "マシン価格を保存し、同期コマンドを送信しました。",
|
||||
"Machine pricing updated": "マシン価格を更新しました",
|
||||
"Machine settings updated successfully.": "機器設定が正常に更新されました。",
|
||||
"Machine temperature exceeds limit": "設定温度超過のため購入一時停止",
|
||||
"Machine to Warehouse": "機器から倉庫",
|
||||
@ -1338,6 +1346,7 @@
|
||||
"No maintenance records found": "メンテナンス記録が見つかりません",
|
||||
"No matching logs found": "一致するログが見つかりません",
|
||||
"No matching machines": "一致する機器がありません",
|
||||
"No matching products": "一致する商品がありません",
|
||||
"No materials available": "利用可能な素材がありません",
|
||||
"No movement records found": "変動記録が見つかりません",
|
||||
"No movements found": "変動が見つかりません",
|
||||
@ -1348,9 +1357,11 @@
|
||||
"No pickup codes found": "受取コードが見つかりません",
|
||||
"No product data matching search criteria": "検索条件に一致する商品データはありません",
|
||||
"No product record found": "商品記録が見つかりません",
|
||||
"No products available for this company": "この会社で販売可能な商品がありません",
|
||||
"No products found": "商品が見つかりません",
|
||||
"No products found in this warehouse": "この倉庫に商品は見つかりません",
|
||||
"No products found matching your criteria.": "条件に一致する商品が見つかりません。",
|
||||
"No products on this machine yet": "このマシンにはまだ商品がありません",
|
||||
"No records found": "記録が見つかりません",
|
||||
"No related detail found": "関連する詳細が見つかりません",
|
||||
"No remark": "備考なし",
|
||||
@ -1827,6 +1838,7 @@
|
||||
"Sales Today": "本日の販売数",
|
||||
"Sales revenue minus product cost net value": "販売額から商品コストを差し引いた純利益",
|
||||
"Save": "保存",
|
||||
"Save & Sync": "保存して同期",
|
||||
"Save Changes": "変更を保存",
|
||||
"Save Config": "設定を保存",
|
||||
"Save Material": "素材を保存",
|
||||
@ -1834,6 +1846,8 @@
|
||||
"Save Settings": "設定を保存",
|
||||
"Save Version": "Save Version",
|
||||
"Save error:": "保存エラー:",
|
||||
"Save failed": "保存に失敗しました",
|
||||
"Saved": "保存しました",
|
||||
"Saved.": "保存しました。",
|
||||
"Saving...": "保存中...",
|
||||
"Scale level and access control": "スケールレベルとアクセス制御",
|
||||
@ -2590,6 +2604,7 @@
|
||||
"of items": "件中",
|
||||
"optional": "任意",
|
||||
"orders": "件",
|
||||
"overrides set": "件のマシン価格を設定済み",
|
||||
"pending": "機器受取待機中",
|
||||
"permissions": "権限設定",
|
||||
"permissions.accounts": "アカウント管理",
|
||||
|
||||
@ -765,6 +765,7 @@
|
||||
"Failed to get invoice print URL": "取得發票列印網址失敗",
|
||||
"Failed to load permissions": "載入權限失敗",
|
||||
"Failed to load preview": "載入預覽失敗",
|
||||
"Failed to load pricing": "載入定價失敗",
|
||||
"Failed to load tab content": "載入分頁內容失敗",
|
||||
"Failed to resolve issues.": "排除異常失敗。",
|
||||
"Failed to save permissions.": "無法儲存權限設定。",
|
||||
@ -897,6 +898,7 @@
|
||||
"Generate Replenishment Order": "產生補貨單",
|
||||
"Generate and manage one-time pickup codes for customers": "產生並管理客戶的一次性取貨碼",
|
||||
"Gift Definitions": "禮品設定",
|
||||
"Global": "全域價",
|
||||
"Global roles accessible by all administrators.": "適用於所有管理者的全域角色。",
|
||||
"Go Authorize": "前往授權",
|
||||
"Go Back": "返回",
|
||||
@ -1020,6 +1022,7 @@
|
||||
"Latitude": "緯度",
|
||||
"Lease": "租賃",
|
||||
"Leave blank to deploy immediately, or set a future time to schedule a background release.": "留空將立即部署更新,或設定未來時間由背景自動排程發布。",
|
||||
"Leave blank to use the global product price. Member price will be capped at the machine selling price.": "留空則沿用全域商品價。會員價會自動夾為不高於機台售價。",
|
||||
"Leave empty for permanent code": "留空表示永久有效",
|
||||
"Leave empty or 0 for permanent code": "留空或輸入 0 則為永久代碼",
|
||||
"Leave empty to inherit company settings": "留空則繼承公司全域設定",
|
||||
@ -1105,6 +1108,8 @@
|
||||
"Machine Model Settings": "機台型號設定",
|
||||
"Machine Name": "機台名稱",
|
||||
"Machine Permissions": "機台權限",
|
||||
"Machine Price": "機台價",
|
||||
"Machine Pricing": "機台定價",
|
||||
"Machine Reboot": "機台 APP 重啟",
|
||||
"Machine Registry": "機台清冊",
|
||||
"Machine Replenishment": "機台補貨單",
|
||||
@ -1121,23 +1126,9 @@
|
||||
"Machine Status List": "機台運行狀態列表",
|
||||
"Machine Stock": "機台庫存",
|
||||
"Machine Stock Movements": "機台庫存流水帳",
|
||||
"Machine Pricing": "機台定價",
|
||||
"Machine Price": "機台價",
|
||||
"Leave blank to use the global product price. Member price will be capped at the machine selling price.": "留空則沿用全域商品價。會員價會自動夾為不高於機台售價。",
|
||||
"No products on this machine yet": "此機台尚無商品",
|
||||
"No products available for this company": "此公司尚無可販售商品",
|
||||
"No matching products": "找不到符合的商品",
|
||||
"overrides set": "項已設定機台價",
|
||||
"Global": "全域價",
|
||||
"Save & Sync": "儲存並同步",
|
||||
"Failed to load pricing": "載入定價失敗",
|
||||
"Saved": "已儲存",
|
||||
"Save failed": "儲存失敗",
|
||||
"Machine pricing saved and sync command pushed.": "機台定價已儲存並推送同步指令。",
|
||||
"Machine pricing updated": "機台定價已更新",
|
||||
"Machine company changed": "機台所屬公司已變更",
|
||||
"Machine System Settings": "機台系統設定",
|
||||
"Machine Utilization": "機台稼動率",
|
||||
"Machine company changed": "機台所屬公司已變更",
|
||||
"Machine created successfully.": "機台已成功建立。",
|
||||
"Machine deleted successfully.": "機台已成功刪除。",
|
||||
"Machine has reconnected to the server and resumed normal heartbeat.": "機台已重新連線至伺服器並恢復正常心跳。",
|
||||
@ -1150,6 +1141,8 @@
|
||||
"Machine normal": "機台正常",
|
||||
"Machine not found": "找不到機台",
|
||||
"Machine permissions updated successfully.": "機台權限已成功更新。",
|
||||
"Machine pricing saved and sync command pushed.": "機台定價已儲存並推送同步指令。",
|
||||
"Machine pricing updated": "機台定價已更新",
|
||||
"Machine settings updated successfully.": "機台設定已成功更新。",
|
||||
"Machine temperature exceeds limit": "機器超過設定溫度,暫停購買",
|
||||
"Machine to Warehouse": "機台對倉庫",
|
||||
@ -1353,6 +1346,7 @@
|
||||
"No maintenance records found": "找不到維修紀錄",
|
||||
"No matching logs found": "找不到符合條件的日誌",
|
||||
"No matching machines": "查無匹配機台",
|
||||
"No matching products": "找不到符合的商品",
|
||||
"No materials available": "沒有可用的素材",
|
||||
"No movement records found": "查無異動紀錄",
|
||||
"No movements found": "尚無異動紀錄",
|
||||
@ -1363,9 +1357,11 @@
|
||||
"No pickup codes found": "找不到取貨碼",
|
||||
"No product data matching search criteria": "沒有符合搜尋條件的商品資料",
|
||||
"No product record found": "無商品紀錄",
|
||||
"No products available for this company": "此公司尚無可販售商品",
|
||||
"No products found": "未找到商品",
|
||||
"No products found in this warehouse": "此倉庫目前無商品庫存",
|
||||
"No products found matching your criteria.": "找不到符合條件的商品。",
|
||||
"No products on this machine yet": "此機台尚無商品",
|
||||
"No records found": "未找到相關紀錄",
|
||||
"No related detail found": "無關聯詳細資料",
|
||||
"No remark": "尚無備註",
|
||||
@ -1842,6 +1838,7 @@
|
||||
"Sales Today": "今日銷售",
|
||||
"Sales revenue minus product cost net value": "銷售額扣除商品成本淨值",
|
||||
"Save": "儲存",
|
||||
"Save & Sync": "儲存並同步",
|
||||
"Save Changes": "儲存變更",
|
||||
"Save Config": "儲存配置",
|
||||
"Save Material": "儲存素材",
|
||||
@ -1849,6 +1846,8 @@
|
||||
"Save Settings": "儲存設定",
|
||||
"Save Version": "儲存版本",
|
||||
"Save error:": "儲存錯誤:",
|
||||
"Save failed": "儲存失敗",
|
||||
"Saved": "已儲存",
|
||||
"Saved.": "已儲存",
|
||||
"Saving...": "儲存中...",
|
||||
"Scale level and access control": "層級與存取控制",
|
||||
@ -2605,6 +2604,7 @@
|
||||
"of items": "筆項目",
|
||||
"optional": "選填",
|
||||
"orders": "筆",
|
||||
"overrides set": "項已設定機台價",
|
||||
"pending": "等待機台領取",
|
||||
"permissions": "權限設定",
|
||||
"permissions.accounts": "帳號管理",
|
||||
|
||||
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