Files
EDI_DELFOR_Parser/edi_parser_minebea.py
T
2025-07-16 13:57:08 +02:00

521 lines
23 KiB
Python

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