From 5f200054ea77cd9e537bec3f327a9d4c831371c6 Mon Sep 17 00:00:00 2001 From: hskwon Date: Mon, 8 Dec 2025 20:25:38 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20clients.is=5Factive=20CHAR(1)=20?= =?UTF-8?q?=E2=86=92=20TINYINT(1)=20Boolean=20=EB=A7=88=EC=9D=B4=EA=B7=B8?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DB: CHAR(1) 'Y'/'N' → TINYINT(1) 0/1 컬럼 타입 변경 - Model: boolean 캐스트 추가, scopeActive() 수정 - Service: toggle(), index() Boolean 로직 적용 - FormRequest: 'in:Y,N' → 'boolean' 검증 규칙 변경 - Swagger: is_active type string → boolean 변경 --- .../Requests/Client/ClientStoreRequest.php | 2 +- .../Requests/Client/ClientUpdateRequest.php | 2 +- app/Models/Orders/Client.php | 2 +- app/Services/ClientService.php | 96 ++----------------- app/Swagger/v1/ClientApi.php | 13 ++- ...3_convert_clients_is_active_to_boolean.php | 42 ++++++++ 6 files changed, 60 insertions(+), 97 deletions(-) create mode 100644 database/migrations/2025_12_08_200913_convert_clients_is_active_to_boolean.php diff --git a/app/Http/Requests/Client/ClientStoreRequest.php b/app/Http/Requests/Client/ClientStoreRequest.php index 9b0dde2..ac57fbe 100644 --- a/app/Http/Requests/Client/ClientStoreRequest.php +++ b/app/Http/Requests/Client/ClientStoreRequest.php @@ -51,7 +51,7 @@ public function rules(): array 'bad_debt_progress' => 'nullable|in:협의중,소송중,회수완료,대손처리', // 기타 'memo' => 'nullable|string', - 'is_active' => 'nullable|in:Y,N', + 'is_active' => 'nullable|boolean', ]; } } diff --git a/app/Http/Requests/Client/ClientUpdateRequest.php b/app/Http/Requests/Client/ClientUpdateRequest.php index 5030bfa..115deec 100644 --- a/app/Http/Requests/Client/ClientUpdateRequest.php +++ b/app/Http/Requests/Client/ClientUpdateRequest.php @@ -51,7 +51,7 @@ public function rules(): array 'bad_debt_progress' => 'nullable|in:협의중,소송중,회수완료,대손처리', // 기타 'memo' => 'nullable|string', - 'is_active' => 'nullable|in:Y,N', + 'is_active' => 'nullable|boolean', ]; } } diff --git a/app/Models/Orders/Client.php b/app/Models/Orders/Client.php index aae063b..5cba13a 100644 --- a/app/Models/Orders/Client.php +++ b/app/Models/Orders/Client.php @@ -76,7 +76,7 @@ public function orders() // 스코프 public function scopeActive($query) { - return $query->where('is_active', 'Y'); + return $query->where('is_active', true); } public function scopeCode($query, string $code) diff --git a/app/Services/ClientService.php b/app/Services/ClientService.php index 36c41e3..cbaa79a 100644 --- a/app/Services/ClientService.php +++ b/app/Services/ClientService.php @@ -3,7 +3,6 @@ namespace App\Services; use App\Models\Orders\Client; -use Illuminate\Support\Facades\Validator; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -30,7 +29,7 @@ public function index(array $params) } if ($onlyActive !== null) { - $query->where('is_active', $onlyActive ? 'Y' : 'N'); + $query->where('is_active', (bool) $onlyActive); } $query->orderBy('client_code')->orderBy('id'); @@ -51,49 +50,9 @@ public function show(int $id) } /** 생성 */ - public function store(array $params) + public function store(array $data) { $tenantId = $this->tenantId(); - $uid = $this->apiUserId(); - - $v = Validator::make($params, [ - 'client_code' => 'required|string|max:50', - 'name' => 'required|string|max:100', - 'client_type' => 'nullable|in:매입,매출,매입매출', - 'contact_person' => 'nullable|string|max:50', - 'phone' => 'nullable|string|max:30', - 'mobile' => 'nullable|string|max:20', - 'fax' => 'nullable|string|max:20', - 'email' => 'nullable|email|max:80', - 'address' => 'nullable|string|max:255', - 'manager_name' => 'nullable|string|max:50', - 'manager_tel' => 'nullable|string|max:20', - 'system_manager' => 'nullable|string|max:50', - 'account_id' => 'nullable|string|max:50', - 'account_password' => 'nullable|string|max:255', - 'purchase_payment_day' => 'nullable|string|max:20', - 'sales_payment_day' => 'nullable|string|max:20', - 'business_no' => 'nullable|string|max:20', - 'business_type' => 'nullable|string|max:50', - 'business_item' => 'nullable|string|max:100', - 'tax_agreement' => 'nullable|boolean', - 'tax_amount' => 'nullable|numeric|min:0', - 'tax_start_date' => 'nullable|date', - 'tax_end_date' => 'nullable|date', - 'bad_debt' => 'nullable|boolean', - 'bad_debt_amount' => 'nullable|numeric|min:0', - 'bad_debt_receive_date' => 'nullable|date', - 'bad_debt_end_date' => 'nullable|date', - 'bad_debt_progress' => 'nullable|in:협의중,소송중,회수완료,대손처리', - 'memo' => 'nullable|string', - 'is_active' => 'nullable|in:Y,N', - ]); - - if ($v->fails()) { - throw new BadRequestHttpException($v->errors()->first()); - } - - $data = $v->validated(); // client_code 중복 검사 $exists = Client::where('tenant_id', $tenantId) @@ -104,72 +63,32 @@ public function store(array $params) } $data['tenant_id'] = $tenantId; - $data['is_active'] = $data['is_active'] ?? 'Y'; + $data['is_active'] = $data['is_active'] ?? true; return Client::create($data); } /** 수정 */ - public function update(int $id, array $params) + public function update(int $id, array $data) { $tenantId = $this->tenantId(); - $uid = $this->apiUserId(); $client = Client::where('tenant_id', $tenantId)->find($id); if (! $client) { throw new NotFoundHttpException(__('error.not_found')); } - $v = Validator::make($params, [ - 'client_code' => 'sometimes|required|string|max:50', - 'name' => 'sometimes|required|string|max:100', - 'client_type' => 'nullable|in:매입,매출,매입매출', - 'contact_person' => 'nullable|string|max:50', - 'phone' => 'nullable|string|max:30', - 'mobile' => 'nullable|string|max:20', - 'fax' => 'nullable|string|max:20', - 'email' => 'nullable|email|max:80', - 'address' => 'nullable|string|max:255', - 'manager_name' => 'nullable|string|max:50', - 'manager_tel' => 'nullable|string|max:20', - 'system_manager' => 'nullable|string|max:50', - 'account_id' => 'nullable|string|max:50', - 'account_password' => 'nullable|string|max:255', - 'purchase_payment_day' => 'nullable|string|max:20', - 'sales_payment_day' => 'nullable|string|max:20', - 'business_no' => 'nullable|string|max:20', - 'business_type' => 'nullable|string|max:50', - 'business_item' => 'nullable|string|max:100', - 'tax_agreement' => 'nullable|boolean', - 'tax_amount' => 'nullable|numeric|min:0', - 'tax_start_date' => 'nullable|date', - 'tax_end_date' => 'nullable|date', - 'bad_debt' => 'nullable|boolean', - 'bad_debt_amount' => 'nullable|numeric|min:0', - 'bad_debt_receive_date' => 'nullable|date', - 'bad_debt_end_date' => 'nullable|date', - 'bad_debt_progress' => 'nullable|in:협의중,소송중,회수완료,대손처리', - 'memo' => 'nullable|string', - 'is_active' => 'nullable|in:Y,N', - ]); - - if ($v->fails()) { - throw new BadRequestHttpException($v->errors()->first()); - } - - $payload = $v->validated(); - // client_code 변경 시 중복 검사 - if (isset($payload['client_code']) && $payload['client_code'] !== $client->client_code) { + if (isset($data['client_code']) && $data['client_code'] !== $client->client_code) { $exists = Client::where('tenant_id', $tenantId) - ->where('client_code', $payload['client_code']) + ->where('client_code', $data['client_code']) ->exists(); if ($exists) { throw new BadRequestHttpException(__('error.duplicate_code')); } } - $client->update($payload); + $client->update($data); return $client->refresh(); } @@ -203,7 +122,6 @@ public function toggle(int $id) throw new NotFoundHttpException(__('error.not_found')); } - // Model에서 is_active가 boolean으로 캐스팅되므로 boolean으로 토글 $client->is_active = ! $client->is_active; $client->save(); diff --git a/app/Swagger/v1/ClientApi.php b/app/Swagger/v1/ClientApi.php index 290a5c8..4073d48 100644 --- a/app/Swagger/v1/ClientApi.php +++ b/app/Swagger/v1/ClientApi.php @@ -41,7 +41,7 @@ * @OA\Property(property="bad_debt_end_date", type="string", format="date", nullable=true, example="2025-06-01", description="채권 만료일"), * @OA\Property(property="bad_debt_progress", type="string", enum={"협의중", "소송중", "회수완료", "대손처리"}, nullable=true, description="진행 상태"), * @OA\Property(property="memo", type="string", nullable=true, example="특이사항 메모", description="메모"), - * @OA\Property(property="is_active", type="string", enum={"Y", "N"}, example="Y"), + * @OA\Property(property="is_active", type="boolean", example=true, description="활성 여부"), * @OA\Property(property="created_at", type="string", example="2025-10-01 12:00:00"), * @OA\Property(property="updated_at", type="string", example="2025-10-01 12:00:00") * ) @@ -116,7 +116,7 @@ * @OA\Property(property="bad_debt_end_date", type="string", format="date", nullable=true, example="2025-06-01", description="채권 만료일"), * @OA\Property(property="bad_debt_progress", type="string", enum={"협의중", "소송중", "회수완료", "대손처리"}, nullable=true, description="진행 상태"), * @OA\Property(property="memo", type="string", nullable=true, example="특이사항 메모", description="메모"), - * @OA\Property(property="is_active", type="string", enum={"Y", "N"}, example="Y") + * @OA\Property(property="is_active", type="boolean", example=true, description="활성 여부") * ) * * @OA\Schema( @@ -153,7 +153,7 @@ * @OA\Property(property="bad_debt_end_date", type="string", format="date", nullable=true, description="채권 만료일"), * @OA\Property(property="bad_debt_progress", type="string", enum={"협의중", "소송중", "회수완료", "대손처리"}, nullable=true, description="진행 상태"), * @OA\Property(property="memo", type="string", nullable=true, description="메모"), - * @OA\Property(property="is_active", type="string", enum={"Y", "N"}) + * @OA\Property(property="is_active", type="boolean", description="활성 여부") * ) */ class ClientApi @@ -257,12 +257,15 @@ public function update() {} * @OA\Delete( * path="/api/v1/clients/{id}", * tags={"Client"}, - * summary="거래처 삭제(soft)", + * summary="거래처 삭제", + * description="주문이 존재하는 거래처는 삭제할 수 없습니다.", * security={{"ApiKeyAuth":{}},{"BearerAuth":{}}}, * * @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")), * - * @OA\Response(response=200, description="삭제 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")) + * @OA\Response(response=200, description="삭제 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")), + * @OA\Response(response=400, description="주문 존재로 삭제 불가", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")), + * @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")) * ) */ public function destroy() {} diff --git a/database/migrations/2025_12_08_200913_convert_clients_is_active_to_boolean.php b/database/migrations/2025_12_08_200913_convert_clients_is_active_to_boolean.php new file mode 100644 index 0000000..217310a --- /dev/null +++ b/database/migrations/2025_12_08_200913_convert_clients_is_active_to_boolean.php @@ -0,0 +1,42 @@ + 'boolean' cast + * - API: true/false + */ +return new class extends Migration +{ + public function up(): void + { + // 1. 'Y' → 1, 'N' → 0 으로 데이터 변환 (임시로 숫자 문자열) + DB::statement("UPDATE clients SET is_active = '1' WHERE is_active = 'Y'"); + DB::statement("UPDATE clients SET is_active = '0' WHERE is_active = 'N' OR is_active = '' OR is_active IS NULL"); + + // 2. 컬럼 타입 변경: CHAR(1) → TINYINT(1) + DB::statement(" + ALTER TABLE clients + MODIFY COLUMN is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '활성여부(1=활성,0=비활성)' + "); + } + + public function down(): void + { + // 1. 컬럼 타입 복구: TINYINT(1) → CHAR(1) + DB::statement(" + ALTER TABLE clients + MODIFY COLUMN is_active CHAR(1) NOT NULL DEFAULT 'Y' COMMENT '활성여부(Y/N)' + "); + + // 2. 1 → 'Y', 0 → 'N' 으로 데이터 복구 + DB::statement("UPDATE clients SET is_active = 'Y' WHERE is_active = '1' OR is_active = 1"); + DB::statement("UPDATE clients SET is_active = 'N' WHERE is_active = '0' OR is_active = 0"); + } +}; \ No newline at end of file