213 lines
7.5 KiB
PHP
213 lines
7.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Tenants\SettingFieldDef;
|
|
use App\Models\Tenants\TenantFieldSetting;
|
|
use App\Models\Tenants\TenantOptionGroup;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Validation\Rule;
|
|
|
|
class TenantFieldSettingService
|
|
{
|
|
/** 활성 테넌트 ID 조회 (공통) */
|
|
protected static function tenantId(): ?int
|
|
{
|
|
return app('tenant_id'); // 미들웨어에서 주입된 테넌트 ID 가정
|
|
}
|
|
|
|
/** 효과값 1개 빌드: 전역정의 + 테넌트설정 merge */
|
|
protected static function buildEffectiveRow(SettingFieldDef $def, ?TenantFieldSetting $s): array
|
|
{
|
|
$enabled = $s ? (bool) $s->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];
|
|
}
|
|
}
|
|
}
|