commit d1614d78d5af2e05be25aa0569ab993b2237c4d6 Author: Dvorinka Date: Wed Jul 16 13:57:08 2025 +0200 first commit diff --git a/__pycache__/edi_parser_cummins.cpython-313.pyc b/__pycache__/edi_parser_cummins.cpython-313.pyc new file mode 100644 index 0000000..4d18e8a Binary files /dev/null and b/__pycache__/edi_parser_cummins.cpython-313.pyc differ diff --git a/__pycache__/edi_parser_main.cpython-313.pyc b/__pycache__/edi_parser_main.cpython-313.pyc new file mode 100644 index 0000000..8719486 Binary files /dev/null and b/__pycache__/edi_parser_main.cpython-313.pyc differ diff --git a/__pycache__/edi_parser_minebea.cpython-313.pyc b/__pycache__/edi_parser_minebea.cpython-313.pyc new file mode 100644 index 0000000..1ed0315 Binary files /dev/null and b/__pycache__/edi_parser_minebea.cpython-313.pyc differ diff --git a/__pycache__/edi_parser_trwkob.cpython-313.pyc b/__pycache__/edi_parser_trwkob.cpython-313.pyc new file mode 100644 index 0000000..a8e9354 Binary files /dev/null and b/__pycache__/edi_parser_trwkob.cpython-313.pyc differ diff --git a/__pycache__/styles.cpython-313.pyc b/__pycache__/styles.cpython-313.pyc new file mode 100644 index 0000000..14350bb Binary files /dev/null and b/__pycache__/styles.cpython-313.pyc differ diff --git a/build_nuitka.py b/build_nuitka.py new file mode 100644 index 0000000..4d5af48 --- /dev/null +++ b/build_nuitka.py @@ -0,0 +1,88 @@ +# build_nuitka.py +import os +import shutil +import subprocess +import sys +from pathlib import Path + +def run_command(command): + """Helper function to run shell commands""" + print(f"Running: {' '.join(command)}") + result = subprocess.run(command, capture_output=True, text=True) + if result.returncode != 0: + print("Error:", result.stderr) + sys.exit(1) + return result + +def main(): + # Configuration + script_name = "edi_parser_main.py" + app_name = "EDI_Parser" + icon_path = None # Set to path of your .ico file if you have one + output_dir = "dist" + temp_dir = "build" + + # Clean up previous builds + for dir_path in [output_dir, temp_dir]: + if os.path.exists(dir_path): + print(f"Cleaning up {dir_path}...") + shutil.rmtree(dir_path) + + # Create output directories + os.makedirs(output_dir, exist_ok=True) + + # Base Nuitka command + cmd = [ + "python", "-m", "nuitka", + "--standalone", + "--onefile", + "--windows-disable-console", # For GUI apps + f"--output-dir={output_dir}", + f"--windows-icon-from-ico={icon_path}" if icon_path else "", + f"--include-package=tkinter", + f"--include-package=openpyxl", + "--enable-plugin=tk-inter", + "--remove-output", + "--assume-yes-for-downloads", + "--follow-imports", + "--follow-import-to=*", + "--nofollow-import-to=*.test", + "--nofollow-import-to=*.tests", + "--nofollow-import-to=*.unittest", + "--nofollow-import-to=*.test_*", + "--nofollow-import-to=*conftest*", + "--nofollow-import-to=*pytest*", + "--nofollow-import-to=*setuptools*", + "--nofollow-import-to=*pip*", + "--nofollow-import-to=*distutils*", + "--nofollow-import-to=*numpy*", # Exclude if not needed + "--nofollow-import-to=*matplotlib*", # Exclude if not needed + "--windows-company-name=YourCompany", + f"--windows-file-version=1.0", + f"--windows-product-version=1.0", + f"--windows-file-description={app_name}", + f"--windows-product-name={app_name}", + script_name + ] + + # Remove empty strings from command list + cmd = [x for x in cmd if x] + + # Run Nuitka + print("Starting Nuitka compilation...") + result = run_command(cmd) + + # Move the final executable if needed + if os.name == 'nt': # Windows + exe_name = f"{app_name}.exe" + src = os.path.join(output_dir, script_name.replace('.py', '.exe')) + dst = os.path.join(".", exe_name) + if os.path.exists(dst): + os.remove(dst) + shutil.move(src, dst) + print(f"\nCompilation complete! Executable created: {exe_name}") + else: + print("\nCompilation complete! Check the dist/ directory for the output.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/edi_parser_cummins.py b/edi_parser_cummins.py new file mode 100644 index 0000000..dcdd0c2 --- /dev/null +++ b/edi_parser_cummins.py @@ -0,0 +1,651 @@ +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 EDIDelforCumminsParser: + def __init__(self, filepath=None): + self.root = tk.Tk() + self.root.title("EDI Cummins Parser") + self.root.geometry("1200x800") + self.header_info = {} + self.partner_info = {} + self.delivery_schedules = [] + self.line_items = [] + + # Handle window close event + self.root.protocol("WM_DELETE_WINDOW", self.on_closing) + + self.setup_ui() + if filepath: + self.load_file(filepath) + + 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)) + # Add buttons + ttk.Button(btn_frame, text="Zpět na hlavní okno", command=self.back_to_main).pack(side=tk.LEFT, padx=(10, 0)) + ttk.Button(btn_frame, text="Export do Excelu", command=self.export_to_excel).pack(side=tk.LEFT, padx=5) + 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) + # Removed 'Jednotka' column as requested + columns = ('Položka', 'Popis', 'Datum', 'Množství', 'Typ', 'SCC', 'Release') + self.delivery_tree = ttk.Treeview(tree_frame, columns=columns, show='headings', height=15) + for col in columns: + self.delivery_tree.heading(col, text=col) + if col == 'Popis': + self.delivery_tree.column(col, width=200) + elif col == 'Položka': + self.delivery_tree.column(col, width=100) + else: + self.delivery_tree.column(col, width=80) + 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): + 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 get_scc_description(self, scc_code): + scc_map = { + '10': 'Backlog', + '1': 'Firm', + '4': 'Forecast' + } + return scc_map.get(scc_code, f'SCC-{scc_code}') + + def parse_edi_file(self, content): + lines = content.strip().split("'") + self.header_info = {} + self.partner_info = {} + self.delivery_schedules = [] + self.line_items = [] + + # Current parsing state + current_part_number = '' + current_description = '' + current_location = '' + current_po = '' + current_scc = '' + current_release = '' + + # Track current line item details + current_line_item = None + + # Temporary storage for quantity waiting for date + pending_quantities = [] + + def create_or_update_line_item(): + nonlocal current_line_item + if not current_part_number: + return None + + line_item = next((item for item in self.line_items + if item['Položka'] == current_part_number), None) + + if not line_item: + line_item = { + 'Položka': current_part_number, + 'Popis': current_description, + 'Objednávka': current_po, + 'Lokace': current_location, + 'RFF': {} + } + self.line_items.append(line_item) + + # Update current line item reference + current_line_item = line_item + return line_item + + 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('UNH'): + parts = line.split('+') + if len(parts) >= 2: + self.header_info['ID zprávy'] = parts[1] + + 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) >= 3: + code = dtm_parts[0] + value = dtm_parts[1] + fmt = dtm_parts[2] + formatted_date = self.parse_date(value, fmt) + if code == '137': + self.header_info['Datum dokumentu'] = formatted_date + elif code == '2': + # This is a delivery date - match with pending quantities + # Only create entries if we have quantities to process + if pending_quantities: + # For SCC 10 (Backlog), we only take the first quantity + if current_scc == '10' and len(pending_quantities) > 0: + qty_info = pending_quantities[0] + # Create line item if it doesn't exist + line_item = next((item for item in self.line_items if item['Položka'] == current_part_number), None) + if not line_item: + line_item = { + 'Položka': current_part_number, + 'Popis': current_description, + 'Objednávka': current_po, + 'Lokace': current_location + } + self.line_items.append(line_item) + + delivery = { + 'Položka': current_part_number, + 'Popis': current_description, + 'Datum': formatted_date, + 'Množství': qty_info['quantity'], + 'Typ': qty_info['type'], + 'SCC': self.get_scc_description(current_scc), + 'Release': current_release, + 'Objednávka': current_po + } + self.delivery_schedules.append(delivery) + else: + # For other SCCs, process all quantities + for qty_info in pending_quantities: + delivery = { + 'Položka': current_part_number, + 'Popis': current_description, + 'Datum': formatted_date, + 'Množství': qty_info['quantity'], + 'Typ': qty_info['type'], + 'SCC': self.get_scc_description(current_scc), + 'Release': current_release + } + self.delivery_schedules.append(delivery) + pending_quantities.clear() + # Don't reset release here to maintain it for next entries + + elif line.startswith('NAD'): + parts = line.split('+') + if len(parts) >= 3: + role = parts[1] + if role == 'SU': # Supplier + name_parts = [p.replace('?+', '').replace('?', '').strip() for p in parts[4:] if p] + self.partner_info['Dodavatel'] = ' '.join(name_parts) + elif role == 'ST': # Ship To + name_parts = [p.replace('?+', '').replace('?', '').strip() for p in parts[4:] if p] + self.partner_info['Příjemce'] = ' '.join(name_parts) + # Store the full address for delivery location + if len(parts) > 5: # If there are address components + address_parts = [] + # Get address lines (parts[5] and beyond) + for part in parts[5:]: + if ':' in part: # Skip parts with qualifiers + break + address_parts.append(part.replace('?+', '').replace('?', '').strip()) + if address_parts: + self.partner_info['Dodací adresa'] = ', '.join(address_parts) + # If no specific address found, use the recipient name as fallback + if not self.partner_info['Dodací adresa'] and name_parts: + self.partner_info['Dodací adresa'] = ' '.join(name_parts) + + elif line.startswith('LIN'): + # Save previous line item if it exists + if current_part_number: + # Process previous line item if exists + create_or_update_line_item() + + parts = line.split('+') + # Process LIN segment + + if len(parts) >= 4: + # Reset part information for new line item + current_part_number = '' + current_description = '' + current_scc = '' + current_release = '' + current_line_item = None + pending_quantities = [] + + # Try to find part number in the LIN segment + for i, part in enumerate(parts[3:], 3): # Skip the first 3 parts (LIN, line number, action code) + # Process part + if ':' in part: # If the part contains a colon, it might be a part number + part_info = part.split(':') + # Process part info + if len(part_info) >= 2 and part_info[1] == 'IN': # Look for part number with 'IN' qualifier + current_part_number = part_info[0] + # Found part number with IN qualifier + break + elif not current_part_number: # If no 'IN' qualifier found, take the first part + current_part_number = part_info[0] + # Using first part as part number + + # If still no part number found, try to get it from the last part + if not current_part_number and parts[3:]: + current_part_number = parts[3].split(':')[0] + # Using fallback part number + + # Final part number processed + + elif line.startswith('IMD'): + parts = line.split('+') + if len(parts) >= 4: + # Extract item description - fixed to properly handle the format + # Looking for the 4th element which contains the description + desc_part = parts[3] if len(parts) > 3 else '' + + # Remove leading colons and extract the actual description + if desc_part.startswith(':::'): + current_description = desc_part[3:].strip() + elif desc_part.startswith('::'): + current_description = desc_part[2:].strip() + elif desc_part.startswith(':'): + current_description = desc_part[1:].strip() + else: + current_description = desc_part.strip() + + # Clean up any remaining formatting + current_description = current_description.replace(':', '').strip() + + elif line.startswith('LOC'): + parts = line.split('+') + if len(parts) >= 3: + current_location = parts[2] + + elif line.startswith('RFF'): + parts = line.split('+') + # Process RFF segment + + if len(parts) >= 2: + ref_parts = parts[1].split(':') + if len(ref_parts) >= 2: + ref_type = ref_parts[0] + ref_value = ref_parts[1] + + # Found RFF reference + + # Create or update line item if it doesn't exist + if not current_line_item: + # Create new line item if none exists + create_or_update_line_item() + + # Store the reference in the current line item + if current_line_item: + if 'RFF' not in current_line_item: + current_line_item['RFF'] = {} + current_line_item['RFF'][ref_type] = ref_value + # RFF stored in line item + + # Special handling for order numbers + if ref_type == 'ON': + current_po = ref_value + current_line_item['Objednávka'] = current_po + # Order number set + elif ref_type == 'RE': + current_release = ref_value + # Clear any pending quantities to ensure release number is applied to new quantities + pending_quantities = [] + # Release number set + else: + # No line item available for RFF + pass + + elif line.startswith('SCC'): + parts = line.split('+') + if len(parts) >= 2: + current_scc = parts[1] + # Clear pending quantities when new SCC starts to prevent duplicates + pending_quantities = [] + # Only reset release for backlog (SCC 10) + if current_scc == '10': + current_release = '' + + elif line.startswith('QTY'): + parts = line.split('+') + if len(parts) >= 2: + qty_parts = parts[1].split(':') + if len(qty_parts) >= 2: + qty_type = qty_parts[0] + quantity = qty_parts[1] + # Removed unit extraction as we don't need it + + # Determine quantity type + qty_type_desc = 'Neznámý' + if qty_type == '1': + qty_type_desc = 'Dodávka' + elif qty_type == '3': + qty_type_desc = 'Kumulativní' + elif qty_type == '48': + qty_type_desc = 'Plánované' + + # Store quantity info waiting for corresponding date + pending_quantities.append({ + 'quantity': quantity, + 'type': qty_type_desc + }) + + # Store line items for reference + unique_parts = {} + for delivery in self.delivery_schedules: + part_num = delivery['Položka'] + if part_num not in unique_parts: + unique_parts[part_num] = { + 'Položka': part_num, + 'Popis': delivery['Popis'] + } + + self.line_items = list(unique_parts.values()) + + def load_file(self, filepath=None): + if filepath: + try: + with open(filepath, 'r', encoding='utf-8') 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 + return False + + def display_data(self): + # Display header info + 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" + 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) + + # Display delivery schedules + for item in self.delivery_tree.get_children(): + self.delivery_tree.delete(item) + + # Sort deliveries by date + def date_sort_key(delivery): + date_str = delivery.get('Datum', '') + try: + return datetime.strptime(date_str, '%d.%m.%Y') if date_str else datetime.max + except: + return datetime.max + + sorted_deliveries = sorted(self.delivery_schedules, key=date_sort_key) + + for delivery in sorted_deliveries: + self.delivery_tree.insert('', tk.END, values=( + delivery.get('Položka', ''), + delivery.get('Popis', ''), + delivery.get('Datum', ''), + delivery.get('Množství', ''), + delivery.get('Typ', ''), + delivery.get('SCC', ''), + delivery.get('Release', '') + )) + + # Display statistics + 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" + stats_content += f"Počet různých položek: {len(self.line_items)}\n" + + # Group by SCC + scc_stats = {} + total_qty = 0 + for delivery in self.delivery_schedules: + scc = delivery.get('SCC', 'Neznámý') + qty_str = delivery.get('Množství', '0') + try: + qty = int(qty_str) + total_qty += qty + if scc not in scc_stats: + scc_stats[scc] = {'count': 0, 'total_qty': 0} + scc_stats[scc]['count'] += 1 + scc_stats[scc]['total_qty'] += qty + except: + pass + + stats_content += f"Celkové množství: {total_qty:,} kusů\n\n" + stats_content += "=== STATISTIKY PO SCC ===\n" + for scc, stats in scc_stats.items(): + stats_content += f"{scc}: {stats['count']} dodávek, {stats['total_qty']:,} kusů\n" + + self.stats_text.insert(1.0, stats_content) + + 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 get_week_number(self, date_str): + """Convert date string to ISO week number""" + try: + # Handle different date formats + if '.' in date_str: + date_obj = datetime.strptime(date_str, '%d.%m.%Y').date() + else: + date_obj = datetime.strptime(date_str, '%Y%m%d').date() + return date_obj.isocalendar()[1] # Returns ISO week number + except Exception as e: + print(f"Error parsing date {date_str}: {e}") + return "" + + def export_to_excel(self): + """Export delivery data to Excel with calendar weeks""" + 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" + + # Headers with week number + headers = ["Týden", "Datum", "Položka", "Popis", "Množství", "Typ", "SCC", "Release", "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') + + # Add data - sort by date from oldest to newest + def parse_date(date_str): + try: + if '.' in date_str: + return datetime.strptime(date_str, '%d.%m.%Y').date() + else: + return datetime.strptime(date_str, '%Y%m%d').date() + except: + return datetime.min.date() + + row_num = 2 + for item in sorted(self.delivery_schedules, key=lambda x: parse_date(x.get('Datum', ''))): + # Get week number from date + week_num = self.get_week_number(item.get('Datum', '')) + + # Format quantity as number, removing any leading quotes + quantity = item.get('Množství', '') + if isinstance(quantity, str): + quantity = quantity.strip("'") + try: + quantity = float(quantity) if quantity else 0 + except (ValueError, TypeError): + quantity = item.get('Množství', '') + + # Format week number as number + try: + week_num = int(week_num) if week_num else 0 + except (ValueError, TypeError): + week_num = 0 + + # Format part number as number if possible + part_number = item.get('Položka', '') + try: + part_number = int(part_number) if part_number.strip() else '' + except (ValueError, AttributeError): + pass + + # Format release as number if possible + release = item.get('Release', '') + try: + release = int(release) if release.strip() else '' + except (ValueError, AttributeError): + pass + + # Add week number and date in the first two columns + ws.cell(row=row_num, column=1, value=week_num) + + # Format date as Excel date + date_str = item.get('Datum', '') + try: + if date_str: + date_obj = datetime.strptime(date_str, '%d.%m.%Y') + ws.cell(row=row_num, column=2, value=date_obj).number_format = 'DD.MM.YYYY' + else: + ws.cell(row=row_num, column=2, value='') + except (ValueError, TypeError): + ws.cell(row=row_num, column=2, value=date_str) + + # Add other data with proper number formatting + ws.cell(row=row_num, column=3, value=part_number) + ws.cell(row=row_num, column=4, value=item.get('Popis', '')) + ws.cell(row=row_num, column=5, value=quantity) # Use formatted quantity + ws.cell(row=row_num, column=6, value=item.get('Typ', '')) + ws.cell(row=row_num, column=7, value=item.get('SCC', '')) + ws.cell(row=row_num, column=8, value=release) + ws.cell(row=row_num, column=9, value=self.partner_info.get('Dodací adresa', '')) # Add delivery location from partner info + row_num += 1 + + # Apply number formatting to all numeric columns + for col in range(1, 10): + 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 == 3: # Part number + cell.number_format = '0' + elif col == 5: # Quantity + cell.number_format = '0' + elif col == 8: # Release + cell.number_format = '0' + + # Auto-adjust column widths + 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 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) + + # Add a summary sheet with just week and quantity + ws_summary = wb.create_sheet("Přehled") + + # Save the file + filename = f"dodavky_cummins_{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"Při exportu došlo k chybě: {str(e)}") + + def run(self): + self.root.mainloop() + +if __name__ == "__main__": + # When run directly, use the main parser to handle file selection + from edi_parser_main import EDIUnifiedParser + EDIUnifiedParser() \ No newline at end of file diff --git a/edi_parser_main.py b/edi_parser_main.py new file mode 100644 index 0000000..17e60ae --- /dev/null +++ b/edi_parser_main.py @@ -0,0 +1,185 @@ +import tkinter as tk +from tkinter import ttk, filedialog, messagebox +import os +from edi_parser_cummins import EDIDelforCumminsParser +from edi_parser_trwkob import EDITrwkobParser +from edi_parser_minebea import EDIDelforParser as EDIDelforMinebeaParser + +class EDIUnifiedParser: + def __init__(self): + self.root = tk.Tk() + self.root.title("EDI Unified Parser") + self.root.geometry("600x400") + self.setup_ui() + # Store reference to main window instance + self.main_window = self + + 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)) + + ttk.Button(btn_frame, text="Načíst EDI soubor", command=self.load_file).pack(side=tk.LEFT) + + self.info_text = tk.Text(main_frame, wrap=tk.WORD, font=('Courier', 10)) + scrollbar = ttk.Scrollbar(main_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 load_file(self): + filepath = filedialog.askopenfilename( + title="Vyberte EDI soubor", + filetypes=[("EDI files", "*.edi"), ("All files", "*.*")] + ) + + if not filepath: + return + + try: + with open(filepath, 'r', encoding='utf-8', errors='replace') as f: + content = f.read() + + # Detect file type based on both filename and content + file_type = self.detect_file_type(filepath, content) + + def run_parser(parser_func): + try: + return parser_func(filepath) + except Exception as e: + messagebox.showerror("Chyba", f"Chyba při spouštění parseru: {str(e)}") + return False + + success = False + if file_type == "cummins": + success = run_parser(self.run_cummins_parser) + elif file_type == "trwkob": + success = run_parser(self.run_trwkob_parser) + elif file_type == "minebea": + success = run_parser(self.run_minebea_parser) + else: + messagebox.showerror("Chyba", "Nepodporovaný typ souboru") + + return success + + except Exception as e: + messagebox.showerror("Chyba", f"Chyba při načítání souboru: {str(e)}") + return False + + def detect_file_type(self, filepath, content): + # Look for patterns in both filename and content + filename = os.path.basename(filepath).upper() + content_upper = content.upper() + + # Check for Cummins patterns (both in filename and content) + cummins_patterns = [ + "CUMMINS", "CMI", "CMI-", "CMI_", + "DELFOR_CUMMINS", "CUMMINS_DELFOR" + ] + if any(pattern in filename for pattern in cummins_patterns) or \ + any(pattern in content_upper for pattern in cummins_patterns): + return "cummins" + + # Check for Minebea patterns + minebea_patterns = [ + "MINEBEA", "MINOL", "MINEBEA-MINOL", "MBM", + "DELFOR_MINEBEA", "MINEBEA_DELFOR" + ] + if any(pattern in filename for pattern in minebea_patterns) or \ + any(pattern in content_upper for pattern in minebea_patterns): + return "minebea" + + # Check for Trwkob patterns + trwkob_patterns = [ + "TRWKOB", "TRW-KOB", "TRW_KOB", "KOBALT", + "DELFOR_TRWKOB", "TRWKOB_DELFOR" + ] + if any(pattern in filename for pattern in trwkob_patterns) or \ + any(pattern in content_upper for pattern in trwkob_patterns): + return "trwkob" + + # If no specific pattern found, try to detect by file structure + if content.startswith("UNB") or content.startswith("UNA"): + # This is a standard EDI file structure + return "minebea" # Default to Minebea as fallback + + return None + + def run_cummins_parser(self, filepath): + try: + # Create parser instance with Tk() root window + parser = EDIDelforCumminsParser() + + # Load the file + success = parser.load_file(filepath) + + if success: + # Start the parser's main loop + parser.root.mainloop() + return True + return False + + except Exception as e: + messagebox.showerror("Chyba", f"Chyba při načítání souboru: {str(e)}") + return False + + def on_parser_close(self, parser): + """Handle parser window closing""" + try: + # First show the main window if it exists + if hasattr(self, 'root') and self.root.winfo_exists(): + self.root.deiconify() + + # Then safely destroy the parser window if it exists + if parser and hasattr(parser, 'root') and parser.root and parser.root.winfo_exists(): + # Schedule the destroy to happen after this method completes + parser.root.after(100, parser.root.destroy) + except Exception as e: + # If anything goes wrong, just try to show the main window + if hasattr(self, 'root') and self.root.winfo_exists(): + self.root.deiconify() + + def run_trwkob_parser(self, filepath): + try: + # Create parser instance - don't set main_window to avoid circular references + parser = EDITrwkobParser() + + # Load the file + success = parser.load_file(filepath) + + if success: + # Start the parser's main loop + parser.root.mainloop() + return True + return False + + except Exception as e: + messagebox.showerror("Chyba", f"Chyba při načítání souboru: {str(e)}") + return False + + def run_minebea_parser(self, filepath): + try: + # Create parser instance with Tk() root window + parser = EDIDelforMinebeaParser() + + # Load the file + success = parser.load_file(filepath) + + if success: + # Start the parser's main loop + parser.root.mainloop() + return True + return False + + except Exception as e: + messagebox.showerror("Chyba", f"Chyba při načítání souboru: {str(e)}") + return False + +def main(): + app = EDIUnifiedParser() + app.root.mainloop() + +if __name__ == "__main__": + main() diff --git a/edi_parser_minebea.py b/edi_parser_minebea.py new file mode 100644 index 0000000..7f5eea1 --- /dev/null +++ b/edi_parser_minebea.py @@ -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() \ No newline at end of file diff --git a/edi_parser_trwkob.py b/edi_parser_trwkob.py new file mode 100644 index 0000000..7575423 --- /dev/null +++ b/edi_parser_trwkob.py @@ -0,0 +1,379 @@ +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)) + 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)) + 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', 'Datum do', 'Množství', 'Typ', 'SCC') + self.delivery_tree = ttk.Treeview(tree_frame, columns=columns, show='headings', height=15) + for col in columns: + self.delivery_tree.heading(col, text=col) + self.delivery_tree.column(col, width=120) + 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 = {} + 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] == '63': + current_delivery['Datum do'] = date_formatted + elif dtm_parts[0] == '64': + 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': + # Kupující: prefer name, fallback to code + self.partner_info['Kupující'] = name if name else code + if full_address: + self.partner_info['Kupující'] += f", {full_address}" + elif role == 'SE': + # Prodávající: prefer name, fallback to code + 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': + # Dodací adresa: show code if name missing + 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 len(qty_parts) >= 3: + qty_type = qty_parts[0] + quantity = qty_parts[1] + unit = qty_parts[2] + if qty_type == '113': + current_delivery['Množství'] = quantity + current_delivery['Jednotka'] = unit + current_delivery['Typ'] = 'Kumulativní' + elif qty_type == '70': + current_delivery['Množství'] = quantity + current_delivery['Jednotka'] = unit + current_delivery['Typ'] = 'Minimální' + elif qty_type == '78': + current_delivery['Množství'] = quantity + current_delivery['Jednotka'] = unit + current_delivery['Typ'] = 'Maximální' + elif line.startswith('SCC'): + parts = line.split('+') + if len(parts) >= 2: + current_delivery['SCC'] = parts[1] + if 'Datum od' in current_delivery and 'Množství' in current_delivery: + self.delivery_schedules.append(current_delivery.copy()) + current_delivery = {'SCC': parts[1]} + + 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) + 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('Datum do', ''), + 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""" + 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" + + # 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 delivery in self.delivery_schedules: + date_str = delivery.get('Datum od', '') + week_num = self.get_week_number(date_str) if date_str 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í', '') + + # Add week number + ws.cell(row=row_num, column=1, value=week_num) + + # Format date as Excel date + try: + if date_str: + date_obj = datetime.strptime(date_str, '%d.%m.%Y') + ws.cell(row=row_num, column=2, value=date_obj).number_format = 'DD.MM.YYYY' + else: + ws.cell(row=row_num, column=2, value='') + except (ValueError, TypeError): + ws.cell(row=row_num, column=2, value=date_str) + + # 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', '')) + ws.cell(row=row_num, column=5, value=scc_desc) # Use SCC description + 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 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 + filename = f"dodavky_{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()