tenantId(); $size = (int) ($params['size'] ?? 20); return CategoryTemplate::query() ->where('tenant_id', $tenantId) ->where('category_id', $categoryId) ->orderByDesc('version_no') ->paginate($size); } public function store(int $categoryId, array $data) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $this->assertCategoryExists($tenantId, $categoryId); $v = Validator::make($data, [ 'version_no' => 'required|integer|min:1', 'template_json' => 'required|json', 'applied_at' => 'required|date', 'remarks' => 'nullable|string|max:255', ]); $payload = $v->validate(); $dup = CategoryTemplate::query() ->where('tenant_id', $tenantId) ->where('category_id', $categoryId) ->where('version_no', $payload['version_no']) ->exists(); if ($dup) { throw new BadRequestHttpException(__('error.duplicate_key')); // version_no 중복 } $payload['tenant_id'] = $tenantId; $payload['category_id'] = $categoryId; $payload['created_by'] = $userId; return CategoryTemplate::create($payload); } public function show(int $tplId) { $tenantId = $this->tenantId(); $tpl = CategoryTemplate::query() ->where('tenant_id', $tenantId) ->find($tplId); if (! $tpl) { throw new BadRequestHttpException(__('error.not_found')); } return $tpl; } public function update(int $tplId, array $data) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $tpl = CategoryTemplate::query() ->where('tenant_id', $tenantId) ->find($tplId); if (! $tpl) { throw new BadRequestHttpException(__('error.not_found')); } $v = Validator::make($data, [ 'template_json' => 'nullable|json', 'applied_at' => 'nullable|date', 'remarks' => 'nullable|string|max:255', ]); $payload = $v->validate(); $payload['updated_by'] = $userId; $tpl->update($payload); return $tpl->refresh(); } public function destroy(int $tplId): void { $tenantId = $this->tenantId(); $tpl = CategoryTemplate::query() ->where('tenant_id', $tenantId) ->find($tplId); if (! $tpl) { throw new BadRequestHttpException(__('error.not_found')); } $tpl->delete(); } /** * 적용 정책: * - categories.active_template_version (또는 별도 맵 테이블)에 version_no 반영 * - (옵션) template_json 기반으로 category_fields를 실제로 갱신하려면 여기서 동기화 */ public function apply(int $categoryId, int $tplId): void { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $tpl = CategoryTemplate::query() ->where('tenant_id', $tenantId) ->where('category_id', $categoryId) ->find($tplId); if (! $tpl) { throw new BadRequestHttpException(__('error.not_found')); } DB::transaction(function () { // 1) categories 테이블에 활성 버전 반영(컬럼이 있다면) // Category::where('tenant_id', $tenantId)->where('id', $categoryId)->update([ // 'active_template_version' => $tpl->version_no, // 'updated_by' => $userId, // ]); // 2) (선택) template_json → category_fields 동기화 // - 추가/수정/삭제 전략은 운영정책에 맞게 구현 // - 여기서는 예시로 "fields" 배열만 처리 // $snapshot = json_decode($tpl->template_json, true); // foreach (($snapshot['fields'] ?? []) as $i => $f) { // // key, name, type, required, default, options 매핑 ... // } }); } public function preview(int $categoryId, int $tplId): array { $tenantId = $this->tenantId(); $tpl = CategoryTemplate::query() ->where('tenant_id', $tenantId) ->where('category_id', $categoryId) ->find($tplId); if (! $tpl) { throw new BadRequestHttpException(__('error.not_found')); } $json = json_decode($tpl->template_json, true); if (! is_array($json)) { throw new BadRequestHttpException(__('error.invalid_payload')); } // 프론트 렌더링 편의 구조로 가공 가능 return $json; } public function diff(int $categoryId, int $a, int $b): array { $tenantId = $this->tenantId(); $aTpl = CategoryTemplate::query() ->where('tenant_id', $tenantId)->where('category_id', $categoryId) ->where('version_no', $a)->first(); $bTpl = CategoryTemplate::query() ->where('tenant_id', $tenantId)->where('category_id', $categoryId) ->where('version_no', $b)->first(); if (! $aTpl || ! $bTpl) { throw new BadRequestHttpException(__('error.not_found')); } $aj = json_decode($aTpl->template_json, true) ?: []; $bj = json_decode($bTpl->template_json, true) ?: []; // 아주 단순한 diff 예시 (fields 키만 비교) $aKeys = collect($aj['fields'] ?? [])->pluck('key')->all(); $bKeys = collect($bj['fields'] ?? [])->pluck('key')->all(); return [ 'added' => array_values(array_diff($bKeys, $aKeys)), 'removed' => array_values(array_diff($aKeys, $bKeys)), // 변경(diff in detail)은 정책에 맞게 확장 ]; } private function assertCategoryExists(int $tenantId, int $categoryId): void { $exists = Category::query() ->where('tenant_id', $tenantId) ->where('id', $categoryId) ->exists(); if (! $exists) { throw new BadRequestHttpException(__('error.category_not_found')); } } }