實作 InventoryService 的批量入庫 (processIncomingInventory) 與庫存調整 (adjustInventory) 邏輯
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 55s
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 55s
This commit is contained in:
@@ -165,4 +165,23 @@ interface InventoryServiceInterface
|
||||
* @return \Illuminate\Support\Collection|null
|
||||
*/
|
||||
public function getPosInventoryByWarehouseCode(string $code);
|
||||
|
||||
/**
|
||||
* 處理批量入庫邏輯 (含批號產生與現有批號累加)。
|
||||
*
|
||||
* @param \App\Modules\Inventory\Models\Warehouse $warehouse
|
||||
* @param array $items 入庫品項清單
|
||||
* @param array $meta 資料包含 inboundDate, reason, notes
|
||||
* @return void
|
||||
*/
|
||||
public function processIncomingInventory(\App\Modules\Inventory\Models\Warehouse $warehouse, array $items, array $meta): void;
|
||||
|
||||
/**
|
||||
* 處理單一庫存項目的調整。
|
||||
*
|
||||
* @param \App\Modules\Inventory\Models\Inventory $inventory
|
||||
* @param array $data 包含 quantity, operation, type, reason, unit_cost 等
|
||||
* @return void
|
||||
*/
|
||||
public function adjustInventory(\App\Modules\Inventory\Models\Inventory $inventory, array $data): void;
|
||||
}
|
||||
@@ -22,10 +22,14 @@ use App\Modules\Core\Contracts\CoreServiceInterface;
|
||||
class InventoryController extends Controller
|
||||
{
|
||||
protected $coreService;
|
||||
protected $inventoryService;
|
||||
|
||||
public function __construct(CoreServiceInterface $coreService)
|
||||
{
|
||||
public function __construct(
|
||||
CoreServiceInterface $coreService,
|
||||
\App\Modules\Inventory\Contracts\InventoryServiceInterface $inventoryService
|
||||
) {
|
||||
$this->coreService = $coreService;
|
||||
$this->inventoryService = $inventoryService;
|
||||
}
|
||||
|
||||
public function index(Request $request, Warehouse $warehouse)
|
||||
@@ -182,97 +186,11 @@ class InventoryController extends Controller
|
||||
]);
|
||||
|
||||
return DB::transaction(function () use ($validated, $warehouse) {
|
||||
foreach ($validated['items'] as $item) {
|
||||
// ... (略,傳遞 unit_cost 交給 Service 處理) ...
|
||||
// 這裡需要修改呼叫 Service 的地方或直接更新邏輯
|
||||
// 為求快速,我將在此更新邏輯
|
||||
|
||||
$inventory = null;
|
||||
|
||||
if ($item['batchMode'] === 'existing') {
|
||||
// 模式 A:選擇現有批號 (包含已刪除的也要能找回來累加)
|
||||
$inventory = Inventory::withTrashed()->findOrFail($item['inventoryId']);
|
||||
if ($inventory->trashed()) {
|
||||
$inventory->restore();
|
||||
}
|
||||
|
||||
// 更新成本 (若有傳入)
|
||||
if (isset($item['unit_cost'])) {
|
||||
$inventory->unit_cost = $item['unit_cost'];
|
||||
}
|
||||
} elseif ($item['batchMode'] === 'none') {
|
||||
// 模式 C:不使用批號 (自動累加至 NO-BATCH)
|
||||
$inventory = $warehouse->inventories()->withTrashed()->firstOrNew(
|
||||
[
|
||||
'product_id' => $item['productId'],
|
||||
'batch_number' => 'NO-BATCH'
|
||||
],
|
||||
[
|
||||
'quantity' => 0,
|
||||
'unit_cost' => $item['unit_cost'] ?? 0,
|
||||
'total_value' => 0,
|
||||
'arrival_date' => $validated['inboundDate'],
|
||||
'expiry_date' => null,
|
||||
'origin_country' => 'TW',
|
||||
]
|
||||
);
|
||||
|
||||
if ($inventory->trashed()) {
|
||||
$inventory->restore();
|
||||
}
|
||||
} else {
|
||||
// 模式 B:建立新批號
|
||||
$originCountry = $item['originCountry'] ?? 'TW';
|
||||
$product = Product::find($item['productId']);
|
||||
|
||||
$batchNumber = Inventory::generateBatchNumber(
|
||||
$product->code ?? 'UNK',
|
||||
$originCountry,
|
||||
$validated['inboundDate']
|
||||
);
|
||||
|
||||
// 檢查是否存在
|
||||
$inventory = $warehouse->inventories()->withTrashed()->firstOrNew(
|
||||
[
|
||||
'product_id' => $item['productId'],
|
||||
'batch_number' => $batchNumber
|
||||
],
|
||||
[
|
||||
'quantity' => 0,
|
||||
'unit_cost' => $item['unit_cost'] ?? 0, // 新增
|
||||
'total_value' => 0, // 稍後計算
|
||||
'location' => $item['location'] ?? null,
|
||||
'arrival_date' => $validated['inboundDate'],
|
||||
'expiry_date' => $item['expiryDate'] ?? null,
|
||||
'origin_country' => $originCountry,
|
||||
]
|
||||
);
|
||||
|
||||
if ($inventory->trashed()) {
|
||||
$inventory->restore();
|
||||
}
|
||||
}
|
||||
|
||||
$currentQty = $inventory->quantity;
|
||||
$newQty = $currentQty + $item['quantity'];
|
||||
|
||||
$inventory->quantity = $newQty;
|
||||
// 更新總價值
|
||||
$inventory->total_value = $inventory->quantity * $inventory->unit_cost;
|
||||
$inventory->saveQuietly(); // 使用 saveQuietly() 避免產生冗餘的「單據已更新」日誌
|
||||
|
||||
// 寫入異動紀錄
|
||||
$inventory->transactions()->create([
|
||||
'type' => '手動入庫',
|
||||
'quantity' => $item['quantity'],
|
||||
'unit_cost' => $inventory->unit_cost, // 記錄成本
|
||||
'balance_before' => $currentQty,
|
||||
'balance_after' => $newQty,
|
||||
'reason' => $validated['reason'] . ($validated['notes'] ? ' - ' . $validated['notes'] : ''),
|
||||
'actual_time' => $validated['inboundDate'],
|
||||
'user_id' => auth()->id(),
|
||||
]);
|
||||
}
|
||||
$this->inventoryService->processIncomingInventory($warehouse, $validated['items'], [
|
||||
'inboundDate' => $validated['inboundDate'],
|
||||
'reason' => $validated['reason'],
|
||||
'notes' => $validated['notes'] ?? '',
|
||||
]);
|
||||
|
||||
return redirect()->route('warehouses.inventory.index', $warehouse->id)
|
||||
->with('success', '庫存記錄已儲存成功');
|
||||
@@ -401,81 +319,7 @@ class InventoryController extends Controller
|
||||
]);
|
||||
|
||||
return DB::transaction(function () use ($validated, $inventory) {
|
||||
$currentQty = (float) $inventory->quantity;
|
||||
$newQty = (float) $validated['quantity'];
|
||||
|
||||
// 判斷是否來自調整彈窗 (包含 operation 參數)
|
||||
$isAdjustment = isset($validated['operation']);
|
||||
$changeQty = 0;
|
||||
|
||||
if ($isAdjustment) {
|
||||
switch ($validated['operation']) {
|
||||
case 'add':
|
||||
$changeQty = (float) $validated['quantity'];
|
||||
$newQty = $currentQty + $changeQty;
|
||||
break;
|
||||
case 'subtract':
|
||||
$changeQty = -(float) $validated['quantity'];
|
||||
$newQty = $currentQty + $changeQty;
|
||||
break;
|
||||
case 'set':
|
||||
$changeQty = $newQty - $currentQty;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// 來自編輯頁面,直接 Set
|
||||
$changeQty = $newQty - $currentQty;
|
||||
}
|
||||
|
||||
// 更新成本 (若有傳)
|
||||
if (isset($validated['unit_cost'])) {
|
||||
$inventory->unit_cost = $validated['unit_cost'];
|
||||
}
|
||||
|
||||
// 更新庫存
|
||||
$inventory->quantity = $newQty;
|
||||
// 更新總值
|
||||
$inventory->total_value = $inventory->quantity * $inventory->unit_cost;
|
||||
$inventory->saveQuietly(); // 使用 saveQuietly() 避免與下方的 transaction 紀錄重複
|
||||
|
||||
// 異動類型映射
|
||||
$type = $validated['type'] ?? ($isAdjustment ? 'manual_adjustment' : 'adjustment');
|
||||
$typeMapping = [
|
||||
'manual_adjustment' => '手動調整庫存',
|
||||
'adjustment' => '盤點調整',
|
||||
'purchase_in' => '採購進貨',
|
||||
'sales_out' => '銷售出庫',
|
||||
'return_in' => '退貨入庫',
|
||||
'return_out' => '退貨出庫',
|
||||
'transfer_in' => '撥補入庫',
|
||||
'transfer_out' => '撥補出庫',
|
||||
];
|
||||
$chineseType = $typeMapping[$type] ?? $type;
|
||||
|
||||
// 如果是編輯頁面來的,且沒傳 type,設為手動編輯
|
||||
if (!$isAdjustment && !isset($validated['type'])) {
|
||||
$chineseType = '手動編輯';
|
||||
}
|
||||
|
||||
// 整理原因
|
||||
$reason = $validated['reason'] ?? ($isAdjustment ? '手動庫存調整' : '編輯頁面更新');
|
||||
if (isset($validated['notes'])) {
|
||||
$reason .= ' - ' . $validated['notes'];
|
||||
}
|
||||
|
||||
// 寫入異動紀錄
|
||||
if (abs($changeQty) > 0.0001) {
|
||||
$inventory->transactions()->create([
|
||||
'type' => $chineseType,
|
||||
'quantity' => $changeQty,
|
||||
'unit_cost' => $inventory->unit_cost, // 記錄
|
||||
'balance_before' => $currentQty,
|
||||
'balance_after' => $newQty,
|
||||
'reason' => $reason,
|
||||
'actual_time' => now(),
|
||||
'user_id' => auth()->id(),
|
||||
]);
|
||||
}
|
||||
$this->inventoryService->adjustInventory($inventory, $validated);
|
||||
|
||||
return redirect()->route('warehouses.inventory.index', $inventory->warehouse_id)
|
||||
->with('success', '庫存資料已更新');
|
||||
|
||||
@@ -674,4 +674,168 @@ class InventoryService implements InventoryServiceInterface
|
||||
->groupBy('inventories.product_id', 'products.external_pos_id', 'products.code', 'products.name')
|
||||
->get();
|
||||
}
|
||||
|
||||
public function processIncomingInventory(Warehouse $warehouse, array $items, array $meta): void
|
||||
{
|
||||
DB::transaction(function () use ($warehouse, $items, $meta) {
|
||||
foreach ($items as $item) {
|
||||
$inventory = null;
|
||||
|
||||
if ($item['batchMode'] === 'existing') {
|
||||
// 模式 A:選擇現有批號 (包含已刪除的也要能找回來累加)
|
||||
$inventory = Inventory::withTrashed()->findOrFail($item['inventoryId']);
|
||||
if ($inventory->trashed()) {
|
||||
$inventory->restore();
|
||||
}
|
||||
|
||||
// 更新成本 (若有傳入)
|
||||
if (isset($item['unit_cost'])) {
|
||||
$inventory->unit_cost = $item['unit_cost'];
|
||||
}
|
||||
} elseif ($item['batchMode'] === 'none') {
|
||||
// 模式 C:不使用批號 (自動累加至 NO-BATCH)
|
||||
$inventory = $warehouse->inventories()->withTrashed()->firstOrNew(
|
||||
[
|
||||
'product_id' => $item['productId'],
|
||||
'batch_number' => 'NO-BATCH'
|
||||
],
|
||||
[
|
||||
'quantity' => 0,
|
||||
'unit_cost' => $item['unit_cost'] ?? 0,
|
||||
'total_value' => 0,
|
||||
'arrival_date' => $meta['inboundDate'],
|
||||
'origin_country' => 'TW',
|
||||
]
|
||||
);
|
||||
|
||||
if ($inventory->trashed()) {
|
||||
$inventory->restore();
|
||||
}
|
||||
} else {
|
||||
// 模式 B:建立新批號
|
||||
$originCountry = $item['originCountry'] ?? 'TW';
|
||||
$product = Product::find($item['productId']);
|
||||
|
||||
$batchNumber = Inventory::generateBatchNumber(
|
||||
$product->code ?? 'UNK',
|
||||
$originCountry,
|
||||
$meta['inboundDate']
|
||||
);
|
||||
|
||||
// 檢查是否存在
|
||||
$inventory = $warehouse->inventories()->withTrashed()->firstOrNew(
|
||||
[
|
||||
'product_id' => $item['productId'],
|
||||
'batch_number' => $batchNumber
|
||||
],
|
||||
[
|
||||
'quantity' => 0,
|
||||
'unit_cost' => $item['unit_cost'] ?? 0,
|
||||
'total_value' => 0,
|
||||
'location' => $item['location'] ?? null,
|
||||
'arrival_date' => $meta['inboundDate'],
|
||||
'expiry_date' => $item['expiryDate'] ?? null,
|
||||
'origin_country' => $originCountry,
|
||||
]
|
||||
);
|
||||
|
||||
if ($inventory->trashed()) {
|
||||
$inventory->restore();
|
||||
}
|
||||
}
|
||||
|
||||
$currentQty = $inventory->quantity;
|
||||
$newQty = $currentQty + $item['quantity'];
|
||||
|
||||
$inventory->quantity = $newQty;
|
||||
// 更新總價值
|
||||
$inventory->total_value = $inventory->quantity * $inventory->unit_cost;
|
||||
$inventory->saveQuietly();
|
||||
|
||||
// 寫入異動紀錄
|
||||
$inventory->transactions()->create([
|
||||
'type' => '手動入庫',
|
||||
'quantity' => $item['quantity'],
|
||||
'unit_cost' => $inventory->unit_cost,
|
||||
'balance_before' => $currentQty,
|
||||
'balance_after' => $newQty,
|
||||
'reason' => $meta['reason'] . (!empty($meta['notes']) ? ' - ' . $meta['notes'] : ''),
|
||||
'actual_time' => $meta['inboundDate'],
|
||||
'user_id' => auth()->id(),
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function adjustInventory(Inventory $inventory, array $data): void
|
||||
{
|
||||
DB::transaction(function () use ($inventory, $data) {
|
||||
$currentQty = (float) $inventory->quantity;
|
||||
$newQty = (float) $data['quantity'];
|
||||
|
||||
$isAdjustment = isset($data['operation']);
|
||||
$changeQty = 0;
|
||||
|
||||
if ($isAdjustment) {
|
||||
switch ($data['operation']) {
|
||||
case 'add':
|
||||
$changeQty = (float) $data['quantity'];
|
||||
$newQty = $currentQty + $changeQty;
|
||||
break;
|
||||
case 'subtract':
|
||||
$changeQty = -(float) $data['quantity'];
|
||||
$newQty = $currentQty + $changeQty;
|
||||
break;
|
||||
case 'set':
|
||||
$changeQty = $newQty - $currentQty;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$changeQty = $newQty - $currentQty;
|
||||
}
|
||||
|
||||
if (isset($data['unit_cost'])) {
|
||||
$inventory->unit_cost = $data['unit_cost'];
|
||||
}
|
||||
|
||||
$inventory->quantity = $newQty;
|
||||
$inventory->total_value = $inventory->quantity * $inventory->unit_cost;
|
||||
$inventory->saveQuietly();
|
||||
|
||||
$type = $data['type'] ?? ($isAdjustment ? 'manual_adjustment' : 'adjustment');
|
||||
$typeMapping = [
|
||||
'manual_adjustment' => '手動調整庫存',
|
||||
'adjustment' => '盤點調整',
|
||||
'purchase_in' => '採購進貨',
|
||||
'sales_out' => '銷售出庫',
|
||||
'return_in' => '退貨入庫',
|
||||
'return_out' => '退貨出庫',
|
||||
'transfer_in' => '撥補入庫',
|
||||
'transfer_out' => '撥補出庫',
|
||||
];
|
||||
$chineseType = $typeMapping[$type] ?? $type;
|
||||
|
||||
if (!$isAdjustment && !isset($data['type'])) {
|
||||
$chineseType = '手動編輯';
|
||||
}
|
||||
|
||||
$reason = $data['reason'] ?? ($isAdjustment ? '手動庫存調整' : '編輯頁面更新');
|
||||
if (!empty($data['notes'])) {
|
||||
$reason .= ' - ' . $data['notes'];
|
||||
}
|
||||
|
||||
if (abs($changeQty) > 0.0001) {
|
||||
$inventory->transactions()->create([
|
||||
'type' => $chineseType,
|
||||
'quantity' => $changeQty,
|
||||
'unit_cost' => $inventory->unit_cost,
|
||||
'balance_before' => $currentQty,
|
||||
'balance_after' => $newQty,
|
||||
'reason' => $reason,
|
||||
'actual_time' => now(),
|
||||
'user_id' => auth()->id(),
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user