From 6d9248678af9da375100a45e8f3d9173b433ac44 Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Thu, 22 Aug 2024 14:30:54 -0300 Subject: [PATCH 1/2] [IMP] l10n_br_purchase_stock: avoid glue modules --- l10n_br_purchase_stock/__manifest__.py | 1 + .../wizards/stock_invocing_onshipping.py | 132 +++++++++++++++--- 2 files changed, 111 insertions(+), 22 deletions(-) diff --git a/l10n_br_purchase_stock/__manifest__.py b/l10n_br_purchase_stock/__manifest__.py index 061775e9ed79..787a903e230a 100644 --- a/l10n_br_purchase_stock/__manifest__.py +++ b/l10n_br_purchase_stock/__manifest__.py @@ -6,6 +6,7 @@ "license": "AGPL-3", "category": "Localisation", "author": "Akretion, Odoo Community Association (OCA)", + "maintainers": ["renatonlima", "mbcosta"], "website": "https://github.com/OCA/l10n-brazil", "version": "14.0.2.0.1", "depends": [ diff --git a/l10n_br_purchase_stock/wizards/stock_invocing_onshipping.py b/l10n_br_purchase_stock/wizards/stock_invocing_onshipping.py index 2c8afc1a0b5c..fc8f86f1b24b 100644 --- a/l10n_br_purchase_stock/wizards/stock_invocing_onshipping.py +++ b/l10n_br_purchase_stock/wizards/stock_invocing_onshipping.py @@ -2,7 +2,7 @@ # Magno Costa # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import fields, models +from odoo import models from odoo.addons.l10n_br_fiscal.constants.fiscal import DOCUMENT_ISSUER_PARTNER @@ -18,16 +18,76 @@ def _build_invoice_values_from_pickings(self, pickings): """ invoice, values = super()._build_invoice_values_from_pickings(pickings) - pick = fields.first(pickings) - if pick.purchase_id: - values["purchase_id"] = pick.purchase_id.id - values["issuer"] = DOCUMENT_ISSUER_PARTNER + purchase_pickings = pickings.filtered(lambda pk: pk.purchase_id) + if purchase_pickings and self._get_invoice_type() != "in_refund": + # Case more than one Purchase Order the fields below will be join + # the others will be overwritting, as done in purchase module, + # one more field include here Note + payment_refs = set() + refs = set() + # Include Note/Narration + narration = set() + for picking in purchase_pickings: + # Campos informados em qualquer caso + purchase = picking.purchase_id - if pick.purchase_id.payment_term_id.id != values.get( - "invoice_payment_term_id" - ): + # Campo purchase_id store=false + # values["purchase_id"] = purchase.id + if picking.fiscal_operation_id: + values["issuer"] = DOCUMENT_ISSUER_PARTNER + + # Refund case don't get values from Purchase Dict + # TODO: Should get any value? + purchase_values = purchase._prepare_invoice() + + # Fields to Join + # origins.add(purchase_values["invoice_origin"]) + payment_refs.add(purchase_values["payment_reference"]) + refs.add(purchase_values["ref"]) + narration.add(purchase_values["narration"]) + + # Original dict from purchase module. + + # Fields to get from original dict: + # - "ref": self.partner_ref or "", + # - "narration": self.notes, + # - "currency_id": self.currency_id.id, + # - "invoice_user_id": self.user_id and self.user_id.id + # or self.env.user.id, + # - "payment_reference": self.partner_ref or "", + # - "partner_bank_id": partner_bank_id.id, + # - "invoice_payment_term_id": self.payment_term_id.id, + + # Fields to remove from Original Dict + vals_to_remove = { + "move_type", + "partner_id", + "fiscal_position_id", + "invoice_origin", + "invoice_line_ids", + "company_id", + # Another fields + "__last_update", + "display_name", + } + + purchase_values_rm = { + k: purchase_values[k] for k in set(purchase_values) - vals_to_remove + } + values.update(purchase_values_rm) + + # Fields to join + if len(purchase_pickings) > 1: values.update( - {"invoice_payment_term_id": pick.purchase_id.payment_term_id.id} + { + "ref": ", ".join(refs)[:2000], + # In this case Origin get Pickings Names + # "invoice_origin": ", ".join(origins), + "payment_reference": len(payment_refs) == 1 + and payment_refs.pop() + or False, + "narration": ", ".join(narration), + } ) return invoice, values @@ -40,8 +100,7 @@ def _get_move_key(self, move): """ key = super()._get_move_key(move) if move.purchase_line_id: - # TODO: deveria permitir agrupar as linhas ? - # Deveria permitir agrupar Pedidos de Compras ? + # Field purchase_line_id in account.move is Many2one key = key + (move.purchase_line_id,) return key @@ -57,16 +116,45 @@ def _get_invoice_line_values(self, moves, invoice_values, invoice): values = super()._get_invoice_line_values(moves, invoice_values, invoice) # Devido ao KEY com purchase_line_id aqui # vem somente um registro - if len(moves) == 1: - # Caso venha apenas uma linha porem sem - # purchase_line_id é preciso ignora-la - if moves.purchase_line_id: - values["purchase_line_id"] = moves.purchase_line_id.id - values[ - "analytic_account_id" - ] = moves.purchase_line_id.account_analytic_id.id - values["analytic_tag_ids"] = [ - (6, 0, moves.purchase_line_id.analytic_tag_ids.ids) - ] + purchase_moves = moves.filtered(lambda ln: ln.purchase_line_id) + if purchase_moves: + purchase_line = purchase_moves.purchase_line_id + # Campos informados em qualquer caso + values["purchase_line_id"] = purchase_line.id + values["analytic_account_id"] = purchase_line.account_analytic_id.id + values["analytic_tag_ids"] = [(6, 0, purchase_line.analytic_tag_ids.ids)] + + # Refund case don't get values from Purchase Line Dict + # TODO: Should get any value? + if self._get_invoice_type() != "in_refund": + # Same make above, get fields informed in + # original of Purchase Line dict: + purchase_line_values = purchase_line._prepare_account_move_line() + + # Fields to get: + # "display_type": self.display_type, + # "sequence": self.sequence, + + # Fields to remove: + vals_to_remove = { + "name", + "product_id", + "product_uom_id", + "quantity", + "price_unit", + "tax_ids", + "analytic_account_id", + "analytic_tag_ids", + "purchase_line_id", + # another fields + "__last_update", + "display_name", + } + + purchase_line_values_rm = { + k: purchase_line_values[k] + for k in set(purchase_line_values) - vals_to_remove + } + values.update(purchase_line_values_rm) return values From 5afbaacdd31dc48167c7278e124613de48bea27e Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Thu, 22 Aug 2024 14:34:45 -0300 Subject: [PATCH 2/2] [IMP] l10n_br_purchase_stock: Line Section/Note --- .../demo/purchase_order.xml | 80 +++++++++++++++++++ .../tests/test_l10n_br_purchase_stock.py | 15 +++- .../wizards/stock_invocing_onshipping.py | 64 +++++++++++++++ 3 files changed, 157 insertions(+), 2 deletions(-) diff --git a/l10n_br_purchase_stock/demo/purchase_order.xml b/l10n_br_purchase_stock/demo/purchase_order.xml index 2f47face97aa..d5760ef7634d 100644 --- a/l10n_br_purchase_stock/demo/purchase_order.xml +++ b/l10n_br_purchase_stock/demo/purchase_order.xml @@ -97,6 +97,46 @@ + + + + TEST SECTION 1 + line_section + 0 + + + + + + TEST NOTE 1 + line_note + 0 + + + + + + 2 + + 500 + + + + 999999 + 003 + Teste - Additional Data + 10 + 10 + 10 + + + + + + Main l10n_br_purchase_stock - teste agrupamento @@ -170,6 +210,46 @@ + + + + TEST SECTION 2 + line_section + 0 + + + + + + TEST NOTE 2 + line_note + 0 + + + + + + 2 + + 500 + + + + 999999 + 003 + Teste - Additional Data + 10 + 10 + 10 + + + + + + diff --git a/l10n_br_purchase_stock/tests/test_l10n_br_purchase_stock.py b/l10n_br_purchase_stock/tests/test_l10n_br_purchase_stock.py index d336f63b2034..fce9ffbb07ea 100644 --- a/l10n_br_purchase_stock/tests/test_l10n_br_purchase_stock.py +++ b/l10n_br_purchase_stock/tests/test_l10n_br_purchase_stock.py @@ -52,7 +52,7 @@ def test_grouping_pickings(self): self.assertIn(picking_2, invoice.picking_ids) # Validar o price_unit usado - for inv_line in invoice.invoice_line_ids: + for inv_line in invoice.invoice_line_ids.filtered(lambda ln: ln.product_id): # TODO: A forma de instalação dos modulos feita no CI # falha o browse aqui # l10n_br_stock_account/models/stock_invoice_onshipping.py:105 @@ -72,6 +72,17 @@ def test_grouping_pickings(self): inv_line.fiscal_operation_line_id, "Missing Fiscal Operation Line." ) + # Section Lines + section_lines = invoice.invoice_line_ids.filtered( + lambda ln: ln.display_type == "line_section" + ) + self.assertEqual(len(section_lines), 2) + # Note Lines + note_lines = invoice.invoice_line_ids.filtered( + lambda ln: ln.display_type == "line_note" + ) + self.assertEqual(len(note_lines), 2) + if hasattr(invoice, "document_serie"): invoice.document_serie = "1" invoice.document_number = "123" @@ -150,7 +161,7 @@ def test_purchase_order_lucro_presumido(self): invoice = self.create_invoice_wizard(picking) # Validar o price_unit usado - for inv_line in invoice.invoice_line_ids: + for inv_line in invoice.invoice_line_ids.filtered(lambda ln: ln.product_id): # TODO: A forma de instalação dos modulos feita no CI # falha o browse aqui # l10n_br_stock_account/models/stock_invoice_onshipping.py:105 diff --git a/l10n_br_purchase_stock/wizards/stock_invocing_onshipping.py b/l10n_br_purchase_stock/wizards/stock_invocing_onshipping.py index fc8f86f1b24b..491d19ffffe4 100644 --- a/l10n_br_purchase_stock/wizards/stock_invocing_onshipping.py +++ b/l10n_br_purchase_stock/wizards/stock_invocing_onshipping.py @@ -158,3 +158,67 @@ def _get_invoice_line_values(self, moves, invoice_values, invoice): values.update(purchase_line_values_rm) return values + + def _create_invoice(self, invoice_values): + """Override this method if you need to change any values of the + invoice and the lines before the invoice creation + :param invoice_values: dict with the invoice and its lines + :return: invoice + """ + purchase = self.env["purchase.order"].browse(invoice_values.get("purchase_id")) + pickings = self._load_pickings() + purchase_pickings = pickings.filtered(lambda pk: pk.purchase_id) + if not purchase_pickings or self._get_invoice_type() == "in_refund": + return super()._create_invoice(invoice_values) + + # Check Other Purchase Lines + section_note_lines = self.env["purchase.order.line"] + # Resequencing + invoice_item_sequence = 10 + invoice_item_seq_dict = {} + for picking in purchase_pickings.sorted(key=lambda p: p.name): + purchase = picking.purchase_id + # Resequencing + for line in purchase.order_line: + invoice_item_seq_dict[line.id] = invoice_item_sequence + invoice_item_sequence += 1 + + # Section and Note Lines + section_note_lines |= purchase.order_line.filtered( + lambda ln: ln.display_type in ("line_section", "line_note") + ) + + for line in section_note_lines: + line_vals = line._prepare_account_move_line() + invoice_values["invoice_line_ids"].append((0, 0, line_vals)) + + # Resequence + for ln in invoice_values["invoice_line_ids"]: + if ln[0] != 5: + if ln[2] and ln[2].get("purchase_line_id"): + ln[2].update( + { + "sequence": invoice_item_seq_dict.get( + ln[2].get("purchase_line_id") + ) + } + ) + + # 3) Create invoices. + moves = self.env["account.move"] + AccountMove = self.env["account.move"].with_context( + default_move_type="in_invoice" + ) + # for vals in invoice_vals_list: + moves |= AccountMove.with_company(self.env.company).create(invoice_values) + + # 4) Some moves might actually be refunds: convert them if the + # total amount is negative + # We do this after the moves have been created since we need taxes, + # etc. to know if the total + # is actually negative or not + moves.filtered( + lambda m: m.currency_id.round(m.amount_total) < 0 + ).action_switch_invoice_into_refund_credit_note() + + return moves