from flask import Flask, request, jsonify, send_file from database import Database import os from datetime import datetime import subprocess app = Flask(__name__) db = Database() @app.route('/') def index(): return app.send_static_file('generator.html') @app.route('/generator') def generator(): return app.send_static_file('generator.html') @app.route('/dashboard') def dashboard(): return app.send_static_file('dashboard.html') @app.route('/preview') def preview(): return app.send_static_file('preview.html') @app.route('/accounting') def accounting(): return app.send_static_file('accounting.html') @app.route('/api/invoices', methods=['GET']) def get_invoices(): filters = {} if request.args.get('status'): filters['status'] = request.args.get('status') if request.args.get('date_from'): filters['date_from'] = request.args.get('date_from') if request.args.get('date_to'): filters['date_to'] = request.args.get('date_to') invoices = db.get_invoices(filters) statistics = db.get_statistics() return jsonify({ 'invoices': invoices, 'statistics': statistics }) @app.route('/api/invoices/', methods=['GET']) def get_invoice(invoice_id): invoice = db.get_invoice(invoice_id) if invoice: return jsonify(invoice) return jsonify({'error': 'Facture non trouvée'}), 404 @app.route('/api/invoices//status', methods=['PUT']) def update_invoice_status(invoice_id): data = request.get_json() if 'status' not in data: return jsonify({'error': 'Statut manquant'}), 400 db.update_invoice_status(invoice_id, data['status']) return jsonify({'message': 'Statut mis à jour avec succès'}) @app.route('/api/invoices//download', methods=['GET']) def download_invoice(invoice_id): invoice = db.get_invoice(invoice_id) if not invoice: return jsonify({'error': 'Facture non trouvée'}), 404 # Créer le dossier pour les fichiers générés s'il n'existe pas os.makedirs('generated', exist_ok=True) # Créer un fichier temporaire pour la facture timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") temp_file = os.path.join('generated', f'invoice_{invoice_id}_{timestamp}.typ') output_pdf = temp_file.replace('.typ', '.pdf') try: # Écrire le contenu Typst with open(temp_file, 'w') as f: f.write(invoice['typst_content']) # Générer le PDF avec Typst result = subprocess.run(['typst', 'compile', temp_file, output_pdf], capture_output=True, text=True) if result.returncode != 0: return jsonify({'error': f'Erreur lors de la génération du PDF: {result.stderr}'}), 500 # Vérifier que le PDF a été créé if not os.path.exists(output_pdf): return jsonify({'error': 'Le PDF n\'a pas été généré'}), 500 # Envoyer le PDF return send_file( output_pdf, as_attachment=True, download_name=f'invoice_{invoice["invoice_number"]}.pdf' ) finally: # Nettoyer les fichiers temporaires if os.path.exists(temp_file): os.remove(temp_file) if os.path.exists(output_pdf): os.remove(output_pdf) @app.route('/api/invoices', methods=['POST']) def create_invoice(): data = request.get_json() # Générer automatiquement le numéro de facture invoice_number = db.get_next_invoice_number() # Ajouter le client client_id = db.add_client( name=data['recipient_name'], address=data['recipient_address'], postal_code=data['recipient_postal_code'], town=data['recipient_town'], country=data['recipient_country'], vat_number=data.get('recipient_vat_number') ) # Traiter les lignes de facturation invoice_items = data.get('items', []) if not invoice_items: # Utiliser une ligne par défaut si aucun élément n'est fourni invoice_items = [{"description": "Poong rental", "amount": data['amount']}] # Générer les lignes pour le tableau Typst invoice_rows = "" for item in invoice_items: invoice_rows += f" [{item['description']}], [{item['amount']}],\n" # Créer le contenu du template Typst typst_content = f'''#let language = "{data['language']}" #let invoice_number = "{invoice_number}" #let amount = "{data['amount']}" #let currency = "{data['currency']}" #let current_date = datetime.today() #let recipient_name = [{data['recipient_name']}] #let recipient_adress = [{data['recipient_address']}] #let recipient_postal_code = [{data['recipient_postal_code']}] #let recipient_town = [{data['recipient_town']}] #let recipient_country = [{data['recipient_country']}] #let recipient_vat_number = {"none" if data.get('recipient_vat_number') is None else f'"{data["recipient_vat_number"]}"'} // Dictionnaire des traductions #let translations = ( français: ( sender: "ÉMETTEUR", recipient: "DESTINATAIRE", invoice: "FACTURE", invoice_number: "Facture N° :", date: "Date :", due_date: "Échéance :", description: "Description", amount: "Montant", total: "Total :", vat_notice: "Association non soumise à la TVA selon l'Art. 10 LTVA", payment_terms: "Cette facture est payable dans les 30 jours suivant sa réception.", banking_info: "COORDONNÉES BANCAIRES", bank: "Banque :", account_holder: "Titulaire :", account_notice: "Compte au nom d'un membre de l'association", association_notice: "Association à but non lucratif non inscrite au registre du commerce", ), deutsch: ( sender: "ABSENDER", recipient: "EMPFÄNGER", invoice: "RECHNUNG", invoice_number: "Rechnungs-Nr.:", date: "Datum:", due_date: "Fälligkeitsdatum:", description: "Beschreibung", amount: "Betrag", total: "Gesamtbetrag:", vat_notice: "Verein nicht mehrwertsteuerpflichtig gemäß Art. 10 MWSTG", payment_terms: "Diese Rechnung ist innerhalb von 30 Tagen nach Erhalt zu bezahlen.", banking_info: "BANKVERBINDUNG", bank: "Bank:", account_holder: "Kontoinhaber:", account_notice: "Konto auf den Namen eines Vereinsmitglieds", association_notice: "Gemeinnütziger Verein, nicht im Handelsregister eingetragen", ), english: ( sender: "FROM", recipient: "TO", invoice: "INVOICE", invoice_number: "Invoice No.:", date: "Date:", due_date: "Due date:", description: "Description", amount: "Amount", total: "Total:", vat_notice: "Association not subject to VAT according to Art. 10 VAT Act", payment_terms: "This invoice is payable within 30 days of receipt.", banking_info: "BANKING DETAILS", bank: "Bank:", account_holder: "Account holder:", account_notice: "Account in the name of an association member", association_notice: "Non-profit association not registered in the commercial register", ), ) // Sélection du dictionnaire en fonction de la langue #let t = translations.at(language, default: translations.français) #set page( margin: 2cm, numbering: none, ) #set text(font: "Arial", size: 11pt) // En-tête de l'association #align(center)[ #text(weight: "bold", size: 14pt)[Cheap Motion Pictures ] #text(style: "italic")[#t.association_notice] ] #v(0.5cm) #grid( columns: (1fr, 1fr), row-gutter: 0.5cm, // Informations de l'émetteur align(left)[ #text(weight: "bold", size: 14pt)[#t.sender] #v(0.2cm) Cheap Motion Pictures #v(0.1cm) rue de l'ale 1 #v(0.1cm) 1003 Lausanne, Suisse #v(0.1cm) Tél: +41 77 471 11 34 #v(0.1cm) Email: contact\@cheapmo.ch ], // Informations du destinataire align(right)[ #text(weight: "bold", size: 14pt)[#t.recipient] #v(0.2cm) #recipient_name #v(0.1cm) #recipient_adress #v(0.1cm) #recipient_postal_code #recipient_town, #recipient_country #if recipient_vat_number != none [VAT: #recipient_vat_number] else [ ]; ], ) #v(0.5cm) // Titre et informations de la facture #align(center)[ #text(weight: "bold", size: 18pt)[#t.invoice] #v(0.3cm) #t.invoice_number 2025-#invoice_number - #t.date #current_date.display("[day]/[month]/[year]") #v(0.2cm) #t.due_date 30/03/2025 ] #v(0.5cm) // Tableau des prestations #table( columns: (auto, 1fr), inset: 10pt, align: (left, right), table.header( [*#t.description*], [*#t.amount (#currency)* ], ), {invoice_rows} [*#t.total*], [*#amount #currency*], ) #v(.5cm) // Informations complémentaires #align(left)[ #t.vat_notice #v(0.3cm) #t.payment_terms ] #v(0.5cm) // Coordonnées bancaires avec notification spéciale #text(weight: "bold", size: 13pt)[#t.banking_info] #v(0.2cm) // Afficher le compte bancaire en fonction de la devise #if currency == "EUR" [ // Compte pour les transactions en euros #t.bank Wise, Rue du Trône 100, 3rd floor, Brussels, 1050, Belgium #v(0.1cm) IBAN: BE22905094540247 #v(0.1cm) BIC/SWIFT: TRWIBEB1XXX #v(0.1cm) #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 #v(0.1cm) IBAN: CH56 0900 0000 1527 2120 9 #v(0.1cm) BIC/SWIFT: POFICHBEXXX #v(0.1cm) #t.account_holder Robin Szymczak ] else [ // Compte par défaut (identique à EUR pour la compatibilité) #t.bank Wise, Rue du Trône 100, 3rd floor, Brussels, 1050, Belgium #v(0.1cm) IBAN: BE22905094540247 #v(0.1cm) BIC/SWIFT: TRWIBEB1XXX #v(0.1cm) #t.account_holder Robin Szymczak ] #v(0.2cm) #t.account_notice // Pied de page avec contact #align(center)[ #v(1cm) #line(length: 100%, stroke: 0.5pt + gray) #v(0.3cm) Cheap Motion Pictures - 1 rue de l'ale - 1003 Lausanne - contact\@cheapmo.ch ] ''' # Ajouter la facture invoice_id = db.add_invoice( invoice_number=invoice_number, client_id=client_id, amount=data['amount'], currency=data['currency'], language=data['language'], typst_content=typst_content ) return jsonify({ 'message': 'Facture créée avec succès', 'invoice_id': invoice_id }) if __name__ == '__main__': app.run(debug=True, port=5001)