157 lines
5.7 KiB
PHP
157 lines
5.7 KiB
PHP
<?php
|
||
|
||
namespace App\Modules\Inventory\Services;
|
||
|
||
use App\Modules\Inventory\Models\Inventory;
|
||
use App\Modules\Inventory\Models\InventoryCountDoc;
|
||
use App\Modules\Inventory\Models\InventoryCountItem;
|
||
use App\Modules\Inventory\Models\Warehouse;
|
||
use App\Modules\Inventory\Models\Product;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Str;
|
||
|
||
class CountService
|
||
{
|
||
/**
|
||
* 建立新的盤點單並執行快照
|
||
*/
|
||
public function createDoc(string $warehouseId, string $remarks = null, int $userId): InventoryCountDoc
|
||
{
|
||
return DB::transaction(function () use ($warehouseId, $remarks, $userId) {
|
||
$doc = InventoryCountDoc::create([
|
||
'warehouse_id' => $warehouseId,
|
||
'status' => 'draft',
|
||
'remarks' => $remarks,
|
||
'created_by' => $userId,
|
||
]);
|
||
|
||
return $doc;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 執行快照:鎖定當前庫存量
|
||
*/
|
||
public function snapshot(InventoryCountDoc $doc): void
|
||
{
|
||
DB::transaction(function () use ($doc) {
|
||
// 清除舊的 items (如果有)
|
||
$doc->items()->delete();
|
||
|
||
// 取得該倉庫所有庫存 (包含 quantity = 0 但未軟刪除的)
|
||
// 這裡可以根據需求決定是否要過濾掉 0 庫存,通常盤點單會希望能看到所有 "帳上有紀錄" 的東西
|
||
$inventories = Inventory::where('warehouse_id', $doc->warehouse_id)
|
||
->whereNull('deleted_at')
|
||
->get();
|
||
|
||
$items = [];
|
||
foreach ($inventories as $inv) {
|
||
$items[] = [
|
||
'count_doc_id' => $doc->id,
|
||
'product_id' => $inv->product_id,
|
||
'batch_number' => $inv->batch_number,
|
||
'system_qty' => $inv->quantity,
|
||
'counted_qty' => null, // 預設未盤點
|
||
'diff_qty' => 0,
|
||
'created_at' => now(),
|
||
'updated_at' => now(),
|
||
];
|
||
}
|
||
|
||
if (!empty($items)) {
|
||
InventoryCountItem::insert($items);
|
||
}
|
||
|
||
$doc->update([
|
||
'status' => 'counting',
|
||
'snapshot_date' => now(),
|
||
]);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 完成盤點:過帳差異
|
||
*/
|
||
public function complete(InventoryCountDoc $doc, int $userId): void
|
||
{
|
||
DB::transaction(function () use ($doc, $userId) {
|
||
foreach ($doc->items as $item) {
|
||
// 如果沒有輸入實盤數量,預設跳過或是視為 0?
|
||
// 安全起見:如果 counted_qty 是 null,表示沒盤到,跳過不處理 (或者依業務邏輯視為0)
|
||
// 這裡假設前端會確保有送出資料,若 null 則不做異動
|
||
if (is_null($item->counted_qty)) {
|
||
continue;
|
||
}
|
||
|
||
$diff = $item->counted_qty - $item->system_qty;
|
||
|
||
// 如果無差異,更新 item 狀態即可 (diff_qty 已經是 computed field 或在儲存時計算)
|
||
// 這裡 update 一下 diff_qty 以防萬一
|
||
$item->update(['diff_qty' => $diff]);
|
||
|
||
if (abs($diff) > 0.0001) {
|
||
// 找回原本的 Inventory
|
||
$inventory = Inventory::where('warehouse_id', $doc->warehouse_id)
|
||
->where('product_id', $item->product_id)
|
||
->where('batch_number', $item->batch_number)
|
||
->first();
|
||
|
||
if (!$inventory) {
|
||
// 如果原本沒庫存紀錄 (例如是新增的盤點項目),需要新建 Inventory
|
||
// 但目前 snapshot 邏輯只抓現有。若允許 "盤盈" (發現不在帳上的),需要額外邏輯
|
||
// 暫時略過 "新增 Inventory" 的複雜邏輯,假設只能針對 existing batch 調整
|
||
continue;
|
||
}
|
||
|
||
$oldQty = $inventory->quantity;
|
||
$newQty = $oldQty + $diff;
|
||
|
||
$inventory->quantity = $newQty;
|
||
$inventory->total_value = $inventory->unit_cost * $newQty;
|
||
$inventory->save();
|
||
|
||
// 寫入 Transaction
|
||
$inventory->transactions()->create([
|
||
'type' => '盤點調整',
|
||
'quantity' => $diff,
|
||
'unit_cost' => $inventory->unit_cost,
|
||
'balance_before' => $oldQty,
|
||
'balance_after' => $newQty,
|
||
'reason' => "盤點單 {$doc->doc_no} 過帳",
|
||
'actual_time' => now(),
|
||
'user_id' => $userId,
|
||
]);
|
||
}
|
||
}
|
||
|
||
$doc->update([
|
||
'status' => 'completed',
|
||
'completed_at' => now(),
|
||
'completed_by' => $userId,
|
||
]);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 更新盤點數量
|
||
*/
|
||
public function updateCount(InventoryCountDoc $doc, array $itemsData): void
|
||
{
|
||
DB::transaction(function () use ($doc, $itemsData) {
|
||
foreach ($itemsData as $data) {
|
||
$item = $doc->items()->find($data['id']);
|
||
if ($item) {
|
||
$countedQty = $data['counted_qty'];
|
||
$diff = is_numeric($countedQty) ? ($countedQty - $item->system_qty) : 0;
|
||
|
||
$item->update([
|
||
'counted_qty' => $countedQty,
|
||
'diff_qty' => $diff,
|
||
'notes' => $data['notes'] ?? $item->notes,
|
||
]);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|