# Créer son module pour Odoo

## Configurer l'environnement de déploiement

Il nous faut ajouter certaines librairies python au projet.

Pour cela nous allons:
- ajouter un fichier `requirements.txt` au dossier `etc`
- ajouter un script `entrypoint.sh` qui sera lancé par le compose

### Fichier `etc/requirements.txt`

??? note "requirements.txt"
    ```
    astor
    python-stdnum>=1.18
    requests_oauthlib
    factur-x
    unicodecsv
    ```

### Fichier `entrypoint.sh`

??? note "entrypoint.sh"
    ```
    #!/bin/bash

    set -e

    # set the postgres database host, port, user and password according to the environment
    # and pass them as arguments to the odoo process if not present in the config file
    : ${HOST:=${DB_PORT_5432_TCP_ADDR:='db'}}
    : ${PORT:=${DB_PORT_5432_TCP_PORT:=5432}}
    : ${USER:=${DB_ENV_POSTGRES_USER:=${POSTGRES_USER:='odoo'}}}
    : ${PASSWORD:=${DB_ENV_POSTGRES_PASSWORD:=${POSTGRES_PASSWORD:='odoo'}}}

    # install python packages
    pip3 install pip --upgrade
    pip3 install -r /etc/odoo/requirements.txt

    # sed -i 's|raise werkzeug.exceptions.BadRequest(msg)|self.jsonrequest = {}|g' /usr/lib/python3/dist-packages/odoo/http.py

    DB_ARGS=()
    function check_config() {
        param="$1"
        value="$2"
        if grep -q -E "^\s*\b${param}\b\s*=" "$ODOO_RC" ; then       
            value=$(grep -E "^\s*\b${param}\b\s*=" "$ODOO_RC" |cut -d " " -f3|sed 's/["\n\r]//g')
        fi;
        DB_ARGS+=("--${param}")
        DB_ARGS+=("${value}")
    }
    check_config "db_host" "$HOST"
    check_config "db_port" "$PORT"
    check_config "db_user" "$USER"
    check_config "db_password" "$PASSWORD"

    case "$1" in
        -- | odoo)
            shift
            if [[ "$1" == "scaffold" ]] ; then
                exec odoo "$@"
            else
                wait-for-psql.py ${DB_ARGS[@]} --timeout=30
                exec odoo "$@" "${DB_ARGS[@]}"
            fi
            ;;
        -*)
            wait-for-psql.py ${DB_ARGS[@]} --timeout=30
            exec odoo "$@" "${DB_ARGS[@]}"
            ;;
        *)
            exec "$@"
    esac

    exit 1
    ```



## Créer un script d'installation automatisée

Ce script python va permettre d'automatiser l'installation des modules de base

??? note "init.py"
    ```
    import odoorpc
    import subprocess
    import os
    import shutil
    import time

    # Configuration
    ODOO_URL = 'http://localhost:10014'
    ODOO_DB = 'odoo'
    ODOO_USERNAME = 'garagenum@gmail.com'
    ODOO_PASSWORD = 'bellinux'

    # Applications to install
    APPS_TO_INSTALL = [
        'account',
        'sale_management',
        'purchase',
        'hr',
        'contacts',
        'hr_expense',
        'project',
        'stock',
        'membership',
        'website',
        'mass_mailing',
        'partner_autocomplete',
        'hr_recruitment',
        'survey',
        'board',
        'mass_mailing_sms',
        'note',
        'website_forum',
        'hr_skills',
        'hr_holidays',
        'website_hr_recruitment',
        'hr_contract',
        'website_slides',
        'board',
        'base_automation',
        'delivery',
        'l10n_fr'
    ]

    MODULES_TO_INSTALL = [
        {'url': 'https://github.com/odoomates/odooapps', 
        'apps': [
            {'name': 'accounting_pdf_reports'},
            {'name': 'om_account_asset'},
            {'name': 'om_account_bank_statement_import'},
            {'name': 'om_account_budget'},
            {'name': 'om_fiscal_year'},
            {'name': 'om_recurring_payments'},
            {'name': 'om_account_daily_reports'},
            {'name': 'om_account_followup'},
            {'name': 'om_account_accountant', 'dir': 'om_account_accountant'},
            {'name': 'om_hr_payroll'},
            {'name': 'om_hr_payroll_account'}
        ]},
        #{'url': 'https://github.com/CybroOdoo/CybroAddons',
        #'apps': [
        #    {'name': 'activity_reminder'},
        #]},
        {'url': 'https://github.com/OCA/server-tools',
        'apps': [  
            {'name': 'base_view_inheritance_extension'}    
        ]},  
        {'url': 'https://github.com/OCA/server-env',
        'apps': [
            {'name': 'server_environment'}
        ]},
        {'url': 'https://github.com/OCA/edi',
        'apps': [ 
            {'name': 'account_einvoice_generate'},
            {'name': 'account_invoice_facturx'},
            {'name': 'base_facturx'}
        ]},   
        {'url': 'https://github.com/OCA/account-invoicing',
        'apps': [  
            {'name': 'account_invoice_transmit_method'}    
        ]},  
        {'url': 'https://github.com/OCA/account-reconcile',
        'apps': [  
            {'name': 'account_statement_base'}    
        ]},  
        {'url': 'https://github.com/OCA/reporting-engine',
        'apps': [  
            {'name': 'report_xlsx'}    
        ]}, 
        {'url': 'https://github.com/OCA/mis-builder',
        'apps': [  
            {'name': 'mis_builder'}    
        ]},  
        {'url': 'https://github.com/OCA/bank-payment',
        'apps': [
            {'name': 'account_payment_mode'},
            {'name': 'account_payment_partner'}
        ]},
        {'url': 'https://github.com/OCA/bank-statement-import',
        'apps': [  
            {'name': 'account_statement_import_base'},
            {'name': 'account_statement_import_file'},
            {'name': 'account_statement_import_sheet_file'}
        ]}, 
        {'url': 'https://github.com/OCA/community-data-files',
        'apps': [
            {'name': 'base_unece'},
            {'name': 'uom_unece'},
            {'name': 'account_tax_unece'},
            {'name': 'account_payment_unece'}
        ]},
        {'url': 'https://github.com/OCA/server-ux',
            'apps': [
                {'name': 'date_range'}
            ]},     
        {'url': 'https://github.com/OCA/l10n-france',
        'apps': [
            {'name': 'l10n_fr_account_tax_unece'},
            {'name': 'l10n_fr_oca'},
            {'name': 'account_statement_impon Systems dashboards. Such style of reports presents KPI in rows and time periods in columns. Reports mainly fetch data from account moves, but can also combine data coming from arbitrary Odoo models. Reports can be exported to PDF, Excel and they can be added to Odoo drt_fr_cfonb'},
            {'name': 'l10n_fr_chorus_account'},
            {'name': 'l10n_fr_account_invoice_facturx'},
            {'name': 'l10n_fr_chorus_factur-x'},
            {'name': 'l10n_fr_fec_oca'},
            {'name': 'l10n_fr_mis_reports'},
            {'name': 'l10n_fr_siret'},
            {'name': 'l10n_fr_siret_lookup'}
        ]},
        {'url': 'https://github.com/OCA/web',
        'apps': [
            {'name': 'web_responsive'}
        ]},
        {'url': 'https://git.legaragenumerique.fr/odoo/gn_odoo',
        'apps': [
            {'name': 'gn_account_pcg_asso'}
        ]}
    ]

    def configure_folders():
        #os.makedirs('addons', exist_ok=True)
        subprocess.run(["setfacl", "-m", "u:101:rwx", './addons'], check=True)
        subprocess.run(["setfacl", "-d", "-m", "u:101:rwx", './addons'], check=True)
        subprocess.run(["setfacl", "-m", "u:101:rwx", './etc'], check=True)
        subprocess.run(["setfacl", "-d", "-m", "u:101:rwx", './etc'], check=True)

    def merge_directories(src, dst):
        for item in os.listdir(src):
            if item != '.git':
                s = os.path.join(src, item)
                d = os.path.join(dst, item)
                if os.path.isdir(s):
                    if not os.path.exists(d):
                        shutil.copytree(s, d)
                    else:
                        merge_directories(s, d)
                else:
                    shutil.copy2(s, d)

    def connect_odoo():
        # Connect to the Odoo server
        odoo = odoorpc.ODOO('localhost', port=10014)
        odoo.login(ODOO_DB, ODOO_USERNAME, ODOO_PASSWORD)
        return odoo


    def install_app(odoo, odoo_app):
        # Check if each module is installed or not
        print(f"Checking {odoo_app} installation...")
        module_id = odoo.env['ir.module.module'].search([('name', '=', odoo_app)])
        if module_id:
            module = odoo.env['ir.module.module'].browse(module_id)[0]
            if module.state not in ['installed', 'to upgrade']:
                time.sleep(0.3)
                module.button_immediate_install()
                print(f"Module {odoo_app} has been installed.")
            else:
                print(f"Module {odoo_app} is already installed.")
        else:
            print(f"Module {odoo_app} not found.")

    def install_apps(odoo):
        for odoo_app in APPS_TO_INSTALL:
            install_app(odoo, odoo_app)
            time.sleep(1)

    def dl_modules(group):
        repo_url = group['url'].rstrip('/')
        addons_path = "addons"

        sparse_checkout_lines = []
        for app in group['apps']:
            if app['name'] != None:
                app_dir = app['dir'] if 'dir' in app else app['name']
                sparse_checkout_lines.append(f"{app_dir}\n")

        clone_path = 'tmp' if group['apps'][0]['name'] != None else os.path.join('tmp', group['url'].split('/')[-1])
        clone_full_path = os.path.join(addons_path, clone_path)
        git_dir_path = os.path.join(clone_full_path, '.git')
        sparse_checkout_file_path = os.path.join(git_dir_path, 'info', 'sparse-checkout')
        branch_name = "16.0"

        print("starting git clone")
        subprocess.run(["git", "clone", "--filter=blob:none", "--sparse", "--branch", branch_name, "--depth", "1", repo_url, clone_path], cwd=addons_path, check=True)

        if not os.path.isdir(sparse_checkout_file_path):
            subprocess.run(["git", "config", "core.sparseCheckout", "true"], cwd=clone_full_path, check=True)

        with open(sparse_checkout_file_path, "w") as sparse_checkout_file:
            sparse_checkout_file.writelines(sparse_checkout_lines)

        print("starting git checkout")
        subprocess.run(["git", "checkout", branch_name], cwd=clone_full_path, check=True)

        src_path = os.path.join(addons_path, 'tmp')
        merge_directories(src_path, addons_path)
        shutil.rmtree(src_path)

        print(f"Modules {', '.join(app['name']  if app['name'] else group['url'] for app in group['apps'])} has been downloaded")

    def update_module_list(odoo):
        ModuleUpdate = odoo.env['base.module.update']
        module_update_id = ModuleUpdate.create({})
        module_update = ModuleUpdate.browse(module_update_id)
        module_update.update_module()
        updated_modules = module_update.updated
        added_modules = module_update.added
        print(f"Updated modules: {updated_modules}, Added modules: {added_modules}")

    def install_modules(odoo):
        for group in MODULES_TO_INSTALL:
            dl_modules(group)
        time.sleep(1)
        update_module_list(odoo)
        time.sleep(1)
        for group in MODULES_TO_INSTALL:
            for app in group['apps']:
                app_name = app['name'] if app['name'] != None else group['url'].split('/')[-1]
                install_app(odoo, app_name)

    def main():
        configure_folders()
        odoo = connect_odoo()
        install_apps(odoo)
        time.sleep(3)
        install_modules(odoo)
        print("Script execution finished!")

    main()

    ```