From a748ccecc5eba830ca4418121c7882bdd3b341a3 Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Tue, 9 Jan 2024 11:55:10 -0300 Subject: [PATCH 01/11] [FIX] l10n_br_fiscal: proper partner_id When mapping the Line Fiscal Operation and Taxes the Partner of the object can be or not the Partner to Invoice/Fiscal. [FIX] l10n_br_fiscal: Document get Fiscal Partner --- .../models/document_line_mixin_methods.py | 19 +++++++++++++++---- .../models/document_mixin_methods.py | 18 +++++++++++++++--- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/l10n_br_fiscal/models/document_line_mixin_methods.py b/l10n_br_fiscal/models/document_line_mixin_methods.py index 47720e6c7a78..4b64e7e26e3a 100644 --- a/l10n_br_fiscal/models/document_line_mixin_methods.py +++ b/l10n_br_fiscal/models/document_line_mixin_methods.py @@ -47,7 +47,7 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel): _name = "l10n_br_fiscal.document.line.mixin.methods" - _description = "Document Fiscal Mixin Methods" + _description = "Fiscal Document Mixin Methods" @api.model def inject_fiscal_fields( @@ -190,7 +190,7 @@ def _compute_taxes(self, taxes, cst=None): self.ensure_one() return taxes.compute_taxes( company=self.company_id, - partner=self.partner_id, + partner=self._get_fiscal_partner(), product=self.product_id, price_unit=self.price_unit, quantity=self.quantity, @@ -355,6 +355,17 @@ def _document_comment(self): d.__document_comment_vals(), d.manual_additional_data ) + def _get_fiscal_partner(self): + """ + Meant to be overriden when the l10n_br_fiscal.document partner_id should not + be the same as the sale.order, purchase.order, account.move (...) partner_id. + + (In the case of invoicing, the invoicing partner set by the user should + get priority over any invoicing contact returned by address_get.) + """ + self.ensure_one() + return self.partner_id + @api.onchange("fiscal_operation_id") def _onchange_fiscal_operation_id(self): if self.fiscal_operation_id: @@ -363,7 +374,7 @@ def _onchange_fiscal_operation_id(self): self._onchange_commercial_quantity() self.fiscal_operation_line_id = self.fiscal_operation_id.line_definition( company=self.company_id, - partner=self.partner_id, + partner=self._get_fiscal_partner(), product=self.product_id, ) self._onchange_fiscal_operation_line_id() @@ -375,7 +386,7 @@ def _onchange_fiscal_operation_line_id(self): if self.fiscal_operation_line_id: mapping_result = self.fiscal_operation_line_id.map_fiscal_taxes( company=self.company_id, - partner=self.partner_id, + partner=self._get_fiscal_partner(), product=self.product_id, ncm=self.ncm_id, nbm=self.nbm_id, diff --git a/l10n_br_fiscal/models/document_mixin_methods.py b/l10n_br_fiscal/models/document_mixin_methods.py index cae500ba1a0f..eec037c42543 100644 --- a/l10n_br_fiscal/models/document_mixin_methods.py +++ b/l10n_br_fiscal/models/document_mixin_methods.py @@ -8,7 +8,7 @@ class FiscalDocumentMixinMethods(models.AbstractModel): _name = "l10n_br_fiscal.document.mixin.methods" - _description = "Document Fiscal Mixin Methods" + _description = "Fiscal Document Mixin Methods" def _prepare_br_fiscal_dict(self, default=False): self.ensure_one() @@ -92,10 +92,22 @@ def _document_comment(self): ) d.fiscal_line_ids._document_comment() + def _get_fiscal_partner(self): + """ + Meant to be overriden when the l10n_br_fiscal.document partner_id should not + be the same as the sale.order, purchase.order, account.move (...) partner_id. + + (In the case of invoicing, the invoicing partner set by the user should + get priority over any invoicing contact returned by address_get.) + """ + self.ensure_one() + return self.partner_id + @api.onchange("partner_id") def _onchange_partner_id_fiscal(self): - if self.partner_id: - self.ind_final = self.partner_id.ind_final + partner = self._get_fiscal_partner() + if partner: + self.ind_final = partner.ind_final for line in self._get_amount_lines(): # reload fiscal data, operation line, cfop, taxes, etc. line._onchange_fiscal_operation_id() From c04f3b4ba99c10bc664a5b0cae5e38807b7c50d4 Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Tue, 13 Aug 2024 17:02:51 -0300 Subject: [PATCH 02/11] [FIX] l10n_br_purchase: proper partner_id When mapping the Line Fiscal Operation and Taxes the Partner of the object can be or not the Partner to Invoice, in the Purchase case the code use address_get to create Invoice. [FIX] l10n_br_purchase: Document get Fiscal Partner --- l10n_br_purchase/models/purchase_order.py | 9 +++++++++ l10n_br_purchase/models/purchase_order_line.py | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/l10n_br_purchase/models/purchase_order.py b/l10n_br_purchase/models/purchase_order.py index f93500ba25c3..1a71866a9227 100644 --- a/l10n_br_purchase/models/purchase_order.py +++ b/l10n_br_purchase/models/purchase_order.py @@ -116,3 +116,12 @@ def _prepare_invoice(self): } ) return invoice_vals + + def _get_fiscal_partner(self): + self.ensure_one() + partner = super()._get_fiscal_partner() + if partner.id != partner.address_get(["invoice"]).get("invoice"): + partner = self.env["res.partner"].browse( + partner.address_get(["invoice"]).get("invoice") + ) + return partner diff --git a/l10n_br_purchase/models/purchase_order_line.py b/l10n_br_purchase/models/purchase_order_line.py index d5bb79834139..08d1d2e4e9c4 100644 --- a/l10n_br_purchase/models/purchase_order_line.py +++ b/l10n_br_purchase/models/purchase_order_line.py @@ -145,3 +145,12 @@ def _prepare_account_move_line(self, move=False): values.update(fiscal_values) return values + + def _get_fiscal_partner(self): + self.ensure_one() + partner = super()._get_fiscal_partner() + if partner.id != partner.address_get(["invoice"]).get("invoice"): + partner = self.env["res.partner"].browse( + partner.address_get(["invoice"]).get("invoice") + ) + return partner From cc659d2e730f849838d3ad48816b093607fd62ad Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Tue, 13 Aug 2024 17:06:21 -0300 Subject: [PATCH 03/11] [IMP] l10n_br_purchase: demo data and tests for the cases where the Partner of the object can be or not the Partner to Invoice. --- l10n_br_purchase/demo/l10n_br_purchase.xml | 232 ++++++++++++++++++ .../tests/test_l10n_br_purchase.py | 137 +++++++++++ 2 files changed, 369 insertions(+) diff --git a/l10n_br_purchase/demo/l10n_br_purchase.xml b/l10n_br_purchase/demo/l10n_br_purchase.xml index e6c7ba10a6c8..cd053341867d 100644 --- a/l10n_br_purchase/demo/l10n_br_purchase.xml +++ b/l10n_br_purchase/demo/l10n_br_purchase.xml @@ -55,6 +55,238 @@ + + + l10n_br_purchase - Endereço de Cobrança Diferente + + draft + + + + + + + + + + + + Office Chair Black + + 4 + + + 25.0 + + + + + + + + + + + + Drawer Black + + 2 + + 50.00 + + + + + + + + + + + + l10n_br_purchase - Endereço de Cobrança Informado + + draft + + + + + + + + + + + + Office Chair Black + + 4 + + + 25.0 + + + + + + + + + + + + Drawer Black + + 2 + + 50.00 + + + + + + + + + + + + l10n_br_purchase - Contato do Endereço de Entrega Informado + + draft + + + + + + + + + + + + Office Chair Black + + 4 + + + 25.0 + + + + + + + + + + + + Drawer Black + + 2 + + 50.00 + + + + + + + + + + + + l10n_br_purchase - Partner que tem um Endereço de Entrega + + draft + + + + + + + + + + + + Office Chair Black + + 4 + + + 25.0 + + + + + + + + + + + + Drawer Black + + 2 + + 50.00 + + + + + + + + + diff --git a/l10n_br_purchase/tests/test_l10n_br_purchase.py b/l10n_br_purchase/tests/test_l10n_br_purchase.py index ad0e63d26cfc..afb3eeb44349 100644 --- a/l10n_br_purchase/tests/test_l10n_br_purchase.py +++ b/l10n_br_purchase/tests/test_l10n_br_purchase.py @@ -609,3 +609,140 @@ def test_compatible_with_international_case(self): invoice.fiscal_document_id, "International case should not has Fiscal Document.", ) + + def test_purchase_with_partner_to_invoice(self): + """ + Test Purchase Order with different Partner to Invoice. + """ + # Caso do Pedido criado com um Partner que possui um Partner to Invoice + purchase = self.env.ref("l10n_br_purchase.main_company-purchase_2") + self._run_purchase_order_onchanges(purchase) + for line in purchase.order_line: + self._run_purchase_line_onchanges(line) + purchase.with_context(tracking_disable=True).button_confirm() + self.assertEqual(purchase.state, "purchase", "Error to confirm Purchase Order.") + purchase.action_create_invoice() + for invoice in purchase.invoice_ids: + self.assertTrue( + invoice.fiscal_document_id, + "Fiscal Document missing for Purchase.", + ) + partner_invoice = self.env["res.partner"].browse( + purchase.partner_id.address_get(["invoice"]).get("invoice") + ) + self.assertEqual( + invoice.partner_id, + partner_invoice, + "The Invoice should be created with the Partner to Invoice", + ) + self.assertNotEqual( + invoice.partner_id, + purchase.partner_id, + "The Invoice should not be created with the Partner of Purchase.", + ) + + # TODO: Deveria criar a Fatura com o Endereço de Entrega + # partner_shipping_id preenchido com o Partner do Pedido + # de Compra? Quando criado pelo Picking o campo é preenchido. + + # self.assertEqual( + # invoice.partner_shipping_id, + # purchase.partner_id, + # "The Invoice should be created with Partner to Shipping.", + # ) + + purchase_2 = self.env.ref("l10n_br_purchase.main_company-purchase_3") + self._run_purchase_order_onchanges(purchase_2) + for line in purchase_2.order_line: + self._run_purchase_line_onchanges(line) + purchase_2.with_context(tracking_disable=True).button_confirm() + self.assertEqual( + purchase_2.state, "purchase", "Error to confirm Purchase Order." + ) + purchase_2.action_create_invoice() + for invoice in purchase_2.invoice_ids: + self.assertTrue( + invoice.fiscal_document_id, + "Fiscal Document missing for Purchase.", + ) + self.assertEqual( + invoice.partner_id, + purchase_2.partner_id, + "The Partner in Purchase and Invoice should be the same.", + ) + # O partner_shipping_id aqui também é vazio, deveria ser preenchido? + # self.assertEqual( + # invoice.partner_shipping_id, + # purchase_2.partner_id, + # "The Invoice should be created with Partner to Shipping.", + # ) + + def test_purchase_with_partner_to_shipping(self): + """Test brazilian Purchase Order with Partner to Shipping.""" + + # Caso do Pedido criado com o Contato de Entrega/Partner to Delivery + purchase = self.env.ref("l10n_br_purchase.main_company-purchase_4") + self._run_purchase_order_onchanges(purchase) + for line in purchase.order_line: + self._run_purchase_line_onchanges(line) + purchase.with_context(tracking_disable=True).button_confirm() + self.assertEqual(purchase.state, "purchase", "Error to confirm Purchase Order.") + purchase.action_create_invoice() + for invoice in purchase.invoice_ids: + self.assertTrue( + invoice.fiscal_document_id, + "Fiscal Document missing for Purchase.", + ) + # TODO: Campo partner_shipping_id está vazio + # deveria estar preenchido? + # self.assertEqual( + # invoice.partner_shipping_id, + # purchase.partner_id, + # "The Invoice should be created with the Partner to Shipping", + # ) + self.assertNotEqual( + invoice.partner_id, + purchase.partner_id, + "The Invoice should not be created with the Partner of Purchase.", + ) + # TODO: Deveria criar a Fatura com o Endereço de Entrega + # partner_shipping_id preenchido com o Partner do Pedido + # de Compra? Quando criado pelo Picking o campo é preenchido. + + # self.assertEqual( + # invoice.partner_shipping_id, + # purchase.partner_id, + # "The Invoice should be created with Partner to Shipping.", + # ) + + # Caso do Pedido criado com o Partner que tem um Contato de Entrega + purchase_2 = self.env.ref("l10n_br_purchase.main_company-purchase_5") + self._run_purchase_order_onchanges(purchase_2) + for line in purchase_2.order_line: + self._run_purchase_line_onchanges(line) + purchase_2.with_context(tracking_disable=True).button_confirm() + self.assertEqual( + purchase_2.state, "purchase", "Error to confirm Purchase Order." + ) + purchase_2.action_create_invoice() + + for invoice in purchase_2.invoice_ids: + self.assertTrue( + invoice.fiscal_document_id, + "Fiscal Document missing for Purchase.", + ) + self.assertEqual( + invoice.partner_id, + purchase_2.partner_id, + "The Partner in Purchase and Invoice should be the same.", + ) + # TODO: O partner_shipping_id aqui também é vazio, + # deveria ser preenchido? + # partner_delivery = self.env["res.partner"].browse( + # purchase.partner_id.address_get(["delivery"]).get("delivery") + # ) + # self.assertEqual( + # invoice.partner_shipping_id, + # partner_delivery, + # "The Invoice should be created with Partner to Shipping.", + # ) From 27f03241a810391ff35285e3676fc775c413ed58 Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Tue, 9 Jan 2024 12:07:32 -0300 Subject: [PATCH 04/11] [IMP] l10n_br_purchase_stock: partner_id tests for the case of mapping Line Fiscal Operation when Partner of the object is not Partner to Invoice or Shipping. --- .../tests/test_l10n_br_purchase_stock.py | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) 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 ac3e2a4a2ba4..dced6833a7ac 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 @@ -264,3 +264,185 @@ def test_compatible_with_international_case(self): invoice_devolution.fiscal_document_id, "International case should not has Fiscal Document.", ) + + def test_purchase_with_partner_to_invoice(self): + """ + Test Purchase Order with different Partner to Invoice. + """ + # Caso do Pedido criado com um Partner que possui um Partner to Invoice + purchase = self.env.ref("l10n_br_purchase.main_company-purchase_2") + purchase.with_context(tracking_disable=True).button_confirm() + self.assertEqual(purchase.state, "purchase", "Error to confirm Purchase Order.") + + picking = purchase.picking_ids + picking.set_to_be_invoiced() + self.assertEqual( + picking.invoice_state, "2binvoiced", "Error to inform Invoice State." + ) + + self.picking_move_state(picking) + self.assertEqual(picking.state, "done") + + self.assertEqual( + purchase.invoice_status, + "to invoice", + "Error in compute field invoice_status," + " before create invoice by Picking.", + ) + invoice = self.create_invoice_wizard(picking) + invoice.action_post() + + self.assertTrue( + invoice.fiscal_document_id, + "Fiscal Document missing for Purchase.", + ) + partner_invoice = self.env["res.partner"].browse( + purchase.partner_id.address_get(["invoice"]).get("invoice") + ) + self.assertEqual( + invoice.partner_id, + partner_invoice, + "The Invoice should be created with the Partner to Invoice", + ) + self.assertNotEqual( + invoice.partner_id, + purchase.partner_id, + "The Invoice should be created with the Partner to Invoice", + ) + # TODO: No l10n_br_purchase quando a Fatura é criada pelo + # Pedido de Compra, deveria criar a Fatura com o + # Endereço de Entrega/partner_shipping_id preenchido com o + # Partner do Pedido como ocorre aqui? + self.assertEqual( + invoice.partner_shipping_id, + picking.partner_id, + "The Invoice should be created with Partner to Shipping.", + ) + + # Caso onde o Pedido é criado com o Partner to Invoice + purchase_2 = self.env.ref("l10n_br_purchase.main_company-purchase_3") + purchase_2.with_context(tracking_disable=True).button_confirm() + self.assertEqual( + purchase_2.state, "purchase", "Error to confirm Purchase Order." + ) + + picking_2 = purchase_2.picking_ids + picking_2.set_to_be_invoiced() + + self.picking_move_state(picking_2) + self.assertEqual(picking.state, "done") + self.assertEqual( + picking_2.invoice_state, "2binvoiced", "Error to inform Invoice State." + ) + self.assertEqual( + purchase_2.invoice_status, + "to invoice", + "Error in compute field invoice_status," + " before create invoice by Picking.", + ) + + invoice = self.create_invoice_wizard(picking_2) + invoice.action_post() + + self.assertEqual( + invoice.partner_id, + purchase_2.partner_id, + "The Partner in Purchase and Invoice should be the same.", + ) + self.assertEqual( + invoice.partner_shipping_id, + picking_2.partner_id, + "The Invoice should be created with Partner to Shipping.", + ) + self.assertEqual( + invoice.partner_shipping_id, + invoice.partner_id, + "The Invoice Partner and Partner to Shipping should be the same.", + ) + + def test_purchase_with_partner_to_shipping(self): + """Test brazilian Purchase Order with Partner to Shipping.""" + + # Caso do Pedido criado com o Contato de Entrega/Partner to Delivery + purchase = self.env.ref("l10n_br_purchase.main_company-purchase_4") + purchase.with_context(tracking_disable=True).button_confirm() + self.assertEqual(purchase.state, "purchase", "Error to confirm Purchase Order.") + + picking = purchase.picking_ids + picking.set_to_be_invoiced() + self.assertEqual( + picking.invoice_state, "2binvoiced", "Error to inform Invoice State." + ) + self.picking_move_state(picking) + self.assertEqual(picking.state, "done") + invoice = self.create_invoice_wizard(picking) + invoice.action_post() + + self.assertTrue( + invoice.fiscal_document_id, + "Fiscal Document missing for Purchase.", + ) + partner_delivery = self.env["res.partner"].browse( + purchase.partner_id.address_get(["delivery"]).get("delivery") + ) + self.assertEqual( + invoice.partner_shipping_id, + partner_delivery, + "The Invoice should be created with the Partner to Delivery", + ) + self.assertNotEqual( + invoice.partner_id, + purchase.partner_id, + "The Invoice should be created with the Partner to Invoice", + ) + # TODO: No l10n_br_purchase quando a Fatura é criada pelo + # Pedido de Compra, deveria criar a Fatura com o + # Endereço de Entrega/partner_shipping_id preenchido com o + # Partner do Pedido como ocorre aqui? + self.assertEqual( + invoice.partner_shipping_id, + picking.partner_id, + "The Invoice should be created with Partner to Shipping.", + ) + + # Caso do Pedido com um Partner que tem um contato como endereço de entrega + purchase_2 = self.env.ref("l10n_br_purchase.main_company-purchase_5") + purchase_2.with_context(tracking_disable=True).button_confirm() + self.assertEqual( + purchase_2.state, "purchase", "Error to confirm Purchase Order." + ) + + picking_2 = purchase_2.picking_ids + picking_2.set_to_be_invoiced() + + self.assertEqual( + picking_2.invoice_state, "2binvoiced", "Error to inform Invoice State." + ) + + self.picking_move_state(picking_2) + self.assertEqual(picking.state, "done") + self.assertEqual( + purchase_2.invoice_status, + "to invoice", + "Error in compute field invoice_status," + " before create invoice by Picking.", + ) + + invoice = self.create_invoice_wizard(picking_2) + invoice.action_post() + + self.assertEqual( + invoice.partner_id, + purchase_2.partner_id, + "The Partner in Purchase and Invoice should be the same.", + ) + self.assertEqual( + invoice.partner_shipping_id, + picking_2.partner_id, + "The Invoice should be created with Partner to Shipping.", + ) + self.assertEqual( + invoice.partner_shipping_id, + invoice.partner_id, + "The Invoice Partner and Partner to Shipping should be the same.", + ) From 1afab81ac85f8dd049f39c388e706deba3d1ba80 Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Tue, 9 Jan 2024 12:09:30 -0300 Subject: [PATCH 05/11] [FIX] l10n_br_sale: proper partner_id Invoice should be create with partner_invoice_id not with partner_id. [FIX] l10n_br_sale: Document get Fiscal Partner [FIX] l10n_br_sale: Related Fiscal Partner [FIX] l10n_br_sale: partner_invoice_id fix When mapping the Line Fiscal Operation and Taxes the Partner of the object can be or not the Partner to Invoice, in case of Sale should it use the field partner_invoice_id. --- l10n_br_sale/models/sale_order.py | 28 +++++++++++++++++++------- l10n_br_sale/models/sale_order_line.py | 13 ++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/l10n_br_sale/models/sale_order.py b/l10n_br_sale/models/sale_order.py index d1eab916767f..98ce12dc2ae5 100644 --- a/l10n_br_sale/models/sale_order.py +++ b/l10n_br_sale/models/sale_order.py @@ -48,17 +48,17 @@ def _fiscal_operation_domain(self): cnpj_cpf = fields.Char( string="CNPJ/CPF", - related="partner_id.cnpj_cpf", + related="partner_invoice_id.cnpj_cpf", ) legal_name = fields.Char( string="Legal Name", - related="partner_id.legal_name", + related="partner_invoice_id.legal_name", ) ie = fields.Char( string="State Tax Number/RG", - related="partner_id.inscr_est", + related="partner_invoice_id.inscr_est", ) discount_rate = fields.Float( @@ -195,12 +195,15 @@ def _create_invoices(self, grouped=False, final=False, date=None): def _prepare_invoice(self): self.ensure_one() result = super()._prepare_invoice() - # O caso Brasil se caracteriza por ter a Operação Fiscal - if self.fiscal_operation_id: - result.update(self._prepare_br_fiscal_dict()) + if self.fiscal_operation_id: # (Brazil) + fiscal_values = self._prepare_br_fiscal_dict() + # unlike super()._prepare_invoice(), prepare_fiscal_dict doesn't consider + # partner_invoice_id, so we adjust the partner_id eventually: + if fiscal_values.get("partner_id") != result.get("partner_id"): + fiscal_values["partner_id"] = result.get("partner_id") + result.update(fiscal_values) document_type_id = self._context.get("document_type_id") - if not document_type_id: # Quando ocorre esse caso? Os Testes não estão passando aqui document_type_id = self.company_id.document_type_id.id @@ -255,3 +258,14 @@ def _amount_by_group(self): ) for line in res ] + + def _get_fiscal_partner(self): + self.ensure_one() + partner = super()._get_fiscal_partner() + # Caso Vendas, a prioridade é do campo informado pelo usuário, quando + # o Partner tem um contato definido como Tipo Invoice o campo + # partner_invoice_id é preenchido com esse valor automaticamente + if partner != self.partner_invoice_id: + partner = self.partner_invoice_id + + return partner diff --git a/l10n_br_sale/models/sale_order_line.py b/l10n_br_sale/models/sale_order_line.py index cebf0d526199..78bd50f97a78 100644 --- a/l10n_br_sale/models/sale_order_line.py +++ b/l10n_br_sale/models/sale_order_line.py @@ -284,3 +284,16 @@ def _get_product_price(self): self.price_unit = self.product_id.standard_price else: self.price_unit = 0.00 + + def _get_fiscal_partner(self): + """ + Return partner_invoice_id if different from partner. + partner_invoice_id is auto filled when the partner + has some invoicing contact defined, but it can be changed. + """ + self.ensure_one() + partner = super()._get_fiscal_partner() + if partner != self.order_id.partner_invoice_id: + partner = self.order_id.partner_invoice_id + + return partner From 8e60ff7f958bb6a2df153e994074811267ccef2e Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Tue, 13 Aug 2024 17:23:24 -0300 Subject: [PATCH 06/11] [IMP] l10n_br_sale: demo data and tests for the case where the Partner of the object can be or not the Partner to Invoice. --- l10n_br_sale/demo/l10n_br_sale.xml | 102 ++++++++++++++++++++++++ l10n_br_sale/tests/test_l10n_br_sale.py | 41 ++++++++++ 2 files changed, 143 insertions(+) diff --git a/l10n_br_sale/demo/l10n_br_sale.xml b/l10n_br_sale/demo/l10n_br_sale.xml index 9bf6c55891a7..4a2476f870f9 100644 --- a/l10n_br_sale/demo/l10n_br_sale.xml +++ b/l10n_br_sale/demo/l10n_br_sale.xml @@ -52,6 +52,108 @@ + + + l10n_br_sale - Endereço de Cobrança + + + + + + + draft + + TESTE + + + + + + Laptop Customized + + 2 + + + 500 + out + + + + + + + + + + + Mouse, Wireless + + 2 + + 500 + out + + + + + + + + + + + l10n_br_sale - Endereço de Entrega + + + + + + + draft + + TESTE + + + + + + Laptop Customized + + 2 + + + 500 + out + + + + + + + + + + + Mouse, Wireless + + 2 + + 500 + out + + + + + + + + Main l10n_br_sale - Serviços diff --git a/l10n_br_sale/tests/test_l10n_br_sale.py b/l10n_br_sale/tests/test_l10n_br_sale.py index 38317ccf7579..338ee0dd42ef 100644 --- a/l10n_br_sale/tests/test_l10n_br_sale.py +++ b/l10n_br_sale/tests/test_l10n_br_sale.py @@ -642,3 +642,44 @@ def test_compatible_with_international_case(self): invoice.fiscal_document_id, "International case should not has Fiscal Document.", ) + + def test_sale_with_partner_to_invoice(self): + """Test brazilian Sale Order with Partner to Invoice.""" + sale = self.env.ref("l10n_br_sale.main_company-sale_2") + self._run_sale_order_onchanges(sale) + for line in sale.order_line: + self._run_sale_line_onchanges(line) + + self._invoice_sale_order(sale) + for invoice in sale.invoice_ids: + self.assertEqual( + invoice.partner_id, + sale.partner_invoice_id, + "The Invoice Partner should be the same as Partner " + "to Invoice in Sale Order.", + ) + self.assertNotEqual( + invoice.partner_id, + sale.partner_id, + "The Invoice should not be created with the Partner of Sale.", + ) + self.assertEqual( + invoice.partner_shipping_id, + sale.partner_shipping_id, + "The Invoice Partner to Shipping should be the same of Sale Order.", + ) + + def test_sale_with_partner_to_shipping(self): + """Test brazilian Sale Order with Partner to Shipping.""" + sale = self.env.ref("l10n_br_sale.main_company-sale_3") + self._run_sale_order_onchanges(sale) + for line in sale.order_line: + self._run_sale_line_onchanges(line) + + self._invoice_sale_order(sale) + for invoice in sale.invoice_ids: + self.assertEqual( + invoice.partner_shipping_id, + sale.partner_shipping_id, + "The Invoice Partner to Shipping should be the same of Sale Order.", + ) From cb1b0ddbe8c5bce06602b89c714aa0f89d0ef80c Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Tue, 9 Jan 2024 12:19:00 -0300 Subject: [PATCH 07/11] [FIX] l10n_br_sale_stock: proper partner_id When Picking has a Sale Order related the Partner used to create the Invoice should be the partner_invoice_id of Sale, because the Partner of Picking can be the partner_shipping_id of Sale Order. [FIX] l10n_br_sale_stock: Get Fiscal Partner --- l10n_br_sale_stock/models/stock_picking.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/l10n_br_sale_stock/models/stock_picking.py b/l10n_br_sale_stock/models/stock_picking.py index 562282467bd1..fe4a903cae74 100644 --- a/l10n_br_sale_stock/models/stock_picking.py +++ b/l10n_br_sale_stock/models/stock_picking.py @@ -8,8 +8,21 @@ class StockPicking(models.Model): _inherit = "stock.picking" def _get_partner_to_invoice(self): + """ + If the partner has some invoicing contact defined + partner_invoice_id is auto filled, but it can also be changed. + partner_invoice_id is used if different from partner_id + """ self.ensure_one() - partner = self.partner_id + partner_id = super()._get_partner_to_invoice() if self.sale_id: - partner = self.sale_id.partner_invoice_id - return partner.address_get(["invoice"]).get("invoice") + if partner_id != self.sale_id.partner_invoice_id.id: + partner_id = self.sale_id.partner_invoice_id.id + return partner_id + + def _get_fiscal_partner(self): + self.ensure_one() + partner = super()._get_fiscal_partner() + if partner != self._get_partner_to_invoice(): + partner = self._get_partner_to_invoice() + return partner From 85d32ed13971ae0ae74f89127a27c9882a18a186 Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Tue, 9 Jan 2024 12:30:18 -0300 Subject: [PATCH 08/11] [FIX] l10n_br_sale_stock: partner_invoice_id fix When mapping the Line Fiscal Operation and Taxes the Partner of the object can be or not the Partner to Invoice, in case of Picking with related a related SO, it should use the partner_invoice_id field in Sale because the Partner of Picking can be the partner_shipping_id of the SO. --- l10n_br_sale_stock/models/stock_move.py | 16 +++++++++++++--- l10n_br_sale_stock/tests/test_sale_stock.py | 12 +++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/l10n_br_sale_stock/models/stock_move.py b/l10n_br_sale_stock/models/stock_move.py index ae65a20609aa..f3b03160f8d1 100644 --- a/l10n_br_sale_stock/models/stock_move.py +++ b/l10n_br_sale_stock/models/stock_move.py @@ -32,10 +32,20 @@ def _get_new_picking_values(self): values = self.sale_line_id.order_id._prepare_br_fiscal_dict() values.update(super()._get_new_picking_values()) - # O self pode conter mais de uma stock.move por isso a Operação Fiscal - # usada é a do cabeçalho do Pedido de Venda já que nas linhas podem ser - # usadas diferentes Operações Fiscais + # self is a recordset, possibly with different fiscal operations + # so we use the fiscal_opration from the SO for the picking: if fiscal_operation: values.update({"fiscal_operation_id": fiscal_operation.id}) return values + + def _get_fiscal_partner(self): + """ + Use partner_invoice_id if different from partner + """ + self.ensure_one() + partner = super()._get_fiscal_partner() + if self.sale_line_id: + if partner != self.sale_line_id.order_id.partner_invoice_id: + partner = self.sale_line_id.order_id.partner_invoice_id + return partner diff --git a/l10n_br_sale_stock/tests/test_sale_stock.py b/l10n_br_sale_stock/tests/test_sale_stock.py index 1e057d5d22be..670f0b489e29 100644 --- a/l10n_br_sale_stock/tests/test_sale_stock.py +++ b/l10n_br_sale_stock/tests/test_sale_stock.py @@ -379,8 +379,18 @@ def test_ungrouping_pickings_partner_shipping_different(self): self.assertEqual(invoice_pick_1.partner_id, sale_order_1.partner_invoice_id) # Fatura criada com o Partner Shipping usado no Picking self.assertEqual(invoice_pick_1.partner_shipping_id, picking.partner_id) + + # TODO: O processo de criação a partir de um Pedido de Venda vem + # preenchido o campo partner_shipping_id, isso deve ser mantido por + # ser considerado o padrão ou é melhor remover o partner_shipping_id + # quando o valor é igual ao partner_id? + # Fatura Agrupada, não deve ter o partner_shipping_id preenchido - invoice_pick_3_4 = invoices.filtered(lambda t: not t.partner_shipping_id) + # invoice_pick_3_4 = invoices.filtered(lambda t: not t.partner_shipping_id) + + invoice_pick_3_4 = invoices.filtered( + lambda t: t.partner_shipping_id == t.partner_id + ) self.assertIn(invoice_pick_3_4, picking3.invoice_ids) self.assertIn(invoice_pick_3_4, picking4.invoice_ids) From 9cb29a1b09b097eeb285952b3549ca24b82b148d Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Tue, 13 Aug 2024 17:34:25 -0300 Subject: [PATCH 09/11] [FIX] l10n_br_stock_account: proper partner_id When mapping the Line Fiscal Operation and Taxes the Partner of the object can be or not the Partner to Invoice, in the Picking case the code use address_get to create Invoice. [FIX] l10n_br_stock_account: Get Fiscal Partner --- l10n_br_stock_account/__manifest__.py | 2 ++ l10n_br_stock_account/models/stock_move.py | 19 +++++++++++++++++++ l10n_br_stock_account/models/stock_picking.py | 18 ++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/l10n_br_stock_account/__manifest__.py b/l10n_br_stock_account/__manifest__.py index 317c61c07f8c..bc85a5730096 100644 --- a/l10n_br_stock_account/__manifest__.py +++ b/l10n_br_stock_account/__manifest__.py @@ -3,10 +3,12 @@ { "name": "Brazilian Localization WMS Accounting", + "summary": "Invoice from Picking (nota fiscal de remessa) and other WMS overrides", "category": "Localisation", "license": "AGPL-3", "author": "Akretion, Odoo Community Association (OCA)", "website": "https://github.com/OCA/l10n-brazil", + "maintainers": ["renatonlima", "mbcosta"], "version": "14.0.3.9.2", "depends": [ "stock_account", diff --git a/l10n_br_stock_account/models/stock_move.py b/l10n_br_stock_account/models/stock_move.py index 784e4c30e3b7..9dac5a28ad0d 100644 --- a/l10n_br_stock_account/models/stock_move.py +++ b/l10n_br_stock_account/models/stock_move.py @@ -279,3 +279,22 @@ def _get_taxes(self, fiscal_position, inv_type): taxes = self.tax_ids return taxes + + def _get_fiscal_partner(self): + """ + Adjust the partner, both for an invoice from picking with + no related SO or PO, + https://github.com/OCA/account-invoicing/blob/14.0/ + stock_picking_invoicing/models/stock_picking.py#L38 + and also in the case of a picking originating from a PO: + https://github.com/OCA/OCB/blob/14.0/addons/purchase/ + models/purchase.py#L556 + """ + self.ensure_one() + partner = super()._get_fiscal_partner() + if partner.id != partner.address_get(["invoice"]).get("invoice"): + partner = self.env["res.partner"].browse( + partner.address_get(["invoice"]).get("invoice") + ) + + return partner diff --git a/l10n_br_stock_account/models/stock_picking.py b/l10n_br_stock_account/models/stock_picking.py index 254fad31126f..ebd8c9bd1ecf 100644 --- a/l10n_br_stock_account/models/stock_picking.py +++ b/l10n_br_stock_account/models/stock_picking.py @@ -121,3 +121,21 @@ def _generate_document_number(self): ) self.document_serie = self.document_serie_id.code self.document_number = self.document_serie_id.next_seq_number() + + def _get_fiscal_partner(self): + """ + Adjust the partner, both for an invoice from picking with + no related SO or PO, + https://github.com/OCA/account-invoicing/blob/14.0/ + stock_picking_invoicing/models/stock_picking.py#L38 + and also in the case of a picking originating from a PO: + https://github.com/OCA/OCB/blob/14.0/addons/purchase/ + models/purchase.py#L556 + """ + self.ensure_one() + partner = super()._get_fiscal_partner() + if partner.id != partner.address_get(["invoice"]).get("invoice"): + partner = self.env["res.partner"].browse( + partner.address_get(["invoice"]).get("invoice") + ) + return partner From 42015e70202c6414edae201bee38f077080a5a9b Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Tue, 13 Aug 2024 17:37:24 -0300 Subject: [PATCH 10/11] [IMP] l10n_br_stock_account: demo data and tests for the case where the Partner of the object can be or not the Partner to Invoice. --- .../demo/l10n_br_stock_account_demo.xml | 436 ++++++++++++++++++ .../tests/test_invoicing_picking.py | 51 +- 2 files changed, 480 insertions(+), 7 deletions(-) diff --git a/l10n_br_stock_account/demo/l10n_br_stock_account_demo.xml b/l10n_br_stock_account/demo/l10n_br_stock_account_demo.xml index f35b3bce527d..44ce8fb4f1f7 100644 --- a/l10n_br_stock_account/demo/l10n_br_stock_account_demo.xml +++ b/l10n_br_stock_account/demo/l10n_br_stock_account_demo.xml @@ -464,6 +464,442 @@ + + + + + + + + 2binvoiced + l10n_br_stock_account - Endereço Entrega diferente Faturamento 5 + + + + + + l10n_br_stock_account - Endereço entrega diferente Faturamento 5 + + + + 2 + + + + + 2binvoiced + + + + 100 + 100 + 100 + + + + + + + + l10n_br_stock_account - Endereço entrega diferente Faturamento 5 + + + + 2 + + + + + 2binvoiced + + + + 100 + 100 + 100 + + + + + + + + + l10n_br_stock_account - Endereço entrega diferente Faturamento 5 + + + + 2 + + + + + 2binvoiced + + + + 100 + 100 + 100 + + + + + + + + + + + + + 2binvoiced + l10n_br_stock_account - Endereço entrega diferente Faturamento 6 + + + + + + l10n_br_stock_account - Endereço entrega diferente Faturamento 6 + + + + 2 + + + + + 2binvoiced + + + + 100 + 100 + 100 + + + + + + + + l10n_br_stock_account - Endereço entrega diferente Faturamento 6 + + + + 2 + + + + + 2binvoiced + + + + 100 + 100 + 100 + + + + + + + + + l10n_br_stock_account - Endereço entrega diferente Faturamento 6 + + + + 2 + + + + + 2binvoiced + + + + 100 + 100 + 100 + + + + + + + + + + + + + + 2binvoiced + l10n_br_stock_account - Partner com Endereço de Faturamento 7 + + + + + + l10n_br_stock_account - Partner com Endereço de Faturamento 7 + + + + 2 + + + + + 2binvoiced + + + + 100 + 100 + 100 + + + + + + + + l10n_br_stock_account - Endereço entrega diferente Faturamento 7 + + + + 2 + + + + + 2binvoiced + + + + 100 + 100 + 100 + + + + + + + + + l10n_br_stock_account - Endereço entrega diferente Faturamento 7 + + + + 2 + + + + + 2binvoiced + + + + 100 + 100 + 100 + + + + + + + + + + + + + 2binvoiced + l10n_br_stock_account - Endereço entrega diferente Faturamento 8 + + + + + + l10n_br_stock_account - Endereço entrega diferente Faturamento 8 + + + + 2 + + + + + 2binvoiced + + + + 100 + 100 + 100 + + + + + + + + l10n_br_stock_account - Endereço entrega diferente Faturamento 8 + + + + 2 + + + + + 2binvoiced + + + + 100 + 100 + 100 + + + + + + + + + l10n_br_stock_account - Endereço entrega diferente Faturamento 8 + + + + 2 + + + + + 2binvoiced + + + + 100 + 100 + 100 + + + + + + diff --git a/l10n_br_stock_account/tests/test_invoicing_picking.py b/l10n_br_stock_account/tests/test_invoicing_picking.py index 1ccc53676d96..3e7060f5304f 100644 --- a/l10n_br_stock_account/tests/test_invoicing_picking.py +++ b/l10n_br_stock_account/tests/test_invoicing_picking.py @@ -194,13 +194,15 @@ def test_picking_invoicing_by_product3(self): invoice_pick_1 = invoicies.filtered( lambda t: t.partner_id == picking.partner_id ) - # TODO - está trazendo o mesmo Partner apesar de ser um endereço do - # de outro principal, o metodo address_get chamado pelo - # get_invoice_partner está trazendo o primeiro is_company. Isso - # significa que no caso de uso de ter um Picking para ser Faturado - # sem relação com um Pedido de Venda/Compras a opção de ter um - # Endereço de Entrega diferente do de Faturamento precirá ser - # feita manualmente na Fatura/Doc Fiscal criados. + # Nesse caso está trazendo o mesmo Partner apesar de ser um endereço + # de outro principal, isso acontece porque o metodo address_get chamado + # pelo get_invoice_partner traz o primeiro res.partner que tem o campo + # company_type definido como Company/empresa então para funcionar o caso + # de Endereço de Entrega diferente do Faturamento o res.partner do + # Endereço de Cobrança precisa estar com o campo company_type com + # Person/Pessoa e não Company/Empresa. + # TODO: A localização BR deveria sobreescrever o metodo address_get + # para ignorar o company_type? self.assertEqual(invoice_pick_1.partner_id, picking.partner_id) self.assertIn(invoice_pick_1, picking.invoice_ids) self.assertIn(picking, invoice_pick_1.picking_ids) @@ -230,6 +232,41 @@ def test_picking_invoicing_by_product3(self): # Should be equals because we delete the invoice self.assertEqual(nb_invoice_before, nb_invoice_after) + # Caso onde por ter no partner do Endereço de Faturamento o campo + # company_type com Person o address_get retorna esse partner e + # permite esse caso do Endereço de Entrega diferente de Faturamento + # TODO: avaliar se a localização deveria sobreescrever o metodo + # address_get para ignorar o campo company_type? + + # Caso onde o Partner tem o Endereço de Entrega definido com o + # company_type person, um Picking é criado com o Endereço de Entrega e + # outro com o Endereço Pincipal, hoje são criadas 2 Faturas as duas estão + # com o partner o Endereço Principal e o partner_shipping_id o + # Endereço de Entrega + # TODO: Nesse caso os Pickings deveriam ser agrupados e criado apenas uma + # Fatura? + # O Picking definido com um partner diferente do Endereço de entrega + # ( contato com o campo Type definido como delivery) deve criar a + # Fatura com o mesmo partner do Picking? + # Isso acontece porque o metodo _get_picking_key considera o partner + # do picking https://github.com/OCA/account-invoicing/blob/14.0/ + # stock_picking_invoicing/wizards/stock_invoice_onshipping.py#L316 + # é preciso avaliar se deve ser alterado na localização ou mesmo + # no modulo stock_picking_invoicing + picking_3 = self.env.ref("l10n_br_stock_account.main_company-picking_5") + self.picking_move_state(picking_3) + picking_4 = self.env.ref("l10n_br_stock_account.main_company-picking_6") + self.picking_move_state(picking_4) + pickings = picking_3 | picking_4 + invoices = self.create_invoice_wizard(pickings) + self.assertEqual(len(invoices), 2) + self.assertEqual(picking_3.invoice_state, "invoiced") + self.assertEqual(picking_4.invoice_state, "invoiced") + + # Caso Endereço de Fatura diferente do de Entrega + self.assertIn(picking_3.invoice_ids, invoices) + self.assertIn(picking_4.invoice_ids, invoices) + def test_picking_split(self): """Test Picking Split created with Fiscal Values.""" self._change_user_company(self.env.ref("base.main_company")) From 3efb527c38218a12467e979582788e96d566f0c0 Mon Sep 17 00:00:00 2001 From: Magno Costa Date: Tue, 9 Jan 2024 12:58:23 -0300 Subject: [PATCH 11/11] [FIX] l10n_br_stock_account: partner_shipping_id when creating an Invoice use the Partner mapping by the methods of module instead of Fiscal Dict and inform partner_shipping_id with Partner of Picking instead in the case of it's different of Partner to Invoice. --- .../wizards/stock_invoice_onshipping.py | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/l10n_br_stock_account/wizards/stock_invoice_onshipping.py b/l10n_br_stock_account/wizards/stock_invoice_onshipping.py index c82566949c9e..e902e2f6cacf 100644 --- a/l10n_br_stock_account/wizards/stock_invoice_onshipping.py +++ b/l10n_br_stock_account/wizards/stock_invoice_onshipping.py @@ -99,17 +99,34 @@ def _build_invoice_values_from_pickings(self, pickings): if picking.fiscal_operation_id and picking.fiscal_operation_id.journal_id: fiscal_vals["journal_id"] = picking.fiscal_operation_id.journal_id.id + # Necessário para o caso do Pedido de Compra criado com o Contato + # de Faturamento onde o Picking é criado com esse partner e está + # sendo necessário preencher o partner_shipping_id com esse partner + # do Picking, sem isso o uso o Partner principal do Contato + if values.get("partner_shipping_id") != values.get("partner_id"): + if not fiscal_vals.get("partner_shipping_id"): + fiscal_vals["partner_shipping_id"] = picking.partner_id.id + # Endereço de Entrega diferente do Endereço de Faturamento # so informado quando é diferente if fiscal_vals["partner_id"] != values["partner_id"]: values["partner_shipping_id"] = fiscal_vals["partner_id"] - else: - # Já no modulo stock_picking_invoicing o campo partner_shipping_id - # é informado mas para evitar ter a NFe com o Endereço de Entrega - # quando esse é o mesmo Endereço, esta sendo removido. - # TODO: Deveria ser informado mesmo quando é o mesmo? Isso não - # acontecia na v12. - del values["partner_shipping_id"] + # Necessário para manter o Partner mapeado pelo metodo + # https://github.com/OCA/account-invoicing/blob/14.0/ + # stock_picking_invoicing/models/stock_picking.py#L38 + fiscal_vals["partner_id"] = values["partner_id"] + + # TODO: Na criação da Fatura pelo Pedido de Venda o campo + # partner_shipping_id sempre vai preenchido, isso deve ser + # considerado o padrão? Ou deveria ser feito como antes aqui + # onde nos casos em que o partner_id é o mesmo que o partner_shipping_id + # o campo era apagado e assim a Fatura era criada sem o Endereço de + # Entrega + # else: + # # Já no modulo stock_picking_invoicing o campo partner_shipping_id + # # é informado mas para evitar ter a NFe com o Endereço de Entrega + # # quando esse é o mesmo Endereço, esta sendo removido. + # del values["partner_shipping_id"] # Ser for feito o update como abaixo o campo # fiscal_operation_id vai vazio