feat: clients.is_active CHAR(1) → TINYINT(1) Boolean 마이그레이션

- 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 변경
This commit is contained in:
2025-12-08 20:25:38 +09:00
parent 8d3ea4bb39
commit 5f200054ea
6 changed files with 60 additions and 97 deletions

View File

@@ -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',
];
}
}

View File

@@ -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',
];
}
}

View File

@@ -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)

View File

@@ -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();

View File

@@ -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() {}

View File

@@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
/**
* clients 테이블 is_active 컬럼을 CHAR(1) 'Y'/'N'에서 TINYINT(1) 0/1로 변환
*
* 프로젝트 표준:
* - DB: tinyint(1) with 0/1
* - Model: 'is_active' => '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");
}
};