= 0xAC00 && $code <= 0xD7A3) { // 한글 유니코드 범위 $index = floor(($code - 0xAC00) / 588); $initials .= self::INITIALS[$index]; } // 한글이 아닌 문자는 무시합니다. } $koreanInitials = ['ㄱ','ㄲ','ㄴ','ㄷ','ㄸ','ㄹ','ㅁ','ㅂ','ㅃ','ㅅ','ㅆ','ㅇ','ㅈ','ㅉ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ']; $englishInitials = ['G','KK','N','D','TT','R','M','B','BB','S','SS','O','J','JJ','CH','K','T','P','H']; $initials = strtr($initials, array_combine($koreanInitials, $englishInitials)); $initials = str_replace(' ', '', $initials); return strtoupper($initials); } /** * 10진수 숫자를 4자리 36진수 문자열로 변환합니다. * * @param int $number * @return string */ private function toBase36(int $number): string { $result = ''; $base = strlen($this->base36Chars); // **수정된 부분: 4자리 고정** for ($i = 0; $i < 4; $i++) { $remainder = $number % $base; $result = $this->base36Chars[$remainder] . $result; $number = floor($number / $base); } return $result; } /** * 36진수 문자열을 10진수 숫자로 변환합니다. * * @param string $base36String * @return int */ private function fromBase36(string $base36String): int { $number = 0; $base = strlen($this->base36Chars); $len = strlen($base36String); for ($i = 0; $i < $len; $i++) { $char = $base36String[$i]; $charValue = strpos($this->base36Chars, $char); // strpos가 false를 반환할 경우를 대비해 예외 처리 if ($charValue === false) { return 0; } $number += $charValue * ($base ** ($len - 1 - $i)); } return $number; } /** * 한글 업체명 기반으로 순환형 테넌트 코드를 생성합니다. * * @param string $tenantName * @return string */ public function generateTenantCode(string $tenantName): string { $cleanTenantName = str_replace(['주식회사', '(주)', '유한회사', '(유)', '유한책임회사', '(유)책', '합명회사', '(합)', '합자회사', '(합자)'], '', $tenantName); // 1. 전처리된 업체명에서 초성 약어 생성 $initials = $this->getInitials($cleanTenantName); // 2. 모든 테넌트들의 코드 중 가장 큰 순번을 찾습니다. $lastNumber = -1; $existingTenants = Tenant::all(); foreach ($existingTenants as $tenant) { // **수정된 부분: 코드 마지막 4자리를 순번으로 간주** if (strlen($tenant->code) >= 4) { $sequenceString = substr($tenant->code, -4); // 마지막 4자리가 36진수 문자열인지 확인 if (strspn($sequenceString, $this->base36Chars) === 4) { $sequence = $this->fromBase36($sequenceString); if ($sequence > $lastNumber) { $lastNumber = $sequence; } } } } // 3. 마지막 순번에 1을 더하고 36^4 (1,679,616)로 나눈 나머지로 순환하도록 유지합니다. // **수정된 부분: 46656 -> 1679616 으로 변경** $nextSequence = ($lastNumber + 1) % 1679616; // 4. 순번을 4자리 36진수 문자열로 포맷 // **수정된 부분: toBase36 호출** $formattedSequence = $this->toBase36($nextSequence); // 5. 초성 약어와 순번을 조합하여 최종 코드 생성 $code = $initials . $formattedSequence; return $code; } /** * 테넌트 목록 조회 (페이징) * * @param array $params [page, size, search 등] */ public static function getTenants(array $params = []) { $pageNo = isset($params['page']) ? (int)$params['page'] : 1; $pageSize = isset($params['size']) ? (int)$params['size'] : 10; $query = Tenant::query(); // (옵션) 간단 검색 예시: 회사명/코드 if (!empty($params['q'])) { $q = trim($params['q']); $query->where(function ($qq) use ($q) { $qq->where('company_name', 'like', "%{$q}%") ->orWhere('code', 'like', "%{$q}%") ->orWhere('email', 'like', "%{$q}%"); }); } // (옵션) 정렬 if (!empty($params['sort']) && in_array($params['sort'], ['company_name','code','created_at','updated_at'])) { $dir = (!empty($params['dir']) && in_array(strtolower($params['dir']), ['asc','desc'])) ? $params['dir'] : 'desc'; $query->orderBy($params['sort'], $dir); } else { $query->orderByDesc('id'); } $paginator = $query->paginate($pageSize, ['*'], 'page', $pageNo); return ApiResponse::response('result', $paginator); } /** * 단일 테넌트 조회 * - params.tenant_id 가 있으면 해당 테넌트 * - 없으면 현재 사용자 기본(is_default=1) 테넌트 * * @param array $params [tenant_id] */ public static function getTenant(array $params = []) { $tenantId = $params['tenant_id'] ?? app('tenant_id'); if (!$tenantId) { // 현재 사용자 기본 테넌트 조회 $apiUser = app('api_user'); $userTenant = UserTenant::where('user_id', $apiUser) ->where('is_default', 1) ->first(); if (!$userTenant) { return ApiResponse::error('활성(기본) 테넌트를 찾을 수 없습니다.', 404); } $tenantId = $userTenant->tenant_id; } // 필요한 컬럼만 선택 (원하면 조정) $query = Tenant::query() ->select('id','company_name','code','email','phone','address','business_num','corp_reg_no','ceo_name','homepage','fax','logo','admin_memo','options','created_at','updated_at') ->where('id', $tenantId); return ApiResponse::response('first', $query); } /** * 테넌트 등록 * * @param array $params */ public static function storeTenants(array $params = []) { $validator = Validator::make($params, [ 'company_name' => 'required|string|max:255', 'email' => 'nullable|email|max:100', 'phone' => 'nullable|string|max:30', 'address' => 'nullable|string|max:255', 'business_num' => 'nullable|string|max:30', 'corp_reg_no' => 'nullable|string|max:30', 'ceo_name' => 'nullable|string|max:100', 'homepage' => 'nullable|string|max:255', 'fax' => 'nullable|string|max:50', 'logo' => 'nullable|string|max:255', 'admin_memo' => 'nullable|string', 'options' => 'nullable', // JSON 문자열 저장이라면 'nullable|json' ]); if ($validator->fails()) { return ApiResponse::error($validator->errors()->first(), 400); } $payload = $validator->validated(); // TenantService 인스턴스를 가져옵니다. $tenantService = app(TenantService::class); // 업체명 기반으로 고유한 코드를 생성합니다. $code = $tenantService->generateTenantCode($payload['company_name']); // 생성된 코드를 페이로드에 추가합니다. $payload['code'] = $code; $tenant = Tenant::create($payload); // 성성된 테넌트를 나의 테넌트로 셋팅 $apiUser = app('api_user'); UserTenant::create([ 'user_id' => $apiUser, 'tenant_id' => $tenant->id, 'is_active' => 0, 'is_default' => 1, 'joined_at' => now(), ]); // 생성된 리소스를 그대로 반환 (목록 카드용 요약 원하면 컬럼 제한) return ApiResponse::response('result', $tenant); } /** * 테넌트 수정 * * @param array $params */ public static function updateTenant(array $params = []) { $validator = Validator::make($params, [ 'company_name' => 'sometimes|string|max:255', 'email' => 'sometimes|nullable|email|max:100', 'phone' => 'sometimes|nullable|string|max:30', 'address' => 'sometimes|nullable|string|max:255', 'business_num' => 'sometimes|nullable|string|max:30', 'corp_reg_no' => 'sometimes|nullable|string|max:30', 'ceo_name' => 'sometimes|nullable|string|max:100', 'homepage' => 'sometimes|nullable|string|max:255', 'fax' => 'sometimes|nullable|string|max:50', 'logo' => 'sometimes|nullable|string|max:255', 'admin_memo' => 'sometimes|nullable|string', 'options' => 'sometimes|nullable', // JSON 문자열이면 'sometimes|nullable|json' ]); if ($validator->fails()) { return ApiResponse::error($validator->errors()->first(), 400); } $payload = $validator->validated(); $tenantId = app('tenant_id') ?? null; unset($payload['tenant_id']); if (empty($payload)) { return ApiResponse::error('수정할 데이터가 없습니다.', 400); } $tenant = Tenant::find($tenantId); if (!$tenant) { return ApiResponse::error('테넌트를 찾을 수 없습니다.', 404); } $tenant->update($payload); return ApiResponse::response('result', $tenant->fresh()); } /** * 테넌트 삭제(탈퇴) — 소프트 삭제 가정 * * @param int $tenant_id */ public static function destroyTenant(array $params = []) { $tenantId = $params['tenant_id'] ?? app('tenant_id'); if (!$tenantId) { return ApiResponse::error('tenant_id가 필요합니다.', 400); } $tenant = Tenant::find($tenantId); if (!$tenant) { return ApiResponse::error('테넌트를 찾을 수 없습니다.', 404); } $tenant->delete(); // SoftDeletes 트레이트가 있으면 소프트 삭제 return ApiResponse::response('success'); } /** * 테넌트 복구 (소프트 삭제된 레코드 대상) * * @param array $params [tenant_id:int] */ public static function restoreTenant(array $params = []) { $tenantId = $params['tenant_id'] ?? app('tenant_id'); // 소프트 삭제 포함 조회 $tenant = Tenant::withTrashed()->find($tenantId); if (!$tenant) { return ApiResponse::error('테넌트를 찾을 수 없습니다.', 404); } if (is_null($tenant->deleted_at)) { // 이미 활성 상태 return ApiResponse::error('이미 활성화된 테넌트입니다.', 400); } $tenant->restore(); // 복구 결과를 data에 담고 싶으면 fresh() 후 필요한 필드만 반환 // return ApiResponse::response('result', $tenant->fresh()); return ApiResponse::response('success'); } }