first commit

This commit is contained in:
Dvorinka
2025-07-16 13:57:08 +02:00
commit d1614d78d5
10 changed files with 1824 additions and 0 deletions
+521
View File
@@ -0,0 +1,521 @@
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import re
from datetime import datetime, date
import os
import openpyxl
from openpyxl.styles import Font, Alignment
from openpyxl.utils import get_column_letter
class EDIDelforParser:
def __init__(self, filepath=None):
self.root = tk.Tk()
self.root.title("EDI MINEBEA Parser")
self.root.geometry("1200x800")
# Hlavní data
self.header_info = {}
self.partner_info = {}
self.delivery_schedules = []
# Handle window close event
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
self.setup_ui()
# If filepath was provided, load it automatically
if filepath:
self.load_file(filepath)
def setup_ui(self):
# Hlavní frame
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Tlačítka pro ovládání
btn_frame = ttk.Frame(main_frame)
btn_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Button(btn_frame, text="Export do Excelu", command=self.export_to_excel).pack(side=tk.LEFT)
ttk.Button(btn_frame, text="Zpět na hlavní okno", command=self.back_to_main).pack(side=tk.LEFT, padx=(10, 0))
# Notebook pro záložky
self.notebook = ttk.Notebook(main_frame)
self.notebook.pack(fill=tk.BOTH, expand=True)
# Záložka - Základní informace
self.info_frame = ttk.Frame(self.notebook)
self.notebook.add(self.info_frame, text="Základní informace")
# Záložka - Dodávky
self.delivery_frame = ttk.Frame(self.notebook)
self.notebook.add(self.delivery_frame, text="Plán dodávek")
# Záložka - Statistiky
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):
# Scrollable text widget pro základní informace
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 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 setup_delivery_tab(self):
# Treeview pro plán dodávek
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)
# Definice sloupců
for col in columns:
self.delivery_tree.heading(col, text=col)
self.delivery_tree.column(col, width=120)
# Scrollbary
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):
# Statistiky
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):
"""Parsuje datum podle EDI formátu"""
try:
if format_code == '203': # CCYYMMDDHHMMSS
# Return only date part without time
return datetime.strptime(date_str, '%Y%m%d%H%M%S').strftime('%d.%m.%Y')
elif format_code == '102': # CCYYMMDD
return datetime.strptime(date_str, '%Y%m%d').strftime('%d.%m.%Y')
else:
return date_str.split(' ')[0] # Return only date part if time is present
except Exception as e:
print(f"Error parsing date {date_str} with format {format_code}: {e}")
return date_str.split(' ')[0] if date_str else ''
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(':')
# Přidáme 20 na začátek roku (předpokládáme 21. století)
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 parse_edi_file(self, content):
"""Parsuje EDI DELFOR soubor"""
lines = content.strip().split("'")
# Reset dat
self.header_info = {}
self.partner_info = {}
self.delivery_schedules = []
current_delivery = {}
for line in lines:
line = line.strip()
if not line:
continue
# UNB - Interchange header
if line.startswith('UNB'):
parts = line.split('+')
if len(parts) >= 5:
self.header_info['Odesílatel'] = parts[2]
# Uložíme kód příjemce, název doplníme později z NAD segmentu
self.header_info['Příjemce_kód'] = parts[3]
self.header_info['Datum/Čas'] = self.parse_edi_datetime(parts[4])
# BGM - Beginning of message
elif line.startswith('BGM'):
parts = line.split('+')
if len(parts) >= 3:
self.header_info['Číslo zprávy'] = parts[2]
# DTM - Date/time
elif line.startswith('DTM'):
parts = line.split('+')
if len(parts) >= 2:
dtm_parts = parts[1].split(':')
if len(dtm_parts) >= 3:
date_formatted = self.parse_date(dtm_parts[1], dtm_parts[2])
if dtm_parts[0] == '137':
self.header_info['Datum dokumentu'] = date_formatted
elif dtm_parts[0] == '63':
current_delivery['Datum do'] = date_formatted
elif dtm_parts[0] == '64':
current_delivery['Datum od'] = date_formatted
# NAD - Name and address
elif line.startswith('NAD'):
parts = line.split('+')
if len(parts) >= 3:
role = parts[1]
code = parts[2] if len(parts) > 2 else ''
# Debug - vypíšeme co parsujeme
print(f"NAD Debug - Role: {role}, Code: {code}, Parts: {parts}")
# Název společnosti je v parts[4] (index 4)
name = parts[4] if len(parts) > 4 else ''
# Adresa začíná od parts[5]
address_parts = []
for i in range(5, len(parts)):
if parts[i]: # Přidáme pouze neprázdné části
address_parts.append(parts[i])
full_address = ', '.join(address_parts) if address_parts else ''
if role == 'BY':
self.partner_info['Kupující'] = name
if full_address:
self.partner_info['Kupující'] += f", {full_address}"
elif role == 'SE':
# Zkontrolujeme, zda SE obsahuje kód příjemce z UNB
print(f"SE Debug - Checking code: {code}, name: {name}")
if '1000500120' in code:
print(f"Found matching code! Setting recipient to: {name}")
self.header_info['Příjemce'] = name
# Pro prodávajícího použijeme název + adresu
if full_address:
self.partner_info['Prodávající'] = f"{name}, {full_address}"
else:
self.partner_info['Prodávající'] = name
elif role == 'CN':
if full_address:
self.partner_info['Dodací adresa'] = f"{name}, {full_address}"
else:
self.partner_info['Dodací adresa'] = name
# LIN - Line item
elif line.startswith('LIN'):
parts = line.split('+')
if len(parts) >= 4:
self.header_info['Číslo položky'] = parts[3]
# PIA - Product identification
elif line.startswith('PIA'):
parts = line.split('+')
if len(parts) >= 3:
self.header_info['Kód produktu'] = parts[2]
# QTY - Quantity
elif line.startswith('QTY'):
parts = line.split('+')
if len(parts) >= 2:
qty_parts = parts[1].split(':')
if len(qty_parts) >= 3:
qty_type = qty_parts[0]
quantity = qty_parts[1]
unit = qty_parts[2]
if qty_type == '113': # Cumulative quantity
current_delivery['Množství'] = quantity
current_delivery['Jednotka'] = unit
current_delivery['Typ'] = 'Kumulativní'
elif qty_type == '70': # Minimum quantity
current_delivery['Množství'] = quantity
current_delivery['Jednotka'] = unit
current_delivery['Typ'] = 'Minimální'
elif qty_type == '78': # Maximum quantity
current_delivery['Množství'] = quantity
current_delivery['Jednotka'] = unit
current_delivery['Typ'] = 'Maximální'
# SCC - Scheduling conditions
elif line.startswith('SCC'):
parts = line.split('+')
if len(parts) >= 2:
current_delivery['SCC'] = parts[1]
# Pokud máme kompletní dodávku, přidáme ji
if 'Datum od' in current_delivery and 'Množství' in current_delivery:
self.delivery_schedules.append(current_delivery.copy())
current_delivery = {'SCC': parts[1]} # Zachováme SCC pro další dodávky
def load_file(self, filepath):
"""Načte EDI soubor"""
try:
# Check if the window still exists
if not hasattr(self, 'root') or not self.root.winfo_exists():
return False
with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
content = f.read()
self.parse_edi_file(content)
# Check again before updating UI
if hasattr(self, 'root') and self.root.winfo_exists():
self.display_data()
return True
return False
except Exception as e:
# Safely show error if window still exists
if hasattr(self, 'root') and self.root.winfo_exists():
messagebox.showerror("Chyba", f"Nelze načíst soubor: {str(e)}")
return False
def display_data(self):
"""Zobrazí naparsovaná data"""
# Check if window still exists
if not hasattr(self, 'info_text') or not hasattr(self, 'root') or not self.root.winfo_exists():
return
try:
# Základní informace
self.info_text.delete(1.0, tk.END)
info_content = "=== HLAVIČKA DOKUMENTU ===\n"
for key, value in self.header_info.items():
# Přeskočíme pomocný klíč
if key != 'Příjemce_kód':
info_content += f"{key}: {value}\n"
# Pokud nemáme název příjemce, zobrazíme alespoň kód
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"
except Exception as e:
# Skip if there's an error during display
print(f"Error displaying data: {e}")
return
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)
# Plán dodávek
for item in self.delivery_tree.get_children():
self.delivery_tree.delete(item)
for delivery in self.delivery_schedules:
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
))
# Statistiky
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"
# Statistiky podle typu
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):
"""Převede řetězec s datem na číslo kalendářního týdne (WW)"""
if not date_str:
return ""
try:
# Handle case where time might be included
date_part = date_str.split(' ')[0]
day, month, year = map(int, date_part.split('.'))
# Handle 2-digit year
if year < 100:
year += 2000 # Assuming 21st century for 2-digit years
dt = date(year, month, day)
week_num = dt.isocalendar()[1]
return week_num
except Exception as e:
print(f"Error getting week number from {date_str}: {e}")
return ""
def export_to_excel(self):
"""Exportuje data o dodávkách do Excelu s kalendářními týdny"""
if not self.delivery_schedules:
messagebox.showwarning("Upozornění", "Žádná data k exportu")
return
try:
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "Dodávky"
# Hlavičky
headers = ["Týden", "Datum od", "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 delivery in self.delivery_schedules:
date_from = delivery.get('Datum od', '')
week_num = self.get_week_number(date_from) if date_from else ""
scc_code = delivery.get('SCC', '')
scc_desc = self.get_scc_description(scc_code)
# Format week number as number
try:
week_num = int(week_num) if week_num else 0
except (ValueError, TypeError):
week_num = 0
# Format quantity as number, removing any leading quotes
quantity = delivery.get('Množství', '')
if isinstance(quantity, str):
quantity = quantity.strip("'")
try:
quantity = float(quantity) if quantity else 0
except (ValueError, TypeError):
quantity = delivery.get('Množství', '')
# Format SCC as number if possible
scc = delivery.get('SCC', '')
try:
scc = int(scc) if scc.strip() else ''
except (ValueError, AttributeError):
pass
# Add week number
ws.cell(row=row_num, column=1, value=week_num)
# Format date as Excel date - handle both with and without time
date_from = delivery.get('Datum od', '')
try:
if date_from:
# Remove time part if present
date_from = date_from.split(' ')[0]
date_from_obj = datetime.strptime(date_from, '%d.%m.%Y')
ws.cell(row=row_num, column=2, value=date_from_obj).number_format = 'DD.MM.YYYY'
except Exception as e:
print(f"Error formatting date: {e}")
ws.cell(row=row_num, column=2, value=date_from.split(' ')[0] if date_from else '')
# Add other data with proper number formatting
ws.cell(row=row_num, column=3, value=quantity) # Use formatted quantity
ws.cell(row=row_num, column=4, value=delivery.get('Typ', ''))
# Use SCC description instead of code
scc_desc = self.get_scc_description(str(scc))
ws.cell(row=row_num, column=5, value=scc_desc)
ws.cell(row=row_num, column=6, value=self.partner_info.get('Dodací adresa', '') or 'XTREME PRESSURE INJECTION JUAREZ, REC LOC 372, EL PASO, 79927') # Add delivery location
row_num += 1
# Apply number formatting to all numeric columns
for col in range(1, 8):
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)):
if col == 1: # Week number
cell.number_format = '0'
elif col == 4: # Quantity
cell.number_format = '0'
elif col == 6: # SCC
cell.number_format = '0'
# Automatické přizpůsobení šířky sloupců
for col in ws.columns:
max_length = 0
column = col[0].column_letter
for cell in col:
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].width = min(adjusted_width, 30)
# Uložení souboru
filename = f"dodavky_minebea_{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 on_closing(self):
"""Handle window close event"""
self.root.destroy() # Close the current window
def back_to_main(self):
"""Closes the current window"""
self.root.destroy()
def run(self):
"""Spustí aplikaci"""
self.root.mainloop()
if __name__ == "__main__":
app = EDIDelforParser()
app.run()