2025-08-18 19:03:46 +09:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
|
|
|
|
|
use App\Models\Tenants\SettingFieldDef;
|
|
|
|
|
use App\Models\Tenants\TenantFieldSetting;
|
|
|
|
|
use App\Models\Tenants\TenantUserProfile;
|
2025-11-06 17:45:49 +09:00
|
|
|
use Illuminate\Support\Facades\DB;
|
2025-08-18 19:03:46 +09:00
|
|
|
|
|
|
|
|
class TenantUserProfileService
|
|
|
|
|
{
|
|
|
|
|
/** 활성 테넌트 ID */
|
|
|
|
|
protected static function tenantId(): ?int
|
|
|
|
|
{
|
|
|
|
|
return app('tenant_id');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 효과값 필드(Enabled) 맵: field_key => [def, setting] */
|
|
|
|
|
protected static function effectiveFieldMap(int $tenantId): array
|
|
|
|
|
{
|
2025-11-06 17:45:49 +09:00
|
|
|
$defs = SettingFieldDef::all()->keyBy('field_key');
|
2025-08-18 19:03:46 +09:00
|
|
|
$settings = TenantFieldSetting::where('tenant_id', $tenantId)->get()->keyBy('field_key');
|
|
|
|
|
|
|
|
|
|
$map = [];
|
|
|
|
|
foreach ($defs as $key => $def) {
|
|
|
|
|
$s = $settings->get($key);
|
2025-11-06 17:45:49 +09:00
|
|
|
$enabled = $s ? (bool) $s->enabled : (bool) $def->is_core;
|
|
|
|
|
if (! $enabled) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-08-18 19:03:46 +09:00
|
|
|
$map[$key] = ['def' => $def, 'setting' => $s];
|
|
|
|
|
}
|
2025-11-06 17:45:49 +09:00
|
|
|
|
2025-08-18 19:03:46 +09:00
|
|
|
return $map;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [GET] 프로필 목록 (페이징)
|
|
|
|
|
* params: per_page?, q?
|
|
|
|
|
*/
|
|
|
|
|
public static function index(array $params = [])
|
|
|
|
|
{
|
|
|
|
|
$tenantId = self::tenantId();
|
2025-11-06 17:45:49 +09:00
|
|
|
if (! $tenantId) {
|
2025-08-19 12:41:17 +09:00
|
|
|
return ['error' => '활성 테넌트가 없습니다.', 'code' => 400];
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
|
|
|
|
|
2025-11-06 17:45:49 +09:00
|
|
|
$per = (int) ($params['per_page'] ?? $params['size'] ?? 20);
|
|
|
|
|
$q = TenantUserProfile::where('tenant_id', $tenantId)
|
2025-08-18 19:03:46 +09:00
|
|
|
->with(['user:id,name,email']);
|
|
|
|
|
|
2025-11-06 17:45:49 +09:00
|
|
|
if (! empty($params['q'])) {
|
2025-08-18 19:03:46 +09:00
|
|
|
$kw = $params['q'];
|
|
|
|
|
$q->where(function ($w) use ($kw) {
|
|
|
|
|
$w->where('display_name', 'like', "%{$kw}%")
|
|
|
|
|
->orWhere('json_extra->employee_no', 'like', "%{$kw}%");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-06 17:45:49 +09:00
|
|
|
$page = isset($params['page']) ? (int) $params['page'] : null;
|
2025-08-18 19:03:46 +09:00
|
|
|
$data = $q->orderByDesc('id')->paginate($per, ['*'], 'page', $page);
|
|
|
|
|
|
2025-08-19 12:41:17 +09:00
|
|
|
return $data;
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [GET] 프로필 단건 조회
|
|
|
|
|
*/
|
|
|
|
|
public static function show(int $userId)
|
|
|
|
|
{
|
|
|
|
|
$tenantId = self::tenantId();
|
2025-11-06 17:45:49 +09:00
|
|
|
if (! $tenantId) {
|
2025-08-19 12:41:17 +09:00
|
|
|
return ['error' => '활성 테넌트가 없습니다.', 'code' => 400];
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
2025-11-06 17:45:49 +09:00
|
|
|
if (! $userId) {
|
2025-08-19 12:41:17 +09:00
|
|
|
return ['error' => 'userId가 올바르지 않습니다.', 'code' => 422];
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$item = TenantUserProfile::where('tenant_id', $tenantId)
|
|
|
|
|
->where('user_id', $userId)
|
|
|
|
|
->with(['user:id,name,email'])
|
|
|
|
|
->first();
|
|
|
|
|
|
2025-11-06 17:45:49 +09:00
|
|
|
if (! $item) {
|
2025-08-19 12:41:17 +09:00
|
|
|
return ['error' => '프로필을 찾을 수 없습니다.', 'code' => 404];
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
|
|
|
|
|
2025-08-19 12:41:17 +09:00
|
|
|
return $item->toArray();
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [PATCH] 관리자 수정: enabled 필드만 write-back
|
|
|
|
|
* - 입력 payload는 field_key: value 포맷(예: {"position":"manager","employee_no":"A-001"})
|
|
|
|
|
*/
|
|
|
|
|
public static function update(int $userId, array $params = [])
|
|
|
|
|
{
|
|
|
|
|
$tenantId = self::tenantId();
|
2025-11-06 17:45:49 +09:00
|
|
|
if (! $tenantId) {
|
2025-08-19 12:41:17 +09:00
|
|
|
return ['error' => '활성 테넌트가 없습니다.', 'code' => 400];
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
2025-11-06 17:45:49 +09:00
|
|
|
if (! $userId) {
|
2025-08-19 12:41:17 +09:00
|
|
|
return ['error' => 'userId가 올바르지 않습니다.', 'code' => 422];
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// (선택) 간단 유효성: 최소 1개 키 존재
|
2025-11-06 17:45:49 +09:00
|
|
|
if (empty($params) || ! is_array($params)) {
|
2025-08-19 12:41:17 +09:00
|
|
|
return ['error' => '수정할 항목이 없습니다.', 'code' => 422];
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
|
|
|
|
|
2025-11-06 17:45:49 +09:00
|
|
|
$fields = self::effectiveFieldMap($tenantId);
|
2025-08-18 19:03:46 +09:00
|
|
|
$profile = TenantUserProfile::firstOrCreate(['tenant_id' => $tenantId, 'user_id' => $userId]);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
DB::transaction(function () use ($fields, $profile, $params) {
|
|
|
|
|
foreach ($fields as $key => $meta) {
|
2025-11-06 17:45:49 +09:00
|
|
|
if (! array_key_exists($key, $params)) {
|
|
|
|
|
continue;
|
|
|
|
|
} // 입력이 없으면 스킵
|
2025-08-18 19:03:46 +09:00
|
|
|
|
2025-11-06 17:45:49 +09:00
|
|
|
$def = $meta['def'];
|
2025-08-18 19:03:46 +09:00
|
|
|
$value = $params[$key];
|
|
|
|
|
|
|
|
|
|
switch ($def->storage_area) {
|
|
|
|
|
case 'tenant_profile':
|
|
|
|
|
if ($def->storage_key) {
|
|
|
|
|
$profile->{$def->storage_key} = $value;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'tenant_profile_json':
|
|
|
|
|
$jsonKey = $def->storage_key ?: $key;
|
2025-11-06 17:45:49 +09:00
|
|
|
$extra = is_array($profile->json_extra) ? $profile->json_extra : (array) json_decode((string) $profile->json_extra, true);
|
2025-08-18 19:03:46 +09:00
|
|
|
$extra[$jsonKey] = $value;
|
|
|
|
|
$profile->json_extra = $extra;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'users':
|
|
|
|
|
// 정책상 기본 비권장: 무시
|
|
|
|
|
// 필요 시 UsersService 등으로 화이트리스트 업데이트 구현
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$profile->save();
|
|
|
|
|
});
|
|
|
|
|
} catch (\Throwable $e) {
|
2025-08-19 12:41:17 +09:00
|
|
|
return ['error' => '프로필 저장 중 오류가 발생했습니다.', 'code' => 500];
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$fresh = TenantUserProfile::where('tenant_id', $tenantId)
|
|
|
|
|
->where('user_id', $userId)
|
|
|
|
|
->with(['user:id,name,email'])
|
|
|
|
|
->first();
|
|
|
|
|
|
2025-08-19 12:41:17 +09:00
|
|
|
return $fresh ? $fresh->toArray() : null;
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [GET] 내 프로필 조회
|
|
|
|
|
*/
|
|
|
|
|
public static function me(array $params = [])
|
|
|
|
|
{
|
|
|
|
|
$tenantId = self::tenantId();
|
2025-11-06 17:45:49 +09:00
|
|
|
if (! $tenantId) {
|
2025-08-19 12:41:17 +09:00
|
|
|
return ['error' => '활성 테넌트가 없습니다.', 'code' => 400];
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
|
|
|
|
$userId = auth()->id();
|
2025-11-06 17:45:49 +09:00
|
|
|
if (! $userId) {
|
2025-08-19 12:41:17 +09:00
|
|
|
return ['error' => '인증 정보가 없습니다.', 'code' => 401];
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$item = TenantUserProfile::where('tenant_id', $tenantId)
|
|
|
|
|
->where('user_id', $userId)
|
|
|
|
|
->with(['user:id,name,email'])
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
// 없으면 null 반환(스펙에 따라 404로 바꾸려면 위와 동일 처리)
|
2025-08-19 12:41:17 +09:00
|
|
|
return $item ? $item->toArray() : null;
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [PATCH] 내 프로필 수정
|
2025-12-29 21:04:32 +09:00
|
|
|
* - 고정 필드(profile_photo_path, display_name)는 직접 처리
|
|
|
|
|
* - 동적 필드(enabled 필드)는 기존 update 로직으로 처리
|
2025-08-18 19:03:46 +09:00
|
|
|
*/
|
|
|
|
|
public static function updateMe(array $params = [])
|
|
|
|
|
{
|
|
|
|
|
$tenantId = self::tenantId();
|
2025-11-06 17:45:49 +09:00
|
|
|
if (! $tenantId) {
|
2025-08-19 12:41:17 +09:00
|
|
|
return ['error' => '활성 테넌트가 없습니다.', 'code' => 400];
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
|
|
|
|
$userId = auth()->id();
|
2025-11-06 17:45:49 +09:00
|
|
|
if (! $userId) {
|
2025-08-19 12:41:17 +09:00
|
|
|
return ['error' => '인증 정보가 없습니다.', 'code' => 401];
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
2025-11-06 17:45:49 +09:00
|
|
|
|
2025-12-29 21:04:32 +09:00
|
|
|
// 고정 필드 추출 (동적 필드 처리 전에 직접 저장)
|
|
|
|
|
$fixedFields = ['profile_photo_path', 'display_name'];
|
|
|
|
|
$fixedData = [];
|
|
|
|
|
foreach ($fixedFields as $field) {
|
|
|
|
|
if (array_key_exists($field, $params)) {
|
|
|
|
|
$fixedData[$field] = $params[$field];
|
|
|
|
|
unset($params[$field]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 고정 필드가 있으면 직접 업데이트
|
|
|
|
|
if (! empty($fixedData)) {
|
|
|
|
|
$profile = TenantUserProfile::firstOrCreate([
|
|
|
|
|
'tenant_id' => $tenantId,
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
]);
|
|
|
|
|
$profile->fill($fixedData);
|
|
|
|
|
$profile->save();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 동적 필드가 남아있으면 기존 로직으로 처리
|
|
|
|
|
if (! empty($params)) {
|
|
|
|
|
return self::update($userId, $params);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 고정 필드만 있었으면 새로고침된 프로필 반환
|
|
|
|
|
$fresh = TenantUserProfile::where('tenant_id', $tenantId)
|
|
|
|
|
->where('user_id', $userId)
|
|
|
|
|
->with(['user:id,name,email'])
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
return $fresh ? $fresh->toArray() : null;
|
2025-08-18 19:03:46 +09:00
|
|
|
}
|
|
|
|
|
}
|