diff --git a/purchase_work_acceptance/models/account_move.py b/purchase_work_acceptance/models/account_move.py index b35a09185ce..f9f832dbc33 100644 --- a/purchase_work_acceptance/models/account_move.py +++ b/purchase_work_acceptance/models/account_move.py @@ -1,8 +1,11 @@ # Copyright 2019 Ecosoft Co., Ltd. (http://ecosoft.co.th) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from collections import defaultdict + from odoo import _, api, fields, models from odoo.exceptions import ValidationError +from odoo.tools import float_compare class AccountMove(models.Model): @@ -29,38 +32,52 @@ def _compute_require_wa(self): ) rec.require_wa = self.wa_id and enforce_wa + def _aggregate_qty_by_product(self, lines, qty_field, uom_field): + """Aggregate quantities by product, converted to the product's base UoM.""" + result = defaultdict(float) + for line in lines: + uom = getattr(line, uom_field) + qty = uom._compute_quantity( + getattr(line, qty_field), line.product_id.uom_id + ) + if qty > 0.0: + result[line.product_id.id] += qty + return dict(result) + + def _check_quantity_wa(self): + self.ensure_one() + precision = self.env["decimal.precision"].precision_get( + "Product Unit of Measure" + ) + wa_qty = self._aggregate_qty_by_product( + self.wa_id.wa_line_ids, "product_qty", "product_uom" + ) + invoice_qty = self._aggregate_qty_by_product( + self.invoice_line_ids.filtered(lambda line: line.control_wa), + "quantity", + "product_uom_id", + ) + all_products = set(wa_qty.keys()) | set(invoice_qty.keys()) + for product_id in all_products: + wa = wa_qty.get(product_id, 0.0) + inv = invoice_qty.get(product_id, 0.0) + if float_compare(wa, inv, precision_digits=precision) != 0: + product = self.env["product.product"].browse(product_id) + raise ValidationError( + _( + "Quantity mismatch for product '%(product)s': " + "WA quantity = %(wa_qty)s, Invoice quantity = %(inv_qty)s", + product=product.display_name, + wa_qty=wa, + inv_qty=inv, + ) + ) + def action_post(self): for rec in self: - if rec.wa_id: - wa_line = {} - for line in rec.wa_id.wa_line_ids: - qty = line.product_uom._compute_quantity( - line.product_qty, line.product_id.uom_id - ) - if qty > 0.0: - if line.product_id.id in wa_line.keys(): - qty_old = wa_line[line.product_id.id] - wa_line[line.product_id.id] = qty_old + qty - else: - wa_line[line.product_id.id] = qty - invoice_line = {} - for line in rec.invoice_line_ids: - qty = line.product_uom_id._compute_quantity( - line.quantity, line.product_id.uom_id - ) - if qty > 0.0: - if line.product_id.id in invoice_line.keys(): - qty_old = invoice_line[line.product_id.id] - invoice_line[line.product_id.id] = qty_old + qty - else: - invoice_line[line.product_id.id] = qty - if wa_line != invoice_line: - raise ValidationError( - _( - "You cannot validate a bill if Quantity not equal " - "accepted quantity" - ) - ) + if not rec.wa_id: + continue + rec._check_quantity_wa() return super().action_post() @api.model @@ -71,3 +88,9 @@ def create(self, vals): lines = filter(lambda l: len(l) == 3 and l[2].get("quantity") != 0, lines) vals["invoice_line_ids"] = list(lines) return super().create(vals) + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + control_wa = fields.Boolean(default=True, copy=False) diff --git a/purchase_work_acceptance/views/account_move_views.xml b/purchase_work_acceptance/views/account_move_views.xml index 1b2e2c51965..068dcaf4d98 100644 --- a/purchase_work_acceptance/views/account_move_views.xml +++ b/purchase_work_acceptance/views/account_move_views.xml @@ -14,6 +14,27 @@ attrs="{'required': [('require_wa','=', True)], 'invisible': [('wa_id', '=', False)]}" /> + + + + + + + + + + + +