[REFACTOR] 統一訂單同步 API 錯誤回應與修正 Linter 警告
This commit is contained in:
@@ -87,42 +87,58 @@ class InventoryService implements InventoryServiceInterface
|
||||
return $stock >= $quantity;
|
||||
}
|
||||
|
||||
public function decreaseStock(int $productId, int $warehouseId, float $quantity, ?string $reason = null, bool $force = false, ?string $slot = null, ?string $referenceType = null, $referenceId = null): void
|
||||
public function decreaseStock(int $productId, int $warehouseId, float $quantity, ?string $reason = null, bool $force = false, ?string $slot = null, ?string $referenceType = null, $referenceId = null, ?string $batchNumber = null): void
|
||||
{
|
||||
DB::transaction(function () use ($productId, $warehouseId, $quantity, $reason, $force, $slot, $referenceType, $referenceId) {
|
||||
$query = Inventory::where('product_id', $productId)
|
||||
->where('warehouse_id', $warehouseId)
|
||||
->where('quantity', '>', 0);
|
||||
|
||||
if ($slot) {
|
||||
$query->where('location', $slot);
|
||||
}
|
||||
DB::transaction(function () use ($productId, $warehouseId, $quantity, $reason, $force, $slot, $referenceType, $referenceId, $batchNumber) {
|
||||
$defaultBatch = 'NO-BATCH';
|
||||
$targetBatch = $batchNumber ?? $defaultBatch;
|
||||
$remainingToDecrease = $quantity;
|
||||
|
||||
$inventories = $query->lockForUpdate()
|
||||
// 1. 優先嘗試扣除指定批號(或預設的 NO-BATCH)
|
||||
$inventories = Inventory::where('product_id', $productId)
|
||||
->where('warehouse_id', $warehouseId)
|
||||
->where('batch_number', $targetBatch)
|
||||
->where('quantity', '>', 0)
|
||||
->when($slot, fn($q) => $q->where('location', $slot))
|
||||
->lockForUpdate()
|
||||
->orderBy('arrival_date', 'asc')
|
||||
->get();
|
||||
|
||||
$remainingToDecrease = $quantity;
|
||||
|
||||
foreach ($inventories as $inventory) {
|
||||
if ($remainingToDecrease <= 0) break;
|
||||
|
||||
$decreaseAmount = min($inventory->quantity, $remainingToDecrease);
|
||||
$this->decreaseInventoryQuantity($inventory->id, $decreaseAmount, $reason, $referenceType, $referenceId);
|
||||
$remainingToDecrease -= $decreaseAmount;
|
||||
}
|
||||
|
||||
// 2. 如果還有剩餘且剛才不是扣 NO-BATCH,則嘗試從 NO-BATCH 補位
|
||||
if ($remainingToDecrease > 0 && $targetBatch !== $defaultBatch) {
|
||||
$fallbackInventories = Inventory::where('product_id', $productId)
|
||||
->where('warehouse_id', $warehouseId)
|
||||
->where('batch_number', $defaultBatch)
|
||||
->where('quantity', '>', 0)
|
||||
->when($slot, fn($q) => $q->where('location', $slot))
|
||||
->lockForUpdate()
|
||||
->orderBy('arrival_date', 'asc')
|
||||
->get();
|
||||
|
||||
foreach ($fallbackInventories as $inventory) {
|
||||
if ($remainingToDecrease <= 0) break;
|
||||
$decreaseAmount = min($inventory->quantity, $remainingToDecrease);
|
||||
$this->decreaseInventoryQuantity($inventory->id, $decreaseAmount, $reason, $referenceType, $referenceId);
|
||||
$remainingToDecrease -= $decreaseAmount;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 處理最終仍不足的情況
|
||||
if ($remainingToDecrease > 0) {
|
||||
if ($force) {
|
||||
// Find any existing inventory record in this warehouse/slot to subtract from, or create one
|
||||
$query = Inventory::where('product_id', $productId)
|
||||
->where('warehouse_id', $warehouseId);
|
||||
|
||||
if ($slot) {
|
||||
$query->where('location', $slot);
|
||||
}
|
||||
|
||||
$inventory = $query->first();
|
||||
// 強制模式下,若指定批號或 NO-BATCH 均不足,統一在 NO-BATCH 建立/扣除負庫存
|
||||
$inventory = Inventory::where('product_id', $productId)
|
||||
->where('warehouse_id', $warehouseId)
|
||||
->where('batch_number', $defaultBatch)
|
||||
->when($slot, fn($q) => $q->where('location', $slot))
|
||||
->first();
|
||||
|
||||
if (!$inventory) {
|
||||
$inventory = Inventory::create([
|
||||
@@ -132,7 +148,7 @@ class InventoryService implements InventoryServiceInterface
|
||||
'quantity' => 0,
|
||||
'unit_cost' => 0,
|
||||
'total_value' => 0,
|
||||
'batch_number' => 'POS-AUTO-' . ($slot ? $slot . '-' : '') . time(),
|
||||
'batch_number' => $defaultBatch,
|
||||
'arrival_date' => now(),
|
||||
'origin_country' => 'TW',
|
||||
'quality_status' => 'normal',
|
||||
@@ -141,7 +157,10 @@ class InventoryService implements InventoryServiceInterface
|
||||
|
||||
$this->decreaseInventoryQuantity($inventory->id, $remainingToDecrease, $reason, $referenceType, $referenceId);
|
||||
} else {
|
||||
throw new \Exception("庫存不足,無法扣除所有請求的數量。");
|
||||
$context = ($targetBatch !== $defaultBatch)
|
||||
? "批號 {$targetBatch} 或 {$defaultBatch}"
|
||||
: "{$defaultBatch}";
|
||||
throw new \Exception("庫存不足,無法扣除所有請求的數量 ({$context})。");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -658,7 +677,7 @@ class InventoryService implements InventoryServiceInterface
|
||||
* @param string $code
|
||||
* @return \Illuminate\Support\Collection|null
|
||||
*/
|
||||
public function getPosInventoryByWarehouseCode(string $code)
|
||||
public function getPosInventoryByWarehouseCode(string $code, array $filters = [])
|
||||
{
|
||||
$warehouse = Warehouse::where('code', $code)->first();
|
||||
|
||||
@@ -666,19 +685,60 @@ class InventoryService implements InventoryServiceInterface
|
||||
return null;
|
||||
}
|
||||
|
||||
// 整理該倉庫的庫存,以 product_id 進行 GROUP BY 並加總 quantity
|
||||
return DB::table('inventories')
|
||||
$query = DB::table('inventories')
|
||||
->join('products', 'inventories.product_id', '=', 'products.id')
|
||||
->leftJoin('categories', 'products.category_id', '=', 'categories.id')
|
||||
->leftJoin('units', 'products.base_unit_id', '=', 'units.id')
|
||||
->where('inventories.warehouse_id', $warehouse->id)
|
||||
->whereNull('inventories.deleted_at')
|
||||
->whereNull('products.deleted_at')
|
||||
->select(
|
||||
'products.id as product_id',
|
||||
'products.external_pos_id',
|
||||
'products.code as product_code',
|
||||
'products.name as product_name',
|
||||
'products.barcode',
|
||||
'categories.name as category_name',
|
||||
'units.name as unit_name',
|
||||
'products.price',
|
||||
'products.brand',
|
||||
'products.specification',
|
||||
'inventories.batch_number',
|
||||
'inventories.expiry_date',
|
||||
DB::raw('SUM(inventories.quantity) as total_quantity')
|
||||
);
|
||||
|
||||
// 加入條件篩選
|
||||
if (!empty($filters['product_id'])) {
|
||||
$query->where('products.id', $filters['product_id']);
|
||||
}
|
||||
|
||||
if (!empty($filters['external_pos_id'])) {
|
||||
$query->where('products.external_pos_id', $filters['external_pos_id']);
|
||||
}
|
||||
|
||||
if (!empty($filters['barcode'])) {
|
||||
$query->where('products.barcode', $filters['barcode']);
|
||||
}
|
||||
|
||||
if (!empty($filters['code'])) {
|
||||
$query->where('products.code', $filters['code']);
|
||||
}
|
||||
|
||||
return $query->groupBy(
|
||||
'inventories.product_id',
|
||||
'products.external_pos_id',
|
||||
'products.code',
|
||||
'products.name',
|
||||
'products.barcode',
|
||||
'categories.name',
|
||||
'units.name',
|
||||
'products.price',
|
||||
'products.brand',
|
||||
'products.specification',
|
||||
'inventories.batch_number',
|
||||
'inventories.expiry_date'
|
||||
)
|
||||
->groupBy('inventories.product_id', 'products.external_pos_id', 'products.code', 'products.name')
|
||||
->get();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user