From 30736518120ab412df1247f56ca674e50cbe057e Mon Sep 17 00:00:00 2001 From: bot-backend Date: Thu, 25 Jun 2026 07:22:58 +0000 Subject: [PATCH] =?UTF-8?q?fix(generative/financial):=20VAT=20only=20on=20?= =?UTF-8?q?parking,=20residential=20=D0=94=D0=94=D0=A3=20exempt=20(=D1=81?= =?UTF-8?q?=D1=82.=20149=20=D0=9D=D0=9A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VAT charged on full gross margin → only on parking value-added; residential (реализация жилья + услуги застройщика по ДДУ) exempt per пп.22/23.1 п.3 ст. 149 НК; input VAT embedded in gross СМР. Σ cashflow == net_profit invariant preserved. PDF + concept-UI caveats synced. +3 tests, deep-review ✅. Refs #1881 --- .../app/services/generative/exporters/pdf.py | 7 ++- backend/app/services/generative/financial.py | 43 ++++++++------ .../services/generative/test_financial_dcf.py | 56 +++++++++++++++++++ .../concept/ConceptVariantsResult.tsx | 10 ++-- 4 files changed, 92 insertions(+), 24 deletions(-) diff --git a/backend/app/services/generative/exporters/pdf.py b/backend/app/services/generative/exporters/pdf.py index a6276e90..b2b2b86b 100644 --- a/backend/app/services/generative/exporters/pdf.py +++ b/backend/app/services/generative/exporters/pdf.py @@ -132,7 +132,7 @@ def _financial_table(variants: Sequence[ConceptVariant]) -> str: ("Земля, млн руб", [_fmt_money(v.financial.land_rub) for v in variants]), ("Итого затраты, млн руб", [_fmt_money(v.financial.cost_rub) for v in variants]), ("Валовая маржа, млн руб", [_fmt_money(v.financial.gross_margin_rub) for v in variants]), - ("НДС (упрощ.), млн руб", [_fmt_money(v.financial.vat_rub) for v in variants]), + ("НДС (паркинг), млн руб", [_fmt_money(v.financial.vat_rub) for v in variants]), ( "Прибыль до налога, млн руб", [_fmt_money(v.financial.profit_before_tax_rub) for v in variants], @@ -180,8 +180,9 @@ def _build_html(variants: Sequence[ConceptVariant]) -> str: "График фаз и темп продаж — типовые допущения, НЕ график конкретного проекта; " "точность метрик зависит от реального графика. Где IRR помечен «оценочный» — поток " "вырожденный (нет смены знака), показан аннуализированный ROI вместо DCF-IRR. " - "НДС — упрощённая оценка (встроенный НДС в положительной марже, не полная механика " - "входного/выходного НДС НК РФ). Налог на прибыль — 25% (с 2025). Цены и себестоимость " + "НДС — реализация жилья и услуги застройщика по ДДУ освобождены (ст. 149 НК РФ), " + "входной НДС по СМР встроен в себестоимость; НДС начисляется только на нежилую часть " + "(машиноместа). Налог на прибыль — 25% (с 2025). Цены и себестоимость " "— рыночные ориентиры. Коммерческие/офисные площади не учитываются (нет в ТЭП).

" "" ) diff --git a/backend/app/services/generative/financial.py b/backend/app/services/generative/financial.py index 82a65566..d9765b42 100644 --- a/backend/app/services/generative/financial.py +++ b/backend/app/services/generative/financial.py @@ -52,19 +52,24 @@ Cascade (in :func:`compute_financial`): * **БДР / taxes** = gross margin − VAT − profit tax → net profit. * **Metrics** = net ROI on cost, net margin on revenue, and DCF NPV/IRR/payback. -VAT — DOCUMENTED SIMPLIFICATION -------------------------------- -Exact НК РФ VAT mechanics (output VAT on flats vs. input-VAT deductions on -construction/materials, эскроу specifics, residential-sale exemptions) are out of -PR-1 scope. We use a conservative, honest approximation on the project's -*value-added* (gross margin): +НДС — ОСВОБОЖДЕНИЕ ЖИЛЬЯ, НДС ТОЛЬКО НА ПАРКИНГ (ст. 149 НК РФ) +------------------------------------------------------------ +Реализация жилых помещений и услуги застройщика по ДДУ ОСВОБОЖДЕНЫ от НДС +(пп.22 и пп.23.1 п.3 ст. 149 НК РФ) → выходной НДС на жильё = 0. Входной НДС по +СМР при этом НЕвозмещаем (п.2 ст.170 НК РФ) и уже «сидит» в gross-ставках +себестоимости (рыночные ориентиры ``_CONSTRUCTION_COST_PER_SQM`` даны С НДС) → +чистая нагрузка по НДС на жилую часть ≈ 0. - vat = max(0, gross_margin) × VAT_RATE / (1 + VAT_RATE) +Машиноместа — НЕЖИЛЫЕ помещения: их реализация ОБЛАГАЕТСЯ НДС 20%, входной НДС +вычитается. Поэтому НДС начисляется ТОЛЬКО на нежилой value-added паркинга +(встроенный НДС в добавленной стоимости машиномест): -i.e. the embedded VAT inside the positive margin. This deliberately *does not* -model the full input/output deduction chain — it is a stand-in that keeps the net -P&L plausible and conservative. A loss-making project pays no VAT here. Replace -with proper output/input modelling when calibrating (PR-2/PR-3). + vat = max(0, revenue_parking − construction_parking) × VAT_RATE / (1 + VAT_RATE) + +Если паркинг тоже окажется под ДДУ-льготой — оценка консервативна (чуть занижает +прибыль завышенным НДС), что соответствует honest/conservative-политике модуля. +Полную механику входного/выходного НДС с эскроу-спецификой не моделируем — +жильё освобождено, входной НДС встроен в gross-СМР. IRR — REAL DCF, PROXY FALLBACK (PR-3) ------------------------------------- @@ -521,8 +526,9 @@ def compute_financial( Полный девелоперский каскад: выручка (жильё + паркинг) → каскад затрат (СМР + ПИР + сети + услуги заказчика + резерв + маркетинг + земля) → БДР с НДС и налогом на прибыль → чистая прибыль + ROI. Поверх статики — помесячный DCF - (NPV/IRR/PBP) по дефолт-графику фаз (см. docstring модуля). НДС — упрощение; - офисов нет в TEAP — пропущены. + (NPV/IRR/PBP) по дефолт-графику фаз (см. docstring модуля). НДС — жильё + освобождено (ст. 149 НК РФ), начисляется только на паркинг; офисов нет в + TEAP — пропущены. Чистая функция: НЕ ходит в БД. Калибровку цены продажи жилья делает API-слой (lookup рынка) и прокидывает сюда через ``market_price_per_sqm``. @@ -583,9 +589,14 @@ def compute_financial( # ── БДР / налоги → чистая прибыль ───────────────────────────────────────── gross_margin = revenue - cost # валовая (как было) - # НДС: УПРОЩЕНО — встроенный НДС в положительной марже (value-added), - # не полная механика входного/выходного НДС НК РФ (см. docstring модуля). - vat = max(0.0, gross_margin) * _VAT_RATE / (1.0 + _VAT_RATE) + # НДС: жилые помещения и услуги застройщика по ДДУ освобождены (пп.22 / пп.23.1 + # п.3 ст. 149 НК РФ) → output-НДС на жильё = 0, а невозмещаемый входной НДС по СМР + # уже сидит в gross-ставках себестоимости. НДС начисляется ТОЛЬКО на нежилую часть + # (машиноместа — нежилые, их реализация облагается): встроенный НДС в value-added + # паркинга. Если паркинг тоже окажется по ДДУ-льготе — оценка консервативна (чуть + # занижает прибыль), что соответствует «honest/conservative» политике модуля. + parking_value_added = revenue_parking - construction_parking + vat = max(0.0, parking_value_added) * _VAT_RATE / (1.0 + _VAT_RATE) profit_before_tax = gross_margin - vat profit_tax = max(0.0, profit_before_tax) * _PROFIT_TAX_RATE net_profit = profit_before_tax - profit_tax diff --git a/backend/tests/services/generative/test_financial_dcf.py b/backend/tests/services/generative/test_financial_dcf.py index 2b5d630f..67e8f5ff 100644 --- a/backend/tests/services/generative/test_financial_dcf.py +++ b/backend/tests/services/generative/test_financial_dcf.py @@ -157,6 +157,62 @@ def test_invariant_holds_for_all_development_types() -> None: assert abs(sum(cf) - model.net_profit_rub) < 1.0, dev_type +# ── НДС: освобождение жилья (ст.149 НК), НДС только на паркинг ─────────────────── + + +def test_vat_zero_for_residential_only_project() -> None: + # Реализация жилья + услуги застройщика по ДДУ освобождены от НДС (пп.22/пп.23.1 + # п.3 ст.149 НК РФ). Проект БЕЗ паркинга → НДС == 0 (раньше было margin×0.20/1.20). + # Profitable comfort-проект всё равно остаётся прибыльным. + t = _teap(residential=10_000.0, gfa=11_000.0, parking=0) + model = financial.compute_financial( + teap=t, housing_class="comfort", land_cost_rub=200_000_000.0, development_type="mid_rise" + ) + # Освобождение явно: нет паркинга → нет облагаемого value-added → НДС ровно 0. + assert model.vat_rub == 0.0 + assert model.revenue_parking_rub == 0.0 + # Налогооблагаемая база до налога теперь == валовой марже (НДС не съедает её). + assert model.profit_before_tax_rub == pytest.approx(model.gross_margin_rub, abs=0.01) + # Проект прибыльный (освобождение НДС не делает его убыточным). + assert model.net_profit_rub > 0 + + +def test_vat_only_on_parking_value_added() -> None: + # С паркингом → НДС > 0 и считается ТОЛЬКО на нежилой value-added машиномест: + # vat == max(0, revenue_parking − construction_parking) × 0.20/1.20. + # Жилая часть (выручка/маржа жилья) в базу НДС НЕ входит (ст.149 НК РФ). + parking = 50 + cls = "comfort" + t = _teap(residential=10_000.0, gfa=11_000.0, parking=parking) + model = financial.compute_financial( + teap=t, housing_class=cls, land_cost_rub=200_000_000.0, development_type="mid_rise" + ) + # Ожидаемое из констант класса: revenue_parking − construction_parking паркинга. + revenue_parking = parking * financial._PARKING_PRICE_PER_SPOT[cls] + construction_parking = parking * financial._PARKING_COST_PER_SPOT[cls] + parking_value_added = revenue_parking - construction_parking + expected_vat = max(0.0, parking_value_added) * financial._VAT_RATE / (1.0 + financial._VAT_RATE) + assert model.vat_rub > 0.0 + assert model.vat_rub == pytest.approx(round(expected_vat, 2), abs=0.01) + # База НДС — именно нежилой value-added, а НЕ полная валовая маржа проекта. + full_margin_vat = ( + max(0.0, model.gross_margin_rub) * financial._VAT_RATE / (1.0 + financial._VAT_RATE) + ) + assert model.vat_rub < full_margin_vat # жильё освобождено → НДС много меньше margin×rate + + +def test_vat_invariant_holds_with_parking_vat() -> None: + # Инвариант Σ cashflow == net_profit держится с новым НДС (vat прокидывается в + # построитель cashflow без изменения формы — меняется только база НДС). + t = _teap(residential=10_000.0, gfa=11_000.0, parking=50) + model = financial.compute_financial( + teap=t, housing_class="comfort", land_cost_rub=200_000_000.0, development_type="mid_rise" + ) + assert model.vat_rub > 0.0 + cf = _rebuild_cashflow(model, construction_months=36, residential_area_sqm=10_000.0) + assert abs(sum(cf) - model.net_profit_rub) < 1.0 + + # ── Настоящий IRR vs proxy fallback ───────────────────────────────────────────── diff --git a/frontend/src/components/concept/ConceptVariantsResult.tsx b/frontend/src/components/concept/ConceptVariantsResult.tsx index eacb9ebd..239e7b5b 100644 --- a/frontend/src/components/concept/ConceptVariantsResult.tsx +++ b/frontend/src/components/concept/ConceptVariantsResult.tsx @@ -298,10 +298,10 @@ function VariantPanel({ parcel, variant }: PanelProps) { {financial.irr_is_proxy ? "IRR помечен как оценочный: денежный поток вырожденный (нет смены знака), показан аннуализированный ROI вместо DCF-IRR. " : ""} - НДС — упрощённая оценка (встроенный НДС в положительной марже, не - полная механика входного/выходного НДС НК РФ); привязан к - признанию выручки. Налог на прибыль — 25% (с 2025 года). Цена - продажи жилья —{" "} + НДС: жильё и услуги застройщика по ДДУ освобождены (ст. 149 НК + РФ); НДС начисляется только на паркинг (нежилые машиноместа). + Входной НДС по строительству уже учтён в себестоимости. Налог на + прибыль — 25% (с 2025 года). Цена продажи жилья —{" "} {financial.price_is_calibrated ? `калибрована по рынку (${priceSourceCaption(financial)})` : "норматив класса (нет рыночных данных по участку)"} @@ -414,7 +414,7 @@ function FinancialCascadeTable({ financial }: { financial: FinancialModel }) { value={formatMoneyCompact(financial.gross_margin_rub)} />