From de302ad8c40ef0c58a44db5ec07ad32a4a1af19e Mon Sep 17 00:00:00 2001
From: daniquilez <dani.quilez@gmail.com>
Date: Wed, 26 Feb 2025 14:45:03 +0100
Subject: [PATCH] =?UTF-8?q?[IMP]=20=E2=9C=A8=20Batch=20create=20service=20?=
 =?UTF-8?q?invoicings?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 energy_communities/views/res_users_view.xml   |  50 +++---
 .../__manifest__.py                           |   1 +
 .../components/sale_order_utils.py            |   3 +
 .../models/__init__.py                        |   1 -
 .../models/contract.py                        |   2 +-
 .../models/res_company.py                     |   8 +
 .../models/service_invoicing_info_mixin.py    |  28 ----
 energy_communities_service_invoicing/utils.py |  26 +++-
 .../views/res_company_views.xml               |  13 ++
 .../wizards/service_invoicing_action.py       |  10 +-
 .../service_invoicing_action_create.py        | 146 ++++++++++++++----
 .../service_invoicing_action_create.xml       |  24 ++-
 12 files changed, 212 insertions(+), 100 deletions(-)
 delete mode 100644 energy_communities_service_invoicing/models/service_invoicing_info_mixin.py

diff --git a/energy_communities/views/res_users_view.xml b/energy_communities/views/res_users_view.xml
index 9eaba37a8..7cb910ae9 100644
--- a/energy_communities/views/res_users_view.xml
+++ b/energy_communities/views/res_users_view.xml
@@ -1,40 +1,28 @@
 <?xml version="1.0" encoding="utf-8" ?>
 <odoo>
     <data noupdate="1">
-        <record model="ir.actions.server" id="print_instance">
-            <field name="name">Push user to Keycloak</field>
-            <field name="model_id" ref="model_res_users" />
-            <field name="binding_model_id" ref="model_res_users" />
-            <field name="binding_type">action</field>
-            <field name="groups_id" eval="[(4,ref('group_platform_manager'))]" />
-            <field name="state">code</field>
-            <field name="code">
-                record.create_users_on_keycloak()
-            </field>
-        </record>
-
-    <record id="view_res_users_form_inherit" model="ir.ui.view">
-        <field name="name">res.users.form.inherit</field>
-        <field name="model">res.users</field>
-        <field name="inherit_id" ref="base.view_users_form" />
-        <field name="arch" type="xml">
+      <record id="view_res_users_form_inherit" model="ir.ui.view">
+          <field name="name">res.users.form.inherit</field>
+          <field name="model">res.users</field>
+          <field name="inherit_id" ref="base.view_users_form" />
+          <field name="arch" type="xml">
             <xpath
-          expr="//notebook//field[@name='role_line_ids']/tree//field[@name='role_id']"
-          position="attributes"
-        >
-                <attribute name="domain" />
+              expr="//notebook//field[@name='role_line_ids']/tree//field[@name='role_id']"
+              position="attributes"
+            >
+              <attribute name="domain" />
             </xpath>
-        </field>
-    </record>
-    <record id="view_res_users_form_inherit_oauth" model="ir.ui.view">
-        <field name="name">res.users.form.inherit.oauth</field>
-        <field name="model">res.users</field>
-        <field name="inherit_id" ref="auth_oauth.view_users_form" />
-        <field name="arch" type="xml">
+          </field>
+      </record>
+      <record id="view_res_users_form_inherit_oauth" model="ir.ui.view">
+          <field name="name">res.users.form.inherit.oauth</field>
+          <field name="model">res.users</field>
+          <field name="inherit_id" ref="auth_oauth.view_users_form" />
+          <field name="arch" type="xml">
             <field name="oauth_access_token" position="after">
-                <field name="last_user_invitation_through_kc" />
+              <field name="last_user_invitation_through_kc" />
             </field>
-        </field>
-    </record>
+          </field>
+      </record>
     </data>
 </odoo>
diff --git a/energy_communities_service_invoicing/__manifest__.py b/energy_communities_service_invoicing/__manifest__.py
index a3ec60b9e..f525cf563 100644
--- a/energy_communities_service_invoicing/__manifest__.py
+++ b/energy_communities_service_invoicing/__manifest__.py
@@ -15,6 +15,7 @@
         "base",
         "contract",
         "sale",
+        "sales_team",
         "purchase",
         "product",
         "product_contract",
diff --git a/energy_communities_service_invoicing/components/sale_order_utils.py b/energy_communities_service_invoicing/components/sale_order_utils.py
index fca1c7d84..5cfeeccaa 100644
--- a/energy_communities_service_invoicing/components/sale_order_utils.py
+++ b/energy_communities_service_invoicing/components/sale_order_utils.py
@@ -36,6 +36,9 @@ class SaleOrderUtils(Component):
                 )
             ],
         }
+        # Apply configuration sales team to service invoicing sales order
+        if company_id.service_invoicing_sale_team_id:
+            so_creation_dict["team_id"] = company_id.service_invoicing_sale_team_id.id
         sale_order = self.env["sale.order"].create(so_creation_dict)
         # Trigger name computattion in oder to include product's description_sale
         for order_line in sale_order.order_line:
diff --git a/energy_communities_service_invoicing/models/__init__.py b/energy_communities_service_invoicing/models/__init__.py
index 4c3afbbec..7ba9edcc6 100644
--- a/energy_communities_service_invoicing/models/__init__.py
+++ b/energy_communities_service_invoicing/models/__init__.py
@@ -1,4 +1,3 @@
-from . import service_invoicing_info_mixin
 from . import abstract_contract
 from . import account_move
 from . import contract
diff --git a/energy_communities_service_invoicing/models/contract.py b/energy_communities_service_invoicing/models/contract.py
index 392880e0a..14932d2aa 100644
--- a/energy_communities_service_invoicing/models/contract.py
+++ b/energy_communities_service_invoicing/models/contract.py
@@ -108,7 +108,7 @@ class ContractContract(models.Model):
             if record.community_company_id:
                 existing_contract = record._get_existing_same_open_contract()
                 if existing_contract:
-                    raise_existing_same_open_contract_error()
+                    raise_existing_same_open_contract_error(existing_contract)
 
     def _compute_received_invoices_count(self):
         for record in self:
diff --git a/energy_communities_service_invoicing/models/res_company.py b/energy_communities_service_invoicing/models/res_company.py
index 11701aaea..1c74ab3bd 100644
--- a/energy_communities_service_invoicing/models/res_company.py
+++ b/energy_communities_service_invoicing/models/res_company.py
@@ -10,3 +10,11 @@ class ResCompany(models.Model):
         comodel_name="account.journal",
         string="Service invoicing journal",
     )
+    service_invoicing_payment_mode_id = fields.Many2one(
+        comodel_name="account.payment.mode",
+        string="Service invoicing payment mode",
+    )
+    service_invoicing_sale_team_id = fields.Many2one(
+        comodel_name="crm.team",
+        string="Service invoicing sales team",
+    )
diff --git a/energy_communities_service_invoicing/models/service_invoicing_info_mixin.py b/energy_communities_service_invoicing/models/service_invoicing_info_mixin.py
deleted file mode 100644
index a1bb3b721..000000000
--- a/energy_communities_service_invoicing/models/service_invoicing_info_mixin.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from odoo import api, fields, models
-
-
-class ServiceInvoicingInfoMixin(models.AbstractModel):
-    _name = "service.invoicing.info.mixin"
-    _description = "Get info about current service invoicing configuration"
-
-    pack_product_product_ids = fields.Many2many(
-        comodel_name="product.product",
-        _compute="_compute_pack_product_product_ids",
-        store=False,
-    )
-
-    @api.depends("name")
-    def _compute_pack_product_product_ids(self):
-        for record in self:
-            pack_product_product_ids = []
-            record.pack_product_product_ids = self.env["product.product"].search(
-                [
-                    (
-                        "categ_id",
-                        "=",
-                        self.env.ref(
-                            "energy_communities_service_invoicing.product_category_pack"
-                        ).id,
-                    )
-                ]
-            )
diff --git a/energy_communities_service_invoicing/utils.py b/energy_communities_service_invoicing/utils.py
index 3da795640..e9d5e6d38 100644
--- a/energy_communities_service_invoicing/utils.py
+++ b/energy_communities_service_invoicing/utils.py
@@ -21,7 +21,25 @@ _SALE_ORDER_SERVICE_INVOICING_ACTION_VALUES = [
 ] + _SERVICE_INVOICING_EXECUTED_ACTION_VALUES[:-1]
 
 
-def service_invoicing_view(env: Environment, service_invoicing_id: ContractContract):
+def service_invoicing_tree_view(env: Environment):
+    return {
+        "type": "ir.actions.act_window",
+        "res_model": "contract.contract",
+        "views": [
+            (
+                env.ref(
+                    "energy_communities_service_invoicing.view_service_invoicing_tree"
+                ).id,
+                "form",
+            ),
+        ],
+        "target": "current",
+    }
+
+
+def service_invoicing_form_view_for_platform_admins(
+    env: Environment, service_invoicing_id: ContractContract
+):
     return {
         "type": "ir.actions.act_window",
         "res_model": "contract.contract",
@@ -39,9 +57,11 @@ def service_invoicing_view(env: Environment, service_invoicing_id: ContractContr
 
 
 # TODO: Think a bit more about more about if this 3 methods must go to contract utils component
-def raise_existing_same_open_contract_error():
+def raise_existing_same_open_contract_error(existing_contract):
     raise ValidationError(
-        _("It can only exists one service contract per Customer and related community.")
+        _(
+            "It already exists an open contract ({}) with same company and community."
+        ).format(existing_contract.name)
     )
 
 
diff --git a/energy_communities_service_invoicing/views/res_company_views.xml b/energy_communities_service_invoicing/views/res_company_views.xml
index a907bb966..3b5673954 100644
--- a/energy_communities_service_invoicing/views/res_company_views.xml
+++ b/energy_communities_service_invoicing/views/res_company_views.xml
@@ -1,4 +1,15 @@
 <odoo>
+  <record id="service_invoicing_action_create_wizard_from_company_action" model="ir.actions.server">
+    <field name="name">Assign pack to community</field>
+    <field name="binding_model_id" ref="model_res_company" />
+    <field name="model_id" ref="model_service_invoicing_action_create_wizard" />
+    <field name="binding_type">action</field>
+    <field name="state">code</field>
+    <field name="groups_id" eval="[(4,ref('energy_communities.group_platform_manager'))]" />
+    <field name="code">
+        action = model.get_service_invoicing_action_create_wizard_form_view()
+    </field>
+  </record>
   <record id="view_service_invocing_company_form" model="ir.ui.view">
     <field name="name">service.invoicing.res.company.form.inherit</field>
     <field name="model">res.company</field>
@@ -9,6 +20,8 @@
           <group>
             <field name="id" invisible="1"/>
             <field name="service_invoicing_journal_id" domain="[('company_id', '=', id)]" />
+            <field name="service_invoicing_payment_mode_id" domain="[('company_id', '=', id)]" />
+            <field name="service_invoicing_sale_team_id" domain="[('company_id', '=', id)]" />
           </group>
         </page>
       </xpath>
diff --git a/energy_communities_service_invoicing/wizards/service_invoicing_action.py b/energy_communities_service_invoicing/wizards/service_invoicing_action.py
index 49c7ed275..ec3423774 100644
--- a/energy_communities_service_invoicing/wizards/service_invoicing_action.py
+++ b/energy_communities_service_invoicing/wizards/service_invoicing_action.py
@@ -6,7 +6,7 @@ from odoo.addons.energy_communities.utils import contract_utils
 
 from ..utils import (
     _SERVICE_INVOICING_EXECUTED_ACTION_VALUES,
-    service_invoicing_view,
+    service_invoicing_form_view_for_platform_admins,
 )
 
 
@@ -46,7 +46,9 @@ class ServiceInvoicingActionWizard(models.TransientModel):
                 self.discount,
                 self.payment_mode_id,
             )
-        return service_invoicing_view(self.env, service_invoicing_id)
+        return service_invoicing_form_view_for_platform_admins(
+            self.env, service_invoicing_id
+        )
 
     def execute_reopen(self):
         with contract_utils(self.env, self.service_invoicing_id) as component:
@@ -57,7 +59,9 @@ class ServiceInvoicingActionWizard(models.TransientModel):
                 self.discount,
                 self.payment_mode_id,
             )
-        return service_invoicing_view(self.env, service_invoicing_id)
+        return service_invoicing_form_view_for_platform_admins(
+            self.env, service_invoicing_id
+        )
 
     def _validate_execute_modify(self):
         if (
diff --git a/energy_communities_service_invoicing/wizards/service_invoicing_action_create.py b/energy_communities_service_invoicing/wizards/service_invoicing_action_create.py
index a68cfef60..33a67f2a1 100644
--- a/energy_communities_service_invoicing/wizards/service_invoicing_action_create.py
+++ b/energy_communities_service_invoicing/wizards/service_invoicing_action_create.py
@@ -13,14 +13,15 @@ from ..utils import (
     get_existing_last_closed_contract,
     get_existing_open_contract,
     raise_existing_same_open_contract_error,
-    service_invoicing_view,
+    service_invoicing_form_view_for_platform_admins,
+    service_invoicing_tree_view,
 )
 
 
 class ServiceInvoicingActionCreateWizard(models.TransientModel):
     _name = "service.invoicing.action.create.wizard"
     _description = "Create service invoicing for an energy community"
-    _inherit = ["user.currentcompany.mixin", "service.invoicing.info.mixin"]
+    _inherit = ["user.currentcompany.mixin"]
 
     company_id = fields.Many2one("res.company", string="Coordinator")
     community_company_id = fields.Many2one(
@@ -28,11 +29,12 @@ class ServiceInvoicingActionCreateWizard(models.TransientModel):
         string="Community",
         domain="[('id', 'in', allowed_community_company_ids)]",
     )
+    community_company_mids = fields.Many2many(
+        comodel_name="res.company",
+    )
     service_pack_id = fields.Many2one(
         "product.product",
         string="Service pack",
-        domain="[('id', 'in', pack_product_product_ids)]",
-        precompute=True,
     )
     payment_mode_id = fields.Many2one(
         "account.payment.mode",
@@ -49,43 +51,67 @@ class ServiceInvoicingActionCreateWizard(models.TransientModel):
     )
     allowed_payment_mode_ids = fields.Many2many(
         comodel_name="account.payment.mode",
-        _compute="_compute_ allowed_payment_mode_ids",
+        _compute="_compute_allowed_payment_mode_ids",
         store=False,
     )
+    pack_product_categ_id = fields.Many2one(
+        "product.category", compute="_compute_pack_product_categ_id", store=False
+    )
 
-    @api.depends("company_id")
+    def _compute_pack_product_categ_id(self):
+        for record in self:
+            record.pack_product_categ_id = self.env.ref(
+                "energy_communities_service_invoicing.product_category_pack"
+            ).id
+
+    @api.depends("company_id", "community_company_mids")
     def _compute_allowed_community_company_ids(self):
         for record in self:
-            record.allowed_community_company_ids = self.env["res.company"].search(
-                [
-                    ("hierarchy_level", "=", "community"),
-                    ("parent_id", "=", record.company_id.id),
-                ]
-            )
+            query = [("hierarchy_level", "=", "community")]
+            if record.community_company_mids:
+                query.append(("parent_id", "=", self.user_current_company.id))
+            else:
+                query.append(("parent_id", "=", record.company_id.id))
+            record.allowed_community_company_ids = self.env["res.company"].search(query)
 
+    @api.depends("company_id", "community_company_mids")
     def _compute_allowed_payment_mode_ids(self):
         for record in self:
+            if record.community_company_mids:
+                query = [("company_id", "=", self.user_current_company.id)]
+            else:
+                query = [("company_id", "=", record.company_id.id)]
             record.allowed_payment_mode_ids = self.env["account.payment.mode"].search(
-                [("company_id", "=", self.user_current_company.id)]
+                query
             )
 
     @api.onchange("company_id")
-    def _on_change_company_id(self):
+    def _compute_service_invoicing_action_create_wizard_allowed_values(self):
         for record in self:
             record._compute_allowed_community_company_ids()
             record._compute_allowed_payment_mode_ids()
-            # TODO: This should be necessary if pack_product_product_ids gets properly auto computed
-            record._compute_pack_product_product_ids()
 
     def execute_create(self):
-        # Check if already open one and raise error
-        existing_contract = get_existing_open_contract(
-            self.env, self.company_id.partner_id, self.community_company_id
-        )
-        if existing_contract:
-            raise_existing_same_open_contract_error()
+        if self.community_company_mids:
+            for community in self.community_company_mids:
+                self._execute_create_one(
+                    community,
+                    community.parent_id,
+                    self.env.company.service_invoicing_payment_mode_id,
+                )
+            return service_invoicing_tree_view(self.env)
+        else:
+            service_invoicing_id = self._execute_create_one(
+                self.community_company_id, self.company_id, self.payment_mode_id
+            )
+            return service_invoicing_form_view_for_platform_admins(
+                self.env, service_invoicing_id
+            )
+
+    def execute_create_one(self, community_company_id, company_id, payment_mode_id):
+        self._validate_service_invoicing_action_create([community_company_id.id])
         existing_closed_contract = get_existing_last_closed_contract(
-            self.env, self.company_id.partner_id, self.community_company_id
+            self.env, company_id.partner_id, community_company_id
         )
         # If existing closed contract reopen it
         if existing_closed_contract:
@@ -95,20 +121,84 @@ class ServiceInvoicingActionCreateWizard(models.TransientModel):
                     self.pricelist_id,
                     self.service_pack_id,
                     self.discount,
-                    self.payment_mode_id,
+                    payment_mode_id,
                 )
         # If none of previous create a new contract
         else:
             with sale_order_utils(self.env) as component:
                 service_invoicing_id = component.create_service_invoicing_initial(
-                    self.company_id,
-                    self.community_company_id,
+                    company_id,
+                    community_company_id,
                     self.service_pack_id,
                     self.pricelist_id,
-                    self.payment_mode_id,
+                    payment_mode_id,
                     datetime.now(),
                     self.discount,
                     "activate",
                     "active_platform_service_invocing",
                 )
-        return service_invoicing_view(self.env, service_invoicing_id)
+        return service_invoicing_id
+
+    def get_service_invoicing_action_create_wizard_form_view(self):
+        if "active_ids" in self.env.context.keys():
+            self._validate_service_invoicing_action_create(
+                self.env.context["active_ids"]
+            )
+            self._validate_service_invoicing_action_create_multicommunity(
+                self.env.context["active_ids"]
+            )
+            wizard = self.env["service.invoicing.action.create.wizard"].create(
+                {
+                    "community_company_mids": self.env.context["active_ids"],
+                }
+            )
+            return {
+                "type": "ir.actions.act_window",
+                "res_model": "service.invoicing.action.create.wizard",
+                "views": [
+                    (
+                        self.env.ref(
+                            "energy_communities_service_invoicing.view_service_invoicing_action_create_wizard_form"
+                        ).id,
+                        "form",
+                    ),
+                ],
+                "target": "new",
+                "res_id": wizard.id,
+            }
+        return False
+
+    def _validate_service_invoicing_action_create(self, company_id_list):
+        impacted_records = self.env["res.company"].browse(company_id_list)
+        # Check all selected companies are communities
+        hierarchy_levels = list(set(impacted_records.mapped("hierarchy_level")))
+        if len(hierarchy_levels) > 1 or hierarchy_levels[0] != "community":
+            raise ValidationError(_("You can only assign pack to communities"))
+        # Check if already open one and raise error
+        for record in impacted_records:
+            existing_contract = get_existing_open_contract(
+                self.env, record.parent_id.partner_id, record
+            )
+            if existing_contract:
+                raise_existing_same_open_contract_error(existing_contract)
+
+    def _validate_service_invoicing_action_create_multicommunity(self, company_id_list):
+        impacted_records = self.env["res.company"].browse(company_id_list)
+        # check all communities have coordinator defined
+        for record in impacted_records:
+            if not record.parent_id:
+                raise ValidationError(
+                    _("Community {} must have a parent coordinator defined").format(
+                        record.name
+                    )
+                )
+        # Check current company has configuration payment mode for multicompany creation
+        if (
+            self.community_company_mids
+            and not self.env.company.service_invoicing_payment_mode_id
+        ):
+            raise ValidationError(
+                _(
+                    "Platform {} must have a service invoicing payment mode defined"
+                ).format(self.env.company.name)
+            )
diff --git a/energy_communities_service_invoicing/wizards/service_invoicing_action_create.xml b/energy_communities_service_invoicing/wizards/service_invoicing_action_create.xml
index 147f07487..776c34b6e 100644
--- a/energy_communities_service_invoicing/wizards/service_invoicing_action_create.xml
+++ b/energy_communities_service_invoicing/wizards/service_invoicing_action_create.xml
@@ -11,12 +11,26 @@
           <group>
             <field name="allowed_community_company_ids" invisible="1" />
             <field name="allowed_payment_mode_ids" invisible="1" />
-            <field name="pack_product_product_ids" invisible="1" />
-            <field name="company_id" required="1" domain="[('hierarchy_level','=','coordinator')]"/>
-            <field name="community_company_id" required="1" />
-            <field name="service_pack_id" required="1"/>
+            <field name="pack_product_categ_id" invisible="1" />
+            <field
+              name="company_id"
+              domain="[('hierarchy_level','=','coordinator')]"
+              attrs="{'invisible':[('community_company_mids','!=',[])],'required':[('community_company_mids','=',[])]}"
+            />
+            <field
+              name="community_company_id"
+              attrs="{'invisible':[('community_company_mids','!=',[])],'required':[('community_company_mids','=',[])]}"
+            />
+            <field name="community_company_mids" />
+            <field name="service_pack_id"
+              required="1"
+              domain="[('categ_id','=',pack_product_categ_id)]"
+            />
             <field name="pricelist_id" required="1" domain="[('company_id','=',False)]"/>
-            <field name="payment_mode_id" required="1" />
+            <field
+              name="payment_mode_id"
+              attrs="{'invisible':[('community_company_mids','!=',[])],'required':[('community_company_mids','=',[])]}"
+            />
             <field name="discount" required="1" />
           </group>
         </sheet>
-- 
GitLab