diff --git a/l10n_br_fiscal/__manifest__.py b/l10n_br_fiscal/__manifest__.py
index 48b7a2a466e4..8b66b61e9cdd 100644
--- a/l10n_br_fiscal/__manifest__.py
+++ b/l10n_br_fiscal/__manifest__.py
@@ -57,6 +57,7 @@
"views/ncm_view.xml",
"views/nbm_view.xml",
"views/nbs_view.xml",
+ "views/product_tag_view.xml",
"views/service_type_view.xml",
"views/cest_view.xml",
"views/product_genre_view.xml",
diff --git a/l10n_br_fiscal/data/operation_data.xml b/l10n_br_fiscal/data/operation_data.xml
index 9834da0c743c..52d6fb79451a 100644
--- a/l10n_br_fiscal/data/operation_data.xml
+++ b/l10n_br_fiscal/data/operation_data.xml
@@ -42,6 +42,109 @@
00
+
+ ST CFOP 6404
+
+
+ ST CFOP 6401
+
+
+
+
+
+
+
+
+ approved
+
+
+
+
+
+
+
+ approved
+
+
+
+
+ Venda ST contribuinte substituto
+ 1
+
+
+ approved
+ 04
+
+ icms
+
+
+
+
+
+ Revenda ST contribuinte substituto
+ 1
+
+
+ approved
+ 00
+
+
+
+
+
+
+
+
+ approved
+
+
+
+
+ Revenda ST contribuinte substituído
+ 1
+
+
+ approved
+ 00
+
+ icms
+
+
+
+
+
+ Revenda ST contribuinte substituído SN
+ 1
+
+
+ approved
+ 00
+
+
Venda não Contribuinte
diff --git a/l10n_br_fiscal/demo/fiscal_document_demo.xml b/l10n_br_fiscal/demo/fiscal_document_demo.xml
index dce66c4fe6ad..b76700a57fb3 100644
--- a/l10n_br_fiscal/demo/fiscal_document_demo.xml
+++ b/l10n_br_fiscal/demo/fiscal_document_demo.xml
@@ -22,7 +22,7 @@
out
-
+
@@ -285,7 +285,7 @@
/>
1
-
+ out
@@ -385,7 +385,7 @@
/>
1
-
+ out
@@ -946,5 +946,116 @@
+
+
+
+
+
+
+ 1
+
+
+ out
+
+
+
+
+ Teste ST
+
+
+ 100
+ 1
+ out
+
+
+
+
+
+
+
+
+
+
+ 100
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+ out
+
+
+
+
+ Teste ST
+
+
+ 100
+ 1
+ out
+
+
+
+
+
+
+
+
+
+
+ 100
+
+
+
+
+
+
+
+
+
diff --git a/l10n_br_fiscal/demo/product_demo.xml b/l10n_br_fiscal/demo/product_demo.xml
index da9bb80e4161..d68a609b6531 100644
--- a/l10n_br_fiscal/demo/product_demo.xml
+++ b/l10n_br_fiscal/demo/product_demo.xml
@@ -1978,4 +1978,126 @@
+
+
+ Office Lamp ST 6404
+ icms
+
+ 35.0
+ 40.0
+ consu
+ 0.01
+
+
+ FURN_8889
+
+
+
+
+
+
+
+
+
+ fiscal_type
+
+ 00
+ selection
+
+
+
+
+
+ icms_origin
+
+ 5
+ selection
+
+
+
+
+
+
+
+ Office Lamp ST 6401
+ icms
+
+ 35.0
+ 40.0
+ consu
+ 0.01
+
+
+ FURN_8880
+
+
+
+
+
+
+
+
+
+ fiscal_type
+
+ 04
+ selection
+
+
+
+
+
+ icms_origin
+
+ 5
+ selection
+
+
+
diff --git a/l10n_br_fiscal/models/__init__.py b/l10n_br_fiscal/models/__init__.py
index e0562968855e..3e487b7450de 100644
--- a/l10n_br_fiscal/models/__init__.py
+++ b/l10n_br_fiscal/models/__init__.py
@@ -20,6 +20,7 @@
from . import ncm
from . import nbm
from . import cest
+from . import product_tag
from . import tax_group
from . import tax
from . import tax_pis_cofins
diff --git a/l10n_br_fiscal/models/operation.py b/l10n_br_fiscal/models/operation.py
index afff7df9249b..34b95f2526c5 100644
--- a/l10n_br_fiscal/models/operation.py
+++ b/l10n_br_fiscal/models/operation.py
@@ -235,6 +235,12 @@ def _line_domain(self, company, partner, product):
("icms_origin", "=", False),
]
+ domain += [
+ "|",
+ ("product_fiscal_tag_ids", "in", product.operation_line_tag_ids.ids),
+ ("product_fiscal_tag_ids", "=", False),
+ ]
+
return domain
def line_definition(self, company, partner, product):
@@ -258,6 +264,7 @@ def score(line):
"product_type",
"tax_icms_or_issqn",
"icms_origin",
+ "product_fiscal_tag_ids",
]
return sum(1 for field in fields if getattr(line, field))
diff --git a/l10n_br_fiscal/models/operation_line.py b/l10n_br_fiscal/models/operation_line.py
index e2cde39e3b8b..9805b628b655 100644
--- a/l10n_br_fiscal/models/operation_line.py
+++ b/l10n_br_fiscal/models/operation_line.py
@@ -105,6 +105,14 @@ class OperationLine(models.Model):
selection=PRODUCT_FISCAL_TYPE, string="Product Fiscal Type"
)
+ product_fiscal_tag_ids = fields.Many2many(
+ comodel_name="l10n_br_fiscal.product.tag",
+ string="Fiscal Product Tags",
+ help="If enabled, only products that share a product tag with this line can "
+ "auto-select this operation line. When other factors are equal, a match will "
+ "be preferred over a line without this setting.",
+ )
+
company_tax_framework = fields.Selection(selection=TAX_FRAMEWORK)
add_to_amount = fields.Boolean(string="Add to Document Amount?", default=True)
diff --git a/l10n_br_fiscal/models/product_tag.py b/l10n_br_fiscal/models/product_tag.py
new file mode 100644
index 000000000000..77156293918d
--- /dev/null
+++ b/l10n_br_fiscal/models/product_tag.py
@@ -0,0 +1,19 @@
+# Copyright (C) 2024 Diego Paradeda - KMEE
+# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
+
+from odoo import _, fields, models
+
+
+class ProductTag(models.Model):
+ _name = "l10n_br_fiscal.product.tag"
+ _description = "Fiscal Product Tags"
+
+ name = fields.Char()
+
+ _sql_constraints = [
+ (
+ "fiscal_tag_name_uniq",
+ "unique (name)",
+ _("Fiscal Product Tag already exists with this code !"),
+ )
+ ]
diff --git a/l10n_br_fiscal/models/product_template.py b/l10n_br_fiscal/models/product_template.py
index 1214a494aeaf..17d135f18b09 100644
--- a/l10n_br_fiscal/models/product_template.py
+++ b/l10n_br_fiscal/models/product_template.py
@@ -67,6 +67,10 @@ def _get_default_ncm_id(self):
comodel_name="l10n_br_fiscal.product.genre", string="Fiscal Product Genre"
)
+ operation_line_tag_ids = fields.Many2many(
+ comodel_name="l10n_br_fiscal.product.tag", string="Operation Line Tags"
+ )
+
service_type_id = fields.Many2one(
comodel_name="l10n_br_fiscal.service.type",
string="Service Type LC 166",
diff --git a/l10n_br_fiscal/security/ir.model.access.csv b/l10n_br_fiscal/security/ir.model.access.csv
index 5242c8d02d2f..bced9e78b2ac 100644
--- a/l10n_br_fiscal/security/ir.model.access.csv
+++ b/l10n_br_fiscal/security/ir.model.access.csv
@@ -38,6 +38,9 @@
"l10n_br_fiscal_product_genre_user","Fiscal Fiscal Product Genre for User","model_l10n_br_fiscal_product_genre","l10n_br_fiscal.group_user",1,0,0,0
"l10n_br_fiscal_product_genre_manager","Fiscal Fiscal Product Genre for Manager","model_l10n_br_fiscal_product_genre","l10n_br_fiscal.group_manager",1,0,0,0
"l10n_br_fiscal_product_genre_maintenance","Fiscal Fiscal Product Genre for Maintenance","model_l10n_br_fiscal_product_genre","l10n_br_fiscal.group_data_maintenance",1,1,1,1
+"l10n_br_fiscal_product_tag_user","Fiscal Fiscal Product Tag for User","model_l10n_br_fiscal_product_tag","l10n_br_fiscal.group_user",1,0,0,0
+"l10n_br_fiscal_product_tag_manager","Fiscal Fiscal Product Tag for Manager","model_l10n_br_fiscal_product_tag","l10n_br_fiscal.group_manager",1,1,1,1
+"l10n_br_fiscal_product_tag_maintenance","Fiscal Fiscal Product Tag for Maintenance","model_l10n_br_fiscal_product_tag","l10n_br_fiscal.group_data_maintenance",1,1,1,1
"l10n_br_fiscal_document_type_user","Fiscal Document Type for User","model_l10n_br_fiscal_document_type","l10n_br_fiscal.group_user",1,0,0,0
"l10n_br_fiscal_document_type_manager","Fiscal Document Type for Manager","model_l10n_br_fiscal_document_type","l10n_br_fiscal.group_manager",1,0,0,0
"l10n_br_fiscal_document_type_maintenance","Fiscal Document Type for Maintenance","model_l10n_br_fiscal_document_type","l10n_br_fiscal.group_data_maintenance",1,1,1,1
diff --git a/l10n_br_fiscal/tests/test_fiscal_document_generic.py b/l10n_br_fiscal/tests/test_fiscal_document_generic.py
index 281ecbd945f4..0d73e2537255 100644
--- a/l10n_br_fiscal/tests/test_fiscal_document_generic.py
+++ b/l10n_br_fiscal/tests/test_fiscal_document_generic.py
@@ -15,6 +15,12 @@ def setUpClass(cls):
# Contribuinte
cls.nfe_same_state = cls.env.ref("l10n_br_fiscal.demo_nfe_same_state")
cls.nfe_other_state = cls.env.ref("l10n_br_fiscal.demo_nfe_other_state")
+ cls.nfe_other_state_st_6404 = cls.env.ref(
+ "l10n_br_fiscal.demo_nfe_other_state_st_6404"
+ )
+ cls.nfe_other_state_st_6401 = cls.env.ref(
+ "l10n_br_fiscal.demo_nfe_other_state_st_6401"
+ )
cls.nfe_not_taxpayer = cls.env.ref("l10n_br_fiscal.demo_nfe_nao_contribuinte")
cls.nfe_not_taxpayer_pf = cls.env.ref(
@@ -291,6 +297,96 @@ def test_nfe_other_state(self):
"from COFINS 3% for Venda de Contribuinte p/ Fora do Estado.",
)
+ def test_nfe_other_state_st_6404(self):
+ """Testing NFe in another state with tax substitution."""
+ empresa_lucro_presumido = self.env.ref("l10n_br_base.empresa_lucro_presumido")
+ self.nfe_other_state_st_6404._onchange_document_serie_id()
+ self.nfe_other_state_st_6404._onchange_fiscal_operation_id()
+
+ for line in self.nfe_other_state_st_6404.fiscal_line_ids:
+ line.with_company(empresa_lucro_presumido.id)._onchange_product_id_fiscal()
+ line.with_company(
+ empresa_lucro_presumido.id
+ )._onchange_commercial_quantity()
+ line.with_company(empresa_lucro_presumido.id)._onchange_ncm_id()
+ line.with_company(
+ empresa_lucro_presumido.id
+ )._onchange_fiscal_operation_id()
+ line.with_company(
+ empresa_lucro_presumido.id
+ )._onchange_fiscal_operation_line_id()
+ line.with_company(empresa_lucro_presumido.id)._onchange_fiscal_taxes()
+
+ self.assertEqual(
+ line.cfop_id.code,
+ "6404",
+ "Error to mapping CFOP 6404"
+ " for Revenda de Contribuinte p/ Fora do Estado.",
+ )
+
+ # ICMS
+ line.with_company(empresa_lucro_presumido.id)._onchange_fiscal_taxes()
+ self.assertEqual(
+ line.icms_tax_id.id,
+ self.env.ref("l10n_br_fiscal.tax_icms_antst").id,
+ "Error to mapping ICMS Cobrado Ant. por ST"
+ " for Venda de Contribuinte p/ Fora do Estado.",
+ )
+ self.assertEqual(
+ line.icms_cst_id.code,
+ "60",
+ "Error to mapping CST 60 from ICMS Cobrado Ant. por ST"
+ " for Venda de Contribuinte p/ Fora do Estado.",
+ )
+
+ def test_nfe_other_state_st_6401(self):
+ """Testing NFe in another state with tax substitution."""
+ empresa_lucro_presumido = self.env.ref("l10n_br_base.empresa_lucro_presumido")
+ self.nfe_other_state_st_6401._onchange_document_serie_id()
+ self.nfe_other_state_st_6401._onchange_fiscal_operation_id()
+
+ for line in self.nfe_other_state_st_6401.fiscal_line_ids:
+ line.with_company(empresa_lucro_presumido.id)._onchange_product_id_fiscal()
+ line.with_company(
+ empresa_lucro_presumido.id
+ )._onchange_commercial_quantity()
+ line.with_company(empresa_lucro_presumido.id)._onchange_ncm_id()
+ line.with_company(
+ empresa_lucro_presumido.id
+ )._onchange_fiscal_operation_id()
+ line.with_company(
+ empresa_lucro_presumido.id
+ )._onchange_fiscal_operation_line_id()
+ line.with_company(empresa_lucro_presumido.id)._onchange_fiscal_taxes()
+
+ self.assertEqual(
+ line.cfop_id.code,
+ "6401",
+ "Error to mapping CFOP 6401"
+ " for Venda de Contribuinte p/ Fora do Estado.",
+ )
+
+ # ICMS
+ line.with_company(empresa_lucro_presumido.id)._onchange_fiscal_taxes()
+ self.assertEqual(
+ line.icms_tax_id.id,
+ self.env.ref("l10n_br_fiscal.tax_icms_12_st").id,
+ "Error to mapping ICMS 010 12%"
+ " for Venda de Contribuinte p/ Fora do Estado.",
+ )
+ self.assertEqual(
+ line.icmsst_tax_id.id,
+ self.env.ref("l10n_br_fiscal.tax_icmsst_p30_50").id,
+ "Error to mapping ICMS 30% MVA 50"
+ " for Venda de Contribuinte p/ Fora do Estado.",
+ )
+ self.assertEqual(
+ line.icms_cst_id.code,
+ "10",
+ "Error to mapping CST 10 from ICMS 010 12%"
+ " for Venda de Contribuinte p/ Fora do Estado.",
+ )
+
def test_nfe_not_taxpayer(self):
"""Test NFe not taxpayer."""
diff --git a/l10n_br_fiscal/views/l10n_br_fiscal_action.xml b/l10n_br_fiscal/views/l10n_br_fiscal_action.xml
index 94f398b5a926..5ed1b8531455 100644
--- a/l10n_br_fiscal/views/l10n_br_fiscal_action.xml
+++ b/l10n_br_fiscal/views/l10n_br_fiscal_action.xml
@@ -516,6 +516,23 @@
+
+
+ Product Tag
+ ir.actions.act_window
+ l10n_br_fiscal.product.tag
+ tree
+
+
+
+
+ Create a new Product Tag
+
+ Product Tags assist in matching Operation Lines with their corresponding products, ensuring accurate identification and organization within the system.
+