Files
star-erp/app/Modules/Inventory/Services/CountService.php

157 lines
5.7 KiB
PHP
Raw Normal View History

<?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,
]);
}
}
});
}
}