feat: [bending] 절곡품 전용 테이블 분리 API
- bending_items 전용 테이블 생성 (items.options → 정규 컬럼 승격) - bending_models 전용 테이블 생성 (가이드레일/케이스/하단마감재 통합) - bending_data JSON 통합 (별도 테이블 → bending_items.bending_data 컬럼) - bending_item_mappings 테이블 DROP (bending_items.code에 흡수) - BendingItemService/BendingCodeService → BendingItem 모델 전환 - GuiderailModelService component 이미지 자동 복사 - ItemsFileController bending_items/bending_models 폴백 지원 - Swagger 스키마 업데이트
This commit is contained in:
@@ -29,7 +29,7 @@ public function index(Request $request): JsonResponse
|
||||
$this->ensureContext($request);
|
||||
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$params = $request->only(['item_category', 'item_sep', 'model_UA', 'check_type', 'model_name', 'search', 'page', 'size']);
|
||||
$params = $request->only(['item_category', 'item_sep', 'model_UA', 'check_type', 'model_name', 'exit_direction', 'search', 'page', 'size']);
|
||||
$paginator = $this->service->list($params);
|
||||
$paginator->getCollection()->transform(fn ($item) => (new GuiderailModelResource($item))->resolve());
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Item\ItemFileUploadRequest;
|
||||
use App\Models\Commons\File;
|
||||
use App\Models\BendingItem;
|
||||
use App\Models\BendingModel;
|
||||
use App\Models\Items\Item;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -53,12 +55,13 @@ public function index(int $id, Request $request)
|
||||
$fieldKey = $request->input('field_key');
|
||||
|
||||
// 품목 존재 확인
|
||||
$this->getItemById($id, $tenantId);
|
||||
$owner = $this->getItemById($id, $tenantId);
|
||||
$docType = $this->getDocumentType($owner);
|
||||
|
||||
// 파일 조회
|
||||
$query = File::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('document_type', self::ITEM_GROUP_ID)
|
||||
->where('document_type', $docType)
|
||||
->where('document_id', $id);
|
||||
|
||||
// 특정 field_key만 조회
|
||||
@@ -94,7 +97,8 @@ public function upload(int $id, ItemFileUploadRequest $request)
|
||||
$existingFileId = $validated['file_id'] ?? null;
|
||||
|
||||
// 품목 존재 확인
|
||||
$this->getItemById($id, $tenantId);
|
||||
$owner = $this->getItemById($id, $tenantId);
|
||||
$docType = $this->getDocumentType($owner);
|
||||
|
||||
$replaced = false;
|
||||
|
||||
@@ -102,7 +106,7 @@ public function upload(int $id, ItemFileUploadRequest $request)
|
||||
if ($existingFileId) {
|
||||
$existingFile = File::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('document_type', self::ITEM_GROUP_ID)
|
||||
->where('document_type', $docType)
|
||||
->where('document_id', $id)
|
||||
->where('id', $existingFileId)
|
||||
->first();
|
||||
@@ -142,7 +146,7 @@ public function upload(int $id, ItemFileUploadRequest $request)
|
||||
'file_type' => $fileType, // 파일 형식 (image, document, excel, archive)
|
||||
'field_key' => $fieldKey, // 비즈니스 용도 (drawing, certificate 등)
|
||||
'document_id' => $id,
|
||||
'document_type' => self::ITEM_GROUP_ID, // group_id
|
||||
'document_type' => $docType,
|
||||
'is_temp' => false,
|
||||
'uploaded_by' => $userId,
|
||||
'created_by' => $userId,
|
||||
@@ -175,12 +179,13 @@ public function delete(int $id, mixed $fileId, Request $request)
|
||||
$tenantId = app('tenant_id');
|
||||
|
||||
// 품목 존재 확인
|
||||
$this->getItemById($id, $tenantId);
|
||||
$owner = $this->getItemById($id, $tenantId);
|
||||
$docType = $this->getDocumentType($owner);
|
||||
|
||||
// 파일 조회
|
||||
$file = File::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('document_type', self::ITEM_GROUP_ID)
|
||||
->where('document_type', $docType)
|
||||
->where('document_id', $id)
|
||||
->where('id', $fileId)
|
||||
->first();
|
||||
@@ -200,19 +205,51 @@ public function delete(int $id, mixed $fileId, Request $request)
|
||||
}
|
||||
|
||||
/**
|
||||
* ID로 품목 조회 (통합 items 테이블)
|
||||
* ID로 품목 조회 (items → bending_items 폴백)
|
||||
*/
|
||||
private function getItemById(int $id, int $tenantId): Item
|
||||
private function getItemById(int $id, int $tenantId): Item|BendingItem|BendingModel
|
||||
{
|
||||
$item = Item::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->find($id);
|
||||
|
||||
if (! $item) {
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
if ($item) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
return $item;
|
||||
// bending_items 폴백
|
||||
$bendingItem = BendingItem::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->find($id);
|
||||
|
||||
if ($bendingItem) {
|
||||
return $bendingItem;
|
||||
}
|
||||
|
||||
// bending_models 폴백
|
||||
$bendingModel = BendingModel::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->find($id);
|
||||
|
||||
if ($bendingModel) {
|
||||
return $bendingModel;
|
||||
}
|
||||
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 품목 유형에 따른 document_type 반환
|
||||
*/
|
||||
private function getDocumentType(Item|BendingItem|BendingModel $item): string
|
||||
{
|
||||
if ($item instanceof BendingItem) {
|
||||
return 'bending_item';
|
||||
}
|
||||
if ($item instanceof BendingModel) {
|
||||
return 'bending_model';
|
||||
}
|
||||
return self::ITEM_GROUP_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@ public function rules(): array
|
||||
'material' => 'nullable|string',
|
||||
'model_UA' => 'nullable|string|in:인정,비인정',
|
||||
'model_name' => 'nullable|string',
|
||||
'legacy_bending_num' => 'nullable|integer',
|
||||
'search' => 'nullable|string|max:100',
|
||||
'page' => 'nullable|integer|min:1',
|
||||
'size' => 'nullable|integer|min:1|max:200',
|
||||
|
||||
@@ -14,9 +14,10 @@ public function authorize(): bool
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'code' => 'required|string|max:100|unique:items,code',
|
||||
'name' => 'required|string|max:200',
|
||||
'unit' => 'nullable|string|max:20',
|
||||
'code' => [
|
||||
'required', 'string', 'max:50',
|
||||
\Illuminate\Validation\Rule::unique('bending_items', 'code')->where('tenant_id', request()->header('X-TENANT-ID', app()->bound('tenant_id') ? app('tenant_id') : 1)),
|
||||
],
|
||||
'item_name' => 'required|string|max:50',
|
||||
'item_sep' => 'required|in:스크린,철재',
|
||||
'item_bending' => 'required|string|max:50',
|
||||
|
||||
@@ -12,61 +12,61 @@ public function toArray(Request $request): array
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'code' => $this->code,
|
||||
'name' => $this->name,
|
||||
'item_type' => $this->item_type,
|
||||
'item_category' => $this->item_category,
|
||||
'unit' => $this->unit,
|
||||
'is_active' => $this->is_active,
|
||||
// options → 최상위로 노출
|
||||
'item_name' => $this->getOption('item_name'),
|
||||
'item_sep' => $this->getOption('item_sep'),
|
||||
'item_bending' => $this->getOption('item_bending'),
|
||||
'item_spec' => $this->getOption('item_spec'),
|
||||
'material' => $this->getOption('material'),
|
||||
'model_name' => $this->getOption('model_name'),
|
||||
'model_UA' => $this->getOption('model_UA'),
|
||||
'legacy_code' => $this->legacy_code,
|
||||
// 정규 컬럼 직접 참조
|
||||
'item_name' => $this->item_name,
|
||||
'item_sep' => $this->item_sep,
|
||||
'item_bending' => $this->item_bending,
|
||||
'item_spec' => $this->item_spec,
|
||||
'material' => $this->material,
|
||||
'model_name' => $this->model_name,
|
||||
'model_UA' => $this->model_UA,
|
||||
'rail_width' => $this->rail_width ? (int) $this->rail_width : null,
|
||||
// 케이스 전용
|
||||
'exit_direction' => $this->exit_direction,
|
||||
'front_bottom' => $this->front_bottom ? (int) $this->front_bottom : null,
|
||||
'box_width' => $this->box_width ? (int) $this->box_width : null,
|
||||
'box_height' => $this->box_height ? (int) $this->box_height : null,
|
||||
'inspection_door' => $this->inspection_door,
|
||||
// 원자재 길이
|
||||
'length_code' => $this->length_code,
|
||||
'length_mm' => $this->length_mm,
|
||||
// 전개도 (JSON 컬럼)
|
||||
'bendingData' => $this->bending_data,
|
||||
// 비정형 속성 (options)
|
||||
'search_keyword' => $this->getOption('search_keyword'),
|
||||
'rail_width' => $this->getOption('rail_width'),
|
||||
'registration_date' => $this->getOption('registration_date'),
|
||||
'author' => $this->getOption('author'),
|
||||
'memo' => $this->getOption('memo'),
|
||||
// 케이스 전용
|
||||
'exit_direction' => $this->getOption('exit_direction'),
|
||||
'front_bottom_width' => $this->getOption('front_bottom_width'),
|
||||
'box_width' => $this->getOption('box_width'),
|
||||
'box_height' => $this->getOption('box_height'),
|
||||
// 전개도
|
||||
'bendingData' => $this->getOption('bendingData'),
|
||||
// PREFIX 관련
|
||||
'prefix' => $this->getOption('prefix'),
|
||||
'length_code' => $this->getOption('length_code'),
|
||||
'length_mm' => $this->getOption('length_mm'),
|
||||
'registration_date' => $this->getOption('registration_date'),
|
||||
// 이미지
|
||||
'image_file_id' => $this->getImageFileId(),
|
||||
// 추적
|
||||
'legacy_bending_num' => $this->getOption('legacy_bending_num'),
|
||||
'legacy_bending_id' => $this->legacy_bending_id,
|
||||
'legacy_bending_num' => $this->legacy_bending_id, // MNG2 호환
|
||||
'modified_by' => $this->getOption('modified_by'),
|
||||
// MNG2 호환 (items 기반 필드명)
|
||||
'name' => $this->item_name,
|
||||
'front_bottom_width' => $this->front_bottom ? (int) $this->front_bottom : null,
|
||||
'item_type' => 'PT',
|
||||
'item_category' => 'BENDING',
|
||||
'unit' => 'EA',
|
||||
// 계산값
|
||||
'width_sum' => $this->getWidthSum(),
|
||||
'bend_count' => $this->getBendCount(),
|
||||
'width_sum' => $this->width_sum,
|
||||
'bend_count' => $this->bend_count,
|
||||
// 메타
|
||||
'is_active' => $this->is_active,
|
||||
'created_at' => $this->created_at?->format('Y-m-d H:i:s'),
|
||||
'updated_at' => $this->updated_at?->format('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
private function getWidthSum(): ?int
|
||||
private function getImageFileId(): ?int
|
||||
{
|
||||
$data = $this->getOption('bendingData', []);
|
||||
if (empty($data)) {
|
||||
return null;
|
||||
}
|
||||
$last = end($data);
|
||||
$file = $this->files()
|
||||
->where('field_key', 'bending_diagram')
|
||||
->orderByDesc('id')
|
||||
->first();
|
||||
|
||||
return isset($last['sum']) ? (int) $last['sum'] : null;
|
||||
}
|
||||
|
||||
private function getBendCount(): int
|
||||
{
|
||||
$data = $this->getOption('bendingData', []);
|
||||
|
||||
return count(array_filter($data, fn ($d) => ($d['rate'] ?? '') !== ''));
|
||||
return $file?->id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,9 @@ class GuiderailModelResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$components = $this->getOption('components', []);
|
||||
$materialSummary = $this->getOption('material_summary');
|
||||
$components = $this->components ?? [];
|
||||
$materialSummary = $this->material_summary;
|
||||
|
||||
// material_summary가 없으면 components에서 계산
|
||||
if (empty($materialSummary) && ! empty($components)) {
|
||||
$materialSummary = $this->calcMaterialSummary($components);
|
||||
}
|
||||
@@ -21,29 +20,34 @@ public function toArray(Request $request): array
|
||||
'id' => $this->id,
|
||||
'code' => $this->code,
|
||||
'name' => $this->name,
|
||||
'item_type' => $this->item_type,
|
||||
'item_category' => $this->item_category,
|
||||
'is_active' => $this->is_active,
|
||||
// 모델 속성
|
||||
'model_name' => $this->getOption('model_name'),
|
||||
'check_type' => $this->getOption('check_type'),
|
||||
'rail_width' => $this->getOption('rail_width'),
|
||||
'rail_length' => $this->getOption('rail_length'),
|
||||
'finishing_type' => $this->getOption('finishing_type'),
|
||||
'item_sep' => $this->getOption('item_sep'),
|
||||
'model_UA' => $this->getOption('model_UA'),
|
||||
'search_keyword' => $this->getOption('search_keyword'),
|
||||
'author' => $this->getOption('author'),
|
||||
// MNG2 호환
|
||||
'item_type' => 'FG',
|
||||
'item_category' => $this->model_type,
|
||||
// 모델 속성 (정규 컬럼)
|
||||
'model_name' => $this->model_name,
|
||||
'check_type' => $this->check_type,
|
||||
'rail_width' => $this->rail_width ? (int) $this->rail_width : null,
|
||||
'rail_length' => $this->rail_length ? (int) $this->rail_length : null,
|
||||
'finishing_type' => $this->finishing_type,
|
||||
'item_sep' => $this->item_sep,
|
||||
'model_UA' => $this->model_UA,
|
||||
'search_keyword' => $this->search_keyword,
|
||||
'author' => $this->author,
|
||||
'memo' => $this->getOption('memo'),
|
||||
'registration_date' => $this->getOption('registration_date'),
|
||||
// 케이스(SHUTTERBOX_MODEL) 전용
|
||||
'exit_direction' => $this->getOption('exit_direction'),
|
||||
'front_bottom_width' => $this->getOption('front_bottom_width'),
|
||||
'box_width' => $this->getOption('box_width'),
|
||||
'box_height' => $this->getOption('box_height'),
|
||||
// 하단마감재(BOTTOMBAR_MODEL) 전용
|
||||
'bar_width' => $this->getOption('bar_width'),
|
||||
'bar_height' => $this->getOption('bar_height'),
|
||||
'registration_date' => $this->registration_date?->format('Y-m-d'),
|
||||
// 케이스 전용
|
||||
'exit_direction' => $this->exit_direction,
|
||||
'front_bottom_width' => $this->front_bottom_width ? (int) $this->front_bottom_width : null,
|
||||
'box_width' => $this->box_width ? (int) $this->box_width : null,
|
||||
'box_height' => $this->box_height ? (int) $this->box_height : null,
|
||||
// 하단마감재 전용
|
||||
'bar_width' => $this->bar_width ? (int) $this->bar_width : null,
|
||||
'bar_height' => $this->bar_height ? (int) $this->bar_height : null,
|
||||
// 수정자
|
||||
'modified_by' => $this->getOption('modified_by'),
|
||||
// 이미지
|
||||
'image_file_id' => $this->getImageFileId(),
|
||||
// 부품 조합
|
||||
'components' => $components,
|
||||
'material_summary' => $materialSummary,
|
||||
@@ -54,18 +58,36 @@ public function toArray(Request $request): array
|
||||
];
|
||||
}
|
||||
|
||||
private function getImageFileId(): ?int
|
||||
{
|
||||
$file = \App\Models\Commons\File::where('document_id', $this->id)
|
||||
->where('document_type', 'bending_model')
|
||||
->where('field_key', 'assembly_image')
|
||||
->whereNull('deleted_at')
|
||||
->orderByDesc('id')
|
||||
->first();
|
||||
|
||||
if (! $file) {
|
||||
$file = $this->files()
|
||||
->where('field_key', 'bending_diagram')
|
||||
->orderByDesc('id')
|
||||
->first();
|
||||
}
|
||||
|
||||
return $file?->id;
|
||||
}
|
||||
|
||||
private function calcMaterialSummary(array $components): array
|
||||
{
|
||||
$summary = [];
|
||||
foreach ($components as $comp) {
|
||||
$material = $comp['material'] ?? null;
|
||||
$widthSum = $comp['width_sum'] ?? 0;
|
||||
$widthSum = $comp['widthsum'] ?? $comp['width_sum'] ?? 0;
|
||||
$qty = $comp['quantity'] ?? 1;
|
||||
if ($material && $widthSum) {
|
||||
$summary[$material] = ($summary[$material] ?? 0) + ($widthSum * $qty);
|
||||
}
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user