From 1873dd604a666292d79039456abb479eb9b837b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=9D=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Fri, 2 Oct 2026 12:10:33 +0300 Subject: [PATCH] Fix payroll scheduled change row locking --- backend/finance/payroll/scheduled_changes.py | 2 +- .../finance/test_payroll_scheduled_changes.py | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/backend/finance/payroll/scheduled_changes.py b/backend/finance/payroll/scheduled_changes.py index 121a0101..b753682e 100644 --- a/backend/finance/payroll/scheduled_changes.py +++ b/backend/finance/payroll/scheduled_changes.py @@ -52,7 +52,7 @@ def materialize_scheduled_changes( component.code: component for component in get_payroll_components(target_period) } source_lines = list( - PayrollInputLine.objects.select_for_update() + PayrollInputLine.objects.select_for_update(of=("self",)) .select_related("created_by", "approved_by") .filter( period=source_period, diff --git a/backend/tests/finance/test_payroll_scheduled_changes.py b/backend/tests/finance/test_payroll_scheduled_changes.py index 0dc625b2..ff1adc77 100644 --- a/backend/tests/finance/test_payroll_scheduled_changes.py +++ b/backend/tests/finance/test_payroll_scheduled_changes.py @@ -237,6 +237,40 @@ def test_marker_waits_until_the_next_period_is_created(scheduled_workspace): assert len(row["applied_scheduled_changes"]) == 1 +def test_materialization_locks_only_payroll_input_lines( + scheduled_workspace, + monkeypatch, +): + from django.db.models import QuerySet + + lock_targets = [] + original_select_for_update = QuerySet.select_for_update + + def track_select_for_update(queryset, *args, **kwargs): + if queryset.model is PayrollInputLine: + lock_targets.append(kwargs.get("of")) + return original_select_for_update(queryset, *args, **kwargs) + + monkeypatch.setattr(QuerySet, "select_for_update", track_select_for_update) + + workspace = scheduled_workspace + component = create_scheduled_component( + workspace["client"], + name="Маркер с безопасной блокировкой", + source_period=workspace["periods"][7], + ) + response = set_cell( + workspace["client"], + period=workspace["periods"][7], + employee=workspace["employee"], + component_code=component["code"], + amount="500.00", + ) + + assert response.status_code == 201, response.content + assert ("self",) in lock_targets + + def test_closed_next_period_rejects_marker_value(scheduled_workspace): workspace = scheduled_workspace target = workspace["periods"][8]