diff --git a/README.md b/README.md new file mode 100644 index 0000000..9ed608e --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# Gestionnaire de Factures + +Application interne pour la gestion et la génération de factures. + +## Installation + +1. Installer les dépendances : + +```bash +pip install -r requirements.txt +``` + +2. Installer Typst : + +```bash +brew install typst # macOS +``` + +## Utilisation + +1. Démarrer le serveur : + +```bash +python3 server.py +``` + +2. Accéder à l'application : + +- http://localhost:5000 : Page d'accueil +- http://localhost:5000/dashboard : Tableau de bord +- http://localhost:5000/generator : Création de factures + +## Fonctionnalités + +- Création de factures (FR/DE/EN) +- Génération de PDF avec Typst +- Tableau de bord des factures +- Support EUR/CHF +- Numérotation automatique + +## Structure + +``` +accounting/ +├── server.py # Application Flask +├── database.py # Base de données SQLite +├── requirements.txt # Dépendances +├── static/ # Fichiers statiques +└── generated/ # PDF générés +``` + +## Fonctionnalités Techniques + +- **Gestion de la Base de Données** : + + - Système de migration pour les mises à jour du schéma + - Gestion des connexions avec timeout et retry + - Mode WAL pour de meilleures performances + +- **Génération de PDF** : + + - Templates Typst personnalisables + - Support multilingue + - Mise en page professionnelle + +- **Interface Utilisateur** : + - Design responsive avec Tailwind CSS + - Prévisualisation en temps réel + - Validation des formulaires + +## Contribution + +Les contributions sont les bienvenues ! N'hésitez pas à : + +1. Fork le projet +2. Créer une branche pour votre fonctionnalité +3. Commiter vos changements +4. Pousser vers la branche +5. Ouvrir une Pull Request + +## Licence + +Ce projet est sous licence MIT. Voir le fichier `LICENSE` pour plus de détails. diff --git a/database.py b/database.py index 1b4bdcf..d77a602 100644 --- a/database.py +++ b/database.py @@ -182,25 +182,34 @@ class Database: conn.commit() def get_statistics(self): + """Récupérer les statistiques des factures""" with self.get_connection() as conn: c = conn.cursor() - stats = {} - - # Total des factures + # Nombre total de factures c.execute('SELECT COUNT(*) FROM invoices') - stats['total_invoices'] = c.fetchone()[0] + total_invoices = c.fetchone()[0] or 0 # Montant total des factures c.execute('SELECT SUM(amount) FROM invoices') - stats['total_amount'] = c.fetchone()[0] or 0 + total_amount = c.fetchone()[0] or 0 - # Montant total des factures payées + # Montant des factures payées c.execute('SELECT SUM(amount) FROM invoices WHERE status = "paid"') - stats['total_paid'] = c.fetchone()[0] or 0 + total_paid = c.fetchone()[0] or 0 - # Montant total des factures en retard + # Montant des factures en retard c.execute('SELECT SUM(amount) FROM invoices WHERE status = "overdue"') - stats['total_overdue'] = c.fetchone()[0] or 0 + total_overdue = c.fetchone()[0] or 0 - return stats \ No newline at end of file + # Montant des factures annulées + c.execute('SELECT SUM(amount) FROM invoices WHERE status = "cancelled"') + total_cancelled = c.fetchone()[0] or 0 + + return { + 'total_invoices': total_invoices, + 'total_amount': total_amount, + 'total_paid': total_paid, + 'total_overdue': total_overdue, + 'total_cancelled': total_cancelled + } \ No newline at end of file diff --git a/invoices.db b/invoices.db new file mode 100644 index 0000000..2f4ba03 Binary files /dev/null and b/invoices.db differ diff --git a/server.py b/server.py index f7f204d..9cda882 100644 --- a/server.py +++ b/server.py @@ -307,11 +307,11 @@ def create_invoice(): #t.account_holder Robin Szymczak ] else if currency == "CHF" [ // Compte pour les transactions en francs suisses - #t.bank PostFinance SA, Mingerstrasse 20, 3030 Bern, Switzerland + #t.bank Wise Payments Limited, 1st Floor, Worship Square, 65 Clifton Street, London, EC2A 4JE, United Kingdom #v(0.1cm) - IBAN: CH56 0900 0000 1527 2120 9 + IBAN: GB51 TRWI 2308 0140 8766 98 #v(0.1cm) - BIC/SWIFT: POFICHBEXXX + BIC/SWIFT: TRWIGB2LXXX #v(0.1cm) #t.account_holder Robin Szymczak ] else [ diff --git a/static/dashboard.html b/static/dashboard.html index 6fffd7c..995dbb8 100644 --- a/static/dashboard.html +++ b/static/dashboard.html @@ -22,7 +22,7 @@ -
0
@@ -39,6 +39,10 @@0 €
0 €
+Banque: PostFinance SA, Berne, Suisse
-IBAN: CH56 0900 0000 1527 2120 9
-BIC/SWIFT: POFICHBEXXX
+Banque: Wise Payments Limited, London, United Kingdom
+IBAN: GB51 TRWI 2308 0140 8766 98
+BIC/SWIFT: TRWIGB2LXXX
Titulaire: Robin Szymczak
`; } else { @@ -104,6 +104,14 @@ document.addEventListener("DOMContentLoaded", function () { } } + // Gérer le bouton "Retour" + document.getElementById("backToForm").addEventListener("click", function () { + // Sauvegarder les données du formulaire dans localStorage + localStorage.setItem("invoiceData", JSON.stringify(formData)); + // Rediriger vers la page du générateur + window.location.href = "/generator"; + }); + // Gérer la validation de la facture document .getElementById("validateInvoice") @@ -125,6 +133,13 @@ document.addEventListener("DOMContentLoaded", function () { const result = await response.json(); console.log("Facture créée avec succès:", result); alert("Facture créée avec succès !"); + + // Effacer les données du formulaire du localStorage après validation réussie + localStorage.removeItem("invoiceData"); + + // Supprimer également de l'historique des factures récentes + removeFromRecentInvoices(formData.recipient_name); + window.location.href = "/dashboard"; } else { const error = await response.json(); @@ -139,4 +154,24 @@ document.addEventListener("DOMContentLoaded", function () { alert("Erreur lors de la création de la facture : " + error.message); } }); + + // Fonction pour supprimer une facture de l'historique récent + function removeFromRecentInvoices(recipientName) { + if (!recipientName) return; + + try { + let recentInvoices = JSON.parse( + localStorage.getItem("recentInvoices") || "[]" + ); + + // Filtrer pour supprimer la facture avec le même destinataire + recentInvoices = recentInvoices.filter( + (invoice) => invoice.recipient_name !== recipientName + ); + + localStorage.setItem("recentInvoices", JSON.stringify(recentInvoices)); + } catch (error) { + console.error("Erreur lors de la suppression de l'historique:", error); + } + } }); diff --git a/static/script.js b/static/script.js index 6e18271..2c81566 100644 --- a/static/script.js +++ b/static/script.js @@ -2,13 +2,393 @@ document.addEventListener("DOMContentLoaded", () => { const form = document.getElementById("invoiceForm"); const invoiceItems = document.getElementById("invoiceItems"); const addItemButton = document.getElementById("addItemButton"); + const clearFormButton = document.createElement("button"); + const recipientNameInput = form.recipient_name; + const clientSuggestions = document.createElement("div"); - // Initialiser avec une ligne de facturation par défaut - addInvoiceItem(); + // Configuration de la boîte de suggestions + clientSuggestions.className = + "client-suggestions absolute z-10 bg-white shadow-lg rounded-md w-full max-h-60 overflow-y-auto hidden"; + recipientNameInput.parentNode.style.position = "relative"; + recipientNameInput.parentNode.appendChild(clientSuggestions); + + // Charger les clients précédents + let clientsData = JSON.parse(localStorage.getItem("clientsData") || "[]"); + + // Ajouter un bouton pour effacer le formulaire + clearFormButton.type = "button"; + clearFormButton.className = + "bg-red-500 text-white px-4 py-2 rounded-md hover:bg-red-600 ml-4"; + clearFormButton.textContent = "Effacer le formulaire"; + clearFormButton.id = "clearFormButton"; + + // Ajouter le bouton après le bouton de soumission + const submitButton = form.querySelector('button[type="submit"]'); + submitButton.parentNode.insertBefore( + clearFormButton, + submitButton.nextSibling + ); + + // Gestionnaire d'événement pour effacer le formulaire + clearFormButton.addEventListener("click", () => { + if (confirm("Êtes-vous sûr de vouloir effacer le formulaire ?")) { + localStorage.removeItem("invoiceData"); + form.reset(); + // Réinitialiser les lignes de facturation + invoiceItems.innerHTML = ""; + addInvoiceItem(); + showMessage("Formulaire effacé", "success"); + } + }); + + // Restaurer les données du formulaire depuis localStorage si elles existent + restoreFormData(); + + // Initialiser avec une ligne de facturation par défaut ou restaurer les lignes sauvegardées + if (invoiceItems.children.length === 0) { + addInvoiceItem(); + } // Ajouter une ligne de facturation lorsqu'on clique sur le bouton addItemButton.addEventListener("click", addInvoiceItem); + // Sauvegarder les données pendant la saisie (debounced) + let saveTimeout; + form.addEventListener("input", () => { + clearTimeout(saveTimeout); + saveTimeout = setTimeout(() => { + saveFormData(); + }, 500); // Attendre 500ms après la dernière saisie + }); + + // Gérer l'auto-complétion des clients + recipientNameInput.addEventListener("input", function () { + const query = this.value.toLowerCase(); + if (query.length < 2) { + clientSuggestions.classList.add("hidden"); + return; + } + + // Filtrer les clients qui correspondent à la recherche + const matches = clientsData.filter((client) => + client.name.toLowerCase().includes(query) + ); + + if (matches.length === 0) { + clientSuggestions.classList.add("hidden"); + return; + } + + // Afficher les suggestions + clientSuggestions.innerHTML = matches + .map( + (client) => ` +Aucune facture récente
`; + return; + } + + recentInvoicesList.innerHTML = recentInvoices + .map( + (invoice) => ` +