fix(generative/financial): VAT only on parking, residential ДДУ exempt (ст. 149 НК)
compute_financial начисляла НДС на ВСЮ валовую маржу — неверно для РФ жилья: реализация жилых помещений и услуги застройщика по ДДУ освобождены (пп.22/пп.23.1 п.3 ст. 149 НК РФ), а невозмещаемый входной НДС по СМР уже встроен в gross-ставки себестоимости. НДС теперь только на нежилой value-added паркинга: max(0, revenue_parking − construction_parking) × 0.20/1.20. Инвариант Σ cashflow == net_profit сохранён (vat прокидывается в построитель cashflow). Обновлены docstring модуля + caveat/label в PDF-экспортёре и concept-UI (ConceptVariantsResult). Repr. comfort: VAT 8.5М → 2.5М, net 31.8М → 36.3М. Deep-review ✅ (налоговый режим независимо подтверждён, edge-cases, инвариант). Refs #1881
This commit is contained in:
parent
89509824c2
commit
2c9629a64c
4 changed files with 92 additions and 24 deletions
|
|
@ -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). Цены и себестоимость "
|
||||
"— рыночные ориентиры. Коммерческие/офисные площади не учитываются (нет в ТЭП).</p>"
|
||||
"</body></html>"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ─────────────────────────────────────────────
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
/>
|
||||
<CascadeRow
|
||||
label="НДС (упрощённо)"
|
||||
label="НДС (паркинг)"
|
||||
value={formatMoneyCompact(financial.vat_rub)}
|
||||
/>
|
||||
<CascadeRow
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue