odoo-addon-l10n-br-fiscal 16.0.19.4.0__py3-none-any.whl → 17.0.4.0.0.1__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 +10 -10
- odoo/addons/l10n_br_fiscal/__init__.py +2 -3
- odoo/addons/l10n_br_fiscal/__manifest__.py +3 -7
- odoo/addons/l10n_br_fiscal/constants/fiscal.py +0 -66
- odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.cest.csv +983 -1043
- odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.cst.csv +0 -58
- odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.tax.csv +0 -31
- odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.tax.group.csv +0 -3
- odoo/addons/l10n_br_fiscal/data/product_data.xml +2 -2
- odoo/addons/l10n_br_fiscal/demo/fiscal_document_demo.xml +88 -0
- odoo/addons/l10n_br_fiscal/i18n/l10n_br_fiscal.pot +28 -1031
- odoo/addons/l10n_br_fiscal/i18n/pt_BR.po +1078 -1197
- odoo/addons/l10n_br_fiscal/models/__init__.py +2 -2
- odoo/addons/l10n_br_fiscal/models/comment.py +20 -18
- odoo/addons/l10n_br_fiscal/models/data_abstract.py +11 -12
- odoo/addons/l10n_br_fiscal/models/document.py +4 -30
- odoo/addons/l10n_br_fiscal/models/document_line.py +3 -16
- odoo/addons/l10n_br_fiscal/models/document_line_mixin.py +27 -1048
- odoo/addons/l10n_br_fiscal/models/document_line_mixin_methods.py +883 -0
- odoo/addons/l10n_br_fiscal/models/document_mixin.py +3 -241
- odoo/addons/l10n_br_fiscal/models/document_mixin_methods.py +239 -0
- odoo/addons/l10n_br_fiscal/models/document_serie.py +5 -3
- odoo/addons/l10n_br_fiscal/models/invalidate_number.py +0 -6
- odoo/addons/l10n_br_fiscal/models/operation.py +0 -11
- odoo/addons/l10n_br_fiscal/models/operation_dashboard.py +2 -3
- odoo/addons/l10n_br_fiscal/models/operation_line.py +0 -28
- odoo/addons/l10n_br_fiscal/models/partner_profile.py +0 -6
- odoo/addons/l10n_br_fiscal/models/product_template.py +0 -4
- odoo/addons/l10n_br_fiscal/models/res_company.py +0 -18
- odoo/addons/l10n_br_fiscal/models/res_partner.py +0 -17
- odoo/addons/l10n_br_fiscal/models/simplified_tax_range.py +0 -8
- odoo/addons/l10n_br_fiscal/models/tax_definition.py +2 -35
- odoo/addons/l10n_br_fiscal/security/ir.model.access.csv +0 -6
- odoo/addons/l10n_br_fiscal/static/description/index.html +8 -8
- odoo/addons/l10n_br_fiscal/tests/__init__.py +0 -1
- odoo/addons/l10n_br_fiscal/tests/test_cnae.py +2 -3
- odoo/addons/l10n_br_fiscal/tests/test_document_edition.py +10 -37
- odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_generic.py +28 -4
- odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_nfse.py +4 -0
- odoo/addons/l10n_br_fiscal/tests/test_service_type.py +2 -3
- odoo/addons/l10n_br_fiscal/tests/test_tax_benefit.py +4 -2
- odoo/addons/l10n_br_fiscal/views/cest_view.xml +2 -2
- odoo/addons/l10n_br_fiscal/views/cnae_view.xml +0 -1
- odoo/addons/l10n_br_fiscal/views/document_line_mixin_view.xml +170 -243
- odoo/addons/l10n_br_fiscal/views/document_line_view.xml +7 -11
- odoo/addons/l10n_br_fiscal/views/document_related_view.xml +15 -11
- odoo/addons/l10n_br_fiscal/views/document_serie_view.xml +1 -1
- odoo/addons/l10n_br_fiscal/views/document_view.xml +32 -44
- odoo/addons/l10n_br_fiscal/views/invalidate_number_view.xml +12 -12
- odoo/addons/l10n_br_fiscal/views/l10n_br_fiscal_action.xml +0 -31
- odoo/addons/l10n_br_fiscal/views/l10n_br_fiscal_menu.xml +2 -18
- odoo/addons/l10n_br_fiscal/views/nbm_view.xml +2 -2
- odoo/addons/l10n_br_fiscal/views/nbs_view.xml +1 -1
- odoo/addons/l10n_br_fiscal/views/ncm_view.xml +3 -3
- odoo/addons/l10n_br_fiscal/views/operation_dashboard_view.xml +27 -18
- odoo/addons/l10n_br_fiscal/views/operation_line_view.xml +2 -5
- odoo/addons/l10n_br_fiscal/views/operation_view.xml +39 -16
- odoo/addons/l10n_br_fiscal/views/partner_profile_view.xml +3 -6
- odoo/addons/l10n_br_fiscal/views/product_genre_view.xml +2 -2
- odoo/addons/l10n_br_fiscal/views/product_product_view.xml +17 -45
- odoo/addons/l10n_br_fiscal/views/product_template_view.xml +16 -35
- odoo/addons/l10n_br_fiscal/views/res_company_view.xml +17 -18
- odoo/addons/l10n_br_fiscal/views/res_config_settings_view.xml +37 -98
- odoo/addons/l10n_br_fiscal/views/res_partner_view.xml +3 -13
- odoo/addons/l10n_br_fiscal/views/service_type_view.xml +2 -3
- odoo/addons/l10n_br_fiscal/views/tax_definition_view.xml +60 -29
- odoo/addons/l10n_br_fiscal/views/tax_estimate_view.xml +2 -2
- odoo/addons/l10n_br_fiscal/views/tax_group_view.xml +4 -4
- odoo/addons/l10n_br_fiscal/views/tax_view.xml +14 -16
- odoo/addons/l10n_br_fiscal/wizards/document_import_wizard_mixin.py +1 -1
- odoo/addons/l10n_br_fiscal/wizards/document_import_wizard_mixin.xml +2 -5
- {odoo_addon_l10n_br_fiscal-16.0.19.4.0.dist-info → odoo_addon_l10n_br_fiscal-17.0.4.0.0.1.dist-info}/METADATA +16 -16
- {odoo_addon_l10n_br_fiscal-16.0.19.4.0.dist-info → odoo_addon_l10n_br_fiscal-17.0.4.0.0.1.dist-info}/RECORD +77 -88
- odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.operation.indicator.csv +0 -27
- odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.tax.classification.csv +0 -163
- odoo/addons/l10n_br_fiscal/migrations/16.0.14.0.5/pre-migration.py +0 -15
- odoo/addons/l10n_br_fiscal/migrations/16.0.2.0.0/pre-migration.py +0 -25
- odoo/addons/l10n_br_fiscal/migrations/16.0.2.15.0/pre-migration.py +0 -19
- odoo/addons/l10n_br_fiscal/migrations/16.0.4.0.0/pre-migration.py +0 -220
- odoo/addons/l10n_br_fiscal/migrations/16.0.5.0.0/pre-migration.py +0 -33
- odoo/addons/l10n_br_fiscal/migrations/16.0.5.2.0/pre-migration.py +0 -21
- odoo/addons/l10n_br_fiscal/models/operation_indicator.py +0 -58
- odoo/addons/l10n_br_fiscal/models/tax_classification.py +0 -81
- odoo/addons/l10n_br_fiscal/tests/test_tax_classification.py +0 -110
- odoo/addons/l10n_br_fiscal/views/operation_indicator_view.xml +0 -75
- odoo/addons/l10n_br_fiscal/views/tax_classification.xml +0 -110
- /odoo/addons/l10n_br_fiscal/migrations/{16.0.13.0.0 → 17.0.2.0.0}/pre-migration.py +0 -0
- /odoo/addons/l10n_br_fiscal/migrations/{16.0.14.0.0 → 17.0.3.0.0}/pre-migration.py +0 -0
- {odoo_addon_l10n_br_fiscal-16.0.19.4.0.dist-info → odoo_addon_l10n_br_fiscal-17.0.4.0.0.1.dist-info}/WHEEL +0 -0
- {odoo_addon_l10n_br_fiscal-16.0.19.4.0.dist-info → odoo_addon_l10n_br_fiscal-17.0.4.0.0.1.dist-info}/top_level.txt +0 -0
|
@@ -8,6 +8,7 @@ from ..constants.fiscal import (
|
|
|
8
8
|
DOCUMENT_ISSUER,
|
|
9
9
|
DOCUMENT_ISSUER_COMPANY,
|
|
10
10
|
FINAL_CUSTOMER,
|
|
11
|
+
FINAL_CUSTOMER_YES,
|
|
11
12
|
FISCAL_COMMENT_DOCUMENT,
|
|
12
13
|
NFE_IND_PRES,
|
|
13
14
|
NFE_IND_PRES_DEFAULT,
|
|
@@ -41,6 +42,7 @@ class FiscalDocumentMixin(models.AbstractModel):
|
|
|
41
42
|
"""
|
|
42
43
|
|
|
43
44
|
_name = "l10n_br_fiscal.document.mixin"
|
|
45
|
+
_inherit = "l10n_br_fiscal.document.mixin.methods"
|
|
44
46
|
_description = "Document Fiscal Mixin Fields"
|
|
45
47
|
|
|
46
48
|
def _date_server_format(self):
|
|
@@ -56,218 +58,6 @@ class FiscalDocumentMixin(models.AbstractModel):
|
|
|
56
58
|
)
|
|
57
59
|
return domain
|
|
58
60
|
|
|
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
|
-
|
|
271
61
|
fiscal_operation_id = fields.Many2one(
|
|
272
62
|
comodel_name="l10n_br_fiscal.operation",
|
|
273
63
|
string="Operation",
|
|
@@ -321,11 +111,7 @@ class FiscalDocumentMixin(models.AbstractModel):
|
|
|
321
111
|
ind_final = fields.Selection(
|
|
322
112
|
selection=FINAL_CUSTOMER,
|
|
323
113
|
string="Final Consumption Operation",
|
|
324
|
-
|
|
325
|
-
inverse="_inverse_ind_final",
|
|
326
|
-
store=True,
|
|
327
|
-
precompute=True,
|
|
328
|
-
readonly=False,
|
|
114
|
+
default=FINAL_CUSTOMER_YES,
|
|
329
115
|
)
|
|
330
116
|
|
|
331
117
|
currency_id = fields.Many2one(
|
|
@@ -345,30 +131,6 @@ class FiscalDocumentMixin(models.AbstractModel):
|
|
|
345
131
|
store=True,
|
|
346
132
|
)
|
|
347
133
|
|
|
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",
|
|
368
|
-
compute="_compute_fiscal_amount",
|
|
369
|
-
store=True,
|
|
370
|
-
)
|
|
371
|
-
|
|
372
134
|
amount_icms_base = fields.Monetary(
|
|
373
135
|
string="ICMS Base",
|
|
374
136
|
compute="_compute_fiscal_amount",
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Copyright (C) 2019 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 api, models
|
|
5
|
+
|
|
6
|
+
from ..constants.fiscal import (
|
|
7
|
+
DOCUMENT_ISSUER_COMPANY,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FiscalDocumentMixinMethods(models.AbstractModel):
|
|
12
|
+
"""
|
|
13
|
+
Provides the method implementations for l10n_br_fiscal.document.mixin.
|
|
14
|
+
|
|
15
|
+
These methods are extracted into this separate mixin due to the way
|
|
16
|
+
l10n_br_fiscal.document.line is incorporated into account.move
|
|
17
|
+
by the l10n_br_account module (decorator pattern).
|
|
18
|
+
|
|
19
|
+
Specifically:
|
|
20
|
+
- In l10n_br_account, fields from l10n_br_fiscal.document
|
|
21
|
+
are added to account.move using Odoo's `_inherits` (composition)
|
|
22
|
+
mechanism.
|
|
23
|
+
- The methods in *this* mixin, however, are intended to be inherited
|
|
24
|
+
using the standard `_inherit` mechanism.
|
|
25
|
+
|
|
26
|
+
This separation is crucial because `_inherits` handles field composition
|
|
27
|
+
but does not inherit methods. Thus, `_inherit` is used to bring in
|
|
28
|
+
these methods. If these methods were defined in the same class as the
|
|
29
|
+
fields of l10n_br_fiscal.document.mixin (which are subject to
|
|
30
|
+
`_inherits`), and account.move.line also used `_inherit` on that
|
|
31
|
+
single class, the fields would be duplicated.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
_name = "l10n_br_fiscal.document.mixin.methods"
|
|
35
|
+
_description = "Fiscal Document Mixin Methods"
|
|
36
|
+
|
|
37
|
+
def _prepare_br_fiscal_dict(self, default=False):
|
|
38
|
+
self.ensure_one()
|
|
39
|
+
fields = self.env["l10n_br_fiscal.document.mixin"]._fields.keys()
|
|
40
|
+
|
|
41
|
+
# we now read the record fiscal fields except the m2m tax:
|
|
42
|
+
vals = self._convert_to_write(self.read(fields)[0])
|
|
43
|
+
|
|
44
|
+
# remove id field to avoid conflicts
|
|
45
|
+
vals.pop("id", None)
|
|
46
|
+
|
|
47
|
+
if default: # in case you want to use new rather than write later
|
|
48
|
+
return {f"default_{k}": vals[k] for k in vals.keys()}
|
|
49
|
+
return vals
|
|
50
|
+
|
|
51
|
+
@api.onchange("document_type_id")
|
|
52
|
+
def _onchange_document_type_id(self):
|
|
53
|
+
if self.document_type_id and self.issuer == DOCUMENT_ISSUER_COMPANY:
|
|
54
|
+
self.document_serie_id = self.document_type_id.get_document_serie(
|
|
55
|
+
self.company_id, self.fiscal_operation_id
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@api.depends("fiscal_operation_id")
|
|
59
|
+
def _compute_document_type_id(self):
|
|
60
|
+
for doc in self.filtered(lambda doc: doc.fiscal_operation_id):
|
|
61
|
+
if doc.issuer == DOCUMENT_ISSUER_COMPANY and not doc.document_type_id:
|
|
62
|
+
doc.document_type_id = doc.company_id.document_type_id
|
|
63
|
+
|
|
64
|
+
def _get_amount_lines(self):
|
|
65
|
+
"""Get object lines instances used to compute fiscal fields"""
|
|
66
|
+
return self.mapped(self._get_fiscal_lines_field_name())
|
|
67
|
+
|
|
68
|
+
def _get_product_amount_lines(self):
|
|
69
|
+
fiscal_line_ids = self._get_amount_lines()
|
|
70
|
+
return fiscal_line_ids.filtered(lambda line: line.product_id.type != "service")
|
|
71
|
+
|
|
72
|
+
@api.model
|
|
73
|
+
def _get_amount_fields(self):
|
|
74
|
+
"""Get all fields with 'amount_' prefix"""
|
|
75
|
+
fields = self.env["l10n_br_fiscal.document.mixin"]._fields.keys()
|
|
76
|
+
prefixes = ("amount_", "fiscal_amount_")
|
|
77
|
+
amount_fields = [f for f in fields if f.startswith(prefixes)]
|
|
78
|
+
return amount_fields
|
|
79
|
+
|
|
80
|
+
@api.depends("document_serie_id", "issuer")
|
|
81
|
+
def _compute_document_serie(self):
|
|
82
|
+
for doc in self:
|
|
83
|
+
if doc.document_serie_id and doc.issuer == DOCUMENT_ISSUER_COMPANY:
|
|
84
|
+
doc.document_serie = doc.document_serie_id.code
|
|
85
|
+
elif doc.document_serie is None:
|
|
86
|
+
doc.document_serie = False
|
|
87
|
+
|
|
88
|
+
@api.depends("document_type_id", "issuer")
|
|
89
|
+
def _compute_document_serie_id(self):
|
|
90
|
+
for doc in self:
|
|
91
|
+
if (
|
|
92
|
+
not doc.document_serie_id
|
|
93
|
+
and doc.document_type_id
|
|
94
|
+
and doc.issuer == DOCUMENT_ISSUER_COMPANY
|
|
95
|
+
):
|
|
96
|
+
doc.document_serie_id = doc.document_type_id.get_document_serie(
|
|
97
|
+
doc.company_id, doc.fiscal_operation_id
|
|
98
|
+
)
|
|
99
|
+
elif doc.document_serie_id is None:
|
|
100
|
+
doc.document_serie_id = False
|
|
101
|
+
|
|
102
|
+
@api.model
|
|
103
|
+
def _get_fiscal_lines_field_name(self):
|
|
104
|
+
return "fiscal_line_ids"
|
|
105
|
+
|
|
106
|
+
def _get_fiscal_amount_field_dependencies(self):
|
|
107
|
+
"""
|
|
108
|
+
Dynamically get the list of field dependencies.
|
|
109
|
+
"""
|
|
110
|
+
if self._abstract:
|
|
111
|
+
return []
|
|
112
|
+
o2m_field_name = self._get_fiscal_lines_field_name()
|
|
113
|
+
target_fields = []
|
|
114
|
+
for field in self._get_amount_fields():
|
|
115
|
+
if (
|
|
116
|
+
field.replace("amount_", "")
|
|
117
|
+
in getattr(self, o2m_field_name)._fields.keys()
|
|
118
|
+
):
|
|
119
|
+
target_fields.append(field.replace("amount_", ""))
|
|
120
|
+
|
|
121
|
+
return [o2m_field_name] + [
|
|
122
|
+
f"{o2m_field_name}.{target_field}" for target_field in target_fields
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
@api.depends(lambda self: self._get_fiscal_amount_field_dependencies())
|
|
126
|
+
def _compute_fiscal_amount(self):
|
|
127
|
+
"""
|
|
128
|
+
Compute and sum various fiscal amounts from the document lines.
|
|
129
|
+
|
|
130
|
+
This method iterates over fields prefixed with 'amount_' (as determined
|
|
131
|
+
by `_get_amount_fields`) and sums corresponding values from the lines
|
|
132
|
+
retrieved by `_get_amount_lines`.
|
|
133
|
+
|
|
134
|
+
It handles cases where delivery costs (freight, insurance, other) are
|
|
135
|
+
defined at the document total level rather than per line.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
fields = self._get_amount_fields()
|
|
139
|
+
for doc in self.filtered(lambda m: m.fiscal_operation_id):
|
|
140
|
+
values = {key: 0.0 for key in fields}
|
|
141
|
+
for line in doc._get_amount_lines():
|
|
142
|
+
for field in fields:
|
|
143
|
+
if field in line._fields.keys():
|
|
144
|
+
values[field] += line[field]
|
|
145
|
+
if field.replace("amount_", "") in line._fields.keys():
|
|
146
|
+
# FIXME this field creates an error in invoice form
|
|
147
|
+
if field == "amount_financial_discount_value":
|
|
148
|
+
values["amount_financial_discount_value"] += (
|
|
149
|
+
0 # line.financial_discount_value
|
|
150
|
+
)
|
|
151
|
+
else:
|
|
152
|
+
values[field] += line[field.replace("amount_", "")]
|
|
153
|
+
|
|
154
|
+
# Valores definidos pelo Total e não pela Linha
|
|
155
|
+
if (
|
|
156
|
+
doc.company_id.delivery_costs == "total"
|
|
157
|
+
or doc.force_compute_delivery_costs_by_total
|
|
158
|
+
):
|
|
159
|
+
values["amount_freight_value"] = doc.amount_freight_value
|
|
160
|
+
values["amount_insurance_value"] = doc.amount_insurance_value
|
|
161
|
+
values["amount_other_value"] = doc.amount_other_value
|
|
162
|
+
|
|
163
|
+
doc.update(values)
|
|
164
|
+
|
|
165
|
+
def _get_fiscal_partner(self):
|
|
166
|
+
"""
|
|
167
|
+
Hook method to determine the fiscal partner for the document.
|
|
168
|
+
|
|
169
|
+
This method is designed to be overridden in implementing models if the
|
|
170
|
+
partner relevant for fiscal purposes (e.g., for tax calculations,
|
|
171
|
+
final consumer status) is different from the main `partner_id`
|
|
172
|
+
of the document record. For instance, an invoice might use a specific
|
|
173
|
+
invoicing contact derived from the main partner.
|
|
174
|
+
|
|
175
|
+
:return: A `res.partner` recordset representing the fiscal partner.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
self.ensure_one()
|
|
179
|
+
return self.partner_id
|
|
180
|
+
|
|
181
|
+
@api.onchange("partner_id")
|
|
182
|
+
def _onchange_partner_id_fiscal(self):
|
|
183
|
+
partner = self._get_fiscal_partner()
|
|
184
|
+
if partner:
|
|
185
|
+
self.ind_final = partner.ind_final
|
|
186
|
+
for line in self._get_amount_lines():
|
|
187
|
+
# reload fiscal data, operation line, cfop, taxes, etc.
|
|
188
|
+
line._onchange_fiscal_operation_id()
|
|
189
|
+
|
|
190
|
+
@api.depends("fiscal_operation_id")
|
|
191
|
+
def _compute_operation_name(self):
|
|
192
|
+
for doc in self:
|
|
193
|
+
if doc.fiscal_operation_id:
|
|
194
|
+
doc.operation_name = doc.fiscal_operation_id.name
|
|
195
|
+
else:
|
|
196
|
+
doc.operation_name = False
|
|
197
|
+
|
|
198
|
+
@api.depends("fiscal_operation_id")
|
|
199
|
+
def _compute_comment_ids(self):
|
|
200
|
+
for doc in self:
|
|
201
|
+
if doc.fiscal_operation_id:
|
|
202
|
+
doc.comment_ids = doc.fiscal_operation_id.comment_ids
|
|
203
|
+
elif doc.comment_ids is None:
|
|
204
|
+
doc.comment_ids = []
|
|
205
|
+
|
|
206
|
+
def _distribute_amount_to_lines(self, amount_field_name, line_field_name):
|
|
207
|
+
for record in self:
|
|
208
|
+
if not (
|
|
209
|
+
record.delivery_costs == "total"
|
|
210
|
+
or record.force_compute_delivery_costs_by_total
|
|
211
|
+
):
|
|
212
|
+
continue
|
|
213
|
+
lines = record._get_product_amount_lines()
|
|
214
|
+
if not lines:
|
|
215
|
+
continue
|
|
216
|
+
amount_to_distribute = record[amount_field_name]
|
|
217
|
+
total_gross = sum(lines.mapped("price_gross"))
|
|
218
|
+
if total_gross > 0:
|
|
219
|
+
distributed_amount = 0
|
|
220
|
+
for line in lines[:-1]:
|
|
221
|
+
proportional_amount = record.currency_id.round(
|
|
222
|
+
amount_to_distribute * (line.price_gross / total_gross)
|
|
223
|
+
)
|
|
224
|
+
line[line_field_name] = proportional_amount
|
|
225
|
+
distributed_amount += proportional_amount
|
|
226
|
+
lines[-1][line_field_name] = amount_to_distribute - distributed_amount
|
|
227
|
+
else:
|
|
228
|
+
lines.write({line_field_name: 0.0})
|
|
229
|
+
if lines:
|
|
230
|
+
lines[0][line_field_name] = amount_to_distribute
|
|
231
|
+
|
|
232
|
+
def _inverse_amount_freight(self):
|
|
233
|
+
self._distribute_amount_to_lines("amount_freight_value", "freight_value")
|
|
234
|
+
|
|
235
|
+
def _inverse_amount_insurance(self):
|
|
236
|
+
self._distribute_amount_to_lines("amount_insurance_value", "insurance_value")
|
|
237
|
+
|
|
238
|
+
def _inverse_amount_other(self):
|
|
239
|
+
self._distribute_amount_to_lines("amount_other_value", "other_value")
|
|
@@ -88,6 +88,11 @@ class DocumentSerie(models.Model):
|
|
|
88
88
|
vals.update({"internal_sequence_id": self._create_sequence(vals)})
|
|
89
89
|
return super().create(vals_list)
|
|
90
90
|
|
|
91
|
+
@api.depends("name")
|
|
92
|
+
def _compute_display_name(self):
|
|
93
|
+
for record in self:
|
|
94
|
+
record.display_name = record.name
|
|
95
|
+
|
|
91
96
|
def write(self, vals):
|
|
92
97
|
if "internal_sequence_id" in vals:
|
|
93
98
|
raise ValidationError(_("You cannot change the internal sequence."))
|
|
@@ -111,9 +116,6 @@ class DocumentSerie(models.Model):
|
|
|
111
116
|
)
|
|
112
117
|
return super().write(vals)
|
|
113
118
|
|
|
114
|
-
def name_get(self):
|
|
115
|
-
return [(r.id, f"{r.name}") for r in self]
|
|
116
|
-
|
|
117
119
|
def _is_invalid_number(self, document_number):
|
|
118
120
|
self.ensure_one()
|
|
119
121
|
is_invalid_number = True
|
|
@@ -28,14 +28,12 @@ class InvalidateNumber(models.Model):
|
|
|
28
28
|
readonly=True,
|
|
29
29
|
default=lambda self: self.env.company.id,
|
|
30
30
|
required=True,
|
|
31
|
-
states={"draft": [("readonly", False)]},
|
|
32
31
|
)
|
|
33
32
|
|
|
34
33
|
document_type_id = fields.Many2one(
|
|
35
34
|
comodel_name="l10n_br_fiscal.document.type",
|
|
36
35
|
required=True,
|
|
37
36
|
readonly=True,
|
|
38
|
-
states={"draft": [("readonly", False)]},
|
|
39
37
|
)
|
|
40
38
|
|
|
41
39
|
document_electronic = fields.Boolean(
|
|
@@ -49,27 +47,23 @@ class InvalidateNumber(models.Model):
|
|
|
49
47
|
('company_id', '=', company_id)]""",
|
|
50
48
|
required=True,
|
|
51
49
|
readonly=True,
|
|
52
|
-
states={"draft": [("readonly", False)]},
|
|
53
50
|
)
|
|
54
51
|
|
|
55
52
|
number_start = fields.Integer(
|
|
56
53
|
string="Initial Number",
|
|
57
54
|
required=True,
|
|
58
55
|
readonly=True,
|
|
59
|
-
states={"draft": [("readonly", False)]},
|
|
60
56
|
)
|
|
61
57
|
|
|
62
58
|
number_end = fields.Integer(
|
|
63
59
|
string="End Number",
|
|
64
60
|
required=True,
|
|
65
61
|
readonly=True,
|
|
66
|
-
states={"draft": [("readonly", False)]},
|
|
67
62
|
)
|
|
68
63
|
|
|
69
64
|
justification = fields.Char(
|
|
70
65
|
required=True,
|
|
71
66
|
readonly=True,
|
|
72
|
-
states={"draft": [("readonly", False)]},
|
|
73
67
|
)
|
|
74
68
|
|
|
75
69
|
state = fields.Selection(
|
|
@@ -72,14 +72,12 @@ class Operation(models.Model):
|
|
|
72
72
|
code = fields.Char(
|
|
73
73
|
required=True,
|
|
74
74
|
readonly=True,
|
|
75
|
-
states={"draft": [("readonly", False)]},
|
|
76
75
|
tracking=True,
|
|
77
76
|
)
|
|
78
77
|
|
|
79
78
|
name = fields.Char(
|
|
80
79
|
required=True,
|
|
81
80
|
readonly=True,
|
|
82
|
-
states={"draft": [("readonly", False)]},
|
|
83
81
|
tracking=True,
|
|
84
82
|
)
|
|
85
83
|
|
|
@@ -88,7 +86,6 @@ class Operation(models.Model):
|
|
|
88
86
|
string="Type",
|
|
89
87
|
required=True,
|
|
90
88
|
readonly=True,
|
|
91
|
-
states={"draft": [("readonly", False)]},
|
|
92
89
|
tracking=True,
|
|
93
90
|
)
|
|
94
91
|
|
|
@@ -97,7 +94,6 @@ class Operation(models.Model):
|
|
|
97
94
|
string="Finalidade",
|
|
98
95
|
default=EDOC_PURPOSE_NORMAL,
|
|
99
96
|
readonly=True,
|
|
100
|
-
states={"draft": [("readonly", False)]},
|
|
101
97
|
tracking=True,
|
|
102
98
|
)
|
|
103
99
|
|
|
@@ -106,7 +102,6 @@ class Operation(models.Model):
|
|
|
106
102
|
string="Default Price Unit?",
|
|
107
103
|
default="sale_price",
|
|
108
104
|
readonly=True,
|
|
109
|
-
states={"draft": [("readonly", False)]},
|
|
110
105
|
tracking=True,
|
|
111
106
|
)
|
|
112
107
|
|
|
@@ -115,7 +110,6 @@ class Operation(models.Model):
|
|
|
115
110
|
default=OPERATION_FISCAL_TYPE_DEFAULT,
|
|
116
111
|
required=True,
|
|
117
112
|
readonly=True,
|
|
118
|
-
states={"draft": [("readonly", False)]},
|
|
119
113
|
tracking=True,
|
|
120
114
|
)
|
|
121
115
|
|
|
@@ -123,7 +117,6 @@ class Operation(models.Model):
|
|
|
123
117
|
comodel_name="l10n_br_fiscal.operation",
|
|
124
118
|
string="Return Operation",
|
|
125
119
|
readonly=True,
|
|
126
|
-
states={"draft": [("readonly", False)]},
|
|
127
120
|
domain="[('fiscal_operation_type', '!=', fiscal_operation_type), "
|
|
128
121
|
"('fiscal_type', 'in', {'sale': ['sale_refund'], 'purchase': "
|
|
129
122
|
"['purchase_refund'], 'other': ['return_in', 'return_out'],"
|
|
@@ -136,7 +129,6 @@ class Operation(models.Model):
|
|
|
136
129
|
comodel_name="l10n_br_fiscal.operation",
|
|
137
130
|
string="Inverse Operation",
|
|
138
131
|
readonly=True,
|
|
139
|
-
states={"draft": [("readonly", False)]},
|
|
140
132
|
tracking=True,
|
|
141
133
|
)
|
|
142
134
|
|
|
@@ -144,7 +136,6 @@ class Operation(models.Model):
|
|
|
144
136
|
comodel_name="res.company",
|
|
145
137
|
string="Company",
|
|
146
138
|
readonly=True,
|
|
147
|
-
states={"draft": [("readonly", False)]},
|
|
148
139
|
tracking=True,
|
|
149
140
|
)
|
|
150
141
|
|
|
@@ -162,7 +153,6 @@ class Operation(models.Model):
|
|
|
162
153
|
inverse_name="fiscal_operation_id",
|
|
163
154
|
string="Operation Document Types",
|
|
164
155
|
readonly=True,
|
|
165
|
-
states={"draft": [("readonly", False)]},
|
|
166
156
|
)
|
|
167
157
|
|
|
168
158
|
line_ids = fields.One2many(
|
|
@@ -170,7 +160,6 @@ class Operation(models.Model):
|
|
|
170
160
|
inverse_name="fiscal_operation_id",
|
|
171
161
|
string="Operation Line",
|
|
172
162
|
readonly=True,
|
|
173
|
-
states={"draft": [("readonly", False)]},
|
|
174
163
|
copy=True,
|
|
175
164
|
)
|
|
176
165
|
|
|
@@ -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,8 +157,7 @@ class Operation(models.Model):
|
|
|
157
157
|
}
|
|
158
158
|
)
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
[action] = self.env.ref(xmlid).read()
|
|
160
|
+
[action] = self.env.ref("l10n_br_fiscal.{action_name}").read()
|
|
162
161
|
action["context"] = ctx
|
|
163
162
|
action["domain"] = self._context.get("use_domain", [])
|
|
164
163
|
action["domain"] += [
|