From 3d492eb2361bf143569521e60c937cc8a1e63d58 Mon Sep 17 00:00:00 2001
From: Carolina Fernandez <carolina.fernandez@tecnativa.com>
Date: Wed, 22 May 2024 13:38:05 +0200
Subject: [PATCH] [IMP] account_financial_report: Open items groupy by salesman

TT49193
---
 account_financial_report/README.rst           |   3 +
 .../i18n/account_financial_report.pot         |  14 +
 account_financial_report/i18n/es.po           |  16 +-
 account_financial_report/report/open_items.py |  54 ++--
 .../report/open_items_xlsx.py                 | 167 ++++++++++--
 .../report/templates/open_items.xml           | 239 ++++++++++++++----
 .../static/description/index.html             |   2 +
 .../tests/test_open_items.py                  |  22 ++
 .../wizard/open_items_wizard.py               |  18 ++
 .../wizard/open_items_wizard_view.xml         |   1 +
 10 files changed, 437 insertions(+), 99 deletions(-)

diff --git a/account_financial_report/README.rst b/account_financial_report/README.rst
index fed874bd..56b9f61d 100644
--- a/account_financial_report/README.rst
+++ b/account_financial_report/README.rst
@@ -49,6 +49,9 @@ Invoicing / Settings / Invoicing / OCA Aged Report Configuration you will be abl
 dynamic intervals that will appear on the Aged Partner Balance.
 For further information, check CONFIGURE.rst
 
+Add new grouped by field to export Open items report grouped by partner salesperson.
+If grouped by is empty or selected partner option it will grouped by Open items by partner.
+
 **Table of contents**
 
 .. contents::
diff --git a/account_financial_report/i18n/account_financial_report.pot b/account_financial_report/i18n/account_financial_report.pot
index 785accf6..53df812c 100644
--- a/account_financial_report/i18n/account_financial_report.pot
+++ b/account_financial_report/i18n/account_financial_report.pot
@@ -945,6 +945,7 @@ msgstr ""
 
 #. module: account_financial_report
 #: model:ir.model.fields,field_description:account_financial_report.field_general_ledger_report_wizard__grouped_by
+#: model:ir.model.fields,field_description:account_financial_report.field_open_items_report_wizard__grouped_by
 msgid "Grouped By"
 msgstr ""
 
@@ -1187,6 +1188,12 @@ msgstr ""
 msgid "Missing Partner"
 msgstr ""
 
+#. module: account_financial_report
+#: code:addons/account_financial_report/report/open_items.py:0
+#, python-format
+msgid "Missing Salesperson"
+msgstr ""
+
 #. module: account_financial_report
 #: model:ir.model,name:account_financial_report.model_account_age_report_configuration_line
 msgid "Model to set interval lines for Age partner balance report"
@@ -1397,6 +1404,12 @@ msgstr ""
 msgid "Partner Initial balance"
 msgstr ""
 
+#. module: account_financial_report
+#. odoo-python
+#: model:ir.model.fields.selection,name:account_financial_report.selection__open_items_report_wizard__grouped_by__salesperson
+msgid "Partner Salesperson"
+msgstr ""
+
 #. module: account_financial_report
 #. odoo-python
 #: code:addons/account_financial_report/report/aged_partner_balance_xlsx.py:0
@@ -1421,6 +1434,7 @@ msgstr ""
 
 #. module: account_financial_report
 #: model:ir.model.fields.selection,name:account_financial_report.selection__general_ledger_report_wizard__grouped_by__partners
+#: model:ir.model.fields.selection,name:account_financial_report.selection__open_items_report_wizard__grouped_by__partners
 msgid "Partners"
 msgstr ""
 
diff --git a/account_financial_report/i18n/es.po b/account_financial_report/i18n/es.po
index aecf0f4e..9e3c69d9 100644
--- a/account_financial_report/i18n/es.po
+++ b/account_financial_report/i18n/es.po
@@ -960,6 +960,7 @@ msgstr "Agrupar por"
 
 #. module: account_financial_report
 #: model:ir.model.fields,field_description:account_financial_report.field_general_ledger_report_wizard__grouped_by
+#: model:ir.model.fields,field_description:account_financial_report.field_open_items_report_wizard__grouped_by
 msgid "Grouped By"
 msgstr "Agrupado por"
 
@@ -1203,7 +1204,13 @@ msgstr "Línea"
 #: code:addons/account_financial_report/report/trial_balance.py:0
 #, python-format
 msgid "Missing Partner"
-msgstr "Falta el Socio"
+msgstr "Falta la empresa"
+
+#. module: account_financial_report
+#: code:addons/account_financial_report/report/open_items.py:0
+#, python-format
+msgid "Missing Salesperson"
+msgstr "Sin comercial"
 
 #. module: account_financial_report
 #: model:ir.model,name:account_financial_report.model_account_age_report_configuration_line
@@ -1418,6 +1425,12 @@ msgstr ""
 msgid "Partner Initial balance"
 msgstr "Saldo Inicial de empresa"
 
+#. module: account_financial_report
+#. odoo-python
+#: model:ir.model.fields.selection,name:account_financial_report.selection__open_items_report_wizard__grouped_by__salesperson
+msgid "Partner Salesperson"
+msgstr "Comercial de la empresa"
+
 #. module: account_financial_report
 #. odoo-python
 #: code:addons/account_financial_report/report/aged_partner_balance_xlsx.py:0
@@ -1442,6 +1455,7 @@ msgstr "Saldo inicial de empresa"
 
 #. module: account_financial_report
 #: model:ir.model.fields.selection,name:account_financial_report.selection__general_ledger_report_wizard__grouped_by__partners
+#: model:ir.model.fields.selection,name:account_financial_report.selection__open_items_report_wizard__grouped_by__partners
 msgid "Partners"
 msgstr "Empresas"
 
diff --git a/account_financial_report/report/open_items.py b/account_financial_report/report/open_items.py
index efc3e9c6..67f8c2b9 100644
--- a/account_financial_report/report/open_items.py
+++ b/account_financial_report/report/open_items.py
@@ -1,5 +1,6 @@
 # © 2016 Julien Coux (Camptocamp)
 # Copyright 2020 ForgeFlow S.L. (https://www.forgeflow.com)
+# Copyright 2024 Tecnativa - Carolina Fernandez
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
 
 import operator
@@ -66,6 +67,7 @@ class OpenItemsReport(models.AbstractModel):
         only_posted_moves,
         company_id,
         date_from,
+        grouped_by,
     ):
         domain = self._get_move_lines_domain_not_reconciled(
             company_id, account_ids, partner_ids, only_posted_moves, date_from
@@ -75,7 +77,7 @@ class OpenItemsReport(models.AbstractModel):
             domain=domain, fields=ml_fields
         )
         journals_ids = set()
-        partners_ids = set()
+        group_ids = set()
         partners_data = {}
         if date_at_object < date.today():
             (
@@ -119,29 +121,27 @@ class OpenItemsReport(models.AbstractModel):
             journals_ids.add(move_line["journal_id"][0])
             acc_id = move_line["account_id"][0]
             # Partners data
-            if move_line["partner_id"]:
-                prt_id = move_line["partner_id"][0]
-                prt_name = move_line["partner_id"][1]
+            partner = self.env["res.partner"]
+            if move_line.get("partner_id"):
+                partner = self.env["res.partner"].browse(move_line["partner_id"][0])
+            if grouped_by == "salesperson":
+                user = partner.user_id
+                group_id = user.id or 0
+                group_name = user.name or _("Missing Salesperson")
             else:
-                prt_id = 0
-                prt_name = _("Missing Partner")
-            if prt_id not in partners_ids:
-                partners_data.update({prt_id: {"id": prt_id, "name": prt_name}})
-                partners_ids.add(prt_id)
-
+                group_id = partner.id or 0
+                group_name = partner.name or _("Missing Partner")
+            if group_id not in group_ids:
+                partners_data.update({group_id: {"id": group_id, "name": group_name}})
+                group_ids.add(group_id)
             # Move line update
-            original = 0
-
             if not float_is_zero(move_line["credit"], precision_digits=2):
                 original = move_line["credit"] * (-1)
-            if not float_is_zero(move_line["debit"], precision_digits=2):
+            else:
                 original = move_line["debit"]
 
             if move_line["ref"] == move_line["name"]:
-                if move_line["ref"]:
-                    ref_label = move_line["ref"]
-                else:
-                    ref_label = ""
+                ref_label = move_line["ref"] or ""
             elif not move_line["ref"]:
                 ref_label = move_line["name"]
             elif not move_line["name"]:
@@ -155,8 +155,8 @@ class OpenItemsReport(models.AbstractModel):
                     "date_maturity": move_line["date_maturity"]
                     and move_line["date_maturity"].strftime("%d/%m/%Y"),
                     "original": original,
-                    "partner_id": prt_id,
-                    "partner_name": prt_name,
+                    "partner_id": partner.id or 0,
+                    "partner_name": partner.name or "",
                     "ref_label": ref_label,
                     "journal_id": move_line["journal_id"][0],
                     "move_name": move_line["move_id"][1],
@@ -172,12 +172,12 @@ class OpenItemsReport(models.AbstractModel):
 
             # Open Items Move Lines Data
             if acc_id not in open_items_move_lines_data.keys():
-                open_items_move_lines_data[acc_id] = {prt_id: [move_line]}
+                open_items_move_lines_data[acc_id] = {group_id: [move_line]}
             else:
-                if prt_id not in open_items_move_lines_data[acc_id].keys():
-                    open_items_move_lines_data[acc_id][prt_id] = [move_line]
+                if group_id not in open_items_move_lines_data[acc_id].keys():
+                    open_items_move_lines_data[acc_id][group_id] = [move_line]
                 else:
-                    open_items_move_lines_data[acc_id][prt_id].append(move_line)
+                    open_items_move_lines_data[acc_id][group_id].append(move_line)
         journals_data = self._get_journals_data(list(journals_ids))
         accounts_data = self._get_accounts_data(open_items_move_lines_data.keys())
         return (
@@ -229,7 +229,9 @@ class OpenItemsReport(models.AbstractModel):
                     move_lines = []
                     for move_line in open_items_move_lines_data[acc_id][prt_id]:
                         move_lines += [move_line]
-                    move_lines = sorted(move_lines, key=lambda k: (k["date"]))
+                    move_lines = sorted(
+                        move_lines, key=lambda k: (k["date"], k["partner_id"])
+                    )
                     new_open_items[acc_id][prt_id] = move_lines
         return new_open_items
 
@@ -244,7 +246,7 @@ class OpenItemsReport(models.AbstractModel):
         date_from = data["date_from"]
         only_posted_moves = data["only_posted_moves"]
         show_partner_details = data["show_partner_details"]
-
+        grouped_by = data["grouped_by"]
         (
             move_lines_data,
             partners_data,
@@ -258,6 +260,7 @@ class OpenItemsReport(models.AbstractModel):
             only_posted_moves,
             company_id,
             date_from,
+            grouped_by,
         )
 
         total_amount = self._calculate_amounts(open_items_move_lines_data)
@@ -280,6 +283,7 @@ class OpenItemsReport(models.AbstractModel):
             "accounts_data": accounts_data,
             "total_amount": total_amount,
             "Open_Items": open_items_move_lines_data,
+            "grouped_by": grouped_by,
         }
 
     def _get_ml_fields(self):
diff --git a/account_financial_report/report/open_items_xlsx.py b/account_financial_report/report/open_items_xlsx.py
index 2e412448..42c7a4af 100644
--- a/account_financial_report/report/open_items_xlsx.py
+++ b/account_financial_report/report/open_items_xlsx.py
@@ -101,11 +101,126 @@ class OpenItemsXslx(models.AbstractModel):
     def _get_col_pos_final_balance_label(self):
         return 5
 
-    def _generate_report_content(self, workbook, report, data, report_data):
-        res_data = self.env[
-            "report.account_financial_report.open_items"
-        ]._get_report_values(report, data)
-        # For each account
+    def _calculate_amounts_by_partner(self, account_id, open_items_move_lines_data):
+        total_amount = {}
+        for line in open_items_move_lines_data:
+            partner_id_key = line["partner_id"]
+            if account_id not in total_amount:
+                total_amount[account_id] = {}
+            if partner_id_key not in total_amount[account_id]:
+                total_amount[account_id][partner_id_key] = {"residual": 0.0}
+            total_amount[account_id][partner_id_key]["residual"] += line[
+                "amount_residual"
+            ]
+        return total_amount
+
+    def _generate_report_content_by_salesperson(
+        self, workbook, report, data, report_data, res_data
+    ):
+        Open_items = res_data["Open_Items"]
+        accounts_data = res_data["accounts_data"]
+        partners_data = res_data["partners_data"]
+        journals_data = res_data["journals_data"]
+        total_amount = res_data["total_amount"]
+
+        for partner_id in partners_data.keys():
+            # Create a new sheet for each partner
+            partner_totals = {}
+            partner_name = partners_data[partner_id]["name"]
+            new_sheet = workbook.add_worksheet(partner_name[:31])
+            report_data["sheet"] = new_sheet
+            report_data["row_pos"] = 0
+
+            for account_id in Open_items.keys():
+                if partner_id in Open_items[account_id]:
+                    self.write_array_title(
+                        accounts_data[account_id]["code"]
+                        + " - "
+                        + accounts_data[account_id]["name"],
+                        report_data,
+                    )
+
+                    # For each partner
+                    if Open_items[account_id]:
+                        type_object = "partner"
+                        # Write partner title
+                        self.write_array_title(
+                            partners_data[partner_id]["name"], report_data
+                        )
+
+                        # Calculate totals by partner_id
+                        partner_totals = self._calculate_amounts_by_partner(
+                            account_id, Open_items[account_id][partner_id]
+                        )
+                        # Display array header for move lines
+                        self.write_array_header(report_data)
+                        # Display account move lines
+                        has_lines = False
+                        for partner_id_key, total_amount_dict in partner_totals.get(
+                            account_id, {}
+                        ).items():
+                            for line in Open_items[account_id][partner_id]:
+                                if line["partner_id"] == partner_id_key:
+                                    line.update(
+                                        {
+                                            "account": accounts_data[account_id][
+                                                "code"
+                                            ],
+                                            "journal": journals_data[
+                                                line["journal_id"]
+                                            ]["code"],
+                                        }
+                                    )
+                                    self.write_line_from_dict(line, report_data)
+                                    has_lines = True
+                            if has_lines:
+                                partner = self.env["res.partner"].browse(partner_id_key)
+                                # Display ending balance line for partner
+                                partner_data = {
+                                    "id": partner_id_key,
+                                    "name": partner.name
+                                    if partner
+                                    else _("Missing Partner"),
+                                    "currency_id": accounts_data[account_id][
+                                        "currency_id"
+                                    ],
+                                    "currency_name": accounts_data[account_id][
+                                        "currency_name"
+                                    ],
+                                    "residual": total_amount_dict,
+                                }
+                                self.write_ending_balance_from_dict(
+                                    partner_data,
+                                    "partner_subtotal",
+                                    partner_totals,
+                                    report_data,
+                                    account_id=account_id,
+                                    partner_id=partner_id_key,
+                                )
+                                has_lines = False
+                        # Display ending balance line for salesperson
+                        partners_data[partner_id].update(
+                            {
+                                "currency_id": accounts_data[account_id]["currency_id"],
+                                "currency_name": accounts_data[account_id][
+                                    "currency_name"
+                                ],
+                            }
+                        )
+                        self.write_ending_balance_from_dict(
+                            partners_data[partner_id],
+                            type_object,
+                            total_amount,
+                            report_data,
+                            account_id=account_id,
+                            partner_id=partner_id,
+                        )
+                        # Line break
+                        report_data["row_pos"] += 1
+
+    def _generate_report_content_by_partner(
+        self, workbook, report, data, report_data, res_data
+    ):
         Open_items = res_data["Open_Items"]
         accounts_data = res_data["accounts_data"]
         partners_data = res_data["partners_data"]
@@ -120,7 +235,6 @@ class OpenItemsXslx(models.AbstractModel):
                 + accounts_data[account_id]["name"],
                 report_data,
             )
-
             # For each partner
             if Open_items[account_id]:
                 if show_partner_details:
@@ -180,18 +294,33 @@ class OpenItemsXslx(models.AbstractModel):
                         )
                         self.write_line_from_dict(line, report_data)
 
-                # Display ending balance line for account
-                type_object = "account"
-                self.write_ending_balance_from_dict(
-                    accounts_data[account_id],
-                    type_object,
-                    total_amount,
-                    report_data,
-                    account_id=account_id,
-                )
+                    # Display ending balance line for account
+                    type_object = "account"
+                    self.write_ending_balance_from_dict(
+                        accounts_data[account_id],
+                        type_object,
+                        total_amount,
+                        report_data,
+                        account_id=account_id,
+                    )
 
-                # 2 lines break
-                report_data["row_pos"] += 2
+                    # 2 lines break
+                    report_data["row_pos"] += 2
+
+    def _generate_report_content(self, workbook, report, data, report_data):
+        res_data = self.env[
+            "report.account_financial_report.open_items"
+        ]._get_report_values(report, data)
+        show_partner_details = res_data["show_partner_details"]
+        grouped_by = res_data["grouped_by"]
+        if grouped_by == "salesperson" and show_partner_details:
+            return self._generate_report_content_by_salesperson(
+                workbook, report, data, report_data, res_data
+            )
+        else:
+            return self._generate_report_content_by_partner(
+                workbook, report, data, report_data, res_data
+            )
 
     def write_ending_balance_from_dict(
         self,
@@ -211,6 +340,10 @@ class OpenItemsXslx(models.AbstractModel):
             name = my_object["code"] + " - " + my_object["name"]
             my_object["residual"] = total_amount[account_id]["residual"]
             label = _("Ending balance")
+        elif type_object == "partner_subtotal":
+            name = my_object["name"]
+            my_object["residual"] = total_amount[account_id][partner_id]["residual"]
+            label = _("Ending balance")
         return super(OpenItemsXslx, self).write_ending_balance_from_dict(
             my_object, name, label, report_data
         )
diff --git a/account_financial_report/report/templates/open_items.xml b/account_financial_report/report/templates/open_items.xml
index 1e410464..ab4cb35b 100644
--- a/account_financial_report/report/templates/open_items.xml
+++ b/account_financial_report/report/templates/open_items.xml
@@ -9,6 +9,7 @@
             </t>
         </t>
     </template>
+
     <template id="account_financial_report.report_open_items_base">
         <!-- Saved flag fields into variables, used to define columns display -->
         <t t-set="foreign_currency" t-value="foreign_currency" />
@@ -28,82 +29,188 @@
                     style="text-align: center;"
                 />
             </div>
-            <!-- Display filters -->
-            <t t-call="account_financial_report.report_open_items_filters" />
-            <t t-foreach="Open_Items.keys()" t-as="account_id">
-                <!-- Display account header -->
-                <div class="act_as_table list_table" style="margin-top: 10px;" />
-                <div class="account_title" style="width: 100%;">
-                    <span t-esc="accounts_data[account_id]['code']" />
-                    -
-                    <span t-esc="accounts_data[account_id]['name']" />
-                </div>
-                <t t-if="not show_partner_details">
-                    <div class="act_as_table data_table" style="width: 100%;">
-                        <t
-                            t-call="account_financial_report.report_open_items_lines_header"
-                        />
-                        <!-- Display account move lines -->
-                        <t t-foreach="Open_Items[account_id]" t-as="line">
-                            <t
-                                t-call="account_financial_report.report_open_items_lines"
-                            />
-                        </t>
+            <t t-if="grouped_by == 'salesperson' and show_partner_details">
+                <t t-foreach="partners_data.keys()" t-as="partner_id">
+                    <t t-call="account_financial_report.report_open_items_filters" />
+                    <div class="act_as_caption account_title">
+                        <span t-esc="partners_data[partner_id]['name']" />
                     </div>
-                </t>
-                <t t-if="show_partner_details">
-                    <div class="page_break">
-                        <!-- Display account partners -->
-                        <t t-foreach="Open_Items[account_id]" t-as="partner_id">
-                            <div class="act_as_caption account_title">
-                                <span t-esc="partners_data[partner_id]['name']" />
+                    <t t-foreach="Open_Items.keys()" t-as="account_id">
+                        <t t-if="partner_id in Open_Items[account_id]">
+                            <div
+                                class="act_as_table list_table"
+                                style="margin-top: 10px;"
+                            />
+                            <div class="account_title" style="width: 100%;">
+                                <span t-esc="accounts_data[account_id]['code']" />
+                                -
+                                <span t-esc="accounts_data[account_id]['name']" />
                             </div>
-                            <div class="act_as_table data_table" style="width: 100%;">
-                                <!-- Display partner header -->
-                                <t
-                                    t-call="account_financial_report.report_open_items_lines_header"
+
+                        <t t-if="Open_Items[account_id]">
+                            <t
+                                    t-set="partner_totals"
+                                    t-value="o._calculate_amounts_by_partner(account_id,Open_Items[account_id][partner_id])"
                                 />
-                                <!-- Display partner move lines -->
-                                <t
-                                    t-foreach="Open_Items[account_id][partner_id]"
-                                    t-as="line"
+                            <t
+                                    t-foreach="partner_totals.get(account_id, {})"
+                                    t-as="partner_id_key"
                                 >
+                                <t t-set="has_lines" t-value="False" />
+                                <div
+                                        class="act_as_table data_table"
+                                        style="width: 100%;"
+                                    >
+
                                     <t
-                                        t-call="account_financial_report.report_open_items_lines"
-                                    />
+                                            t-foreach="Open_Items[account_id][partner_id]"
+                                            t-as="line"
+                                        >
+                                    <t t-if="line['partner_id'] == partner_id_key">
+                                            <t t-set="has_lines" t-value="True" />
+                                    </t>
+
                                 </t>
-                            </div>
+                                <t t-if="has_lines">
+                                    <!-- Display partner header -->
+                                    <t
+                                                t-call="account_financial_report.report_open_items_lines_header"
+                                            />
+                                </t>
+                                <t
+                                            t-foreach="Open_Items[account_id][partner_id]"
+                                            t-as="line"
+                                        >
+                                        <t t-if="line['partner_id'] == partner_id_key">
+                                            <!-- Display partner move lines -->
+                                            <t
+                                                    t-call="account_financial_report.report_open_items_lines"
+                                                />
+                                        </t>
+
+                                        </t>
+                                </div>
+                                <!-- Check if there were any lines displayed for the partner -->
+                                <t t-if="has_lines">
+                                    <!-- Calculate and display subtotal for current partner_id -->
+                                    <t
+                                            t-call="account_financial_report.report_open_items_ending_cumul"
+                                        >
+                                        <t
+                                                t-set="currency_id"
+                                                t-value="accounts_data[account_id]['currency_name']"
+                                            />
+                                        <t
+                                                t-set="type"
+                                                t-value="'partner_subtotal_type'"
+                                            />
+                                    </t>
+                                </t>
+                            </t>
+
+                        </t>
+                        <!-- Display account footer -->
                             <t
                                 t-call="account_financial_report.report_open_items_ending_cumul"
                             >
-                                <t
+                            <t
                                     t-set="account_or_partner_id"
                                     t-value="partners_data[partner_id]"
                                 />
-                                <t
+                            <t
                                     t-set="currency_id"
                                     t-value="accounts_data[account_id]['currency_name']"
                                 />
-                                <t t-set="type" t-value='"partner_type"' />
-                            </t>
+                            <t t-set="type" t-value='"partner_type"' />
                         </t>
+                    </t>
+
+                </t>
+                <div style="page-break-after: always;" />
+            </t>
+            </t>
+            <t t-else="">
+            <!-- Display filters -->
+            <t t-call="account_financial_report.report_open_items_filters" />
+                <t t-foreach="Open_Items.keys()" t-as="account_id">
+                    <!-- Display account header -->
+                    <div class="act_as_table list_table" style="margin-top: 10px;" />
+                    <div class="account_title" style="width: 100%;">
+                        <span t-esc="accounts_data[account_id]['code']" />
+                        -
+                        <span t-esc="accounts_data[account_id]['name']" />
                     </div>
-                </t>
-                <!-- Display account footer -->
-                <t t-call="account_financial_report.report_open_items_ending_cumul">
-                    <t
-                        t-set="account_or_partner_id"
-                        t-value="accounts_data[account_id]"
-                    />
-                    <t
-                        t-set="currency_id"
-                        t-value="accounts_data[account_id]['currency_name']"
-                    />
-                    <t t-set="type" t-value='"account_type"' />
-                </t>
+                    <t t-if="not show_partner_details">
+                        <div class="act_as_table data_table" style="width: 100%;">
+                            <t
+                                t-call="account_financial_report.report_open_items_lines_header"
+                            />
+                            <!-- Display account move lines -->
+                            <t t-foreach="Open_Items[account_id]" t-as="line">
+                                <t
+                                    t-call="account_financial_report.report_open_items_lines"
+                                />
+                            </t>
+                        </div>
+                    </t>
+                    <t t-if="show_partner_details">
+                        <div class="page_break">
+                            <!-- Display account partners -->
+                            <t t-foreach="Open_Items[account_id]" t-as="partner_id">
+                                <div class="act_as_caption account_title">
+                                    <span t-esc="partners_data[partner_id]['name']" />
+                                </div>
+                                <div
+                                    class="act_as_table data_table"
+                                    style="width: 100%;"
+                                >
+                                    <!-- Display partner header -->
+                                    <t
+                                        t-call="account_financial_report.report_open_items_lines_header"
+                                    />
+                                    <!-- Display partner move lines -->
+                                    <t
+                                        t-foreach="Open_Items[account_id][partner_id]"
+                                        t-as="line"
+                                    >
+                                        <t
+                                            t-call="account_financial_report.report_open_items_lines"
+                                        />
+                                    </t>
+                                </div>
+                                <t
+                                    t-call="account_financial_report.report_open_items_ending_cumul"
+                                >
+                                    <t
+                                        t-set="account_or_partner_id"
+                                        t-value="partners_data[partner_id]"
+                                    />
+                                    <t
+                                        t-set="currency_id"
+                                        t-value="accounts_data[account_id]['currency_name']"
+                                    />
+                                    <t t-set="type" t-value='"partner_type"' />
+                                </t>
+                            </t>
+                        </div>
+                    </t>
+                    <!-- Display account footer -->
+                    <t t-call="account_financial_report.report_open_items_ending_cumul">
+                        <t
+                            t-set="account_or_partner_id"
+                            t-value="accounts_data[account_id]"
+                        />
+                        <t
+                            t-set="currency_id"
+                            t-value="accounts_data[account_id]['currency_name']"
+                        />
+                        <t t-set="type" t-value='"account_type"' />
+                    </t>
+            </t>
             </t>
         </div>
     </template>
+
     <template id="account_financial_report.report_open_items_filters">
         <div class="act_as_table data_table" style="width: 100%;">
             <div class="act_as_row labels">
@@ -294,6 +401,20 @@
                         Partner ending balance
                     </div>
                 </t>
+                <t t-if='type == "partner_subtotal_type"'>
+                    <div class="act_as_cell first_column" style="width: 36.34%;" />
+                    <t
+                        t-set="partner"
+                        t-value="env['res.partner'].browse(partner_id_key)"
+                    />
+                    <t t-if="partner">
+                        <span t-esc="partner.name" />
+                    </t>
+                    <div class="act_as_cell right" style="width: 28.66%;">
+                        Ending
+                        balance
+                    </div>
+                </t>
                 <!--## date_due-->
                 <div class="act_as_cell" style="width: 6.47%;" />
                 <!--## amount_total_due-->
@@ -312,6 +433,12 @@
                             t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"
                         />
                     </t>
+                    <t t-if='type == "partner_subtotal_type"'>
+                        <span
+                            t-esc="partner_totals[account_id][partner_id_key]['residual']"
+                            t-options="{'widget': 'monetary', 'display_currency': res_company.currency_id}"
+                        />
+                    </t>
                 </div>
                 <!--## amount_total_due_currency + amount_residual_currency -->
                 <t t-if="foreign_currency">
diff --git a/account_financial_report/static/description/index.html b/account_financial_report/static/description/index.html
index 381e13f5..6ddc5721 100644
--- a/account_financial_report/static/description/index.html
+++ b/account_financial_report/static/description/index.html
@@ -387,6 +387,8 @@ currency balances are not available.</p>
 <p>Invoicing / Settings / Invoicing / OCA Aged Report Configuration you will be able to set
 dynamic intervals that will appear on the Aged Partner Balance.
 For further information, check CONFIGURE.rst</p>
+<p>Add new grouped by field to export Open items report grouped by partner salesperson.
+If grouped by is empty or selected partner option it will grouped by Open items by partner.</p>
 <p><strong>Table of contents</strong></p>
 <div class="contents local topic" id="contents">
 <ul class="simple">
diff --git a/account_financial_report/tests/test_open_items.py b/account_financial_report/tests/test_open_items.py
index 4cfc7c88..1229de2d 100644
--- a/account_financial_report/tests/test_open_items.py
+++ b/account_financial_report/tests/test_open_items.py
@@ -1,7 +1,9 @@
 # Author: Julien Coux
 # Copyright 2016 Camptocamp SA
+# Copyright 2024 Tecnativa - Carolina Fernandez
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
 
+from odoo.fields import Date
 from odoo.tests import tagged
 
 from odoo.addons.account.tests.common import AccountTestInvoicingCommon
@@ -66,3 +68,23 @@ class TestOpenItems(AccountTestInvoicingCommon):
         [open_items_code_set.add(account.code) for account in open_items.account_ids]
         self.assertEqual(len(open_items_code_set), len(all_accounts_code_set))
         self.assertTrue(open_items_code_set == all_accounts_code_set)
+
+    def test_open_items_grouped_by(self):
+        open_item_wizard = self.env["open.items.report.wizard"]
+        all_accounts = self.env["account.account"].search(
+            [
+                ("reconcile", "=", True),
+            ],
+            order="code",
+        )
+        wizard = open_item_wizard.create(
+            {
+                "date_at": Date.today(),
+                "account_code_from": self.account001.id,
+                "account_code_to": all_accounts[-1].id,
+                "grouped_by": "salesperson",
+            }
+        )
+        wizard.on_change_account_range()
+        res = wizard._prepare_report_open_items()
+        self.assertEqual(res["grouped_by"], wizard.grouped_by)
diff --git a/account_financial_report/wizard/open_items_wizard.py b/account_financial_report/wizard/open_items_wizard.py
index 7e696f48..467164db 100644
--- a/account_financial_report/wizard/open_items_wizard.py
+++ b/account_financial_report/wizard/open_items_wizard.py
@@ -59,6 +59,10 @@ class OpenItemsReportWizard(models.TransientModel):
         comodel_name="account.account",
         help="Ending account in a range",
     )
+    grouped_by = fields.Selection(
+        selection=[("partners", "Partners"), ("salesperson", "Partner Salesperson")],
+        default="partners",
+    )
 
     @api.onchange("account_code_from", "account_code_to")
     def on_change_account_range(self):
@@ -134,6 +138,19 @@ class OpenItemsReportWizard(models.TransientModel):
         else:
             self.account_ids = None
 
+    def _calculate_amounts_by_partner(self, account_id, open_items_move_lines_data):
+        total_amount = {}
+        for line in open_items_move_lines_data:
+            partner_id_key = line["partner_id"]
+            if account_id not in total_amount:
+                total_amount[account_id] = {}
+            if partner_id_key not in total_amount[account_id]:
+                total_amount[account_id][partner_id_key] = {"residual": 0.0}
+            total_amount[account_id][partner_id_key]["residual"] += line[
+                "amount_residual"
+            ]
+        return total_amount
+
     def _print_report(self, report_type):
         self.ensure_one()
         data = self._prepare_report_open_items()
@@ -165,6 +182,7 @@ class OpenItemsReportWizard(models.TransientModel):
             "account_ids": self.account_ids.ids,
             "partner_ids": self.partner_ids.ids or [],
             "account_financial_report_lang": self.env.lang,
+            "grouped_by": self.grouped_by,
         }
 
     def _export(self, report_type):
diff --git a/account_financial_report/wizard/open_items_wizard_view.xml b/account_financial_report/wizard/open_items_wizard_view.xml
index 3b65204d..610b5367 100644
--- a/account_financial_report/wizard/open_items_wizard_view.xml
+++ b/account_financial_report/wizard/open_items_wizard_view.xml
@@ -21,6 +21,7 @@
                     <group name="other_filters">
                         <field name="target_move" widget="radio" />
                         <field name="show_partner_details" />
+                        <field name="grouped_by" />
                         <field name="hide_account_at_0" />
                         <field name="foreign_currency" />
                     </group>