*/ protected array $trackableFields = [ 'subject', 'body', 'click_url', 'recipients', 'is_active', ]; /** * 다국어 JSON 컬럼 — 운영자 수정을 dot-path(`"subject.ja"` 등) sub-key 단위로 기록. * * 미선언 시 수정이 컬럼 전체 항목(`"subject"`)으로 기록되어 시더 재실행이 컬럼을 * 통째로 얼리고(다른 로케일 갱신·신규 팩 로케일 추가 불가), 언어팩 병합의 * 사용자 수정 보존(Case B)도 로케일 단위로 판정할 수 없다 (#597 에서 교정). * * @var array */ protected array $translatableTrackableFields = [ 'subject', 'body', ]; /** * 모델 이벤트 등록 — 템플릿 변경 시 알림 정의 캐시 자동 삭제. */ protected static function booted(): void { $invalidate = function () { try { app(NotificationDefinitionService::class)->invalidateAllCache(); } catch (\Throwable) { // 테스트/마이그레이션 환경에서 서비스 미등록 시 무시 } }; static::saved($invalidate); static::deleted($invalidate); } /** * 활동 로그 추적 필드. * * @var array> */ public static array $activityLogFields = [ 'subject' => ['label_key' => 'activity_log.fields.subject', 'type' => 'text'], 'body' => ['label_key' => 'activity_log.fields.body', 'type' => 'text'], 'is_active' => ['label_key' => 'activity_log.fields.is_active', 'type' => 'boolean'], 'recipients' => ['label_key' => 'activity_log.fields.recipients', 'type' => 'text'], ]; /** * @var string */ protected $table = 'notification_templates'; /** * @var array */ protected $fillable = [ 'definition_id', 'channel', 'subject', 'body', 'click_url', 'recipients', 'is_active', 'is_default', 'user_overrides', 'updated_by', ]; /** * 캐스팅 정의. * * @return array */ protected function casts(): array { return [ 'recipients' => 'array', 'user_overrides' => 'array', ]; } /** * 알림 정의 관계. * * @return BelongsTo */ public function definition(): BelongsTo { return $this->belongsTo(NotificationDefinition::class, 'definition_id'); } /** * 수정자 관계. * * @return BelongsTo */ public function updater(): BelongsTo { return $this->belongsTo(User::class, 'updated_by'); } /** * 수신자 규칙이 설정되어 있는지 확인합니다. * * @return bool */ public function hasRecipientConfig(): bool { return ! empty($this->recipients); } /** * 특정 채널의 템플릿 조회. * * @param Builder $query * @param string $channel * @return Builder */ public function scopeByChannel(Builder $query, string $channel): Builder { return $query->where('channel', $channel); } }