First Upload

master
Sebastian Serfling 2025-02-17 16:16:53 +01:00
commit d5a8c8e9f4
10 changed files with 561 additions and 0 deletions

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

10
.idea/Shopware-API.iml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.12 (Shopware-API)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

6
.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12 (Shopware-API)" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Shopware-API.iml" filepath="$PROJECT_DIR$/.idea/Shopware-API.iml" />
</modules>
</component>
</project>

4
.idea/vcs.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings" defaultProject="true" />
</project>

View File

@ -0,0 +1,74 @@
%PDF-1.3
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
1 0 obj
<<
/F1 2 0 R /F2 3 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/Contents 8 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 7 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
5 0 obj
<<
/PageMode /UseNone /Pages 7 0 R /Type /Catalog
>>
endobj
6 0 obj
<<
/Author (anonymous) /CreationDate (D:20250217154506+01'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:20250217154506+01'00') /Producer (ReportLab PDF Library - www.reportlab.com)
/Subject (unspecified) /Title (Lieferschein 17.02.2025) /Trapped /False
>>
endobj
7 0 obj
<<
/Count 1 /Kids [ 4 0 R ] /Type /Pages
>>
endobj
8 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 582
>>
stream
Gau0@b>,r/&A7TLHNYUG]sd6[,GK.N!mq9+>ccM4TI(f%'mr2_*c5S"LJ"4-OlFS[B%Ji95hnf/k9:/@9]>o<!_#XeJF\L`H4/3oW*2]aPA37;E48R]$:,d]"O\9Mk:4gNlKgFX0A;YK=.L'ROM4#,]&ak07u'aW2:p?B<j,O><.4jI7Kaq]./*IUPY]eVQb)0,ekVrZN._a#+HBE3_ei$>K5.CPg8Bp0<=_#mOWiRQ,I*i0=rL\4qVjb[P=F?@[bAN\2MF%R.Crp@Cm[*FR5(Z]/+]K(;PQf7OMKX4Ksh(9.a00pk]=5\0^fWFpJKKEO32"5Yjc)XQg9>GrZR/)_>NP43kO/5Q&+WXS9<a2#U^W`&g[iH,9a`1Gc67,Me4FhIN`(bb7BFGj5M!`:ILG'@J(I8cOQL1ld$XT/[4a\=CD-C9/;jj,koAL_SX:l,alb=82AqGm0dNA-d^qS.(neTc"(U'\P'<EU.N,C4;ZAqWF[a4(^E`g8O!jWRG$3:bD.u6e"tr1D=u1_1?*b<[;_052\'UHbW.3qbQrsK.$TK4*.%oi+Q5;/4LL:0KYu1rVKfuB~>endstream
endobj
xref
0 9
0000000000 65535 f
0000000073 00000 n
0000000114 00000 n
0000000221 00000 n
0000000333 00000 n
0000000536 00000 n
0000000604 00000 n
0000000915 00000 n
0000000974 00000 n
trailer
<<
/ID
[<52cc85184a6e7bde8ef927cb4ba48ccd><52cc85184a6e7bde8ef927cb4ba48ccd>]
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
/Info 6 0 R
/Root 5 0 R
/Size 9
>>
startxref
1646
%%EOF

108
check_api.py Normal file
View File

@ -0,0 +1,108 @@
import os
import customtkinter as ctk
import winreg
from PIL import Image
import webbrowser
REG_PATH = r"Software\ShopwareDeliver"
def rerun():
"""Löscht Registry-Werte und ruft run() auf, falls Werte fehlen"""
try:
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, REG_PATH, 0, winreg.KEY_SET_VALUE) as key:
all_deleted = True # Flag, um zu prüfen, ob alle Werte gelöscht wurden
for name in ["CLIENT_ID", "CLIENT_SECRET", "API_URL"]:
try:
winreg.DeleteValue(key, name)
print(f"{name} wurde gelöscht.")
except FileNotFoundError:
print(f"⚠️ {name} war nicht vorhanden.")
all_deleted = False # Mindestens ein Wert fehlte bereits
if all_deleted:
print("✅ Alle Werte erfolgreich gelöscht.")
run() # Falls alles erfolgreich gelöscht wurde, rufe run() auf
else:
print("⚠️ Einige Werte waren nicht vorhanden, run() wird nicht aufgerufen.")
except FileNotFoundError:
print("⚠️ Fehler: Registry-Schlüssel nicht gefunden!")
run() # Falls die gesamte Registry fehlt, rufe run() auf
def run():
REG_PATH = r"Software\ShopwareDeliver"
def get_registry_value(name):
"""Liest einen Wert aus der Windows-Registry"""
try:
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, REG_PATH, 0, winreg.KEY_READ) as key:
value, _ = winreg.QueryValueEx(key, name)
return value
except FileNotFoundError:
return None
def set_registry_value(name, value):
"""Speichert einen Wert in der Windows-Registry"""
with winreg.CreateKey(winreg.HKEY_CURRENT_USER, REG_PATH) as key:
winreg.SetValueEx(key, name, 0, winreg.REG_SZ, value)
def open_link(event=None):
webbrowser.open("https://itdata-gera.de")
# Prüfe, ob die Werte existieren
CLIENT_ID = get_registry_value("CLIENT_ID")
CLIENT_SECRET = get_registry_value("CLIENT_SECRET")
API_URL = get_registry_value("API_URL")
# Falls Werte fehlen, öffne das Eingabe-Fenster
if not CLIENT_ID or not CLIENT_SECRET or not API_URL:
class SecretInputApp(ctk.CTk):
def __init__(self):
super().__init__()
self.title("API-Konfiguration")
self.geometry("400x300")
ctk.CTkLabel(self, text="Shopware Deliver", font=("Arial", 20)).pack(pady=5)
ctk.CTkLabel(self, text="Bitte geben Sie die API-Daten ein:", font=("Arial", 16)).pack(pady=10)
self.client_id_entry = ctk.CTkEntry(self, placeholder_text="Client ID")
self.client_id_entry.pack(pady=5, padx=10, fill="x")
self.client_secret_entry = ctk.CTkEntry(self, placeholder_text="Client Secret", show="*")
self.client_secret_entry.pack(pady=5, padx=10, fill="x")
self.api_url_entry = ctk.CTkEntry(self, placeholder_text="Shopware URL")
self.api_url_entry.pack(pady=5, padx=10, fill="x")
self.save_button = ctk.CTkButton(self, text="Speichern", command=self.save_keys)
self.save_button.pack(pady=20)
def save_keys(self):
client_id = self.client_id_entry.get()
client_secret = self.client_secret_entry.get()
api_url = self.api_url_entry.get()
if client_id and client_secret and api_url:
# Speichere die Werte in der Windows-Registry
set_registry_value("CLIENT_ID", client_id)
set_registry_value("CLIENT_SECRET", client_secret)
set_registry_value("API_URL", api_url)
self.destroy() # Fenster schließen nach dem Speichern
else:
ctk.CTkLabel(self, text="Alle Felder müssen ausgefüllt sein!", text_color="red").pack(pady=5)
# Starte die GUI
app = SecretInputApp()
app.mainloop()
# Jetzt sind die Werte sicher gespeichert und können im restlichen Skript genutzt werden
CLIENT_ID = get_registry_value("CLIENT_ID")
CLIENT_SECRET = get_registry_value("CLIENT_SECRET")
API_URL = get_registry_value("API_URL")
return CLIENT_ID, CLIENT_SECRET, API_URL

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

342
main.py Normal file
View File

@ -0,0 +1,342 @@
import tkinter
from PIL import Image
import requests
import webbrowser
import customtkinter as ctk
from tkinter import messagebox
from tkinter import ttk
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from datetime import datetime
import datetime
import check_api
STATUS_PAID = "in_progress"
keys = check_api.run()
CLIENT_ID = keys[0]
CLIENT_SECRET = keys[1]
SHOPWARE_URL = keys[2]
current_order_id = None
def get_order_line_items(access_token, order_id):
headers = {
"sw-access-key": CLIENT_ID,
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
items_url = f"{SHOPWARE_URL}/api/order/{order_id}/line-items"
response = requests.get(items_url, headers=headers)
response.raise_for_status()
return response.json()["data"]
def get_order_customer(access_token, customer_id):
headers = {
"sw-access-key": CLIENT_ID,
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
customer_url = f"{SHOPWARE_URL}/api/order-customer/{customer_id}"
response = requests.get(customer_url, headers=headers)
response.raise_for_status()
return response.json()["data"]
def get_order_billing_address(access_token, order_id):
headers = {
"sw-access-key": CLIENT_ID,
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
address_url = f"{SHOPWARE_URL}/api/order/{order_id}/billing-address/"
response = requests.get(address_url, headers=headers)
response.raise_for_status()
return response.json()["data"][0]
def get_access_token():
token_url = f"{SHOPWARE_URL}/api/oauth/token"
data = {
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"grant_type": "client_credentials"
}
response = requests.post(token_url, data=data)
response.raise_for_status()
return response.json()["access_token"]
def get_paid_orders(access_token):
headers = {
"sw-access-key": CLIENT_ID,
"authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
orders_url = f"{SHOPWARE_URL}/api/search/order"
query = {
"limit": 100,
"page": 1,
"filter": [{
"type": "equals",
"field": "stateMachineState.technicalName",
"value": STATUS_PAID
}]
}
response = requests.post(orders_url, headers=headers, json=query)
response.raise_for_status()
return response.json()["data"]
def get_order_details(access_token, order_id):
headers = {
"sw-access-key": CLIENT_ID,
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
items_url = f"{SHOPWARE_URL}/api/order/{order_id}/line-items"
address_url = f"{SHOPWARE_URL}/api/order/{order_id}/billing-address/"
response_items = requests.get(items_url, headers=headers)
response_address = requests.get(address_url, headers=headers)
response_items.raise_for_status()
response_address.raise_for_status()
return response_items.json()["data"], response_address.json()["data"][0]
def show_order_details(event):
global current_order_id
selected_item = tree_orders.focus()
if not selected_item:
return
values = tree_orders.item(selected_item, "values")
order_number = values[0]
customer_name = values[1]
access_token = get_access_token()
orders = get_paid_orders(access_token)
selected_order = next((o for o in orders if o['attributes']['orderNumber'] == order_number), None)
if selected_order:
order_id = selected_order['id']
current_order_id = order_id
product_line, billing_address = get_order_details(access_token, order_id)
frame_orders.pack_forget()
tree_details.delete(*tree_details.get_children())
for product in product_line:
label = product['attributes'].get('label', 'Unbekanntes Produkt')
quantity = product['attributes']['quantity']
price = product['attributes']['totalPrice']
tree_details.insert("", "end", values=(label, quantity, f"{price:.2f}"))
label_details.configure(text=f"Bestellung: {order_number} - {customer_name}")
frame_details.pack(fill="both", expand=True)
def back_to_orders():
frame_details.pack_forget()
frame_orders.pack(fill="both", expand=True)
def print_single_order():
if not current_order_id:
messagebox.showerror("Fehler", "Keine Bestellung ausgewählt!")
return
access_token = get_access_token()
orders = get_paid_orders(access_token)
selected_order = next((o for o in orders if o['id'] == current_order_id), None)
if selected_order:
filename = generate_combined_pdf([selected_order], access_token)
messagebox.showinfo("Erfolg", f"Lieferschein {filename} wurde erstellt!")
else:
messagebox.showerror("Fehler", "Bestellung nicht gefunden!")
def generate_combined_pdf(orders, access_token):
current_date = datetime.datetime.today().strftime("%d.%m.%Y")
filename = f"Lieferschein_{current_date}.pdf"
pdf = canvas.Canvas(filename, pagesize=A4)
pdf.setTitle(f"Lieferschein {current_date}")
pdf.setFont("Helvetica-Bold", 16)
pdf.drawString(50, 800, "Sammel-Lieferschein")
pdf.setFont("Helvetica", 12)
pdf.drawString(50, 780, f"Erstellt am: {current_date}")
y_position = 750
for order in orders:
try:
customer_id = order["relationships"]["orderCustomer"]["data"]["id"]
customer = get_order_customer(access_token, customer_id)
billing_address = get_order_billing_address(access_token, order["id"])
product_line = get_order_line_items(access_token, order["id"])
# Bestell-Header
pdf.setFont("Helvetica-Bold", 12)
pdf.drawString(50, y_position, f"Bestellnummer: {order['attributes']['orderNumber']}")
date_obj = datetime.datetime.strptime(order['attributes']['orderDate'], "%Y-%m-%dT%H:%M:%S.%f%z")
date_str = date_obj.strftime("%d.%m.%Y")
pdf.setFont("Helvetica", 10)
pdf.drawString(50, y_position - 20, f"Bestelldatum: {date_str}")
# Rahmen um Lieferadresse
pdf.setStrokeColorRGB(0, 0, 0) # Schwarz
#pdf.rect(45, y_position - 110, 300, 80, stroke=1, fill=0) # Rechteck um Adresse
# Lieferadresse
pdf.setFont("Helvetica-Bold", 10)
pdf.drawString(50, y_position - 100, "Lieferadresse:")
pdf.setFont("Helvetica", 10)
pdf.drawString(50, y_position - 120, f"{customer['attributes']['firstName']} {customer['attributes']['lastName']}")
pdf.drawString(50, y_position - 140, f"{billing_address['attributes']['street']}")
pdf.drawString(50, y_position - 160, f"{billing_address['attributes']['zipcode']} {billing_address['attributes']['city']}")
y_position -= 180 # Platz nach Adresse
# Produktliste
for product in product_line:
pdf.rect(50, y_position - 2, 10, 10, stroke=1, fill=0) # Checkbox für Abhaken
label = product['attributes'].get('label', 'Unbekanntes Produkt')
product_number = product['attributes']['payload'].get('productNumber', 'Keine Nummer')
quantity = product['attributes']['quantity']
price = product['attributes']['totalPrice']
pdf.drawString(70, y_position, f"{label}")
pdf.drawString(250, y_position, f"{product_number}")
pdf.drawString(350, y_position, f"{quantity}")
pdf.drawString(400, y_position, f"{price:.2f}")
y_position -= 20
# Falls die Seite voll ist, neue Seite beginnen
if y_position < 100:
pdf.showPage()
y_position = 800
y_position -= 40 # Platz nach jeder Bestellung
except Exception as e:
print(f"Fehler beim Erstellen des Lieferscheins für Bestellung {order['attributes']['orderNumber']}: {str(e)}")
pdf.save()
print(f"Lieferschein gespeichert: {filename}")
return filename
def print_all_orders():
access_token = get_access_token()
orders = get_paid_orders(access_token)
if not orders:
messagebox.showinfo("Info", "Keine Bestellungen gefunden.")
return
filename = generate_combined_pdf(orders, access_token)
messagebox.showinfo("Erfolg", f"Gesammelter Lieferschein {filename} wurde erstellt!")
def update_table():
access_token = get_access_token()
orders = get_paid_orders(access_token)
# Tabelle leeren
for row in tree_orders.get_children():
tree_orders.delete(row)
for order in orders:
order_number = order['attributes']['orderNumber']
_, billing_address = get_order_details(access_token, order['id'])
tree_orders.insert("", "end", values=(
order_number,
f"{billing_address['attributes']['firstName']} {billing_address['attributes']['lastName']}",
billing_address['attributes']['street'],
f"{billing_address['attributes']['zipcode']} {billing_address['attributes']['city']}"
))
def open_link(event=None):
webbrowser.open("https://itdata-gera.de")
def execute_with_error_handling(func, *args, **kwargs):
try:
func(*args, **kwargs) # Die gewünschte Funktion ausführen
except Exception as e:
root.after(0, show_error_message, str(e)) # Fehler in GUI anzeigen
# GUI-Fehlermeldung anzeigen
def show_error_message(error_text):
tkinter.messagebox.showerror("Fehler", f"Ein Fehler ist aufgetreten:\n{error_text}")
# GUI-Erstellung
root = ctk.CTk()
root.title("Lieferschein-Drucker")
root.resizable(False, False)
window_width = 900
window_height = 500
# Bildschirmgröße abrufen
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# Position berechnen (Mitte des Bildschirms)
x_position = (screen_width // 2) - (window_width // 2)
y_position = (screen_height // 2) - (window_height // 2)
# Fenstergröße & Position setzen
root.geometry(f"{window_width}x{window_height}+{x_position}+{y_position}")
# **Bestellübersicht**
frame_orders = ctk.CTkFrame(root)
button_all_print = ctk.CTkButton(frame_orders, text="API ändern", command=lambda: execute_with_error_handling(check_api.rerun),fg_color="#2b2b2b",text_color="white",anchor=tkinter.RIGHT)
button_all_print.pack(pady=5,anchor="ne")
label_orders = ctk.CTkLabel(frame_orders, text=f"Bezahlte Bestellungen - {datetime.datetime.now().strftime("%d.%m.%Y")}", font=("Helvetica", 15))
label_orders.pack(pady=5)
columns_orders = ("Bestellnummer", "Kunde", "Straße", "PLZ/Stadt")
tree_orders = ttk.Treeview(frame_orders, columns=columns_orders, show="headings", cursor="hand2")
for col in columns_orders:
tree_orders.heading(col, text=col)
tree_orders.column(col, width=150)
tree_orders.pack(expand=True, fill="both")
tree_orders.bind("<<TreeviewSelect>>", show_order_details)
button_update = ctk.CTkButton(frame_orders, text="Bestellungen laden", command=lambda: execute_with_error_handling(update_table), fg_color="lightblue",text_color="black",anchor=tkinter.RIGHT)
button_update.pack(side="left",pady=5,padx=10)
logo = ctk.CTkImage(dark_image=Image.open('logo.png'),size=(130,30))
logo_label = ctk.CTkLabel(root, text="", image=logo, fg_color="#2b2b2b",cursor="hand2")
logo_label.bind("<Button-1>", open_link)
logo_label.place(relx=0.5, rely=1.0, anchor="s",y=-15)
button_all_print = ctk.CTkButton(frame_orders, text="Gesammelten Lieferschein drucken", command=lambda: execute_with_error_handling(print_all_orders),fg_color="orange",text_color="black")
button_all_print.pack(side="right",pady=15, padx=10)
frame_orders.pack(fill="both", expand=True)
# **Detailansicht**
frame_details = ctk.CTkFrame(root)
label_details = ctk.CTkLabel(frame_details, text="", font=("Helvetica", 12))
label_details.pack(pady=5)
columns_details = ("Produkt", "Anzahl", "Preis")
tree_details = ttk.Treeview(frame_details, columns=columns_details, show="headings")
for col in columns_details:
tree_details.heading(col, text=col)
tree_details.pack(expand=True, fill="both")
button_back = ctk.CTkButton(frame_details, text="Zurück", command=back_to_orders)
button_back.pack(side="left", padx=20, pady=15)
logo_label_1 = ctk.CTkLabel(root, text="", image=logo, fg_color="#2b2b2b",cursor="hand2")
logo_label_1.bind("<Button-1>", open_link)
logo_label_1.place(relx=0.5, rely=1.0, anchor="s",y=-15)
button_print = ctk.CTkButton(frame_details, text="Diese Bestellung drucken", command=print_single_order, fg_color="lightgreen", text_color="black")
button_print.pack(side="right", padx=20,pady=15)
# Error Handling für Timeout
root.after(100, lambda: execute_with_error_handling(update_table))
root.mainloop()