diff --git a/main.py b/main.py index 520bbe7..15a8c8f 100644 --- a/main.py +++ b/main.py @@ -180,7 +180,7 @@ def format_cii_date(date_str): # ========================================== -# GENERATOR A: XRECHNUNG (UBL FORMAT) - UNBERÜHRT +# GENERATOR A: XRECHNUNG (UBL FORMAT) # ========================================== def generate_compliant_xrechnung(data): INVOICE_NS = "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" @@ -338,7 +338,7 @@ def generate_compliant_xrechnung(data): # ========================================== -# GENERATOR B: ZUGFeRD 2.4 (CII FORMAT UN/CEFACT) - NEU +# GENERATOR B: ZUGFeRD 2.4 (CII FORMAT UN/CEFACT) # ========================================== def generate_compliant_zugferd_cii(data): RSM = "urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" @@ -351,12 +351,10 @@ def generate_compliant_zugferd_cii(data): root = ET.Element(f"{{{RSM}}}CrossIndustryInvoice") - # Context ex_doc_ctx = ET.SubElement(root, f"{{{RAM}}}ExchangedDocumentContext") guideline = ET.SubElement(ex_doc_ctx, f"{{{RAM}}}GuidelineSpecifiedDocumentContextParameter") ET.SubElement(guideline, f"{{{RAM}}}ID").text = "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0" - # Header Document ex_doc = ET.SubElement(root, f"{{{RSM}}}ExchangedDocument") ET.SubElement(ex_doc, f"{{{RAM}}}ID").text = str(data["invoice_number"]) ET.SubElement(ex_doc, f"{{{RAM}}}TypeCode").text = str(data.get("invoice_type_code", "380")) @@ -368,37 +366,30 @@ def generate_compliant_zugferd_cii(data): note = ET.SubElement(ex_doc, f"{{{RAM}}}IncludedNote") ET.SubElement(note, f"{{{RAM}}}Content").text = str(data["invoice_note"]).strip() - # Trade Transaction trade_trans = ET.SubElement(root, f"{{{RSM}}}SupplyChainTradeTransaction") vat_rate = float(data['vat_rate_percent']) vat_category = "S" if vat_rate > 0.0 else "E" - # --- POSITIONS-SCHLEIFE --- for index, item in enumerate(data["items"], start=1): line_item = ET.SubElement(trade_trans, f"{{{RAM}}}IncludedSupplyChainTradeLineItem") - # Document Line doc_line = ET.SubElement(line_item, f"{{{RAM}}}AssociatedDocumentLineDocument") ET.SubElement(doc_line, f"{{{RAM}}}LineID").text = str(index) - # Product / Item trade_prod = ET.SubElement(line_item, f"{{{RAM}}}SpecifiedTradeProduct") ET.SubElement(trade_prod, f"{{{RAM}}}Name").text = str(item.get("name", "Dienstleistung")) if item.get("description") and str(item["description"]).strip() != "": ET.SubElement(trade_prod, f"{{{RAM}}}Description").text = str(item["description"]).strip() - # Agreement (Price) line_agree = ET.SubElement(line_item, f"{{{RAM}}}SpecifiedLineTradeAgreement") net_price = ET.SubElement(line_agree, f"{{{RAM}}}GrossPriceProductTradePrice") ET.SubElement(net_price, f"{{{RAM}}}ChargeAmount").text = f"{item['unit_price']:.2f}" - # Delivery (Quantity) line_deliv = ET.SubElement(line_item, f"{{{RAM}}}SpecifiedLineTradeDelivery") u_code = map_unece_unit_code(item.get("unit", "Stk.")) ET.SubElement(line_deliv, f"{{{RAM}}}BilledQuantity", {"unitCode": u_code}).text = f"{item['quantity']:.4f}" - # Settlement (Financials per Item) line_settle = ET.SubElement(line_item, f"{{{RAM}}}SpecifiedLineTradeSettlement") trade_tax = ET.SubElement(line_settle, f"{{{RAM}}}ApplicableTradeTax") ET.SubElement(trade_tax, f"{{{RAM}}}TypeCode").text = "VAT" @@ -408,11 +399,9 @@ def generate_compliant_zugferd_cii(data): mon_sum = ET.SubElement(line_settle, f"{{{RAM}}}SpecifiedTradeSettlementLineMonetarySummation") ET.SubElement(mon_sum, f"{{{RAM}}}LineTotalAmount").text = f"{item['total_amount']:.2f}" - # Header Trade Agreement (Sender & Customer & Global IDs) header_agree = ET.SubElement(trade_trans, f"{{{RAM}}}ApplicableHeaderTradeAgreement") ET.SubElement(header_agree, f"{{{RAM}}}BuyerReference").text = str(data["buyer_reference"]) - # Supplier supplier_party = ET.SubElement(header_agree, f"{{{RAM}}}SellerTradeParty") ET.SubElement(supplier_party, f"{{{RAM}}}Name").text = "scitech it solutions GmbH" @@ -428,7 +417,6 @@ def generate_compliant_zugferd_cii(data): supp_tax = ET.SubElement(supplier_party, f"{{{RAM}}}SpecifiedTaxRegistration") ET.SubElement(supp_tax, f"{{{RAM}}}ID", {"schemeID": "VA"}).text = "DE218436692" - # Customer customer_party = ET.SubElement(header_agree, f"{{{RAM}}}BuyerTradeParty") ET.SubElement(customer_party, f"{{{RAM}}}Name").text = str(data["recipient_name"]) @@ -438,7 +426,6 @@ def generate_compliant_zugferd_cii(data): ET.SubElement(cust_addr, f"{{{RAM}}}CityName").text = str(data["recipient_city"]) ET.SubElement(cust_addr, f"{{{RAM}}}CountryID").text = "DE" - # Global References (BT-12, BT-13, BT-16) if data.get("order_id") and str(data["order_id"]).strip() != "": order_ref = ET.SubElement(header_agree, f"{{{RAM}}}BuyerOrderReferencedDocument") ET.SubElement(order_ref, f"{{{RAM}}}IssuerAssignedID").text = str(data["order_id"]).strip() @@ -447,7 +434,6 @@ def generate_compliant_zugferd_cii(data): contract_ref = ET.SubElement(header_agree, f"{{{RAM}}}ContractReferencedDocument") ET.SubElement(contract_ref, f"{{{RAM}}}IssuerAssignedID").text = str(data["contract_id"]).strip() - # Header Trade Delivery header_deliv = ET.SubElement(trade_trans, f"{{{RAM}}}ApplicableHeaderTradeDelivery") if data.get("delivery_note_id") and str(data["delivery_note_id"]).strip() != "": dn_ref = ET.SubElement(header_deliv, f"{{{RAM}}}DeliveryNoteReferencedDocument") @@ -458,7 +444,6 @@ def generate_compliant_zugferd_cii(data): occ_date = ET.SubElement(act_deliv, f"{{{RAM}}}OccurrenceDateTime") ET.SubElement(occ_date, f"{{{UDT}}}DateTimeString", {"format": "102"}).text = format_cii_date(data["delivery_date"]) - # Header Trade Settlement header_settle = ET.SubElement(trade_trans, f"{{{RAM}}}ApplicableHeaderTradeSettlement") ET.SubElement(header_settle, f"{{{RAM}}}InvoiceCurrencyCode").text = "EUR" @@ -467,7 +452,6 @@ def generate_compliant_zugferd_cii(data): payee_acc = ET.SubElement(pay_means, f"{{{RAM}}}PayeePartyCreditorFinancialAccount") ET.SubElement(payee_acc, f"{{{RAM}}}IBANID").text = str(data["iban"]).replace(" ", "") - # Global Tax Total header_tax = ET.SubElement(header_settle, f"{{{RAM}}}ApplicableTradeTax") ET.SubElement(header_tax, f"{{{RAM}}}CalculatedAmount").text = f"{data['total_vat_amount']:.2f}" ET.SubElement(header_tax, f"{{{RAM}}}TypeCode").text = "VAT" @@ -475,7 +459,6 @@ def generate_compliant_zugferd_cii(data): ET.SubElement(header_tax, f"{{{RAM}}}CategoryCode").text = vat_category ET.SubElement(header_tax, f"{{{RAM}}}RateApplicablePercent").text = f"{vat_rate:.2f}" - # Billing Period (BT-26) if data.get("service_start_date") and data.get("service_end_date"): period = ET.SubElement(header_settle, f"{{{RAM}}}BillingSpecifiedPeriod") start_pt = ET.SubElement(period, f"{{{RAM}}}StartDateTime") @@ -483,7 +466,6 @@ def generate_compliant_zugferd_cii(data): end_pt = ET.SubElement(period, f"{{{RAM}}}EndDateTime") ET.SubElement(end_pt, f"{{{UDT}}}DateTimeString", {"format": "102"}).text = format_cii_date(data["service_end_date"]) - # Monetary Summation header_sum = ET.SubElement(header_settle, f"{{{RAM}}}SpecifiedTradeSettlementHeaderMonetarySummation") ET.SubElement(header_sum, f"{{{RAM}}}LineTotalAmount").text = f"{data['total_net_amount']:.2f}" ET.SubElement(header_sum, f"{{{RAM}}}TaxBasisTotalAmount").text = f"{data['total_net_amount']:.2f}" @@ -743,15 +725,12 @@ with col2: else: if st.button("🔒 ZUGFeRD PDF exportieren", type="primary", width="stretch"): try: - # CII XML aus dem zentralen Objekt bauen cii_xml_string = generate_compliant_zugferd_cii(final_invoice_dict) - # Original-PDF laden uploaded_file.seek(0) original_pdf_bytes = uploaded_file.read() doc = fitz.open(stream=original_pdf_bytes, filetype="pdf") - # Datei einbetten nach ZUGFeRD 2.4 Standard doc.embfile_add( "factur-x.xml", cii_xml_string.encode("utf-8"), @@ -759,7 +738,6 @@ with col2: desc="Factur-X/ZUGFeRD E-Invoice" ) - # Offizielles ZUGFeRD 2.4/Factur-X Extension Schema für XMP injizieren xmp_xml = """ @@ -812,7 +790,8 @@ with col2: """ - doc.set_xmp_metadata(xmp_xml) + # 🔥 FIX: Benutze doc.set_metadata mit dem xmp-Keyword anstelle von set_xmp_metadata + doc.set_metadata(doc.metadata, xmp=xmp_xml) zugferd_pdf_bytes = doc.tobytes() doc.close() @@ -832,7 +811,7 @@ st.markdown("---") st.caption( "📝 **Hinweise:**\n\n" "**Human-in-the-Loop:** " - "Das Tool dient als Automations-Assistent. Vor dem finalen XML-Export ist eine visuelle Endkontrolle der extrahierten Daten " + "Das Tool dient as Automations-Assistent. Vor dem finalen XML-Export ist eine visuelle Endkontrolle der extrahierten Daten " "(insbesondere Rechnungsnummer und Empfängeradresse) durch den Sachbearbeiter zwingend erforderlich.\n\n" "**GoBD-Konformität:** " "Das Tool generiert ausschließlich das strukturierte XML-Format. Für die Einhaltung der GoBD-Richtlinien "