feat: Items API CRUD 기능 추가 (BP-MES Phase 1 Day 3-5)

- ItemsController 및 ItemsService CRUD 메서드 구현
- FormRequest 검증 클래스 추가 (ItemStoreRequest, ItemUpdateRequest)
- Swagger 문서 완성 (ItemsApi.php)
- 품목 생성/조회/수정/삭제 엔드포인트 추가
- i18n 메시지 키 추가 (message.item)
- Code 기반 라우팅 적용
- Hybrid 구조 지원 (고정 필드 + attributes JSON)
This commit is contained in:
2025-11-17 11:22:49 +09:00
parent 63ab79b910
commit a23b727557
7 changed files with 454 additions and 206 deletions

View File

@@ -5,6 +5,7 @@
use App\Models\Materials\Material;
use App\Models\Products\Product;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ItemsService extends Service
@@ -220,4 +221,122 @@ private function fetchPrices(string $itemType, int $itemId, ?int $clientId, ?str
'purchase' => $purchasePrice,
];
}
}
/**
* 품목 생성 (Product 전용)
*
* @param array $data 검증된 데이터
*/
public function createItem(array $data): Product
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
// 품목 코드 중복 체크
$exists = Product::query()
->where('tenant_id', $tenantId)
->where('code', $data['code'])
->exists();
if ($exists) {
throw new BadRequestHttpException(__('error.duplicate_code'));
}
$payload = $data;
$payload['tenant_id'] = $tenantId;
$payload['created_by'] = $userId;
$payload['is_sellable'] = $payload['is_sellable'] ?? true;
$payload['is_purchasable'] = $payload['is_purchasable'] ?? false;
$payload['is_producible'] = $payload['is_producible'] ?? false;
return Product::create($payload);
}
/**
* 품목 수정 (Product 전용)
*
* @param string $code 품목 코드
* @param array $data 검증된 데이터
*/
public function updateItem(string $code, array $data): Product
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
$product = Product::query()
->where('tenant_id', $tenantId)
->where('code', $code)
->first();
if (! $product) {
throw new NotFoundHttpException(__('error.not_found'));
}
// 코드 변경 시 중복 체크
if (isset($data['code']) && $data['code'] !== $code) {
$exists = Product::query()
->where('tenant_id', $tenantId)
->where('code', $data['code'])
->where('id', '!=', $product->id)
->exists();
if ($exists) {
throw new BadRequestHttpException(__('error.duplicate_code'));
}
}
$data['updated_by'] = $userId;
$product->update($data);
return $product->refresh();
}
/**
* 품목 삭제 (Product 전용, Soft Delete)
*
* @param string $code 품목 코드
*/
public function deleteItem(string $code): void
{
$tenantId = $this->tenantId();
$product = Product::query()
->where('tenant_id', $tenantId)
->where('code', $code)
->first();
if (! $product) {
throw new NotFoundHttpException(__('error.not_found'));
}
$product->delete();
}
/**
* 품목 상세 조회 (code 기반, BOM 포함 옵션)
*
* @param string $code 품목 코드
* @param bool $includeBom BOM 포함 여부
*/
public function getItemByCode(string $code, bool $includeBom = false): Product
{
$tenantId = $this->tenantId();
$query = Product::query()
->with('category:id,name')
->where('tenant_id', $tenantId)
->where('code', $code);
if ($includeBom) {
$query->with('componentLines.childProduct:id,code,name,unit');
}
$product = $query->first();
if (! $product) {
throw new NotFoundHttpException(__('error.not_found'));
}
return $product;
}
}