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
@@ -12,6 +12,23 @@ from odoo.osv import expression
12
12
 
13
13
 
14
14
  class DataAbstract(models.AbstractModel):
15
+ """
16
+ Abstract base model for fiscal master data in Brazilian localization.
17
+
18
+ This model provides common structure and functionality for fiscal
19
+ data entities (NCM, CFOP, CST, etc.). It includes:
20
+ - Standard fields: `code`, `name`, `active`, and a computed
21
+ `code_unmasked` (for searching codes without punctuation).
22
+ - Default ordering by `code`.
23
+ - Enhanced search: Modifies search views and `_name_search`
24
+ to allow searching by `code`, `code_unmasked`, and `name`
25
+ simultaneously.
26
+ - Standardized display name format in `name_get`
27
+ (`<code> - <name>`).
28
+ - Permission control for archiving/unarchanging, restricted
29
+ to users in 'l10n_br_fiscal.group_manager' group.
30
+ """
31
+
15
32
  _name = "l10n_br_fiscal.data.abstract"
16
33
  _description = "Fiscal Data Abstract"
17
34
  _order = "code"
@@ -46,6 +63,15 @@ class DataAbstract(models.AbstractModel):
46
63
  def fields_view_get(
47
64
  self, view_id=None, view_type="form", toolbar=False, submenu=False
48
65
  ):
66
+ """
67
+ Modify search view architecture to enhance 'code' field filtering.
68
+
69
+ Intercept the search view definition, altering `filter_domain`
70
+ for the 'code' field. This lets users search by raw 'code',
71
+ 'code_unmasked' (code without punctuation), or 'name' of the
72
+ record when typing into the 'code' filter in the search panel.
73
+ """
74
+
49
75
  model_view = super().fields_view_get(view_id, view_type, toolbar, submenu)
50
76
 
51
77
  if view_type == "search":
@@ -89,7 +89,7 @@ class DataNcmNbsAbstract(models.AbstractModel):
89
89
 
90
90
  config = DeOlhoNoImposto(
91
91
  company.ibpt_token,
92
- misc.punctuation_rm(company.cnpj_cpf),
92
+ misc.punctuation_rm(company.vat),
93
93
  company.state_id.code,
94
94
  odooconfig.get("ibpt_request_timeout")
95
95
  or self.env["ir.config_parameter"]
@@ -10,17 +10,21 @@ from odoo import _, api, fields, models
10
10
  from odoo.exceptions import ValidationError
11
11
 
12
12
  from ..constants.fiscal import (
13
- DOCUMENT_ISSUER,
13
+ COMMENT_TYPE_COMMERCIAL,
14
+ COMMENT_TYPE_FISCAL,
14
15
  DOCUMENT_ISSUER_COMPANY,
15
16
  DOCUMENT_ISSUER_DICT,
16
17
  DOCUMENT_ISSUER_PARTNER,
17
18
  EDOC_PURPOSE,
18
19
  EDOC_PURPOSE_NORMAL,
20
+ EDOC_REFUND_CREDIT_TYPE,
21
+ EDOC_REFUND_DEBIT_TYPE,
19
22
  FISCAL_IN_OUT_DICT,
20
23
  MODELO_FISCAL_CTE,
21
24
  MODELO_FISCAL_NFCE,
22
25
  MODELO_FISCAL_NFE,
23
26
  MODELO_FISCAL_NFSE,
27
+ PUBLIC_ENTIRY_TYPE,
24
28
  SITUACAO_EDOC,
25
29
  SITUACAO_EDOC_AUTORIZADA,
26
30
  SITUACAO_EDOC_CANCELADA,
@@ -32,29 +36,37 @@ from ..constants.fiscal import (
32
36
 
33
37
 
34
38
  class Document(models.Model):
35
- """Implementação base dos documentos fiscais
36
-
37
- Devemos sempre ter em mente que o modelo que vai usar este módulo abstrato
38
- tem diversos metodos importantes e a intenção que os módulos da OCA que
39
- extendem este modelo, funcionem se possível sem a necessidade de
40
- codificação extra.
41
-
42
- É preciso também estar atento que o documento fiscal tem dois estados:
43
-
44
- - Estado do documento eletrônico / não eletônico: state_edoc
45
- - Estado FISCAL: state_fiscal
46
-
47
- O estado fiscal é um campo que é alterado apenas algumas vezes pelo código
48
- e é de responsabilidade do responsável fiscal pela empresa de manter a
49
- integridade do mesmo, pois ele não tem um fluxo realmente definido e
50
- interfere no lançamento do registro no arquivo do SPED FISCAL.
39
+ """
40
+ Base implementation for Brazilian fiscal documents.
41
+
42
+ This model serves as the foundational structure for various fiscal
43
+ documents within the Brazilian localization. It's designed to be
44
+ extensible, allowing other OCA modules to build upon it, ideally
45
+ minimizing the need for additional custom coding for common fiscal
46
+ document functionalities.
47
+
48
+ Key aspects to note:
49
+ - The fiscal document manages two primary states:
50
+ - Electronic Document State (`state_edoc`): Reflects the status
51
+ of the document in its electronic lifecycle (e.g., Draft,
52
+ Authorized, Cancelled).
53
+ - Fiscal State (`state_fiscal`): Represents the document's status
54
+ from a purely fiscal accounting perspective (e.g., Regular,
55
+ Cancelled for fiscal purposes). This state is less automated
56
+ and often managed by the fiscal responsible to ensure correct
57
+ reporting, such as in SPED Fiscal.
58
+
59
+ This model inherits common fields and methods from
60
+ `l10n_br_fiscal.document.mixin` and includes features for document
61
+ numbering, key validation, partner and company fiscal details, line
62
+ items and returns.
51
63
  """
52
64
 
53
65
  _name = "l10n_br_fiscal.document"
54
66
  _inherit = [
55
- "l10n_br_fiscal.document.mixin.fields",
56
- "l10n_br_fiscal.document.move.mixin",
67
+ "l10n_br_fiscal.document.mixin",
57
68
  "mail.thread",
69
+ "mail.activity.mixin",
58
70
  ]
59
71
  _description = "Fiscal Document"
60
72
  _check_company_auto = True
@@ -85,9 +97,9 @@ class Document(models.Model):
85
97
  )
86
98
 
87
99
  fiscal_operation_id = fields.Many2one(
88
- domain="[('state', '=', 'approved'), "
89
- "'|', ('fiscal_operation_type', '=', fiscal_operation_type),"
90
- " ('fiscal_operation_type', '=', 'all')]",
100
+ "l10n_br_fiscal.operation",
101
+ string="Fiscal Operation",
102
+ domain="[('state', '=', 'approved')]",
91
103
  )
92
104
 
93
105
  fiscal_operation_type = fields.Selection(
@@ -112,10 +124,6 @@ class Document(models.Model):
112
124
  default=lambda self: self.env.user,
113
125
  )
114
126
 
115
- operation_name = fields.Char(
116
- copy=False,
117
- )
118
-
119
127
  document_electronic = fields.Boolean(
120
128
  related="document_type_id.electronic",
121
129
  string="Electronic?",
@@ -136,8 +144,16 @@ class Document(models.Model):
136
144
  partner_id = fields.Many2one(
137
145
  comodel_name="res.partner",
138
146
  string="Partner",
147
+ inverse="_inverse_partner_id",
139
148
  )
140
149
 
150
+ @api.onchange("partner_id")
151
+ def _inverse_partner_id(self):
152
+ for doc in self:
153
+ for line in doc.fiscal_line_ids:
154
+ if line.partner_id != doc.partner_id:
155
+ line.partner_id = doc.partner_id
156
+
141
157
  partner_shipping_id = fields.Many2one(
142
158
  comodel_name="res.partner",
143
159
  string="Shipping Address",
@@ -161,6 +177,24 @@ class Document(models.Model):
161
177
  selection=EDOC_PURPOSE,
162
178
  string="Finalidade",
163
179
  default=EDOC_PURPOSE_NORMAL,
180
+ compute="_compute_edoc_purpose",
181
+ store=True,
182
+ precompute=True,
183
+ )
184
+
185
+ edoc_refund_debit_type = fields.Selection(
186
+ selection=EDOC_REFUND_DEBIT_TYPE,
187
+ string="Tipo de Nota de Débito",
188
+ )
189
+
190
+ edoc_refund_credit_type = fields.Selection(
191
+ selection=EDOC_REFUND_CREDIT_TYPE,
192
+ string="Tipo de Nota de Crédito",
193
+ )
194
+
195
+ public_entity_type = fields.Selection(
196
+ selection=PUBLIC_ENTIRY_TYPE,
197
+ string="Tipo de Entidade Governamental",
164
198
  )
165
199
 
166
200
  document_type = fields.Char(
@@ -178,9 +212,9 @@ class Document(models.Model):
178
212
  )
179
213
 
180
214
  currency_id = fields.Many2one(
215
+ related="company_id.currency_id",
181
216
  comodel_name="res.currency",
182
217
  string="Currency",
183
- compute="_compute_currency_id",
184
218
  )
185
219
 
186
220
  # this related "state" field is required for the status bar widget
@@ -188,23 +222,6 @@ class Document(models.Model):
188
222
  # of objects where the fiscal mixin might be injected.
189
223
  state = fields.Selection(related="state_edoc", string="State")
190
224
 
191
- issuer = fields.Selection(
192
- selection=DOCUMENT_ISSUER,
193
- default=DOCUMENT_ISSUER_COMPANY,
194
- )
195
-
196
- document_subsequent_ids = fields.One2many(
197
- comodel_name="l10n_br_fiscal.subsequent.document",
198
- inverse_name="source_document_id",
199
- copy=True,
200
- )
201
-
202
- document_subsequent_generated = fields.Boolean(
203
- string="Subsequent documents generated?",
204
- compute="_compute_document_subsequent_generated",
205
- default=False,
206
- )
207
-
208
225
  transport_modal = fields.Selection(
209
226
  selection=[
210
227
  ("01", "Rodoviário"),
@@ -227,6 +244,29 @@ class Document(models.Model):
227
244
  ],
228
245
  string="Tomador do Serviço",
229
246
  )
247
+ partner_legal_name = fields.Char(
248
+ string="Legal Name",
249
+ related="partner_id.legal_name",
250
+ )
251
+ partner_cnpj_cpf = fields.Char(
252
+ string="CNPJ",
253
+ related="partner_id.vat",
254
+ )
255
+ partner_l10n_br_ie_code = fields.Char(
256
+ string="State Tax Number",
257
+ related="partner_id.l10n_br_ie_code",
258
+ )
259
+
260
+ processador_edoc = fields.Selection(
261
+ related="company_id.processador_edoc",
262
+ )
263
+ company_l10n_br_ie_code_st = fields.Char(
264
+ string="Company ST State Tax Number",
265
+ )
266
+
267
+ fiscal_additional_data = fields.Text()
268
+
269
+ customer_additional_data = fields.Text()
230
270
 
231
271
  @api.constrains("document_key")
232
272
  def _check_key(self):
@@ -256,9 +296,9 @@ class Document(models.Model):
256
296
 
257
297
  if documents:
258
298
  raise ValidationError(
259
- _(
260
- "There is already a fiscal document with this " "key: {} !"
261
- ).format(record.document_key)
299
+ _("There is already a fiscal document with this key: {} !").format(
300
+ record.document_key
301
+ )
262
302
  )
263
303
  else:
264
304
  ChaveEdoc(chave=record.document_key, validar=True)
@@ -299,10 +339,18 @@ class Document(models.Model):
299
339
  )
300
340
  )
301
341
 
302
- @api.depends("company_id")
303
- def _compute_currency_id(self):
304
- for doc in self:
305
- doc.currency_id = doc.company_id.currency_id or self.env.company.currency_id
342
+ @api.onchange("fiscal_operation_type")
343
+ def _onchange_fiscal_operation_type(self):
344
+ domain = [("state", "=", "approved")]
345
+ if self.fiscal_operation_type:
346
+ domain.append(("fiscal_operation_type", "=", self.fiscal_operation_type))
347
+ if (
348
+ self.fiscal_operation_id
349
+ and self.fiscal_operation_id.fiscal_operation_type
350
+ != self.fiscal_operation_type
351
+ ):
352
+ self.fiscal_operation_id = False
353
+ return {"domain": {"fiscal_operation_id": domain}}
306
354
 
307
355
  def _compute_document_name(self):
308
356
  self.ensure_one()
@@ -323,19 +371,19 @@ class Document(models.Model):
323
371
  name += "/" + type_serie_number
324
372
  if self.document_date:
325
373
  name += " - " + self.document_date.strftime("%d/%m/%Y")
326
- if not self.partner_cnpj_cpf:
374
+ if not self.partner_id.vat:
327
375
  name += " - " + _("Unidentified Consumer")
328
- elif self.partner_legal_name:
329
- name += " - " + self.partner_legal_name
330
- name += " - " + self.partner_cnpj_cpf
376
+ elif self.partner_id.legal_name:
377
+ name += " - " + self.partner_id.legal_name
378
+ name += " - " + self.partner_id.vat
331
379
  else:
332
- name += " - " + self.partner_name
333
- name += " - " + self.partner_cnpj_cpf
380
+ name += " - " + self.partner_id.name
381
+ name += " - " + self.partner_id.vat
334
382
  elif self._context.get("fiscal_document_no_company"):
335
383
  name += type_serie_number
336
384
  else:
337
385
  name += "{name}/{type_serie_number}".format(
338
- name=self.company_name or "",
386
+ name=self.company_id.name or "",
339
387
  type_serie_number=type_serie_number,
340
388
  )
341
389
  return name
@@ -359,22 +407,9 @@ class Document(models.Model):
359
407
  for r in self:
360
408
  r.name = r._compute_document_name()
361
409
 
362
- @api.depends(
363
- "fiscal_line_ids.estimate_tax",
364
- "fiscal_line_ids.price_gross",
365
- "fiscal_line_ids.amount_untaxed",
366
- "fiscal_line_ids.amount_tax",
367
- "fiscal_line_ids.amount_taxed",
368
- "fiscal_line_ids.amount_total",
369
- "fiscal_line_ids.financial_total",
370
- "fiscal_line_ids.financial_total_gross",
371
- "fiscal_line_ids.financial_discount_value",
372
- "fiscal_line_ids.amount_tax_included",
373
- "fiscal_line_ids.amount_tax_not_included",
374
- "fiscal_line_ids.amount_tax_withholding",
375
- )
376
- def _compute_fiscal_amount(self):
377
- return super()._compute_fiscal_amount()
410
+ @api.model
411
+ def _get_fiscal_lines_field_name(self):
412
+ return "fiscal_line_ids"
378
413
 
379
414
  def unlink(self):
380
415
  forbidden_states_unlink = [
@@ -403,14 +438,12 @@ class Document(models.Model):
403
438
  if not fsc_op:
404
439
  raise ValidationError(
405
440
  _(
406
- "The fiscal operation {} has no return Fiscal "
407
- "Operation defined"
441
+ "The fiscal operation {} has no return Fiscal Operation defined"
408
442
  ).format(record.fiscal_operation_id)
409
443
  )
410
444
 
411
445
  new_doc = record.copy()
412
446
  new_doc.fiscal_operation_id = fsc_op
413
- new_doc._onchange_fiscal_operation_id()
414
447
 
415
448
  for line in new_doc.fiscal_line_ids:
416
449
  fsc_op_line = line.fiscal_operation_id.return_fiscal_operation_id
@@ -422,9 +455,6 @@ class Document(models.Model):
422
455
  ).format(line.fiscal_operation_id)
423
456
  )
424
457
  line.fiscal_operation_id = fsc_op_line
425
- line._onchange_fiscal_operation_id()
426
- line._onchange_fiscal_operation_line_id()
427
-
428
458
  return_docs |= new_doc
429
459
  return return_docs
430
460
 
@@ -469,152 +499,31 @@ class Document(models.Model):
469
499
  # see https://github.com/OCA/l10n-brazil/pull/3272
470
500
  pass
471
501
 
472
- def _get_email_template(self, state):
473
- self.ensure_one()
474
- return self.document_type_id.document_email_ids.search(
475
- [
476
- "|",
477
- ("state_edoc", "=", False),
478
- ("state_edoc", "=", state),
479
- ("issuer", "=", self.issuer),
480
- "|",
481
- ("document_type_id", "=", False),
482
- ("document_type_id", "=", self.document_type_id.id),
483
- ],
484
- limit=1,
485
- order="state_edoc, document_type_id",
486
- ).mapped("email_template_id")
487
-
488
- def send_email(self, state):
489
- self.ensure_one()
490
- email_template = self._get_email_template(state)
491
- if email_template:
492
- email_template.with_context(
493
- default_attachment_ids=self._get_mail_attachment()
494
- ).send_mail(self.id)
495
-
496
- def _after_change_state(self, old_state, new_state):
497
- self.ensure_one()
498
- result = super()._after_change_state(old_state, new_state)
499
- self.send_email(new_state)
500
- return result
501
-
502
- @api.onchange("fiscal_operation_id")
503
- def _onchange_fiscal_operation_id(self):
504
- result = super()._onchange_fiscal_operation_id()
505
- if self.fiscal_operation_id:
506
- self.fiscal_operation_type = self.fiscal_operation_id.fiscal_operation_type
507
- self.edoc_purpose = self.fiscal_operation_id.edoc_purpose
508
-
509
- if self.issuer == DOCUMENT_ISSUER_COMPANY and not self.document_type_id:
510
- self.document_type_id = self.company_id.document_type_id
511
-
512
- subsequent_documents = [(6, 0, {})]
513
- for subsequent_id in self.fiscal_operation_id.mapped(
514
- "operation_subsequent_ids"
515
- ):
516
- subsequent_documents.append(
517
- (
518
- 0,
519
- 0,
520
- {
521
- "source_document_id": self.id,
522
- "subsequent_operation_id": subsequent_id.id,
523
- "fiscal_operation_id": subsequent_id.subsequent_operation_id.id,
524
- },
525
- )
526
- )
527
- self.document_subsequent_ids = subsequent_documents
528
- return result
502
+ @api.depends("fiscal_operation_id")
503
+ def _compute_edoc_purpose(self):
504
+ for record in self:
505
+ record.edoc_purpose = record.fiscal_operation_id.edoc_purpose
529
506
 
530
- def _prepare_referenced_subsequent(self, doc_referenced):
531
- self.ensure_one()
507
+ def __document_comment_vals(self):
532
508
  return {
533
- "document_id": self.id,
534
- "document_related_id": doc_referenced.id,
535
- "document_type_id": doc_referenced.document_type_id.id,
536
- "document_serie": doc_referenced.document_serie,
537
- "document_number": doc_referenced.document_number,
538
- "document_date": doc_referenced.document_date,
539
- "document_key": doc_referenced.document_key,
509
+ "user": self.env.user,
510
+ "ctx": self._context,
511
+ "doc": self,
540
512
  }
541
513
 
542
- def _document_reference(self, documents_referenced):
543
- self.ensure_one()
544
- for doc_referenced in documents_referenced:
545
- self.env["l10n_br_fiscal.document.related"].create(
546
- self._prepare_referenced_subsequent(doc_referenced)
514
+ def _document_comment(self):
515
+ for d in self:
516
+ # Fiscal Comments
517
+ d.fiscal_additional_data = d.comment_ids.filtered(
518
+ lambda c: c.comment_type == COMMENT_TYPE_FISCAL
519
+ ).compute_message(
520
+ d.__document_comment_vals(), d.manual_fiscal_additional_data
547
521
  )
548
522
 
549
- @api.depends("document_subsequent_ids.subsequent_document_id")
550
- def _compute_document_subsequent_generated(self):
551
- for document in self:
552
- if not document.document_subsequent_ids:
553
- document.document_subsequent_generated = False
554
- else:
555
- document.document_subsequent_generated = all(
556
- subsequent_id.operation_performed
557
- for subsequent_id in document.document_subsequent_ids
558
- )
559
-
560
- def _generates_subsequent_operations(self):
561
- for record in self.filtered(lambda doc: not doc.document_subsequent_generated):
562
- for subsequent_id in record.document_subsequent_ids.filtered(
563
- lambda doc_sub: doc_sub._confirms_document_generation()
564
- ):
565
- subsequent_id.generate_subsequent_document()
566
-
567
- def cancel_edoc(self):
568
- self.ensure_one()
569
- if any(
570
- doc.state_edoc == SITUACAO_EDOC_AUTORIZADA
571
- for doc in self.document_subsequent_ids.mapped("document_subsequent_ids")
572
- ):
573
- message = _(
574
- "Canceling the document is not allowed: one or more "
575
- "associated documents have already been authorized."
523
+ # Commercial Comments
524
+ d.customer_additional_data = d.comment_ids.filtered(
525
+ lambda c: c.comment_type == COMMENT_TYPE_COMMERCIAL
526
+ ).compute_message(
527
+ d.__document_comment_vals(), d.manual_customer_additional_data
576
528
  )
577
- raise UserWarning(message)
578
-
579
- def _get_mail_attachment(self):
580
- self.ensure_one()
581
- attachment_ids = []
582
- if self.state_edoc == SITUACAO_EDOC_AUTORIZADA:
583
- if self.file_report_id:
584
- attachment_ids.append(self.file_report_id.id)
585
- if self.authorization_file_id:
586
- attachment_ids.append(self.authorization_file_id.id)
587
- return attachment_ids
588
-
589
- def action_send_email(self):
590
- """Open a window to compose an email, with the fiscal document_type
591
- template message loaded by default
592
- """
593
- self.ensure_one()
594
- template = self._get_email_template(self.state)
595
- compose_form = self.env.ref("mail.email_compose_message_wizard_form", False)
596
- lang = self.env.context.get("lang")
597
- if template and template.lang:
598
- lang = template._render_template(template.lang, self._name, [self.id])
599
- self = self.with_context(lang=lang)
600
- ctx = dict(
601
- default_model="l10n_br_fiscal.document",
602
- default_res_id=self.id,
603
- default_use_template=bool(template),
604
- default_attachment_ids=self._get_mail_attachment(),
605
- default_template_id=template and template.id or False,
606
- default_composition_mode="comment",
607
- model_description=self.document_type_id.name or self._name,
608
- force_email=True,
609
- )
610
- return {
611
- "name": _("Send Fiscal Document Email Notification"),
612
- "type": "ir.actions.act_window",
613
- "view_type": "form",
614
- "view_mode": "form",
615
- "res_model": "mail.compose.message",
616
- "views": [(compose_form.id, "form")],
617
- "view_id": compose_form.id,
618
- "target": "new",
619
- "context": ctx,
620
- }
529
+ d.fiscal_line_ids._document_comment()
@@ -1,10 +1,25 @@
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 fields, models
4
+ from odoo import api, fields, models
5
5
 
6
6
 
7
7
  class DocumentLine(models.Model):
8
+ """
9
+ Represents a line item within a Brazilian fiscal document.
10
+
11
+ This model defines the core structure of a fiscal document line,
12
+ primarily linking it to its parent document (`l10n_br_fiscal.document`)
13
+ and holding essential line-specific data like quantity and a
14
+ descriptive name.
15
+
16
+ The vast majority of detailed fiscal fields (e.g., product, NCM,
17
+ CFOP, various tax bases and values) and their complex computation
18
+ logic are inherited from `l10n_br_fiscal.document.line.mixin`.
19
+ This delegation ensures code reusability and keeps this model
20
+ focused on its direct relationships and core line properties.
21
+ """
22
+
8
23
  _name = "l10n_br_fiscal.document.line"
9
24
  _inherit = "l10n_br_fiscal.document.line.mixin"
10
25
  _description = "Fiscal Document Line"
@@ -15,12 +30,18 @@ class DocumentLine(models.Model):
15
30
  ondelete="cascade",
16
31
  )
17
32
 
18
- name = fields.Text()
33
+ name = fields.Char(
34
+ compute="_compute_name",
35
+ store=True,
36
+ precompute=True,
37
+ readonly=False,
38
+ )
19
39
 
20
40
  company_id = fields.Many2one(
21
41
  comodel_name="res.company",
22
42
  related="document_id.company_id",
23
43
  store=True,
44
+ precompute=True,
24
45
  string="Company",
25
46
  )
26
47
 
@@ -29,13 +50,36 @@ class DocumentLine(models.Model):
29
50
  )
30
51
 
31
52
  partner_id = fields.Many2one(
32
- related="document_id.partner_id",
53
+ comodel_name="res.partner",
54
+ compute="_compute_partner_id",
33
55
  store=True,
56
+ precompute=True,
57
+ readonly=False,
34
58
  )
35
59
 
36
- quantity = fields.Float(default=1.0)
60
+ # Do not depend on `document_id.partner_id`, the inverse is taking care of that
61
+ def _compute_partner_id(self):
62
+ for line in self:
63
+ line.partner_id = line.document_id.partner_id
64
+
65
+ uom_id = fields.Many2one(
66
+ comodel_name="uom.uom",
67
+ string="UOM",
68
+ compute="_compute_uom_id",
69
+ store=True,
70
+ readonly=False,
71
+ precompute=True,
72
+ )
37
73
 
38
- ind_final = fields.Selection(related="document_id.ind_final")
74
+ price_unit = fields.Float(
75
+ digits="Product Price",
76
+ compute="_compute_price_unit_fiscal",
77
+ store=True,
78
+ precompute=True,
79
+ readonly=False,
80
+ )
81
+
82
+ quantity = fields.Float(default=1.0)
39
83
 
40
84
  # Usado para tornar Somente Leitura os campos dos custos
41
85
  # de entrega quando a definição for por Total
@@ -50,3 +94,36 @@ class DocumentLine(models.Model):
50
94
  edoc_purpose = fields.Selection(
51
95
  related="document_id.edoc_purpose",
52
96
  )
97
+
98
+ additional_data = fields.Text()
99
+
100
+ @api.depends("product_id")
101
+ def _compute_name(self):
102
+ for line in self:
103
+ if line.product_id:
104
+ line.name = line.product_id.display_name
105
+ else:
106
+ line.name = False
107
+
108
+ @api.depends("product_id")
109
+ def _compute_uom_id(self):
110
+ for line in self:
111
+ if line.fiscal_operation_type == "in":
112
+ line.uom_id = line.product_id.uom_po_id
113
+ else:
114
+ line.uom_id = line.product_id.uom_id
115
+
116
+ def __document_comment_vals(self):
117
+ self.ensure_one()
118
+ return {
119
+ "user": self.env.user,
120
+ "ctx": self._context,
121
+ "doc": self.document_id if hasattr(self, "document_id") else None,
122
+ "item": self,
123
+ }
124
+
125
+ def _document_comment(self):
126
+ for line in self:
127
+ line.additional_data = line.comment_ids.compute_message(
128
+ line.__document_comment_vals(), line.manual_additional_data
129
+ )