""" 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)