From f583e947dbef9ac0dad64b68b4a4e7386255c0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sat, 21 Mar 2026 11:01:29 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[bending]=20=ED=92=88=EB=AA=A9=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20BD-XX-nn=20=EC=9E=90=EB=8F=99=20=EC=B1=84=EB=B2=88?= =?UTF-8?q?=20+=20code=20=EC=88=98=EC=A0=95=20=EB=B6=88=EA=B0=80=20+=20pre?= =?UTF-8?q?fixes=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Api/V1/BendingItemController.php | 13 ++++ app/Services/BendingItemService.php | 68 ++++++++++++++++++- routes/api/v1/production.php | 1 + 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/V1/BendingItemController.php b/app/Http/Controllers/Api/V1/BendingItemController.php index ec948b37..2261768d 100644 --- a/app/Http/Controllers/Api/V1/BendingItemController.php +++ b/app/Http/Controllers/Api/V1/BendingItemController.php @@ -53,6 +53,19 @@ public function filters(Request $request): JsonResponse ); } + public function prefixes(Request $request): JsonResponse + { + $this->ensureContext($request); + + return ApiResponse::handle( + fn () => [ + 'prefixes' => $this->service->prefixes(), + 'labels' => BendingItemService::PREFIX_LABELS, + ], + __('message.fetched') + ); + } + public function show(Request $request, int $id): JsonResponse { $this->ensureContext($request); diff --git a/app/Services/BendingItemService.php b/app/Services/BendingItemService.php index 92bf00ce..99c7dca4 100644 --- a/app/Services/BendingItemService.php +++ b/app/Services/BendingItemService.php @@ -45,9 +45,23 @@ public function find(int $id): BendingItem public function create(array $data): BendingItem { + $code = $data['code'] ?? ''; + + // BD-XX 접두사가 있으면 순번 자동 채번 + if (preg_match('/^BD-([A-Z]{2})$/i', $code, $m)) { + $code = $this->generateCode($m[1]); + } elseif (preg_match('/^BD-([A-Z]{2})-\d+$/i', $code)) { + // BD-XX-nn 완전한 코드 — 그대로 사용 (중복 검증) + $exists = BendingItem::withoutGlobalScopes()->where('code', $code)->exists(); + if ($exists) { + $prefix = substr($code, 3, 2); + $code = $this->generateCode($prefix); + } + } + return BendingItem::create([ 'tenant_id' => $this->tenantId(), - 'code' => $data['code'], + 'code' => $code, 'legacy_code' => $data['legacy_code'] ?? null, 'legacy_bending_id' => $data['legacy_bending_id'] ?? null, 'item_name' => $data['item_name'] ?? $data['name'] ?? '', @@ -76,8 +90,9 @@ public function update(int $id, array $data): BendingItem { $item = BendingItem::findOrFail($id); + // code는 수정 불가 (고유 품목코드) $columns = [ - 'code', 'item_name', 'item_sep', 'item_bending', + 'item_name', 'item_sep', 'item_bending', 'material', 'item_spec', 'model_name', 'model_UA', 'rail_width', 'exit_direction', 'box_width', 'box_height', 'front_bottom', 'inspection_door', 'length_code', 'length_mm', @@ -134,4 +149,53 @@ private function buildOptions(array $data): ?array 'search_keyword', 'registration_date', 'author', 'memo', 'parent_num', 'modified_by', ]; + + /** + * BD-{prefix}-{순번} 코드 자동 채번 + */ + private function generateCode(string $prefix): string + { + $prefix = strtoupper($prefix); + + $lastCode = BendingItem::withoutGlobalScopes() + ->where('code', 'like', "BD-{$prefix}-%") + ->orderByRaw('CAST(SUBSTRING(code, ?) AS UNSIGNED) DESC', [strlen("BD-{$prefix}-") + 1]) + ->value('code'); + + $nextSeq = 1; + if ($lastCode && preg_match('/BD-[A-Z]{2}-(\d+)$/', $lastCode, $m)) { + $nextSeq = (int) $m[1] + 1; + } + + $pad = $nextSeq >= 100 ? 3 : 2; + + return 'BD-'.$prefix.'-'.str_pad($nextSeq, $pad, '0', STR_PAD_LEFT); + } + + /** + * 사용 가능한 분류코드 접두사 목록 + */ + public function prefixes(): array + { + return BendingItem::withoutGlobalScopes() + ->where('code', 'like', 'BD-%') + ->selectRaw('SUBSTRING(code, 4, 2) as prefix, COUNT(*) as cnt') + ->groupBy('prefix') + ->orderBy('prefix') + ->pluck('cnt', 'prefix') + ->toArray(); + } + + /** 분류코드 접두사 정의 */ + public const PREFIX_LABELS = [ + 'RS' => '가이드레일 SUS마감재', 'RM' => '가이드레일 본체/보강', 'RC' => '가이드레일 C형', + 'RD' => '가이드레일 D형', 'RE' => '가이드레일 측면마감', 'RT' => '가이드레일 절단판', + 'RH' => '가이드레일 뒷보강', 'RN' => '가이드레일 비인정', + 'CP' => '케이스 밑면판/점검구', 'CF' => '케이스 전면판', 'CB' => '케이스 후면코너/후면부', + 'CL' => '케이스 린텔', 'CX' => '케이스 상부덮개', + 'BS' => '하단마감재 SUS', 'BE' => '하단마감재 EGI', 'BH' => '하단마감재 보강평철', + 'TS' => '철재 하단마감재 SUS', 'TE' => '철재 하단마감재 EGI', + 'XE' => '마구리', 'LE' => 'L-BAR', + 'ZP' => '특수 밑면/점검구', 'ZF' => '특수 전면판', 'ZB' => '특수 후면', + ]; } diff --git a/routes/api/v1/production.php b/routes/api/v1/production.php index cba64d32..6f589afd 100644 --- a/routes/api/v1/production.php +++ b/routes/api/v1/production.php @@ -139,6 +139,7 @@ Route::prefix('bending-items')->group(function () { Route::get('', [BendingItemController::class, 'index'])->name('v1.bending-items.index'); Route::get('/filters', [BendingItemController::class, 'filters'])->name('v1.bending-items.filters'); + Route::get('/prefixes', [BendingItemController::class, 'prefixes'])->name('v1.bending-items.prefixes'); Route::post('', [BendingItemController::class, 'store'])->name('v1.bending-items.store'); Route::get('/{id}', [BendingItemController::class, 'show'])->whereNumber('id')->name('v1.bending-items.show'); Route::put('/{id}', [BendingItemController::class, 'update'])->whereNumber('id')->name('v1.bending-items.update');