enabled : (bool) $def->is_core; return [ 'field_key' => $def->field_key, 'label' => $def->label, 'data_type' => $def->data_type, 'input_type' => $def->input_type, 'option_source' => $def->option_source, 'option_payload' => $def->option_payload, 'storage_area' => $def->storage_area, 'storage_key' => $def->storage_key, 'is_core' => (bool) $def->is_core, // tenant overlay 'enabled' => $enabled, 'required' => $s ? (bool) $s->required : false, 'sort_order' => $s ? (int) $s->sort_order : 0, 'option_group_id' => $s ? $s->option_group_id : null, 'code_group' => $s ? $s->code_group : null, ]; } /** * [GET] 효과값 목록 * - 코어(is_core=1)는 기본 enabled=true * - enabled=false는 제외 * - sort_order → field_key 순 정렬 */ public static function index(array $params = []) { $tenantId = self::tenantId(); if (! $tenantId) { return ['error' => '활성 테넌트가 없습니다.', 'code' => 400]; } $defs = SettingFieldDef::query() ->orderByDesc('is_core') ->orderBy('field_key') ->get() ->keyBy('field_key'); $settings = TenantFieldSetting::where('tenant_id', $tenantId) ->get() ->keyBy('field_key'); $rows = []; foreach ($defs as $key => $def) { $s = $settings->get($key); $effective = self::buildEffectiveRow($def, $s); if ($effective['enabled'] !== true) { continue; } $rows[] = $effective; } usort($rows, fn ($a, $b) => ($a['sort_order'] <=> $b['sort_order']) ?: strcmp($a['field_key'], $b['field_key']) ); return array_values($rows); } /** * [PUT] 대량 업서트 * params: * items: [{field_key, enabled?, required?, sort_order?, option_group_id?, code_group?}, ...] */ public static function bulkUpsert(array $params = []) { $tenantId = self::tenantId(); if (! $tenantId) { return ['error' => '활성 테넌트가 없습니다.', 'code' => 400]; } $v = Validator::make($params, [ 'items' => ['required', 'array', 'min:1'], 'items.*.field_key' => ['required', 'string', 'max:64', Rule::exists('setting_field_defs', 'field_key')], 'items.*.enabled' => ['nullable', 'boolean'], 'items.*.required' => ['nullable', 'boolean'], 'items.*.sort_order' => ['nullable', 'integer'], 'items.*.option_group_id' => ['nullable', 'integer'], 'items.*.code_group' => ['nullable', 'string', 'max:50'], ]); if ($v->fails()) { return ['error' => $v->errors()->first(), 'code' => 422]; } $payload = $v->validated(); try { DB::transaction(function () use ($payload, $tenantId) { foreach ($payload['items'] as $row) { // option_group_id 유효성(해당 테넌트 소유인지) if (! empty($row['option_group_id'])) { $exists = TenantOptionGroup::where('tenant_id', $tenantId) ->where('id', $row['option_group_id']) ->exists(); if (! $exists) { throw new \RuntimeException('option_group_id is invalid for this tenant'); } } TenantFieldSetting::updateOrCreate( ['tenant_id' => $tenantId, 'field_key' => $row['field_key']], [ 'enabled' => isset($row['enabled']) ? (int) $row['enabled'] : 0, 'required' => isset($row['required']) ? (int) $row['required'] : 0, 'sort_order' => isset($row['sort_order']) ? (int) $row['sort_order'] : 0, 'option_group_id' => $row['option_group_id'] ?? null, 'code_group' => $row['code_group'] ?? null, ] ); } }); } catch (\RuntimeException $e) { return ['error' => $e->getMessage(), 'code' => 422]; } catch (\Throwable $e) { return ['error' => '저장 중 오류가 발생했습니다.', 'code' => 500]; } return ['updated' => true]; } /** * [PATCH] 단건 수정 * - 응답은 FieldEffective 포맷으로 반환 */ public static function updateOne(string $fieldKey, array $params = []) { $tenantId = self::tenantId(); if (! $tenantId) { return ['error' => '활성 테넌트가 없습니다.', 'code' => 400]; } // 전역 key 존재 확인 $def = SettingFieldDef::where('field_key', $fieldKey)->first(); if (! $def) { return ['error' => 'field_key not found', 'code' => 404]; } $v = Validator::make($params, [ 'enabled' => ['nullable', 'boolean'], 'required' => ['nullable', 'boolean'], 'sort_order' => ['nullable', 'integer'], 'option_group_id' => ['nullable', 'integer'], 'code_group' => ['nullable', 'string', 'max:50'], ]); if ($v->fails()) { return ['error' => $v->errors()->first(), 'code' => 422]; } $data = $v->validated(); // option_group_id 테넌트 소유 검증 if (! empty($data['option_group_id'])) { $ok = TenantOptionGroup::where('tenant_id', $tenantId) ->where('id', $data['option_group_id']) ->exists(); if (! $ok) { return ['error' => 'option_group_id is invalid for this tenant', 'code' => 422]; } } try { $item = TenantFieldSetting::firstOrNew([ 'tenant_id' => $tenantId, 'field_key' => $fieldKey, ]); // 제공된 키만 반영 foreach (['enabled', 'required', 'sort_order', 'option_group_id', 'code_group'] as $k) { if (array_key_exists($k, $data)) { $item->{$k} = $data[$k]; } } $item->save(); // 최신 설정 다시 로드하여 효과값으로 리턴 $s = TenantFieldSetting::where('tenant_id', $tenantId) ->where('field_key', $fieldKey) ->first(); $effective = self::buildEffectiveRow($def, $s); return $effective; } catch (\Throwable $e) { return ['error' => '수정 중 오류가 발생했습니다.', 'code' => 500]; } } }