diff --git a/.gitignore b/.gitignore index b98a174..a1a8580 100644 --- a/.gitignore +++ b/.gitignore @@ -294,3 +294,5 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk +# Sensitive details +config.yaml diff --git a/app/data/invoices.csv b/app/data/invoices.csv index 6d7aba2..32e76ac 100644 --- a/app/data/invoices.csv +++ b/app/data/invoices.csv @@ -1,2 +1,2 @@ Rechnungsnummer;Email;Anrede -RE357891;isaak.buslovich@trocknerheld.de;Isaak Trocknerheld +RE255111;fws-rs@vielhuber.eu;Sehr geehrter Herr Vielhuber diff --git a/app/lexoffice.py b/app/lexoffice.py new file mode 100644 index 0000000..3d22a0c --- /dev/null +++ b/app/lexoffice.py @@ -0,0 +1,58 @@ +""" +Module: lexoffice.py +Description: Provides an interface to interact with the Lexoffice API. +This module enables operations such as retrieving invoice details from Lexoffice, +making it easier to integrate Lexoffice API functionality into a Flask application. +""" + +from pathlib import Path +import yaml +import requests +import logging + + +class LexofficeAPI: + """Class to handle interactions with the Lexoffice API.""" + + def __init__(self): + """Initialize with the Lexoffice API access token from a config file.""" + self.base_url = "https://api.lexoffice.io/v1" + self.headers = { + "Authorization": f"Bearer {self._load_api_token()}", + "Content-Type": "application/json", + "Accept": "application/json" + } + + @staticmethod + def _load_api_token() -> str: + """Load the API token from the config.yaml file.""" + config_path = Path(__file__).parent.parent / 'config.yaml' + with config_path.open('r') as file: + config = yaml.safe_load(file) + return config['lexoffice']['api_token'] + + def get_invoice_total_gross_amount(self, invoice_id: str) -> float: + """ + Retrieve the total gross amount for a specific invoice. + + Args: + invoice_id (str): Unique identifier of the invoice. + + Returns: + float or None: Total gross amount of the invoice, or None if an error occurs. + """ + url = f"{self.base_url}/invoices/{invoice_id}" + + try: + response = requests.get(url, headers=self.headers) + response.raise_for_status() + return response.json().get('totalPrice', {}).get('totalGrossAmount') + + except requests.exceptions.RequestException as e: + logging.error(f"Error fetching invoice: {e}") + return None + +# Usage example: +# lexoffice_api = LexofficeAPI() +# total_gross_amount = lexoffice_api.get_invoice_total_gross_amount('your_invoice_id') +# print(total_gross_amount) diff --git a/app/routes.py b/app/routes.py index ff794a7..4da4e89 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,8 +1,10 @@ from flask import render_template, redirect, url_for, current_app, render_template_string, request import os import csv - from app import app, htmx +import logging + +from app.lexoffice import LexofficeAPI def read_invoices_from_csv(csv_file_path): @@ -10,10 +12,11 @@ def read_invoices_from_csv(csv_file_path): with open(csv_file_path, mode='r', encoding='utf-8') as file: return list(csv.DictReader(file, delimiter=';')) except FileNotFoundError: - current_app.logger.error(f"CSV file not found at {csv_file_path}") + logging.error(f"CSV file not found at {csv_file_path}") + return [] except csv.Error as e: - current_app.logger.error(f"Error reading CSV file: {e}") - return [] + logging.error(f"Error reading CSV file: {e}") + return [] @app.route('/load-invoices') @@ -48,31 +51,46 @@ def buchhaltung(): @app.route('/add-invoice', methods=['POST']) def add_invoice(): csv_file_path = os.path.join(current_app.root_path, 'data', 'invoices.csv') + + # Extract invoice data from form new_invoice = { 'Rechnungsnummer': request.form['rechnungsnummer'], 'Email': request.form['email'], - 'Anrede': request.form['anrede'] + 'Anrede': request.form['anrede'], + # Initialize 'Betrag' with a placeholder (to be updated) + 'Betrag': 'N/A' } try: - # Read the existing data + # Fetch the total gross amount for the new invoice using LexofficeAPI + lexoffice_api = LexofficeAPI('your_access_token') # Replace with actual access token + total_gross_amount = lexoffice_api.get_invoice_total_gross_amount(new_invoice['Rechnungsnummer']) + + # Update 'Betrag' in the new invoice data + if total_gross_amount is not None: + new_invoice['Betrag'] = total_gross_amount + + # Read existing data from CSV with open(csv_file_path, 'r', newline='', encoding='utf-8') as csvfile: reader = csv.DictReader(csvfile, delimiter=';') fieldnames = reader.fieldnames + if 'Betrag' not in fieldnames: + fieldnames.append('Betrag') existing_data = list(reader) # Add the new invoice at the beginning of the data existing_data.insert(0, new_invoice) - # Write the updated data back to the file + # Write the updated data back to the CSV file with open(csv_file_path, 'w', newline='', encoding='utf-8') as csvfile: writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter=';') writer.writeheader() writer.writerows(existing_data) return redirect(url_for('buchhaltung')) + except Exception as e: - current_app.logger.error(f"Error updating CSV file: {e}") + logging.error(f"Error updating CSV file: {e}") return "An error occurred while adding the invoice", 500 diff --git a/app/static/css/style.css b/app/static/css/style.css index b5af572..186760f 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -1,5 +1,5 @@ /* Import Google Fonts */ -@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap'); /* Root and Variable Definitions */ :root { @@ -17,7 +17,7 @@ /* Body and Global Styles */ body { - font-family: 'Roboto', sans-serif; + font-family: 'Inter', sans-serif; margin: 0; padding: 0; background: var(--light-grey); diff --git a/app/templates/partials/invoice_table.html b/app/templates/partials/invoice_table.html index 673e891..c691328 100644 --- a/app/templates/partials/invoice_table.html +++ b/app/templates/partials/invoice_table.html @@ -5,6 +5,7 @@