feat(mera/pdf): дисклеймер 135-ФЗ в подвале каждой страницы отчёта (блок 4.2) (#2885)
Some checks failed
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Has been cancelled

This commit is contained in:
lekss361 2026-08-14 12:47:37 +00:00
commit 321cf85e87

View file

@ -238,6 +238,11 @@ _FS_MD = "10.5pt" # базовый текст (body), значения в та
_FS_LG = "13pt" # заголовки страниц (h2, PT Serif)
_FS_XL = "16pt" # главный заголовок cover (h1, PT Serif)
_FS_XXL = "22pt" # крупные ценовые цифры (dual-price блок)
# Намеренное исключение из 6-шаговой шкалы: running-footer — @page margin-box с
# фиксированной высотой (19mm ≈ 53.9pt), делить с mono-мета-строкой/wordmark
# практически нечем (см. _page_footer). 135-ФЗ дисклеймер (Блок 4.2) должен
# влезать в ~380-450 симв. на каждой странице без пятой пустой страницы.
_FS_XXS = "5pt" # ТОЛЬКО 135-ФЗ футер-дисклеймер (_page_footer) — не переиспользовать
# ── Embedded fonts (PT Sans / PT Serif, ParaType, SIL OFL 1.1) ──────────────
@ -528,6 +533,20 @@ def _page_header(brand, report_num: str, report_date: dt.date) -> str: # type:
)
# Блок 4.2 юр-требований: должен печататься в подвале КАЖДОЙ страницы отчёта
# (не только cover). Текст утверждён владельцем продукта дословно — не менять
# формулировку без явного запроса. Заведён как модульная константа (не inline
# в _page_footer), чтобы не расползалась по нескольким билдерам страниц.
_PDF_135FZ_FOOTER_NOTICE = (
"Документ содержит индикативный (ориентировочный) расчёт стоимости объекта, "
"сформированный автоматически сервисом «МЕРА». Не является отчётом об оценке "
"по Федеральному закону № 135-ФЗ и не имеет установленной этим законом "
"юридической силы. Не предназначен для использования при ипотечном "
"кредитовании, в судебных разбирательствах, нотариальных действиях и иных "
"случаях, где законом предусмотрено обязательное проведение независимой оценки."
)
def _page_footer(
brand, # type: ignore[no-untyped-def]
report_num: str,
@ -542,7 +561,10 @@ def _page_footer(
строка 1 mono meta ( отчёта / дата / срок действия); тонкая градиентная
линия-разделитель; строка 2 точка акцента + wordmark (brand.name НЕ
хардкод «МЕРА», white-label остаётся рабочим). Номер версии продукта здесь
хардкод «МЕРА», white-label остаётся рабочим); строка 3 135-ФЗ дисклеймер
(Блок 4.2, _PDF_135FZ_FOOTER_NOTICE) печатается на КАЖДОЙ странице, т.к.
footer рендерится один раз как running @page margin-box (см. вызов в
generate_trade_in_pdf), а не per-page. Номер версии продукта здесь
НЕ дублируется единственное место вывода версии в PDF running-header
(_page_header product_version_line()); раньше рядом с wordmark висел
decorative "vN.N.N" (_REPORT_ENGINE_VERSION), не связанный с реальной
@ -552,6 +574,17 @@ def _page_footer(
на офер-странице), которого нет в веб-референсе (там нет пагинации). Не
удалён вынесен приглушённой строкой НАД HUD-баром, чтобы не терять
полезную для печатного многостраничного отчёта информацию.
#footer-height-budget (2026-08-14, Блок 4.2): @bottom-center margin-box
высотой = page margin-bottom (см. _build_css). Добавление 135-ФЗ текста
(~440 симв.) потребовало И сжать существующий HUD-хром (margin-top
64pt, padding-top 86pt, line-height мета/wordmark строк 1.351.15,
градиент-разделитель margin 6pt 03pt 0 экономия ~13pt), И минимально
поднять @page margin-bottom (19mm21mm, +2mm/+5.67pt) сжатия одного
подвала было недостаточно без деградации до нечитаемого. Риск: margin-bottom
режет тело КАЖДОЙ из 4 страниц потенциальный откат к 5-й почти пустой
странице (регрессия, чинившаяся в 42a50cf8) реальным рендером
(WeasyPrint/Pango, недоступен на Windows-деве) не подтверждено, см. PR.
"""
note_html = ""
if page_note:
@ -583,18 +616,18 @@ def _page_footer(
# тела страницы) и был источником сложности; заменён на простую тонкую
# градиентную линию-разделитель между строками meta/wordmark.
return f"""
<div style="margin-top:6pt;">
<div style="margin-top:4pt;">
{note_html}
<div style="border-top:1pt solid {_LINE_SOFT};padding-top:8pt;
<div style="border-top:1pt solid {_LINE_SOFT};padding-top:6pt;
font-family:{mono_family};font-size:{_FS_XS};letter-spacing:0.06em;
color:{_MUTED_2};">
color:{_MUTED_2};line-height:1.15;">
<div style="white-space:nowrap;overflow:hidden;">
ОТЧЁТ <span style="color:{_MUTED};">{_html.escape(report_num)}</span>
<span style="margin-left:16pt;">ДАТА
<span style="color:{_MUTED};">{report_date.strftime("%d.%m.%Y")}</span></span>
{valid_until_html}
</div>
<div style="height:1pt;margin:6pt 0;background:linear-gradient(90deg,
<div style="height:1pt;margin:3pt 0;background:linear-gradient(90deg,
transparent,{_LINE_DOTTED} 15%,{_ACCENT} 50%,{_LINE_DOTTED} 85%,
transparent);"></div>
<div style="display:flex;align-items:center;gap:7pt;min-width:0;">
@ -606,6 +639,11 @@ def _page_footer(
{_html.escape(brand.name).upper()}</span>
</div>
</div>
<div style="margin-top:3pt;font-family:'PT Sans','DejaVu Sans',sans-serif;
font-size:{_FS_XXS};line-height:1.15;color:{_MUTED_2};
overflow-wrap:anywhere;">
{_html.escape(_PDF_135FZ_FOOTER_NOTICE)}
</div>
</div>
"""
@ -1962,7 +2000,11 @@ def _build_css(brand=None) -> str: # type: ignore[no-untyped-def]
}}
@page {{
size: A4;
margin: 25mm 18mm 19mm 18mm;
/* bottom 19mm21mm (#footer-height-budget, Блок 4.2): +2mm — минимум,
которого не хватило внутри @bottom-center margin-box (высота margin-box
= margin-bottom) даже после сжатия HUD-хрома _page_footer под 135-ФЗ
дисклеймер на каждой странице. См. арифметику в _page_footer(). */
margin: 25mm 18mm 21mm 18mm;
@top-center {{ content: element(runningHeader); vertical-align: bottom; }}
@bottom-center {{ content: element(runningFooter); vertical-align: top; }}
}}