2025-12-26 15:45:48 +09:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
|
|
|
|
|
use App\Models\Tenants\Stock;
|
|
|
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
|
|
|
|
|
|
|
|
|
class StockService extends Service
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* 재고 목록 조회
|
|
|
|
|
*/
|
|
|
|
|
public function index(array $params): LengthAwarePaginator
|
|
|
|
|
{
|
|
|
|
|
$tenantId = $this->tenantId();
|
|
|
|
|
|
|
|
|
|
$query = Stock::query()
|
2025-12-29 15:31:00 +09:00
|
|
|
->where('tenant_id', $tenantId)
|
|
|
|
|
->with('item');
|
2025-12-26 15:45:48 +09:00
|
|
|
|
|
|
|
|
// 검색어 필터
|
|
|
|
|
if (! empty($params['search'])) {
|
|
|
|
|
$search = $params['search'];
|
|
|
|
|
$query->where(function ($q) use ($search) {
|
|
|
|
|
$q->where('item_code', 'like', "%{$search}%")
|
|
|
|
|
->orWhere('item_name', 'like', "%{$search}%");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 품목유형 필터
|
|
|
|
|
if (! empty($params['item_type'])) {
|
|
|
|
|
$query->where('item_type', $params['item_type']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 재고 상태 필터
|
|
|
|
|
if (! empty($params['status'])) {
|
|
|
|
|
$query->where('status', $params['status']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 위치 필터
|
|
|
|
|
if (! empty($params['location'])) {
|
|
|
|
|
$query->where('location', 'like', "%{$params['location']}%");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 정렬
|
|
|
|
|
$sortBy = $params['sort_by'] ?? 'item_code';
|
|
|
|
|
$sortDir = $params['sort_dir'] ?? 'asc';
|
|
|
|
|
$query->orderBy($sortBy, $sortDir);
|
|
|
|
|
|
|
|
|
|
// 페이지네이션
|
|
|
|
|
$perPage = $params['per_page'] ?? 20;
|
|
|
|
|
|
|
|
|
|
return $query->paginate($perPage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 재고 통계 조회
|
|
|
|
|
*/
|
|
|
|
|
public function stats(): array
|
|
|
|
|
{
|
|
|
|
|
$tenantId = $this->tenantId();
|
|
|
|
|
|
|
|
|
|
$totalItems = Stock::where('tenant_id', $tenantId)->count();
|
|
|
|
|
|
|
|
|
|
$normalCount = Stock::where('tenant_id', $tenantId)
|
|
|
|
|
->where('status', 'normal')
|
|
|
|
|
->count();
|
|
|
|
|
|
|
|
|
|
$lowCount = Stock::where('tenant_id', $tenantId)
|
|
|
|
|
->where('status', 'low')
|
|
|
|
|
->count();
|
|
|
|
|
|
|
|
|
|
$outCount = Stock::where('tenant_id', $tenantId)
|
|
|
|
|
->where('status', 'out')
|
|
|
|
|
->count();
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'total_items' => $totalItems,
|
|
|
|
|
'normal_count' => $normalCount,
|
|
|
|
|
'low_count' => $lowCount,
|
|
|
|
|
'out_count' => $outCount,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 재고 상세 조회 (LOT 포함)
|
|
|
|
|
*/
|
|
|
|
|
public function show(int $id): Stock
|
|
|
|
|
{
|
|
|
|
|
$tenantId = $this->tenantId();
|
|
|
|
|
|
|
|
|
|
return Stock::query()
|
|
|
|
|
->where('tenant_id', $tenantId)
|
2025-12-29 15:31:00 +09:00
|
|
|
->with(['item', 'lots' => function ($query) {
|
2025-12-26 15:45:48 +09:00
|
|
|
$query->orderBy('fifo_order');
|
|
|
|
|
}])
|
|
|
|
|
->findOrFail($id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 품목코드로 재고 조회
|
|
|
|
|
*/
|
|
|
|
|
public function findByItemCode(string $itemCode): ?Stock
|
|
|
|
|
{
|
|
|
|
|
$tenantId = $this->tenantId();
|
|
|
|
|
|
|
|
|
|
return Stock::query()
|
|
|
|
|
->where('tenant_id', $tenantId)
|
|
|
|
|
->where('item_code', $itemCode)
|
|
|
|
|
->first();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 품목유형별 통계
|
|
|
|
|
*/
|
|
|
|
|
public function statsByItemType(): array
|
|
|
|
|
{
|
|
|
|
|
$tenantId = $this->tenantId();
|
|
|
|
|
|
|
|
|
|
$stats = Stock::where('tenant_id', $tenantId)
|
|
|
|
|
->selectRaw('item_type, COUNT(*) as count, SUM(stock_qty) as total_qty')
|
|
|
|
|
->groupBy('item_type')
|
|
|
|
|
->get()
|
|
|
|
|
->keyBy('item_type');
|
|
|
|
|
|
|
|
|
|
$result = [];
|
|
|
|
|
foreach (Stock::ITEM_TYPES as $key => $label) {
|
|
|
|
|
$data = $stats->get($key);
|
|
|
|
|
$result[$key] = [
|
|
|
|
|
'label' => $label,
|
|
|
|
|
'count' => $data?->count ?? 0,
|
|
|
|
|
'total_qty' => $data?->total_qty ?? 0,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
|
}
|
|
|
|
|
}
|