Files
Gnuboard7/app/Models/NotificationTemplate.php
HeuJung e7fa1b5535 feat(message_bizppurio): 알림 템플릿 편집 통합·검수 의견·관리탭 개선 및 언어팩 번역 보존 결함군 수정
비즈뿌리오 알림톡 UX 개편 후속:
- 코어/게시판/이커머스 알림 템플릿 [편집] 모달에 확장 채널 편집 영역
 (extension_point 2종 + hidden_template_editor 채널 메타)을 신설하고,
 알림톡 템플릿·문자(SMS)·수신자 규칙을 한 창에서 통합 저장하도록 개정
 (행 하단은 상태 요약 전용, 승인 여부 2단 배지)
- 검수 신청에 검수자 전달 의견(comment) 동봉 — kapi request 로만 전달,
 행에는 저장하지 않음 (FormRequest 신설 + API 문서 백필)
- 관리 탭 모바일 카드뷰·도구줄 코어 관례 통일, 사이트맵 알림 정의
 다국어 라벨 결측 수정 + 코어 정의 라벨 패리티 테스트 신설

언어팩 번역 소실·보존 결함군 (보완 실측 → 커밋 전 점검에서 연쇄 확정,
전부 실패 테스트 선행 후 수정):
- [기본값 복원]이 활성 언어팩 번역(ja)을 영구 소실 → 복원 기본값에
 시딩과 같은 주입기(SSoT)로 팩 로케일 병합 (notification·identity)
- 코어 알림/본인인증 시더의 언어팩 주입 전면 불능 → 주입기가 연관/리스트
 양형태 수용 + identity 시더의 config 복합 키 보존
- 실패한 언어팩 업데이트가 active 팩을 installed 로 방치 → 상태 복원 확장
- 병합·제거의 사용자 수정 보존 판정 사문(연관 전제 isset) → dot-path 판정
 교정, 팩 주입 저장의 user_overrides 오염 차단(시딩 바인딩), 템플릿
 2모델 translatableTrackableFields 선언(beta.4 설계 누락), 같은 프로세스
 활성화 시 시더 번역 필터 stale(캡처 인스턴스 캐시) 교정
- 7.0.9 업그레이드 스텝 신설 — 활성 팩 seed 재동기화로 기설치본의 소실
 번역 자동 복구 (멱등·운영자 수정 보존·팩별 실패 격리)
2026-08-24 14:51:37 +09:00

149 lines
3.8 KiB
PHP

<?php
namespace App\Models;
use App\Models\Concerns\HasUserOverrides;
use App\Models\Concerns\NotificationContentBehavior;
use App\Services\NotificationDefinitionService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class NotificationTemplate extends Model
{
use HasFactory, HasUserOverrides, NotificationContentBehavior;
/**
* 사용자 수정 보존 대상 필드.
*
* @var array<int, string>
*/
protected array $trackableFields = [
'subject',
'body',
'click_url',
'recipients',
'is_active',
];
/**
* 다국어 JSON 컬럼 — 운영자 수정을 dot-path(`"subject.ja"` 등) sub-key 단위로 기록.
*
* 미선언 시 수정이 컬럼 전체 항목(`"subject"`)으로 기록되어 시더 재실행이 컬럼을
* 통째로 얼리고(다른 로케일 갱신·신규 팩 로케일 추가 불가), 언어팩 병합의
* 사용자 수정 보존(Case B)도 로케일 단위로 판정할 수 없다 (#597 에서 교정).
*
* @var array<int, string>
*/
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<string, array<string, string>>
*/
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<int, string>
*/
protected $fillable = [
'definition_id',
'channel',
'subject',
'body',
'click_url',
'recipients',
'is_active',
'is_default',
'user_overrides',
'updated_by',
];
/**
* 캐스팅 정의.
*
* @return array<string, string>
*/
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);
}
}