/
jukeze
/
UPD
Обзор
Документация
Войти
/
jukeze
/
UPD
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
upd_tool.py
464 строки
20 KB
jukeze
upload files
25 фев 2026, 11:06
Верифицирован
25 фев 2026, 11:06
6a7d4b3
Код
Авторство
О чём код?
import pdfplumber import re import tkinter as tk from tkinter import filedialog, messagebox from lxml import etree from datetime import datetime import os import uuid # ================= НАСТРОЙКИ (ТОЛЬКО ПРОДАВЕЦ!) ================= SELLER_NAME = "ИП Войтюль Татьяна Васильевна" SELLER_INN = "575206870319" SELLER_ADDRESS = "г. Севастополь, ул. Тургенева, д. 47" SELLER_BANK = "МОСКОВСКИЙ ФИЛИАЛ АО КБ МОДУЛЬБАНК" SELLER_BIK = "044525092" SELLER_ACCOUNT = "40802810670010031716" SELLER_KORSCHET = "30101810645250000092" SELLER_PHONE = "+7-905-168-54-48" SELLER_EMAIL = "vseroliki@ya.ru" OKEI_CODE = "796" CURRENCY_CODE = "643" CURRENCY_NAME = "Российский рубль" # ================================================================ def clean_text(text): if not text: return "" text = re.sub(r'\s+', ' ', str(text)) return text.strip() def clean_company_name(name): if not name: return "NONAME" prefixes = [ r'ОБЩЕСТВО\s+С\s+ОГРАНИЧЕННОЙ\s+ОТВЕТСТВЕННОСТЬЮ\s*"?', r'ООО\s*"?', r'АКЦИОНЕРНОЕ\s+ОБЩЕСТВО\s*"?', r'АО\s*"?', r'ПАО\s*"?', r'ИП\s*"?', ] for prefix in prefixes: name = re.sub(prefix, '', name, flags=re.IGNORECASE) name = re.sub(r'^["\']|["\']$', '', name) return name.strip() def clean_filename(text): if not text: return "NONAME" text = clean_company_name(text) text = re.sub(r'[\\/:*?"<>|]', '', str(text)) text = re.sub(r'[\s\-]+', '_', text) text = re.sub(r'_+', '_', text)[:40] return text.strip('_').upper() def parse_month(month_str): months = { "января": "01", "февраля": "02", "марта": "03", "апреля": "04", "мая": "05", "июня": "06", "июля": "07", "августа": "08", "сентября": "09", "октября": "10", "ноября": "11", "декабря": "12" } return months.get(month_str.lower(), "01") def parse_address(addr_text): """✅ Извлекает компоненты адреса из строки (ЛЮБОЙ регион)""" result = { "index": "", "region_code": "59", "region": "Пермский край", "city": "", "street": "", "building": "" } if not addr_text: return result addr_text = clean_text(addr_text) parts = [p.strip() for p in addr_text.split(',') if p.strip()] for part in parts: part = part.strip() if not part: continue # Индекс (6 цифр) if re.match(r'^\d{6}$', part): result["index"] = part # Регион - ВСЕ ВОЗМОЖНЫЕ ВАРИАНТЫ elif 'Пермский край' in part: result["region"] = "Пермский край" result["region_code"] = "59" elif 'Орловская область' in part or 'Орловская Область' in part: result["region"] = "Орловская область" result["region_code"] = "57" elif 'Севастополь' in part and 'край' not in part and 'область' not in part: result["region"] = "Севастополь" result["region_code"] = "92" elif 'Москва' in part: result["region"] = "Москва" result["region_code"] = "77" elif 'Санкт-Петербург' in part: result["region"] = "Санкт-Петербург" result["region_code"] = "78" elif 'край' in part.lower(): result["region"] = part elif 'область' in part.lower() or 'Область' in part: result["region"] = part # Город elif re.match(r'^г\.?\s*[А-Яа-яЁё\-]+$', part, re.IGNORECASE): result["city"] = re.sub(r'^г\.?\s*', '', part, flags=re.IGNORECASE) # Улица elif re.match(r'^(ул\.?|пр\.?|пер\.?|бул\.?|ал\.?|ш\.?|наб\.?)\s*[А-Яа-яЁё\-\s]+$', part, re.IGNORECASE): result["street"] = part # Номер дома elif re.match(r'^д\.?\s*\d+[А-Яа-яЁё\-\.]*$', part, re.IGNORECASE): if not result["building"]: result["building"] = part if part.lower().startswith('д') else 'д. ' + part elif re.match(r'^\d+[А-Яа-яЁё\-\.]*$', part) and not result["building"]: result["building"] = "д. " + part return result def make_filename(data): number = data.get("number", "0000") buyer = clean_filename(data.get("buyer_name", "UNKNOWN")) doc_date = data.get("date", datetime.now().strftime("%Y-%m-%d")) if not buyer or buyer == "NONAME": buyer = "CLIENT" return f"UPD_{number}_{buyer}_{doc_date}.xml" def parse_pdf(path): data = {} items = [] with pdfplumber.open(path) as pdf: page = pdf.pages[0] text = page.extract_text() # Номер документа num = re.search(r"Акт выполненных работ №\s*(\d+)", text) data["number"] = num.group(1) if num else "1" # Дата документа date = re.search(r"от\s+(\d{1,2})\s+(\S+)\s+(\d{4})", text) if date: day = date.group(1).zfill(2) month = parse_month(date.group(2)) year = date.group(3) data["date"] = f"{year}-{month}-{day}" else: data["date"] = datetime.now().strftime("%Y-%m-%d") # ✅ ДАННЫЕ ЗАКАЗЧИКА - ВСЕ ИЗ PDF! # Захватываем ВСЁ после КПП (включая адрес на следующей строке) buyer = re.search(r'Заказчик:\s*(.+?),\s*ИНН\s*(\d+),\s*КПП\s*(\d+)[,\s\n]*(.+?)(?=\n\n|\n[А-Я]|\Z)', text, re.DOTALL) if buyer: data["buyer_name"] = clean_text(buyer.group(1)) data["buyer_inn"] = buyer.group(2).strip() data["buyer_kpp"] = buyer.group(3).strip() # Адрес идёт после КПП addr_text = clean_text(buyer.group(4)) data["buyer_address"] = parse_address(addr_text) else: data["buyer_name"] = "Неизвестный заказчик" data["buyer_inn"] = "0000000000" data["buyer_kpp"] = "" data["buyer_address"] = parse_address("") # Таблица с услугами tables = page.extract_tables() for table in tables: for row in table: if not row: continue row_text = ''.join(str(cell) for cell in row if cell).lower() if 'наименование' in row_text or 'итого' in row_text: continue row_num = None name_idx = -1 for i, cell in enumerate(row): if cell and str(cell).strip().isdigit(): val = int(str(cell).strip()) if 1 <= val <= 99: row_num = val name_idx = i + 1 break if row_num and name_idx < len(row): name = row[name_idx] if name_idx < len(row) else "" numbers = [] for i, cell in enumerate(row): if cell: cleaned = re.sub(r'[^\d,.]', '', str(cell)) if cleaned and (',' in cleaned or '.' in cleaned): try: numbers.append((i, float(cleaned.replace(',', '.')))) except: pass if len(numbers) >= 2: price_val = numbers[-2][1] sum_val = numbers[-1][1] items.append({ "num": row_num, "name": clean_text(name), "qty": "1", "price": f"{price_val:.2f}", "sum": f"{sum_val:.2f}" }) items.sort(key=lambda x: x["num"]) data["items"] = items total = sum(float(item["sum"]) for item in items) data["total"] = f"{total:.2f}" return data def generate_xml(data, save_path): file_guid = str(uuid.uuid4()) file_id = f"ON_NSCHFDOPPR_{SELLER_INN}_{data['buyer_inn']}_{datetime.now().strftime('%Y%m%d')}_{file_guid}_1_1_0_0_1_00" root = etree.Element("Файл", ВерсФорм="5.03", ВерсПрог="UPD_Tool 1.0", ИдФайл=file_id) doc = etree.SubElement(root, "Документ", КНД="1115131", Функция="СЧФДОП", ДатаИнфПр=datetime.now().strftime("%d.%m.%Y"), ВремИнфПр=datetime.now().strftime("%H.%M.%S"), НаимЭконСубСост=SELLER_NAME, НаимДокОпр="Универсальный передаточный документ") date_parts = data["date"].split("-") formatted_date = f"{date_parts[2]}.{date_parts[1]}.{date_parts[0]}" # ✅ 1. СвСчФакт sv = etree.SubElement(doc, "СвСчФакт", НомерДок=data["number"], ДатаДок=formatted_date) # ✅ ИспрДок - НомИспр="1" для ОРИГИНАЛА! etree.SubElement(sv, "ИспрДок", НомИспр="1", ДатаИспр=formatted_date) # ✅ СвПрод (ПРОДАВЕЦ - ПОСТОЯННЫЕ ДАННЫЕ) prod = etree.SubElement(sv, "СвПрод", КодОПФ="12200", ПолнНаимОПФ="Индивидуальный предприниматель") id_prod = etree.SubElement(prod, "ИдСв") sv_ip = etree.SubElement(id_prod, "СвИП", ИННФЛ=SELLER_INN) etree.SubElement(sv_ip, "ФИО", Фамилия="Войтюль", Имя="Татьяна", Отчество="Васильевна") addr_prod = etree.SubElement(prod, "Адрес") etree.SubElement(addr_prod, "АдрРФ", КодРегион="92", НаимРегион="Севастополь", Индекс="299011", Город="Севастополь", Улица="ул. Тургенева", Дом="д. 47") bank_prod = etree.SubElement(prod, "БанкРекв", НомерСчета=SELLER_ACCOUNT) etree.SubElement(bank_prod, "СвБанк", НаимБанк=SELLER_BANK, БИК=SELLER_BIK, КорСчет=SELLER_KORSCHET) contact_prod = etree.SubElement(prod, "Контакт") etree.SubElement(contact_prod, "Тлф").text = SELLER_PHONE etree.SubElement(contact_prod, "ЭлПочта").text = SELLER_EMAIL # ✅ СвПокуп (ПОКУПАТЕЛЬ - ДАННЫЕ ИЗ PDF!) pok = etree.SubElement(sv, "СвПокуп", КодОПФ="12300", ПолнНаимОПФ="Общество с ограниченной ответственностью") id_pok = etree.SubElement(pok, "ИдСв") inn = data["buyer_inn"] if len(inn) == 10: etree.SubElement(id_pok, "СвЮЛУч", НаимОрг=data["buyer_name"], ИННЮЛ=inn, КПП=data["buyer_kpp"]) else: etree.SubElement(id_pok, "СвИП", ИННФЛ=inn) # ✅ Адрес покупателя - ИЗ PDF (не жестко заданный!) addr = data.get("buyer_address", {}) addr_pok = etree.SubElement(pok, "Адрес") etree.SubElement(addr_pok, "АдрРФ", КодРегион=addr.get("region_code", "59"), НаимРегион=addr.get("region", "Пермский край"), Индекс=addr.get("index", ""), Город=addr.get("city", ""), Улица=addr.get("street", ""), Дом=addr.get("building", "")) etree.SubElement(sv, "ДенИзм", КодОКВ=CURRENCY_CODE, НаимОКВ=CURRENCY_NAME, КурсВал="1") # ✅ 2. СвПродПер sv_prod_per = etree.SubElement(doc, "СвПродПер") sv_per = etree.SubElement(sv_prod_per, "СвПер", СодОпер="Услуги оказаны", ДатаПер=formatted_date) osn_per = etree.SubElement(sv_per, "ОснПер", РеквНаимДок="Акт выполненных работ", РеквНомерДок=data["number"], РеквДатаДок=formatted_date) rekvid_osn = etree.SubElement(osn_per, "РеквИдРекСост") etree.SubElement(rekvid_osn, "ДаннИно", КодСтр="112", НаимСтран="Россия", Наим="Акт выполненных работ", ИдСтат="ИГ", ИныеСвед="Акт выполненных работ") # ✅ 3. ТаблСчФакт - ПЕРЕД Подписант table = etree.SubElement(doc, "ТаблСчФакт") for item in data["items"]: item_elem = etree.SubElement(table, "СведТов", НомСтр=str(item["num"]), НаимТов=item["name"], ОКЕИ_Тов=OKEI_CODE, НаимЕдИзм="усл", КолТов=item["qty"], ЦенаТов=item["price"], СтТовБезНДС=item["sum"], НалСт="без НДС", СтТовУчНал=item["sum"]) accise = etree.SubElement(item_elem, "Акциз") etree.SubElement(accise, "БезАкциз").text = "без акциза" tax = etree.SubElement(item_elem, "СумНал") etree.SubElement(tax, "БезНДС").text = "без НДС" # ✅ Добавляем КолТовВсего total_qty = sum(float(item["qty"]) for item in data["items"]) total_elem = etree.SubElement(table, "ВсегоОпл", СтТовБезНДСВсего=data["total"], СтТовУчНалВсего=data["total"], КолТовВсего=f"{total_qty:.2f}") sum_nal = etree.SubElement(total_elem, "СумНалВсего") etree.SubElement(sum_nal, "БезНДС").text = "без НДС" # ✅ 4. Подписант - ПОСЛЕ ТаблСчФакт signer = etree.SubElement(doc, "Подписант", Должн="Индивидуальный предприниматель", ТипПодпис="1", ДатаПодДок=datetime.now().strftime("%d.%m.%Y"), СпосПодтПолном="4") etree.SubElement(signer, "ФИО", Фамилия="Войтюль", Имя="Татьяна", Отчество="Васильевна") # ✅ 5. ОснДоверОргСост - ПОСЛЕ Подписант osn = etree.SubElement(doc, "ОснДоверОргСост", РеквНаимДок="Устав", РеквНомерДок="1", РеквДатаДок="01.01.2020") rekvid = etree.SubElement(osn, "РеквИдРекСост") etree.SubElement(rekvid, "ДаннИно", КодСтр="112", НаимСтран="Россия", Наим="Устав", ИдСтат="ИГ", ИныеСвед="Устав индивидуального предпринимателя") xml_bytes = etree.tostring(root, encoding='utf-8', pretty_print=True, xml_declaration=True) xml_string = xml_bytes.decode('utf-8') xml_string = xml_string.replace("encoding='utf-8'", "encoding='windows-1251'") with open(save_path, 'wb') as f: f.write(xml_string.encode('windows-1251')) def open_file(): path = filedialog.askopenfilename(filetypes=[("PDF files", "*.pdf")]) if not path: return try: data = parse_pdf(path) if not data["items"]: messagebox.showwarning("Внимание", "Услуги не найдены в таблице.\nПроверьте формат PDF.") return default_name = make_filename(data) save_path = filedialog.asksaveasfilename( defaultextension=".xml", filetypes=[("XML files", "*.xml")], initialfile=default_name ) if save_path: generate_xml(data, save_path) addr = data.get("buyer_address", {}) messagebox.showinfo("Готово", f"XML создан успешно!\n\n" f"Файл: {os.path.basename(save_path)}\n" f"Заказчик: {data['buyer_name']}\n" f"ИНН: {data['buyer_inn']}\n" f"КПП: {data['buyer_kpp']}\n" f"Адрес: {addr.get('index','')} {addr.get('region','')} {addr.get('city','')} {addr.get('street','')} {addr.get('building','')}\n" f"Услуг: {len(data['items'])}\n" f"Сумма: {data['total']} ₽") except Exception as e: messagebox.showerror("Ошибка", f"Произошла ошибка:\n{str(e)}") if __name__ == "__main__": app = tk.Tk() app.title("Акт → УПД 2 Конвертер (Услуги)") app.geometry("450x300") app.resizable(False, False) label = tk.Label(app, text="Конвертер Актов в УПД 2 для Диадока", font=("Arial", 12, "bold")) label.pack(pady=20) info = tk.Label(app, text="Формат: 5.03 (Приказ ЕД-7-26/970@)\nУПД 2: Услуги (без НДС)\nКодировка: windows-1251\nТел: +7-905-168-54-48", font=("Arial", 9), fg="gray", justify="center") info.pack(pady=10) btn = tk.Button(app, text="📂 Выбрать PDF и создать XML", command=open_file, height=2, width=35, bg="#4CAF50", fg="white", font=("Arial", 11, "bold")) btn.pack(pady=20) app.mainloop()