Files
sam-api/app/Services/StockService.php
kent 5ec201b985 feat: H-2 재고 현황 API 구현
- StockController: 재고 조회 및 통계 API
- StockService: 재고 비즈니스 로직
- Stock, StockLot 모델: 재고/로트 관리
- Swagger 문서화
- stocks, stock_lots 테이블 마이그레이션

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

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

138 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);
// 검색어 필터
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(['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;
}
}