diff --git a/.env b/.env index 28c0ea8..5948ca5 100644 --- a/.env +++ b/.env @@ -2,4 +2,12 @@ MYSQL_HOST="172.17.1.21" MYSQL_USER="root" MYSQL_PASSWORD="N53yBCswuawzBzS445VNAhWVMs3N59Gb9szEsrzXRBzarDqpdETpQeyt5v5CGe" MYSQL_DATABASE="Kunden" -MYSQL_AUTH='mysql_native_password' \ No newline at end of file +MYSQL_AUTH='mysql_native_password' + +## Ticketsystem +ZAMMAD_URL = "https://ticket.stines.de/api/v1" +ZAMMAD_API_TOKEN ="1v4XGY7cZpBXSfb4s_tIBbywQjcaDV6q65IXQyVXrrBDqVtmAtLxM5tOqIAp0VXZ" + +## API-Server +API_SERVER = "http://api.stines.de:8001" +API_TOKEN = "^YWUbG7yX*V!tV^KBSd*2c&vdN3wV9a2i7f3hfGFMBYFxi6#mMiJGiaA5KEHE%B*miK%qb7rQ67gmcYP@gqmux8" \ No newline at end of file diff --git a/.streamlit/config.toml b/.streamlit/config.toml index fe7c2dc..6bb3aa9 100644 --- a/.streamlit/config.toml +++ b/.streamlit/config.toml @@ -4,4 +4,6 @@ primaryColor="#1abc9c" backgroundColor="#2c3e50" secondaryBackgroundColor="#34495e" textColor="#ffffff" -font="sans serif" \ No newline at end of file +font="sans serif" + +layout="wide" \ No newline at end of file diff --git a/app.py b/app.py index 2d9e41d..429cc27 100644 --- a/app.py +++ b/app.py @@ -2,9 +2,10 @@ import streamlit as st import sites.services_reporting as sr import sites.userlist as us import sites.server as s - +import sites.tickets as ti +import sites.lastrun as lr # Page Settings -st.set_page_config(page_title="Reporting") +st.set_page_config(page_title="Reporting",layout="wide") # Load custom CSS def load_css(file_name): @@ -27,17 +28,25 @@ if st.sidebar.button('Home'): st.session_state.page = 'Home' if st.sidebar.button('Services Reporting'): st.session_state.page = 'Services Reporting' -if st.sidebar.button('User'): - st.session_state.page = 'User' +if st.sidebar.button('User Filter'): + st.session_state.page = 'User Filter' if st.sidebar.button('Server'): st.session_state.page = 'Server' +if st.sidebar.button('Tickets'): + st.session_state.page = 'Tickets' +if st.sidebar.button('Last-Run'): + st.session_state.page = 'Last-Run' # Page display logic if st.session_state.page == 'Home': home() elif st.session_state.page == 'Services Reporting': sr.services_reporting() -elif st.session_state.page == 'User': +elif st.session_state.page == 'User Filter': us.user_filter() elif st.session_state.page == 'Server': s.server_filter() +elif st.session_state.page == 'Tickets': + ti.ticket_filter() +elif st.session_state.page == 'Last-Run': + lr.user_filter() \ No newline at end of file diff --git a/apps/ticket_export/.env b/apps/ticket_export/.env new file mode 100644 index 0000000..5948ca5 --- /dev/null +++ b/apps/ticket_export/.env @@ -0,0 +1,13 @@ +MYSQL_HOST="172.17.1.21" +MYSQL_USER="root" +MYSQL_PASSWORD="N53yBCswuawzBzS445VNAhWVMs3N59Gb9szEsrzXRBzarDqpdETpQeyt5v5CGe" +MYSQL_DATABASE="Kunden" +MYSQL_AUTH='mysql_native_password' + +## Ticketsystem +ZAMMAD_URL = "https://ticket.stines.de/api/v1" +ZAMMAD_API_TOKEN ="1v4XGY7cZpBXSfb4s_tIBbywQjcaDV6q65IXQyVXrrBDqVtmAtLxM5tOqIAp0VXZ" + +## API-Server +API_SERVER = "http://api.stines.de:8001" +API_TOKEN = "^YWUbG7yX*V!tV^KBSd*2c&vdN3wV9a2i7f3hfGFMBYFxi6#mMiJGiaA5KEHE%B*miK%qb7rQ67gmcYP@gqmux8" \ No newline at end of file diff --git a/apps/ticket_export/main.py b/apps/ticket_export/main.py new file mode 100644 index 0000000..83a0a43 --- /dev/null +++ b/apps/ticket_export/main.py @@ -0,0 +1,278 @@ +import mysql.connector +from docx import Document +from datetime import datetime +from docx.shared import Pt +from docx.oxml.ns import qn +from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK +from docx.enum.table import WD_ALIGN_VERTICAL +from docx.oxml import OxmlElement +import os +from datetime import datetime, timedelta +from dotenv import load_dotenv +from dateutil.relativedelta import relativedelta + +load_dotenv() + +def fetch_tickets_from_database(): + # Verbindung zur Datenbank herstellen + mydb = mysql.connector.connect( + host=os.getenv("MYSQL_HOST"), + user=os.getenv("MYSQL_USER"), + password=os.getenv("MYSQL_PASSWORD"), + database=os.getenv("MYSQL_DATABASE") + ) + cursor = mydb.cursor() + + # Tickets abrufen, inklusive customer_ID + cursor.execute(""" + SELECT t.`number`, t.title, t.createdate, t.`type`, t.customer_ID, tct.firstdate, t.time, s.price, t.service_ID + FROM Kunden.tickets t + JOIN Kunden.`tickets.customer.timerange` tct ON t.customer_ID = tct.customer_ID + JOIN Kunden.services s ON s.service_ID = t.service_ID + ORDER by t.createdate ASC + """) + tickets = cursor.fetchall() + mydb.close() + return tickets + +def fetch_customer_data(customer_id): + # Verbindung zur Datenbank herstellen + mydb = mysql.connector.connect( + host=os.getenv("MYSQL_HOST"), + user=os.getenv("MYSQL_USER"), + password=os.getenv("MYSQL_PASSWORD"), + database=os.getenv("MYSQL_DATABASE") + ) + cursor = mydb.cursor() + + # Kundendaten basierend auf customer_ID abrufen + cursor.execute("""SELECT co.companyname, co.street,co.housenumber, co.postcode, co.city, c.customer FROM company co + JOIN customers c ON c.customer_ID = co.customer_ID + WHERE co.customer_id = %s""", (customer_id,)) + customer_data = cursor.fetchone() + + mydb.close() + return customer_data + +def fetch_customer_servicetime(customer_id): + # Verbindung zur Datenbank herstellen + mydb = mysql.connector.connect( + host=os.getenv("MYSQL_HOST"), + user=os.getenv("MYSQL_USER"), + password=os.getenv("MYSQL_PASSWORD"), + database=os.getenv("MYSQL_DATABASE") + ) + cursor = mydb.cursor() + + # Kundendaten basierend auf customer_ID abrufen + cursor.execute("""SELECT servicetime FROM `tickets.customers.servicetime` + WHERE customer_id = %s""", (customer_id,)) + customer_servicetime = cursor.fetchone() + + mydb.close() + return customer_servicetime + + +def fetch_customer_price(price, customer_ID, service_ID): + mydb = mysql.connector.connect( + host=os.getenv("MYSQL_HOST"), + user=os.getenv("MYSQL_USER"), + password=os.getenv("MYSQL_PASSWORD"), + database=os.getenv("MYSQL_DATABASE") + ) + cursor = mydb.cursor() + + # Prozentwert aus der Datenbank abrufen + cursor.execute(f"""SELECT percent FROM `customers.pricechange` + WHERE customer_ID = {customer_ID} and service_ID = {service_ID}""") + customer_price_percent = cursor.fetchone() + mydb.close() + + # Preis in Minuten umrechnen (da der aktuelle Preis in Stunden angegeben ist) + price_per_minute = price / 60 # Umrechnung von Stunden auf Minuten + + # Falls ein Prozentwert existiert, diesen auf den Minutenpreis anwenden + if customer_price_percent: + price_per_minute += price_per_minute * (customer_price_percent[0] / 100) + # print(f"Preis pro Minute nach Aufschlag von {customer_price_percent[0]}%: {price_per_minute:.2f} €") + else: + print(f"Standard Preis pro Minute: {price_per_minute:.2f} €") + + return price_per_minute + +def replace_text_in_run_with_format(run, key, value): + if f"{{{key}}}" in run.text: + run.text = run.text.replace(f"{{{key}}}", str(value)) + run.font.name = 'Verdana' + run.font.size = Pt(10) + run.font.bold = False + run.font.italic = False + +def insert_page_break(paragraph): + run = paragraph.add_run() + run.add_break(WD_BREAK.PAGE) + +def replace_text_in_paragraph(paragraph, key, value): + for run in paragraph.runs: + replace_text_in_run_with_format(run, key, value) + +def set_cell_border(cell, **kwargs): + tc = cell._tc + tcPr = tc.get_or_add_tcPr() + + borders = OxmlElement('w:tcBorders') + + for border_name in ["top", "left", "bottom", "right"]: + border = kwargs.get(border_name, None) + if border: + element = OxmlElement(f"w:{border_name}") + element.set(qn("w:val"), border.get("val", "single")) + element.set(qn("w:sz"), border.get("sz", "4")) + element.set(qn("w:space"), border.get("space", "0")) + element.set(qn("w:color"), border.get("color", "000000")) + borders.append(element) + + tcPr.append(borders) + +def fill_template(doc_path, output_path, data, tickets): + doc = Document(doc_path) + + # Platzhalter in normalem Text ersetzen + for paragraph in doc.paragraphs: + for key, value in data.items(): + if f"{{{key}}}" in paragraph.text: + replace_text_in_paragraph(paragraph, key, value) + if "{page_breaker}" in paragraph.text: + if len(tickets) >= 7: + paragraph.text = paragraph.text.replace("{page_breaker}", "") + insert_page_break(paragraph) + else: + paragraph.text = paragraph.text.replace("{page_breaker}", "") + + # Platzhalter in der ersten Tabelle ersetzen + first_table = doc.tables[0] + for row in first_table.rows: + for cell in row.cells: + for paragraph in cell.paragraphs: + for key, value in data.items(): + replace_text_in_paragraph(paragraph, key, value) + + # Bearbeiten der zweiten Tabelle für Ticketdaten + second_table = doc.tables[1] + for ticket in tickets: + row = second_table.add_row() + row.cells[0].text = ticket[0] # Ticketnummer + row.cells[1].text = ticket[1] # Tickettitel + row.cells[2].text = ticket[2].strftime("%d.%m.%Y") # createdate als String formatieren + row.cells[3].text = str(ticket[6]) # timerange (time_unit) + + # Formatierung und Ausrichtung für jede Zelle der neuen Zeile setzen + for idx, cell in enumerate(row.cells): + for paragraph in cell.paragraphs: + if idx == 1: + paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT + else: + paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER + + for run in paragraph.runs: + run.font.name = 'Verdana' + run.font.size = Pt(10) + run.font.bold = False + cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER + set_cell_border(cell, top={"val": "single", "sz": "4", "color": "000000"}, + bottom={"val": "single", "sz": "4", "color": "000000"}, + left={"val": "single", "sz": "4", "color": "000000"}, + right={"val": "single", "sz": "4", "color": "000000"}) + + # Berechnungen (z.B. Gesamtzeit, Preise) + gesamt_time_unit = sum(int(ticket[6]) for ticket in tickets) + data['gesamt_time_unit'] = gesamt_time_unit + + # Zeit größer als Servicetime + if gesamt_time_unit >= customer_servicetime[0]: + data['sl_time_unit'] = gesamt_time_unit + customer_servicetime[0] + data['sl_minus_unit'] = gesamt_time_unit - customer_servicetime[0] + data['time_unit_sum'] = gesamt_time_unit + data['zl_time_unit'] + data['price_per_minute'] = customer_price + + data['price_ex_mwst'] = gesamt_time_unit * data['price_per_minute'] + data['mwst_set'] = data['price_ex_mwst'] * 0.19 + data['sum'] = data['price_ex_mwst'] + data['mwst_set'] + + data['price_ex_mwst'] = f"{data['price_ex_mwst']:.2f}".replace(".",",") + data['mwst_set'] = f"{data['mwst_set']:.2f}".replace(".",",") + data['sum'] = f"{data['sum']:.2f}".replace(".",",") + + + # Platzhalter in der dritten Tabelle ersetzen + third_table = doc.tables[2] + for row in third_table.rows: + for cell in row.cells: + for paragraph in cell.paragraphs: + for key, value in data.items(): + if key == "sum": + # Setze den Text und mache ihn fett + for run in paragraph.runs: + if f"{{{key}}}" in run.text: + run.text = run.text.replace(f"{{{key}}}", str(value)) + run.font.bold = True # Fett setzen + else: + replace_text_in_paragraph(paragraph, key, value) + + doc.save(output_path) + +# Hauptprogramm +if __name__ == "__main__": + tickets = fetch_tickets_from_database() + service_ID = next(iter(set(ticket[8] for ticket in tickets))) + price = next(iter(set(ticket[7] for ticket in tickets))) + customer_ids = set(ticket[4] for ticket in tickets) + + for customer_id in customer_ids: + customer_data = fetch_customer_data(customer_id) + customer_servicetime = fetch_customer_servicetime(customer_id) + customer_price = fetch_customer_price(price, customer_id, service_ID) + customer_tickets = [ticket for ticket in tickets if ticket[4] == customer_id] + + if not customer_data: + print(f"Keine Kundendaten für Kunden-ID {customer_id} gefunden!") + continue + + day = customer_tickets[0][5] + now = datetime.now() + if now.month == 1: + last_month = 12 + year = now.year - 1 + else: + last_month = now.month - 1 + year = now.year + startdate = datetime(year, last_month, day) + enddate = startdate + relativedelta(months=1) - timedelta(days=1) + + data = { + "name": customer_data[0], + "street": customer_data[1], + "housenumber": customer_data[2], + "postcode": customer_data[3], + "city": customer_data[4], + "customernumber": customer_data[5], + "year": datetime.now().year, + "ordernumber": "0001", + "startdate": startdate.strftime("%d.%m.%Y"), + "enddate": enddate.strftime("%d.%m.%Y"), + "today": datetime.now().strftime("%d.%m.%Y"), + "price_per_minute": customer_price, + "servicetime": customer_servicetime[0], + "gesamt_time_unit": 0, + "price_ex_mwst": 0, + "sl_time_unit": 0, + "sl_minus_unit": 0, + "zl_time_unit": 0, + "time_unit_sum": 0, + "mwst_set": 0, + "sum": 0, + } + + output_path = f"apps/ticket_export/exports/RE2024.{customer_data[5]}.{data['ordernumber']}.docx" + fill_template('apps/ticket_export/template.docx', output_path, data, customer_tickets) +print("True") diff --git a/apps/ticket_export/template.docx b/apps/ticket_export/template.docx new file mode 100644 index 0000000..18a9b43 Binary files /dev/null and b/apps/ticket_export/template.docx differ diff --git a/sites/lastrun.py b/sites/lastrun.py new file mode 100644 index 0000000..354f646 --- /dev/null +++ b/sites/lastrun.py @@ -0,0 +1,94 @@ +import streamlit as st +import pandas as pd +import mysql.connector +from datetime import datetime +import os +from dotenv import load_dotenv + +load_dotenv() + + +def get_filtered_users(customer_id, service_id): + mydb = mysql.connector.connect( + host=os.getenv("MYSQL_HOST"), + user=os.getenv("MYSQL_USER"), + password=os.getenv("MYSQL_PASSWORD"), + database=os.getenv("MYSQL_DATABASE") + ) + + query = f"""SELECT * FROM Kunden.`services.reporting` where DATE(reportingdate) = CURDATE() """ + + + if customer_id: + query += f"AND customer_ID = {customer_id}" + if service_id: + query += f" AND service_ID = {service_id}" + query += " ORDER BY reportingdate DESC" + users = pd.read_sql_query(query, mydb) + print(query) + mydb.close() + return users + + +def get_initial_data(): + mydb = mysql.connector.connect( + host=os.getenv("MYSQL_HOST"), + user=os.getenv("MYSQL_USER"), + password=os.getenv("MYSQL_PASSWORD"), + database=os.getenv("MYSQL_DATABASE") + ) + # Fetch unique service IDs and names + service_id_query = """ + SELECT DISTINCT s.service_ID, s.name + FROM Kunden.services s + """ + service_ids = pd.read_sql_query(service_id_query, mydb) + + # Fetch customer information + customer_query = """ + SELECT DISTINCT c.customer_ID, c.customer, co.companyname + FROM Kunden.company co + JOIN Kunden.customers c ON co.customer_ID = c.customer_ID + """ + customers = pd.read_sql_query(customer_query, mydb) + + mydb.close() + return service_ids, customers + + +def user_filter(): + st.title("User Filter :mag_right:") + # Get initial data for widgets + initial_service_ids, customers = get_initial_data() + # Combine service_ID and name for display + service_options = initial_service_ids.apply(lambda row: f"{row['service_ID']} - {row['name']}", axis=1) + + # Add selection widget for customer ID + selected_customer = st.selectbox( + 'Select Customer', + ["All"] + customers.apply(lambda row: f"{row['customer_ID']} - {row['companyname']} - {row['customer']}", + axis=1).tolist() + ) + + # Extract customer_ID from selected option + selected_customer_id = None if selected_customer == "All" else int(selected_customer.split(' - ')[0]) + + # Add selection widget for service ID + selected_service = st.selectbox( + 'Select Service', + ["All"] + service_options.tolist() + ) + + # Extract service_ID from selected option + selected_service_id = None if selected_service == "All" else int(selected_service.split(' - ')[0]) + + # Add a button to apply filters + if st.button('Apply Filters'): + # Fetch filtered data from the database + filtered_data = get_filtered_users(selected_customer_id, selected_service_id) + + # Display the filtered data + if not filtered_data.empty: + st.dataframe(filtered_data) + else: + st.write("No data available for the selected filters.") \ No newline at end of file diff --git a/sites/server.py b/sites/server.py index 0b4a3d6..98d435b 100644 --- a/sites/server.py +++ b/sites/server.py @@ -31,7 +31,6 @@ def get_filtered_server(customer_id, service_id, service_status): query += f" AND s.status = {service_status}" users = pd.read_sql_query(query, mydb) - print(query) mydb.close() return users diff --git a/sites/tickets.py b/sites/tickets.py new file mode 100644 index 0000000..4e882c4 --- /dev/null +++ b/sites/tickets.py @@ -0,0 +1,140 @@ +import streamlit as st +import pandas as pd +import subprocess +import mysql.connector +from datetime import datetime +import os +from dotenv import load_dotenv + +load_dotenv() +total_time_unit = [] + +def get_filtered_users(customer_id): + mydb = mysql.connector.connect( + host=os.getenv("MYSQL_HOST"), + user=os.getenv("MYSQL_USER"), + password=os.getenv("MYSQL_PASSWORD"), + database=os.getenv("MYSQL_DATABASE") + ) + + # Prepare the base query + query = f""" + SELECT number,title,createdate,time FROM Kunden.tickets + WHERE 1=1 + """ + if customer_id: + query += f"AND customer_ID = {customer_id} order by createdate" + + users = pd.read_sql_query(query, mydb) + mydb.close() + + for column in users.select_dtypes(include=['datetime64[ns]', 'datetime64[ns, UTC]']).columns: + users[column] = users[column].dt.strftime('%d.%m.%Y') + + users.iloc[:, 3] = users.iloc[:, 3].str.split('.').str[0] + users['time'] = users['time'].astype(int) + + base_url = "https://ticket.stines.de/#ticket/zoom/number/" # Replace with your actual base URL + users['Link'] = users['number'].apply(lambda x: f'{x}') + + return users + +def get_initial_data(): + mydb = mysql.connector.connect( + host=os.getenv("MYSQL_HOST"), + user=os.getenv("MYSQL_USER"), + password=os.getenv("MYSQL_PASSWORD"), + database=os.getenv("MYSQL_DATABASE") + ) + # Fetch unique service IDs and names + service_id_query = """ + SELECT DISTINCT s.service_ID, s.name + FROM Kunden.services s + """ + service_ids = pd.read_sql_query(service_id_query, mydb) + + # Fetch customer information + customer_query = """ + SELECT DISTINCT c.customer_ID, c.customer, co.companyname + FROM Kunden.company co + JOIN Kunden.customers c ON co.customer_ID = c.customer_ID + """ + customers = pd.read_sql_query(customer_query, mydb) + + mydb.close() + return service_ids, customers + +def run_script_and_list_documents(): + output_area = st.empty() # Platzhalter für die Ausgabe + document_paths = [] # Liste der erstellten Dokumente + + # Verzeichnis, in dem das Skript ausgeführt werden soll + working_directory = "apps/ticket_export/exports" + + # Führe das Skript in dem angegebenen Verzeichnis aus + result = subprocess.run( + ["python3", "apps/ticket_export/main.py"], + capture_output=True, + text=True + ) + + # Suche nach den erstellten Dokumenten im Arbeitsverzeichnis + for filename in os.listdir("apps/ticket_export/exports"): + if filename.endswith(".docx"): + full_path = os.path.join(working_directory, filename) + document_paths.append(full_path) + + # Zeige die Liste der erstellten Dokumente an und biete sie zum Download an + if document_paths: + st.write("Erstellte Dokumente:") + for doc_path in document_paths: + with open(doc_path, "rb") as file: + st.download_button( + label=f"Download {os.path.basename(doc_path)}", + data=file, + file_name=os.path.basename(doc_path), + mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document" + ) + else: + st.write("Keine Dokumente gefunden.") + +def ticket_filter(): + st.title("Ticket Filter :mag_right:") + # Get initial data for widgets + initial_service_ids, customers = get_initial_data() + + # Add selection widget for customer ID + selected_customer = st.selectbox( + 'Select Customer', + ["All"] + customers.apply(lambda row: f"{row['customer_ID']} - {row['companyname']} - {row['customer']}", + axis=1).tolist() + ) + # Extract customer_ID from selected option + selected_customer_id = None if selected_customer == "All" else int(selected_customer.split(' - ')[0]) + + if st.button("Rechnung erstellen"): + st.write("Das Skript wird ausgeführt...") + run_script_and_list_documents() + + # Add a button to apply filters + if st.button('Apply Filters'): + # Fetch filtered data from the database + filtered_data = get_filtered_users(selected_customer_id) + if not filtered_data.empty: + # st.dataframe(filtered_data,hide_index=True) + st.markdown(filtered_data.to_html(escape=False), unsafe_allow_html=True) + # Convert DataFrame to CSV + csv = filtered_data.drop(columns=['Link']).to_csv(index=False) + + st.write(f"Total Time Unit: {filtered_data['time'].sum()}") + + # Create a download button with a custom file name + st.download_button( + label="Download CSV", + data=csv, + file_name=f"filtered_data_{selected_customer}.csv", # Custom file name + mime='text/csv', + ) + + else: + st.write("No data available for the selected filters.") \ No newline at end of file diff --git a/sites/userlist.py b/sites/userlist.py index ea7e495..c1b6278 100644 --- a/sites/userlist.py +++ b/sites/userlist.py @@ -18,7 +18,7 @@ def get_filtered_users(customer_id, service_id, service_status): # Prepare the base query query = f""" - SELECT us.user_id, u.username, s.service_ID, uss.status, uss.timestamp + SELECT us.user_id, u.username, s.service_ID, uss.status, uss.timestamp, c.companyname FROM Kunden.`users.status` us JOIN ( SELECT user_id, MAX(timestamp) AS latest_timestamp @@ -28,14 +28,16 @@ def get_filtered_users(customer_id, service_id, service_status): JOIN Kunden.users u ON u.user_ID = us.user_id JOIN Kunden.`users.services` uss ON us.user_id = uss.user_id JOIN Kunden.services s ON uss.service_ID = s.service_ID - WHERE us.customer_ID = {customer_id} and us.status = {service_status} + JOIN Kunden.company c ON c.customer_ID = us.customer_ID + WHERE 1=1 """ - + if customer_id: + query += f"AND us.customer_ID = {customer_id}" if service_id: query += f" AND s.service_ID = {service_id}" if service_status: query += f" AND uss.status = {service_status}" - + query += " ORDER BY uss.status DESC" users = pd.read_sql_query(query, mydb) mydb.close() return users