odoo-addon-l10n-br-fiscal 16.0.8.0.2__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.
- odoo/addons/l10n_br_fiscal/README.rst +1 -1
- odoo/addons/l10n_br_fiscal/__manifest__.py +10 -3
- odoo/addons/l10n_br_fiscal/constants/fiscal.py +64 -18
- odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.cest.csv +1043 -983
- odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.cst.csv +58 -0
- odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.document.type.csv +1 -0
- odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.operation.indicator.csv +27 -0
- odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.tax.classification.csv +163 -0
- odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.tax.csv +31 -0
- odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.tax.group.csv +3 -0
- odoo/addons/l10n_br_fiscal/data/operation_data.xml +1 -1
- odoo/addons/l10n_br_fiscal/data/uom_data.xml +186 -33
- odoo/addons/l10n_br_fiscal/demo/fiscal_document_demo.xml +3 -377
- odoo/addons/l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml +0 -12
- odoo/addons/l10n_br_fiscal/demo/fiscal_operation_demo.xml +2 -2
- odoo/addons/l10n_br_fiscal/i18n/l10n_br_fiscal.pot +902 -304
- odoo/addons/l10n_br_fiscal/i18n/pt_BR.po +22 -22
- odoo/addons/l10n_br_fiscal/migrations/16.0.13.0.0/pre-migration.py +25 -0
- odoo/addons/l10n_br_fiscal/migrations/16.0.14.0.0/pre-migration.py +30 -0
- odoo/addons/l10n_br_fiscal/migrations/16.0.14.0.5/pre-migration.py +15 -0
- odoo/addons/l10n_br_fiscal/models/__init__.py +3 -2
- odoo/addons/l10n_br_fiscal/models/comment.py +2 -2
- odoo/addons/l10n_br_fiscal/models/data_ncm_nbs_abstract.py +1 -1
- odoo/addons/l10n_br_fiscal/models/document.py +83 -226
- odoo/addons/l10n_br_fiscal/models/document_line.py +67 -5
- odoo/addons/l10n_br_fiscal/models/document_line_mixin.py +1932 -136
- odoo/addons/l10n_br_fiscal/models/document_mixin.py +248 -17
- odoo/addons/l10n_br_fiscal/models/document_related.py +11 -8
- odoo/addons/l10n_br_fiscal/models/document_serie.py +33 -0
- odoo/addons/l10n_br_fiscal/models/ibpt.py +1 -1
- odoo/addons/l10n_br_fiscal/models/icms_regulation.py +1 -1
- odoo/addons/l10n_br_fiscal/models/invalidate_number.py +4 -5
- odoo/addons/l10n_br_fiscal/models/operation_dashboard.py +3 -2
- odoo/addons/l10n_br_fiscal/models/operation_indicator.py +58 -0
- odoo/addons/l10n_br_fiscal/models/operation_line.py +28 -0
- odoo/addons/l10n_br_fiscal/models/partner_profile.py +6 -0
- odoo/addons/l10n_br_fiscal/models/product_template.py +5 -1
- odoo/addons/l10n_br_fiscal/models/res_company.py +18 -0
- odoo/addons/l10n_br_fiscal/models/res_partner.py +27 -6
- odoo/addons/l10n_br_fiscal/models/simplified_tax_range.py +8 -0
- odoo/addons/l10n_br_fiscal/models/tax.py +7 -3
- odoo/addons/l10n_br_fiscal/models/tax_classification.py +81 -0
- odoo/addons/l10n_br_fiscal/models/tax_pis_cofins_base.py +1 -1
- odoo/addons/l10n_br_fiscal/models/tax_pis_cofins_credit.py +1 -1
- odoo/addons/l10n_br_fiscal/models/uom_uom.py +24 -0
- odoo/addons/l10n_br_fiscal/security/fiscal_security.xml +6 -16
- odoo/addons/l10n_br_fiscal/security/ir.model.access.csv +8 -2
- odoo/addons/l10n_br_fiscal/static/description/index.html +1 -1
- odoo/addons/l10n_br_fiscal/tests/__init__.py +3 -0
- odoo/addons/l10n_br_fiscal/tests/test_document_edition.py +308 -0
- odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_generic.py +23 -111
- odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_nfse.py +5 -13
- odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_serie.py +60 -0
- odoo/addons/l10n_br_fiscal/tests/test_ibpt.py +2 -2
- odoo/addons/l10n_br_fiscal/tests/test_icms_regulation.py +2 -2
- odoo/addons/l10n_br_fiscal/tests/test_tax_benefit.py +14 -20
- odoo/addons/l10n_br_fiscal/tests/test_tax_classification.py +110 -0
- odoo/addons/l10n_br_fiscal/tools.py +1 -1
- odoo/addons/l10n_br_fiscal/views/cest_view.xml +2 -4
- odoo/addons/l10n_br_fiscal/views/cfop_view.xml +3 -5
- odoo/addons/l10n_br_fiscal/views/city_taxation_code.xml +1 -4
- odoo/addons/l10n_br_fiscal/views/cnae_view.xml +2 -4
- odoo/addons/l10n_br_fiscal/views/comment_view.xml +2 -4
- odoo/addons/l10n_br_fiscal/views/cst_view.xml +6 -8
- odoo/addons/l10n_br_fiscal/views/{document_fiscal_line_mixin_view.xml → document_line_mixin_view.xml} +525 -388
- odoo/addons/l10n_br_fiscal/views/document_line_view.xml +101 -82
- odoo/addons/l10n_br_fiscal/views/document_related_view.xml +44 -46
- odoo/addons/l10n_br_fiscal/views/document_serie_view.xml +2 -6
- odoo/addons/l10n_br_fiscal/views/document_type_view.xml +0 -2
- odoo/addons/l10n_br_fiscal/views/document_view.xml +304 -346
- odoo/addons/l10n_br_fiscal/views/icms_regulation_view.xml +14 -16
- odoo/addons/l10n_br_fiscal/views/icms_relief_view.xml +8 -10
- odoo/addons/l10n_br_fiscal/views/invalidate_number_view.xml +46 -48
- odoo/addons/l10n_br_fiscal/views/l10n_br_fiscal_action.xml +162 -244
- odoo/addons/l10n_br_fiscal/views/l10n_br_fiscal_menu.xml +16 -72
- odoo/addons/l10n_br_fiscal/views/legal_nature_view.xml +0 -2
- odoo/addons/l10n_br_fiscal/views/nbm_view.xml +5 -6
- odoo/addons/l10n_br_fiscal/views/nbs_view.xml +5 -6
- odoo/addons/l10n_br_fiscal/views/ncm_view.xml +12 -15
- odoo/addons/l10n_br_fiscal/views/operation_dashboard_view.xml +13 -12
- odoo/addons/l10n_br_fiscal/views/operation_indicator_view.xml +75 -0
- odoo/addons/l10n_br_fiscal/views/operation_line_view.xml +22 -21
- odoo/addons/l10n_br_fiscal/views/operation_view.xml +3 -6
- odoo/addons/l10n_br_fiscal/views/partner_profile_view.xml +3 -6
- odoo/addons/l10n_br_fiscal/views/product_genre_view.xml +7 -9
- odoo/addons/l10n_br_fiscal/views/product_product_view.xml +37 -14
- odoo/addons/l10n_br_fiscal/views/product_template_view.xml +34 -14
- odoo/addons/l10n_br_fiscal/views/res_company_view.xml +40 -38
- odoo/addons/l10n_br_fiscal/views/res_config_settings_view.xml +23 -28
- odoo/addons/l10n_br_fiscal/views/res_partner_view.xml +13 -2
- odoo/addons/l10n_br_fiscal/views/service_type_view.xml +7 -8
- odoo/addons/l10n_br_fiscal/views/simplified_tax_range_view.xml +0 -2
- odoo/addons/l10n_br_fiscal/views/simplified_tax_view.xml +0 -2
- odoo/addons/l10n_br_fiscal/views/tax_classification.xml +110 -0
- odoo/addons/l10n_br_fiscal/views/tax_definition_view.xml +157 -129
- odoo/addons/l10n_br_fiscal/views/tax_estimate_view.xml +0 -2
- odoo/addons/l10n_br_fiscal/views/tax_group_view.xml +3 -6
- odoo/addons/l10n_br_fiscal/views/tax_ipi_control_seal_view.xml +0 -2
- odoo/addons/l10n_br_fiscal/views/tax_ipi_guideline_class_view.xml +0 -2
- odoo/addons/l10n_br_fiscal/views/tax_ipi_guideline_view.xml +2 -4
- odoo/addons/l10n_br_fiscal/views/tax_pis_cofins_base_view.xml +2 -4
- odoo/addons/l10n_br_fiscal/views/tax_pis_cofins_credit_view.xml +2 -4
- odoo/addons/l10n_br_fiscal/views/tax_pis_cofins_view.xml +5 -7
- odoo/addons/l10n_br_fiscal/views/tax_view.xml +5 -7
- odoo/addons/l10n_br_fiscal/views/uom_uom.xml +52 -0
- odoo/addons/l10n_br_fiscal/wizards/__init__.py +1 -0
- odoo/addons/l10n_br_fiscal/wizards/base_wizard_mixin.py +1 -1
- odoo/addons/l10n_br_fiscal/wizards/document_import_wizard_mixin.py +129 -0
- odoo/addons/l10n_br_fiscal/wizards/document_import_wizard_mixin.xml +41 -0
- {odoo_addon_l10n_br_fiscal-16.0.8.0.2.dist-info → odoo_addon_l10n_br_fiscal-16.0.19.4.0.dist-info}/METADATA +3 -3
- {odoo_addon_l10n_br_fiscal-16.0.8.0.2.dist-info → odoo_addon_l10n_br_fiscal-16.0.19.4.0.dist-info}/RECORD +113 -99
- {odoo_addon_l10n_br_fiscal-16.0.8.0.2.dist-info → odoo_addon_l10n_br_fiscal-16.0.19.4.0.dist-info}/WHEEL +1 -1
- odoo/addons/l10n_br_fiscal/models/document_line_mixin_methods.py +0 -814
- odoo/addons/l10n_br_fiscal/models/document_mixin_methods.py +0 -363
- {odoo_addon_l10n_br_fiscal-16.0.8.0.2.dist-info → odoo_addon_l10n_br_fiscal-16.0.19.4.0.dist-info}/top_level.txt +0 -0
|
@@ -8,7 +8,6 @@ from ..constants.fiscal import (
|
|
|
8
8
|
DOCUMENT_ISSUER,
|
|
9
9
|
DOCUMENT_ISSUER_COMPANY,
|
|
10
10
|
FINAL_CUSTOMER,
|
|
11
|
-
FINAL_CUSTOMER_YES,
|
|
12
11
|
FISCAL_COMMENT_DOCUMENT,
|
|
13
12
|
NFE_IND_PRES,
|
|
14
13
|
NFE_IND_PRES_DEFAULT,
|
|
@@ -33,7 +32,6 @@ class FiscalDocumentMixin(models.AbstractModel):
|
|
|
33
32
|
- Inverse methods for distributing header-level costs (freight, insurance)
|
|
34
33
|
to lines.
|
|
35
34
|
- Hooks for customizing data retrieval (e.g., lines, fiscal partner).
|
|
36
|
-
- Onchange helpers for common fiscal fields.
|
|
37
35
|
|
|
38
36
|
Models using this mixin are often expected to also include fields defined
|
|
39
37
|
in `l10n_br_fiscal.document.mixin` for methods like
|
|
@@ -43,16 +41,11 @@ class FiscalDocumentMixin(models.AbstractModel):
|
|
|
43
41
|
"""
|
|
44
42
|
|
|
45
43
|
_name = "l10n_br_fiscal.document.mixin"
|
|
46
|
-
_inherit = "l10n_br_fiscal.document.mixin.methods"
|
|
47
44
|
_description = "Document Fiscal Mixin Fields"
|
|
48
45
|
|
|
49
46
|
def _date_server_format(self):
|
|
50
47
|
return fields.Datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
|
51
48
|
|
|
52
|
-
@api.model
|
|
53
|
-
def _default_operation(self):
|
|
54
|
-
return False
|
|
55
|
-
|
|
56
49
|
@api.model
|
|
57
50
|
def _operation_domain(self):
|
|
58
51
|
domain = (
|
|
@@ -63,11 +56,222 @@ class FiscalDocumentMixin(models.AbstractModel):
|
|
|
63
56
|
)
|
|
64
57
|
return domain
|
|
65
58
|
|
|
59
|
+
def _prepare_br_fiscal_dict(self, default=False):
|
|
60
|
+
self.ensure_one()
|
|
61
|
+
fields = self.env["l10n_br_fiscal.document.mixin"]._fields.keys()
|
|
62
|
+
|
|
63
|
+
# we now read the record fiscal fields except the m2m tax:
|
|
64
|
+
vals = self._convert_to_write(self.read(fields)[0])
|
|
65
|
+
|
|
66
|
+
# remove id field to avoid conflicts
|
|
67
|
+
vals.pop("id", None)
|
|
68
|
+
|
|
69
|
+
if default: # in case you want to use new rather than write later
|
|
70
|
+
return {f"default_{k}": vals[k] for k in vals.keys()}
|
|
71
|
+
return vals
|
|
72
|
+
|
|
73
|
+
@api.onchange("document_type_id")
|
|
74
|
+
def _onchange_document_type_id(self):
|
|
75
|
+
if self.document_type_id and self.issuer == DOCUMENT_ISSUER_COMPANY:
|
|
76
|
+
self.document_serie_id = self.document_type_id.get_document_serie(
|
|
77
|
+
self.company_id, self.fiscal_operation_id
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
@api.depends("fiscal_operation_id")
|
|
81
|
+
def _compute_document_type_id(self):
|
|
82
|
+
for doc in self.filtered(lambda doc: doc.fiscal_operation_id):
|
|
83
|
+
if doc.issuer == DOCUMENT_ISSUER_COMPANY and not doc.document_type_id:
|
|
84
|
+
doc.document_type_id = doc.company_id.document_type_id
|
|
85
|
+
|
|
86
|
+
def _get_amount_lines(self):
|
|
87
|
+
"""Get object lines instances used to compute fiscal fields"""
|
|
88
|
+
return self.mapped(self._get_fiscal_lines_field_name())
|
|
89
|
+
|
|
90
|
+
def _get_product_amount_lines(self):
|
|
91
|
+
fiscal_line_ids = self._get_amount_lines()
|
|
92
|
+
return fiscal_line_ids.filtered(lambda line: line.product_id.type != "service")
|
|
93
|
+
|
|
94
|
+
@api.model
|
|
95
|
+
def _get_amount_fields(self):
|
|
96
|
+
"""Get all fields with 'amount_' prefix"""
|
|
97
|
+
fields = self.env["l10n_br_fiscal.document.mixin"]._fields.keys()
|
|
98
|
+
prefixes = ("amount_", "fiscal_amount_")
|
|
99
|
+
amount_fields = [f for f in fields if f.startswith(prefixes)]
|
|
100
|
+
return amount_fields
|
|
101
|
+
|
|
102
|
+
@api.depends("document_serie_id", "issuer")
|
|
103
|
+
def _compute_document_serie(self):
|
|
104
|
+
for doc in self:
|
|
105
|
+
if doc.document_serie_id and doc.issuer == DOCUMENT_ISSUER_COMPANY:
|
|
106
|
+
doc.document_serie = doc.document_serie_id.code
|
|
107
|
+
elif doc.document_serie is None:
|
|
108
|
+
doc.document_serie = False
|
|
109
|
+
|
|
110
|
+
@api.depends("document_type_id", "issuer")
|
|
111
|
+
def _compute_document_serie_id(self):
|
|
112
|
+
for doc in self:
|
|
113
|
+
if (
|
|
114
|
+
not doc.document_serie_id
|
|
115
|
+
and doc.document_type_id
|
|
116
|
+
and doc.issuer == DOCUMENT_ISSUER_COMPANY
|
|
117
|
+
):
|
|
118
|
+
doc.document_serie_id = doc.document_type_id.get_document_serie(
|
|
119
|
+
doc.company_id, doc.fiscal_operation_id
|
|
120
|
+
)
|
|
121
|
+
elif doc.document_serie_id is None:
|
|
122
|
+
doc.document_serie_id = False
|
|
123
|
+
|
|
124
|
+
@api.model
|
|
125
|
+
def _get_fiscal_lines_field_name(self):
|
|
126
|
+
return "fiscal_line_ids"
|
|
127
|
+
|
|
128
|
+
def _get_fiscal_amount_field_dependencies(self):
|
|
129
|
+
"""
|
|
130
|
+
Dynamically get the list of field dependencies.
|
|
131
|
+
"""
|
|
132
|
+
if self._abstract:
|
|
133
|
+
return []
|
|
134
|
+
o2m_field_name = self._get_fiscal_lines_field_name()
|
|
135
|
+
target_fields = []
|
|
136
|
+
for field in self._get_amount_fields():
|
|
137
|
+
if (
|
|
138
|
+
field.replace("amount_", "")
|
|
139
|
+
in getattr(self, o2m_field_name)._fields.keys()
|
|
140
|
+
):
|
|
141
|
+
target_fields.append(field.replace("amount_", ""))
|
|
142
|
+
|
|
143
|
+
return [o2m_field_name] + [
|
|
144
|
+
f"{o2m_field_name}.{target_field}" for target_field in target_fields
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
@api.depends(lambda self: self._get_fiscal_amount_field_dependencies())
|
|
148
|
+
def _compute_fiscal_amount(self):
|
|
149
|
+
"""
|
|
150
|
+
Compute and sum various fiscal amounts from the document lines.
|
|
151
|
+
|
|
152
|
+
This method iterates over fields prefixed with 'amount_' (as determined
|
|
153
|
+
by `_get_amount_fields`) and sums corresponding values from the lines
|
|
154
|
+
retrieved by `_get_amount_lines`.
|
|
155
|
+
|
|
156
|
+
It handles cases where delivery costs (freight, insurance, other) are
|
|
157
|
+
defined at the document total level rather than per line.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
fields = self._get_amount_fields()
|
|
161
|
+
for doc in self.filtered(lambda m: m.fiscal_operation_id):
|
|
162
|
+
values = {key: 0.0 for key in fields}
|
|
163
|
+
for line in doc._get_amount_lines():
|
|
164
|
+
for field in fields:
|
|
165
|
+
if field in line._fields.keys():
|
|
166
|
+
values[field] += line[field]
|
|
167
|
+
if field.replace("amount_", "") in line._fields.keys():
|
|
168
|
+
# FIXME this field creates an error in invoice form
|
|
169
|
+
if field == "amount_financial_discount_value":
|
|
170
|
+
values["amount_financial_discount_value"] += (
|
|
171
|
+
0 # line.financial_discount_value
|
|
172
|
+
)
|
|
173
|
+
else:
|
|
174
|
+
values[field] += line[field.replace("amount_", "")]
|
|
175
|
+
|
|
176
|
+
# Valores definidos pelo Total e não pela Linha
|
|
177
|
+
if (
|
|
178
|
+
doc.company_id.delivery_costs == "total"
|
|
179
|
+
or doc.force_compute_delivery_costs_by_total
|
|
180
|
+
):
|
|
181
|
+
values["amount_freight_value"] = doc.amount_freight_value
|
|
182
|
+
values["amount_insurance_value"] = doc.amount_insurance_value
|
|
183
|
+
values["amount_other_value"] = doc.amount_other_value
|
|
184
|
+
|
|
185
|
+
doc.update(values)
|
|
186
|
+
|
|
187
|
+
def _get_fiscal_partner(self):
|
|
188
|
+
"""
|
|
189
|
+
Hook method to determine the fiscal partner for the document.
|
|
190
|
+
|
|
191
|
+
This method is designed to be overridden in implementing models if the
|
|
192
|
+
partner relevant for fiscal purposes (e.g., for tax calculations,
|
|
193
|
+
final consumer status) is different from the main `partner_id`
|
|
194
|
+
of the document record. For instance, an invoice might use a specific
|
|
195
|
+
invoicing contact derived from the main partner.
|
|
196
|
+
|
|
197
|
+
:return: A `res.partner` recordset representing the fiscal partner.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
self.ensure_one()
|
|
201
|
+
return self.partner_id
|
|
202
|
+
|
|
203
|
+
@api.depends("partner_id")
|
|
204
|
+
def _compute_ind_final(self):
|
|
205
|
+
for doc in self:
|
|
206
|
+
partner = doc._get_fiscal_partner()
|
|
207
|
+
if partner:
|
|
208
|
+
doc.ind_final = partner.ind_final
|
|
209
|
+
else:
|
|
210
|
+
# Default Value
|
|
211
|
+
doc.ind_final = "1" # Yes
|
|
212
|
+
|
|
213
|
+
@api.onchange("ind_final")
|
|
214
|
+
def _inverse_ind_final(self):
|
|
215
|
+
for doc in self:
|
|
216
|
+
for line in doc._get_amount_lines():
|
|
217
|
+
if line.ind_final != doc.ind_final:
|
|
218
|
+
line.ind_final = doc.ind_final
|
|
219
|
+
|
|
220
|
+
@api.depends("fiscal_operation_id")
|
|
221
|
+
def _compute_operation_name(self):
|
|
222
|
+
for doc in self:
|
|
223
|
+
if doc.fiscal_operation_id:
|
|
224
|
+
doc.operation_name = doc.fiscal_operation_id.name
|
|
225
|
+
else:
|
|
226
|
+
doc.operation_name = False
|
|
227
|
+
|
|
228
|
+
@api.depends("fiscal_operation_id")
|
|
229
|
+
def _compute_comment_ids(self):
|
|
230
|
+
for doc in self:
|
|
231
|
+
if doc.fiscal_operation_id:
|
|
232
|
+
doc.comment_ids = doc.fiscal_operation_id.comment_ids
|
|
233
|
+
elif doc.comment_ids is None:
|
|
234
|
+
doc.comment_ids = []
|
|
235
|
+
|
|
236
|
+
def _distribute_amount_to_lines(self, amount_field_name, line_field_name):
|
|
237
|
+
for record in self:
|
|
238
|
+
if not (
|
|
239
|
+
record.delivery_costs == "total"
|
|
240
|
+
or record.force_compute_delivery_costs_by_total
|
|
241
|
+
):
|
|
242
|
+
continue
|
|
243
|
+
lines = record._get_product_amount_lines()
|
|
244
|
+
if not lines:
|
|
245
|
+
continue
|
|
246
|
+
amount_to_distribute = record[amount_field_name]
|
|
247
|
+
total_gross = sum(lines.mapped("price_gross"))
|
|
248
|
+
if total_gross > 0:
|
|
249
|
+
distributed_amount = 0
|
|
250
|
+
for line in lines[:-1]:
|
|
251
|
+
proportional_amount = record.currency_id.round(
|
|
252
|
+
amount_to_distribute * (line.price_gross / total_gross)
|
|
253
|
+
)
|
|
254
|
+
line[line_field_name] = proportional_amount
|
|
255
|
+
distributed_amount += proportional_amount
|
|
256
|
+
lines[-1][line_field_name] = amount_to_distribute - distributed_amount
|
|
257
|
+
else:
|
|
258
|
+
lines.write({line_field_name: 0.0})
|
|
259
|
+
if lines:
|
|
260
|
+
lines[0][line_field_name] = amount_to_distribute
|
|
261
|
+
|
|
262
|
+
def _inverse_amount_freight(self):
|
|
263
|
+
self._distribute_amount_to_lines("amount_freight_value", "freight_value")
|
|
264
|
+
|
|
265
|
+
def _inverse_amount_insurance(self):
|
|
266
|
+
self._distribute_amount_to_lines("amount_insurance_value", "insurance_value")
|
|
267
|
+
|
|
268
|
+
def _inverse_amount_other(self):
|
|
269
|
+
self._distribute_amount_to_lines("amount_other_value", "other_value")
|
|
270
|
+
|
|
66
271
|
fiscal_operation_id = fields.Many2one(
|
|
67
272
|
comodel_name="l10n_br_fiscal.operation",
|
|
68
273
|
string="Operation",
|
|
69
274
|
domain=lambda self: self._operation_domain(),
|
|
70
|
-
default=_default_operation,
|
|
71
275
|
)
|
|
72
276
|
|
|
73
277
|
operation_name = fields.Char(
|
|
@@ -90,7 +294,6 @@ class FiscalDocumentMixin(models.AbstractModel):
|
|
|
90
294
|
|
|
91
295
|
fiscal_operation_type = fields.Selection(
|
|
92
296
|
related="fiscal_operation_id.fiscal_operation_type",
|
|
93
|
-
readonly=True,
|
|
94
297
|
)
|
|
95
298
|
|
|
96
299
|
ind_pres = fields.Selection(
|
|
@@ -107,14 +310,10 @@ class FiscalDocumentMixin(models.AbstractModel):
|
|
|
107
310
|
store=True,
|
|
108
311
|
)
|
|
109
312
|
|
|
110
|
-
fiscal_additional_data = fields.Text()
|
|
111
|
-
|
|
112
313
|
manual_fiscal_additional_data = fields.Text(
|
|
113
314
|
help="Fiscal Additional data manually entered by user",
|
|
114
315
|
)
|
|
115
316
|
|
|
116
|
-
customer_additional_data = fields.Text()
|
|
117
|
-
|
|
118
317
|
manual_customer_additional_data = fields.Text(
|
|
119
318
|
help="Customer Additional data manually entered by user",
|
|
120
319
|
)
|
|
@@ -122,7 +321,11 @@ class FiscalDocumentMixin(models.AbstractModel):
|
|
|
122
321
|
ind_final = fields.Selection(
|
|
123
322
|
selection=FINAL_CUSTOMER,
|
|
124
323
|
string="Final Consumption Operation",
|
|
125
|
-
|
|
324
|
+
compute="_compute_ind_final",
|
|
325
|
+
inverse="_inverse_ind_final",
|
|
326
|
+
store=True,
|
|
327
|
+
precompute=True,
|
|
328
|
+
readonly=False,
|
|
126
329
|
)
|
|
127
330
|
|
|
128
331
|
currency_id = fields.Many2one(
|
|
@@ -137,7 +340,31 @@ class FiscalDocumentMixin(models.AbstractModel):
|
|
|
137
340
|
help="Amount without discount.",
|
|
138
341
|
)
|
|
139
342
|
|
|
140
|
-
|
|
343
|
+
fiscal_amount_untaxed = fields.Monetary(
|
|
344
|
+
compute="_compute_fiscal_amount",
|
|
345
|
+
store=True,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
amount_ibs_base = fields.Monetary(
|
|
349
|
+
string="IBS Base",
|
|
350
|
+
compute="_compute_fiscal_amount",
|
|
351
|
+
store=True,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
amount_ibs_value = fields.Monetary(
|
|
355
|
+
string="IBS Value",
|
|
356
|
+
compute="_compute_fiscal_amount",
|
|
357
|
+
store=True,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
amount_cbs_base = fields.Monetary(
|
|
361
|
+
string="CBS Base",
|
|
362
|
+
compute="_compute_fiscal_amount",
|
|
363
|
+
store=True,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
amount_cbs_value = fields.Monetary(
|
|
367
|
+
string="CBS Value",
|
|
141
368
|
compute="_compute_fiscal_amount",
|
|
142
369
|
store=True,
|
|
143
370
|
)
|
|
@@ -405,12 +632,12 @@ class FiscalDocumentMixin(models.AbstractModel):
|
|
|
405
632
|
store=True,
|
|
406
633
|
)
|
|
407
634
|
|
|
408
|
-
|
|
635
|
+
fiscal_amount_tax = fields.Monetary(
|
|
409
636
|
compute="_compute_fiscal_amount",
|
|
410
637
|
store=True,
|
|
411
638
|
)
|
|
412
639
|
|
|
413
|
-
|
|
640
|
+
fiscal_amount_total = fields.Monetary(
|
|
414
641
|
compute="_compute_fiscal_amount",
|
|
415
642
|
store=True,
|
|
416
643
|
)
|
|
@@ -481,6 +708,10 @@ class FiscalDocumentMixin(models.AbstractModel):
|
|
|
481
708
|
|
|
482
709
|
document_type_id = fields.Many2one(
|
|
483
710
|
comodel_name="l10n_br_fiscal.document.type",
|
|
711
|
+
compute="_compute_document_type_id",
|
|
712
|
+
store=True,
|
|
713
|
+
precompute=True,
|
|
714
|
+
readonly=False,
|
|
484
715
|
)
|
|
485
716
|
|
|
486
717
|
document_serie_id = fields.Many2one(
|
|
@@ -56,7 +56,7 @@ class DocumentRelated(models.Model):
|
|
|
56
56
|
default="cnpj",
|
|
57
57
|
)
|
|
58
58
|
|
|
59
|
-
|
|
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("
|
|
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,
|
|
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.
|
|
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,7 +115,7 @@ class DocumentRelated(models.Model):
|
|
|
112
115
|
self.cnpj_cpf = False
|
|
113
116
|
self.cpfcnpj_type = False
|
|
114
117
|
self.document_date = False
|
|
115
|
-
self.
|
|
118
|
+
self.l10n_br_ie_code = False
|
|
116
119
|
|
|
117
120
|
if related.document_type_id.code in ("01", "04"):
|
|
118
121
|
self.document_key = False
|
|
@@ -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.
|
|
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.
|
|
139
|
-
related.partner_id and related.partner_id.
|
|
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
|
|
|
@@ -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(
|
|
48
|
+
raise UserError(f"Error in the request: {e}") from e
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def get_ibpt_product(
|
|
@@ -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
|
|
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 =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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):
|
|
@@ -127,7 +127,7 @@ class Operation(models.Model):
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
def open_action(self):
|
|
130
|
-
"""
|
|
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
|
-
|
|
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",
|
|
@@ -167,6 +172,13 @@ class OperationLine(models.Model):
|
|
|
167
172
|
cfop = self.cfop_export_id
|
|
168
173
|
return cfop
|
|
169
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
|
+
|
|
170
182
|
def _build_mapping_result_ipi(self, mapping_result, tax_definition):
|
|
171
183
|
if tax_definition and tax_definition.ipi_guideline_id:
|
|
172
184
|
mapping_result["ipi_guideline"] = tax_definition.ipi_guideline_id
|
|
@@ -246,12 +258,15 @@ class OperationLine(models.Model):
|
|
|
246
258
|
(l10n_br_fiscal.tax.ipi.guideline).
|
|
247
259
|
- 'icms_tax_benefit_id': The determined ICMS tax benefit record
|
|
248
260
|
ID (l10n_br_fiscal.tax.definition) or False.
|
|
261
|
+
- 'tax_classification': The determined Tax Classification record
|
|
262
|
+
(l10n_br_fiscal.tax.classification).
|
|
249
263
|
"""
|
|
250
264
|
mapping_result = {
|
|
251
265
|
"taxes": {},
|
|
252
266
|
"cfop": False,
|
|
253
267
|
"ipi_guideline": self.env.ref("l10n_br_fiscal.tax_guideline_999"),
|
|
254
268
|
"icms_tax_benefit_id": False,
|
|
269
|
+
"tax_classification": False,
|
|
255
270
|
}
|
|
256
271
|
|
|
257
272
|
self.ensure_one()
|
|
@@ -259,6 +274,9 @@ class OperationLine(models.Model):
|
|
|
259
274
|
# Define CFOP
|
|
260
275
|
mapping_result["cfop"] = self._get_cfop(company, partner)
|
|
261
276
|
|
|
277
|
+
# Define Tax Classification
|
|
278
|
+
mapping_result["tax_classification"] = self._get_tax_classification(company)
|
|
279
|
+
|
|
262
280
|
# 1 Get Tax Defs from Company
|
|
263
281
|
for tax_definition in company.tax_definition_ids.map_tax_definition(
|
|
264
282
|
company,
|
|
@@ -273,6 +291,16 @@ class OperationLine(models.Model):
|
|
|
273
291
|
):
|
|
274
292
|
self._build_mapping_result(mapping_result, tax_definition)
|
|
275
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
|
+
|
|
276
304
|
# 2 From NCM
|
|
277
305
|
if not ncm and product:
|
|
278
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(
|