From 50421af796e9354cbfc6691cba0bf0dc5c989540 Mon Sep 17 00:00:00 2001
From: jpp-odoo <jpp@odoo.com>
Date: Mon, 19 Jun 2023 11:46:57 +0000
Subject: [PATCH] [FIX] web: Settings headers should not be dirty

When a user changes a header setting, the record should not be marked as
dirty. If the user clicks on a button, the save dialog should not be
shown.

Next commit will bring a functional test and "steps to reproduce" of
this issue in website.

task-3265100

Part-of: odoo/odoo#125550
Co-authored-by: "Guillaume (gdi)" <gdi@odoo.com>
---
 .../settings_form_arch_parser.js              | 16 ++++
 .../settings_form_view/settings_form_view.js  | 15 ++++
 .../settings_form_view_tests.js               | 81 +++++++++++++++++++
 3 files changed, 112 insertions(+)
 create mode 100644 addons/web/static/src/webclient/settings_form_view/settings_form_arch_parser.js

diff --git a/addons/web/static/src/webclient/settings_form_view/settings_form_arch_parser.js b/addons/web/static/src/webclient/settings_form_view/settings_form_arch_parser.js
new file mode 100644
index 000000000000..f5e8457908de
--- /dev/null
+++ b/addons/web/static/src/webclient/settings_form_view/settings_form_arch_parser.js
@@ -0,0 +1,16 @@
+/** @odoo-module */
+
+import { evaluateExpr } from "@web/core/py_js/py";
+import { formView } from "@web/views/form/form_view";
+
+export class SettingsArchParser extends formView.ArchParser {
+    parseXML() {
+        const result = super.parseXML(...arguments);
+        Array.from(result.querySelectorAll(".app_settings_header field")).forEach((el) => {
+            const options = evaluateExpr(el.getAttribute("options") || "{}");
+            options.isHeaderField = true;
+            el.setAttribute("options", JSON.stringify(options));
+        });
+        return result;
+    }
+}
diff --git a/addons/web/static/src/webclient/settings_form_view/settings_form_view.js b/addons/web/static/src/webclient/settings_form_view/settings_form_view.js
index 41924b4c206e..0ed51eae429a 100644
--- a/addons/web/static/src/webclient/settings_form_view/settings_form_view.js
+++ b/addons/web/static/src/webclient/settings_form_view/settings_form_view.js
@@ -7,6 +7,7 @@ import { SettingsFormController } from "./settings_form_controller";
 import { SettingsFormRenderer } from "./settings_form_renderer";
 import { SettingsFormCompiler } from "./settings_form_compiler";
 import BasicModel from "web.BasicModel";
+import { SettingsArchParser } from "./settings_form_arch_parser";
 
 const BaseSettingsModel = BasicModel.extend({
     isNew(id) {
@@ -14,6 +15,19 @@ const BaseSettingsModel = BasicModel.extend({
             ? true
             : this._super.apply(this, arguments);
     },
+    _applyChange: function (recordID, changes, options) {
+        // Check if the changes isHeaderField.
+        const record = this.localData[recordID];
+        let isHeaderField = false;
+        for (const fieldName of Object.keys(changes)) {
+            const fieldInfo = record.fieldsInfo[options.viewType][fieldName];
+            isHeaderField = fieldInfo.options && fieldInfo.options.isHeaderField;
+        }
+        if (isHeaderField) {
+            options.doNotSetDirty = true;
+        }
+        return this._super.apply(this, arguments);
+    },
 });
 
 class SettingsRelationalModel extends formView.Model {}
@@ -23,6 +37,7 @@ export const settingsFormView = {
     ...formView,
     display: {},
     buttonTemplate: "web.SettingsFormView.Buttons",
+    ArchParser: SettingsArchParser,
     Model: SettingsRelationalModel,
     ControlPanel: ControlPanel,
     Controller: SettingsFormController,
diff --git a/addons/web/static/tests/webclient/settings_form_view/settings_form_view_tests.js b/addons/web/static/tests/webclient/settings_form_view/settings_form_view_tests.js
index 325b252bfca7..c3431b9e0427 100644
--- a/addons/web/static/tests/webclient/settings_form_view/settings_form_view_tests.js
+++ b/addons/web/static/tests/webclient/settings_form_view/settings_form_view_tests.js
@@ -1044,6 +1044,87 @@ QUnit.module("SettingsFormView", (hooks) => {
         }
     );
 
+    QUnit.test("header field don't dirty settings", async (assert) => {
+        assert.expect(6);
+
+        serverData.actions = {
+            1: {
+                id: 1,
+                name: "Settings view",
+                res_model: "res.config.settings",
+                type: "ir.actions.act_window",
+                views: [[1, "form"]],
+            },
+            4: {
+                id: 4,
+                name: "Other action",
+                res_model: "task",
+                type: "ir.actions.act_window",
+                views: [[2, "list"]],
+            },
+        };
+
+        serverData.views = {
+            "res.config.settings,1,form": `
+                <form string="Settings" js_class="base_settings">
+                    <div class="settings">
+                        <div class="app_settings_block" string="CRM" data-key="crm">
+                            <div class="app_settings_header pt-1 pb-1" style="background-color: #FEF0D0;">
+                                <div class="col-xs-12 col-md-6 ms-0 o_setting_box">
+                                    <div class="o_setting_right_pane border-start-0 ms-0 ps-0">
+                                        <div class="content-group">
+                                            <div class="row flex-row flex-nowrap mt8 align-items-center">
+                                                <label class="col text-nowrap ml8 flex-nowrap" string="Foo" for="foo_config_id"/>
+                                                <field name="foo" title="Foo?."/>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <button name="4" string="Execute action" type="action"/>
+                        </div>
+                    </div>
+                </form>`,
+            "task,2,list": '<tree><field name="display_name"/></tree>',
+            "res.config.settings,false,search": "<search></search>",
+            "task,false,search": "<search></search>",
+        };
+
+        const mockRPC = (route, args) => {
+            if (args.method === "create") {
+                assert.deepEqual(
+                    args.args[0],
+                    { foo: true },
+                    "should create a record with foo=true"
+                );
+            }
+        };
+
+        const webClient = await createWebClient({ serverData, mockRPC });
+
+        await doAction(webClient, 1);
+
+        assert.containsNone(
+            target,
+            ".o_field_boolean input:checked",
+            "checkbox should not be checked"
+        );
+
+        await click(target.querySelector(".o_field_boolean input"));
+        assert.containsOnce(target, ".o_field_boolean input:checked", "checkbox should be checked");
+
+        assert.containsNone(
+            target,
+            ".modal-title",
+            "should not say that there are unsaved changes"
+        );
+
+        await click(target.querySelector("button[name='4']"));
+        assert.containsNone(document.body, ".modal", "should not open a warning dialog");
+
+        assert.containsOnce(target, ".o_list_view", "should be open list view");
+    });
+
     QUnit.test("clicking a button with dirty settings -- save", async (assert) => {
         registry.category("services").add(
             "action",
-- 
GitLab