diff --git a/.badges/code_style-black-000000.svg b/.badges/code_style-black-000000.svg
deleted file mode 100644
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_LmJhZGdlcy9jb2RlX3N0eWxlLWJsYWNrLTAwMDAwMC5zdmc=..0000000000000000000000000000000000000000
--- a/.badges/code_style-black-000000.svg
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<svg xmlns="http://www.w3.org/2000/svg" width="114" height="20">
-  <linearGradient id="b" x2="0" y2="100%">
-    <stop offset="0" stop-color="#bbb" stop-opacity=".1" />
-    <stop offset="1" stop-opacity=".1" />
-  </linearGradient>
-  <mask id="anybadge_1">
-    <rect width="114" height="20" rx="3" fill="#fff" />
-  </mask>
-  <g mask="url(#anybadge_1)">
-    <path fill="#555" d="M0 0h72v20H0z" />
-    <path fill="#000000" d="M72 0h42v20H72z" />
-    <path fill="url(#b)" d="M0 0h114v20H0z" />
-  </g>
-  <g
-    fill="#fff"
-    text-anchor="middle"
-    font-family="DejaVu Sans,Verdana,Geneva,sans-serif"
-    font-size="11"
-  >
-    <text x="37.0" y="15" fill="#010101" fill-opacity=".3">code style</text>
-    <text x="36.0" y="14">code style</text>
-  </g>
-  <g
-    fill="#fff"
-    text-anchor="middle"
-    font-family="DejaVu Sans,Verdana,Geneva,sans-serif"
-    font-size="11"
-  >
-    <text x="94.0" y="15" fill="#010101" fill-opacity=".3">black</text>
-    <text x="93.0" y="14">black</text>
-  </g>
-</svg>
diff --git a/.badges/code_style-ruff.svg b/.badges/code_style-ruff.svg
new file mode 100644
index 0000000000000000000000000000000000000000..7aec64ecb1ec0f227eaca40c4b6284889d603767_LmJhZGdlcy9jb2RlX3N0eWxlLXJ1ZmYuc3Zn
--- /dev/null
+++ b/.badges/code_style-ruff.svg
@@ -0,0 +1,49 @@
+<svg
+  xmlns="http://www.w3.org/2000/svg"
+  xmlns:xlink="http://www.w3.org/1999/xlink"
+  width="53"
+  height="20"
+  role="img"
+  aria-label="Ruff"
+>
+  <title>Ruff</title>
+  <linearGradient id="s" x2="0" y2="100%">
+    <stop offset="0" stop-color="#bbb" stop-opacity=".1" />
+    <stop offset="1" stop-opacity=".1" />
+  </linearGradient>
+  <clipPath id="r">
+    <rect width="53" height="20" rx="3" fill="#fff" />
+  </clipPath>
+  <g clip-path="url(#r)">
+    <rect width="20" height="20" fill="#555" />
+    <rect x="20" width="33" height="20" fill="#261230" />
+    <rect width="53" height="20" fill="url(#s)" />
+  </g>
+  <g
+    fill="#fff"
+    text-anchor="middle"
+    font-family="Verdana,Geneva,DejaVu Sans,sans-serif"
+    text-rendering="geometricPrecision"
+    font-size="110"
+  >
+    <image
+      x="5"
+      y="3"
+      width="10"
+      height="14"
+      xlink:href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEwIiBoZWlnaHQ9IjYyMiIgdmlld0JveD0iMCAwIDUxMCA2MjIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yMDYuNzAxIDBDMjAwLjk2NCAwIDE5Ni4zMTQgNC42NDEzMSAxOTYuMzE0IDEwLjM2NjdWNDEuNDY2N0MxOTYuMzE0IDQ3LjE5MiAxOTEuNjYzIDUxLjgzMzMgMTg1LjkyNyA1MS44MzMzSDE1Ni44NDNDMTUxLjEwNyA1MS44MzMzIDE0Ni40NTYgNTYuNDc0NiAxNDYuNDU2IDYyLjJWMTQ1LjEzM0MxNDYuNDU2IDE1MC44NTkgMTQxLjgwNiAxNTUuNSAxMzYuMDY5IDE1NS41SDEwNi45ODZDMTAxLjI0OSAxNTUuNSA5Ni41OTg4IDE2MC4xNDEgOTYuNTk4OCAxNjUuODY3VjIyMi44ODNDOTYuNTk4OCAyMjguNjA5IDkxLjk0ODQgMjMzLjI1IDg2LjIxMTggMjMzLjI1SDU3LjEyODNDNTEuMzkxNyAyMzMuMjUgNDYuNzQxMyAyMzcuODkxIDQ2Ljc0MTMgMjQzLjYxN1YzMDAuNjMzQzQ2Ljc0MTMgMzA2LjM1OSA0Mi4wOTA5IDMxMSAzNi4zNTQ0IDMxMUgxMC4zODdDNC42NTA0IDMxMSAwIDMxNS42NDEgMCAzMjEuMzY3VjM1Mi40NjdDMCAzNTguMTkyIDQuNjUwNCAzNjIuODMzIDEwLjM4NyAzNjIuODMzSDE0NS40MThDMTUxLjE1NCAzNjIuODMzIDE1NS44MDQgMzY3LjQ3NSAxNTUuODA0IDM3My4yVjQzMC4yMTdDMTU1LjgwNCA0MzUuOTQyIDE1MS4xNTQgNDQwLjU4MyAxNDUuNDE4IDQ0MC41ODNIMTE2LjMzNEMxMTAuNTk3IDQ0MC41ODMgMTA1Ljk0NyA0NDUuMjI1IDEwNS45NDcgNDUwLjk1VjUwNy45NjdDMTA1Ljk0NyA1MTMuNjkyIDEwMS4yOTcgNTE4LjMzMyA5NS41NjAxIDUxOC4zMzNINjYuNDc2NkM2MC43NCA1MTguMzMzIDU2LjA4OTYgNTIyLjk3NSA1Ni4wODk2IDUyOC43VjYxMS42MzNDNTYuMDg5NiA2MTcuMzU5IDYwLjc0IDYyMiA2Ni40NzY2IDYyMkgxNDkuNTcyQzE1NS4zMDkgNjIyIDE1OS45NTkgNjE3LjM1OSAxNTkuOTU5IDYxMS42MzNWNTcwLjE2N0gyMDEuNTA3QzIwNy4yNDQgNTcwLjE2NyAyMTEuODk0IDU2NS41MjUgMjExLjg5NCA1NTkuOFY1MjguN0MyMTEuODk0IDUyMi45NzUgMjE2LjU0NCA1MTguMzMzIDIyMi4yODEgNTE4LjMzM0gyNTEuMzY1QzI1Ny4xMDEgNTE4LjMzMyAyNjEuNzUyIDUxMy42OTIgMjYxLjc1MiA1MDcuOTY3VjQ3Ni44NjdDMjYxLjc1MiA0NzEuMTQxIDI2Ni40MDIgNDY2LjUgMjcyLjEzOCA0NjYuNUgzMDEuMjIyQzMwNi45NTkgNDY2LjUgMzExLjYwOSA0NjEuODU5IDMxMS42MDkgNDU2LjEzM1Y0MjUuMDMzQzMxMS42MDkgNDE5LjMwOCAzMTYuMjU5IDQxNC42NjcgMzIxLjk5NiA0MTQuNjY3SDM1MS4wNzlDMzU2LjgxNiA0MTQuNjY3IDM2MS40NjYgNDEwLjAyNSAzNjEuNDY2IDQwNC4zVjM3My4yQzM2MS40NjYgMzY3LjQ3NSAzNjYuMTE3IDM2Mi44MzMgMzcxLjg1MyAzNjIuODMzSDQwMC45MzdDNDA2LjY3MyAzNjIuODMzIDQxMS4zMjQgMzU4LjE5MiA0MTEuMzI0IDM1Mi40NjdWMzIxLjM2N0M0MTEuMzI0IDMxNS42NDEgNDE1Ljk3NCAzMTEgNDIxLjcxMSAzMTFINDUwLjc5NEM0NTYuNTMxIDMxMSA0NjEuMTgxIDMwNi4zNTkgNDYxLjE4MSAzMDAuNjMzVjIxNy43QzQ2MS4xODEgMjExLjk3NSA0NTYuNTMxIDIwNy4zMzMgNDUwLjc5NCAyMDcuMzMzSDQyMC42NzJDNDE0LjkzNiAyMDcuMzMzIDQxMC4yODUgMjAyLjY5MiA0MTAuMjg1IDE5Ni45NjdWMTY1Ljg2N0M0MTAuMjg1IDE2MC4xNDEgNDE0LjkzNiAxNTUuNSA0MjAuNjcyIDE1NS41SDQ0OS43NTZDNDU1LjQ5MiAxNTUuNSA0NjAuMTQzIDE1MC44NTkgNDYwLjE0MyAxNDUuMTMzVjExNC4wMzNDNDYwLjE0MyAxMDguMzA4IDQ2NC43OTMgMTAzLjY2NyA0NzAuNTMgMTAzLjY2N0g0OTkuNjEzQzUwNS4zNSAxMDMuNjY3IDUxMCA5OS4wMjUzIDUxMCA5My4zVjEwLjM2NjdDNTEwIDQuNjQxMzIgNTA1LjM1IDAgNDk5LjYxMyAwSDIwNi43MDFaTTE2OC4yNjkgNDQwLjU4M0MxNjIuNTMyIDQ0MC41ODMgMTU3Ljg4MiA0NDUuMjI1IDE1Ny44ODIgNDUwLjk1VjUwNy45NjdDMTU3Ljg4MiA1MTMuNjkyIDE1My4yMzEgNTE4LjMzMyAxNDcuNDk1IDUxOC4zMzNIMTE4LjQxMUMxMTIuNjc1IDUxOC4zMzMgMTA4LjAyNCA1MjIuOTc1IDEwOC4wMjQgNTI4LjdWNTU5LjhDMTA4LjAyNCA1NjUuNTI1IDExMi42NzUgNTcwLjE2NyAxMTguNDExIDU3MC4xNjdIMTU5Ljk1OVY1MjguN0MxNTkuOTU5IDUyMi45NzUgMTY0LjYxIDUxOC4zMzMgMTcwLjM0NiA1MTguMzMzSDE5OS40M0MyMDUuMTY2IDUxOC4zMzMgMjA5LjgxNyA1MTMuNjkyIDIwOS44MTcgNTA3Ljk2N1Y0NzYuODY3QzIwOS44MTcgNDcxLjE0MSAyMTQuNDY3IDQ2Ni41IDIyMC4yMDQgNDY2LjVIMjQ5LjI4N0MyNTUuMDI0IDQ2Ni41IDI1OS42NzQgNDYxLjg1OSAyNTkuNjc0IDQ1Ni4xMzNWNDI1LjAzM0MyNTkuNjc0IDQxOS4zMDggMjY0LjMyNSA0MTQuNjY3IDI3MC4wNjEgNDE0LjY2N0gyOTkuMTQ1QzMwNC44ODEgNDE0LjY2NyAzMDkuNTMyIDQxMC4wMjUgMzA5LjUzMiA0MDQuM1YzNzMuMkMzMDkuNTMyIDM2Ny40NzUgMzE0LjE4MiAzNjIuODMzIDMxOS45MTkgMzYyLjgzM0gzNDkuMDAyQzM1NC43MzkgMzYyLjgzMyAzNTkuMzg5IDM1OC4xOTIgMzU5LjM4OSAzNTIuNDY3VjMyMS4zNjdDMzU5LjM4OSAzMTUuNjQxIDM2NC4wMzkgMzExIDM2OS43NzYgMzExSDM5OC44NTlDNDA0LjU5NiAzMTEgNDA5LjI0NiAzMDYuMzU5IDQwOS4yNDYgMzAwLjYzM1YyNjkuNTMzQzQwOS4yNDYgMjYzLjgwOCA0MDQuNTk2IDI1OS4xNjcgMzk4Ljg1OSAyNTkuMTY3SDMxOC44OEMzMTMuMTQzIDI1OS4xNjcgMzA4LjQ5MyAyNTQuNTI1IDMwOC40OTMgMjQ4LjhWMjE3LjdDMzA4LjQ5MyAyMTEuOTc1IDMxMy4xNDMgMjA3LjMzMyAzMTguODggMjA3LjMzM0gzNDcuOTYzQzM1My43IDIwNy4zMzMgMzU4LjM1IDIwMi42OTIgMzU4LjM1IDE5Ni45NjdWMTY1Ljg2N0MzNTguMzUgMTYwLjE0MSAzNjMuMDAxIDE1NS41IDM2OC43MzcgMTU1LjVIMzk3LjgyMUM0MDMuNTU3IDE1NS41IDQwOC4yMDggMTUwLjg1OSA0MDguMjA4IDE0NS4xMzNWMTE0LjAzM0M0MDguMjA4IDEwOC4zMDggNDEyLjg1OCAxMDMuNjY3IDQxOC41OTUgMTAzLjY2N0g0NDcuNjc4QzQ1My40MTUgMTAzLjY2NyA0NTguMDY1IDk5LjAyNTMgNDU4LjA2NSA5My4zVjYyLjJDNDU4LjA2NSA1Ni40NzQ2IDQ1My40MTUgNTEuODMzMyA0NDcuNjc4IDUxLjgzMzNIMjA4Ljc3OEMyMDMuMDQxIDUxLjgzMzMgMTk4LjM5MSA1Ni40NzQ2IDE5OC4zOTEgNjIuMlYxNDUuMTMzQzE5OC4zOTEgMTUwLjg1OSAxOTMuNzQxIDE1NS41IDE4OC4wMDQgMTU1LjVIMTU4LjkyMUMxNTMuMTg0IDE1NS41IDE0OC41MzQgMTYwLjE0MSAxNDguNTM0IDE2NS44NjdWMjIyLjg4M0MxNDguNTM0IDIyOC42MDkgMTQzLjg4MyAyMzMuMjUgMTM4LjE0NyAyMzMuMjVIMTA5LjA2M0MxMDMuMzI3IDIzMy4yNSA5OC42NzYyIDIzNy44OTEgOTguNjc2MiAyNDMuNjE3VjMwMC42MzNDOTguNjc2MiAzMDYuMzU5IDEwMy4zMjcgMzExIDEwOS4wNjMgMzExSDE5Ny4zNTJDMjAzLjA4OSAzMTEgMjA3LjczOSAzMTUuNjQxIDIwNy43MzkgMzIxLjM2N1Y0MzAuMjE3QzIwNy43MzkgNDM1Ljk0MiAyMDMuMDg5IDQ0MC41ODMgMTk3LjM1MiA0NDAuNTgzSDE2OC4yNjlaIiBmaWxsPSIjRDdGRjY0Ii8+PC9zdmc+"
+    />
+    <text
+      aria-hidden="true"
+      x="355"
+      y="150"
+      fill="#010101"
+      fill-opacity=".3"
+      transform="scale(.1)"
+      textLength="230"
+    >
+      Ruff
+    </text>
+    <text x="355" y="140" transform="scale(.1)" fill="#fff" textLength="230">Ruff</text>
+  </g>
+</svg>
diff --git a/.eslintrc.yml b/.eslintrc.yml
deleted file mode 100644
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_LmVzbGludHJjLnltbA==..0000000000000000000000000000000000000000
--- a/.eslintrc.yml
+++ /dev/null
@@ -1,187 +0,0 @@
-env:
-  browser: true
-  es6: true
-
-# See https://github.com/OCA/odoo-community.org/issues/37#issuecomment-470686449
-parserOptions:
-  ecmaVersion: 2019
-
-overrides:
-  - files:
-      - "**/*.esm.js"
-    parserOptions:
-      sourceType: module
-
-# Globals available in Odoo that shouldn't produce errorings
-globals:
-  _: readonly
-  $: readonly
-  fuzzy: readonly
-  jQuery: readonly
-  moment: readonly
-  odoo: readonly
-  openerp: readonly
-  owl: readonly
-
-# Styling is handled by Prettier, so we only need to enable AST rules;
-# see https://github.com/OCA/maintainer-quality-tools/pull/618#issuecomment-558576890
-rules:
-  accessor-pairs: warn
-  array-callback-return: warn
-  callback-return: warn
-  capitalized-comments:
-    - warn
-    - always
-    - ignoreConsecutiveComments: true
-      ignoreInlineComments: true
-  complexity:
-    - warn
-    - 15
-  constructor-super: warn
-  dot-notation: warn
-  eqeqeq: warn
-  global-require: warn
-  handle-callback-err: warn
-  id-blacklist: warn
-  id-match: warn
-  init-declarations: error
-  max-depth: warn
-  max-nested-callbacks: warn
-  max-statements-per-line: warn
-  no-alert: warn
-  no-array-constructor: warn
-  no-caller: warn
-  no-case-declarations: warn
-  no-class-assign: warn
-  no-cond-assign: error
-  no-const-assign: error
-  no-constant-condition: warn
-  no-control-regex: warn
-  no-debugger: error
-  no-delete-var: warn
-  no-div-regex: warn
-  no-dupe-args: error
-  no-dupe-class-members: error
-  no-dupe-keys: error
-  no-duplicate-case: error
-  no-duplicate-imports: error
-  no-else-return: warn
-  no-empty-character-class: warn
-  no-empty-function: error
-  no-empty-pattern: error
-  no-empty: warn
-  no-eq-null: error
-  no-eval: error
-  no-ex-assign: error
-  no-extend-native: warn
-  no-extra-bind: warn
-  no-extra-boolean-cast: warn
-  no-extra-label: warn
-  no-fallthrough: warn
-  no-func-assign: error
-  no-global-assign: error
-  no-implicit-coercion:
-    - warn
-    - allow: ["~"]
-  no-implicit-globals: warn
-  no-implied-eval: warn
-  no-inline-comments: warn
-  no-inner-declarations: warn
-  no-invalid-regexp: warn
-  no-irregular-whitespace: warn
-  no-iterator: warn
-  no-label-var: warn
-  no-labels: warn
-  no-lone-blocks: warn
-  no-lonely-if: error
-  no-mixed-requires: error
-  no-multi-str: warn
-  no-native-reassign: error
-  no-negated-condition: warn
-  no-negated-in-lhs: error
-  no-new-func: warn
-  no-new-object: warn
-  no-new-require: warn
-  no-new-symbol: warn
-  no-new-wrappers: warn
-  no-new: warn
-  no-obj-calls: warn
-  no-octal-escape: warn
-  no-octal: warn
-  no-param-reassign: warn
-  no-path-concat: warn
-  no-process-env: warn
-  no-process-exit: warn
-  no-proto: warn
-  no-prototype-builtins: warn
-  no-redeclare: warn
-  no-regex-spaces: warn
-  no-restricted-globals: warn
-  no-restricted-imports: warn
-  no-restricted-modules: warn
-  no-restricted-syntax: warn
-  no-return-assign: error
-  no-script-url: warn
-  no-self-assign: warn
-  no-self-compare: warn
-  no-sequences: warn
-  no-shadow-restricted-names: warn
-  no-shadow: warn
-  no-sparse-arrays: warn
-  no-sync: warn
-  no-this-before-super: warn
-  no-throw-literal: warn
-  no-undef-init: warn
-  no-undef: error
-  no-unmodified-loop-condition: warn
-  no-unneeded-ternary: error
-  no-unreachable: error
-  no-unsafe-finally: error
-  no-unused-expressions: error
-  no-unused-labels: error
-  no-unused-vars: error
-  no-use-before-define: error
-  no-useless-call: warn
-  no-useless-computed-key: warn
-  no-useless-concat: warn
-  no-useless-constructor: warn
-  no-useless-escape: warn
-  no-useless-rename: warn
-  no-void: warn
-  no-with: warn
-  operator-assignment: [error, always]
-  prefer-const: warn
-  radix: warn
-  require-yield: warn
-  sort-imports: warn
-  spaced-comment: [error, always]
-  strict: [error, function]
-  use-isnan: error
-  valid-jsdoc:
-    - warn
-    - prefer:
-        arg: param
-        argument: param
-        augments: extends
-        constructor: class
-        exception: throws
-        func: function
-        method: function
-        prop: property
-        return: returns
-        virtual: abstract
-        yield: yields
-      preferType:
-        array: Array
-        bool: Boolean
-        boolean: Boolean
-        number: Number
-        object: Object
-        str: String
-        string: String
-      requireParamDescription: false
-      requireReturn: false
-      requireReturnDescription: false
-      requireReturnType: false
-  valid-typeof: warn
-  yoda: warn
diff --git a/.flake8 b/.flake8
deleted file mode 100644
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_LmZsYWtlOA==..0000000000000000000000000000000000000000
--- a/.flake8
+++ /dev/null
@@ -1,5 +0,0 @@
-[flake8]
-max-line-length = 88
-per-file-ignores=
-    __init__.py:F401
-    __manifest__.py:B018
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_LmdpdGxhYi1jaS55bWw=..7aec64ecb1ec0f227eaca40c4b6284889d603767_LmdpdGxhYi1jaS55bWw= 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,3 +1,3 @@
 include:
   - project: xcg/ci-templates
-    file: /odoo/16.0/gitlab-ci.yaml
+    file: /odoo/18.0/gitlab-ci.yaml
diff --git a/.prettierrc.yml b/.prettierrc.yml
deleted file mode 100644
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_LnByZXR0aWVycmMueW1s..0000000000000000000000000000000000000000
--- a/.prettierrc.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-# Defaults for all prettier-supported languages.
-# Prettier will complete this with settings from .editorconfig file.
-bracketSpacing: false
-printWidth: 88
-proseWrap: always
-semi: true
-trailingComma: "es5"
-xmlWhitespaceSensitivity: "ignore"
diff --git a/NEWS.rst b/NEWS.rst
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_TkVXUy5yc3Q=..7aec64ecb1ec0f227eaca40c4b6284889d603767_TkVXUy5yc3Q= 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -1,13 +1,6 @@
 Changelog
 =========
 
-16.0.2.0.0
-----------
-
-Heavy refactor of the module to use common message and envelope models.
-
-Added new request mode.
-
-16.0.1.0.2
+18.0.1.0.0
 ----------
 
@@ -12,13 +5,3 @@
 ----------
 
-Use unittest.mock instead of mock.
-
-16.0.1.0.1
-----------
-
-Fix pyproject.toml so that the package can be uploaded.
-
-16.0.1.0.0
-----------
-
-Migration.
+Initial version.
diff --git a/README.rst b/README.rst
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_UkVBRE1FLnJzdA==..7aec64ecb1ec0f227eaca40c4b6284889d603767_UkVBRE1FLnJzdA== 100644
--- a/README.rst
+++ b/README.rst
@@ -15,10 +15,10 @@
 .. |license| image:: .badges/licence-AGPL--3-blue.svg
     :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
     :alt: License: AGPL-3
-.. |black| image:: .badges/code_style-black-000000.svg
-    :target: https://github.com/psf/black
-    :alt: Black
+.. |ruff| image:: .badges/code_style-ruff.svg
+    :target: https://github.com/astral-sh/ruff
+    :alt: Ruff
 .. |prettier| image:: .badges/code_style-prettier-ff69b4.svg
     :target: https://github.com/prettier/prettier
     :alt: Prettier
 
@@ -21,8 +21,8 @@
 .. |prettier| image:: .badges/code_style-prettier-ff69b4.svg
     :target: https://github.com/prettier/prettier
     :alt: Prettier
 
-|coverage| |pylint| |maturity| |license| |black| |prettier|
+|coverage| |pylint| |maturity| |license| |ruff| |prettier|
 
 Emit messages to `Xbus <https://xbus.io/>`_ from Odoo.
 
diff --git a/__manifest__.py b/__manifest__.py
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_X19tYW5pZmVzdF9fLnB5..7aec64ecb1ec0f227eaca40c4b6284889d603767_X19tYW5pZmVzdF9fLnB5 100644
--- a/__manifest__.py
+++ b/__manifest__.py
@@ -1,7 +1,7 @@
 ##############################################################################
 #
 #    Xbus Emitter for Odoo
-#    Copyright © 2015, 2022, 2023 XCG Consulting <https://xcg-consulting.fr>
+#    Copyright © 2015, 2022, 2023, 2024 XCG Consulting <https://xcg-consulting.fr>
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
@@ -21,7 +21,7 @@
     "name": "Xbus Emitter",
     "license": "AGPL-3",
     "summary": "Emit messages from Odoo to Xbus",
-    "version": "16.0.2.0.0",
+    "version": "18.0.1.0.0",
     "category": "Technical",
     "author": "XCG Consulting",
     "website": "https://orbeet.io/",
diff --git a/doc/autotodo.py b/doc/autotodo.py
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_ZG9jL2F1dG90b2RvLnB5..7aec64ecb1ec0f227eaca40c4b6284889d603767_ZG9jL2F1dG90b2RvLnB5 100755
--- a/doc/autotodo.py
+++ b/doc/autotodo.py
@@ -2,7 +2,7 @@
 ##############################################################################
 #
 #    OpenERP, Open Source Management Solution
-#    Copyright © 2014, 2018, 2022 XCG Consulting
+#    Copyright © 2014, 2018, 2022, 2024 XCG Consulting
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
@@ -55,5 +55,5 @@
         path = i[0]
         line = i[1]
         lines = (line - 3, min(line + 4, path_file_length[path]))
-        class_name = ":class:`%s`" % os.path.basename(os.path.splitext(path)[0])
+        class_name = f":class:`{os.path.basename(os.path.splitext(path)[0])}`"
         f.write(
@@ -59,6 +59,6 @@
         f.write(
-            "%s\n"
-            "%s\n\n"
-            "Line %s\n"
-            "\t.. literalinclude:: %s\n"
+            "{}\n"
+            "{}\n\n"
+            "Line {}\n"
+            "\t.. literalinclude:: {}\n"
             "\t\t:language: python\n"
@@ -64,7 +64,6 @@
             "\t\t:language: python\n"
-            "\t\t:lines: %s-%s\n"
-            "\t\t:emphasize-lines: %s\n"
-            % (
+            "\t\t:lines: {}-{}\n"
+            "\t\t:emphasize-lines: {}\n".format(
                 class_name,
                 "-" * len(class_name),
                 line,
@@ -75,10 +74,10 @@
             )
         )
         if lineno_start:
-            f.write("\t\t:lineno-start: %s\n" % lines[0])
+            f.write(f"\t\t:lineno-start: {lines[0]}\n")
         f.write("\n")
 
 
 def create_autotodo(folder, todolist, path_file_length: Mapping[str, int]):
     with open("autotodo", "w+") as f:
         for tag, info in list(todolist.items()):
@@ -79,10 +78,10 @@
         f.write("\n")
 
 
 def create_autotodo(folder, todolist, path_file_length: Mapping[str, int]):
     with open("autotodo", "w+") as f:
         for tag, info in list(todolist.items()):
-            f.write("%s\n%s\n\n" % (tag, "=" * len(tag)))
+            f.write("{}\n{}\n\n".format(tag, "=" * len(tag)))
             write_info(f, info, folder, path_file_length)
 
 
@@ -102,7 +101,7 @@
 def scan_file(filename, tags) -> tuple[dict[str, tuple[str, int, str]], int]:
     res = {tag: [] for tag in tags}
     line_num: int = 0
-    with open(filename, "r") as f:
+    with open(filename) as f:
         for line_num, line in enumerate(f):
             for tag in tags:
                 if tag in line:
diff --git a/doc/conf.py b/doc/conf.py
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_ZG9jL2NvbmYucHk=..7aec64ecb1ec0f227eaca40c4b6284889d603767_ZG9jL2NvbmYucHk= 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -8,7 +8,6 @@
 # serve to show the default.
 
 import ast
-import configparser
 import os
 import sys
 
@@ -12,10 +11,6 @@
 import os
 import sys
 
-from odoo_scripts.config import Configuration
-
-import odoo
-
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
@@ -33,7 +28,6 @@
     "sphinx.ext.todo",
     "sphinx.ext.coverage",
     "sphinx.ext.graphviz",
-    "sphinxodooautodoc.ext.autodoc",
 ]
 
 # Add any paths that contain templates here, relative to this directory.
@@ -50,7 +44,7 @@
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 #
-with open(os.path.join("..", "__manifest__.py"), "r") as f:
+with open(os.path.join("..", "__manifest__.py")) as f:
     read_data = f.read()
 d = ast.literal_eval(read_data)
 # The full version, including alpha/beta/rc tags.
@@ -98,7 +92,7 @@
 html_static_path = ["_static"]
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = "%sdoc" % module_nospace
+htmlhelp_basename = f"{module_nospace}doc"
 
 # -- Options for LaTeX output ---------------------------------------------
 
@@ -110,8 +104,8 @@
 latex_documents = [
     (
         master_doc,
-        "%s.tex" % module_nospace,
-        "%s Documentation" % project,
+        f"{module_nospace}.tex",
+        f"{project} Documentation",
         author,
         "manual",
     )
@@ -121,7 +115,7 @@
 
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
-man_pages = [(master_doc, module_lowercase, "%s Documentation" % project, [author], 1)]
+man_pages = [(master_doc, module_lowercase, f"{project} Documentation", [author], 1)]
 
 # -- Options for Texinfo output -------------------------------------------
 
@@ -132,7 +126,7 @@
     (
         master_doc,
         module_nospace,
-        "%s Documentation" % project,
+        f"{project} Documentation",
         author,
         module_nospace,
         module_description,
@@ -141,56 +135,4 @@
 ]
 
 # Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {"https://docs.python.org/3/": None}
-
-
-#
-# sphinx-odoo-autodoc
-#
-
-# sphinxodooautodoc_addons : List of addons name to load (if empty, no addon will be
-# loaded)
-this_module = os.path.basename(
-    os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-)
-sphinxodooautodoc_addons = [this_module]
-# sphinxodooautodoc_root_path : Path of the Odoo root directory
-sphinxodooautodoc_root_path = os.path.dirname(
-    os.path.dirname(os.path.abspath(odoo.__file__))
-)
-# sphinxodooautodoc_addons_path : List of paths were Odoo addons to load are located
-c = None
-# find setup file of superproject, if any
-directory = os.path.dirname(os.getenv("PWD"))
-while not c and directory:
-    setup_path = os.path.join(directory, "setup.cfg")
-    if os.path.isfile(setup_path):
-        c = configparser.ConfigParser()
-        c.read(setup_path)
-        if c.has_section("odoo_scripts"):
-            # reload with odoo_scripts
-            c = Configuration(directory)
-        else:
-            c = None
-    if not c:
-        if os.path.dirname(directory) != directory:
-            directory = os.path.dirname(directory)
-        else:
-            directory = None
-
-sphinxodooautodoc_addons_path = list()
-
-if c:
-    addon_dirs = set(os.path.dirname(path) for path in c.modules)
-
-    for line in addon_dirs:
-        sphinxodooautodoc_addons_path.append(os.path.join(directory, line))
-else:
-    # add this directory top dir
-    sphinxodooautodoc_addons_path.append(
-        os.path.dirname(os.path.dirname(os.getenv("PWD")))
-    )
-    other_addons = os.getenv("ODOO_ADDONS_PATH", default=None)
-    if other_addons:
-        for addon_path in other_addons.split(","):
-            sphinxodooautodoc_addons_path.append(addon_path)
+intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
diff --git a/doc/modules.rst b/doc/modules.rst
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_ZG9jL21vZHVsZXMucnN0..7aec64ecb1ec0f227eaca40c4b6284889d603767_ZG9jL21vZHVsZXMucnN0 100644
--- a/doc/modules.rst
+++ b/doc/modules.rst
@@ -1,6 +1,6 @@
-xbus_emitter
-============
+odoo.addons.xbus_emitter
+========================
 
 .. toctree::
    :maxdepth: 4
 
@@ -3,5 +3,5 @@
 
 .. toctree::
    :maxdepth: 4
 
-   xbus_emitter
+   odoo.addons.xbus_emitter
diff --git a/doc/odoo.addons.xbus_emitter.models.rst b/doc/odoo.addons.xbus_emitter.models.rst
new file mode 100644
index 0000000000000000000000000000000000000000..7aec64ecb1ec0f227eaca40c4b6284889d603767_ZG9jL29kb28uYWRkb25zLnhidXNfZW1pdHRlci5tb2RlbHMucnN0
--- /dev/null
+++ b/doc/odoo.addons.xbus_emitter.models.rst
@@ -0,0 +1,29 @@
+odoo.addons.xbus\_emitter.models package
+========================================
+
+Submodules
+----------
+
+odoo.addons.xbus\_emitter.models.odoo.addons.xbus\_emitter module
+-----------------------------------------------------------------
+
+.. automodule:: odoo.addons.xbus_emitter.models.odoo.addons.xbus_emitter
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+odoo.addons.xbus\_emitter.models.odoo.addons.xbus\_emitter\_job module
+----------------------------------------------------------------------
+
+.. automodule:: odoo.addons.xbus_emitter.models.odoo.addons.xbus_emitter_job
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: odoo.addons.xbus_emitter.models
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/doc/odoo.addons.xbus_emitter.rst b/doc/odoo.addons.xbus_emitter.rst
new file mode 100644
index 0000000000000000000000000000000000000000..7aec64ecb1ec0f227eaca40c4b6284889d603767_ZG9jL29kb28uYWRkb25zLnhidXNfZW1pdHRlci5yc3Q=
--- /dev/null
+++ b/doc/odoo.addons.xbus_emitter.rst
@@ -0,0 +1,19 @@
+odoo.addons.xbus\_emitter package
+=================================
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   odoo.addons.xbus_emitter.models
+   odoo.addons.xbus_emitter.tests
+
+Module contents
+---------------
+
+.. automodule:: odoo.addons.xbus_emitter
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/doc/odoo.addons.xbus_emitter.tests.rst b/doc/odoo.addons.xbus_emitter.tests.rst
new file mode 100644
index 0000000000000000000000000000000000000000..7aec64ecb1ec0f227eaca40c4b6284889d603767_ZG9jL29kb28uYWRkb25zLnhidXNfZW1pdHRlci50ZXN0cy5yc3Q=
--- /dev/null
+++ b/doc/odoo.addons.xbus_emitter.tests.rst
@@ -0,0 +1,29 @@
+odoo.addons.xbus\_emitter.tests package
+=======================================
+
+Submodules
+----------
+
+odoo.addons.xbus\_emitter.tests.test\_odoo.addons.xbus\_emitter module
+----------------------------------------------------------------------
+
+.. automodule:: odoo.addons.xbus_emitter.tests.test_odoo.addons.xbus_emitter
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+odoo.addons.xbus\_emitter.tests.test\_odoo.addons.xbus\_emitter\_job module
+---------------------------------------------------------------------------
+
+.. automodule:: odoo.addons.xbus_emitter.tests.test_odoo.addons.xbus_emitter_job
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: odoo.addons.xbus_emitter.tests
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/doc/xbus_emitter.models.rst b/doc/xbus_emitter.models.rst
deleted file mode 100644
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_ZG9jL3hidXNfZW1pdHRlci5tb2RlbHMucnN0..0000000000000000000000000000000000000000
--- a/doc/xbus_emitter.models.rst
+++ /dev/null
@@ -1,29 +0,0 @@
-odoo.addons.xbus\_emitter.models package
-========================================
-
-Submodules
-----------
-
-odoo.addons.xbus\_emitter.models.xbus\_emitter module
------------------------------------------------------
-
-.. automodule:: odoo.addons.xbus_emitter.models.xbus_emitter
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-odoo.addons.xbus\_emitter.models.xbus\_emitter\_job module
-----------------------------------------------------------
-
-.. automodule:: odoo.addons.xbus_emitter.models.xbus_emitter_job
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-Module contents
----------------
-
-.. automodule:: odoo.addons.xbus_emitter.models
-   :members:
-   :undoc-members:
-   :show-inheritance:
diff --git a/doc/xbus_emitter.rst b/doc/xbus_emitter.rst
deleted file mode 100644
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_ZG9jL3hidXNfZW1pdHRlci5yc3Q=..0000000000000000000000000000000000000000
--- a/doc/xbus_emitter.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-odoo.addons.xbus\_emitter package
-=================================
-
-Subpackages
------------
-
-.. toctree::
-   :maxdepth: 4
-
-   xbus_emitter.models
-   xbus_emitter.tests
-
-Module contents
----------------
-
-.. automodule:: odoo.addons.xbus_emitter
-   :members:
-   :undoc-members:
-   :show-inheritance:
diff --git a/doc/xbus_emitter.tests.rst b/doc/xbus_emitter.tests.rst
deleted file mode 100644
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_ZG9jL3hidXNfZW1pdHRlci50ZXN0cy5yc3Q=..0000000000000000000000000000000000000000
--- a/doc/xbus_emitter.tests.rst
+++ /dev/null
@@ -1,29 +0,0 @@
-odoo.addons.xbus\_emitter.tests package
-=======================================
-
-Submodules
-----------
-
-odoo.addons.xbus\_emitter.tests.test\_xbus\_emitter module
-----------------------------------------------------------
-
-.. automodule:: odoo.addons.xbus_emitter.tests.test_xbus_emitter
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-odoo.addons.xbus\_emitter.tests.test\_xbus\_emitter\_job module
----------------------------------------------------------------
-
-.. automodule:: odoo.addons.xbus_emitter.tests.test_xbus_emitter_job
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-Module contents
----------------
-
-.. automodule:: odoo.addons.xbus_emitter.tests
-   :members:
-   :undoc-members:
-   :show-inheritance:
diff --git a/eslint.config.cjs b/eslint.config.cjs
new file mode 100644
index 0000000000000000000000000000000000000000..7aec64ecb1ec0f227eaca40c4b6284889d603767_ZXNsaW50LmNvbmZpZy5janM=
--- /dev/null
+++ b/eslint.config.cjs
@@ -0,0 +1,204 @@
+jsdoc = require("eslint-plugin-jsdoc");
+
+const config = [
+  {
+    plugins: {
+      jsdoc,
+    },
+    languageOptions: {
+      globals: {
+        _: "readonly",
+        $: "readonly",
+        fuzzy: "readonly",
+        jQuery: "readonly",
+        moment: "readonly",
+        odoo: "readonly",
+        openerp: "readonly",
+        owl: "readonly",
+        luxon: "readonly",
+      },
+      ecmaVersion: 2024,
+      sourceType: "script",
+    },
+    rules: {
+      "accessor-pairs": "warn",
+      "array-callback-return": "warn",
+      "callback-return": "warn",
+      "capitalized-comments": [
+        "warn",
+        "always",
+        {
+          ignoreConsecutiveComments: true,
+          ignoreInlineComments: true,
+        },
+      ],
+      complexity: ["warn", 15],
+      "constructor-super": "warn",
+      "dot-notation": "warn",
+      eqeqeq: "warn",
+      "global-require": "warn",
+      "handle-callback-err": "warn",
+      "id-blacklist": "warn",
+      "id-match": "warn",
+      "init-declarations": "error",
+      "max-depth": "warn",
+      "max-nested-callbacks": "warn",
+      "max-statements-per-line": "warn",
+      "no-alert": "warn",
+      "no-array-constructor": "warn",
+      "no-caller": "warn",
+      "no-case-declarations": "warn",
+      "no-class-assign": "warn",
+      "no-cond-assign": "error",
+      "no-const-assign": "error",
+      "no-constant-condition": "warn",
+      "no-control-regex": "warn",
+      "no-debugger": "error",
+      "no-delete-var": "warn",
+      "no-div-regex": "warn",
+      "no-dupe-args": "error",
+      "no-dupe-class-members": "error",
+      "no-dupe-keys": "error",
+      "no-duplicate-case": "error",
+      "no-duplicate-imports": "error",
+      "no-else-return": "warn",
+      "no-empty-character-class": "warn",
+      "no-empty-function": "error",
+      "no-empty-pattern": "error",
+      "no-empty": "warn",
+      "no-eq-null": "error",
+      "no-eval": "error",
+      "no-ex-assign": "error",
+      "no-extend-native": "warn",
+      "no-extra-bind": "warn",
+      "no-extra-boolean-cast": "warn",
+      "no-extra-label": "warn",
+      "no-fallthrough": "warn",
+      "no-func-assign": "error",
+      "no-global-assign": "error",
+      "no-implicit-coercion": [
+        "warn",
+        {
+          allow: ["~"],
+        },
+      ],
+      "no-implicit-globals": "warn",
+      "no-implied-eval": "warn",
+      "no-inline-comments": "warn",
+      "no-inner-declarations": "warn",
+      "no-invalid-regexp": "warn",
+      "no-irregular-whitespace": "warn",
+      "no-iterator": "warn",
+      "no-label-var": "warn",
+      "no-labels": "warn",
+      "no-lone-blocks": "warn",
+      "no-lonely-if": "error",
+      "no-mixed-requires": "error",
+      "no-multi-str": "warn",
+      "no-native-reassign": "error",
+      "no-negated-condition": "warn",
+      "no-negated-in-lhs": "error",
+      "no-new-func": "warn",
+      "no-new-object": "warn",
+      "no-new-require": "warn",
+      "no-new-symbol": "warn",
+      "no-new-wrappers": "warn",
+      "no-new": "warn",
+      "no-obj-calls": "warn",
+      "no-octal-escape": "warn",
+      "no-octal": "warn",
+      "no-param-reassign": "warn",
+      "no-path-concat": "warn",
+      "no-process-env": "warn",
+      "no-process-exit": "warn",
+      "no-proto": "warn",
+      "no-prototype-builtins": "warn",
+      "no-redeclare": "warn",
+      "no-regex-spaces": "warn",
+      "no-restricted-globals": "warn",
+      "no-restricted-imports": "warn",
+      "no-restricted-modules": "warn",
+      "no-restricted-syntax": "warn",
+      "no-return-assign": "error",
+      "no-script-url": "warn",
+      "no-self-assign": "warn",
+      "no-self-compare": "warn",
+      "no-sequences": "warn",
+      "no-shadow-restricted-names": "warn",
+      "no-shadow": "warn",
+      "no-sparse-arrays": "warn",
+      "no-sync": "warn",
+      "no-this-before-super": "warn",
+      "no-throw-literal": "warn",
+      "no-undef-init": "warn",
+      "no-undef": "error",
+      "no-unmodified-loop-condition": "warn",
+      "no-unneeded-ternary": "error",
+      "no-unreachable": "error",
+      "no-unsafe-finally": "error",
+      "no-unused-expressions": "error",
+      "no-unused-labels": "error",
+      "no-unused-vars": "error",
+      "no-use-before-define": "error",
+      "no-useless-call": "warn",
+      "no-useless-computed-key": "warn",
+      "no-useless-concat": "warn",
+      "no-useless-constructor": "warn",
+      "no-useless-escape": "warn",
+      "no-useless-rename": "warn",
+      "no-void": "warn",
+      "no-with": "warn",
+      "operator-assignment": ["error", "always"],
+      "prefer-const": "warn",
+      radix: "warn",
+      "require-yield": "warn",
+      "sort-imports": "warn",
+      "spaced-comment": ["error", "always"],
+      strict: ["error", "function"],
+      "use-isnan": "error",
+      "jsdoc/check-tag-names": "warn",
+      "jsdoc/check-types": "warn",
+      "jsdoc/require-param-description": "off",
+      "jsdoc/require-return": "off",
+      "jsdoc/require-return-description": "off",
+      "jsdoc/require-return-type": "off",
+      "valid-typeof": "warn",
+      yoda: "warn",
+    },
+    settings: {
+      jsdoc: {
+        tagNamePreference: {
+          arg: "param",
+          argument: "param",
+          augments: "extends",
+          constructor: "class",
+          exception: "throws",
+          func: "function",
+          method: "function",
+          prop: "property",
+          return: "returns",
+          virtual: "abstract",
+          yield: "yields",
+        },
+        preferredTypes: {
+          array: "Array",
+          bool: "Boolean",
+          boolean: "Boolean",
+          number: "Number",
+          object: "Object",
+          str: "String",
+          string: "String",
+        },
+      },
+    },
+  },
+  {
+    files: ["**/*.esm.js"],
+    languageOptions: {
+      ecmaVersion: 2024,
+      sourceType: "module",
+    },
+  },
+];
+
+module.exports = config;
diff --git a/models/xbus_emitter.py b/models/xbus_emitter.py
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_bW9kZWxzL3hidXNfZW1pdHRlci5weQ==..7aec64ecb1ec0f227eaca40c4b6284889d603767_bW9kZWxzL3hidXNfZW1pdHRlci5weQ== 100644
--- a/models/xbus_emitter.py
+++ b/models/xbus_emitter.py
@@ -1,7 +1,7 @@
 ##############################################################################
 #
 #    Xbus emitter for Odoo
-#    Copyright © 2015, 2020, 2023 XCG Consulting <https://xcg-consulting.fr>
+#    Copyright © 2015, 2020, 2023, 2024 XCG Consulting <https://xcg-consulting.fr>
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
diff --git a/models/xbus_emitter_job.py b/models/xbus_emitter_job.py
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_bW9kZWxzL3hidXNfZW1pdHRlcl9qb2IucHk=..7aec64ecb1ec0f227eaca40c4b6284889d603767_bW9kZWxzL3hidXNfZW1pdHRlcl9qb2IucHk= 100644
--- a/models/xbus_emitter_job.py
+++ b/models/xbus_emitter_job.py
@@ -1,7 +1,7 @@
 ##############################################################################
 #
 #    Xbus emitter for Odoo
-#    Copyright © 2015, 2020, 2022, 2023 XCG Consulting <https://xcg-consulting.fr>
+#    Copyright © 2015, 2020, 2022, 2023, 2024 XCG Consulting <https://xcg-consulting.fr>
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
diff --git a/prettier.config.cjs b/prettier.config.cjs
new file mode 100644
index 0000000000000000000000000000000000000000..7aec64ecb1ec0f227eaca40c4b6284889d603767_cHJldHRpZXIuY29uZmlnLmNqcw==
--- /dev/null
+++ b/prettier.config.cjs
@@ -0,0 +1,12 @@
+/** @type {import('prettier').Config} */
+
+const config = {
+  bracketSpacing: false,
+  printWidth: 88,
+  proseWrap: "always",
+  semi: true,
+  trailingComma: "es5",
+  xmlWhitespaceSensitivity: "ignore",
+};
+
+module.exports = config;
diff --git a/pyproject.toml b/pyproject.toml
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_cHlwcm9qZWN0LnRvbWw=..7aec64ecb1ec0f227eaca40c4b6284889d603767_cHlwcm9qZWN0LnRvbWw= 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,10 +3,10 @@
 dynamic = ["version"]
 description = "Emit messages to Xbus from Odoo."
 readme = "README.rst"
-requires-python = "~=3.10.0"
+requires-python = "~=3.11"
 license = { file = "LICENSE", name = "GNU Affero General Public License v3" }
 keywords = ["odoo"]
 authors = [{ name = "XCG Consulting" }]
 classifiers = [
   "Programming Language :: Python",
   "Programming Language :: Python :: 3",
@@ -7,7 +7,8 @@
 license = { file = "LICENSE", name = "GNU Affero General Public License v3" }
 keywords = ["odoo"]
 authors = [{ name = "XCG Consulting" }]
 classifiers = [
   "Programming Language :: Python",
   "Programming Language :: Python :: 3",
+  "Programming Language :: Python :: 3.11",
   "Framework :: Odoo",
@@ -13,5 +14,5 @@
   "Framework :: Odoo",
-  "Framework :: Odoo :: 16.0",
+  "Framework :: Odoo :: 18.0",
   "License :: OSI Approved :: GNU Affero General Public License v3"
 ]
 dependencies = [
@@ -15,9 +16,9 @@
   "License :: OSI Approved :: GNU Affero General Public License v3"
 ]
 dependencies = [
-  "odoo==16.0.*",
-  "odoo-addon-base_context >=16.0.1.0.0,<16.0.2",
-  "odoo-addon-xbus_common >=16.0.1.0.0,<16.0.2"
+  "odoo==18.0.*",
+  "odoo-addon-base_context >=18.0.1.0.0,<18.0.2",
+  "odoo-addon-xbus_common >=18.0.1.0.0,<18.0.2"
 ]
 
 [project.optional-dependencies]
@@ -21,8 +22,8 @@
 ]
 
 [project.optional-dependencies]
-doc = ["sphinx", "sphinx-odoo-autodoc"]
-test = ["odoo-addon-xbus_common[test] >=16.0.1.0.0,<16.0.2"]
+doc = ["sphinx"]
+test = ["odoo-addon-xbus_common[test] >=18.0.1.0.0,<18.0.2"]
 
 [project.urls]
 repository = "https://orus.io/xcg/odoo-modules/xbus_emitter"
@@ -26,6 +27,6 @@
 
 [project.urls]
 repository = "https://orus.io/xcg/odoo-modules/xbus_emitter"
-changelog = "https://orus.io/xcg/odoo-modules/xbus_emitter/-/blob/branch/16.0/NEWS.rst"
+changelog = "https://orus.io/xcg/odoo-modules/xbus_emitter/-/blob/branch/18.0/NEWS.rst"
 
 [build-system]
@@ -30,5 +31,5 @@
 
 [build-system]
-requires = ["setuptools >=64.0.0", "wheel", "setuptools_scm[toml] >=6.2"]
-build-backend = "setuptools.build_meta"
+requires = ["hatchling >=1.19", "hatch-vcs"]
+build-backend = "hatchling.build"
 
@@ -34,5 +35,13 @@
 
-[tool.setuptools]
-# When using this template, update this value
-package-dir = { "odoo.addons.xbus_emitter" = "." }
+[tool.hatch.build]
+exclude = [
+  "/doc/",
+  "/.editorconfig",
+  "/eslint.config.cjs",
+  "/.gitlab-ci.yml",
+  "/.hgignore",
+  "/.hgtags",
+  "/prettier.config.cjs",
+  "/.yamllint.yaml"
+]
 
@@ -38,4 +47,13 @@
 
-[tool.setuptools.package-data]
-"*" = ["*"]
+[tool.hatch.build.targets.wheel]
+include = [
+  "*.csv",
+  "/i18n/",
+  "/static/",
+  "README.rst",
+  "*.xml",
+  "*.py",
+  "*.svg",
+  "*.png"
+]
 
@@ -41,3 +59,5 @@
 
-[tool.setuptools_scm]
+[tool.hatch.build.targets.wheel.sources]
+# TEMPLATE update this value
+"" = "odoo/addons/xbus_emitter"
 
@@ -43,5 +63,5 @@
 
-[tool.black]
-target = 3.10
+[tool.hatch.version]
+source = "vcs"
 
 [tool.isort]
@@ -46,15 +66,11 @@
 
 [tool.isort]
-py_version = 310
-profile = "black"
-known_odoo = ['odoo']
-known_odoo_addons = ['odoo.addons']
-sections = [
-  'FUTURE',
-  'STDLIB',
-  'THIRDPARTY',
-  'ODOO',
-  'ODOO_ADDONS',
-  'FIRSTPARTY',
-  'LOCALFOLDER'
+section-order = [
+  "future",
+  "standard-library",
+  "third-party",
+  "odoo",
+  "odoo-addons",
+  "first-party",
+  "local-folder"
 ]
@@ -60,1 +76,25 @@
 ]
+
+[tool.isort.sections]
+"odoo" = ["odoo"]
+"odoo-addons" = ["odoo.addons"]
+
+[tool.ruff.lint.mccabe]
+max-complexity = 16
+
+[tool.ruff]
+target-version = "py311"
+
+[tool.ruff.lint]
+extend-select = [
+  "B",
+  "C90",
+  "E501", # line too long (default 88)
+  "I", # isort
+  "UP", # pyupgrade
+
+]
+
+[tool.ruff.lint.per-file-ignores]
+"__init__.py" = ["F401", "I001"] # ignore unused and unsorted imports in __init__.py
+"__manifest__.py" = ["B018"] # useless expression
diff --git a/tests/test_xbus_emitter.py b/tests/test_xbus_emitter.py
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_dGVzdHMvdGVzdF94YnVzX2VtaXR0ZXIucHk=..7aec64ecb1ec0f227eaca40c4b6284889d603767_dGVzdHMvdGVzdF94YnVzX2VtaXR0ZXIucHk= 100644
--- a/tests/test_xbus_emitter.py
+++ b/tests/test_xbus_emitter.py
@@ -1,7 +1,7 @@
 ##############################################################################
 #
 #    Xbus emitter for Odoo
-#    Copyright © 2015, 2022, 2023 XCG Consulting <https://xcg-consulting.fr/>
+#    Copyright © 2015, 2022, 2023, 2024 XCG Consulting <https://xcg-consulting.fr/>
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
diff --git a/tests/test_xbus_emitter_job.py b/tests/test_xbus_emitter_job.py
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_dGVzdHMvdGVzdF94YnVzX2VtaXR0ZXJfam9iLnB5..7aec64ecb1ec0f227eaca40c4b6284889d603767_dGVzdHMvdGVzdF94YnVzX2VtaXR0ZXJfam9iLnB5 100644
--- a/tests/test_xbus_emitter_job.py
+++ b/tests/test_xbus_emitter_job.py
@@ -1,7 +1,7 @@
 ##############################################################################
 #
 #    Xbus Emitter for Odoo
-#    Copyright © 2015, 2023 XCG Consulting <https://xcg-consulting.fr>
+#    Copyright © 2015, 2023, 2024 XCG Consulting <https://xcg-consulting.fr>
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
@@ -23,7 +23,6 @@
 from unittest import mock
 
 from odoo import Command
-
 from odoo.addons.xbus_common.tests.modelcase import ModelCase
 
 from ..models.xbus_emitter_job import XbusEmitterJob
diff --git a/views/xbus_emitter.xml b/views/xbus_emitter.xml
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_dmlld3MveGJ1c19lbWl0dGVyLnhtbA==..7aec64ecb1ec0f227eaca40c4b6284889d603767_dmlld3MveGJ1c19lbWl0dGVyLnhtbA== 100644
--- a/views/xbus_emitter.xml
+++ b/views/xbus_emitter.xml
@@ -6,5 +6,5 @@
         <field name="name">xbus_emitter_list</field>
         <field name="model">xbus.emitter</field>
         <field name="arch" type="xml">
-            <tree>
+            <list>
                 <field name="name" />
@@ -10,5 +10,5 @@
                 <field name="name" />
-            </tree>
+            </list>
         </field>
     </record>
 
@@ -29,7 +29,7 @@
     <record id="xbus_emitter_action" model="ir.actions.act_window">
         <field name="name">Xbus emitters</field>
         <field name="res_model">xbus.emitter</field>
-        <field name="view_mode">tree,form</field>
+        <field name="view_mode">list,form</field>
     </record>
 
     <menuitem
diff --git a/views/xbus_emitter_job.xml b/views/xbus_emitter_job.xml
index 8400cf94edb87cb0a14c32070c0032fe3ad3f1fd_dmlld3MveGJ1c19lbWl0dGVyX2pvYi54bWw=..7aec64ecb1ec0f227eaca40c4b6284889d603767_dmlld3MveGJ1c19lbWl0dGVyX2pvYi54bWw= 100644
--- a/views/xbus_emitter_job.xml
+++ b/views/xbus_emitter_job.xml
@@ -74,7 +74,7 @@
         <field name="name">xbus_emitter_job_list</field>
         <field name="model">xbus.emitter.job</field>
         <field name="arch" type="xml">
-            <tree
+            <list
                 decoration-success="state in ('sent_success')"
                 decoration-danger="state in ('sent_error')"
             >
@@ -84,7 +84,7 @@
                 <field name="write_date" />
                 <field name="state" />
                 <field name="process_state" />
-            </tree>
+            </list>
         </field>
     </record>
 
@@ -134,7 +134,7 @@
     <record id="xbus_emitter_job_action" model="ir.actions.act_window">
         <field name="name">Xbus Emitter Jobs</field>
         <field name="res_model">xbus.emitter.job</field>
-        <field name="view_mode">tree,form</field>
+        <field name="view_mode">list,form</field>
     </record>
 
     <menuitem