This commit is contained in:
dfuhrmann3 2026-07-07 10:23:51 +02:00
parent aad83afd54
commit fa977594eb
1 changed files with 52 additions and 36 deletions

88
main.py
View File

@ -94,6 +94,22 @@ class InvoiceMetadata(BaseModel):
# Zentrale Geschäftskonstanten - hier zentral pflegen statt an mehreren Stellen im Code. # Zentrale Geschäftskonstanten - hier zentral pflegen statt an mehreren Stellen im Code.
VERKAEUFER_IBAN = "DE06120700240456207000" VERKAEUFER_IBAN = "DE06120700240456207000"
# Verkäufer-Stammdaten (SCITECH). Werte bleiben fest hinterlegt, sind aber an EINER Stelle
# gepflegt und werden in beiden XML-Generatoren sowie in der UI referenziert - so bleibt bei
# einer Adress-/Kontaktänderung nichts inkonsistent zurück.
VERKAEUFER = {
"name": "scitech it solutions GmbH", # BT-27 (Firmenname)
"email": "office@scitech-gmbh.de", # BT-34 (elektronische Adresse) / Kontakt
"phone": "+49 3302 4940537", # BT-42 (Kontakttelefon)
"contact_name": "Geschäftsführung", # BG-6 (Ansprechpartner)
"street": "Hermann-Schumann-Str. 4", # BT-35
"zip": "16761", # BT-38
"city": "Hennigsdorf", # BT-37
"country": "DE", # BT-40
"vat_id": "DE218436692", # BT-31 (Umsatzsteuer-ID)
"register_id": "HRB 6058 NP", # BT-30 (Handelsregisternummer, NICHT die USt-ID)
}
def berechne_faelligkeit(invoice_date_str, term_days): def berechne_faelligkeit(invoice_date_str, term_days):
"""Fälligkeitsdatum = Rechnungsdatum + term_days. Gibt 'YYYY-MM-DD' zurück, oder leer, """Fälligkeitsdatum = Rechnungsdatum + term_days. Gibt 'YYYY-MM-DD' zurück, oder leer,
@ -281,29 +297,29 @@ def generate_compliant_xrechnung(data):
# --- SENDER (SCITECH) --- # --- SENDER (SCITECH) ---
supplier = ET.SubElement(root, f"{{{CAC}}}AccountingSupplierParty") supplier = ET.SubElement(root, f"{{{CAC}}}AccountingSupplierParty")
party_supp = ET.SubElement(supplier, f"{{{CAC}}}Party") party_supp = ET.SubElement(supplier, f"{{{CAC}}}Party")
ET.SubElement(party_supp, f"{{{CBC}}}EndpointID", {"schemeID": "EM"}).text = "office@scitech-gmbh.de" ET.SubElement(party_supp, f"{{{CBC}}}EndpointID", {"schemeID": "EM"}).text = VERKAEUFER["email"]
addr_supp = ET.SubElement(party_supp, f"{{{CAC}}}PostalAddress") addr_supp = ET.SubElement(party_supp, f"{{{CAC}}}PostalAddress")
ET.SubElement(addr_supp, f"{{{CBC}}}StreetName").text = "Hermann-Schumann-Str. 4" ET.SubElement(addr_supp, f"{{{CBC}}}StreetName").text = VERKAEUFER["street"]
ET.SubElement(addr_supp, f"{{{CBC}}}CityName").text = "Hennigsdorf" ET.SubElement(addr_supp, f"{{{CBC}}}CityName").text = VERKAEUFER["city"]
ET.SubElement(addr_supp, f"{{{CBC}}}PostalZone").text = "16761" ET.SubElement(addr_supp, f"{{{CBC}}}PostalZone").text = VERKAEUFER["zip"]
country_supp = ET.SubElement(addr_supp, f"{{{CAC}}}Country") country_supp = ET.SubElement(addr_supp, f"{{{CAC}}}Country")
ET.SubElement(country_supp, f"{{{CBC}}}IdentificationCode").text = "DE" ET.SubElement(country_supp, f"{{{CBC}}}IdentificationCode").text = VERKAEUFER["country"]
tax_scheme_supp = ET.SubElement(party_supp, f"{{{CAC}}}PartyTaxScheme") tax_scheme_supp = ET.SubElement(party_supp, f"{{{CAC}}}PartyTaxScheme")
ET.SubElement(tax_scheme_supp, f"{{{CBC}}}CompanyID").text = "DE218436692" ET.SubElement(tax_scheme_supp, f"{{{CBC}}}CompanyID").text = VERKAEUFER["vat_id"]
scheme_supp = ET.SubElement(tax_scheme_supp, f"{{{CAC}}}TaxScheme") scheme_supp = ET.SubElement(tax_scheme_supp, f"{{{CAC}}}TaxScheme")
ET.SubElement(scheme_supp, f"{{{CBC}}}ID").text = "VAT" ET.SubElement(scheme_supp, f"{{{CBC}}}ID").text = "VAT"
legal_supp = ET.SubElement(party_supp, f"{{{CAC}}}PartyLegalEntity") legal_supp = ET.SubElement(party_supp, f"{{{CAC}}}PartyLegalEntity")
ET.SubElement(legal_supp, f"{{{CBC}}}RegistrationName").text = "scitech it solutions GmbH" ET.SubElement(legal_supp, f"{{{CBC}}}RegistrationName").text = VERKAEUFER["name"]
# BT-30 Registernummer (Handelsregister) - NICHT identisch mit der Umsatzsteuer-ID (BT-31)! # BT-30 Registernummer (Handelsregister) - NICHT identisch mit der Umsatzsteuer-ID (BT-31)!
ET.SubElement(legal_supp, f"{{{CBC}}}CompanyID").text = "HRB 6058 NP" ET.SubElement(legal_supp, f"{{{CBC}}}CompanyID").text = VERKAEUFER["register_id"]
contact_supp = ET.SubElement(party_supp, f"{{{CAC}}}Contact") contact_supp = ET.SubElement(party_supp, f"{{{CAC}}}Contact")
ET.SubElement(contact_supp, f"{{{CBC}}}Name").text = "Geschäftsführung" ET.SubElement(contact_supp, f"{{{CBC}}}Name").text = VERKAEUFER["contact_name"]
ET.SubElement(contact_supp, f"{{{CBC}}}Telephone").text = "+49 3302 4940537" ET.SubElement(contact_supp, f"{{{CBC}}}Telephone").text = VERKAEUFER["phone"]
ET.SubElement(contact_supp, f"{{{CBC}}}ElectronicMail").text = "office@scitech-gmbh.de" ET.SubElement(contact_supp, f"{{{CBC}}}ElectronicMail").text = VERKAEUFER["email"]
# --- Empfänger --- # --- Empfänger ---
customer = ET.SubElement(root, f"{{{CAC}}}AccountingCustomerParty") customer = ET.SubElement(root, f"{{{CAC}}}AccountingCustomerParty")
@ -461,35 +477,35 @@ def generate_compliant_zugferd_cii(data):
ET.SubElement(header_agree, f"{{{RAM}}}BuyerReference").text = str(data["customer_number"]).strip() ET.SubElement(header_agree, f"{{{RAM}}}BuyerReference").text = str(data["customer_number"]).strip()
supplier_party = ET.SubElement(header_agree, f"{{{RAM}}}SellerTradeParty") supplier_party = ET.SubElement(header_agree, f"{{{RAM}}}SellerTradeParty")
ET.SubElement(supplier_party, f"{{{RAM}}}Name").text = "scitech it solutions GmbH" ET.SubElement(supplier_party, f"{{{RAM}}}Name").text = VERKAEUFER["name"]
supp_legal = ET.SubElement(supplier_party, f"{{{RAM}}}SpecifiedLegalOrganization") supp_legal = ET.SubElement(supplier_party, f"{{{RAM}}}SpecifiedLegalOrganization")
# BT-30 Registernummer (Handelsregister) - NICHT identisch mit der Umsatzsteuer-ID (BT-31)! # BT-30 Registernummer (Handelsregister) - NICHT identisch mit der Umsatzsteuer-ID (BT-31)!
# Fest eingetragen, analog zu den übrigen fixen Verkäufer-Stammdaten. # Fest eingetragen, analog zu den übrigen fixen Verkäufer-Stammdaten.
ET.SubElement(supp_legal, f"{{{RAM}}}ID").text = "HRB 6058 NP" ET.SubElement(supp_legal, f"{{{RAM}}}ID").text = VERKAEUFER["register_id"]
# BG-6 SELLER CONTACT (BR-DE-2 Pflicht für XRechnung). Muss laut XSD nach # BG-6 SELLER CONTACT (BR-DE-2 Pflicht für XRechnung). Muss laut XSD nach
# SpecifiedLegalOrganization und vor PostalTradeAddress stehen. # SpecifiedLegalOrganization und vor PostalTradeAddress stehen.
supp_contact = ET.SubElement(supplier_party, f"{{{RAM}}}DefinedTradeContact") supp_contact = ET.SubElement(supplier_party, f"{{{RAM}}}DefinedTradeContact")
ET.SubElement(supp_contact, f"{{{RAM}}}PersonName").text = "Geschäftsführung" ET.SubElement(supp_contact, f"{{{RAM}}}PersonName").text = VERKAEUFER["contact_name"]
supp_contact_tel = ET.SubElement(supp_contact, f"{{{RAM}}}TelephoneUniversalCommunication") supp_contact_tel = ET.SubElement(supp_contact, f"{{{RAM}}}TelephoneUniversalCommunication")
ET.SubElement(supp_contact_tel, f"{{{RAM}}}CompleteNumber").text = "+49 3302 4940537" ET.SubElement(supp_contact_tel, f"{{{RAM}}}CompleteNumber").text = VERKAEUFER["phone"]
supp_contact_mail = ET.SubElement(supp_contact, f"{{{RAM}}}EmailURIUniversalCommunication") supp_contact_mail = ET.SubElement(supp_contact, f"{{{RAM}}}EmailURIUniversalCommunication")
ET.SubElement(supp_contact_mail, f"{{{RAM}}}URIID").text = "office@scitech-gmbh.de" ET.SubElement(supp_contact_mail, f"{{{RAM}}}URIID").text = VERKAEUFER["email"]
supp_addr = ET.SubElement(supplier_party, f"{{{RAM}}}PostalTradeAddress") supp_addr = ET.SubElement(supplier_party, f"{{{RAM}}}PostalTradeAddress")
ET.SubElement(supp_addr, f"{{{RAM}}}PostcodeCode").text = "16761" ET.SubElement(supp_addr, f"{{{RAM}}}PostcodeCode").text = VERKAEUFER["zip"]
ET.SubElement(supp_addr, f"{{{RAM}}}LineOne").text = "Hermann-Schumann-Str. 4" ET.SubElement(supp_addr, f"{{{RAM}}}LineOne").text = VERKAEUFER["street"]
ET.SubElement(supp_addr, f"{{{RAM}}}CityName").text = "Hennigsdorf" ET.SubElement(supp_addr, f"{{{RAM}}}CityName").text = VERKAEUFER["city"]
ET.SubElement(supp_addr, f"{{{RAM}}}CountryID").text = "DE" ET.SubElement(supp_addr, f"{{{RAM}}}CountryID").text = VERKAEUFER["country"]
# BT-34 Elektronische Adresse des Verkäufers (PEPPOL-EN16931-R020 Pflicht). Muss laut # BT-34 Elektronische Adresse des Verkäufers (PEPPOL-EN16931-R020 Pflicht). Muss laut
# XSD nach PostalTradeAddress und vor SpecifiedTaxRegistration stehen. # XSD nach PostalTradeAddress und vor SpecifiedTaxRegistration stehen.
supp_uri = ET.SubElement(supplier_party, f"{{{RAM}}}URIUniversalCommunication") supp_uri = ET.SubElement(supplier_party, f"{{{RAM}}}URIUniversalCommunication")
ET.SubElement(supp_uri, f"{{{RAM}}}URIID", {"schemeID": "EM"}).text = "office@scitech-gmbh.de" ET.SubElement(supp_uri, f"{{{RAM}}}URIID", {"schemeID": "EM"}).text = VERKAEUFER["email"]
supp_tax = ET.SubElement(supplier_party, f"{{{RAM}}}SpecifiedTaxRegistration") supp_tax = ET.SubElement(supplier_party, f"{{{RAM}}}SpecifiedTaxRegistration")
ET.SubElement(supp_tax, f"{{{RAM}}}ID", {"schemeID": "VA"}).text = "DE218436692" ET.SubElement(supp_tax, f"{{{RAM}}}ID", {"schemeID": "VA"}).text = VERKAEUFER["vat_id"]
customer_party = ET.SubElement(header_agree, f"{{{RAM}}}BuyerTradeParty") customer_party = ET.SubElement(header_agree, f"{{{RAM}}}BuyerTradeParty")
ET.SubElement(customer_party, f"{{{RAM}}}Name").text = str(data["recipient_name"]) ET.SubElement(customer_party, f"{{{RAM}}}Name").text = str(data["recipient_name"])
@ -743,7 +759,7 @@ with col2:
st.code(res_meta_text) st.code(res_meta_text)
st.stop() st.stop()
if final_data.get("recipient_email") == "office@scitech-gmbh.de": if final_data.get("recipient_email") == VERKAEUFER["email"]:
final_data["recipient_email"] = "" final_data["recipient_email"] = ""
final_data["items"] = extract_invoice_items_pure_python(pdf_text) final_data["items"] = extract_invoice_items_pure_python(pdf_text)
@ -808,14 +824,14 @@ with col2:
c_s, c_r = st.columns(2) c_s, c_r = st.columns(2)
with c_s: with c_s:
st.markdown("**Absender (Verkäufer):**") st.markdown("**Absender (Verkäufer):**")
st.text_input("Firma (BT-27)", value="scitech it solutions GmbH", disabled=True) st.text_input("Firma (BT-27)", value=VERKAEUFER["name"], disabled=True)
st.text_input("E-Mail (BT-34)", value="office@scitech-gmbh.de", disabled=True) st.text_input("E-Mail (BT-34)", value=VERKAEUFER["email"], disabled=True)
st.text_input("Telefon (BT-42)", value="+49 3302 4940537", disabled=True) st.text_input("Telefon (BT-42)", value=VERKAEUFER["phone"], disabled=True)
st.text_input("Straße (BT-35)", value="Hermann-Schumann-Str. 4", disabled=True) st.text_input("Straße (BT-35)", value=VERKAEUFER["street"], disabled=True)
st.text_input("PLZ (BT-38)", value="16761", disabled=True) st.text_input("PLZ (BT-38)", value=VERKAEUFER["zip"], disabled=True)
st.text_input("Stadt (BT-37)", value="Hennigsdorf", disabled=True) st.text_input("Stadt (BT-37)", value=VERKAEUFER["city"], disabled=True)
st.text_input("Umsatzsteuer-ID (BT-31)", value="DE218436692", disabled=True) st.text_input("Umsatzsteuer-ID (BT-31)", value=VERKAEUFER["vat_id"], disabled=True)
st.text_input("Registernummer (BT-30)", value="HRB 6058 NP", disabled=True) st.text_input("Registernummer (BT-30)", value=VERKAEUFER["register_id"], disabled=True)
st.text_input("IBAN (BT-84)", value=VERKAEUFER_IBAN, disabled=True) st.text_input("IBAN (BT-84)", value=VERKAEUFER_IBAN, disabled=True)
with c_r: with c_r:
st.markdown("**Empfänger (Kunde):**") st.markdown("**Empfänger (Kunde):**")