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 :

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:

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:

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 :

Au final rien de plus que ce qu’il y avait dans notre cahier des charges sauf :

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">

Ensuite :

<field name="name">ethersync.account.tree</field>
<field name="model">ethersync.account</field>
<field name="arch" type="xml">

Pour finir :

<tree string="Account">
     <field name="name"/>
     <field name="birthday"/>
     <field name="invoicefrequence"/>
     <field name="clientid"/>
</tree>

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:

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.