v1.0.178: генератор тестовых файлов (схема Опуса). 61 файл: 10 контрактов × (договор+спека v1+v2+допники) + ошибки + ZIP + дубликаты. Комментарий в каждом файле.
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user