odoo-addon-l10n-br-fiscal 16.0.2.17.0__py3-none-any.whl → 16.0.19.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. odoo/addons/l10n_br_fiscal/README.rst +11 -4
  2. odoo/addons/l10n_br_fiscal/__manifest__.py +20 -12
  3. odoo/addons/l10n_br_fiscal/constants/fiscal.py +64 -18
  4. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.cest.csv +1043 -983
  5. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.cfop.csv +620 -620
  6. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.cst.csv +58 -0
  7. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.document.type.csv +1 -0
  8. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.legal.nature.csv +82 -0
  9. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.nbs.csv +791 -764
  10. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.operation.indicator.csv +27 -0
  11. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.partner.profile.csv +11 -0
  12. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.tax.classification.csv +163 -0
  13. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.tax.csv +32 -0
  14. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.tax.group.csv +3 -0
  15. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal_icms_tax_definition_data.xml +340 -352
  16. odoo/addons/l10n_br_fiscal/data/operation_data.xml +1 -1
  17. odoo/addons/l10n_br_fiscal/data/simplified_tax_data.xml +5 -5
  18. odoo/addons/l10n_br_fiscal/data/uom.alias.csv +25 -0
  19. odoo/addons/l10n_br_fiscal/data/uom_data.xml +104 -33
  20. odoo/addons/l10n_br_fiscal/demo/__init__.py +21 -15
  21. odoo/addons/l10n_br_fiscal/demo/company_demo.xml +6 -0
  22. odoo/addons/l10n_br_fiscal/demo/fiscal_document_demo.xml +3 -377
  23. odoo/addons/l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml +0 -12
  24. odoo/addons/l10n_br_fiscal/demo/fiscal_operation_demo.xml +2 -2
  25. odoo/addons/l10n_br_fiscal/demo/icms_tax_definition_demo.xml +5 -2
  26. odoo/addons/l10n_br_fiscal/demo/res_users_demo.xml +2 -2
  27. odoo/addons/l10n_br_fiscal/i18n/l10n_br_fiscal.pot +1161 -804
  28. odoo/addons/l10n_br_fiscal/i18n/pt_BR.po +22 -22
  29. odoo/addons/l10n_br_fiscal/migrations/16.0.13.0.0/pre-migration.py +25 -0
  30. odoo/addons/l10n_br_fiscal/migrations/16.0.14.0.0/pre-migration.py +30 -0
  31. odoo/addons/l10n_br_fiscal/migrations/16.0.14.0.5/pre-migration.py +15 -0
  32. odoo/addons/l10n_br_fiscal/migrations/16.0.4.0.0/pre-migration.py +220 -0
  33. odoo/addons/l10n_br_fiscal/migrations/16.0.5.0.0/pre-migration.py +33 -0
  34. odoo/addons/l10n_br_fiscal/migrations/16.0.5.2.0/pre-migration.py +21 -0
  35. odoo/addons/l10n_br_fiscal/models/__init__.py +3 -8
  36. odoo/addons/l10n_br_fiscal/models/cest.py +0 -8
  37. odoo/addons/l10n_br_fiscal/models/cfop.py +91 -0
  38. odoo/addons/l10n_br_fiscal/models/city_taxation_code.py +1 -3
  39. odoo/addons/l10n_br_fiscal/models/comment.py +2 -2
  40. odoo/addons/l10n_br_fiscal/models/cst.py +0 -1
  41. odoo/addons/l10n_br_fiscal/models/data_abstract.py +26 -0
  42. odoo/addons/l10n_br_fiscal/models/data_ncm_nbs_abstract.py +1 -1
  43. odoo/addons/l10n_br_fiscal/models/document.py +131 -222
  44. odoo/addons/l10n_br_fiscal/models/document_line.py +82 -5
  45. odoo/addons/l10n_br_fiscal/models/document_line_mixin.py +1952 -138
  46. odoo/addons/l10n_br_fiscal/models/document_mixin.py +741 -6
  47. odoo/addons/l10n_br_fiscal/models/document_related.py +12 -9
  48. odoo/addons/l10n_br_fiscal/models/document_serie.py +33 -0
  49. odoo/addons/l10n_br_fiscal/models/document_type.py +0 -6
  50. odoo/addons/l10n_br_fiscal/models/ibpt.py +1 -1
  51. odoo/addons/l10n_br_fiscal/models/icms_regulation.py +2 -2
  52. odoo/addons/l10n_br_fiscal/models/invalidate_number.py +4 -5
  53. odoo/addons/l10n_br_fiscal/models/legal_nature.py +20 -0
  54. odoo/addons/l10n_br_fiscal/models/nbm.py +0 -8
  55. odoo/addons/l10n_br_fiscal/models/ncm.py +0 -12
  56. odoo/addons/l10n_br_fiscal/models/operation.py +49 -15
  57. odoo/addons/l10n_br_fiscal/models/operation_dashboard.py +3 -2
  58. odoo/addons/l10n_br_fiscal/models/operation_indicator.py +58 -0
  59. odoo/addons/l10n_br_fiscal/models/operation_line.py +75 -6
  60. odoo/addons/l10n_br_fiscal/models/partner_profile.py +6 -0
  61. odoo/addons/l10n_br_fiscal/models/product_mixin.py +24 -21
  62. odoo/addons/l10n_br_fiscal/models/product_template.py +23 -13
  63. odoo/addons/l10n_br_fiscal/models/res_company.py +31 -9
  64. odoo/addons/l10n_br_fiscal/models/res_partner.py +38 -6
  65. odoo/addons/l10n_br_fiscal/models/simplified_tax.py +0 -3
  66. odoo/addons/l10n_br_fiscal/models/simplified_tax_range.py +8 -0
  67. odoo/addons/l10n_br_fiscal/models/tax.py +144 -55
  68. odoo/addons/l10n_br_fiscal/models/tax_classification.py +81 -0
  69. odoo/addons/l10n_br_fiscal/models/tax_definition.py +72 -23
  70. odoo/addons/l10n_br_fiscal/models/tax_pis_cofins.py +0 -3
  71. odoo/addons/l10n_br_fiscal/models/tax_pis_cofins_base.py +1 -1
  72. odoo/addons/l10n_br_fiscal/models/tax_pis_cofins_credit.py +1 -1
  73. odoo/addons/l10n_br_fiscal/models/uom_uom.py +15 -30
  74. odoo/addons/l10n_br_fiscal/security/fiscal_security.xml +11 -27
  75. odoo/addons/l10n_br_fiscal/security/ir.model.access.csv +11 -10
  76. odoo/addons/l10n_br_fiscal/static/description/index.html +27 -21
  77. odoo/addons/l10n_br_fiscal/static/src/js/list_renderer_with_button.esm.js +38 -0
  78. odoo/addons/l10n_br_fiscal/tests/__init__.py +3 -2
  79. odoo/addons/l10n_br_fiscal/tests/test_document_edition.py +308 -0
  80. odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_generic.py +23 -129
  81. odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_nfse.py +5 -15
  82. odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_serie.py +60 -0
  83. odoo/addons/l10n_br_fiscal/tests/test_ibpt.py +4 -3
  84. odoo/addons/l10n_br_fiscal/tests/test_icms_regulation.py +2 -2
  85. odoo/addons/l10n_br_fiscal/tests/test_ncm.py +4 -1
  86. odoo/addons/l10n_br_fiscal/tests/test_tax_benefit.py +17 -22
  87. odoo/addons/l10n_br_fiscal/tests/test_tax_classification.py +110 -0
  88. odoo/addons/l10n_br_fiscal/tools.py +1 -1
  89. odoo/addons/l10n_br_fiscal/views/cest_view.xml +2 -4
  90. odoo/addons/l10n_br_fiscal/views/cfop_view.xml +25 -5
  91. odoo/addons/l10n_br_fiscal/views/city_taxation_code.xml +1 -4
  92. odoo/addons/l10n_br_fiscal/views/cnae_view.xml +2 -4
  93. odoo/addons/l10n_br_fiscal/views/comment_view.xml +2 -4
  94. odoo/addons/l10n_br_fiscal/views/cst_view.xml +6 -8
  95. odoo/addons/l10n_br_fiscal/views/{document_fiscal_line_mixin_view.xml → document_line_mixin_view.xml} +525 -385
  96. odoo/addons/l10n_br_fiscal/views/document_line_view.xml +101 -82
  97. odoo/addons/l10n_br_fiscal/views/document_related_view.xml +44 -46
  98. odoo/addons/l10n_br_fiscal/views/document_serie_view.xml +2 -6
  99. odoo/addons/l10n_br_fiscal/views/document_type_view.xml +0 -8
  100. odoo/addons/l10n_br_fiscal/views/document_view.xml +303 -370
  101. odoo/addons/l10n_br_fiscal/views/icms_regulation_view.xml +14 -16
  102. odoo/addons/l10n_br_fiscal/views/icms_relief_view.xml +8 -10
  103. odoo/addons/l10n_br_fiscal/views/invalidate_number_view.xml +46 -48
  104. odoo/addons/l10n_br_fiscal/views/l10n_br_fiscal_action.xml +166 -280
  105. odoo/addons/l10n_br_fiscal/views/l10n_br_fiscal_menu.xml +25 -99
  106. odoo/addons/l10n_br_fiscal/views/legal_nature_view.xml +40 -0
  107. odoo/addons/l10n_br_fiscal/views/nbm_view.xml +5 -6
  108. odoo/addons/l10n_br_fiscal/views/nbs_view.xml +5 -6
  109. odoo/addons/l10n_br_fiscal/views/ncm_view.xml +12 -15
  110. odoo/addons/l10n_br_fiscal/views/operation_dashboard_view.xml +13 -12
  111. odoo/addons/l10n_br_fiscal/views/operation_indicator_view.xml +75 -0
  112. odoo/addons/l10n_br_fiscal/views/operation_line_view.xml +22 -21
  113. odoo/addons/l10n_br_fiscal/views/operation_view.xml +4 -19
  114. odoo/addons/l10n_br_fiscal/views/partner_profile_view.xml +3 -6
  115. odoo/addons/l10n_br_fiscal/views/product_genre_view.xml +7 -9
  116. odoo/addons/l10n_br_fiscal/views/product_product_view.xml +37 -14
  117. odoo/addons/l10n_br_fiscal/views/product_template_view.xml +34 -14
  118. odoo/addons/l10n_br_fiscal/views/res_company_view.xml +55 -52
  119. odoo/addons/l10n_br_fiscal/views/res_config_settings_view.xml +23 -28
  120. odoo/addons/l10n_br_fiscal/views/res_partner_view.xml +22 -2
  121. odoo/addons/l10n_br_fiscal/views/service_type_view.xml +7 -8
  122. odoo/addons/l10n_br_fiscal/views/simplified_tax_range_view.xml +0 -2
  123. odoo/addons/l10n_br_fiscal/views/simplified_tax_view.xml +0 -2
  124. odoo/addons/l10n_br_fiscal/views/tax_classification.xml +110 -0
  125. odoo/addons/l10n_br_fiscal/views/tax_definition_view.xml +157 -129
  126. odoo/addons/l10n_br_fiscal/views/tax_estimate_view.xml +0 -2
  127. odoo/addons/l10n_br_fiscal/views/tax_group_view.xml +3 -6
  128. odoo/addons/l10n_br_fiscal/views/tax_ipi_control_seal_view.xml +0 -2
  129. odoo/addons/l10n_br_fiscal/views/tax_ipi_guideline_class_view.xml +0 -2
  130. odoo/addons/l10n_br_fiscal/views/tax_ipi_guideline_view.xml +2 -4
  131. odoo/addons/l10n_br_fiscal/views/tax_pis_cofins_base_view.xml +2 -4
  132. odoo/addons/l10n_br_fiscal/views/tax_pis_cofins_credit_view.xml +2 -4
  133. odoo/addons/l10n_br_fiscal/views/tax_pis_cofins_view.xml +5 -7
  134. odoo/addons/l10n_br_fiscal/views/tax_view.xml +5 -7
  135. odoo/addons/l10n_br_fiscal/views/uom_uom.xml +24 -17
  136. odoo/addons/l10n_br_fiscal/wizards/__init__.py +1 -0
  137. odoo/addons/l10n_br_fiscal/wizards/base_wizard_mixin.py +1 -1
  138. odoo/addons/l10n_br_fiscal/wizards/document_import_wizard_mixin.py +129 -0
  139. odoo/addons/l10n_br_fiscal/wizards/document_import_wizard_mixin.xml +41 -0
  140. {odoo_addon_l10n_br_fiscal-16.0.2.17.0.dist-info → odoo_addon_l10n_br_fiscal-16.0.19.4.0.dist-info}/METADATA +15 -6
  141. odoo_addon_l10n_br_fiscal-16.0.19.4.0.dist-info/RECORD +210 -0
  142. {odoo_addon_l10n_br_fiscal-16.0.2.17.0.dist-info → odoo_addon_l10n_br_fiscal-16.0.19.4.0.dist-info}/WHEEL +1 -1
  143. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal_email_template.xml +0 -68
  144. odoo/addons/l10n_br_fiscal/data/partner_profile_data.xml +0 -96
  145. odoo/addons/l10n_br_fiscal/data/uom_alternative_data.xml +0 -58
  146. odoo/addons/l10n_br_fiscal/demo/l10n_br_fiscal_document_email.xml +0 -54
  147. odoo/addons/l10n_br_fiscal/demo/subsequent_operation_demo.xml +0 -10
  148. odoo/addons/l10n_br_fiscal/models/document_email.py +0 -74
  149. odoo/addons/l10n_br_fiscal/models/document_line_mixin_methods.py +0 -913
  150. odoo/addons/l10n_br_fiscal/models/document_mixin_fields.py +0 -473
  151. odoo/addons/l10n_br_fiscal/models/document_mixin_methods.py +0 -269
  152. odoo/addons/l10n_br_fiscal/models/document_move_mixin.py +0 -261
  153. odoo/addons/l10n_br_fiscal/models/subsequent_document.py +0 -203
  154. odoo/addons/l10n_br_fiscal/models/subsequent_operation.py +0 -54
  155. odoo/addons/l10n_br_fiscal/models/uom_uom_alternative.py +0 -22
  156. odoo/addons/l10n_br_fiscal/tests/test_subsequent_operation.py +0 -71
  157. odoo/addons/l10n_br_fiscal/tests/test_uom_uom.py +0 -22
  158. odoo/addons/l10n_br_fiscal/views/document_email_view.xml +0 -48
  159. odoo/addons/l10n_br_fiscal/views/subsequent_document_view.xml +0 -43
  160. odoo/addons/l10n_br_fiscal/views/subsequent_operation_view.xml +0 -21
  161. odoo_addon_l10n_br_fiscal-16.0.2.17.0.dist-info/RECORD +0 -205
  162. {odoo_addon_l10n_br_fiscal-16.0.2.17.0.dist-info → odoo_addon_l10n_br_fiscal-16.0.19.4.0.dist-info}/top_level.txt +0 -0
@@ -56,7 +56,7 @@ class DocumentRelated(models.Model):
56
56
  default="cnpj",
57
57
  )
58
58
 
59
- inscr_est = fields.Char(string="Inscr. Estadual/RG", size=16)
59
+ l10n_br_ie_code = fields.Char(string="Inscr. Estadual/RG", size=16)
60
60
 
61
61
  document_date = fields.Date(string="Data")
62
62
 
@@ -87,11 +87,14 @@ class DocumentRelated(models.Model):
87
87
  for record in self:
88
88
  check_cnpj_cpf(record.env, record.cnpj_cpf, self.env.ref("base.br"))
89
89
 
90
- @api.constrains("inscr_est", "state_id")
90
+ @api.constrains("l10n_br_ie_code", "state_id")
91
91
  def _check_ie(self):
92
92
  for record in self:
93
93
  check_ie(
94
- record.env, record.inscr_est, record.state_id, self.env.ref("base.br")
94
+ record.env,
95
+ record.l10n_br_ie_code,
96
+ record.state_id,
97
+ self.env.ref("base.br"),
95
98
  )
96
99
 
97
100
  @api.onchange("document_related_id")
@@ -101,7 +104,7 @@ class DocumentRelated(models.Model):
101
104
  return False
102
105
 
103
106
  self.document_type_id = related.document_type_id
104
- self.document_total_amount = related.amount_total
107
+ self.document_total_amount = related.fiscal_amount_total
105
108
  self.document_total_weight = related.total_weight
106
109
 
107
110
  if related.document_type_id.electronic:
@@ -112,10 +115,10 @@ class DocumentRelated(models.Model):
112
115
  self.cnpj_cpf = False
113
116
  self.cpfcnpj_type = False
114
117
  self.document_date = False
115
- self.inscr_est = False
118
+ self.l10n_br_ie_code = False
116
119
 
117
120
  if related.document_type_id.code in ("01", "04"):
118
- self.access_key = False
121
+ self.document_key = False
119
122
  self.document_serie = related.document_serie
120
123
  self.document_number = related.document_number
121
124
  self.state_id = (
@@ -125,7 +128,7 @@ class DocumentRelated(models.Model):
125
128
  or False
126
129
  )
127
130
 
128
- self.cnpj_cpf = related.partner_id and related.partner_id.cnpj_cpf or False
131
+ self.cnpj_cpf = related.partner_id and related.partner_id.vat or False
129
132
 
130
133
  if related.partner_id.is_company:
131
134
  self.cpfcnpj_type = "cnpj"
@@ -135,8 +138,8 @@ class DocumentRelated(models.Model):
135
138
  self.document_date = related.document_date
136
139
 
137
140
  if related.document_type_id.code == "04":
138
- self.inscr_est = (
139
- related.partner_id and related.partner_id.inscr_est or False
141
+ self.l10n_br_ie_code = (
142
+ related.partner_id and related.partner_id.l10n_br_ie_code or False
140
143
  )
141
144
 
142
145
  @api.onchange("cnpj_cpf", "cpfcnpj_type")
@@ -3,11 +3,13 @@
3
3
  # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
4
4
 
5
5
  from odoo import _, api, fields, models
6
+ from odoo.exceptions import ValidationError
6
7
 
7
8
  from ..constants.fiscal import (
8
9
  DOCUMENT_ISSUER_COMPANY,
9
10
  FISCAL_IN_OUT,
10
11
  FISCAL_IN_OUT_DEFAULT,
12
+ SITUACAO_EDOC_EM_DIGITACAO,
11
13
  )
12
14
 
13
15
 
@@ -55,6 +57,14 @@ class DocumentSerie(models.Model):
55
57
  string="Invalidate Number Range",
56
58
  )
57
59
 
60
+ _sql_constraints = [
61
+ (
62
+ "document_serie_unique",
63
+ "unique(code, document_type_id, company_id)",
64
+ "A Fiscal Document Serie already exists for this document type.",
65
+ )
66
+ ]
67
+
58
68
  @api.model
59
69
  def _create_sequence(self, values):
60
70
  """Create new no_gap entry sequence for every
@@ -78,6 +88,29 @@ class DocumentSerie(models.Model):
78
88
  vals.update({"internal_sequence_id": self._create_sequence(vals)})
79
89
  return super().create(vals_list)
80
90
 
91
+ def write(self, vals):
92
+ if "internal_sequence_id" in vals:
93
+ raise ValidationError(_("You cannot change the internal sequence."))
94
+ if "code" in vals:
95
+ for serie in self:
96
+ if serie.code == vals["code"]:
97
+ continue
98
+ if self.env["l10n_br_fiscal.document"].search_count(
99
+ [
100
+ ("document_serie_id", "=", serie.id),
101
+ ("state_edoc", "not in", [SITUACAO_EDOC_EM_DIGITACAO]),
102
+ ],
103
+ limit=1,
104
+ ):
105
+ raise ValidationError(
106
+ _(
107
+ "You cannot change the code of a document "
108
+ "serie %(name)s that is already in use.",
109
+ name=serie.name,
110
+ )
111
+ )
112
+ return super().write(vals)
113
+
81
114
  def name_get(self):
82
115
  return [(r.id, f"{r.name}") for r in self]
83
116
 
@@ -35,12 +35,6 @@ class DocumentType(models.Model):
35
35
  required=True,
36
36
  )
37
37
 
38
- document_email_ids = fields.One2many(
39
- comodel_name="l10n_br_fiscal.document.email",
40
- inverse_name="document_type_id",
41
- string="Email Template Definition",
42
- )
43
-
44
38
  document_serie_ids = fields.One2many(
45
39
  comodel_name="l10n_br_fiscal.document.serie",
46
40
  inverse_name="document_type_id",
@@ -45,7 +45,7 @@ def _request(ws_url, params, ibpt_request_timeout=30):
45
45
  elif response.status_code == requests.codes.service_unavailable:
46
46
  raise UserError(_("IBPT Service Unavailable - {!r}").format(ws_url))
47
47
  except Exception as e:
48
- raise UserError(_("Error in the request: {}").format(e)) from e
48
+ raise UserError(f"Error in the request: {e}") from e
49
49
 
50
50
 
51
51
  def get_ibpt_product(
@@ -50,7 +50,7 @@ class ICMSRegulation(models.Model):
50
50
  _inherit = ["mail.thread", "mail.activity.mixin"]
51
51
  _description = "Tax ICMS Regulation"
52
52
 
53
- name = fields.Text(required=True, index=True)
53
+ name = fields.Char(required=True, index=True)
54
54
 
55
55
  icms_imported_tax_id = fields.Many2one(
56
56
  comodel_name="l10n_br_fiscal.tax",
@@ -2101,7 +2101,7 @@ class ICMSRegulation(models.Model):
2101
2101
  company.state_id != partner.state_id
2102
2102
  and partner.ind_ie_dest == NFE_IND_IE_DEST_9
2103
2103
  and operation_line.fiscal_operation_type == FISCAL_OUT
2104
- or operation_line.fiscal_operation_id.fiscal_type == "return_in"
2104
+ or operation_line.fiscal_operation_id.fiscal_type != "return_in"
2105
2105
  and operation_line.fiscal_operation_type == FISCAL_IN
2106
2106
  ):
2107
2107
  domain = self._build_map_tax_def_domain(
@@ -105,11 +105,10 @@ class InvalidateNumber(models.Model):
105
105
  @api.depends("document_type_id", "document_serie_id", "number_start", "number_end")
106
106
  def _compute_name(self):
107
107
  for record in self:
108
- record.name = "{type}/({serie}): {start} - {end}".format(
109
- type=record.document_type_id.type,
110
- serie=record.document_serie_id.name,
111
- start=record.number_start,
112
- end=record.number_end,
108
+ record.name = (
109
+ f"{record.document_type_id.type}/"
110
+ f"({record.document_serie_id.name}): "
111
+ f"{record.number_start} - {record.number_end}"
113
112
  )
114
113
 
115
114
  def unlink(self):
@@ -0,0 +1,20 @@
1
+ # Copyright (C) 2025 Renato Lima - Akretion <renato.lima@akretion.com.br>
2
+ # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
3
+
4
+ from odoo import fields, models
5
+
6
+
7
+ class LegalNature(models.Model):
8
+ _name = "l10n_br_fiscal.legal.nature"
9
+ _inherit = "l10n_br_fiscal.data.abstract"
10
+ _description = "Legal Nature"
11
+
12
+ code = fields.Char(size=5)
13
+
14
+ _sql_constraints = [
15
+ (
16
+ "fiscal_legal_nature_code_uniq",
17
+ "unique (code)",
18
+ "Legal Nature already exists with this code !",
19
+ )
20
+ ]
@@ -15,26 +15,18 @@ class Nbm(models.Model):
15
15
 
16
16
  code_unmasked = fields.Char(size=10, unaccent=False)
17
17
 
18
- name = fields.Text(required=True, index=True)
19
-
20
18
  product_tmpl_ids = fields.One2many(inverse_name="nbm_id")
21
19
 
22
20
  ncms = fields.Char(string="NCM")
23
21
 
24
22
  ncm_ids = fields.Many2many(
25
23
  comodel_name="l10n_br_fiscal.ncm",
26
- relation="fiscal_nbm_ncm_rel",
27
- column1="nbm_id",
28
- column2="ncm_id",
29
24
  readonly=True,
30
25
  string="NCMs",
31
26
  )
32
27
 
33
28
  tax_definition_ids = fields.Many2many(
34
29
  comodel_name="l10n_br_fiscal.tax.definition",
35
- relation="tax_definition_nbm_rel",
36
- column1="nbm_id",
37
- column2="tax_definition_id",
38
30
  readonly=True,
39
31
  string="Tax Definition",
40
32
  )
@@ -44,36 +44,24 @@ class Ncm(models.Model):
44
44
 
45
45
  tax_definition_ids = fields.Many2many(
46
46
  comodel_name="l10n_br_fiscal.tax.definition",
47
- relation="tax_definition_ncm_rel",
48
- column1="ncm_id",
49
- column2="tax_definition_id",
50
47
  readonly=True,
51
48
  string="Tax Definition",
52
49
  )
53
50
 
54
51
  cest_ids = fields.Many2many(
55
52
  comodel_name="l10n_br_fiscal.cest",
56
- relation="fiscal_cest_ncm_rel",
57
- column1="ncm_id",
58
- column2="cest_id",
59
53
  readonly=True,
60
54
  string="CESTs",
61
55
  )
62
56
 
63
57
  nbm_ids = fields.Many2many(
64
58
  comodel_name="l10n_br_fiscal.nbm",
65
- relation="fiscal_nbm_ncm_rel",
66
- column1="ncm_id",
67
- column2="nbm_id",
68
59
  readonly=True,
69
60
  string="NBMs",
70
61
  )
71
62
 
72
63
  piscofins_ids = fields.Many2many(
73
64
  comodel_name="l10n_br_fiscal.tax.pis.cofins",
74
- relation="fiscal_pis_cofins_ncm_rel",
75
- column1="ncm_id",
76
- column2="piscofins_id",
77
65
  readonly=True,
78
66
  string="PIS/COFINS",
79
67
  )
@@ -1,7 +1,7 @@
1
1
  # Copyright (C) 2013 Renato Lima - Akretion
2
2
  # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
3
3
 
4
- from odoo import _, api, fields, models
4
+ from odoo import _, fields, models
5
5
  from odoo.exceptions import UserError
6
6
 
7
7
  from ..constants.fiscal import (
@@ -17,6 +17,54 @@ from ..constants.fiscal import (
17
17
 
18
18
 
19
19
  class Operation(models.Model):
20
+ """
21
+ Defines a Fiscal Operation, representing the nature and fiscal intent of
22
+ a transaction (e.g., Sale, Return, Import, Industrialization).
23
+
24
+ A Fiscal Operation is a core configuration entity in the Brazilian
25
+ fiscal localization. It serves as a central point to orchestrate how
26
+ fiscal documents and their lines are processed by determining:
27
+
28
+ 1. **Transaction Type**: Specifies if the operation is an Inbound
29
+ (e.g., purchase, return from customer), Outbound (e.g., sale,
30
+ return to supplier), or Other type of fiscal movement.
31
+
32
+ 2. **Operation Lines (`line_ids`)**: Each Fiscal Operation contains
33
+ one or more `l10n_br_fiscal.operation.line` records. These lines
34
+ define specific rules or conditions (based on partner profiles,
35
+ product types, company tax regime, etc.) under which a
36
+ particular set of fiscal treatments apply. The system selects
37
+ the "best match" operation line based on the context of a
38
+ transaction.
39
+
40
+ 3. **CFOP (Código Fiscal de Operações e Prestações)**: The selected
41
+ `operation.line` determines the appropriate CFOP codes
42
+ (internal, external, export) for the transaction line. The CFOP
43
+ itself carries significant fiscal meaning and influences tax
44
+ calculations and reporting.
45
+
46
+ 4. **Tax Definitions (`tax_definition_ids` on `operation.line` and `cfop`)**:
47
+ The selected `operation.line` and the determined `cfop` can both
48
+ hold `l10n_br_fiscal.tax.definition` records. These tax
49
+ definitions specify which taxes (ICMS, IPI, PIS, COFINS, etc.)
50
+ apply, along with their respective CST/CSOSN codes and other
51
+ parameters. This linkage allows the Fiscal Operation to drive
52
+ the tax calculation engine.
53
+
54
+ 5. **Document Characteristics**: It can also define default behaviors
55
+ or properties for documents generated under this operation, such
56
+ as the electronic document purpose (`edoc_purpose`), default
57
+ price type (sale vs. cost), and links to inverse or return
58
+ operations.
59
+
60
+ Essentially, when a fiscal document is created, the user selects a
61
+ Fiscal Operation. The system then uses this operation and the
62
+ transactional context (partner, product, company) to find the most
63
+ suitable `operation.line`. This line, in turn, provides the CFOP
64
+ and a set of tax definitions, which are then used by the tax engine
65
+ to calculate all applicable taxes.
66
+ """
67
+
20
68
  _name = "l10n_br_fiscal.operation"
21
69
  _description = "Fiscal Operation"
22
70
  _inherit = ["mail.thread", "mail.activity.mixin"]
@@ -128,19 +176,10 @@ class Operation(models.Model):
128
176
 
129
177
  comment_ids = fields.Many2many(
130
178
  comodel_name="l10n_br_fiscal.comment",
131
- relation="l10n_br_fiscal_operation_comment_rel",
132
- column1="fiscal_operation_id",
133
- column2="comment_id",
134
179
  domain=[("object", "=", FISCAL_COMMENT_DOCUMENT)],
135
180
  string="Comment",
136
181
  )
137
182
 
138
- operation_subsequent_ids = fields.One2many(
139
- comodel_name="l10n_br_fiscal.subsequent.operation",
140
- inverse_name="fiscal_operation_id",
141
- string="Subsequent Operation",
142
- )
143
-
144
183
  _sql_constraints = [
145
184
  (
146
185
  "fiscal_operation_code_uniq",
@@ -265,11 +304,6 @@ class Operation(models.Model):
265
304
  best_line = max(lines, key=score)
266
305
  return best_line
267
306
 
268
- @api.onchange("operation_subsequent_ids")
269
- def _onchange_operation_subsequent_ids(self):
270
- for sub_operation in self.operation_subsequent_ids:
271
- sub_operation.fiscal_operation_id = self.id
272
-
273
307
  def copy(self, default=None):
274
308
  """
275
309
  Inherit copy to edit field code. This is needed because the field is
@@ -127,7 +127,7 @@ class Operation(models.Model):
127
127
  }
128
128
 
129
129
  def open_action(self):
130
- """return action based on type for related journals"""
130
+ """Return action based on type for related journals"""
131
131
 
132
132
  _fiscal_type_map = {
133
133
  "purchase": "in",
@@ -157,7 +157,8 @@ class Operation(models.Model):
157
157
  }
158
158
  )
159
159
 
160
- [action] = self.env.ref("l10n_br_fiscal.%s" % action_name).read()
160
+ xmlid = f"l10n_br_fiscal.{action_name}"
161
+ [action] = self.env.ref(xmlid).read()
161
162
  action["context"] = ctx
162
163
  action["domain"] = self._context.get("use_domain", [])
163
164
  action["domain"] += [
@@ -0,0 +1,58 @@
1
+ # Copyright (C) 2025 Marcel Savegnago <https://escodoo.com.br>
2
+ # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
3
+
4
+ from odoo import fields, models
5
+
6
+
7
+ class OperationIndicator(models.Model):
8
+ """Operation Indicator
9
+
10
+ This model stores the Operation Indicators (cIndOp) table according to
11
+ Annex VII of Technical Note No. 004/2025 from NFS-e Nacional, which is
12
+ part of the Brazilian Tax Reform (Reforma Tributária do Consumo).
13
+
14
+ The cIndOp field is used in the Service Provision Declaration (DPS)
15
+ to categorize consumption operations, as required by Art. 11 of
16
+ Complementary Law No. 214/2025.
17
+
18
+ This table will become mandatory from January 1, 2026.
19
+ """
20
+
21
+ _name = "l10n_br_fiscal.operation.indicator"
22
+ _inherit = "l10n_br_fiscal.data.abstract"
23
+ _description = "Operation Indicator"
24
+ _order = "code"
25
+
26
+ code = fields.Char(
27
+ string="Operation Indicator Code",
28
+ required=True,
29
+ index=True,
30
+ size=6,
31
+ help="Operation indicator code according to Annex VII of NT 004/2025 "
32
+ "(e.g., 020101, 030101)",
33
+ )
34
+
35
+ operation_type = fields.Text(
36
+ required=True,
37
+ help="Type of operation according to Art. 11 of Complementary Law "
38
+ "No. 214/2025",
39
+ )
40
+
41
+ operation_location = fields.Text(
42
+ string="Operation Location Consideration",
43
+ help="Where the operation is considered to take place according to "
44
+ "the legislation",
45
+ )
46
+
47
+ supply_characteristic = fields.Text(
48
+ help="Specific characteristic of the supply execution that determines "
49
+ "the place of supply",
50
+ )
51
+
52
+ supply_location = fields.Text(
53
+ string="DFe Supply Location",
54
+ required=True,
55
+ help="Place of supply to be identified in the Digital Fiscal Document "
56
+ "(DFe), such as supplier establishment, acquirer address, "
57
+ "recipient address, or other locations depending on the operation",
58
+ )
@@ -37,6 +37,11 @@ class OperationLine(models.Model):
37
37
 
38
38
  document_type_id = fields.Many2one(comodel_name="l10n_br_fiscal.document.type")
39
39
 
40
+ tax_classification_id = fields.Many2one(
41
+ comodel_name="l10n_br_fiscal.tax.classification",
42
+ string="Tax Classification",
43
+ )
44
+
40
45
  cfop_internal_id = fields.Many2one(
41
46
  comodel_name="l10n_br_fiscal.cfop",
42
47
  string="CFOP Internal",
@@ -65,14 +70,12 @@ class OperationLine(models.Model):
65
70
  related="fiscal_operation_id.fiscal_operation_type",
66
71
  string="Fiscal Operation Type",
67
72
  store=True,
68
- readonly=True,
69
73
  )
70
74
 
71
75
  fiscal_type = fields.Selection(
72
76
  related="fiscal_operation_id.fiscal_type",
73
77
  string="Fiscal Type",
74
78
  store=True,
75
- readonly=True,
76
79
  )
77
80
 
78
81
  tax_icms_or_issqn = fields.Selection(
@@ -120,9 +123,6 @@ class OperationLine(models.Model):
120
123
 
121
124
  comment_ids = fields.Many2many(
122
125
  comodel_name="l10n_br_fiscal.comment",
123
- relation="l10n_br_fiscal_operation_line_comment_rel",
124
- column1="fiscal_operation_line_id",
125
- column2="comment_id",
126
126
  domain=[("object", "=", FISCAL_COMMENT_LINE)],
127
127
  string="Comment",
128
128
  )
@@ -155,7 +155,7 @@ class OperationLine(models.Model):
155
155
  else:
156
156
  if not company.document_type_id:
157
157
  raise UserError(
158
- _("You need set a default fiscal document " "in your company !")
158
+ _("You need to set a default fiscal document in your company!")
159
159
  )
160
160
 
161
161
  document_type = company.document_type_id
@@ -172,6 +172,13 @@ class OperationLine(models.Model):
172
172
  cfop = self.cfop_export_id
173
173
  return cfop
174
174
 
175
+ def _get_tax_classification(self, company):
176
+ if self.tax_classification_id:
177
+ return self.tax_classification_id
178
+ elif company.tax_classification_id:
179
+ return company.tax_classification_id
180
+ return self.env["l10n_br_fiscal.tax.classification"]
181
+
175
182
  def _build_mapping_result_ipi(self, mapping_result, tax_definition):
176
183
  if tax_definition and tax_definition.ipi_guideline_id:
177
184
  mapping_result["ipi_guideline"] = tax_definition.ipi_guideline_id
@@ -206,11 +213,60 @@ class OperationLine(models.Model):
206
213
  service_type=None,
207
214
  ind_final=None,
208
215
  ):
216
+ """
217
+ Map and determine the applicable fiscal taxes, CFOP, IPI guideline,
218
+ and ICMS tax benefit for a given context.
219
+
220
+ The method aggregates tax definitions from various sources, applying a
221
+ precedence order:
222
+ 1. Company-level tax definitions.
223
+ 2. NCM-defined taxes (IPI, II).
224
+ 3. ICMS Regulation specific taxes.
225
+ 4. Taxes defined directly on this fiscal operation line.
226
+ 5. Taxes defined on the determined CFOP.
227
+ 6. Taxes from the partner's fiscal profile.
228
+
229
+ It also filters taxes based on whether the product is subject to
230
+ ICMS or ISSQN.
231
+
232
+ :param company: The company record (res.company).
233
+ :param partner: The partner record (res.partner).
234
+ :param product: Optional product record (product.product).
235
+ :param fiscal_price: (Unused in direct logic; kept for signature
236
+ consistency for overrides/extensions)
237
+ :param fiscal_quantity: (Unused in direct logic; kept for signature
238
+ consistency for overrides/extensions)
239
+ :param ncm: Optional NCM record (l10n_br_fiscal.ncm);
240
+ defaults to product's NCM.
241
+ :param nbm: Optional NBM record (l10n_br_fiscal.nbm);
242
+ defaults to product's NBM.
243
+ :param nbs: Optional NBS record (l10n_br_fiscal.nbs);
244
+ defaults to product's NBS.
245
+ :param cest: Optional CEST record (l10n_br_fiscal.cest);
246
+ defaults to product's CEST.
247
+ :param city_taxation_code: Optional City Taxation Code record
248
+ (l10n_br_fiscal.city.taxation.code).
249
+ :param service_type: Optional Service Type record
250
+ (l10n_br_fiscal.service.type).
251
+ :param ind_final: (Passed to icms_regulation_id.map_tax; not directly
252
+ used for tax calculation here)
253
+ :return: A dictionary containing:
254
+ - 'taxes': A dictionary of applicable tax records
255
+ (l10n_br_fiscal.tax) keyed by their tax_domain.
256
+ - 'cfop': The determined CFOP record (l10n_br_fiscal.cfop).
257
+ - 'ipi_guideline': The determined IPI guideline record
258
+ (l10n_br_fiscal.tax.ipi.guideline).
259
+ - 'icms_tax_benefit_id': The determined ICMS tax benefit record
260
+ ID (l10n_br_fiscal.tax.definition) or False.
261
+ - 'tax_classification': The determined Tax Classification record
262
+ (l10n_br_fiscal.tax.classification).
263
+ """
209
264
  mapping_result = {
210
265
  "taxes": {},
211
266
  "cfop": False,
212
267
  "ipi_guideline": self.env.ref("l10n_br_fiscal.tax_guideline_999"),
213
268
  "icms_tax_benefit_id": False,
269
+ "tax_classification": False,
214
270
  }
215
271
 
216
272
  self.ensure_one()
@@ -218,6 +274,9 @@ class OperationLine(models.Model):
218
274
  # Define CFOP
219
275
  mapping_result["cfop"] = self._get_cfop(company, partner)
220
276
 
277
+ # Define Tax Classification
278
+ mapping_result["tax_classification"] = self._get_tax_classification(company)
279
+
221
280
  # 1 Get Tax Defs from Company
222
281
  for tax_definition in company.tax_definition_ids.map_tax_definition(
223
282
  company,
@@ -232,6 +291,16 @@ class OperationLine(models.Model):
232
291
  ):
233
292
  self._build_mapping_result(mapping_result, tax_definition)
234
293
 
294
+ # 1_5 From Tax Classification
295
+ if mapping_result["tax_classification"]:
296
+ mapping_result["taxes"][
297
+ mapping_result["tax_classification"].tax_cbs_id.tax_domain
298
+ ] = mapping_result["tax_classification"].tax_cbs_id
299
+
300
+ mapping_result["taxes"][
301
+ mapping_result["tax_classification"].tax_ibs_id.tax_domain
302
+ ] = mapping_result["tax_classification"].tax_ibs_id
303
+
235
304
  # 2 From NCM
236
305
  if not ncm and product:
237
306
  ncm = product.ncm_id
@@ -8,6 +8,7 @@ from odoo.exceptions import ValidationError
8
8
  from ..constants.fiscal import (
9
9
  NFE_IND_IE_DEST,
10
10
  NFE_IND_IE_DEST_DEFAULT,
11
+ PUBLIC_ENTIRY_TYPE,
11
12
  TAX_FRAMEWORK,
12
13
  TAX_FRAMEWORK_NORMAL,
13
14
  )
@@ -32,6 +33,11 @@ class PartnerProfile(models.Model):
32
33
  "other government-controlled organizations.",
33
34
  )
34
35
 
36
+ public_entity_type = fields.Selection(
37
+ selection=PUBLIC_ENTIRY_TYPE,
38
+ string="Tipo de Entidade Governamental",
39
+ )
40
+
35
41
  default = fields.Boolean(string="Default Profile", default=True)
36
42
 
37
43
  ind_ie_dest = fields.Selection(
@@ -15,26 +15,29 @@ class ProductMixin(models.AbstractModel):
15
15
  _name = "l10n_br_fiscal.product.mixin"
16
16
  _description = "Fiscal Product Mixin"
17
17
 
18
- @api.onchange("fiscal_type")
19
- def _onchange_fiscal_type(self):
20
- for r in self:
21
- if r.fiscal_type == PRODUCT_FISCAL_TYPE_SERVICE:
22
- r.ncm_id = self.env.ref(NCM_FOR_SERVICE_REF)
23
- r.tax_icms_or_issqn = TAX_DOMAIN_ISSQN
24
- else:
25
- r.tax_icms_or_issqn = TAX_DOMAIN_ICMS
18
+ @api.depends("fiscal_type", "fiscal_genre_id")
19
+ def _compute_ncm_id(self):
20
+ for product in self:
21
+ if product.fiscal_type == PRODUCT_FISCAL_TYPE_SERVICE:
22
+ product.ncm_id = self.env.ref(NCM_FOR_SERVICE_REF)
23
+ elif product.fiscal_genre_id and product.ncm_id:
24
+ if product.fiscal_genre_id.code != product.ncm_id.code[0:2]:
25
+ product.ncm_id = False
26
+ elif product.ncm_id is None:
27
+ product.ncm_id = False
26
28
 
27
- @api.onchange("ncm_id")
28
- def _onchange_ncm_id(self):
29
- for r in self:
30
- if r.ncm_id:
31
- r.fiscal_genre_id = self.env["l10n_br_fiscal.product.genre"].search(
32
- [("code", "=", r.ncm_id.code[0:2])]
33
- )
29
+ @api.depends("ncm_id")
30
+ def _compute_fiscal_genre_id(self):
31
+ for product in self:
32
+ if product.ncm_id:
33
+ product.fiscal_genre_id = self.env[
34
+ "l10n_br_fiscal.product.genre"
35
+ ].search([("code", "=", product.ncm_id.code[0:2])])
34
36
 
35
- @api.onchange("fiscal_genre_id")
36
- def _onchange_fiscal_genre_id(self):
37
- for r in self:
38
- if r.fiscal_genre_id and r.ncm_id:
39
- if r.fiscal_genre_id.code != r.ncm_id.code[0:2]:
40
- r.ncm_id = False
37
+ @api.depends("fiscal_type")
38
+ def _compute_tax_icms_or_issqn(self):
39
+ for product in self:
40
+ if product.fiscal_type == PRODUCT_FISCAL_TYPE_SERVICE:
41
+ product.tax_icms_or_issqn = TAX_DOMAIN_ISSQN
42
+ else:
43
+ product.tax_icms_or_issqn = TAX_DOMAIN_ICMS