Skip to content

Commit

Permalink
[IMP] sale_order_invoice_amount: Invoice calculation based on quantities
Browse files Browse the repository at this point in the history
  • Loading branch information
MeritxellAForgeFlow committed Feb 7, 2025
1 parent a6465b4 commit 155d21e
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 71 deletions.
1 change: 1 addition & 0 deletions sale_order_invoice_amount/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
],
"data": [
"views/sale_order_view.xml",
"views/sale_order_config_settings.xml",
],
"installable": True,
"pre_init_hook": "pre_init_hook",
Expand Down
21 changes: 21 additions & 0 deletions sale_order_invoice_amount/migrations/17.0.1.0.0/post-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2024 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import logging

from odoo import SUPERUSER_ID, api

_logger = logging.getLogger(__name__)


def migrate(cr, version):
if not version:
return

env = api.Environment(cr, SUPERUSER_ID, {})
_logger.info(
"Set 'enable_amount_invoiced_based_on_quantity to True "
"so the amount to invoice is calculated based on quantity"
)
companies = env["res.company"].search([])
companies.write({"enable_amount_invoiced_based_on_quantity": True})
2 changes: 2 additions & 0 deletions sale_order_invoice_amount/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from . import sale_order
from . import sale_order_config_settings
from . import res_company
13 changes: 13 additions & 0 deletions sale_order_invoice_amount/models/res_company.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2025 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)

from odoo import fields, models


class ResCompany(models.Model):
_inherit = "res.company"

enable_amount_invoiced_based_on_quantity = fields.Boolean(
string="Enable computing amount invoiced based on quantity",
default=False,
)
95 changes: 52 additions & 43 deletions sale_order_invoice_amount/models/sale_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
class SaleOrder(models.Model):
_inherit = "sale.order"

invoiced_amount = fields.Monetary(
compute="_compute_invoice_amount",
amount_invoiced = fields.Monetary(
compute="_compute_amount_invoiced",
store=True,
help="Order amount already invoiced.",
)

uninvoiced_amount = fields.Monetary(
compute="_compute_invoice_amount",
amount_to_invoice = fields.Monetary(
compute="_compute_amount_to_invoice",
store=True,
help="Order amount to be invoiced",
)
Expand All @@ -26,42 +26,51 @@ class SaleOrder(models.Model):
"amount_total",
"invoice_ids.state",
)
def _compute_invoice_amount(self):
for rec in self:
if rec.state != "cancel" and rec.invoice_ids:
rec.invoiced_amount = 0.0
for invoice in rec.invoice_ids:
if invoice.state != "cancel":
if (
invoice.currency_id != rec.currency_id
and rec.currency_id != invoice.company_currency_id
):
rec.invoiced_amount += invoice.currency_id._convert(
invoice.amount_total_in_currency_signed,
rec.currency_id,
invoice.company_id,
invoice.invoice_date or fields.Date.today(),
def _compute_amount_invoiced(self):
if not self.env.company.enable_amount_invoiced_based_on_quantity:
return super()._compute_amount_invoiced()
else:
for rec in self:
if rec.state != "cancel" and rec.invoice_ids:
rec.amount_invoiced = 0.0
for invoice in rec.invoice_ids:
if invoice.state != "cancel":
invoices = rec.invoice_ids.filtered(
lambda x: x.state == "posted"
)
else:
rec.invoiced_amount += invoice.amount_total_signed
# Uninvoiced amount could not be equal to total - invoiced amount.
# For example if the amount invoiced does not match with the price unit.
rec.uninvoiced_amount = max(
0,
sum(
(line.product_uom_qty - line.qty_invoiced)
* (line.price_total / line.product_uom_qty)
for line in rec.order_line.filtered(
lambda sl: sl.product_uom_qty > 0
)
),
)
else:
rec.invoiced_amount = 0.0
if (
invoice.currency_id != rec.currency_id
and rec.currency_id != invoice.company_currency_id
):
rec.amount_invoiced += (
invoices._get_sale_order_invoiced_amount(rec)
)
else:
rec.amount_invoiced += invoice.amount_total_signed
else:
rec.amount_invoiced = 0.0

# Amount to invoice could not be equal to total - amount invoiced.
# For example if the amount invoiced does not match with the price unit.
@api.depends("invoice_ids.state")
def _compute_amount_to_invoice(self):
if not self.env.company.enable_amount_invoiced_based_on_quantity:
return super()._compute_amount_to_invoice()
else:
for rec in self:
if rec.state in ["draft", "sent", "cancel"]:
rec.uninvoiced_amount = 0.0
rec.amount_to_invoice = 0.0
else:
rec.uninvoiced_amount = rec.amount_total
rec.amount_to_invoice = max(
0,
sum(
(line.product_uom_qty - line.qty_invoiced)
* (line.price_total / line.product_uom_qty)
for line in rec.order_line.filtered(
lambda sl: sl.product_uom_qty > 0
)
),
)

@api.depends(
"order_line.tax_id",
Expand All @@ -80,14 +89,14 @@ def _compute_tax_totals(self):
lang_env = order.with_context(lang=order.partner_id.lang).env
order.tax_totals.update(
{
"invoiced_amount": order.invoiced_amount,
"formatted_invoiced_amount": formatLang(
lang_env, order.invoiced_amount, currency_obj=order.currency_id
"amount_invoiced": order.amount_invoiced,
"formatted_amount_invoiced": formatLang(
lang_env, order.amount_invoiced, currency_obj=order.currency_id
),
"uninvoiced_amount": order.uninvoiced_amount,
"formatted_uninvoiced_amount": formatLang(
"amount_to_invoice": order.amount_to_invoice,
"formatted_amount_to_invoice": formatLang(
lang_env,
order.uninvoiced_amount,
order.amount_to_invoice,
currency_obj=order.currency_id,
),
}
Expand Down
13 changes: 13 additions & 0 deletions sale_order_invoice_amount/models/sale_order_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2025 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)

from odoo import fields, models


class SaleConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

enable_amount_invoiced_based_on_quantity = fields.Boolean(
related="company_id.enable_amount_invoiced_based_on_quantity",
readonly=False,
)
1 change: 1 addition & 0 deletions sale_order_invoice_amount/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
- Mateu Griful \<<[email protected]>\>
- Lois Rilo \<<[email protected]>\>
- Meritxell Abellan \<<[email protected]>\>
Loading

0 comments on commit 155d21e

Please sign in to comment.