v1.0.178: генератор тестовых файлов (схема Опуса). 61 файл: 10 контрактов × (договор+спека v1+v2+допники) + ошибки + ZIP + дубликаты. Комментарий в каждом файле.

This commit is contained in:
“Naeel”
2026-06-25 11:03:41 +04:00
parent c022133de0
commit 3e548ad2a4
78 changed files with 2433 additions and 0 deletions
+267
View File
@@ -0,0 +1,267 @@
"""
templates.py — Шаблоны документов (python-docx).
build_contract(ctx) — Договор на оказание технологических услуг
build_spec(ctx) — Спецификация и стоимость услуг
build_addendum(ctx) — Дополнительное соглашение
Каждый документ начинается с параграфа-комментария:
«Данный файл: [тип], компания [X], номер [N], версия [V].
Содержит: [краткое описание]. Сгенерирован автоматически для тестирования.»
"""
from docx import Document
from docx.shared import Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT
import pools
# ═══════════════════════════════════════════════════════════════
# Хелперы
# ═══════════════════════════════════════════════════════════════
def _comment_para(doc, text):
"""Добавить параграф-комментарий в начало документа (серый, мелкий)."""
p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
run = p.add_run(text)
run.font.size = Pt(8)
run.font.color.rgb = RGBColor(0x80, 0x80, 0x80)
run.font.italic = True
def _add_title(doc, text):
p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = p.add_run(text)
run.bold = True
run.font.size = Pt(14)
def _add_heading(doc, text):
p = doc.add_paragraph()
run = p.add_run(text)
run.bold = True
run.font.size = Pt(12)
def _add_para(doc, text):
p = doc.add_paragraph(text)
return p
def _add_parties_table(doc, customer, provider, provider_dir, city, date):
"""Таблица реквизитов сторон (1×2): Заказчик | Исполнитель."""
table = doc.add_table(rows=1, cols=2)
table.style = 'Table Grid'
table.alignment = WD_TABLE_ALIGNMENT.CENTER
c0 = table.rows[0].cells[0]
c1 = table.rows[0].cells[1]
cust_info = f'{customer["legal"]}\nЮридический адрес:\n\n{customer.get("dir","")}\n_______________/ФИО/\n М.П.'
prov_info = f'{provider}\nЮридический адрес:\n{pools.ADDRESS}\n{provider_dir}\n_______________/Степаненко В.И./\n М.П.'
c0.text = f"Заказчик\n{cust_info}"
c1.text = f"Исполнитель\n{prov_info}"
def _add_services_table(doc, services, total_str, with_end_date=False, label="Абонентские услуги"):
"""Таблица услуг: №, Наименование, Цена, Объем, Сумма, Дата начала [+ Дата окончания] + Итого."""
_add_para(doc, f"{label}:")
cols = 7 if with_end_date else 6
header = ["№ п/п", "Наименование услуг", "Цена, руб. с НДС", "Объем услуг", "Сумма, руб. с НДС", "Планируемая дата начала оказания услуг"]
if with_end_date:
header.append("Дата окончания оказания услуг")
table = doc.add_table(rows=1, cols=cols)
table.style = 'Table Grid'
# Заголовок
for i, h in enumerate(header):
table.rows[0].cells[i].text = h
# Строки услуг
for s in services:
row = table.add_row()
cells = row.cells
cells[0].text = str(s["num"])
cells[1].text = s["name"]
cells[2].text = s["price_str"]
cells[3].text = str(s["qty"])
cells[4].text = s["sum_str"]
cells[5].text = s.get("date_start", pools.random_date(2026, 3, 3))
if with_end_date:
cells[6].text = s.get("date_end", "")
# Итого
row = table.add_row()
row.cells[0].text = ""
row.cells[1].text = f"Итого {label.lower()} в руб с учетом НДС"
row.cells[2].text = ""
row.cells[3].text = ""
row.cells[4].text = total_str
row.cells[5].text = "-"
if with_end_date:
row.cells[6].text = "-"
# ═══════════════════════════════════════════════════════════════
# Билдеры
# ═══════════════════════════════════════════════════════════════
def build_contract(ctx):
"""
ctx = {
company, number, date, company_idx,
services[], total, total_str,
comment (описание для тестов)
}
"""
doc = Document()
c = ctx["company"]
num = ctx["number"]
date = ctx["date"]
# ── Комментарий для тестов ──
_comment_para(doc, (
f"Данный файл: ДОГОВОР, компания {c['prefix']}, № {num} от {date}.\n"
f"Стороны: {c['legal']} (Заказчик) + {pools.PROVIDER} (Исполнитель).\n"
f"Сгенерирован автоматически для тестирования сервиса contracts.\n"
f"{ctx.get('comment','')}"
))
_add_title(doc, "Договор на оказание технологических услуг")
_add_para(doc, f"{num}")
_add_para(doc, f'{c["legal"]} именуемое в дальнейшем "Заказчик", в лице {c["dir"]} ФИО, действующего на основании Устава, '
f'и {pools.PROVIDER}, именуемое в дальнейшем "Исполнитель", в лице {pools.PROVIDER_DIR}, '
f'действующего на основании Устава, заключили настоящий Договор о нижеследующем.')
# Термины
_add_heading(doc, "ТЕРМИНЫ И ОПРЕДЕЛЕНИЯ")
terms = [
"Услуги – оказываемые Исполнителем Заказчику технологические услуги в области компьютерных и телекоммуникационных технологий.",
"Абонентские услуги – Услуги, указанные в Спецификации, предоставляемые ежемесячно.",
"Инсталляционные услуги – Услуги по установке и настройке оборудования Заказчика.",
"Технологическая площадка – специализированное помещение (ЦОД) по адресу: г. Москва, 1-я Стекольная 7с5.",
"Отчетный период – календарный месяц.",
]
for t in terms:
_add_para(doc, t)
# Предмет
_add_heading(doc, "ПРЕДМЕТ ДОГОВОРА")
_add_para(doc, f"Исполнитель обязуется оказывать Заказчику технологические Услуги, указанные в Спецификации, "
f"а Заказчик обязуется принимать и оплачивать Услуги. Место оказания: {pools.CITY}, {pools.ADDRESS}.")
# Стоимость и оплата
_add_heading(doc, "СТОИМОСТЬ УСЛУГ И ПОРЯДОК ОПЛАТЫ")
_add_para(doc, f"Стоимость Услуг указана в Спецификации (Приложение №1). "
f"Оплата ежемесячная, постоплатой 100%, на основании УПД и счета. "
f"Срок оплаты — 10 рабочих дней с даты выставления счета.")
# Прочие условия (сокращённо)
_add_heading(doc, "ОСОБЫЕ УСЛОВИЯ")
_add_para(doc, "Исполнитель вправе приостановить оказание Услуг при просрочке платежей более 10 рабочих дней. "
"Заказчик обязан соблюдать правила Технологической площадки.")
_add_heading(doc, "СРОК ДЕЙСТВИЯ")
_add_para(doc, "Договор вступает в силу с момента подписания. Начальный период — 12 месяцев. "
"Автоматическая пролонгация, если ни одна из Сторон не уведомит о прекращении за 30 дней.")
# Реквизиты
_add_heading(doc, "ЮРИДИЧЕСКИЕ АДРЕСА И РЕКВИЗИТЫ СТОРОН")
_add_para(doc, f"{pools.CITY} {date}г.")
_add_parties_table(doc, c, pools.PROVIDER, pools.PROVIDER_DIR, pools.CITY, date)
return doc
def build_spec(ctx):
"""
ctx = {
company, number, date, contract_date,
services[], total, total_str,
version (1 или 2),
label (название секции),
comment
}
Для v2 — slight differences в services (цены/объём/состав).
"""
doc = Document()
c = ctx["company"]
num = ctx["number"]
date = ctx["date"]
contract_date = ctx.get("contract_date", date)
ver = ctx.get("version", 1)
_comment_para(doc, (
f"Данный файл: СПЕЦИФИКАЦИЯ v{ver}, компания {c['prefix']}, к договору № {num} от {contract_date}.\n"
f"Версия {ver}: {'базовая' if ver == 1 else 'ревизия с изменениями'}.\n"
f"Содержит: {len(ctx['services'])} строк(и) услуг, итого {ctx['total_str']} руб.\n"
f"{ctx.get('comment','')}"
))
_add_title(doc, f"Приложение № 1")
_add_para(doc, f"к Договору на оказание технологических услуг № {num} от {contract_date} г.")
_add_para(doc, "")
_add_heading(doc, "Спецификация и стоимость услуг")
_add_para(doc, "Состав и стоимость Услуг")
label = ctx.get("label", f"Абонентские услуги с {date}")
_add_para(doc, label)
_add_para(doc, f"Общая стоимость Ежемесячных услуг составляет {ctx['total_str']} руб.")
_add_services_table(doc, ctx["services"], ctx["total_str"], with_end_date=True, label=label)
# Дата и город
_add_para(doc, "")
_add_para(doc, f"{pools.CITY} {date}г.")
return doc
def build_addendum(ctx):
"""
ctx = {
company, number, date, contract_date, addendum_num,
services_install[], install_total, install_total_str,
services_monthly[], monthly_total, monthly_total_str,
comment
}
"""
doc = Document()
c = ctx["company"]
num = ctx["number"]
date = ctx["date"]
contract_date = ctx.get("contract_date", date)
anum = ctx.get("addendum_num", 1)
_comment_para(doc, (
f"Данный файл: ДОПОЛНИТЕЛЬНОЕ СОГЛАШЕНИЕ №{anum}, компания {c['prefix']}, к договору № {num} от {contract_date}.\n"
f"Содержит: инсталляционные услуги ({len(ctx.get('services_install',[]))} стр.), "
f"абонентские услуги ({len(ctx.get('services_monthly',[]))} стр.).\n"
f"{ctx.get('comment','')}"
))
_add_title(doc, f"Дополнительное Соглашение №{anum}")
_add_para(doc, f"к Договору № {num} от {contract_date}")
_add_para(doc, f"{pools.CITY} «{date.split('.')[0]}» {_month_name(int(date.split('.')[1]))} {date.split('.')[2]}")
_add_para(doc, f'{c["legal"]} именуемое в дальнейшем "Заказчик", в лице {c["dir"]} ФИО, '
f'и {pools.PROVIDER}, именуемое в дальнейшем "Исполнитель", в лице {pools.PROVIDER_DIR}, '
f'заключили настоящее Соглашение о нижеследующем:')
_add_heading(doc, "1. ПРЕДМЕТ ДОПОЛНИТЕЛЬНОГО СОГЛАШЕНИЯ")
_add_para(doc, "Изменить Спецификацию и стоимость Услуг. Утвердить новую Спецификацию в соответствии с Приложением №1 к настоящему Соглашению.")
if ctx.get("services_install"):
_add_para(doc, "")
_add_para(doc, f"Общая стоимость Инсталляционных услуг составляет {ctx['install_total_str']} руб.")
_add_services_table(doc, ctx["services_install"], ctx["install_total_str"], with_end_date=False, label="Разовые услуги")
if ctx.get("services_monthly"):
_add_para(doc, "")
_add_para(doc, f"Общая стоимость Ежемесячных услуг составляет {ctx['monthly_total_str']} руб.")
_add_services_table(doc, ctx["services_monthly"], ctx["monthly_total_str"], with_end_date=True, label="Абонентские услуги")
_add_heading(doc, "ПРОЧИЕ УСЛОВИЯ")
_add_para(doc, "Во всём остальном, что не урегулировано настоящим Соглашением, действуют положения Договора.")
_add_heading(doc, "ЮРИДИЧЕСКИЕ АДРЕСА И РЕКВИЗИТЫ СТОРОН")
_add_parties_table(doc, c, pools.PROVIDER, pools.PROVIDER_DIR, pools.CITY, date)
return doc
def _month_name(m):
months = ["", "января", "февраля", "марта", "апреля", "мая", "июня",
"июля", "августа", "сентября", "октября", "ноября", "декабря"]
return months[m] if 1 <= m <= 12 else str(m)