feat: ItemService 동적 테이블 라우팅 구현
- item_type → ItemPage.source_table → Model 클래스 동적 라우팅 - getModelInfoByItemType(): item_type으로 Model 정보 조회 (캐싱) - newQuery(): 동적 Query Builder 생성 - 모든 CRUD 메서드 item_type 필수 파라미터로 변경 - ItemsController item_type 전달 로직 수정 - 에러 메시지 추가 (item_type_required, invalid_source_table) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,57 +7,73 @@
|
||||
use App\Http\Requests\Item\ItemBatchDeleteRequest;
|
||||
use App\Http\Requests\Item\ItemStoreRequest;
|
||||
use App\Http\Requests\Item\ItemUpdateRequest;
|
||||
use App\Services\ItemsService;
|
||||
use App\Services\ItemService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ItemsController extends Controller
|
||||
{
|
||||
public function __construct(private ItemsService $service) {}
|
||||
public function __construct(private ItemService $service) {}
|
||||
|
||||
/**
|
||||
* 통합 품목 목록 조회 (materials + products)
|
||||
* 통합 품목 목록 조회 (items 테이블)
|
||||
*
|
||||
* GET /api/v1/items
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$filters = $request->only(['type', 'search', 'q', 'category_id', 'is_active']);
|
||||
$perPage = (int) ($request->input('size') ?? 20);
|
||||
$includeDeleted = filter_var($request->input('include_deleted', false), FILTER_VALIDATE_BOOLEAN);
|
||||
$params = [
|
||||
'size' => $request->input('size', 20),
|
||||
'q' => $request->input('q') ?? $request->input('search'),
|
||||
'category_id' => $request->input('category_id'),
|
||||
'item_type' => $request->input('type') ?? $request->input('item_type'),
|
||||
'active' => $request->input('is_active') ?? $request->input('active'),
|
||||
];
|
||||
|
||||
return $this->service->getItems($filters, $perPage, $includeDeleted);
|
||||
return $this->service->index($params);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 단일 품목 조회
|
||||
* 단일 품목 조회 (동적 테이블 라우팅)
|
||||
*
|
||||
* GET /api/v1/items/{id}?item_type=FG|PT|SM|RM|CS&include_price=true&client_id=1&price_date=2025-01-10
|
||||
* GET /api/v1/items/{id}?item_type=FG&include_price=true&client_id=1&price_date=2025-01-10
|
||||
*
|
||||
* @param string item_type 품목 유형 (필수 - 동적 테이블 라우팅)
|
||||
*/
|
||||
public function show(Request $request, int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
$itemType = strtoupper($request->input('item_type', 'FG'));
|
||||
// item_type 필수 (동적 테이블 라우팅에 사용)
|
||||
$itemType = strtoupper($request->input('item_type', ''));
|
||||
$includePrice = filter_var($request->input('include_price', false), FILTER_VALIDATE_BOOLEAN);
|
||||
$clientId = $request->input('client_id') ? (int) $request->input('client_id') : null;
|
||||
$priceDate = $request->input('price_date');
|
||||
|
||||
return $this->service->getItem($id, $itemType, $includePrice, $clientId, $priceDate);
|
||||
if ($includePrice) {
|
||||
$clientId = $request->input('client_id') ? (int) $request->input('client_id') : null;
|
||||
$priceDate = $request->input('price_date');
|
||||
|
||||
return $this->service->showWithPrice($id, $itemType, $clientId, $priceDate);
|
||||
}
|
||||
|
||||
return $this->service->show($id, $itemType);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 품목 상세 조회 (code 기반, BOM 포함 옵션)
|
||||
* 품목 상세 조회 (동적 테이블 라우팅, code 기반, BOM 포함 옵션)
|
||||
*
|
||||
* GET /api/v1/items/code/{code}?include_bom=true
|
||||
* GET /api/v1/items/code/{code}?item_type=FG&include_bom=true
|
||||
*
|
||||
* @param string item_type 품목 유형 (필수 - 동적 테이블 라우팅)
|
||||
*/
|
||||
public function showByCode(Request $request, string $code)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $code) {
|
||||
// item_type 필수 (동적 테이블 라우팅에 사용)
|
||||
$itemType = strtoupper($request->input('item_type', ''));
|
||||
$includeBom = filter_var($request->input('include_bom', false), FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
return $this->service->getItemByCode($code, $includeBom);
|
||||
return $this->service->showByCode($code, $itemType, $includeBom);
|
||||
}, __('message.item.fetched'));
|
||||
}
|
||||
|
||||
@@ -69,7 +85,7 @@ public function showByCode(Request $request, string $code)
|
||||
public function store(ItemStoreRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->createItem($request->all());
|
||||
return $this->service->store($request->all());
|
||||
}, __('message.item.created'));
|
||||
}
|
||||
|
||||
@@ -81,38 +97,44 @@ public function store(ItemStoreRequest $request)
|
||||
public function update(int $id, ItemUpdateRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id, $request) {
|
||||
return $this->service->updateItem($id, $request->all());
|
||||
return $this->service->update($id, $request->all());
|
||||
}, __('message.item.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 품목 삭제 (Soft Delete)
|
||||
* 품목 삭제 (동적 테이블 라우팅, Soft Delete)
|
||||
*
|
||||
* DELETE /api/v1/items/{id}?item_type=FG|PT|SM|RM|CS
|
||||
* DELETE /api/v1/items/{id}?item_type=FG
|
||||
*
|
||||
* @param string item_type 품목 유형 (필수 - 동적 테이블 라우팅)
|
||||
*/
|
||||
public function destroy(Request $request, int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
$itemType = strtoupper($request->input('item_type', 'FG'));
|
||||
$this->service->deleteItem($id, $itemType);
|
||||
// item_type 필수 (동적 테이블 라우팅에 사용)
|
||||
$itemType = strtoupper($request->input('item_type', ''));
|
||||
$this->service->destroy($id, $itemType);
|
||||
|
||||
return 'success';
|
||||
}, __('message.item.deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 품목 일괄 삭제 (Soft Delete)
|
||||
* 품목 일괄 삭제 (동적 테이블 라우팅, Soft Delete)
|
||||
*
|
||||
* DELETE /api/v1/items/batch
|
||||
* DELETE /api/v1/items/batch?item_type=FG
|
||||
*
|
||||
* @param string item_type 품목 유형 (필수 - 동적 테이블 라우팅)
|
||||
*/
|
||||
public function batchDestroy(ItemBatchDeleteRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$validated = $request->validated();
|
||||
$itemType = strtoupper($validated['item_type'] ?? 'FG');
|
||||
$this->service->batchDeleteItems($validated['ids'], $itemType);
|
||||
// item_type 필수 (동적 테이블 라우팅에 사용)
|
||||
$itemType = strtoupper($validated['item_type'] ?? '');
|
||||
$deletedCount = $this->service->batchDestroy($validated['ids'], $itemType);
|
||||
|
||||
return 'success';
|
||||
return ['deleted_count' => $deletedCount];
|
||||
}, __('message.item.batch_deleted'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,81 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Constants\SystemFields;
|
||||
use App\Models\Commons\Category;
|
||||
use App\Models\ItemMaster\ItemField;
|
||||
use App\Models\Items\Item;
|
||||
use App\Models\ItemMaster\ItemPage;
|
||||
use App\Models\Items\ItemDetail;
|
||||
use App\Models\Products\CommonCode;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
||||
class ItemService extends Service
|
||||
{
|
||||
/**
|
||||
* item_type 캐시 (동일 요청 내 중복 조회 방지)
|
||||
*/
|
||||
private array $modelCache = [];
|
||||
|
||||
/**
|
||||
* item_type으로 해당하는 Model 클래스와 메타정보 조회
|
||||
*
|
||||
* @param string $itemType 품목 유형 (FG, PT, SM, RM, CS 등)
|
||||
* @return array{model: string, page: ItemPage, source_table: string}
|
||||
*/
|
||||
private function getModelInfoByItemType(string $itemType): array
|
||||
{
|
||||
$itemType = strtoupper($itemType);
|
||||
$tenantId = $this->tenantId();
|
||||
$cacheKey = "{$tenantId}_{$itemType}";
|
||||
|
||||
if (isset($this->modelCache[$cacheKey])) {
|
||||
return $this->modelCache[$cacheKey];
|
||||
}
|
||||
|
||||
$page = ItemPage::where('tenant_id', $tenantId)
|
||||
->where('item_type', $itemType)
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
|
||||
if (! $page) {
|
||||
throw new BadRequestHttpException(__('error.invalid_item_type'));
|
||||
}
|
||||
|
||||
$modelClass = $page->getTargetModelClass();
|
||||
if (! $modelClass) {
|
||||
throw new BadRequestHttpException(__('error.invalid_source_table'));
|
||||
}
|
||||
|
||||
$this->modelCache[$cacheKey] = [
|
||||
'model' => $modelClass,
|
||||
'page' => $page,
|
||||
'source_table' => $page->source_table,
|
||||
];
|
||||
|
||||
return $this->modelCache[$cacheKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* item_type으로 Model 인스턴스 생성
|
||||
*/
|
||||
private function newModelInstance(string $itemType): Model
|
||||
{
|
||||
$info = $this->getModelInfoByItemType($itemType);
|
||||
|
||||
return new $info['model'];
|
||||
}
|
||||
|
||||
/**
|
||||
* item_type으로 Query Builder 생성
|
||||
*/
|
||||
private function newQuery(string $itemType)
|
||||
{
|
||||
$info = $this->getModelInfoByItemType($itemType);
|
||||
$modelClass = $info['model'];
|
||||
|
||||
return $modelClass::query()->where('tenant_id', $this->tenantId());
|
||||
}
|
||||
|
||||
/**
|
||||
* items 테이블의 고정 컬럼 목록 조회 (SystemFields + ItemField 기반)
|
||||
*/
|
||||
@@ -162,21 +226,26 @@ protected function fetchCategoryTree(?int $parentId = null)
|
||||
}
|
||||
|
||||
/**
|
||||
* 목록/검색
|
||||
* 목록/검색 (동적 테이블 라우팅)
|
||||
*
|
||||
* @param array $params 검색 파라미터 (item_type 필수)
|
||||
*/
|
||||
public function index(array $params): LengthAwarePaginator
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$size = (int) ($params['size'] ?? $params['per_page'] ?? 20);
|
||||
$q = trim((string) ($params['q'] ?? $params['search'] ?? ''));
|
||||
$categoryId = $params['category_id'] ?? null;
|
||||
$itemType = $params['item_type'] ?? null;
|
||||
$active = $params['active'] ?? null;
|
||||
|
||||
$query = Item::query()
|
||||
->with(['category:id,name', 'details'])
|
||||
->where('tenant_id', $tenantId);
|
||||
// item_type 필수 검증
|
||||
if (! $itemType) {
|
||||
throw new BadRequestHttpException(__('error.item_type_required'));
|
||||
}
|
||||
|
||||
// 동적 테이블 라우팅
|
||||
$query = $this->newQuery($itemType)
|
||||
->with(['category:id,name', 'details']);
|
||||
|
||||
// 검색어
|
||||
if ($q !== '') {
|
||||
@@ -192,11 +261,6 @@ public function index(array $params): LengthAwarePaginator
|
||||
$query->where('category_id', (int) $categoryId);
|
||||
}
|
||||
|
||||
// item_type 필터
|
||||
if ($itemType) {
|
||||
$query->where('item_type', strtoupper($itemType));
|
||||
}
|
||||
|
||||
// 활성 상태
|
||||
if ($active !== null && $active !== '') {
|
||||
$query->where('is_active', (bool) $active);
|
||||
@@ -220,13 +284,25 @@ public function index(array $params): LengthAwarePaginator
|
||||
}
|
||||
|
||||
/**
|
||||
* 생성
|
||||
* 생성 (동적 테이블 라우팅)
|
||||
*
|
||||
* @param array $data 생성 데이터 (item_type 필수)
|
||||
*/
|
||||
public function store(array $data): Item
|
||||
public function store(array $data): Model
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
// item_type 필수 검증
|
||||
$itemType = $data['item_type'] ?? null;
|
||||
if (! $itemType) {
|
||||
throw new BadRequestHttpException(__('error.item_type_required'));
|
||||
}
|
||||
|
||||
// 동적 모델 정보 조회
|
||||
$modelInfo = $this->getModelInfoByItemType($itemType);
|
||||
$modelClass = $modelInfo['model'];
|
||||
|
||||
// 동적 필드를 options에 병합
|
||||
$dynamicOptions = $this->extractDynamicOptions($data);
|
||||
if (! empty($dynamicOptions)) {
|
||||
@@ -239,8 +315,8 @@ public function store(array $data): Item
|
||||
$data['options'] = $this->normalizeOptions($data['options']);
|
||||
}
|
||||
|
||||
// tenant별 code 유니크 체크
|
||||
$dup = Item::query()
|
||||
// tenant별 code 유니크 체크 (동적 테이블)
|
||||
$dup = $modelClass::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('code', $data['code'])
|
||||
->exists();
|
||||
@@ -248,10 +324,10 @@ public function store(array $data): Item
|
||||
throw new BadRequestHttpException(__('error.duplicate_key'));
|
||||
}
|
||||
|
||||
// items 테이블 데이터
|
||||
// 테이블 데이터 준비
|
||||
$itemData = [
|
||||
'tenant_id' => $tenantId,
|
||||
'item_type' => strtoupper($data['item_type']),
|
||||
'item_type' => strtoupper($itemType),
|
||||
'code' => $data['code'],
|
||||
'name' => $data['name'],
|
||||
'unit' => $data['unit'] ?? null,
|
||||
@@ -264,27 +340,37 @@ public function store(array $data): Item
|
||||
'created_by' => $userId,
|
||||
];
|
||||
|
||||
$item = Item::create($itemData);
|
||||
$item = $modelClass::create($itemData);
|
||||
|
||||
// item_details 테이블 데이터
|
||||
$detailData = $this->extractDetailData($data);
|
||||
$detailData['item_id'] = $item->id;
|
||||
ItemDetail::create($detailData);
|
||||
// item_details 테이블 데이터 (items 테이블인 경우에만)
|
||||
if ($modelInfo['source_table'] === 'items') {
|
||||
$detailData = $this->extractDetailData($data);
|
||||
$detailData['item_id'] = $item->id;
|
||||
ItemDetail::create($detailData);
|
||||
$item->load('details');
|
||||
}
|
||||
|
||||
return $item->load('details');
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 단건 조회
|
||||
* 단건 조회 (동적 테이블 라우팅)
|
||||
*
|
||||
* @param int $id 품목 ID
|
||||
* @param string $itemType 품목 유형 (필수)
|
||||
*/
|
||||
public function show(int $id): Item
|
||||
public function show(int $id, string $itemType): Model
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
// 동적 테이블 라우팅
|
||||
$modelInfo = $this->getModelInfoByItemType($itemType);
|
||||
$query = $this->newQuery($itemType);
|
||||
|
||||
$item = Item::query()
|
||||
->with(['category:id,name', 'details'])
|
||||
->where('tenant_id', $tenantId)
|
||||
->find($id);
|
||||
// items 테이블인 경우 관계 로드
|
||||
if ($modelInfo['source_table'] === 'items') {
|
||||
$query->with(['category:id,name', 'details']);
|
||||
}
|
||||
|
||||
$item = $query->find($id);
|
||||
|
||||
if (! $item) {
|
||||
throw new BadRequestHttpException(__('error.not_found'));
|
||||
@@ -294,14 +380,27 @@ public function show(int $id): Item
|
||||
}
|
||||
|
||||
/**
|
||||
* 수정
|
||||
* 수정 (동적 테이블 라우팅)
|
||||
*
|
||||
* @param int $id 품목 ID
|
||||
* @param array $data 수정 데이터 (item_type 필수)
|
||||
*/
|
||||
public function update(int $id, array $data): Item
|
||||
public function update(int $id, array $data): Model
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
$item = Item::query()->where('tenant_id', $tenantId)->find($id);
|
||||
// item_type 필수 검증
|
||||
$itemType = $data['item_type'] ?? null;
|
||||
if (! $itemType) {
|
||||
throw new BadRequestHttpException(__('error.item_type_required'));
|
||||
}
|
||||
|
||||
// 동적 모델 정보 조회
|
||||
$modelInfo = $this->getModelInfoByItemType($itemType);
|
||||
$modelClass = $modelInfo['model'];
|
||||
|
||||
$item = $this->newQuery($itemType)->find($id);
|
||||
if (! $item) {
|
||||
throw new BadRequestHttpException(__('error.not_found'));
|
||||
}
|
||||
@@ -318,9 +417,9 @@ public function update(int $id, array $data): Item
|
||||
$data['options'] = $this->normalizeOptions($data['options']);
|
||||
}
|
||||
|
||||
// code 변경 시 중복 체크
|
||||
// code 변경 시 중복 체크 (동적 테이블)
|
||||
if (isset($data['code']) && $data['code'] !== $item->code) {
|
||||
$dup = Item::query()
|
||||
$dup = $modelClass::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('code', $data['code'])
|
||||
->exists();
|
||||
@@ -329,7 +428,7 @@ public function update(int $id, array $data): Item
|
||||
}
|
||||
}
|
||||
|
||||
// items 테이블 업데이트
|
||||
// 테이블 업데이트
|
||||
$itemData = array_intersect_key($data, array_flip([
|
||||
'item_type', 'code', 'name', 'unit', 'category_id',
|
||||
'bom', 'attributes', 'options', 'description', 'is_active',
|
||||
@@ -342,44 +441,60 @@ public function update(int $id, array $data): Item
|
||||
|
||||
$item->update($itemData);
|
||||
|
||||
// item_details 테이블 업데이트
|
||||
$detailData = $this->extractDetailData($data);
|
||||
if (! empty($detailData)) {
|
||||
$item->details()->updateOrCreate(
|
||||
['item_id' => $item->id],
|
||||
$detailData
|
||||
);
|
||||
// item_details 테이블 업데이트 (items 테이블인 경우에만)
|
||||
if ($modelInfo['source_table'] === 'items') {
|
||||
$detailData = $this->extractDetailData($data);
|
||||
if (! empty($detailData)) {
|
||||
$item->details()->updateOrCreate(
|
||||
['item_id' => $item->id],
|
||||
$detailData
|
||||
);
|
||||
}
|
||||
$item->load('details');
|
||||
}
|
||||
|
||||
return $item->load('details')->refresh();
|
||||
return $item->refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* 삭제 (soft delete)
|
||||
* 삭제 (동적 테이블 라우팅, soft delete)
|
||||
*
|
||||
* @param int $id 품목 ID
|
||||
* @param string $itemType 품목 유형 (필수)
|
||||
*/
|
||||
public function destroy(int $id): void
|
||||
public function destroy(int $id, string $itemType): void
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
$item = Item::query()->where('tenant_id', $tenantId)->find($id);
|
||||
// 동적 테이블 라우팅
|
||||
$item = $this->newQuery($itemType)->find($id);
|
||||
if (! $item) {
|
||||
throw new BadRequestHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
$item->deleted_by = $userId;
|
||||
$item->save();
|
||||
$item->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 간편 검색 (모달/드롭다운)
|
||||
* 간편 검색 (동적 테이블 라우팅, 모달/드롭다운)
|
||||
*
|
||||
* @param array $params 검색 파라미터 (item_type 필수)
|
||||
*/
|
||||
public function search(array $params)
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$q = trim((string) ($params['q'] ?? ''));
|
||||
$limit = (int) ($params['limit'] ?? 20);
|
||||
$itemType = $params['item_type'] ?? null;
|
||||
|
||||
$query = Item::query()->where('tenant_id', $tenantId);
|
||||
// item_type 필수 검증
|
||||
if (! $itemType) {
|
||||
throw new BadRequestHttpException(__('error.item_type_required'));
|
||||
}
|
||||
|
||||
// 동적 테이블 라우팅
|
||||
$query = $this->newQuery($itemType);
|
||||
|
||||
if ($q !== '') {
|
||||
$query->where(function ($w) use ($q) {
|
||||
@@ -388,24 +503,23 @@ public function search(array $params)
|
||||
});
|
||||
}
|
||||
|
||||
if ($itemType) {
|
||||
$query->where('item_type', strtoupper($itemType));
|
||||
}
|
||||
|
||||
return $query->orderBy('name')
|
||||
->limit($limit)
|
||||
->get(['id', 'code', 'name', 'item_type', 'category_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 활성/비활성 토글
|
||||
* 활성/비활성 토글 (동적 테이블 라우팅)
|
||||
*
|
||||
* @param int $id 품목 ID
|
||||
* @param string $itemType 품목 유형 (필수)
|
||||
*/
|
||||
public function toggle(int $id): array
|
||||
public function toggle(int $id, string $itemType): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
$item = Item::query()->where('tenant_id', $tenantId)->find($id);
|
||||
// 동적 테이블 라우팅
|
||||
$item = $this->newQuery($itemType)->find($id);
|
||||
if (! $item) {
|
||||
throw new BadRequestHttpException(__('error.not_found'));
|
||||
}
|
||||
@@ -417,6 +531,97 @@ public function toggle(int $id): array
|
||||
return ['id' => $item->id, 'is_active' => $item->is_active];
|
||||
}
|
||||
|
||||
/**
|
||||
* code 기반 품목 조회 (동적 테이블 라우팅, BOM 포함 옵션)
|
||||
*
|
||||
* @param string $code 품목 코드
|
||||
* @param string $itemType 품목 유형 (필수)
|
||||
* @param bool $includeBom BOM 포함 여부
|
||||
*/
|
||||
public function showByCode(string $code, string $itemType, bool $includeBom = false): Model
|
||||
{
|
||||
// 동적 테이블 라우팅
|
||||
$modelInfo = $this->getModelInfoByItemType($itemType);
|
||||
$query = $this->newQuery($itemType)->where('code', $code);
|
||||
|
||||
// items 테이블인 경우 관계 로드
|
||||
if ($modelInfo['source_table'] === 'items') {
|
||||
$query->with(['category:id,name', 'details']);
|
||||
}
|
||||
|
||||
$item = $query->first();
|
||||
|
||||
if (! $item) {
|
||||
throw new BadRequestHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
// BOM 포함 시 child items 로드 (items 테이블인 경우에만)
|
||||
if ($includeBom && ! empty($item->bom) && method_exists($item, 'loadBomChildren')) {
|
||||
$item->loadBomChildren();
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 일괄 삭제 (동적 테이블 라우팅, soft delete)
|
||||
*
|
||||
* @param array $ids 품목 ID 배열
|
||||
* @param string $itemType 품목 유형 (필수)
|
||||
*/
|
||||
public function batchDestroy(array $ids, string $itemType): int
|
||||
{
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
// 동적 테이블 라우팅
|
||||
$items = $this->newQuery($itemType)
|
||||
->whereIn('id', $ids)
|
||||
->get();
|
||||
|
||||
if ($items->isEmpty()) {
|
||||
throw new BadRequestHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
$deletedCount = 0;
|
||||
foreach ($items as $item) {
|
||||
$item->deleted_by = $userId;
|
||||
$item->save();
|
||||
$item->delete();
|
||||
$deletedCount++;
|
||||
}
|
||||
|
||||
return $deletedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 가격 정보 포함 조회 (동적 테이블 라우팅)
|
||||
*
|
||||
* @param int $id 품목 ID
|
||||
* @param string $itemType 품목 유형 (필수)
|
||||
* @param int|null $clientId 거래처 ID
|
||||
* @param string|null $priceDate 가격 기준일
|
||||
*/
|
||||
public function showWithPrice(int $id, string $itemType, ?int $clientId = null, ?string $priceDate = null): array
|
||||
{
|
||||
$item = $this->show($id, $itemType);
|
||||
$data = $item->toArray();
|
||||
|
||||
// PricingService로 가격 조회
|
||||
try {
|
||||
$pricingService = app(\App\Services\Pricing\PricingService::class);
|
||||
$itemTypeCode = in_array(strtoupper($itemType), ['FG', 'PT']) ? 'PRODUCT' : 'MATERIAL';
|
||||
|
||||
$data['prices'] = [
|
||||
'sale' => $pricingService->getPriceByType($itemTypeCode, $id, 'SALE', $clientId, $priceDate),
|
||||
'purchase' => $pricingService->getPriceByType($itemTypeCode, $id, 'PURCHASE', $clientId, $priceDate),
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
$data['prices'] = ['sale' => null, 'purchase' => null];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* item_details 데이터 추출
|
||||
*/
|
||||
@@ -437,4 +642,4 @@ private function extractDetailData(array $data): array
|
||||
|
||||
return array_intersect_key($data, array_flip($detailFields));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,9 @@
|
||||
'field_not_found' => '필드를 찾을 수 없습니다.',
|
||||
'field_key_reserved' => '":field_key"은(는) 시스템 예약어로 사용할 수 없습니다.',
|
||||
'bom_not_found' => 'BOM 항목을 찾을 수 없습니다.',
|
||||
'item_type_required' => '품목 유형(item_type)은 필수입니다.',
|
||||
'invalid_item_type' => '유효하지 않은 품목 유형입니다.',
|
||||
'invalid_source_table' => '품목 유형에 대한 소스 테이블이 설정되지 않았습니다.',
|
||||
|
||||
// 품목 관리 관련
|
||||
'item' => [
|
||||
|
||||
Reference in New Issue
Block a user