diff --git a/src/apps/exchange/services.py b/src/apps/exchange/services.py index d577596..444cdfb 100644 --- a/src/apps/exchange/services.py +++ b/src/apps/exchange/services.py @@ -1168,6 +1168,7 @@ class ExchangePackageImportService: skipped_count = 0 created_lines_count = 0 updated_lines_count = 0 + deleted_lines_count = 0 for row in rows: organization = cls._resolve_organization( @@ -1218,6 +1219,7 @@ class ExchangePackageImportService: ) created_lines_count += line_result["created"] updated_lines_count += line_result["updated"] + deleted_lines_count += line_result["deleted"] return { "created": created_count, @@ -1225,6 +1227,7 @@ class ExchangePackageImportService: "skipped": skipped_count, "created_lines": created_lines_count, "updated_lines": updated_lines_count, + "deleted_lines": deleted_lines_count, } @classmethod @@ -1234,7 +1237,7 @@ class ExchangePackageImportService: lines: Any, ) -> dict[str, int]: if lines is None: - return {"created": 0, "updated": 0} + return {"created": 0, "updated": 0, "deleted": 0} if not isinstance(lines, list): raise ExchangeImportError( "Поле lines финансового отчета должно быть списком" @@ -1242,6 +1245,7 @@ class ExchangePackageImportService: created_count = 0 updated_count = 0 + expected_keys: set[tuple[str, str, int]] = set() for line in lines: if not isinstance(line, dict): raise ExchangeImportError( @@ -1252,6 +1256,7 @@ class ExchangePackageImportService: if not form_code or not line_code: continue year = cls._parse_int_value(line.get("year"), field_name="year") + expected_keys.add((form_code, line_code, year)) defaults = { "line_name": cls._clean_string(line.get("line_name")), @@ -1281,7 +1286,22 @@ class ExchangePackageImportService: elif state == "updated": updated_count += 1 - return {"created": created_count, "updated": updated_count} + stale_line_ids = [ + line.pk + for line in report.lines.all() + if (line.form_code, line.line_code, line.year) not in expected_keys + ] + deleted_count = 0 + if stale_line_ids: + deleted_count = FinancialReportLine.objects.filter( + pk__in=stale_line_ids + ).delete()[0] + + return { + "created": created_count, + "updated": updated_count, + "deleted": deleted_count, + } @classmethod def _upsert_arbitration_cases( diff --git a/tests/apps/exchange/test_api.py b/tests/apps/exchange/test_api.py index 2956002..d8676a3 100644 --- a/tests/apps/exchange/test_api.py +++ b/tests/apps/exchange/test_api.py @@ -769,6 +769,51 @@ class ExchangePackageApiTest(APITestCase): duplicate_import = ExchangePackageImport.objects.order_by("-created_at").first() self.assertIsNotNone(duplicate_import.duplicate_of) + def test_upload_replaces_stale_financial_report_lines(self): + first_payload = build_exchange_payload() + first_archive = build_exchange_archive( + package_id="pkg-financial-year-2025", + data=first_payload, + ) + first_response = self.client.post( + self.url, + {"file": first_archive}, + format="multipart", + HTTP_X_EXCHANGE_TOKEN=TEST_TOKEN, + ) + self.assertEqual(first_response.status_code, status.HTTP_201_CREATED) + self.assertEqual( + list(FinancialReportLine.objects.values_list("year", flat=True)), + [2025], + ) + + second_payload = build_exchange_payload() + second_payload["financial_reports"][0]["lines"][0]["year"] = 2026 + second_archive = build_exchange_archive( + package_id="pkg-financial-year-2026", + data=second_payload, + ) + second_response = self.client.post( + self.url, + {"file": second_archive}, + format="multipart", + HTTP_X_EXCHANGE_TOKEN=TEST_TOKEN, + ) + + self.assertEqual(second_response.status_code, status.HTTP_201_CREATED) + self.assertEqual( + list(FinancialReportLine.objects.values_list("year", flat=True)), + [2026], + ) + self.assertEqual( + second_response.data["result"]["financial_reports"]["created_lines"], + 1, + ) + self.assertEqual( + second_response.data["result"]["financial_reports"]["deleted_lines"], + 1, + ) + def test_cli_import_uses_same_pipeline(self): archive = build_exchange_archive( package_id="pkg-cli-001",