""" منطق تغییر status روی NodeProgress. دو نقطه‌ی ورودی، هر دو از content/receivers.py صدا زده می‌شن (نه مستقیم از views.py) تا اگه جای دیگه‌ای هم (مثلاً یک import/bulk-completion از ai_tutor) رکورد UserAnswer یا UserLessonCompletion ساخته بشه، وضعیت Node هم خودکار به‌روز بمونه — دقیقاً همون الگوی signal-based که برای discount/subscription استفاده شده. """ from django.utils import timezone from statuslog.signals import status_changed from .models import Node, NodeProgress, UserLessonCompletion def _get_or_create_progress(user_id, node): return NodeProgress.objects.get_or_create(user_id=user_id, node=node) def _emit_if_changed(progress, old_status, event, user_id): if progress.status != old_status: status_changed.send( sender='content.NodeProgress', object_id=progress.id, old_status=old_status, new_status=progress.status, event=event, user_id=user_id, ) def touch_node_progress_on_answer(user_id, question): """ وقتی کاربر به یک سوال پاسخ می‌ده (چه درست چه غلط)، یعنی روی Node مربوطه فعالیت داشته. اگه Node هنوز شروع نشده یا رها شده بوده، به in_progress برمی‌گرده. """ node = question.lesson.node progress, _created = _get_or_create_progress(user_id, node) old_status = progress.status now = timezone.now() if progress.status in ('not_started', 'abandoned'): progress.status = 'in_progress' if progress.started_at is None: progress.started_at = now progress.last_activity_at = now progress.save(update_fields=['status', 'started_at', 'last_activity_at']) _emit_if_changed(progress, old_status, event='answer_activity', user_id=user_id) return progress def update_node_progress_on_lesson_completion(user_id, lesson): """ وقتی یک Lesson تکمیل می‌شه، تعداد Lessonهای تکمیل‌شده‌ی Node رو به‌روز می‌کنه؛ اگه همه‌ی Lessonهای Node تکمیل شده باشن، status میره روی completed، وگرنه (یا اگه از abandoned برگشته) in_progress. """ node = lesson.node progress, _created = _get_or_create_progress(user_id, node) old_status = progress.status now = timezone.now() lesson_ids = list(node.lessons.values_list('id', flat=True)) completed_count = UserLessonCompletion.objects.filter( user_id=user_id, lesson_id__in=lesson_ids ).values('lesson_id').distinct().count() progress.lessons_completed_count = completed_count if progress.started_at is None: progress.started_at = now progress.last_activity_at = now if lesson_ids and completed_count >= len(lesson_ids): progress.status = 'completed' progress.completed_at = now else: progress.status = 'in_progress' progress.save(update_fields=[ 'lessons_completed_count', 'status', 'started_at', 'completed_at', 'last_activity_at', ]) _emit_if_changed(progress, old_status, event='lesson_completed', user_id=user_id) return progress def node_progress_summary(node): """برای سریالایزر: تعداد کل Lessonهای یک Node (لازم برای lessonsTotal).""" return node.lessons.count()