From 5b770733ae6b34f7adb79cf02418618d1d0c8ed1 Mon Sep 17 00:00:00 2001
From: benjamin <benjamin@le-filament.com>
Date: Thu, 25 Aug 2022 16:47:57 +0200
Subject: [PATCH] [add] wizard multi partner & retrieve module list from server

---
 __init__.py                             |  2 +-
 __manifest__.py                         |  4 +-
 controllers/__init__.py                 |  2 +-
 controllers/banner_release.py           | 13 ++++
 models/res_partner.py                   | 64 ++++++++++++++++-
 models/res_partner_module.py            | 22 ++++--
 security/ir.model.access.csv            |  2 +
 templates/header_template.xml           | 29 ++++++++
 views/menus.xml                         |  2 +-
 views/res_partner.xml                   | 24 +++++--
 views/res_partner_module.xml            | 13 +++-
 views/res_partner_release.xml           |  6 +-
 wizard/__init__.py                      |  4 ++
 wizard/multi_partner_release_wizard.py  | 93 +++++++++++++++++++++++++
 wizard/multi_partner_release_wizard.xml | 66 ++++++++++++++++++
 15 files changed, 325 insertions(+), 21 deletions(-)
 create mode 100644 controllers/banner_release.py
 create mode 100644 templates/header_template.xml
 create mode 100644 wizard/__init__.py
 create mode 100644 wizard/multi_partner_release_wizard.py
 create mode 100644 wizard/multi_partner_release_wizard.xml

diff --git a/__init__.py b/__init__.py
index 48a63e5..0ae31d9 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,4 +1,4 @@
 # Copyright 2021 Le Filament (https://le-filament.com)
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
 
-from . import controllers, models
+from . import controllers, models, wizard
diff --git a/__manifest__.py b/__manifest__.py
index 29acf73..a3c08a2 100644
--- a/__manifest__.py
+++ b/__manifest__.py
@@ -15,8 +15,11 @@
     "data": [
         # security
         "security/ir.model.access.csv",
+        # wizard
+        "wizard/multi_partner_release_wizard.xml",
         # templates
         "templates/assets.xml",
+        "templates/header_template.xml",
         "templates/partner_release_detail.xml",
         "templates/partner_release_list.xml",
         # views
@@ -26,7 +29,6 @@
         "views/res_partner_release_module.xml",
         # views menu
         "views/menus.xml",
-        # wizard
     ],
     "qweb": [
         "static/src/xml/*.xml",
diff --git a/controllers/__init__.py b/controllers/__init__.py
index bf688a2..75f6a06 100644
--- a/controllers/__init__.py
+++ b/controllers/__init__.py
@@ -1,4 +1,4 @@
 # Copyright 2022 Le Filament (https://le-filament.com)
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
 
-from . import portal
+from . import banner_release, portal
diff --git a/controllers/banner_release.py b/controllers/banner_release.py
new file mode 100644
index 0000000..948ff54
--- /dev/null
+++ b/controllers/banner_release.py
@@ -0,0 +1,13 @@
+# © 2022 Le Filament (<http://www.le-filament.com>)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from odoo import http
+from odoo.http import request
+
+
+class BannerController(http.Controller):
+    @http.route(["/lefilament_release/header"], type="json", auth="user")
+    def release_header(self):
+        return {
+            "html": request.env.ref("lefilament_release.header_template")._render({})
+        }
diff --git a/models/res_partner.py b/models/res_partner.py
index 44b61a4..c94fd7d 100644
--- a/models/res_partner.py
+++ b/models/res_partner.py
@@ -1,7 +1,9 @@
 # © 2022 Le Filament (<http://www.le-filament.com>)
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
 
-from odoo import fields, models
+import requests
+
+from odoo import _, exceptions, fields, models
 
 
 class ResPartner(models.Model):
@@ -14,13 +16,18 @@ class ResPartner(models.Model):
     release_ids = fields.One2many(
         comodel_name="res.partner.release", inverse_name="partner_id", string="Releases"
     )
-    module_ids = fields.One2many(
-        comodel_name="res.partner.module", inverse_name="partner_id", string="Modules"
+    module_ids = fields.Many2many(
+        comodel_name="res.partner.module",
+        column1="partner_id",
+        column2="module_id",
+        string="Modules",
     )
     release_share_link = fields.Char(
         string="Lien de partage client",
         compute="_compute_release_share_link",
     )
+    server_url = fields.Char("URL du serveur")
+    database_name = fields.Char("Nom de la base de données")
 
     # ------------------------------------------------------
     # Computed fields / Search Fields
@@ -46,6 +53,57 @@ class ResPartner(models.Model):
             if partner.is_company:
                 partner._portal_ensure_token()
 
+    def retrieve_modules(self):
+        existing_modules = self.env["res.partner.module"].search([])
+        for partner in self:
+            if not partner.server_url or not partner.database_name:
+                raise exceptions.UserError(
+                    _("L'url et la base de données doivent être renseignés.")
+                )
+
+            try:
+                response = requests.get(
+                    partner.server_url + "/get-modules",
+                    headers={"DbName": partner.database_name},
+                ).json()
+            except Exception as e:
+                raise exceptions.UserError(e.__str__())
+
+            for module in response:
+                if module.get("author_type") != "odoo":
+                    # Mise à jour du module
+                    if module.get("name") in existing_modules.mapped("name"):
+                        existing_modules.filtered(
+                            lambda m: m.name == module.get("name")
+                        ).update(
+                            {
+                                "author": module.get("author"),
+                                "author_type": module.get("author_type"),
+                                "partner_ids": [(4, partner.id, 0)],
+                            }
+                        )
+                    else:
+                        # Création du module
+                        partner.update({"module_ids": [(0, 0, module)]})
+            # Check modules désinstallés
+            module_uninstalled = list(
+                set(partner.module_ids.mapped("name"))
+                - set(
+                    map(
+                        lambda m: m.get("name")
+                        if m.get("author_type") != "odoo"
+                        else None,
+                        response,
+                    )
+                )
+            )
+            if module_uninstalled:
+                module_ids = partner.module_ids.filtered(
+                    lambda m: m.name in module_uninstalled
+                )
+                for module in module_ids:
+                    partner.update({"module_ids": [(3, module.id, 0)]})
+
     # ------------------------------------------------------
     # Common function
     # ------------------------------------------------------
diff --git a/models/res_partner_module.py b/models/res_partner_module.py
index d9dca5b..83dacac 100644
--- a/models/res_partner_module.py
+++ b/models/res_partner_module.py
@@ -12,12 +12,22 @@ class PartnerModule(models.Model):
     # Fields declaration
     # ------------------------------------------------------
     name = fields.Char("Nom du module", required=True)
-    partner_id = fields.Many2one(
+    partner_ids = fields.Many2many(
         comodel_name="res.partner",
-        string="Client",
-        ondelete="restrict",
+        column1="module_id",
+        column2="partner_id",
+        string="Clients",
         domain=[("is_company", "=", True)],
     )
+    author = fields.Char("Auteur")
+    author_type = fields.Selection(
+        [
+            ("lefilament", "Le Filament"),
+            ("oca", "OCA"),
+            ("other", "Autre"),
+        ],
+        string="Type",
+    )
     active = fields.Boolean("Actif", default=True)
 
     # ------------------------------------------------------
@@ -25,9 +35,9 @@ class PartnerModule(models.Model):
     # ------------------------------------------------------
     _sql_constraints = [
         (
-            "uniq_partner_module_name",
-            "unique(partnaer_id, name)",
-            "Module name must be unique for a partner",
+            "uniq_module_name",
+            "unique(name)",
+            "Ce module existe déjà",
         ),
     ]
     # ------------------------------------------------------
diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv
index 38425cc..745fb47 100644
--- a/security/ir.model.access.csv
+++ b/security/ir.model.access.csv
@@ -2,3 +2,5 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
 access_res_partner_release,access_res_partner_release,model_res_partner_release,base.group_user,1,1,1,1
 access_res_partner_module,access_res_partner_module,model_res_partner_module,base.group_user,1,1,1,1
 access_res_partner_release_module,access_res_partner_release_module,model_res_partner_release_module,base.group_user,1,1,1,1
+access_multi_partner_release_wizard,access_multi_partner_release_wizard,model_multi_partner_release_wizard,base.group_user,1,1,1,1
+access_multi_partner_release_line_wizard,access_multi_partner_release_line_wizard,model_multi_partner_release_line_wizard,base.group_user,1,1,1,1
diff --git a/templates/header_template.xml b/templates/header_template.xml
new file mode 100644
index 0000000..a60722d
--- /dev/null
+++ b/templates/header_template.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<odoo>
+    <template id="header_template" name="lefilament_release.header_template">
+        <div id="release" style="background-color: #fff; padding: 16px 0">
+            <div class="container">
+                <div class="row">
+                    <div class="col-12 text-center">
+                        <div
+                            class="btn-group"
+                            role="group"
+                            aria-label="Releases"
+                            style="padding-top: 5px; padding-bottom: 5px;"
+                        >
+                            <a
+                                type="action"
+                                name="%(lefilament_release.multi_partner_release_wizard_act_window)d"
+                                class="btn btn-outline-info"
+                                style="width: 250px;"
+                                tabindex="4"
+                            >
+                                Saisir pour plusieurs clients
+                            </a>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </template>
+</odoo>
diff --git a/views/menus.xml b/views/menus.xml
index 7fd6b52..cb0bcbd 100644
--- a/views/menus.xml
+++ b/views/menus.xml
@@ -7,10 +7,10 @@
         <menuitem
             name="Gestion de releases"
             id="res_partner_release_menu"
-            action="res_partner_release_act_window"
             sequence="150"
             web_icon="lefilament_release,static/description/menu_icon.png"
         />
+
         <menuitem
             name="Gestion de releases"
             id="res_partner_release_all_menu"
diff --git a/views/res_partner.xml b/views/res_partner.xml
index ab44d28..4263284 100644
--- a/views/res_partner.xml
+++ b/views/res_partner.xml
@@ -16,12 +16,28 @@
                         attrs="{'invisible': [('is_company', '!=', True)]}"
                         string="Modules Odoo"
                     >
+                        <group>
+                            <group>
+                                <div class="text-muted" colspan="2">
+                                    Format de l'url : https://exemple.com
+                                </div>
+                                <field name="server_url" />
+                                <field name="database_name" />
+                            </group>
+                            <group>
+                                <button
+                                    name="retrieve_modules"
+                                    type="object"
+                                    string="Récupérer la liste depuis le serveur"
+                                    class="btn-info"
+                                />
+                            </group>
+                        </group>
                         <field name="module_ids">
-                            <tree
-                                editable="top"
-                                context="{'default_partner_id': active_id}"
-                            >
+                            <tree editable="top">
                                 <field name="name" />
+                                <field name="author_type" />
+                                <field name="author" />
                             </tree>
                         </field>
                     </page>
diff --git a/views/res_partner_module.xml b/views/res_partner_module.xml
index d4c139d..d8449ef 100644
--- a/views/res_partner_module.xml
+++ b/views/res_partner_module.xml
@@ -8,6 +8,9 @@
             <field name="arch" type="xml">
                 <search string="Modules">
                     <field name="name" />
+                    <field name="author_type" />
+                    <field name="author" />
+                    <field name="partner_ids" />
                 </search>
             </field>
         </record>
@@ -17,9 +20,15 @@
             <field name="name">res.partner.module.tree</field>
             <field name="model">res.partner.module</field>
             <field name="arch" type="xml">
-                <tree string="Modules" editable="top">
+                <tree string="Modules">
                     <field name="name" />
-                    <field name="partner_id" options="{'no_create': 1,}" />
+                    <field name="author_type" />
+                    <field name="author" />
+                    <field
+                        name="partner_ids"
+                        options="{'no_create': 1,}"
+                        widget="many2many_tags"
+                    />
                     <field name="active" />
                 </tree>
             </field>
diff --git a/views/res_partner_release.xml b/views/res_partner_release.xml
index 3a7f9b6..fee3b93 100644
--- a/views/res_partner_release.xml
+++ b/views/res_partner_release.xml
@@ -79,7 +79,6 @@
                                     <field name="release_id" invisible="1" />
                                     <field
                                         name="module_id"
-                                        domain="['|', ('partner_id', '=', partner_id), ('partner_id', '=', False)]"
                                         options="{'no_create': True, 'no_edit': True}"
                                     />
                                     <field name="release_type" />
@@ -102,7 +101,10 @@
             <field name="name">res.partner.release.tree</field>
             <field name="model">res.partner.release</field>
             <field name="arch" type="xml">
-                <tree string="Releases Client">
+                <tree
+                    string="Releases Client"
+                    banner_route="/lefilament_release/header"
+                >
                     <field name="release_date" />
                     <field name="user_id" />
                     <field name="partner_id" />
diff --git a/wizard/__init__.py b/wizard/__init__.py
new file mode 100644
index 0000000..1e2b773
--- /dev/null
+++ b/wizard/__init__.py
@@ -0,0 +1,4 @@
+# Copyright 2021 Le Filament (https://le-filament.com)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
+
+from . import multi_partner_release_wizard
diff --git a/wizard/multi_partner_release_wizard.py b/wizard/multi_partner_release_wizard.py
new file mode 100644
index 0000000..2466cee
--- /dev/null
+++ b/wizard/multi_partner_release_wizard.py
@@ -0,0 +1,93 @@
+# Copyright 2022 Le Filament (https://le-filament.com)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
+
+from odoo import fields, models, api
+
+
+class MultiPartnerReleaseWizard(models.TransientModel):
+    _name = "multi.partner.release.wizard"
+    _description = "Wizard de création de release pour plusieurs clients"
+
+    # ------------------------------------------------------
+    # Fields declaration
+    # ------------------------------------------------------
+    user_id = fields.Many2one(
+        comodel_name="res.users",
+        string="Utilisateur",
+        required=True,
+        domain=[("share", "=", False)],
+        default=lambda self: self.env.user,
+    )
+    partner_ids = fields.Many2many(
+        comodel_name="res.partner",
+        string="Client",
+        required=True,
+        domain=[("is_company", "=", True)],
+    )
+    release_date = fields.Datetime(
+        string="Date de mise en production",
+        default=fields.Datetime.now(),
+    )
+    release_module_ids = fields.One2many(
+        comodel_name="multi.partner.release.line.wizard",
+        inverse_name="release_id",
+        string="Modules mis à jour",
+    )
+    description = fields.Text("Description")
+
+    # ------------------------------------------------------
+    # Button actions
+    # ------------------------------------------------------
+    def create_releases(self):
+        for partner in self.partner_ids:
+            detail_module = []
+            for module in self.release_module_ids:
+                detail_module.append(
+                    (0, 0, {
+                       "module_id": module.module_id.id,
+                       "release_type": module.release_type,
+                       "log": module.log,
+                    })
+                )
+            partner.write({
+                "release_ids": [(0, 0, {
+                    "user_id": self.user_id.id,
+                    "release_date": self.release_date,
+                    "description": self.description,
+                    "release_module_ids": detail_module,
+                })]
+            })
+        return {
+            'type': 'ir.actions.client',
+            'tag': 'reload',
+        }
+
+
+class MultiPartnerReleaseLineWizard(models.TransientModel):
+    _name = "multi.partner.release.line.wizard"
+    _description = "Modules de la release"
+
+    # ------------------------------------------------------
+    # Fields declaration
+    # ------------------------------------------------------
+    release_id = fields.Many2one(
+        comodel_name="multi.partner.release.wizard",
+        string="Releases",
+        ondelete="cascade",
+        copy=False,
+    )
+    module_id = fields.Many2one(
+        comodel_name="res.partner.module",
+        string="Module",
+        required=True,
+    )
+    log = fields.Text("Liste des changements")
+    release_type = fields.Selection(
+        selection=[
+            ("fix", "[fix]"),
+            ("update", "[update]"),
+            ("add", "[add]"),
+            ("clean", "[clean]"),
+        ],
+        string="Type",
+    )
diff --git a/wizard/multi_partner_release_wizard.xml b/wizard/multi_partner_release_wizard.xml
new file mode 100644
index 0000000..7683ce7
--- /dev/null
+++ b/wizard/multi_partner_release_wizard.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<odoo>
+    <data>
+        <!-- Form View -->
+        <record id="multi_partner_release_wizard_form_view" model="ir.ui.view">
+            <field name="name">multi.partner.release.wizard.form</field>
+            <field name="model">multi.partner.release.wizard</field>
+            <field name="arch" type="xml">
+                <form string="Releases Client">
+                    <sheet>
+                        <group>
+                            <group name="partner_details">
+                                <field name="release_date" />
+                                <field
+                                    name="partner_ids"
+                                    widget="many2many_tags"
+                                    options="{'always_reload': True, 'no_create': True, 'no_edit': True}"
+                                />
+                            </group>
+                            <group name="release_details">
+                                <field
+                                    name="user_id"
+                                    options="{'no_create': True, 'no_edit': True}"
+                                />
+                                <field name="create_date" string="Créé le" />
+                            </group>
+                            <hr />
+                            <label for="description" class="font-weight-bold" />
+                            <field name="description" />
+                            <hr />
+                            <label for="release_module_ids" class="font-weight-bold" />
+                            <field
+                                name="release_module_ids"
+                                context="{'default_release_id': active_id}"
+                            >
+                                <tree editable="top">
+                                    <field name="release_id" invisible="1" />
+                                    <field
+                                        name="module_id"
+                                        options="{'no_create': True, 'no_edit': True}"
+                                    />
+                                    <field name="release_type" />
+                                    <field name="log" />
+                                </tree>
+                            </field>
+                        </group>
+                        <footer>
+                            <button name="create_releases" type="object" string="Créer les releases" class="btn-primary"/>
+                            <button string="Fermer" class="oe_link" special="cancel" />
+                        </footer>
+                    </sheet>
+                </form>
+            </field>
+        </record>
+
+        <!-- Action Window -->
+        <record id="multi_partner_release_wizard_act_window" model="ir.actions.act_window">
+            <field name="name">Releases Client</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">multi.partner.release.wizard</field>
+            <field name="view_mode">form</field>
+            <field name="target">new</field>
+        </record>
+
+    </data>
+</odoo>
-- 
GitLab