Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion account_credit_control/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@ Contributors
- Akim Juillerat (Camptocamp) <akim.juillerat@camptocamp.com>
- Kinner Vachhani (Access Bookings Ltd) <kin.vachhani@gmail.com>
- Raf Ven <raf.ven@dynapps.be>
- Quentin Groulard (ACSONE) <quentin.groulard@acsone.eu>
- `Acsone <https://www.acsone.eu>`__:

- Quentin Groulard
- Yannick Payot

- `Tecnativa <https://www.tecnativa.com>`__:

- Vicent Cubells
Expand Down
94 changes: 54 additions & 40 deletions account_credit_control/models/credit_control_communication.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Copyright 2018 Access Bookings Ltd (https://accessbookings.com)
# Copyright 2020 Manuel Calero - Tecnativa
# Copyright 2023 Tecnativa - Víctor Martínez
# Copyright 2019 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import threading

Expand Down Expand Up @@ -114,61 +115,74 @@ def _get_credit_lines(
):
"""Return credit lines related to a partner and a policy level"""
cr_line_obj = self.env["credit.control.line"]
cr_lines = cr_line_obj.search(
[
("id", "in", line_ids),
("partner_id", "=", partner_id),
("policy_level_id", "=", level_id),
("currency_id", "=", currency_id),
("company_id", "=", company_id),
]
domain = [
("id", "in", line_ids),
("partner_id", "=", partner_id),
("currency_id", "=", currency_id),
("company_id", "=", company_id),
("policy_level_id", "=", level_id),
]
return cr_line_obj.search(domain, order="level DESC")

@api.model
def _sql_credit_lines_groups(self):
"""Create a query to return:
partner, level, currency, company
"""
return (
"SELECT DISTINCT"
" partner_id,"
" policy_level_id,"
" policy_level.level,"
" line.currency_id,"
" line.company_id"
" FROM credit_control_line AS line"
" JOIN credit_control_policy_level as policy_level"
" ON (line.policy_level_id = policy_level.id)"
" WHERE line.id in %s"
" ORDER BY policy_level.level, line.currency_id"
)
return cr_lines

@api.model
def _aggregate_credit_lines(self, lines):
"""Aggregate credit control line by partner, level, and currency"""
if not lines:
return []
def _get_credit_line_groups(self, lines):
"""Returns key groups to be processed"""
# Needed for related stored fields
# are recomputed before executing the SQL
lines.flush_recordset()
sql = (
"SELECT distinct partner_id, policy_level_id, "
" credit_control_line.currency_id, "
" credit_control_policy_level.level, "
" credit_control_line.company_id "
" FROM credit_control_line JOIN credit_control_policy_level "
" ON (credit_control_line.policy_level_id = "
" credit_control_policy_level.id)"
" WHERE credit_control_line.id in %s"
" ORDER by credit_control_policy_level.level, "
" credit_control_line.currency_id"
)
sql = self._sql_credit_lines_groups()
cr = self.env.cr
cr.execute(sql, (tuple(lines.ids),))
res = cr.dictfetchall()
return cr.dictfetchall()

@api.model
def _prepare_communication_data(self, cr_lines):
line = cr_lines[0]
company = line.company_id or self.env.company
return {
"credit_control_line_ids": [(6, 0, cr_lines.ids)],
"partner_id": line.partner_id.id,
"policy_level_id": line.policy_level_id.id,
"currency_id": line.currency_id.id or company.currency_id.id,
"company_id": company.id,
}

@api.model
def _aggregate_credit_lines(self, lines):
"""Aggregate credit control line by partner, level, and currency"""
if not lines:
return []
datas = []
for group in res:
data = {}
level_lines = self._get_credit_lines(
for group in self._get_credit_line_groups(lines):
grouped_lines = self._get_credit_lines(
lines.ids,
group["partner_id"],
group["policy_level_id"],
group["currency_id"],
group["company_id"],
)
company = (
self.env["res.company"].browse(group["company_id"])
if group["company_id"]
else self.env.company
)
company_currency = company.currency_id
data["credit_control_line_ids"] = [(6, 0, level_lines.ids)]
data["partner_id"] = group["partner_id"]
data["policy_level_id"] = group["policy_level_id"]
data["currency_id"] = group["currency_id"] or company_currency.id
data["company_id"] = group["company_id"] or company.id
if not grouped_lines:
continue
data = self._prepare_communication_data(grouped_lines)
datas.append(data)
return datas

Expand Down
3 changes: 2 additions & 1 deletion account_credit_control/models/credit_control_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class CreditControlLine(models.Model):
partner_id = fields.Many2one(
comodel_name="res.partner",
required=True,
readonly=False,
index=True,
)
commercial_partner_id = fields.Many2one(
comodel_name="res.partner",
Expand Down Expand Up @@ -116,6 +116,7 @@ class CreditControlLine(models.Model):
policy_id = fields.Many2one(
comodel_name="credit.control.policy",
related="policy_level_id.policy_id",
index=True,
store=True,
)
level = fields.Integer(
Expand Down
4 changes: 3 additions & 1 deletion account_credit_control/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
- Akim Juillerat (Camptocamp) \<<akim.juillerat@camptocamp.com>\>
- Kinner Vachhani (Access Bookings Ltd) \<<kin.vachhani@gmail.com>\>
- Raf Ven \<<raf.ven@dynapps.be>\>
- Quentin Groulard (ACSONE) \<<quentin.groulard@acsone.eu>\>
- [Acsone](https://www.acsone.eu):
- Quentin Groulard
- Yannick Payot
- [Tecnativa](https://www.tecnativa.com):
- Vicent Cubells
- Manuel Calero
Expand Down
6 changes: 5 additions & 1 deletion account_credit_control/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,11 @@ <h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
<li>Akim Juillerat (Camptocamp) &lt;<a class="reference external" href="mailto:akim.juillerat&#64;camptocamp.com">akim.juillerat&#64;camptocamp.com</a>&gt;</li>
<li>Kinner Vachhani (Access Bookings Ltd) &lt;<a class="reference external" href="mailto:kin.vachhani&#64;gmail.com">kin.vachhani&#64;gmail.com</a>&gt;</li>
<li>Raf Ven &lt;<a class="reference external" href="mailto:raf.ven&#64;dynapps.be">raf.ven&#64;dynapps.be</a>&gt;</li>
<li>Quentin Groulard (ACSONE) &lt;<a class="reference external" href="mailto:quentin.groulard&#64;acsone.eu">quentin.groulard&#64;acsone.eu</a>&gt;</li>
<li><a class="reference external" href="https://www.acsone.eu">Acsone</a>:<ul>
<li>Quentin Groulard</li>
<li>Yannick Payot</li>
</ul>
</li>
<li><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:<ul>
<li>Vicent Cubells</li>
<li>Manuel Calero</li>
Expand Down
2 changes: 1 addition & 1 deletion account_credit_control/wizard/credit_control_printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def _default_line_ids(self):
context = self.env.context
if context.get("active_model") != "credit.control.line":
return False
return context.get("active_ids", False)
return context.get("active_ids")

mark_as_sent = fields.Boolean(
string="Mark letter lines as done",
Expand Down
102 changes: 102 additions & 0 deletions account_credit_control_aggregate_level/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
======================
Account Credit Control
======================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:abaee855dd44ae136c041103b894cef9609b42a63c31df8f9da726b5caabb0d7
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fcredit--control-lightgray.png?logo=github
:target: https://github.com/OCA/credit-control/tree/18.0/account_credit_control_aggregate_level
:alt: OCA/credit-control
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/credit-control-18-0/credit-control-18-0-account_credit_control_aggregate_level
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/credit-control&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

Account Credit Control module is a part of Financial Tools used in
business to ensure that once sales are made they are realised as cash.
This module helps to identify outstanding debt beyond tolerance level
and setup followup method.

**Table of contents**

.. contents::
:local:

Configuration
=============

On credit policy enable ``Aggregate lower levels`` to aggregate lines
from lower level and same partner when a credit control line generated
by this policy is processed.

Usage
=====

The option ``Aggregate lower levels`` on ``Credit Control Policy`` helps
to manage high credit control line generation rates (e.g. if you are
selling monthly subscriptions). In such situation you may want to avoid
spamming reminders to customers with several credit control lines of
different levels, and only take action for the highest level. Thus, a
communication is sent for a high level will also contain the lower level
lines. Plus, you can use the filter ``Group Lines`` in the
``Credit Control Lines`` menu to hide lines that will be auto-processed
when a highest level line is.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/credit-control/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/credit-control/issues/new?body=module:%20account_credit_control_aggregate_level%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* ACSONE SA/NV

Contributors
------------

- `Acsone <https://www.acsone.eu>`__:

- Quentin Groulard
- Yannick Payot

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/credit-control <https://github.com/OCA/credit-control/tree/18.0/account_credit_control_aggregate_level>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
3 changes: 3 additions & 0 deletions account_credit_control_aggregate_level/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models
from . import wizard
20 changes: 20 additions & 0 deletions account_credit_control_aggregate_level/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2019 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Account Credit Control",
"version": "18.0.1.0.0",
"author": "ACSONE SA/NV, Odoo Community Association (OCA)",
"maintainer": "ACSONE SA/NV",
"category": "Finance",
"depends": ["account_credit_control"],
"website": "https://github.com/OCA/credit-control",
"data": [
# Views
"views/credit_control_line.xml",
"views/credit_control_policy.xml",
],
"demo": [],
"installable": True,
"license": "AGPL-3",
"application": True,
}
4 changes: 4 additions & 0 deletions account_credit_control_aggregate_level/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import credit_control_communication
from . import credit_control_line
from . import credit_control_policy
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright 2019 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, models


class CreditControlCommunication(models.Model):
_inherit = "credit.control.communication"

@api.model
def _get_credit_lines(
self, line_ids, partner_id, level_id, currency_id, company_id
):
# Handle case when no policy_level is provided.
if level_id:
return super()._get_credit_lines(
line_ids, partner_id, level_id, currency_id, company_id
)
cr_line_obj = self.env["credit.control.line"]
domain = [
("id", "in", line_ids),
("partner_id", "=", partner_id),
("currency_id", "=", currency_id),
("company_id", "=", company_id),
]
return cr_line_obj.search(domain, order="level DESC")

@api.model
def _sql_credit_lines_groups(self):
# Remove level when aggregation is enabled on policy
# returns NULL instead of an ID for the level
return (
"SELECT DISTINCT"
" partner_id,"
" CASE"
" WHEN policy.aggregate_levels"
" THEN NULL"
" ELSE policy_level.id"
" END AS policy_level_id,"
" line.currency_id,"
" line.company_id"
" FROM credit_control_line AS line"
" JOIN credit_control_policy_level as policy_level"
" ON (line.policy_level_id = policy_level.id)"
" JOIN credit_control_policy as policy"
" ON (policy_level.policy_id = policy.id)"
" WHERE line.id in %s"
)

@api.model
def _prepare_communication_data(self, cr_lines):
# Redefine policy_level to ensure it's the highest
res = super()._prepare_communication_data(cr_lines)
highest_policy_lvl = cr_lines.mapped("policy_level_id").sorted("level")[0]
res["policy_level_id"] = highest_policy_lvl.id
return res
Loading
Loading