모듈에서 빠진 항목도 다시 동기화할 수 있다
LearningX의 출결 항목은 과목 모듈에서 제거돼도 AttendanceItem 자체가 남을 수 있다. 학습 분석에는 그 항목을 가리키는 음수 ID의 가상 Assignment가 유지된다. 이 행을 다시 조회하면 주차 관계가 없는 상태로 syncLearningX에 들어올 수 있다.
기존 getWeekIdAttribute는 $this->week를 읽었다. 이 lazy 조회가 getWeekAttribute를 호출하고, 그 안의 관계 조회가 다시 $this->week_id를 읽었다. 두 accessor가 재진입하면서 PHP magic getter의 재귀 가드에 걸려 Undefined property 오류가 발생했다.
변경은 이미 로드된 관계만 직접 확인하도록 바꿨다. 아래는 실제 주차 확인 부분이다. 뒤에서는 로드된 PureCanvasCourse로 주차를 찾고, 찾을 수 없으면 null을 반환한다.
$week = $this->relationLoaded('week') ? $this->getRelation('week') : null;
if ($week !== null) {
return $week->id;
} 현재 모듈에서 빠졌다는 이유로 학습 항목을 삭제하지 않는다
주차를 찾지 못한 항목은 week_id=null로 남긴다. Assignment의 삭제 의존 대상에는 assignment_participations가 있어서, 항목을 soft delete하면 수강 이력에도 영향을 준다. 현재 모듈의 소속과 과거 참여 기록은 다른 수명을 가진다.
이 처리는 모듈에서만 빠진 Canvas 과제를 주차 소속 없이 유지하는 기존 의미와도 맞는다. 모듈에 재추가하면 구성 경로가 주차 관계를 다시 설정할 수 있다. 원본 복제 뒤의 파생 동기화가 필요한 행을 만드는 문제라면, 이 변경은 소속을 잃은 행을 어떤 상태로 유지할지 정하는 문제다.
position은 null 대신 0으로 저장한다. PostgreSQL 기반 학습 분석 배포의 NOT NULL 제약 때문에 null은 저장 단계에서 다시 실패할 수 있다. 주차가 없는 항목은 주차 정렬에 들어가지 않아 이 0이 사용되지 않는다. 다만 모듈 안의 항목도 원래 position이 null이면 0이 되므로, 기존 순서를 모두 복원했다는 뜻은 아니다.
남은 행의 저장 상태부터 확인한다
회귀 테스트는 가상 Assignment를 저장한 뒤 모듈 아이템만 삭제하고 과목을 다시 조회한다. 동기화 뒤 주차 소속 없음, inCourseBuilder=false, position=0, 미삭제 상태를 확인하고, 저장 후 raw DB 속성도 다시 읽는다. 관계가 없는 행이 재진입해 저장까지 도달하는 경로를 다룬다.
실제 참여 이력 행을 만들어 개수 보존을 검사하거나 재추가 복원까지 실행하는 테스트는 아니다. 이력 보존의 이유는 삭제 의존 관계에 있으며, 테스트가 직접 확인하는 대상은 Assignment의 생존과 저장값이다. 유형별 course builder 카운트에 이런 행이 포함될 수 있는 기존 동작도 이번 변경 범위에 남는다.