Files
sam-api/app/Services/TenantUserProfileService.php
kent abf4d09f1b fix: 프로필 이미지 업로드 시 profile_photo_path 필드 직접 처리
- updateMe() 메서드에서 profile_photo_path, display_name 고정 필드를
  effectiveFieldMap() 동적 필드 처리 전에 직접 저장하도록 수정
- 기존 로직은 SettingFieldDef 테이블 기반 동적 필드만 처리하여
  profile_photo_path가 무시되던 문제 해결

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 21:04:32 +09:00

230 lines
7.6 KiB
PHP

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