fix(finance): scope workbook import row lock

This commit is contained in:
lizerk 2026-07-21 18:08:08 +03:00
parent d3d2bb4462
commit e936bc869e
2 changed files with 22 additions and 6 deletions

View File

@ -486,6 +486,15 @@ def _ensure_import_allowed(period: PayrollPeriod):
_error("PERIOD_INPUTS_LOCKED", "Сначала верните текущий расчёт на исправление.")
def _period_import_lock_queryset():
# current_run is nullable, so PostgreSQL must lock only the period table.
# Locking every joined table raises an error for the outer join when a
# period has no current run.
return PayrollPeriod.objects.select_for_update(of=("self",)).select_related(
"current_run"
)
@transaction.atomic
def apply_workbook_import(
period_id: int,
@ -515,11 +524,7 @@ def apply_workbook_import(
"WORKBOOK_CHANGED",
"После предпросмотра выбран другой файл. Повторите проверку.",
)
period = (
PayrollPeriod.objects.select_for_update()
.select_related("current_run")
.get(pk=period_id)
)
period = _period_import_lock_queryset().get(pk=period_id)
_ensure_import_allowed(period)
if period.lock_version != expected_period_lock_version:
_error("STALE_PERIOD", "Период уже изменён; повторите предпросмотр.")

View File

@ -10,7 +10,11 @@ from django.urls import reverse
from openpyxl import Workbook
from finance.models import PayrollDailyWorkEntry, PayrollPeriod, PayrollWorkRecord
from finance.payroll.workbook_import import _sanitize_styles, parse_workbook
from finance.payroll.workbook_import import (
_period_import_lock_queryset,
_sanitize_styles,
parse_workbook,
)
pytestmark = pytest.mark.django_db
@ -64,6 +68,13 @@ def make_period(creator):
)
def test_import_period_lock_targets_only_the_period_table():
query = _period_import_lock_queryset().query
assert query.select_for_update is True
assert query.select_for_update_of == ("self",)
def test_invalid_legacy_colour_can_be_repaired_before_parsing():
payload = workbook_bytes()
source = zipfile.ZipFile(io.BytesIO(payload))