Files
EDI_DELFOR_Parser/edi_parser_trwkob.py
T
2025-07-17 14:57:26 +02:00

500 lines
23 KiB
Python

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from datetime import datetime, date
import os
import openpyxl
from openpyxl.styles import Font, Alignment
from openpyxl.utils import get_column_letter
class EDITrwkobParser:
def __init__(self, filepath=None):
self.root = tk.Tk()
self.root.title("EDI TRWKOB Parser")
self.root.geometry("1200x800")
self.header_info = {}
self.partner_info = {}
self.delivery_schedules = []
self.setup_ui()
self.main_window = None
def setup_ui(self):
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
btn_frame = ttk.Frame(main_frame)
btn_frame.pack(fill=tk.X, pady=(0, 10))
# Styly pro tlačítka
style = ttk.Style()
style.configure('Excel.TButton',
background='#217346', # Excel zelená barva
foreground='white',
font=('Segoe UI', 10, 'bold'),
padding=5)
# Vytvoření tlačítek s odsazením a styly
btn_back = ttk.Button(btn_frame, text="Zpět na hlavní okno", command=self.back_to_main)
btn_export = ttk.Button(btn_frame,
text="📊 Export do Excelu",
command=self.export_to_excel,
style='Excel.TButton')
# Uspořádání tlačítek s odsazením
btn_back.pack(side=tk.LEFT, padx=(0, 5))
btn_export.pack(side=tk.LEFT)
self.notebook = ttk.Notebook(main_frame)
self.notebook.pack(fill=tk.BOTH, expand=True)
self.info_frame = ttk.Frame(self.notebook)
self.notebook.add(self.info_frame, text="Základní informace")
self.delivery_frame = ttk.Frame(self.notebook)
self.notebook.add(self.delivery_frame, text="Plán dodávek")
self.stats_frame = ttk.Frame(self.notebook)
self.notebook.add(self.stats_frame, text="Statistiky")
self.setup_info_tab()
self.setup_delivery_tab()
self.setup_stats_tab()
def setup_info_tab(self):
text_frame = ttk.Frame(self.info_frame)
text_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.info_text = tk.Text(text_frame, wrap=tk.WORD, font=('Courier', 10))
scrollbar = ttk.Scrollbar(text_frame, orient=tk.VERTICAL, command=self.info_text.yview)
self.info_text.configure(yscrollcommand=scrollbar.set)
self.info_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
def setup_delivery_tab(self):
tree_frame = ttk.Frame(self.delivery_frame)
tree_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
columns = ('Datum od', 'Množství', 'Typ', 'SCC')
self.delivery_tree = ttk.Treeview(tree_frame, columns=columns, show='headings', height=15)
# Set column widths
self.delivery_tree.column('Datum od', width=100)
self.delivery_tree.column('Množství', width=100)
self.delivery_tree.column('Typ', width=150)
self.delivery_tree.column('SCC', width=200)
# Set column headings
for col in columns:
self.delivery_tree.heading(col, text=col)
v_scrollbar = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL, command=self.delivery_tree.yview)
h_scrollbar = ttk.Scrollbar(tree_frame, orient=tk.HORIZONTAL, command=self.delivery_tree.xview)
self.delivery_tree.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set)
self.delivery_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
def setup_stats_tab(self):
stats_frame = ttk.Frame(self.stats_frame)
stats_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.stats_text = tk.Text(stats_frame, wrap=tk.WORD, font=('Courier', 10))
stats_scrollbar = ttk.Scrollbar(stats_frame, orient=tk.VERTICAL, command=self.stats_text.yview)
self.stats_text.configure(yscrollcommand=stats_scrollbar.set)
self.stats_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
stats_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
def parse_date(self, date_str, format_code):
try:
if format_code == '102':
return datetime.strptime(date_str, '%Y%m%d').strftime('%d.%m.%Y')
else:
return date_str
except:
return date_str
def parse_edi_datetime(self, datetime_str):
"""Parsuje EDI datum/čas z UNB segmentu (YYMMDD:HHMM)"""
try:
if ':' in datetime_str:
date_part, time_part = datetime_str.split(':')
full_date = '20' + date_part
formatted_date = datetime.strptime(full_date, '%Y%m%d').strftime('%d.%m.%Y')
formatted_time = datetime.strptime(time_part, '%H%M').strftime('%H:%M')
return f"{formatted_date} {formatted_time}"
return datetime_str
except:
return datetime_str
def load_file(self, filepath):
"""Load and parse the specified EDI file"""
try:
with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
content = f.read()
self.parse_edi_file(content)
self.display_data()
return True
except Exception as e:
messagebox.showerror("Chyba", f"Nelze načíst soubor: {str(e)}")
return False
def parse_edi_file(self, content):
lines = content.strip().split("'")
self.header_info = {}
self.partner_info = {}
self.delivery_schedules = []
current_delivery = {}
# Helper function to add a delivery if it's complete
def add_delivery_if_complete(delivery):
if all(key in delivery for key in ['Datum od', 'Množství', 'Typ', 'SCC']):
# Add the delivery - we'll handle duplicates later
self.delivery_schedules.append(delivery.copy())
for line in lines:
line = line.strip()
if not line:
continue
if line.startswith('UNB'):
parts = line.split('+')
if len(parts) >= 5:
self.header_info['Odesílatel'] = parts[2]
self.header_info['Příjemce_kód'] = parts[3]
self.header_info['Datum/Čas'] = self.parse_edi_datetime(parts[4])
elif line.startswith('BGM'):
parts = line.split('+')
if len(parts) >= 3:
self.header_info['Číslo zprávy'] = parts[2]
elif line.startswith('DTM'):
parts = line.split('+')
if len(parts) >= 2:
dtm_parts = parts[1].split(':')
if len(dtm_parts) >= 2:
date_formatted = self.parse_date(dtm_parts[1], dtm_parts[2] if len(dtm_parts) > 2 else '')
if dtm_parts[0] == '137':
self.header_info['Datum dokumentu'] = date_formatted
elif dtm_parts[0] == '64': # Start date
current_delivery['Datum od'] = date_formatted
elif dtm_parts[0] == '63': # End date (we'll use this if no start date)
if 'Datum od' not in current_delivery:
current_delivery['Datum od'] = date_formatted
elif line.startswith('NAD'):
parts = line.split('+')
if len(parts) >= 3:
role = parts[1]
code = parts[2]
name = parts[4] if len(parts) > 4 else ''
address_parts = []
for i in range(5, len(parts)):
if parts[i]:
address_parts.append(parts[i])
full_address = ', '.join(address_parts) if address_parts else ''
if role == 'BY':
self.partner_info['Kupující'] = name if name else code
if full_address:
self.partner_info['Kupující'] += f", {full_address}"
elif role == 'SE':
self.header_info['Příjemce'] = name if name else code
if full_address:
self.partner_info['Prodávající'] = f"{name if name else code}, {full_address}"
else:
self.partner_info['Prodávající'] = name if name else code
elif role == 'CN':
if full_address:
self.partner_info['Dodací adresa'] = f"{name if name else code}, {full_address}"
else:
self.partner_info['Dodací adresa'] = name if name else code
elif line.startswith('LIN'):
parts = line.split('+')
if len(parts) >= 4:
self.header_info['Číslo položky'] = parts[3]
elif line.startswith('PIA'):
parts = line.split('+')
if len(parts) >= 3:
self.header_info['Kód produktu'] = parts[2]
elif line.startswith('QTY'):
parts = line.split('+')
if len(parts) >= 2:
qty_parts = parts[1].split(':')
if qty_parts:
qty_type = qty_parts[0]
quantity = qty_parts[1] if len(qty_parts) > 1 else ''
unit = qty_parts[2] if len(qty_parts) > 2 else 'PCE' # Default to PCE if not specified
# Only process QTY if we have a date and SCC
if 'Datum od' in current_delivery and 'SCC' in current_delivery:
if qty_type in ['113', '12']: # Plánované množství k dodání
current_delivery['Množství'] = quantity
current_delivery['Jednotka'] = unit
current_delivery['Typ'] = 'Plánované množství'
add_delivery_if_complete(current_delivery)
elif qty_type == '70':
current_delivery['Množství'] = quantity
current_delivery['Jednotka'] = unit
current_delivery['Typ'] = 'Minimální'
add_delivery_if_complete(current_delivery)
elif qty_type == '78':
current_delivery['Množství'] = quantity
current_delivery['Jednotka'] = unit
current_delivery['Typ'] = 'Maximální'
add_delivery_if_complete(current_delivery)
elif line.startswith('SCC'):
# Add the previous delivery if it's complete
if all(key in current_delivery for key in ['Datum od', 'Množství', 'Typ', 'SCC']):
add_delivery_if_complete(current_delivery)
parts = line.split('+')
if len(parts) >= 2:
# Start a new delivery with the SCC code
current_delivery = {'SCC': parts[1]}
# Handle end of delivery schedule
elif line.startswith('UNS+S'):
if all(key in current_delivery for key in ['Datum od', 'Množství', 'Typ', 'SCC']):
add_delivery_if_complete(current_delivery)
# After processing all lines, remove exact duplicates
unique_deliveries = []
seen = set()
for delivery in self.delivery_schedules:
# Create a unique key based on all fields
delivery_key = (
delivery.get('Datum od', ''),
delivery.get('Množství', ''),
delivery.get('Typ', ''),
delivery.get('SCC', ''),
delivery.get('Jednotka', '')
)
if delivery_key not in seen:
seen.add(delivery_key)
unique_deliveries.append(delivery)
# Replace the delivery schedules with the deduplicated list
self.delivery_schedules = unique_deliveries
def display_data(self):
self.info_text.delete(1.0, tk.END)
info_content = "=== HLAVIČKA DOKUMENTU ===\n"
for key, value in self.header_info.items():
if key != 'Příjemce_kód':
info_content += f"{key}: {value}\n"
if 'Příjemce' not in self.header_info and 'Příjemce_kód' in self.header_info:
info_content += f"Příjemce: {self.header_info['Příjemce_kód']}\n"
info_content += "\n=== INFORMACE O PARTNERECH ===\n"
for key, value in self.partner_info.items():
info_content += f"{key}: {value}\n"
self.info_text.insert(1.0, info_content)
# Clear the tree
for item in self.delivery_tree.get_children():
self.delivery_tree.delete(item)
# Process all deliveries without deduplication
deliveries_to_display = []
# Filter out 'Maximální' and 'Minimální' types
for delivery in self.delivery_schedules:
delivery_type = delivery.get('Typ', '')
# Skip 'Maximální' and 'Minimální' types
if delivery_type in ['Maximální', 'Minimální']:
continue
date_from = delivery.get('Datum od', '')
quantity = delivery.get('Množství', '')
# Skip if any required field is missing
if not (date_from and quantity):
continue
# Convert date strings to datetime objects for sorting
try:
date_obj = datetime.strptime(date_from, '%d.%m.%Y')
deliveries_to_display.append((date_obj, delivery))
except (ValueError, TypeError):
# If date parsing fails, keep the original order
deliveries_to_display.append((datetime.max, delivery))
# Sort deliveries by date (from oldest to newest)
deliveries_to_display.sort(key=lambda x: x[0])
# Add all deliveries to the tree
for date_obj, delivery in deliveries_to_display:
scc_code = delivery.get('SCC', '')
scc_desc = self.get_scc_description(scc_code)
self.delivery_tree.insert('', tk.END, values=(
delivery.get('Datum od', ''),
delivery.get('Množství', ''),
delivery.get('Typ', ''),
scc_desc
))
self.stats_text.delete(1.0, tk.END)
stats_content = "=== STATISTIKY ===\n"
stats_content += f"Celkový počet dodávek: {len(self.delivery_schedules)}\n"
total_qty = sum(int(d.get('Množství', 0)) for d in self.delivery_schedules if d.get('Množství', '').isdigit())
stats_content += f"Celkové množství: {total_qty:,} kusů\n"
type_stats = {}
for delivery in self.delivery_schedules:
delivery_type = delivery.get('Typ', 'Neznámý')
if delivery_type not in type_stats:
type_stats[delivery_type] = {'počet': 0, 'množství': 0}
type_stats[delivery_type]['počet'] += 1
if delivery.get('Množství', '').isdigit():
type_stats[delivery_type]['množství'] += int(delivery.get('Množství', 0))
stats_content += "\n=== STATISTIKY PODLE TYPU ===\n"
for delivery_type, stats in type_stats.items():
stats_content += f"{delivery_type}: {stats['počet']} dodávek, {stats['množství']:,} kusů\n"
self.stats_text.insert(1.0, stats_content)
def get_week_number(self, date_str):
"""Convert date string to ISO week number (WW)"""
try:
day, month, year = map(int, date_str.split('.'))
dt = date(year, month, day)
return dt.isocalendar()[1]
except (ValueError, AttributeError):
return ""
def get_scc_description(self, scc_code):
"""Convert SCC code to descriptive name"""
scc_mapping = {
'10': 'Backlog',
'1': 'Fix',
'4': 'Forecast',
'': 'Neznámé',
}
return scc_mapping.get(scc_code, f'Neznámý kód: {scc_code}')
def export_to_excel(self):
"""Export delivery data to Excel with calendar weeks, sorted chronologically and without duplicates"""
if not self.delivery_schedules:
messagebox.showwarning("Upozornění", "Žádná data k exportu")
return
try:
# Process all deliveries without deduplication
processed_deliveries = []
for delivery in self.delivery_schedules:
date_str = delivery.get('Datum od', '')
delivery_type = delivery.get('Typ', '')
# Skip 'Maximální' and 'Minimální' types and empty dates
if not date_str or delivery_type in ['Maximální', 'Minimální']:
continue
try:
# Parse date
date_obj = datetime.strptime(date_str, '%d.%m.%Y').date()
# Get delivery details
quantity = delivery.get('Množství', '').strip("'")
scc_code = delivery.get('SCC', '')
# Store with date object and original delivery for sorting
processed_deliveries.append((date_obj, {
'date_str': date_str,
'quantity': quantity,
'type': delivery_type,
'scc_code': scc_code,
'scc_desc': self.get_scc_description(scc_code),
'delivery': delivery
}))
except (ValueError, TypeError) as e:
print(f"Chyba při zpracování data: {date_str}, {e}")
continue
# Sort by date (earliest first)
processed_deliveries.sort(key=lambda x: x[0])
# Create Excel workbook and worksheet
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "Dodávky"
# Headers
headers = ["Týden", "Datum", "Množství", "Typ", "SCC", "Dodací místo"]
for col_num, header in enumerate(headers, 1):
cell = ws.cell(row=1, column=col_num, value=header)
cell.font = Font(bold=True)
cell.alignment = Alignment(horizontal='center')
# Data
row_num = 2
for date_obj, delivery_data in processed_deliveries:
# Add week number (ISO week)
ws.cell(row=row_num, column=1, value=date_obj.isocalendar()[1])
# Add date with proper Excel formatting
ws.cell(row=row_num, column=2, value=date_obj).number_format = 'DD.MM.YYYY'
# Add quantity as number
try:
quantity = float(delivery_data['quantity']) if delivery_data['quantity'] else 0
ws.cell(row=row_num, column=3, value=quantity)
except (ValueError, TypeError):
ws.cell(row=row_num, column=3, value=delivery_data['quantity'])
# Add type and SCC description as text
ws.cell(row=row_num, column=4, value=delivery_data['type']).number_format = '@'
ws.cell(row=row_num, column=5, value=delivery_data['scc_desc']).number_format = '@'
# Add delivery address
delivery_address = self.partner_info.get('Dodací adresa', '') or 'XTREME PRESSURE INJECTION JUAREZ, REC LOC 372, EL PASO, 79927'
ws.cell(row=row_num, column=6, value=delivery_address).number_format = '@'
row_num += 1
# Apply number formatting to numeric columns
for col in [1, 3]: # Only format week number and quantity columns
for row in ws.iter_rows(min_row=2, min_col=col, max_col=col):
for cell in row:
if isinstance(cell.value, (int, float)):
cell.number_format = '0'
# Auto-adjust column widths
for column in ws.columns:
max_length = 0
column_letter = get_column_letter(column[0].column)
for cell in column:
try:
# For dates, use the formatted string length
if hasattr(cell, 'is_date') and cell.is_date:
cell_value = cell.value.strftime('%d.%m.%Y') if cell.value else ''
else:
cell_value = str(cell.value) if cell.value is not None else ''
if len(cell_value) > max_length:
max_length = len(cell_value)
except:
pass
adjusted_width = (max_length + 2)
ws.column_dimensions[column_letter].width = min(adjusted_width, 30)
# Save the file with trwkob in the name
filename = f"dodavky_trwkob_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
filepath = filedialog.asksaveasfilename(
defaultextension=".xlsx",
filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")],
initialfile=filename
)
if filepath:
wb.save(filepath)
messagebox.showinfo("Hotovo", f"Data byla úspěšně exportována do souboru:\n{filepath}")
except Exception as e:
messagebox.showerror("Chyba", f"Chyba při exportu do Excelu: {str(e)}")
def back_to_main(self):
"""Closes the current window and returns to the main application"""
# Close current window
self.root.destroy()
# Return to main window
if self.main_window:
self.main_window.root.deiconify() # Show the main window if it exists
def run(self):
self.root.mainloop()
if __name__ == "__main__":
app = EDITrwkobParser()
app.run()