# HG changeset patch
# User Vincent Hatakeyama <vincent.hatakeyama@xcg-consulting.fr>
# Date 1642429766 -3600
#      Mon Jan 17 15:29:26 2022 +0100
# Node ID 51a9e864323fd6f4c6a4f9032ce7df8fd574fbd3
# Parent  4e0d93089bbfe1b432c3fcfa9f3289bc941088cf
✨ Odoo 15 compatibility

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -39,6 +39,8 @@
   script:
   # restart odoo with installed modules and use it in background
   - $CI_PROJECT_DIR/start --db_host=db -d test_setup --max-cron-threads=0 --data-dir /var/lib/odoo &
+  # install current version as this is the one tested, not the one in the image
+  - python3 -m pip install --disable-pip-version-check --no-cache-dir --system --upgrade $CI_PROJECT_DIR
   - sleep 3
   # then test the setup files
   - import_jsonrpc -v --host localhost --password admin -d test_setup --protocol jsonrpc -p 8069 --directory tests/import
@@ -54,6 +56,8 @@
   script:
   # restart odoo with installed modules and use it in background
   - $CI_PROJECT_DIR/start --db_host=db -d test_setup --max-cron-threads=0 --data-dir /var/lib/odoo &
+  # install current version as this is the one tested, not the one in the image
+  - python3 -m pip install --disable-pip-version-check --no-cache-dir --system --upgrade $CI_PROJECT_DIR
   - sleep 3
   # then test the setup files
   - import_base_import -v --host localhost --password admin -d test_setup --protocol jsonrpc -p 8069 --directory tests/import
@@ -79,6 +83,14 @@
     ODOO_TYPE: odoo13
   allow_failure: true
 
+import_jsonrpc_odoo15_test:
+  extends: import_jsonrpc_odoo11_test
+  image:
+    name: registry.xcg.io/odoo/odoo:15.0
+    entrypoint: [""]
+  variables:
+    ODOO_TYPE: odoo15
+
 docker_build_copy_test:
   needs:
   - job: build-docker-image
diff --git a/NEWS.rst b/NEWS.rst
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -2,6 +2,11 @@
 History
 =======
 
+14.1.0
+------
+
+Odoo 15 compatibility.
+
 14.0.0
 ------
 
diff --git a/odoo_scripts/config.py b/odoo_scripts/config.py
--- a/odoo_scripts/config.py
+++ b/odoo_scripts/config.py
@@ -200,6 +200,7 @@
             "odoo10",
             "odoo11",
             "odoo13",
+            "odoo15",
         ):
             if path:
                 _logger.info(
diff --git a/odoo_scripts/docker_build.py b/odoo_scripts/docker_build.py
--- a/odoo_scripts/docker_build.py
+++ b/odoo_scripts/docker_build.py
@@ -152,7 +152,7 @@
             myfile.write(
                 "RUN DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "
             )
-            if odoo_type in ("odoo11", "odoo13"):
+            if odoo_type in ("odoo11", "odoo13", "odoo15"):
                 myfile.write(
                     "python3-watchdog python3-ipdb python3-pyinotify "
                     "python3-wheel\n"
diff --git a/odoo_scripts/docker_dev_start.py b/odoo_scripts/docker_dev_start.py
--- a/odoo_scripts/docker_dev_start.py
+++ b/odoo_scripts/docker_dev_start.py
@@ -28,9 +28,9 @@
 
 _logger = logging.getLogger(__name__)
 
-__version__ = "3.0.0"
+__version__ = "3.2.0"
 __date__ = "2017-08-11"
-__updated__ = "2020-11-10"
+__updated__ = "2022-01-17"
 
 
 def main(argv=None):  # IGNORE:C0111
@@ -364,6 +364,7 @@
     coverage = (nmspc.test or nmspc.test_default) and odoo_type in (
         "odoo11",
         "odoo13",
+        "odoo15",
     )
     arg = ["coverage-start" if coverage else "start"]
     if db_password:
@@ -461,7 +462,7 @@
         _logger.info("Local configuration file found: %s", local_conf_path)
         odoo_conf_file = (
             "/etc/odoo/odoo.conf"
-            if odoo_type in ("odoo11", "odoo12", "odoo13")
+            if odoo_type in ("odoo11", "odoo12", "odoo13", "odoo15")
             else "/opt/odoo/etc/odoo.conf"
         )
         mounts.append(
@@ -796,7 +797,7 @@
 
     # developer mode options
     if dev:
-        if odoo_type in ("odoo10", "odoo11", "odoo13"):
+        if odoo_type in ("odoo10", "odoo11", "odoo13", "odoo15"):
             # automatically add reload if not already asks for
             if not any(opt in dev_opts for opt in ("all", "reload")):
                 dev_opts.append("reload")
@@ -819,7 +820,7 @@
     # bind Odoo sources
     if odoo_sources:
         _logger.info("Binding Odoo sources at %s", odoo_sources)
-        if odoo_type in ("odoo10", "odoo11", "odoo13"):
+        if odoo_type in ("odoo10", "odoo11", "odoo13", "odoo15"):
             # Image 11 uses python 3.5 if based on xenial, or 3.6 for bionic
             # 13 uses bionic
             if odoo_type == "odoo10":
@@ -828,6 +829,8 @@
                 pythons = ("python3.5", "python3.6")
             elif odoo_type == "odoo13":
                 pythons = ("python3.8",)
+            elif odoo_type == "odoo15":
+                pythons = ("python3.9",)
             else:
                 pythons = ("python3.6",)
             for python in pythons:
diff --git a/odoo_scripts/import_jsonrpc.py b/odoo_scripts/import_jsonrpc.py
--- a/odoo_scripts/import_jsonrpc.py
+++ b/odoo_scripts/import_jsonrpc.py
@@ -20,9 +20,9 @@
 
 _logger = logging.getLogger(__name__)
 
-__version__ = "1.1.0"
+__version__ = "1.2.0"
 __date__ = "2020-02-17"
-__updated__ = "2021-12-29"
+__updated__ = "2022-01-17"
 
 
 def import_with_jsonrpc(
@@ -55,9 +55,29 @@
 
     def _ref(xmlid: str, raise_if_not_found: bool = True) -> Union[bool, int]:
         if xmlid not in xmlid_cache:
-            full_xmlid = xmlid if "." in xmlid else "." + xmlid
             try:
-                record_id = imd.xmlid_to_res_id(full_xmlid, raise_if_not_found)
+                # o.env.ref also exists but also fails in odoo 15.
+                # xmlid_to_res_id is now _xmlid_to_res_id
+                if o.version == "15.0":
+                    if "." in xmlid:
+                        module, xml_id = xmlid.split(".", maxsplit=1)
+                    else:
+                        module, xml_id = "", xmlid
+                    # issue with check_object_reference: inactive record are
+                    # not found, so use a good old search
+                    imd_id = imd.search(
+                        [("name", "=", xml_id), ("module", "=", module)]
+                    )
+                    record_id = (
+                        imd.read(imd_id, ["res_id"])[0]["res_id"]
+                        if imd_id
+                        else None
+                    )
+                else:
+                    full_xmlid = xmlid if "." in xmlid else "." + xmlid
+                    record_id = imd.xmlid_to_res_id(
+                        full_xmlid, raise_if_not_found
+                    )
             except RPCError as error:
                 if allow_missing_refs:
                     _logger.warning(str(error))
diff --git a/odoo_scripts/import_sql.py b/odoo_scripts/import_sql.py
--- a/odoo_scripts/import_sql.py
+++ b/odoo_scripts/import_sql.py
@@ -14,9 +14,9 @@
 
 _logger = logging.getLogger(__name__)
 
-__version__ = "1.0.0"
+__version__ = "1.1.0"
 __date__ = "2020-06-08"
-__updated__ = "2020-06-08"
+__updated__ = "2022-01-18"
 
 
 def _execute(cursor, query, parameters):
@@ -24,6 +24,18 @@
     cursor.execute(query, parameters)
 
 
+def _column_exists(cursor, table_name: str, column_name: str) -> bool:
+    """Return true if the column exists in the table."""
+    _execute(
+        cursor,
+        "SELECT count(*) FROM information_schema.columns WHERE table_name=%s "
+        "and column_name=%s",
+        (table_name, column_name),
+    )
+    result = cursor.fetchall()[0][0]
+    return bool(result)
+
+
 def sql_import(
     connection, table_filenames: List[Tuple[str, str]], delimiter: str
 ) -> None:
@@ -41,6 +53,10 @@
             _logger.info(
                 "Importing - %s in %s (table %s)", csv_file, model, table
             )
+            has_date_init = _column_exists(cur, "ir_model_data", "date_init")
+            has_date_update = _column_exists(
+                cur, "ir_model_data", "date_update"
+            )
             with open(csv_file, "r") as file:
                 reader = csv.DictReader(
                     file, delimiter=delimiter, quotechar='"'
@@ -99,14 +115,15 @@
                                 ),
                             )
                             _execute(cur, query, list(row.values()) + [res_id])
-                            # update date_update in ir_model_data
-                            _execute(
-                                cur,
-                                "UPDATE ir_model_data "
-                                "SET date_update=(now() at time zone 'UTC') "
-                                "WHERE id=%s",
-                                (imd_id,),
-                            )
+                            # update date_update in ir_model_data if it exists
+                            if has_date_update:
+                                _execute(
+                                    cur,
+                                    "UPDATE ir_model_data SET "
+                                    "date_update=(now() at time zone 'UTC') "
+                                    "WHERE id=%s",
+                                    (imd_id,),
+                                )
                             written += 1
                         else:
                             # otherwise do an insert and update the columns
@@ -130,11 +147,24 @@
                             db_id = cur.fetchone()[0]
                             _execute(
                                 cur,
-                                "INSERT INTO ir_model_data (module, name, "
-                                "model, res_id, date_init, date_update) "
-                                "VALUES (%s, %s, %s, %s, "
-                                "(now() at time zone 'UTC'), "
-                                "(now() at time zone 'UTC'))",
+                                "INSERT INTO ir_model_data (create_date, "
+                                "create_uid, write_date, write_uid, module, "
+                                "name, model, res_id"
+                                + (", date_init" if has_date_init else "")
+                                + (", date_update" if has_date_update else "")
+                                + ") VALUES ((now() at time zone 'UTC'), 1, "
+                                "(now() at time zone 'UTC'), 1, %s, %s, %s, %s"
+                                + (
+                                    ",(now() at time zone 'UTC')"
+                                    if has_date_init
+                                    else ""
+                                )
+                                + (
+                                    ",(now() at time zone 'UTC')"
+                                    if has_date_update
+                                    else ""
+                                )
+                                + ")",
                                 (module, name, model, db_id),
                             )
                             created += 1
@@ -183,7 +213,7 @@
     program_license = """%s
 
   Created by Vincent Hatakeyama on %s.
-  Copyright 2020 XCG Consulting. All rights reserved.
+  Copyright 2020, 2022 XCG Consulting. All rights reserved.
 
   Licensed under the MIT License
 
diff --git a/start b/start
--- a/start
+++ b/start
@@ -27,7 +27,7 @@
         odoo_command=openerp-server
         odoo_addons_path=${ODOO_ADDONS_PATH:-/opt/odoo/sources/odoo}
         ;;
-    odoo10 | odoo11 | odoo13)
+    odoo10 | odoo11 | odoo13 | odoo15)
         odoo_command=odoo
         ;;
 esac