Files
sam-api/app/Services/StockService.php
kent a639e0fa60 feat(SAM/API): Stock 모델에 Item 관계 추가 및 시더 생성
- Stock 모델에 item_id 필드 및 item() 관계 추가
- StockService에 item eager loading 적용
- stocks 테이블 item_id 마이그레이션 추가
- StockReceivingSeeder 더미 데이터 시더 생성
- Process 모델 Traits 경로 수정

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 15:31:00 +09:00

139 lines
3.5 KiB
PHP

<?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()
->where('tenant_id', $tenantId)
->with('item');
// 검색어 필터
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)
->with(['item', 'lots' => function ($query) {
$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;
}
}