odoo-addon-l10n-br-fiscal 18.0.2.0.0.10__py3-none-any.whl → 18.0.5.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.

Potentially problematic release.


This version of odoo-addon-l10n-br-fiscal might be problematic. Click here for more details.

Files changed (47) hide show
  1. odoo/addons/l10n_br_fiscal/README.rst +1 -1
  2. odoo/addons/l10n_br_fiscal/__manifest__.py +2 -2
  3. odoo/addons/l10n_br_fiscal/constants/fiscal.py +46 -18
  4. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.document.type.csv +1 -0
  5. odoo/addons/l10n_br_fiscal/data/operation_data.xml +1 -1
  6. odoo/addons/l10n_br_fiscal/demo/fiscal_document_demo.xml +3 -179
  7. odoo/addons/l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml +0 -4
  8. odoo/addons/l10n_br_fiscal/demo/fiscal_operation_demo.xml +2 -2
  9. odoo/addons/l10n_br_fiscal/i18n/l10n_br_fiscal.pot +142 -36
  10. odoo/addons/l10n_br_fiscal/i18n/pt_BR.po +190 -58
  11. odoo/addons/l10n_br_fiscal/migrations/18.0.3.0.0/pre-migration.py +30 -0
  12. odoo/addons/l10n_br_fiscal/models/comment.py +3 -1
  13. odoo/addons/l10n_br_fiscal/models/document.py +27 -8
  14. odoo/addons/l10n_br_fiscal/models/document_line.py +51 -8
  15. odoo/addons/l10n_br_fiscal/models/document_line_mixin.py +118 -31
  16. odoo/addons/l10n_br_fiscal/models/document_line_mixin_methods.py +263 -282
  17. odoo/addons/l10n_br_fiscal/models/document_mixin.py +8 -5
  18. odoo/addons/l10n_br_fiscal/models/document_mixin_methods.py +49 -151
  19. odoo/addons/l10n_br_fiscal/models/document_related.py +1 -1
  20. odoo/addons/l10n_br_fiscal/models/document_serie.py +33 -0
  21. odoo/addons/l10n_br_fiscal/models/icms_regulation.py +1 -1
  22. odoo/addons/l10n_br_fiscal/models/operation_dashboard.py +3 -2
  23. odoo/addons/l10n_br_fiscal/models/partner_profile.py +6 -0
  24. odoo/addons/l10n_br_fiscal/models/res_partner.py +7 -0
  25. odoo/addons/l10n_br_fiscal/models/tax.py +7 -3
  26. odoo/addons/l10n_br_fiscal/security/fiscal_security.xml +6 -16
  27. odoo/addons/l10n_br_fiscal/security/ir.model.access.csv +0 -1
  28. odoo/addons/l10n_br_fiscal/static/description/index.html +1 -1
  29. odoo/addons/l10n_br_fiscal/tests/__init__.py +1 -0
  30. odoo/addons/l10n_br_fiscal/tests/test_document_edition.py +175 -10
  31. odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_generic.py +13 -42
  32. odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_nfse.py +0 -5
  33. odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_serie.py +60 -0
  34. odoo/addons/l10n_br_fiscal/tests/test_tax_benefit.py +2 -5
  35. odoo/addons/l10n_br_fiscal/views/document_line_mixin_view.xml +1 -0
  36. odoo/addons/l10n_br_fiscal/views/document_line_view.xml +3 -3
  37. odoo/addons/l10n_br_fiscal/views/document_view.xml +20 -15
  38. odoo/addons/l10n_br_fiscal/views/l10n_br_fiscal_menu.xml +0 -9
  39. odoo/addons/l10n_br_fiscal/views/operation_dashboard_view.xml +3 -3
  40. odoo/addons/l10n_br_fiscal/views/product_product_view.xml +33 -6
  41. odoo/addons/l10n_br_fiscal/views/product_template_view.xml +17 -4
  42. odoo/addons/l10n_br_fiscal/views/res_partner_view.xml +6 -0
  43. odoo/addons/l10n_br_fiscal/wizards/base_wizard_mixin.py +1 -1
  44. {odoo_addon_l10n_br_fiscal-18.0.2.0.0.10.dist-info → odoo_addon_l10n_br_fiscal-18.0.5.0.0.1.dist-info}/METADATA +3 -3
  45. {odoo_addon_l10n_br_fiscal-18.0.2.0.0.10.dist-info → odoo_addon_l10n_br_fiscal-18.0.5.0.0.1.dist-info}/RECORD +47 -45
  46. {odoo_addon_l10n_br_fiscal-18.0.2.0.0.10.dist-info → odoo_addon_l10n_br_fiscal-18.0.5.0.0.1.dist-info}/WHEEL +0 -0
  47. {odoo_addon_l10n_br_fiscal-18.0.2.0.0.10.dist-info → odoo_addon_l10n_br_fiscal-18.0.5.0.0.1.dist-info}/top_level.txt +0 -0
@@ -4,44 +4,22 @@
4
4
  from copy import deepcopy
5
5
 
6
6
  from lxml import etree
7
+ from lxml.builder import E
7
8
 
8
9
  from odoo import Command, api, models
9
10
 
10
- from ..constants.fiscal import CFOP_DESTINATION_EXPORT, FISCAL_IN
11
- from ..constants.icms import ICMS_BASE_TYPE_DEFAULT, ICMS_ST_BASE_TYPE_DEFAULT
12
-
13
- FISCAL_TAX_ID_FIELDS = [
14
- "cofins_tax_id",
15
- "cofins_wh_tax_id",
16
- "cofinsst_tax_id",
17
- "csll_tax_id",
18
- "csll_wh_tax_id",
19
- "icms_tax_id",
20
- "icmsfcp_tax_id",
21
- "icmssn_tax_id",
22
- "icmsst_tax_id",
23
- "icmsfcpst_tax_id",
24
- "ii_tax_id",
25
- "inss_tax_id",
26
- "inss_wh_tax_id",
27
- "ipi_tax_id",
28
- "irpj_tax_id",
29
- "irpj_wh_tax_id",
30
- "issqn_tax_id",
31
- "issqn_wh_tax_id",
32
- "pis_tax_id",
33
- "pis_wh_tax_id",
34
- "pisst_tax_id",
35
- ]
36
-
37
- FISCAL_CST_ID_FIELDS = [
38
- "icms_cst_id",
39
- "ipi_cst_id",
40
- "pis_cst_id",
41
- "pisst_cst_id",
42
- "cofins_cst_id",
43
- "cofinsst_cst_id",
44
- ]
11
+ from ..constants.fiscal import (
12
+ CFOP_DESTINATION_EXPORT,
13
+ FISCAL_IN,
14
+ FISCAL_TAX_ID_FIELDS,
15
+ TAX_BASE_TYPE_PERCENT,
16
+ TAX_DOMAIN_ICMS,
17
+ )
18
+ from ..constants.icms import (
19
+ ICMS_BASE_TYPE_DEFAULT,
20
+ ICMS_ORIGIN_DEFAULT,
21
+ ICMS_ST_BASE_TYPE_DEFAULT,
22
+ )
45
23
 
46
24
 
47
25
  class FiscalDocumentLineMixinMethods(models.AbstractModel):
@@ -81,6 +59,23 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
81
59
  Inject common fiscal fields into view placeholder elements.
82
60
  Used for invoice line, sale order line, purchase order line...
83
61
  """
62
+
63
+ # the list of computed fields we will add to the view when missing
64
+ missing_line_fields = set(
65
+ [
66
+ fname
67
+ for fname, _field in filter(
68
+ lambda item: item[1].compute
69
+ in (
70
+ "_compute_tax_fields",
71
+ "_compute_fiscal_tax_ids",
72
+ "_compute_product_fiscal_fields",
73
+ ),
74
+ self.env["l10n_br_fiscal.document.line.mixin"]._fields.items(),
75
+ )
76
+ ]
77
+ )
78
+
84
79
  fiscal_view = self.env.ref(
85
80
  "l10n_br_fiscal.document_fiscal_line_mixin_form"
86
81
  ).sudo()
@@ -129,13 +124,19 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
129
124
  e.attrib["name"] for e in target_node if e.tag == "field"
130
125
  ]
131
126
  for fiscal_node in fiscal_nodes:
127
+ if fiscal_node.attrib["name"] in missing_line_fields:
128
+ missing_line_fields.remove(fiscal_node.attrib["name"])
132
129
  if fiscal_node.attrib["name"] in existing_fields:
133
130
  continue
134
131
  field = deepcopy(fiscal_node)
135
132
  if not field.attrib.get("optional"):
136
- field.attrib["invisible"] = "0"
137
133
  field.attrib["optional"] = "hide"
138
134
  target_node.append(field)
135
+ for fname in missing_line_fields:
136
+ if fname not in existing_fields:
137
+ target_node.append(
138
+ E.field(name=fname, string=fname, optional="hide")
139
+ )
139
140
  return doc
140
141
 
141
142
  @api.model
@@ -146,23 +147,22 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
146
147
  return arch, view
147
148
 
148
149
  @api.depends(
149
- "fiscal_price",
150
150
  "discount_value",
151
- "insurance_value",
152
- "other_value",
153
- "freight_value",
154
- "fiscal_quantity",
155
151
  "amount_tax_not_included",
156
- "amount_tax_included",
157
152
  "amount_tax_withholding",
158
- "uot_id",
159
- "product_id",
160
- "partner_id",
161
- "company_id",
162
153
  "price_unit",
163
154
  "quantity",
164
- "icms_relief_id",
165
155
  "fiscal_operation_line_id",
156
+ "cfop_id",
157
+ "icms_relief_value",
158
+ "insurance_value",
159
+ "other_value",
160
+ "freight_value",
161
+ "pis_value",
162
+ "cofins_value",
163
+ "icms_value",
164
+ "ii_value",
165
+ "ii_customhouse_charges",
166
166
  )
167
167
  def _compute_fiscal_amounts(self):
168
168
  for record in self:
@@ -171,11 +171,11 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
171
171
  # Total value of products or services
172
172
  record.price_gross = round_curr.round(record.price_unit * record.quantity)
173
173
  record.amount_fiscal = record.price_gross - record.discount_value
174
- record.amount_tax = record.amount_tax_not_included
174
+ record.fiscal_amount_tax = record.amount_tax_not_included
175
175
 
176
176
  add_to_amount = sum(record[a] for a in record._add_fields_to_amount())
177
177
  rm_to_amount = sum(record[r] for r in record._rm_fields_to_amount())
178
- record.amount_untaxed = (
178
+ record.fiscal_amount_untaxed = (
179
179
  record.price_gross
180
180
  - record.discount_value
181
181
  + add_to_amount
@@ -183,13 +183,17 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
183
183
  )
184
184
 
185
185
  # Valor do documento (NF)
186
- record.amount_total = record.amount_untaxed + record.amount_tax
186
+ record.fiscal_amount_total = (
187
+ record.fiscal_amount_untaxed + record.fiscal_amount_tax
188
+ )
187
189
 
188
190
  # Valor Liquido (TOTAL + IMPOSTOS - RETENÇÕES)
189
- record.amount_taxed = record.amount_total - record.amount_tax_withholding
191
+ record.amount_taxed = (
192
+ record.fiscal_amount_total - record.amount_tax_withholding
193
+ )
190
194
 
191
195
  # Valor do documento (NF) - RETENÇÕES
192
- record.amount_total = record.amount_taxed
196
+ record.fiscal_amount_total = record.amount_taxed
193
197
 
194
198
  # Valor financeiro
195
199
  if (
@@ -206,7 +210,7 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
206
210
  record.financial_total_gross = record.financial_total = 0.0
207
211
  record.financial_discount_value = 0.0
208
212
 
209
- @api.depends("tax_icms_or_issqn", "partner_is_public_entity")
213
+ @api.depends("tax_icms_or_issqn", "partner_id")
210
214
  def _compute_allow_csll_irpj(self):
211
215
  """Calculates the possibility of 'CSLL' and 'IRPJ' tax charges."""
212
216
  for line in self:
@@ -233,82 +237,63 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
233
237
  return {f"default_{k}": vals[k] for k in vals.keys()}
234
238
  return vals
235
239
 
236
- @api.onchange("fiscal_operation_id", "company_id", "partner_id", "product_id")
237
- def _onchange_fiscal_operation_id(self):
238
- if self.fiscal_operation_id:
239
- self.fiscal_operation_line_id = self.fiscal_operation_id.line_definition(
240
- company=self.company_id,
241
- partner=self._get_fiscal_partner(),
242
- product=self.product_id,
243
- )
240
+ @api.depends("fiscal_operation_id", "partner_id", "product_id")
241
+ def _compute_fiscal_operation_line_id(self):
242
+ for line in self:
243
+ if line.fiscal_operation_id:
244
+ line.fiscal_operation_line_id = (
245
+ line.fiscal_operation_id.line_definition(
246
+ company=line.company_id,
247
+ partner=line.partner_id,
248
+ product=line.product_id,
249
+ )
250
+ )
244
251
 
245
- def _get_fiscal_tax_ids_dependencies(self):
246
- """
247
- Dynamically get the list of fields dependencies, overriden in l10n_br_purchase.
248
- """
249
- return [
250
- "company_id",
251
- "partner_id",
252
- "fiscal_operation_line_id",
253
- "product_id",
254
- "ncm_id",
255
- "nbs_id",
256
- "nbm_id",
257
- "cest_id",
258
- "city_taxation_code_id",
259
- "service_type_id",
260
- "ind_final",
261
- ]
262
-
263
- @api.depends(lambda self: self._get_fiscal_tax_ids_dependencies())
252
+ @api.depends(
253
+ "partner_id",
254
+ "fiscal_operation_line_id",
255
+ "product_id",
256
+ "ncm_id",
257
+ "nbs_id",
258
+ "nbm_id",
259
+ "cest_id",
260
+ "city_taxation_code_id",
261
+ "service_type_id",
262
+ "ind_final",
263
+ )
264
264
  def _compute_fiscal_tax_ids(self):
265
- """
266
- Use fiscal_operation_line_id to map and compute the applicable Brazilian taxes.
267
-
268
- Among the dependencies, company_id, partner_id and ind_final are related
269
- to the fiscal document/line container. When called from account.move.line
270
- via _inherits on newID records, we read these values from the related aml
271
- to work around and _inherits/precompute limitation.
272
- """
273
- if self._context.get("skip_compute_fiscal_tax_ids"):
274
- return
275
265
  for line in self:
276
- if hasattr(line, "account_line_ids") and line.account_line_ids:
277
- # it seems Odoo 16 ORM has a limitation when line is an
278
- # l10n_br_fiscal.document.line that is edited via an account.move.line
279
- # form and when both are a newID, then line relational field might be
280
- # empty here. But in this case, we detect it and we wrap it back in the
281
- wrapped_line = line.account_line_ids[0]
282
- else:
283
- wrapped_line = line
284
-
285
- if wrapped_line.fiscal_operation_line_id:
286
- mapping_result = wrapped_line.fiscal_operation_line_id.map_fiscal_taxes(
287
- company=wrapped_line.company_id,
288
- partner=wrapped_line._get_fiscal_partner(),
289
- product=wrapped_line.product_id,
290
- ncm=wrapped_line.ncm_id,
291
- nbm=wrapped_line.nbm_id,
292
- nbs=wrapped_line.nbs_id,
293
- cest=wrapped_line.cest_id,
294
- city_taxation_code=wrapped_line.city_taxation_code_id,
295
- service_type=wrapped_line.service_type_id,
296
- ind_final=wrapped_line.ind_final,
266
+ if line.fiscal_operation_line_id:
267
+ mapping_result = line.fiscal_operation_line_id.map_fiscal_taxes(
268
+ company=line.company_id,
269
+ partner=line._get_fiscal_partner(),
270
+ product=line.product_id,
271
+ ncm=line.ncm_id,
272
+ nbm=line.nbm_id,
273
+ nbs=line.nbs_id,
274
+ cest=line.cest_id,
275
+ city_taxation_code=line.city_taxation_code_id,
276
+ service_type=line.service_type_id,
277
+ ind_final=line.ind_final,
297
278
  )
298
279
  line.cfop_id = mapping_result["cfop"]
299
280
  line.ipi_guideline_id = mapping_result["ipi_guideline"]
300
281
  line.icms_tax_benefit_id = mapping_result["icms_tax_benefit_id"]
301
- if wrapped_line._is_imported():
302
- return
282
+
283
+ if line._is_imported():
284
+ continue
303
285
 
304
286
  taxes = line.env["l10n_br_fiscal.tax"]
305
287
  for tax in mapping_result["taxes"].values():
306
288
  taxes |= tax
307
289
  line.fiscal_tax_ids = taxes
308
- line.comment_ids = line.fiscal_operation_line_id.comment_ids
309
290
 
310
- else:
311
- line.fiscal_tax_ids = [Command.clear()]
291
+ @api.depends("fiscal_operation_line_id")
292
+ def _compute_comment_ids(self):
293
+ for line in self:
294
+ line.comment_ids = [
295
+ Command.set(line.fiscal_operation_line_id.comment_ids.ids)
296
+ ]
312
297
 
313
298
  @api.model
314
299
  def _build_null_mask_dict(self) -> dict:
@@ -326,85 +311,115 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
326
311
  mask_dict[fiscal_tax_field] = False
327
312
  return mask_dict
328
313
 
329
- def _get_tax_fields_dependencies(self):
330
- """
331
- Dynamically get the list of fields dependencies, overriden in l10n_br_purchase.
332
- """
333
- # IMPORTANT NOTE: as _compute_fiscal_tax_ids triggers _compute_tax_fields,
334
- # we don't put fields that trigger _compute_fiscal_tax_ids as dependencies here.
335
- return [
336
- "price_unit",
337
- "quantity",
338
- "uom_id",
339
- "fiscal_price",
340
- "fiscal_quantity",
341
- "uot_id",
342
- "discount_value",
343
- "insurance_value",
344
- "ii_customhouse_charges",
345
- "ii_iof_value",
346
- "other_value",
347
- "freight_value",
348
- "cfop_id",
349
- "icmssn_range_id",
350
- "icms_origin",
351
- "icms_cst_id",
352
- "icms_relief_id",
353
- "fiscal_tax_ids",
354
- ]
355
-
356
- @api.depends(lambda self: self._get_tax_fields_dependencies())
314
+ def write(self, vals):
315
+ res = super().write(vals)
316
+
317
+ # Verifica se algum campo de imposto relevante foi alterado no 'write'
318
+ tax_fields_in_vals = [fld for fld in vals if fld in FISCAL_TAX_ID_FIELDS]
319
+
320
+ if tax_fields_in_vals:
321
+ # Por segurança, sempre recalcula se um campo relevante mudou.
322
+ self._update_fiscal_tax_ids()
323
+
324
+ return res
325
+
326
+ def _update_fiscal_tax_ids(self):
327
+ taxes = self.env["l10n_br_fiscal.tax"]
328
+ for fiscal_tax_field in FISCAL_TAX_ID_FIELDS:
329
+ taxes |= self[fiscal_tax_field]
330
+
331
+ for line in self:
332
+ taxes_groups = line.fiscal_tax_ids.mapped("tax_domain")
333
+ fiscal_taxes = line.fiscal_tax_ids.filtered(
334
+ lambda ft, taxes_groups=taxes_groups: ft.tax_domain not in taxes_groups
335
+ )
336
+ line.fiscal_tax_ids = fiscal_taxes + taxes
337
+
338
+ @api.onchange(*FISCAL_TAX_ID_FIELDS)
339
+ def _onchange_fiscal_taxes(self):
340
+ self._update_fiscal_tax_ids()
341
+
342
+ @api.depends(
343
+ "partner_id",
344
+ "fiscal_tax_ids",
345
+ "product_id",
346
+ "price_unit",
347
+ "quantity",
348
+ "uom_id",
349
+ "fiscal_price",
350
+ "fiscal_quantity",
351
+ "uot_id",
352
+ "discount_value",
353
+ "insurance_value",
354
+ "ii_customhouse_charges",
355
+ "ii_iof_value",
356
+ "other_value",
357
+ "freight_value",
358
+ "ncm_id",
359
+ "nbs_id",
360
+ "nbm_id",
361
+ "cest_id",
362
+ "fiscal_operation_line_id",
363
+ "cfop_id",
364
+ "icmssn_range_id",
365
+ "icms_origin",
366
+ "icms_cst_id",
367
+ "ind_final",
368
+ "icms_relief_id",
369
+ )
357
370
  def _compute_tax_fields(self):
358
371
  """
359
372
  Compute base, percent, value... tax fields for ICMS, IPI, PIS, COFINS... taxes.
360
373
  """
361
- if self._context.get("skip_compute_tax_fields"):
362
- return
363
-
364
374
  null_mask = None
365
375
  for line in self.filtered(lambda line: not line._is_imported()):
366
- if hasattr(line, "account_line_ids") and line.account_line_ids:
367
- # it seems Odoo 16 ORM has a limitation when line is an
368
- # l10n_br_fiscal.document.line that is edited via an account.move.line
369
- # form and when both are a newID, then line relational field might be
370
- # empty here. But in this case, we detect it and we wrap it back in the
371
- wrapped_line = line.account_line_ids[0]
372
- else:
373
- wrapped_line = line
374
-
375
376
  if null_mask is None:
376
377
  null_mask = self._build_null_mask_dict()
377
378
  to_update = null_mask.copy()
378
- if wrapped_line.fiscal_operation_line_id:
379
- compute_result = wrapped_line.fiscal_tax_ids.compute_taxes(
380
- company=wrapped_line.company_id,
381
- partner=wrapped_line._get_fiscal_partner(),
382
- product=wrapped_line.product_id,
383
- price_unit=wrapped_line.price_unit,
384
- quantity=wrapped_line.quantity,
385
- uom_id=wrapped_line.uom_id,
386
- fiscal_price=wrapped_line.fiscal_price,
387
- fiscal_quantity=wrapped_line.fiscal_quantity,
388
- uot_id=wrapped_line.uot_id,
389
- discount_value=wrapped_line.discount_value,
390
- insurance_value=wrapped_line.insurance_value,
391
- ii_customhouse_charges=wrapped_line.ii_customhouse_charges,
392
- ii_iof_value=wrapped_line.ii_iof_value,
393
- other_value=wrapped_line.other_value,
394
- freight_value=wrapped_line.freight_value,
395
- ncm=wrapped_line.ncm_id,
396
- nbs=wrapped_line.nbs_id,
397
- nbm=wrapped_line.nbm_id,
398
- cest=wrapped_line.cest_id,
399
- operation_line=wrapped_line.fiscal_operation_line_id,
400
- cfop=wrapped_line.cfop_id,
401
- icmssn_range=wrapped_line.icmssn_range_id,
402
- icms_origin=wrapped_line.icms_origin,
403
- icms_cst_id=wrapped_line.icms_cst_id,
404
- ind_final=wrapped_line.ind_final,
405
- icms_relief_id=wrapped_line.icms_relief_id,
379
+ # prepare with default values
380
+ to_update.update(
381
+ {
382
+ "icms_base_type": ICMS_BASE_TYPE_DEFAULT,
383
+ "icmsst_base_type": ICMS_ST_BASE_TYPE_DEFAULT,
384
+ "ipi_base_type": TAX_BASE_TYPE_PERCENT,
385
+ "cofins_base_type": TAX_BASE_TYPE_PERCENT,
386
+ "cofinsst_base_type": TAX_BASE_TYPE_PERCENT,
387
+ "cofins_wh_base_type": TAX_BASE_TYPE_PERCENT,
388
+ "pis_base_type": TAX_BASE_TYPE_PERCENT,
389
+ "pisst_base_type": TAX_BASE_TYPE_PERCENT,
390
+ "pis_wh_base_type": TAX_BASE_TYPE_PERCENT,
391
+ }
392
+ )
393
+ if line.fiscal_operation_line_id:
394
+ compute_result = line.fiscal_tax_ids.compute_taxes(
395
+ company=line.company_id,
396
+ partner=line._get_fiscal_partner(),
397
+ product=line.product_id,
398
+ price_unit=line.price_unit,
399
+ quantity=line.quantity,
400
+ uom_id=line.uom_id,
401
+ fiscal_price=line.fiscal_price,
402
+ fiscal_quantity=line.fiscal_quantity,
403
+ uot_id=line.uot_id,
404
+ discount_value=line.discount_value,
405
+ insurance_value=line.insurance_value,
406
+ ii_customhouse_charges=line.ii_customhouse_charges,
407
+ ii_iof_value=line.ii_iof_value,
408
+ other_value=line.other_value,
409
+ freight_value=line.freight_value,
410
+ ncm=line.ncm_id,
411
+ nbs=line.nbs_id,
412
+ nbm=line.nbm_id,
413
+ cest=line.cest_id,
414
+ operation_line=line.fiscal_operation_line_id,
415
+ cfop=line.cfop_id,
416
+ icmssn_range=line.icmssn_range_id,
417
+ icms_origin=line.icms_origin,
418
+ icms_cst_id=line.icms_cst_id,
419
+ ind_final=line.ind_final,
420
+ icms_relief_id=line.icms_relief_id,
406
421
  )
407
- to_update.update(wrapped_line._prepare_tax_fields(compute_result))
422
+ to_update.update(line._prepare_tax_fields(compute_result))
408
423
  else:
409
424
  compute_result = {}
410
425
  to_update.update(
@@ -419,11 +434,11 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
419
434
  "estimate_tax": compute_result.get("estimate_tax", 0.0),
420
435
  }
421
436
  )
422
- in_draft_mode = wrapped_line != wrapped_line._origin
437
+ in_draft_mode = line != line._origin
423
438
  if in_draft_mode:
424
- wrapped_line.update(to_update)
439
+ line.update(to_update)
425
440
  else:
426
- wrapped_line.write(to_update)
441
+ line.write(to_update)
427
442
 
428
443
  def _prepare_tax_fields(self, compute_result):
429
444
  self.ensure_one()
@@ -454,6 +469,10 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
454
469
  "cost_price": line.product_id.standard_price,
455
470
  }.get(line.fiscal_operation_id.default_price_unit, 0)
456
471
 
472
+ def _get_document(self):
473
+ self.ensure_one()
474
+ return self.document_id
475
+
457
476
  def _get_fiscal_partner(self):
458
477
  """
459
478
  Meant to be overriden when the l10n_br_fiscal.document partner_id should not
@@ -465,48 +484,47 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
465
484
  self.ensure_one()
466
485
  return self.partner_id
467
486
 
468
- @api.onchange("product_id")
469
- def _onchange_product_id_fiscal(self):
470
- if not self.fiscal_operation_id:
471
- return
472
- if self.product_id:
473
- self.name = self.product_id.display_name
474
- self.fiscal_type = self.product_id.fiscal_type
475
- self.uom_id = self.product_id.uom_id
476
- self.ncm_id = self.product_id.ncm_id
477
- self.nbm_id = self.product_id.nbm_id
478
- self.tax_icms_or_issqn = self.product_id.tax_icms_or_issqn
479
- self.icms_origin = self.product_id.icms_origin
480
- self.cest_id = self.product_id.cest_id
481
- self.nbs_id = self.product_id.nbs_id
482
- self.fiscal_genre_id = self.product_id.fiscal_genre_id
483
- self.service_type_id = self.product_id.service_type_id
484
- self.uot_id = self.product_id.uot_id or self.product_id.uom_id
485
- if self.product_id.city_taxation_code_ids:
486
- company_city_id = self.company_id.city_id
487
- city_id = self.product_id.city_taxation_code_ids.filtered(
488
- lambda r: r.city_id == company_city_id
489
- )
490
- if city_id:
491
- self.city_taxation_code_id = city_id
492
- self.issqn_fg_city_id = company_city_id
493
- else:
494
- self.name = False
495
- self.fiscal_type = False
496
- self.uom_id = False
497
- self.ncm_id = False
498
- self.nbm_id = False
499
- self.tax_icms_or_issqn = False
500
- self.icms_origin = False
501
- self.cest_id = False
502
- self.nbs_id = False
503
- self.fiscal_genre_id = False
504
- self.service_type_id = False
505
- self.city_taxation_code_id = False
506
- self.uot_id = False
507
-
508
- self._compute_price_unit_fiscal()
509
- self._onchange_fiscal_operation_id()
487
+ @api.depends("product_id")
488
+ def _compute_product_fiscal_fields(self):
489
+ for line in self:
490
+ if not line.product_id:
491
+ # reset to default values:
492
+ line.fiscal_type = False
493
+ line.ncm_id = False
494
+ line.nbm_id = False
495
+ line.tax_icms_or_issqn = TAX_DOMAIN_ICMS
496
+ line.icms_origin = ICMS_ORIGIN_DEFAULT
497
+ line.cest_id = False
498
+ line.nbs_id = False
499
+ line.fiscal_genre_id = False
500
+ line.service_type_id = False
501
+ continue
502
+ p = line.product_id
503
+ line.fiscal_type = p.fiscal_type
504
+ line.ncm_id = p.ncm_id
505
+ line.nbm_id = p.nbm_id
506
+ line.tax_icms_or_issqn = p.tax_icms_or_issqn
507
+ line.icms_origin = p.icms_origin
508
+ line.cest_id = p.cest_id
509
+ line.nbs_id = p.nbs_id
510
+ line.fiscal_genre_id = p.fiscal_genre_id
511
+ line.service_type_id = p.service_type_id
512
+
513
+ @api.depends("product_id")
514
+ def _compute_city_taxation_code_id(self):
515
+ for line in self:
516
+ if not line.product_id:
517
+ line.city_taxation_code_id = False
518
+ continue
519
+ company_city = line.company_id.city_id
520
+ city_tax_codes = line.product_id.city_taxation_code_ids
521
+ city_tax_code = city_tax_codes.filtered(
522
+ lambda r, _city_id=company_city: r.city_id == _city_id
523
+ )
524
+ if city_tax_code:
525
+ line.city_taxation_code_id = city_tax_code
526
+ else:
527
+ line.city_taxation_code_id = False
510
528
 
511
529
  def _prepare_fields_issqn(self, tax_dict):
512
530
  self.ensure_one()
@@ -749,68 +767,31 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
749
767
  "cofinsst_value": tax_dict.get("tax_value", 0.00),
750
768
  }
751
769
 
752
- @api.onchange(*FISCAL_TAX_ID_FIELDS)
753
- def _onchange_fiscal_taxes(self):
754
- taxes = self.env["l10n_br_fiscal.tax"]
755
- for fiscal_tax_field in FISCAL_TAX_ID_FIELDS:
756
- taxes |= self[fiscal_tax_field]
757
-
758
- for line in self:
759
- taxes_groups = line.fiscal_tax_ids.mapped("tax_domain")
760
- fiscal_taxes = line.fiscal_tax_ids.filtered(
761
- lambda ft, taxes_groups=taxes_groups: ft.tax_domain not in taxes_groups
762
- )
763
- line.fiscal_tax_ids = fiscal_taxes + taxes
764
-
765
- @api.depends("uom_id")
770
+ @api.depends("product_id", "uom_id")
766
771
  def _compute_uot_id(self):
767
772
  for line in self:
768
- if not line.uot_id:
769
- line.uot_id = line.uom_id
770
-
771
- @api.onchange("price_unit")
772
- def _onchange_price_unit_fiscal(self):
773
- self.fiscal_price = 0
774
- self._compute_fiscal_price()
773
+ p = line.product_id
774
+ line.uot_id = (p.uot_id if p else False) or line.uom_id
775
775
 
776
776
  @api.depends("price_unit")
777
777
  def _compute_fiscal_price(self):
778
778
  for line in self:
779
- # this test and the onchange are required to avoid
780
- # resetting manual changes in fiscal_price
781
- if not line.fiscal_price:
782
- if line.product_id and line.price_unit:
783
- line.fiscal_price = line.price_unit / (
784
- line.product_id.uot_factor or 1.0
785
- )
786
- else:
787
- line.fiscal_price = line.price_unit
788
-
789
- @api.onchange("quantity")
790
- def _onchange_quantity_fiscal(self):
791
- self.fiscal_quantity = 0
792
- self._compute_fiscal_quantity()
779
+ if line.product_id and line.price_unit:
780
+ line.fiscal_price = line.price_unit / (
781
+ line.product_id.uot_factor or 1.0
782
+ )
783
+ else:
784
+ line.fiscal_price = line.price_unit
793
785
 
794
786
  @api.depends("quantity")
795
787
  def _compute_fiscal_quantity(self):
796
788
  for line in self:
797
- # this test and the onchange are required to avoid
798
- # resetting manual changes in fiscal_quantity
799
- if not line.fiscal_quantity:
800
- if line.product_id and line.quantity:
801
- line.fiscal_quantity = line.quantity * (
802
- line.product_id.uot_factor or 1.0
803
- )
804
- else:
805
- line.fiscal_quantity = line.quantity
806
-
807
- @api.onchange("city_taxation_code_id")
808
- def _onchange_city_taxation_code_id(self):
809
- if self.city_taxation_code_id:
810
- self.cnae_id = self.city_taxation_code_id.cnae_id
811
- self._onchange_fiscal_operation_id()
812
- if self.city_taxation_code_id.city_id:
813
- self.update({"issqn_fg_city_id": self.city_taxation_code_id.city_id})
789
+ if line.product_id and line.quantity:
790
+ line.fiscal_quantity = line.quantity * (
791
+ line.product_id.uot_factor or 1.0
792
+ )
793
+ else:
794
+ line.fiscal_quantity = line.quantity
814
795
 
815
796
  @api.model
816
797
  def _add_fields_to_amount(self):