diff --git a/backend/api/v1/employees/serializers/employee.py b/backend/api/v1/employees/serializers/employee.py index bba84928..fe7cbba2 100644 --- a/backend/api/v1/employees/serializers/employee.py +++ b/backend/api/v1/employees/serializers/employee.py @@ -7,6 +7,7 @@ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema_field from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType +from django.db.models import Q from django.utils import timezone from employees.models import EmployeeDepartment, Position, Skill from employees.services.personnel_state import resolve_employee_personnel_state @@ -114,7 +115,14 @@ def _employee_attendance_status_payload(obj): and not has_attendance_annotation and hasattr(obj, "attendance_records") ): - latest_record = obj.attendance_records.order_by("-date", "-id").first() + latest_record = ( + obj.attendance_records.filter( + (Q(arrival_time__isnull=False) & ~Q(arrival_time="")) + | (Q(departure_time__isnull=False) & ~Q(departure_time="")) + ) + .order_by("-date", "-id") + .first() + ) if latest_record is not None: record_id = latest_record.id record_date = latest_record.date @@ -144,9 +152,7 @@ def _employee_attendance_status_payload(obj): else: display_date = str(record_date) - display = f"{label} · {display_date}" - if time_value: - display = f"{display}, {time_value}" + display = f"{display_date}, {time_value}" if time_value else display_date return { "record_id": record_id, diff --git a/backend/api/v1/employees/views/employees.py b/backend/api/v1/employees/views/employees.py index 232d9385..96db83b5 100644 --- a/backend/api/v1/employees/views/employees.py +++ b/backend/api/v1/employees/views/employees.py @@ -83,6 +83,9 @@ class EmployeeViewSet(viewsets.ModelViewSet): def _with_last_attendance_status(queryset): latest_attendance = AttendanceRecord.objects.filter( employee_id=OuterRef("pk") + ).filter( + (Q(arrival_time__isnull=False) & ~Q(arrival_time="")) + | (Q(departure_time__isnull=False) & ~Q(departure_time="")) ).order_by("-date", "-id") return queryset.annotate( latest_attendance_record_id=Subquery( diff --git a/backend/tests/api/v1/employees/test_employees.py b/backend/tests/api/v1/employees/test_employees.py index e386077a..e2ea1930 100644 --- a/backend/tests/api/v1/employees/test_employees.py +++ b/backend/tests/api/v1/employees/test_employees.py @@ -355,7 +355,7 @@ def test_retrieve_exposes_last_attendance_status_to_authenticated_users( "time": "17:45", "event": "departure", "label": "Уход", - "display": "Уход · 11.04.2026, 17:45", + "display": "11.04.2026, 17:45", } @@ -387,6 +387,40 @@ def test_retrieve_last_attendance_status_uses_arrival_when_no_departure( assert payload["time"] == "09:05" assert payload["event"] == "arrival" assert payload["label"] == "Приход" + assert payload["display"] == "12.04.2026, 09:05" + + +def test_retrieve_last_attendance_status_skips_records_without_events( + api_client: APIClient, +): + viewer = make_user("attendance-event-viewer@example.com") + target = make_user("attendance-event-target@example.com") + run = AttendanceAnalysisRun.objects.create( + employee=target, + period_start=dt.date(2026, 4, 1), + period_end=dt.date(2026, 4, 30), + ) + event_record = AttendanceRecord.objects.create( + analysis_run=run, + employee=target, + date=dt.date(2026, 4, 29), + arrival_time="08:47", + ) + AttendanceRecord.objects.create( + analysis_run=run, + employee=target, + date=dt.date(2026, 4, 30), + arrival_time=None, + departure_time=None, + ) + + api_client.force_authenticate(user=viewer) + response = api_client.get(reverse("api:v1:employees-detail", args=[target.pk])) + + assert response.status_code == status.HTTP_200_OK + payload = response.json()["attendance_status"] + assert payload["record_id"] == event_record.id + assert payload["display"] == "29.04.2026, 08:47" def test_update_attendance_aliases_normalizes_values(api_client: APIClient): diff --git a/frontend/src/app/users/[id]/page.tsx b/frontend/src/app/users/[id]/page.tsx index fe4af496..948ea634 100644 --- a/frontend/src/app/users/[id]/page.tsx +++ b/frontend/src/app/users/[id]/page.tsx @@ -580,6 +580,8 @@ export default function UserDetailPage() { onEditAction={handleEditAction} personnelState={person.personnel_state} sortedActions={sortedActions} + initialVisibleCount={3} + showCountLabel={false} truncateCommentLength={120} expandedCommentLength={240} /> diff --git a/frontend/src/components/attendance/EmployeeAttendanceCard.tsx b/frontend/src/components/attendance/EmployeeAttendanceCard.tsx index faae36ef..8e8bf4e3 100644 --- a/frontend/src/components/attendance/EmployeeAttendanceCard.tsx +++ b/frontend/src/components/attendance/EmployeeAttendanceCard.tsx @@ -1001,59 +1001,66 @@ export default function EmployeeAttendanceCard({