Facturation Odoo automatique
Comment automatiser la création de factures avec Odoo
(par Mathieu le 22/04/2020)
Géneration de factures via l’api d’Odoo
Nous avons decidé d’automatiser la génération des factures d’hébergement. Nous utilisons Odoo pour faire nos factures. Il peut faire de nombreuses choses cependant nous ne l’utilisons quasiment que pour la création de nos factures. Odoo dispose d’une API qui utilise le protocole XML-RPC et permet de créer des modules, ce dont nous allons nous servir pour pouvoir automatiser la création de nos factures.
État des lieux
Pour la facturation actuellement nous utilisons :
- des fiches client dans Odoo qui contiennent le compte client (nom du client, adresse de facturation…)
- un tableur qui regroupe les comptes d’hébergement des clients, le montant des factures, le mode de paiement et d’autre informations.
La personne chargée de réaliser les factures prend donc les fiches client qu’elle relie au compte hébergement grâce à sa connaissance des clients et réalise la facture avec les deux jeux d’informations. C’est assez long et dépendant des connaissances de la personne pour éviter les erreurs donc peu efficace et chronophage.
Les informations des comptes d’hébergement sont connues grâce au hostsboard que nous avons développé et qui est généré tous les jours. Toutes les informations techniques des comptes d’hébergement sont dessus (formule, espace utilisé, nombre de sites, nombre de visites,…). Nous avons rendu ces données accessibles pour les utiliser depuis un autre programme qui voudrait réaliser la facturation conjointement avec les informations contenues dans Odoo grace à son API.
Objectif
Notre objectif est de limiter au maximum l’intervention humaine lors de la facturation et d’automatiser cette tâche redondante.
Cahier des charges
Module Odoo
Notre module aura pour but de regrouper les informations sur les comptes hébergement qui sont actuellement dans le cerveau d’un être humain et dans le tableur.
Les informations dont nous aurons besoin sont:
- Nom du compte hébergement
- date de facturation
- Id du compte client
- Fréquence de paiement
- Mode de paiement
- Fin de la facturation
Il nous faudra aussi un onglet pour pouvoir afficher les informations dans Odoo.
Script API
Le script aura pour but de faire les appels à l’API, de récupérer les informations des comptes d’hébergement du hostboard et de traiter tout ça.
Après coup je me suis rendu compte que nous aurions très certainement pu faire la totalité du programme dans le module Odoo directement, cela sera sûrement pour une v2 du coup.
Création du module Odoo
Pour la création d’un module Odoo, il faut manipuler le langage python et du XML. Le code python va nous servir à faire la déclaration d’une classe qui contiendra la déclaration de nos champs de base de données. La partie XML va servir à l’affichage dans Odoo.
La création d’un module Odoo se fait rapidement avec la commande :
odoo-bin scaffold <nom module> <chemin>
Cette commande crée un module vide qui va nous servir de base pour la suite. Pour indiquer la localisation du module à Odoo il vous faut ajouter l’option au lancement de Odoo :
--addons-path <chemin du module>
Arborescence d’un module Odoo
Un module odoo est constitué de:
- Deux fichiers à la racine :
- __init__.py sert à indiquer a Odoo où est le code python qu’il doit importer
- __manifest__.py contient la description du module, les module nécessaire à son bon fonctionnement si nécessaire et les imports des fichiers xml
- 3 dossiers :
- models contient le code python qui sert à la création de la table et les fonctions du module et à faire du traitement sur les données
- views contient des fichiers en XML qui permettent d’afficher notre module dans Odoo
- security contient un CSV qui permet d’ajouter ou de modifier les droits d’accès au module
Partie Python
Dans le dossier models nous avons 2 fichiers. Nous allons nous servir du fichier models.py pour réaliser notre script. Attention si vous utilisez un autre fichier vous devrez le declarer dans le __init__.py du dossier models :
from . import <nom du fichier>
Notre fichier models.py:
class ethersync(models.Model):
# Nom de la table
_name = 'ethersync.account'
# Description du module
_description = "Module pour la génération des factures"
# Nom du compte d'hébergement
name = fields.Char(required=True, index=True)
# Date de facturation
birthday = fields.Date(required=True)
# Fréquence de facturation
invoicefrequence = fields.Selection([("mensuel","Mensuel"),("annuel","Annuel")],required=True)
# Id du client dans la table res.partner
clientid = fields.Many2one('res.partner',required=True)
# Mode de paiement du client
paymentmode = fields.Many2one("account.payment.term",required=True)
# Fin du contrat
invoiceend = fields.Date()
# Supplement d'information si besoin
information = fields.Text()
# Contrainte de la base
_sql_constraints = [("nameUnique","UNIQUE(name)", "Cannot use same name twice")
On déclare une nouvelle colonne dans la base avec la fonction fields puis le type de champs désiré.
Les types de champs que nous utilisons sont :
- Char permet de rentrer une ligne de texte.
- Date une date.
- Selection une liste de choix. Le premier terme est celui contenu dans la base, le second est celui affiché dans Odoo.
- Many2one nous permet d’aller chercher une donnée contenue dans une autre table, ce qui va stocker dans la table l’ID de l’objet en question.
- Text permet de rentrer plusieurs lignes de texte.
Au final rien de plus que ce qu’il y avait dans notre cahier des charges sauf :
- Le champ information si nous avons des données particulières à ajouter selon le client.
- Et la contrainte de la base de données qui nous permet de nous assurer qu’un compte hébergement n’est pas déja rentré.
Partie XML
Dans le dossier views nous allons utiliser le views.xml pareil que pour la partie en python. Si vous changez le nom ou l’emplacement du fichier views.xml vous devrez répercuter les modifications dans le fichier __manifest__.py.
Notre fichier views.xml en entier :
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!--Affichage en vu "tree"-->
<record id="view_ethersync_tree" model="ir.ui.view">
<field name="name">ethersync.account.tree</field>
<field name="model">ethersync.account</field>
<field name="arch" type="xml">
<tree string="Account">
<field name="name"/>
<field name="birthday"/>
<field name="invoicefrequence"/>
<field name="clientid"/>
</tree>
</field>
</record>
<!--Affichage en vu "form" -->
<record id="view_ethersync_form" model="ir.ui.view">
<field name="name">ethersync.account.form</field>
<field name="model">ethersync.account</field>
<field name="arch" type="xml">
<form style="text-align:left;">
<group name="main">
<field name="name" />
<field name="birthday"/>
<field name="invoicefrequence"/>
<field name="clientid"/>
<field name="paymentmode"/>
<field name="invoiceend"/>
<field name="TVA"/>
<field name="information"/>
</group>
</form>
</field>
</record>
<!--Affichage d'un menu -->
<record model="ir.actions.act_window" id="action_view_ethersync">
<field name="name">Comptes Hébergement</field>
<field name="res_model">ethersync.account</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[]</field>
</record>
<!--Bouton dans le menu en haut-->
<menuitem id="menu_sync" name="EtherSync"/>
<!--Bouton dans le menu menu_sync -->
<menuitem id="ethersync_menu" name="Comptes Hébergement" parent="menu_sync" action="action_view_ethersync"/>
</data>
</odoo>
Décortiquons une partie pour mieux comprendre :
<record id="view_ethersync_tree" model="ir.ui.view">
<field name="name">ethersync.account.tree</field>
<field name="model">ethersync.account</field>
<field name="arch" type="xml">
<tree string="Account">
<field name="name"/>
<field name="birthday"/>
<field name="invoicefrequence"/>
<field name="clientid"/>
</tree>
</field>
</record>
La première ligne d’abord :
<record id="view_ethersync_tree" model="ir.ui.view">
- record signifie que nous créons un nouvel enregistrement.
- id est l’id utilisé pour l’appel dans d’autres enregistrements.
- model est la manière dont Odoo doit traiter cet enregistrement en l’occurrence, là nous lui indiquions que c’est une vue dans l’interface utilisateur.
Ensuite :
<field name="name">ethersync.account.tree</field>
<field name="model">ethersync.account</field>
<field name="arch" type="xml">
- La première ligne permet de nommer l’enregistrement.
- La seconde indique dans quelle table Odoo doit aller chercher les données à afficher en l’occurrence la table que nous avons créée précédemment.
- La dernière ligne indique à Odoo que la suite sera en xml car nous avons aussi la possibilité de l’écrire en HTML.
Pour finir :
<tree string="Account">
<field name="name"/>
<field name="birthday"/>
<field name="invoicefrequence"/>
<field name="clientid"/>
</tree>
- La premiere ligne indique le type d’affichage que l’on desire en l’occurrence le type tree qui dans Odoo permet d’afficher un tableau avec toutes les données de la table.
- Les lignes suivantes servent à définir quels champs de la table seront affichés dans le tableau.
Odoo dispose de differents modes d’affichage : le mode tree permet d’afficher la table à la manière d’un tableau, le mode form permet l’affichage d’une ligne de la table sous forme d’une fiche, en mode form nous avons la possibilité de modifier l’enregistrement dans la table plutôt utile donc.
Le mode tree est le mode d’affichage par default quand on clique sur le bouton Ethersync et ensuite quand on clique sur un enregistrement nous avons la ligne en mode form.
Script
Le script va s’occuper de réaliser les appels API et de récupérer des données du hostboard pour les agréger de manière cohérente.
Nous faisons les appels API vers Odoo avec du Python.
Les appels API sur Odoo se font de cette manière :
uid = info.authenticate(db, username, password, {})
models = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(url))
Ce sont les 2 variables dont nous aurons besoin pour réaliser les différents appels.
Exemple d’un appel pour la création d’une facture :
models.execute_kw(db, uid, password, 'account.invoice', 'create', [send])
account.invoice est la table qui est requétée
create est le type de requête
La variable send est un dictionnaire qu’il faut encapsuler dans une liste pour réaliser l’envoi. Elle contient les informations de la facture.
Les appels sont de différents types:
- create pour créer des lignes dans la base
- search et/ou read pour faire de la recherche et de la lecture dans la base
Exemple d’une requête search and read:
models.execute_kw(db, uid, password,'ethersync.account', 'search_read',[[["clientid","=", i]]])
Création d’une facture complète
Pour la création d’une facture nous allons devoir d’abord créer la facture en elle même puis les lignes de cette facture et enfin ajouter les taxes à cette facture :
Création de la facture
La table qui contient les factures est la table account.invoice les données que nous devons lui fournir sont :
send = {
# L'état de la facture a la création
"stat": "draft",
# Si elle a été envoyé
"sent":False,
# Clé de la table res.partner
"partner_id": <client de la facturation>,
# Clé de la table account.payment.term
"payment_term_id": <mode de payment>,
# Clé de la table res.currency
"currency_id":<id de la monnaie>,
# Clé de la table account.journal
"journal_id":<id de l'operation facturé>,
# Valeur fixe clé de la table res.company
"company_id":<id de la societe créant le facture>,
# Clé de la table res.partner
"commercial_partner_id":<client de la facturation>,
# Valeur fixe clé de la table res.user
"create_uid":<id créateur de la facture>,
# Valeur fixe clé de la table res.user
"write_uid":<id créateur de la facture,
# Valeur fixe clé de la table crm.team
"team_id":<id l'équipe qui crée la facture>,
"comment": <commentaire afficher sur la facture>,
}
Une fois notre dictionnaire prêt nous l’envoyons:
invoice_id = models.execute_kw(db, uid, password, 'account.invoice', 'create', [send])
L’API nous retourne si tout va bien l’ID de notre facture qu’il nous faut conserver pour pouvoir appliquer les différentes lignes et la TVA.
Création d’une ligne de la facture
Les lignes de la facture sont ce qui correspond à chaque produit qui sont facturés, une par produit. La table qui contient les lignes des factures est account.invoice.line.
Le dictionnaire:
send = {
# Je ne sais pas pourquoi mais c'est la valeur sur toutes les factures précédentes, verifier chez vous que c'est bien la même
"sequence":10,
# Id de la facture à ajouter une ligne
"invoice_id": <id de votre facture>,
# Cela sert pour faire l'arrondi
"uom_id":1,
# Clé dans la account.account
"account_id":<id du type de vente réaliser>,
# Valeur fixe clé de la table res.company
"company_id":<id de la societe créant le facture>,
# Clé de la table res.currency
"currency_id":<id de la monnaie>,
# Faire un arrondi
"is_rounding_line": False,
# Valeur fixe clé de la table res.user
"create_uid":<id créateur de la facture>,
# Valeur fixe clé de la table res.user
"write_uid":<id créateur de la facture,
# Plus de détaille juste en dessous du code
"invoice_line_tax_ids" :<tableau avec la TVA ex : [[6,1,[163]]]>,
"discount":<possible promotion en pourcentage>,
#Clé de la table product.product
"product_id":<id du produit>,
"price_unit":<prix du produit>,
"name":<nom de la ligne>,
"quantity":<quantité de produit>
}
Pour la ligne invoice_line_tax_ids il vous faut juste changer le dernier nombre par l’ID de la TVA/taxe que vous voulez ajouter. C’est une solution trouvée sur le forum officiel d’Odoo que j’ai adaptée, je ne connais pas vraiment les implications des 2 premiers nombres.
Après avoir complété notre dictionnaire, nous pouvons réaliser l’envoi :
models.execute_kw(db, uid, password, 'account.invoice.line', 'create',[send])
Comme pour la création de la facture l’API nous retourne ID la ligne si cela fonctionne.
Ajout de la TVA
Quand une facture est créée via l’API, Odoo n’ajoute pas la TVA au prix final tout seul donc nous devons le lui dire de le faire.
Le dictionnaire et la ligne précédente:
# Nous récupérons les info sur la taxe que nous avons mis sur les différentes lignes de la facture
taxe = models.execute_kw(db, uid, password, 'account.tax', 'search_read', [[["id", "=", invoice_taxe]]])
send = {
# ID de la facture
"invoice_id": invoice_id,
# Noms de la tva
"name":taxe[0]["description"],
"tax_id": <id de la taxe>,
# Clé dans la account.account
"account_id": <id de l'action réaliser pour la TVA il est different de celui de celui du produit>,
# Même principe que pour la sequence précédente
"sequence": 9,
# Valeur fixe clé de la table res.company
"company_id":<id de la societe créant le facture>,
# Clé de la table res.currency
"currency_id":<id de la monnaie>,
# Valeur fixe clé de la table res.user
"create_uid":<id créateur de la facture>,
# Valeur fixe clé de la table res.user
"write_uid":<id créateur de la facture,
"manual": False,
# Montant avec la taxe
"amount": invoice[0]["amount_untaxed"] * taxe[0]["amount"]/100,
# Montant arrondi
"amount_rounding": 0.0,
# Montant sans la taxe
"base": invoice[0]["amount_untaxed"],
}
Une fois notre dictionnaire complet :
models.execute_kw(db, uid, password, 'account.invoice.tax', 'create', [send])
Une fois ces trois étapes réalisées nous avons fait une facture complète et donc nous avons réalisé notre objectif premier qui est de n’avoir besoin d’aucun humain ou presque pour réaliser la facturation :).
Conclusion
Notre objectif est accompli : notre facturation est quasiment automatique. La validation des factures se fait encore à la main pour vérifier que nous n’envoyons pas n’importe quoi à nos clients mais pour le reste c’est automatique. Il existe de nombreuse améliorations possibles pour ce module et nous ne somme pas experts dans l’utilisation d’Odoo.
C’était après tout notre première expérience avec l’API et les modules. Dans le futur (pour une seconde version) nous pourrons très certainement supprimer la partie script et créer un module de facturation automatique mais pour le moment cela fonctionne plutôt bien comme cela.