240 lines
12 KiB
Python
240 lines
12 KiB
Python
"""
|
||
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
|
||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
||
import pools
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# Хелперы
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
|
||
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"]
|
||
|
||
# ── Комментарий в manifest.json, не в документе ──
|
||
|
||
_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)
|
||
|
||
_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)
|
||
|
||
_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)
|