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

Potentially problematic release.


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

Files changed (78) hide show
  1. odoo/addons/l10n_br_fiscal/README.rst +1 -1
  2. odoo/addons/l10n_br_fiscal/__manifest__.py +7 -3
  3. odoo/addons/l10n_br_fiscal/constants/fiscal.py +64 -18
  4. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.cest.csv +1043 -983
  5. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.cst.csv +58 -0
  6. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.document.type.csv +1 -0
  7. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.operation.indicator.csv +27 -0
  8. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.tax.classification.csv +163 -0
  9. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.tax.csv +31 -0
  10. odoo/addons/l10n_br_fiscal/data/l10n_br_fiscal.tax.group.csv +3 -0
  11. odoo/addons/l10n_br_fiscal/data/operation_data.xml +1 -1
  12. odoo/addons/l10n_br_fiscal/demo/fiscal_document_demo.xml +3 -179
  13. odoo/addons/l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml +0 -4
  14. odoo/addons/l10n_br_fiscal/demo/fiscal_operation_demo.xml +2 -2
  15. odoo/addons/l10n_br_fiscal/i18n/l10n_br_fiscal.pot +665 -79
  16. odoo/addons/l10n_br_fiscal/i18n/pt_BR.po +713 -102
  17. odoo/addons/l10n_br_fiscal/migrations/18.0.3.0.0/pre-migration.py +30 -0
  18. odoo/addons/l10n_br_fiscal/models/__init__.py +2 -2
  19. odoo/addons/l10n_br_fiscal/models/comment.py +3 -1
  20. odoo/addons/l10n_br_fiscal/models/data_abstract.py +9 -6
  21. odoo/addons/l10n_br_fiscal/models/document.py +27 -8
  22. odoo/addons/l10n_br_fiscal/models/document_line.py +51 -8
  23. odoo/addons/l10n_br_fiscal/models/document_line_mixin.py +1107 -35
  24. odoo/addons/l10n_br_fiscal/models/document_mixin.py +244 -6
  25. odoo/addons/l10n_br_fiscal/models/document_related.py +1 -1
  26. odoo/addons/l10n_br_fiscal/models/document_serie.py +33 -0
  27. odoo/addons/l10n_br_fiscal/models/icms_regulation.py +1 -1
  28. odoo/addons/l10n_br_fiscal/models/operation_dashboard.py +3 -2
  29. odoo/addons/l10n_br_fiscal/models/operation_indicator.py +58 -0
  30. odoo/addons/l10n_br_fiscal/models/operation_line.py +28 -0
  31. odoo/addons/l10n_br_fiscal/models/partner_profile.py +6 -0
  32. odoo/addons/l10n_br_fiscal/models/product_template.py +4 -0
  33. odoo/addons/l10n_br_fiscal/models/res_company.py +17 -0
  34. odoo/addons/l10n_br_fiscal/models/res_partner.py +17 -0
  35. odoo/addons/l10n_br_fiscal/models/simplified_tax_range.py +8 -0
  36. odoo/addons/l10n_br_fiscal/models/tax.py +7 -3
  37. odoo/addons/l10n_br_fiscal/models/tax_classification.py +81 -0
  38. odoo/addons/l10n_br_fiscal/security/fiscal_security.xml +6 -16
  39. odoo/addons/l10n_br_fiscal/security/ir.model.access.csv +7 -2
  40. odoo/addons/l10n_br_fiscal/static/description/index.html +1 -1
  41. odoo/addons/l10n_br_fiscal/tests/__init__.py +2 -0
  42. odoo/addons/l10n_br_fiscal/tests/test_document_edition.py +175 -10
  43. odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_generic.py +13 -42
  44. odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_nfse.py +0 -5
  45. odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_serie.py +60 -0
  46. odoo/addons/l10n_br_fiscal/tests/test_tax_benefit.py +2 -5
  47. odoo/addons/l10n_br_fiscal/tests/test_tax_classification.py +110 -0
  48. odoo/addons/l10n_br_fiscal/views/document_line_mixin_view.xml +107 -4
  49. odoo/addons/l10n_br_fiscal/views/document_line_view.xml +7 -3
  50. odoo/addons/l10n_br_fiscal/views/document_view.xml +34 -15
  51. odoo/addons/l10n_br_fiscal/views/icms_regulation_view.xml +1 -5
  52. odoo/addons/l10n_br_fiscal/views/l10n_br_fiscal_action.xml +30 -0
  53. odoo/addons/l10n_br_fiscal/views/l10n_br_fiscal_menu.xml +16 -9
  54. odoo/addons/l10n_br_fiscal/views/nbs_view.xml +1 -5
  55. odoo/addons/l10n_br_fiscal/views/ncm_view.xml +1 -5
  56. odoo/addons/l10n_br_fiscal/views/operation_dashboard_view.xml +3 -3
  57. odoo/addons/l10n_br_fiscal/views/operation_indicator_view.xml +75 -0
  58. odoo/addons/l10n_br_fiscal/views/operation_line_view.xml +4 -5
  59. odoo/addons/l10n_br_fiscal/views/operation_view.xml +1 -5
  60. odoo/addons/l10n_br_fiscal/views/product_product_view.xml +33 -6
  61. odoo/addons/l10n_br_fiscal/views/product_template_view.xml +22 -4
  62. odoo/addons/l10n_br_fiscal/views/res_company_view.xml +6 -0
  63. odoo/addons/l10n_br_fiscal/views/res_partner_view.xml +10 -0
  64. odoo/addons/l10n_br_fiscal/views/service_type_view.xml +1 -5
  65. odoo/addons/l10n_br_fiscal/views/tax_classification.xml +108 -0
  66. odoo/addons/l10n_br_fiscal/views/tax_definition_view.xml +1 -5
  67. odoo/addons/l10n_br_fiscal/views/tax_view.xml +2 -2
  68. odoo/addons/l10n_br_fiscal/wizards/__init__.py +1 -1
  69. odoo/addons/l10n_br_fiscal/wizards/base_wizard_mixin.py +1 -1
  70. odoo/addons/l10n_br_fiscal/wizards/document_import_wizard.py +234 -0
  71. odoo/addons/l10n_br_fiscal/wizards/{document_import_wizard_mixin.xml → document_import_wizard.xml} +26 -7
  72. {odoo_addon_l10n_br_fiscal-18.0.2.0.0.10.dist-info → odoo_addon_l10n_br_fiscal-18.0.7.1.0.dist-info}/METADATA +3 -3
  73. {odoo_addon_l10n_br_fiscal-18.0.2.0.0.10.dist-info → odoo_addon_l10n_br_fiscal-18.0.7.1.0.dist-info}/RECORD +75 -68
  74. odoo/addons/l10n_br_fiscal/models/document_line_mixin_methods.py +0 -837
  75. odoo/addons/l10n_br_fiscal/models/document_mixin_methods.py +0 -349
  76. odoo/addons/l10n_br_fiscal/wizards/document_import_wizard_mixin.py +0 -129
  77. {odoo_addon_l10n_br_fiscal-18.0.2.0.0.10.dist-info → odoo_addon_l10n_br_fiscal-18.0.7.1.0.dist-info}/WHEEL +0 -0
  78. {odoo_addon_l10n_br_fiscal-18.0.2.0.0.10.dist-info → odoo_addon_l10n_br_fiscal-18.0.7.1.0.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,29 @@
1
1
  # Copyright (C) 2019 Renato Lima - Akretion <renato.lima@akretion.com.br>
2
2
  # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
3
3
 
4
- from odoo import api, fields, models
4
+ from copy import deepcopy
5
+
6
+ from lxml import etree
7
+ from lxml.builder import E
8
+
9
+ from odoo import Command, api, fields, models
5
10
 
6
11
  from ..constants.fiscal import (
12
+ CFOP_DESTINATION_EXPORT,
13
+ FINAL_CUSTOMER,
7
14
  FISCAL_COMMENT_LINE,
15
+ FISCAL_IN,
16
+ FISCAL_TAX_ID_FIELDS,
8
17
  PRODUCT_FISCAL_TYPE,
9
18
  TAX_BASE_TYPE,
10
19
  TAX_BASE_TYPE_PERCENT,
20
+ TAX_DOMAIN_CBS,
11
21
  TAX_DOMAIN_COFINS,
12
22
  TAX_DOMAIN_COFINS_ST,
13
23
  TAX_DOMAIN_COFINS_WH,
14
24
  TAX_DOMAIN_CSLL,
15
25
  TAX_DOMAIN_CSLL_WH,
26
+ TAX_DOMAIN_IBS,
16
27
  TAX_DOMAIN_ICMS,
17
28
  TAX_DOMAIN_ICMS_FCP,
18
29
  TAX_DOMAIN_ICMS_FCP_ST,
@@ -66,16 +77,9 @@ class FiscalDocumentLineMixin(models.AbstractModel):
66
77
  ISSQN, etc.), covering their respective bases, rates, and
67
78
  calculated values.
68
79
  - Line-level totals and cost components.
69
-
70
- It inherits computational logic, onchange handlers, and other complex
71
- methods from `l10n_br_fiscal.document.line.mixin.methods`. Models
72
- that represent actual document lines (e.g.,
73
- `l10n_br_fiscal.document.line`) should inherit this mixin to
74
- acquire the necessary fiscal field definitions and associated behaviors.
75
80
  """
76
81
 
77
82
  _name = "l10n_br_fiscal.document.line.mixin"
78
- _inherit = "l10n_br_fiscal.document.line.mixin.methods"
79
83
  _description = "Document Fiscal Mixin"
80
84
 
81
85
  @api.model
@@ -98,6 +102,810 @@ class FiscalDocumentLineMixin(models.AbstractModel):
98
102
  domain = [("state", "=", "approved")]
99
103
  return domain
100
104
 
105
+ @api.model
106
+ def inject_fiscal_fields(
107
+ self,
108
+ doc,
109
+ view_ref="l10n_br_fiscal.document_fiscal_line_mixin_form",
110
+ xpath_mappings=None,
111
+ ):
112
+ """
113
+ Inject common fiscal fields into view placeholder elements.
114
+ Used for invoice line, sale order line, purchase order line...
115
+ """
116
+
117
+ # the list of computed fields we will add to the view when missing
118
+ missing_line_fields = set(
119
+ [
120
+ fname
121
+ for fname, _field in filter(
122
+ lambda item: item[1].compute
123
+ in (
124
+ "_compute_tax_fields",
125
+ "_compute_fiscal_tax_ids",
126
+ "_compute_product_fiscal_fields",
127
+ ),
128
+ self.env["l10n_br_fiscal.document.line.mixin"]._fields.items(),
129
+ )
130
+ ]
131
+ )
132
+
133
+ fiscal_view = self.env.ref(
134
+ "l10n_br_fiscal.document_fiscal_line_mixin_form"
135
+ ).sudo()
136
+ fsc_doc = etree.fromstring(
137
+ fiscal_view.with_context(inherit_branding=True).get_combined_arch()
138
+ )
139
+
140
+ if xpath_mappings is None:
141
+ xpath_mappings = (
142
+ # (placeholder_xpath, fiscal_xpath)
143
+ (".//group[@name='fiscal_fields']", "//group[@name='fiscal_fields']"),
144
+ (".//page[@name='fiscal_taxes']", "//page[@name='fiscal_taxes']"),
145
+ (
146
+ ".//page[@name='fiscal_line_extra_info']",
147
+ "//page[@name='fiscal_line_extra_info']",
148
+ ),
149
+ # these will only collect (invisible) fields for onchanges:
150
+ (
151
+ ".//control[@name='fiscal_fields']...",
152
+ "//group[@name='fiscal_fields']//field",
153
+ ),
154
+ (
155
+ ".//control[@name='fiscal_taxes_fields']...",
156
+ "//page[@name='fiscal_taxes']//field",
157
+ ),
158
+ (
159
+ ".//control[@name='fiscal_line_extra_info_fields']...",
160
+ "//page[@name='fiscal_line_extra_info']//field",
161
+ ),
162
+ )
163
+ for placeholder_xpath, fiscal_xpath in xpath_mappings:
164
+ placeholder_nodes = doc.findall(placeholder_xpath)
165
+ if not placeholder_nodes:
166
+ continue
167
+ fiscal_nodes = fsc_doc.xpath(fiscal_xpath)
168
+ for target_node in placeholder_nodes:
169
+ if len(fiscal_nodes) == 1:
170
+ # replace unique placeholder
171
+ # (deepcopy is required to inject fiscal nodes in possible
172
+ # next places)
173
+ replace_node = deepcopy(fiscal_nodes[0])
174
+ target_node.getparent().replace(target_node, replace_node)
175
+ else:
176
+ # append multiple fields to placeholder container
177
+ existing_fields = [
178
+ e.attrib["name"] for e in target_node if e.tag == "field"
179
+ ]
180
+ for fiscal_node in fiscal_nodes:
181
+ if fiscal_node.attrib["name"] in missing_line_fields:
182
+ missing_line_fields.remove(fiscal_node.attrib["name"])
183
+ if fiscal_node.attrib["name"] in existing_fields:
184
+ continue
185
+ field = deepcopy(fiscal_node)
186
+ if not field.attrib.get("optional"):
187
+ field.attrib["optional"] = "hide"
188
+ target_node.append(field)
189
+ for fname in missing_line_fields:
190
+ if fname not in existing_fields:
191
+ target_node.append(
192
+ E.field(name=fname, string=fname, optional="hide")
193
+ )
194
+ return doc
195
+
196
+ @api.model
197
+ def _get_view(self, view_id=None, view_type="form", **options):
198
+ arch, view = super()._get_view(view_id, view_type, **options)
199
+ if view_type == "form":
200
+ arch = self.inject_fiscal_fields(arch)
201
+ return arch, view
202
+
203
+ @api.depends(
204
+ "discount_value",
205
+ "amount_tax_not_included",
206
+ "amount_tax_withholding",
207
+ "price_unit",
208
+ "quantity",
209
+ "fiscal_operation_line_id",
210
+ "cfop_id",
211
+ "icms_relief_value",
212
+ "insurance_value",
213
+ "other_value",
214
+ "freight_value",
215
+ "pis_value",
216
+ "cofins_value",
217
+ "icms_value",
218
+ "ii_value",
219
+ "ii_customhouse_charges",
220
+ )
221
+ def _compute_fiscal_amounts(self):
222
+ for record in self:
223
+ round_curr = record.currency_id or self.env.ref("base.BRL")
224
+
225
+ # Total value of products or services
226
+ record.price_gross = round_curr.round(record.price_unit * record.quantity)
227
+ record.amount_fiscal = record.price_gross - record.discount_value
228
+ record.fiscal_amount_tax = record.amount_tax_not_included
229
+
230
+ add_to_amount = sum(record[a] for a in record._add_fields_to_amount())
231
+ rm_to_amount = sum(record[r] for r in record._rm_fields_to_amount())
232
+ record.fiscal_amount_untaxed = (
233
+ record.price_gross
234
+ - record.discount_value
235
+ + add_to_amount
236
+ - rm_to_amount
237
+ )
238
+
239
+ # Valor do documento (NF)
240
+ record.fiscal_amount_total = (
241
+ record.fiscal_amount_untaxed + record.fiscal_amount_tax
242
+ )
243
+
244
+ # Valor Liquido (TOTAL + IMPOSTOS - RETENÇÕES)
245
+ record.amount_taxed = (
246
+ record.fiscal_amount_total - record.amount_tax_withholding
247
+ )
248
+
249
+ # Valor do documento (NF) - RETENÇÕES
250
+ record.fiscal_amount_total = record.amount_taxed
251
+
252
+ # Valor financeiro
253
+ if (
254
+ record.fiscal_operation_line_id
255
+ and record.fiscal_operation_line_id.add_to_amount
256
+ and (not record.cfop_id or record.cfop_id.finance_move)
257
+ ):
258
+ record.financial_total = record.amount_taxed
259
+ record.financial_total_gross = (
260
+ record.financial_total + record.discount_value
261
+ )
262
+ record.financial_discount_value = record.discount_value
263
+ else:
264
+ record.financial_total_gross = record.financial_total = 0.0
265
+ record.financial_discount_value = 0.0
266
+
267
+ @api.depends("tax_icms_or_issqn", "partner_id")
268
+ def _compute_allow_csll_irpj(self):
269
+ """Calculates the possibility of 'CSLL' and 'IRPJ' tax charges."""
270
+ for line in self:
271
+ # Determine if 'CSLL' and 'IRPJ' taxes may apply:
272
+ # 1. When providing services (tax_icms_or_issqn == "issqn")
273
+ # 2. When supplying products to public entities (partner_is_public_entity
274
+ # is True)
275
+ if line.tax_icms_or_issqn == "issqn" or line.partner_is_public_entity:
276
+ line.allow_csll_irpj = True # Tax charges may apply
277
+ else:
278
+ line.allow_csll_irpj = False # No tax charges expected
279
+
280
+ def _prepare_br_fiscal_dict(self, default=False):
281
+ self.ensure_one()
282
+ fields = self.env["l10n_br_fiscal.document.line.mixin"]._fields.keys()
283
+
284
+ # we now read the record fiscal fields except the m2m tax:
285
+ vals = self._convert_to_write(self.read(fields)[0])
286
+
287
+ # remove id field to avoid conflicts
288
+ vals.pop("id", None)
289
+
290
+ if default: # in case you want to use new rather than write later
291
+ return {f"default_{k}": vals[k] for k in vals.keys()}
292
+ return vals
293
+
294
+ @api.depends("fiscal_operation_id", "partner_id", "product_id")
295
+ def _compute_fiscal_operation_line_id(self):
296
+ for line in self:
297
+ if line.fiscal_operation_id:
298
+ line.fiscal_operation_line_id = (
299
+ line.fiscal_operation_id.line_definition(
300
+ company=line.company_id,
301
+ partner=line.partner_id,
302
+ product=line.product_id,
303
+ )
304
+ )
305
+
306
+ @api.depends(
307
+ "partner_id",
308
+ "fiscal_operation_line_id",
309
+ "product_id",
310
+ "ncm_id",
311
+ "nbs_id",
312
+ "nbm_id",
313
+ "cest_id",
314
+ "city_taxation_code_id",
315
+ "service_type_id",
316
+ "ind_final",
317
+ )
318
+ def _compute_fiscal_tax_ids(self):
319
+ for line in self:
320
+ if line.fiscal_operation_line_id:
321
+ mapping_result = line.fiscal_operation_line_id.map_fiscal_taxes(
322
+ company=line.company_id,
323
+ partner=line._get_fiscal_partner(),
324
+ product=line.product_id,
325
+ ncm=line.ncm_id,
326
+ nbm=line.nbm_id,
327
+ nbs=line.nbs_id,
328
+ cest=line.cest_id,
329
+ city_taxation_code=line.city_taxation_code_id,
330
+ service_type=line.service_type_id,
331
+ ind_final=line.ind_final,
332
+ )
333
+ line.cfop_id = mapping_result["cfop"]
334
+ line.ipi_guideline_id = mapping_result["ipi_guideline"]
335
+ line.tax_classification_id = mapping_result["tax_classification"]
336
+ line.icms_tax_benefit_id = mapping_result["icms_tax_benefit_id"]
337
+
338
+ if line._is_imported():
339
+ continue
340
+
341
+ taxes = line.env["l10n_br_fiscal.tax"]
342
+ for tax in mapping_result["taxes"].values():
343
+ taxes |= tax
344
+ line.fiscal_tax_ids = taxes
345
+
346
+ @api.depends("fiscal_operation_line_id")
347
+ def _compute_comment_ids(self):
348
+ for line in self:
349
+ line.comment_ids = [
350
+ Command.set(line.fiscal_operation_line_id.comment_ids.ids)
351
+ ]
352
+
353
+ @api.model
354
+ def _build_null_mask_dict(self) -> dict:
355
+ """
356
+ Build a null values mask dict to reset all fiscal fields.
357
+ """
358
+ mask_dict = {
359
+ f[0]: False
360
+ for f in filter(
361
+ lambda f: f[1].compute == "_compute_tax_fields",
362
+ self.env["l10n_br_fiscal.document.line.mixin"]._fields.items(),
363
+ )
364
+ }
365
+ for fiscal_tax_field in FISCAL_TAX_ID_FIELDS:
366
+ mask_dict[fiscal_tax_field] = False
367
+ return mask_dict
368
+
369
+ def write(self, vals):
370
+ res = super().write(vals)
371
+
372
+ # Verifica se algum campo de imposto relevante foi alterado no 'write'
373
+ tax_fields_in_vals = [fld for fld in vals if fld in FISCAL_TAX_ID_FIELDS]
374
+
375
+ if tax_fields_in_vals:
376
+ # Por segurança, sempre recalcula se um campo relevante mudou.
377
+ self._update_fiscal_tax_ids()
378
+
379
+ return res
380
+
381
+ def _update_fiscal_tax_ids(self):
382
+ taxes = self.env["l10n_br_fiscal.tax"]
383
+ for fiscal_tax_field in FISCAL_TAX_ID_FIELDS:
384
+ taxes |= self[fiscal_tax_field]
385
+
386
+ for line in self:
387
+ taxes_groups = line.fiscal_tax_ids.mapped("tax_domain")
388
+ fiscal_taxes = line.fiscal_tax_ids.filtered(
389
+ lambda ft, taxes_groups=taxes_groups: ft.tax_domain not in taxes_groups
390
+ )
391
+ line.fiscal_tax_ids = fiscal_taxes + taxes
392
+
393
+ @api.onchange(*FISCAL_TAX_ID_FIELDS)
394
+ def _onchange_fiscal_taxes(self):
395
+ self._update_fiscal_tax_ids()
396
+
397
+ @api.depends(
398
+ "partner_id",
399
+ "fiscal_tax_ids",
400
+ "product_id",
401
+ "price_unit",
402
+ "quantity",
403
+ "uom_id",
404
+ "fiscal_price",
405
+ "fiscal_quantity",
406
+ "uot_id",
407
+ "discount_value",
408
+ "insurance_value",
409
+ "ii_customhouse_charges",
410
+ "ii_iof_value",
411
+ "other_value",
412
+ "freight_value",
413
+ "ncm_id",
414
+ "nbs_id",
415
+ "nbm_id",
416
+ "cest_id",
417
+ "fiscal_operation_line_id",
418
+ "cfop_id",
419
+ "icmssn_range_id",
420
+ "icms_origin",
421
+ "icms_cst_id",
422
+ "ind_final",
423
+ "icms_relief_id",
424
+ )
425
+ def _compute_tax_fields(self):
426
+ """
427
+ Compute base, percent, value... tax fields for ICMS, IPI, PIS, COFINS... taxes.
428
+ """
429
+ null_mask = None
430
+ for line in self.filtered(lambda line: not line._is_imported()):
431
+ if null_mask is None:
432
+ null_mask = self._build_null_mask_dict()
433
+ to_update = null_mask.copy()
434
+ # prepare with default values
435
+ to_update.update(
436
+ {
437
+ "icms_base_type": ICMS_BASE_TYPE_DEFAULT,
438
+ "icmsst_base_type": ICMS_ST_BASE_TYPE_DEFAULT,
439
+ "ipi_base_type": TAX_BASE_TYPE_PERCENT,
440
+ "cofins_base_type": TAX_BASE_TYPE_PERCENT,
441
+ "cofinsst_base_type": TAX_BASE_TYPE_PERCENT,
442
+ "cofins_wh_base_type": TAX_BASE_TYPE_PERCENT,
443
+ "pis_base_type": TAX_BASE_TYPE_PERCENT,
444
+ "pisst_base_type": TAX_BASE_TYPE_PERCENT,
445
+ "pis_wh_base_type": TAX_BASE_TYPE_PERCENT,
446
+ "cbs_base_type": TAX_BASE_TYPE_PERCENT,
447
+ "ibs_base_type": TAX_BASE_TYPE_PERCENT,
448
+ }
449
+ )
450
+ if line.fiscal_operation_line_id:
451
+ compute_result = line.fiscal_tax_ids.compute_taxes(
452
+ company=line.company_id,
453
+ partner=line._get_fiscal_partner(),
454
+ product=line.product_id,
455
+ price_unit=line.price_unit,
456
+ quantity=line.quantity,
457
+ uom_id=line.uom_id,
458
+ fiscal_price=line.fiscal_price,
459
+ fiscal_quantity=line.fiscal_quantity,
460
+ uot_id=line.uot_id,
461
+ discount_value=line.discount_value,
462
+ insurance_value=line.insurance_value,
463
+ ii_customhouse_charges=line.ii_customhouse_charges,
464
+ ii_iof_value=line.ii_iof_value,
465
+ other_value=line.other_value,
466
+ freight_value=line.freight_value,
467
+ ncm=line.ncm_id,
468
+ nbs=line.nbs_id,
469
+ nbm=line.nbm_id,
470
+ cest=line.cest_id,
471
+ operation_line=line.fiscal_operation_line_id,
472
+ cfop=line.cfop_id,
473
+ icmssn_range=line.icmssn_range_id,
474
+ icms_origin=line.icms_origin,
475
+ icms_cst_id=line.icms_cst_id,
476
+ ind_final=line.ind_final,
477
+ icms_relief_id=line.icms_relief_id,
478
+ )
479
+ to_update.update(line._prepare_tax_fields(compute_result))
480
+ else:
481
+ compute_result = {}
482
+ to_update.update(
483
+ {
484
+ "amount_tax_included": compute_result.get("amount_included", 0.0),
485
+ "amount_tax_not_included": compute_result.get(
486
+ "amount_not_included", 0.0
487
+ ),
488
+ "amount_tax_withholding": compute_result.get(
489
+ "amount_withholding", 0.0
490
+ ),
491
+ "estimate_tax": compute_result.get("estimate_tax", 0.0),
492
+ }
493
+ )
494
+ in_draft_mode = line != line._origin
495
+ if in_draft_mode:
496
+ line.update(to_update)
497
+ else:
498
+ line.write(to_update)
499
+
500
+ def _prepare_tax_fields(self, compute_result):
501
+ self.ensure_one()
502
+ tax_values = {}
503
+ if self._is_imported():
504
+ return tax_values
505
+ computed_taxes = compute_result.get("taxes", {})
506
+ for tax in self.fiscal_tax_ids:
507
+ computed_tax = computed_taxes.get(tax.tax_domain, {})
508
+ tax_field_name = f"{tax.tax_domain}_tax_id"
509
+ if hasattr(self, tax_field_name):
510
+ tax_values[tax_field_name] = tax.ids[0]
511
+ method = getattr(self, f"_prepare_fields_{tax.tax_domain}", None)
512
+ if method and computed_tax:
513
+ prepared_fields = method(computed_tax)
514
+ if prepared_fields:
515
+ tax_values.update(prepared_fields)
516
+ return tax_values
517
+
518
+ @api.depends(
519
+ "product_id",
520
+ "fiscal_operation_id",
521
+ )
522
+ def _compute_price_unit_fiscal(self): # OK when edited from aml?? c-> check
523
+ for line in self:
524
+ line.price_unit = {
525
+ "sale_price": line.product_id.list_price,
526
+ "cost_price": line.product_id.standard_price,
527
+ }.get(line.fiscal_operation_id.default_price_unit, 0)
528
+
529
+ def _get_document(self):
530
+ self.ensure_one()
531
+ return self.document_id
532
+
533
+ def _get_fiscal_partner(self):
534
+ """
535
+ Meant to be overriden when the l10n_br_fiscal.document partner_id should not
536
+ be the same as the sale.order, purchase.order, account.move (...) partner_id.
537
+
538
+ (In the case of invoicing, the invoicing partner set by the user should
539
+ get priority over any invoicing contact returned by address_get.)
540
+ """
541
+ self.ensure_one()
542
+ return self.partner_id
543
+
544
+ @api.depends("product_id")
545
+ def _compute_product_fiscal_fields(self):
546
+ for line in self:
547
+ if not line.product_id:
548
+ # reset to default values:
549
+ line.fiscal_type = False
550
+ line.ncm_id = False
551
+ line.nbm_id = False
552
+ line.tax_icms_or_issqn = TAX_DOMAIN_ICMS
553
+ line.icms_origin = ICMS_ORIGIN_DEFAULT
554
+ line.cest_id = False
555
+ line.nbs_id = False
556
+ line.fiscal_genre_id = False
557
+ line.service_type_id = False
558
+ line.operation_indicator_id = False
559
+ continue
560
+ p = line.product_id
561
+ line.fiscal_type = p.fiscal_type
562
+ line.ncm_id = p.ncm_id
563
+ line.nbm_id = p.nbm_id
564
+ line.tax_icms_or_issqn = p.tax_icms_or_issqn
565
+ line.icms_origin = p.icms_origin
566
+ line.cest_id = p.cest_id
567
+ line.nbs_id = p.nbs_id
568
+ line.fiscal_genre_id = p.fiscal_genre_id
569
+ line.service_type_id = p.service_type_id
570
+ line.operation_indicator_id = p.operation_indicator_id
571
+
572
+ @api.depends("product_id")
573
+ def _compute_city_taxation_code_id(self):
574
+ for line in self:
575
+ if not line.product_id:
576
+ line.city_taxation_code_id = False
577
+ continue
578
+ company_city = line.company_id.city_id
579
+ city_tax_codes = line.product_id.city_taxation_code_ids
580
+ city_tax_code = city_tax_codes.filtered(
581
+ lambda r, _city_id=company_city: r.city_id == _city_id
582
+ )
583
+ if city_tax_code:
584
+ line.city_taxation_code_id = city_tax_code
585
+ else:
586
+ line.city_taxation_code_id = False
587
+
588
+ def _prepare_fields_issqn(self, tax_dict):
589
+ self.ensure_one()
590
+ return {
591
+ "issqn_base": tax_dict.get("base"),
592
+ "issqn_percent": tax_dict.get("percent_amount"),
593
+ "issqn_reduction": tax_dict.get("percent_reduction"),
594
+ "issqn_value": tax_dict.get("tax_value"),
595
+ }
596
+
597
+ def _prepare_fields_issqn_wh(self, tax_dict):
598
+ self.ensure_one()
599
+ return {
600
+ "issqn_wh_base": tax_dict.get("base"),
601
+ "issqn_wh_percent": tax_dict.get("percent_amount"),
602
+ "issqn_wh_reduction": tax_dict.get("percent_reduction"),
603
+ "issqn_wh_value": tax_dict.get("tax_value"),
604
+ }
605
+
606
+ def _prepare_fields_csll(self, tax_dict):
607
+ self.ensure_one()
608
+ return {
609
+ "csll_base": tax_dict.get("base"),
610
+ "csll_percent": tax_dict.get("percent_amount"),
611
+ "csll_reduction": tax_dict.get("percent_reduction"),
612
+ "csll_value": tax_dict.get("tax_value"),
613
+ }
614
+
615
+ def _prepare_fields_csll_wh(self, tax_dict):
616
+ self.ensure_one()
617
+ return {
618
+ "csll_wh_base": tax_dict.get("base"),
619
+ "csll_wh_percent": tax_dict.get("percent_amount"),
620
+ "csll_wh_reduction": tax_dict.get("percent_reduction"),
621
+ "csll_wh_value": tax_dict.get("tax_value"),
622
+ }
623
+
624
+ def _prepare_fields_irpj(self, tax_dict):
625
+ self.ensure_one()
626
+ return {
627
+ "irpj_base": tax_dict.get("base"),
628
+ "irpj_percent": tax_dict.get("percent_amount"),
629
+ "irpj_reduction": tax_dict.get("percent_reduction"),
630
+ "irpj_value": tax_dict.get("tax_value"),
631
+ }
632
+
633
+ def _prepare_fields_irpj_wh(self, tax_dict):
634
+ self.ensure_one()
635
+ return {
636
+ "irpj_wh_base": tax_dict.get("base"),
637
+ "irpj_wh_percent": tax_dict.get("percent_amount"),
638
+ "irpj_wh_reduction": tax_dict.get("percent_reduction"),
639
+ "irpj_wh_value": tax_dict.get("tax_value"),
640
+ }
641
+
642
+ def _prepare_fields_inss(self, tax_dict):
643
+ self.ensure_one()
644
+ return {
645
+ "inss_base": tax_dict.get("base"),
646
+ "inss_percent": tax_dict.get("percent_amount"),
647
+ "inss_reduction": tax_dict.get("percent_reduction"),
648
+ "inss_value": tax_dict.get("tax_value"),
649
+ }
650
+
651
+ def _prepare_fields_inss_wh(self, tax_dict):
652
+ self.ensure_one()
653
+ return {
654
+ "inss_wh_base": tax_dict.get("base"),
655
+ "inss_wh_percent": tax_dict.get("percent_amount"),
656
+ "inss_wh_reduction": tax_dict.get("percent_reduction"),
657
+ "inss_wh_value": tax_dict.get("tax_value"),
658
+ }
659
+
660
+ def _prepare_fields_icms(self, tax_dict):
661
+ self.ensure_one()
662
+ cst_id = tax_dict.get("cst_id").id if tax_dict.get("cst_id") else False
663
+ return {
664
+ "icms_cst_id": cst_id,
665
+ "icms_base_type": tax_dict.get("icms_base_type", ICMS_BASE_TYPE_DEFAULT),
666
+ "icms_base": tax_dict.get("base", 0.0),
667
+ "icms_percent": tax_dict.get("percent_amount", 0.0),
668
+ "icms_reduction": tax_dict.get("percent_reduction", 0.0),
669
+ "icms_value": tax_dict.get("tax_value", 0.0),
670
+ "icms_origin_percent": tax_dict.get("icms_origin_perc", 0.0),
671
+ "icms_destination_percent": tax_dict.get("icms_dest_perc", 0.0),
672
+ "icms_sharing_percent": tax_dict.get("icms_sharing_percent", 0.0),
673
+ "icms_destination_base": tax_dict.get("icms_dest_base", 0.0),
674
+ "icms_origin_value": tax_dict.get("icms_origin_value", 0.0),
675
+ "icms_destination_value": tax_dict.get("icms_dest_value", 0.0),
676
+ "icms_relief_value": tax_dict.get("icms_relief", 0.0),
677
+ }
678
+
679
+ @api.onchange(
680
+ "icms_base",
681
+ "icms_percent",
682
+ "icms_reduction",
683
+ "icms_value",
684
+ "icms_destination_base",
685
+ "icms_origin_percent",
686
+ "icms_destination_percent",
687
+ "icms_sharing_percent",
688
+ "icms_origin_value",
689
+ "icms_tax_benefit_id",
690
+ )
691
+ def _onchange_icms_fields(self):
692
+ if self.icms_tax_benefit_id:
693
+ self.icms_tax_id = self.icms_tax_benefit_id.tax_id
694
+
695
+ @api.onchange("tax_classification_id")
696
+ def _onchange_tax_classification_id(self):
697
+ if self.tax_classification_id:
698
+ self.ibs_tax_id = self.tax_classification_id.tax_ibs_id
699
+ self.cbs_tax_id = self.tax_classification_id.tax_cbs_id
700
+
701
+ def _prepare_fields_icmssn(self, tax_dict):
702
+ self.ensure_one()
703
+ cst_id = tax_dict.get("cst_id").id if tax_dict.get("cst_id") else False
704
+ icmssn_base = tax_dict.get("base", 0.0)
705
+ icmssn_credit_value = tax_dict.get("tax_value", 0.0)
706
+ simple_value = icmssn_base * self.icmssn_range_id.total_tax_percent
707
+ simple_without_icms_value = simple_value - icmssn_credit_value
708
+ return {
709
+ "icms_cst_id": cst_id,
710
+ "icmssn_base": icmssn_base,
711
+ "icmssn_percent": tax_dict.get("percent_amount"),
712
+ "icmssn_reduction": tax_dict.get("percent_reduction"),
713
+ "icmssn_credit_value": icmssn_credit_value,
714
+ "simple_value": simple_value,
715
+ "simple_without_icms_value": simple_without_icms_value,
716
+ }
717
+
718
+ def _prepare_fields_icmsst(self, tax_dict):
719
+ self.ensure_one()
720
+ return {
721
+ "icmsst_base_type": tax_dict.get(
722
+ "icmsst_base_type", ICMS_ST_BASE_TYPE_DEFAULT
723
+ ),
724
+ "icmsst_mva_percent": tax_dict.get("icmsst_mva_percent"),
725
+ "icmsst_percent": tax_dict.get("percent_amount"),
726
+ "icmsst_reduction": tax_dict.get("percent_reduction"),
727
+ "icmsst_base": tax_dict.get("base"),
728
+ "icmsst_value": tax_dict.get("tax_value"),
729
+ }
730
+
731
+ def _prepare_fields_icmsfcp(self, tax_dict):
732
+ self.ensure_one()
733
+ return {
734
+ "icmsfcp_base": tax_dict.get("base", 0.0),
735
+ "icmsfcp_percent": tax_dict.get("percent_amount", 0.0),
736
+ "icmsfcp_value": tax_dict.get("tax_value", 0.0),
737
+ }
738
+
739
+ def _prepare_fields_icmsfcpst(self, tax_dict):
740
+ self.ensure_one()
741
+ return {
742
+ "icmsfcpst_base": self.icmsst_base,
743
+ "icmsfcpst_percent": tax_dict.get("percent_amount", 0.0),
744
+ "icmsfcpst_value": tax_dict.get("tax_value", 0.0),
745
+ }
746
+
747
+ def _prepare_fields_ipi(self, tax_dict):
748
+ self.ensure_one()
749
+ cst_id = tax_dict.get("cst_id").id if tax_dict.get("cst_id") else False
750
+ return {
751
+ "ipi_cst_id": cst_id,
752
+ "ipi_base_type": tax_dict.get("base_type", False),
753
+ "ipi_base": tax_dict.get("base", 0.00),
754
+ "ipi_percent": tax_dict.get("percent_amount", 0.00),
755
+ "ipi_reduction": tax_dict.get("percent_reduction", 0.00),
756
+ "ipi_value": tax_dict.get("tax_value", 0.00),
757
+ }
758
+
759
+ def _prepare_fields_ii(self, tax_dict):
760
+ self.ensure_one()
761
+ return {
762
+ "ii_base": tax_dict.get("base", 0.00),
763
+ "ii_percent": tax_dict.get("percent_amount", 0.00),
764
+ "ii_value": tax_dict.get("tax_value", 0.00),
765
+ }
766
+
767
+ def _prepare_fields_cbs(self, tax_dict):
768
+ self.ensure_one()
769
+ cst_id = tax_dict.get("cst_id").id if tax_dict.get("cst_id") else False
770
+ return {
771
+ "cbs_cst_id": cst_id,
772
+ "cbs_base_type": tax_dict.get("base_type", False),
773
+ "cbs_base": tax_dict.get("base", 0.00),
774
+ "cbs_percent": tax_dict.get("percent_amount", 0.00),
775
+ "cbs_reduction": tax_dict.get("percent_reduction", 0.00),
776
+ "cbs_value": tax_dict.get("tax_value", 0.00),
777
+ }
778
+
779
+ def _prepare_fields_ibs(self, tax_dict):
780
+ self.ensure_one()
781
+ cst_id = tax_dict.get("cst_id").id if tax_dict.get("cst_id") else False
782
+ return {
783
+ "ibs_cst_id": cst_id,
784
+ "ibs_base_type": tax_dict.get("base_type", False),
785
+ "ibs_base": tax_dict.get("base", 0.00),
786
+ "ibs_percent": tax_dict.get("percent_amount", 0.00),
787
+ "ibs_reduction": tax_dict.get("percent_reduction", 0.00),
788
+ "ibs_value": tax_dict.get("tax_value", 0.00),
789
+ }
790
+
791
+ def _prepare_fields_pis(self, tax_dict):
792
+ self.ensure_one()
793
+ cst_id = tax_dict.get("cst_id").id if tax_dict.get("cst_id") else False
794
+ return {
795
+ "pis_cst_id": cst_id,
796
+ "pis_base_type": tax_dict.get("base_type"),
797
+ "pis_base": tax_dict.get("base", 0.00),
798
+ "pis_percent": tax_dict.get("percent_amount", 0.00),
799
+ "pis_reduction": tax_dict.get("percent_reduction", 0.00),
800
+ "pis_value": tax_dict.get("tax_value", 0.00),
801
+ }
802
+
803
+ def _prepare_fields_pis_wh(self, tax_dict):
804
+ self.ensure_one()
805
+ return {
806
+ "pis_wh_base_type": tax_dict.get("base_type"),
807
+ "pis_wh_base": tax_dict.get("base", 0.00),
808
+ "pis_wh_percent": tax_dict.get("percent_amount", 0.00),
809
+ "pis_wh_reduction": tax_dict.get("percent_reduction", 0.00),
810
+ "pis_wh_value": tax_dict.get("tax_value", 0.00),
811
+ }
812
+
813
+ def _prepare_fields_pisst(self, tax_dict):
814
+ self.ensure_one()
815
+ cst_id = tax_dict.get("cst_id").id if tax_dict.get("cst_id") else False
816
+ return {
817
+ "pisst_cst_id": cst_id,
818
+ "pisst_base_type": tax_dict.get("base_type"),
819
+ "pisst_base": tax_dict.get("base", 0.00),
820
+ "pisst_percent": tax_dict.get("percent_amount", 0.00),
821
+ "pisst_reduction": tax_dict.get("percent_reduction", 0.00),
822
+ "pisst_value": tax_dict.get("tax_value", 0.00),
823
+ }
824
+
825
+ def _prepare_fields_cofins(self, tax_dict):
826
+ self.ensure_one()
827
+ cst_id = tax_dict.get("cst_id").id if tax_dict.get("cst_id") else False
828
+ return {
829
+ "cofins_cst_id": cst_id,
830
+ "cofins_base_type": tax_dict.get("base_type"),
831
+ "cofins_base": tax_dict.get("base", 0.00),
832
+ "cofins_percent": tax_dict.get("percent_amount", 0.00),
833
+ "cofins_reduction": tax_dict.get("percent_reduction", 0.00),
834
+ "cofins_value": tax_dict.get("tax_value", 0.00),
835
+ }
836
+
837
+ def _prepare_fields_cofins_wh(self, tax_dict):
838
+ self.ensure_one()
839
+ return {
840
+ "cofins_wh_base_type": tax_dict.get("base_type"),
841
+ "cofins_wh_base": tax_dict.get("base", 0.00),
842
+ "cofins_wh_percent": tax_dict.get("percent_amount", 0.00),
843
+ "cofins_wh_reduction": tax_dict.get("percent_reduction", 0.00),
844
+ "cofins_wh_value": tax_dict.get("tax_value", 0.00),
845
+ }
846
+
847
+ def _prepare_fields_cofinsst(self, tax_dict):
848
+ self.ensure_one()
849
+ cst_id = tax_dict.get("cst_id").id if tax_dict.get("cst_id") else False
850
+ return {
851
+ "cofinsst_cst_id": cst_id,
852
+ "cofinsst_base_type": tax_dict.get("base_type"),
853
+ "cofinsst_base": tax_dict.get("base", 0.00),
854
+ "cofinsst_percent": tax_dict.get("percent_amount", 0.00),
855
+ "cofinsst_reduction": tax_dict.get("percent_reduction", 0.00),
856
+ "cofinsst_value": tax_dict.get("tax_value", 0.00),
857
+ }
858
+
859
+ @api.depends("product_id", "uom_id")
860
+ def _compute_uot_id(self):
861
+ for line in self:
862
+ p = line.product_id
863
+ line.uot_id = (p.uot_id if p else False) or line.uom_id
864
+
865
+ @api.depends("price_unit")
866
+ def _compute_fiscal_price(self):
867
+ for line in self:
868
+ if line.product_id and line.price_unit:
869
+ line.fiscal_price = line.price_unit / (
870
+ line.product_id.uot_factor or 1.0
871
+ )
872
+ else:
873
+ line.fiscal_price = line.price_unit
874
+
875
+ @api.depends("quantity")
876
+ def _compute_fiscal_quantity(self):
877
+ for line in self:
878
+ if line.product_id and line.quantity:
879
+ line.fiscal_quantity = line.quantity * (
880
+ line.product_id.uot_factor or 1.0
881
+ )
882
+ else:
883
+ line.fiscal_quantity = line.quantity
884
+
885
+ @api.model
886
+ def _add_fields_to_amount(self):
887
+ fields_to_amount = ["insurance_value", "other_value", "freight_value"]
888
+ if (
889
+ self.cfop_id.destination == CFOP_DESTINATION_EXPORT
890
+ and self.fiscal_operation_id.fiscal_operation_type == FISCAL_IN
891
+ ):
892
+ fields_to_amount.append("pis_value")
893
+ fields_to_amount.append("cofins_value")
894
+ fields_to_amount.append("icms_value")
895
+ fields_to_amount.append("ii_value")
896
+ fields_to_amount.append("ii_customhouse_charges")
897
+ return fields_to_amount
898
+
899
+ @api.model
900
+ def _rm_fields_to_amount(self):
901
+ return ["icms_relief_value"]
902
+
903
+ def _is_imported(self):
904
+ # When the mixin is used for instance
905
+ # in a PO line or SO line, there is no document_id
906
+ # and we consider the document is not imported
907
+ return hasattr(self, "document_id") and self.document_id.imported_document
908
+
101
909
  currency_id = fields.Many2one(
102
910
  comodel_name="res.currency",
103
911
  string="Currency",
@@ -113,7 +921,10 @@ class FiscalDocumentLineMixin(models.AbstractModel):
113
921
  tax_icms_or_issqn = fields.Selection(
114
922
  selection=TAX_ICMS_OR_ISSQN,
115
923
  string="ICMS or ISSQN Tax",
116
- default=TAX_DOMAIN_ICMS,
924
+ compute="_compute_product_fiscal_fields",
925
+ store=True,
926
+ readonly=False,
927
+ precompute=True,
117
928
  )
118
929
 
119
930
  partner_is_public_entity = fields.Boolean(related="partner_id.is_public_entity")
@@ -125,26 +936,58 @@ class FiscalDocumentLineMixin(models.AbstractModel):
125
936
 
126
937
  price_unit = fields.Float(
127
938
  digits="Product Price",
128
- compute="_compute_price_unit_fiscal",
939
+ store=True,
940
+ )
941
+
942
+ partner_id = fields.Many2one(comodel_name="res.partner", string="Partner")
943
+
944
+ company_id = fields.Many2one(
945
+ comodel_name="res.company",
946
+ string="Company",
947
+ )
948
+
949
+ ind_final = fields.Selection(
950
+ selection=FINAL_CUSTOMER,
951
+ string="Consumidor final",
952
+ compute="_compute_ind_final",
129
953
  store=True,
130
954
  precompute=True,
131
955
  readonly=False,
132
956
  )
133
957
 
134
- partner_id = fields.Many2one(comodel_name="res.partner", string="Partner")
958
+ def _compute_ind_final(self):
959
+ for line in self:
960
+ doc = line._get_document()
961
+ if line.ind_final != doc.ind_final:
962
+ line.ind_final = doc.ind_final
135
963
 
136
964
  partner_company_type = fields.Selection(related="partner_id.company_type")
137
965
 
138
- uom_id = fields.Many2one(comodel_name="uom.uom", string="UOM")
966
+ uom_id = fields.Many2one(
967
+ comodel_name="uom.uom",
968
+ string="UOM",
969
+ )
139
970
 
140
971
  quantity = fields.Float(
141
972
  digits="Product Unit of Measure",
142
973
  )
143
974
 
144
- fiscal_type = fields.Selection(selection=PRODUCT_FISCAL_TYPE)
975
+ fiscal_type = fields.Selection(
976
+ selection=PRODUCT_FISCAL_TYPE,
977
+ compute="_compute_product_fiscal_fields",
978
+ store=True,
979
+ readonly=False,
980
+ precompute=True,
981
+ )
145
982
 
146
983
  ncm_id = fields.Many2one(
147
- comodel_name="l10n_br_fiscal.ncm", index=True, string="NCM"
984
+ comodel_name="l10n_br_fiscal.ncm",
985
+ index=True,
986
+ string="NCM",
987
+ compute="_compute_product_fiscal_fields",
988
+ store=True,
989
+ readonly=False,
990
+ precompute=True,
148
991
  )
149
992
 
150
993
  nbm_id = fields.Many2one(
@@ -152,6 +995,10 @@ class FiscalDocumentLineMixin(models.AbstractModel):
152
995
  index=True,
153
996
  string="NBM",
154
997
  domain="[('ncm_ids', '=', ncm_id)]",
998
+ compute="_compute_product_fiscal_fields",
999
+ store=True,
1000
+ readonly=False,
1001
+ precompute=True,
155
1002
  )
156
1003
 
157
1004
  cest_id = fields.Many2one(
@@ -159,10 +1006,20 @@ class FiscalDocumentLineMixin(models.AbstractModel):
159
1006
  index=True,
160
1007
  string="CEST",
161
1008
  domain="[('ncm_ids', '=', ncm_id)]",
1009
+ compute="_compute_product_fiscal_fields",
1010
+ store=True,
1011
+ readonly=False,
1012
+ precompute=True,
162
1013
  )
163
1014
 
164
1015
  nbs_id = fields.Many2one(
165
- comodel_name="l10n_br_fiscal.nbs", index=True, string="NBS"
1016
+ comodel_name="l10n_br_fiscal.nbs",
1017
+ index=True,
1018
+ string="NBS",
1019
+ compute="_compute_product_fiscal_fields",
1020
+ store=True,
1021
+ readonly=False,
1022
+ precompute=True,
166
1023
  )
167
1024
 
168
1025
  fiscal_operation_id = fields.Many2one(
@@ -184,8 +1041,12 @@ class FiscalDocumentLineMixin(models.AbstractModel):
184
1041
  fiscal_operation_line_id = fields.Many2one(
185
1042
  comodel_name="l10n_br_fiscal.operation.line",
186
1043
  string="Operation Line",
1044
+ compute="_compute_fiscal_operation_line_id",
187
1045
  domain="[('fiscal_operation_id', '=', fiscal_operation_id), "
188
1046
  "('state', '=', 'approved')]",
1047
+ store=True,
1048
+ precompute=True,
1049
+ readonly=False,
189
1050
  )
190
1051
 
191
1052
  cfop_id = fields.Many2one(
@@ -258,11 +1119,11 @@ class FiscalDocumentLineMixin(models.AbstractModel):
258
1119
  compute="_compute_fiscal_amounts",
259
1120
  )
260
1121
 
261
- amount_untaxed = fields.Monetary(
1122
+ fiscal_amount_untaxed = fields.Monetary(
262
1123
  compute="_compute_fiscal_amounts",
263
1124
  )
264
1125
 
265
- amount_tax = fields.Monetary(
1126
+ fiscal_amount_tax = fields.Monetary(
266
1127
  compute="_compute_fiscal_amounts",
267
1128
  )
268
1129
 
@@ -270,7 +1131,7 @@ class FiscalDocumentLineMixin(models.AbstractModel):
270
1131
  compute="_compute_fiscal_amounts",
271
1132
  )
272
1133
 
273
- amount_total = fields.Monetary(
1134
+ fiscal_amount_total = fields.Monetary(
274
1135
  compute="_compute_fiscal_amounts",
275
1136
  )
276
1137
 
@@ -289,14 +1150,35 @@ class FiscalDocumentLineMixin(models.AbstractModel):
289
1150
  compute="_compute_fiscal_amounts",
290
1151
  )
291
1152
 
292
- amount_tax_included = fields.Monetary()
1153
+ amount_tax_included = fields.Monetary(
1154
+ compute="_compute_tax_fields",
1155
+ store=True,
1156
+ precompute=True,
1157
+ readonly=False,
1158
+ )
293
1159
 
294
- amount_tax_not_included = fields.Monetary()
1160
+ amount_tax_not_included = fields.Monetary(
1161
+ compute="_compute_tax_fields",
1162
+ store=True,
1163
+ precompute=True,
1164
+ readonly=False,
1165
+ )
295
1166
 
296
- amount_tax_withholding = fields.Monetary(string="Tax Withholding")
1167
+ amount_tax_withholding = fields.Monetary(
1168
+ string="Tax Withholding",
1169
+ compute="_compute_tax_fields",
1170
+ store=True,
1171
+ precompute=True,
1172
+ readonly=False,
1173
+ )
297
1174
 
298
1175
  fiscal_genre_id = fields.Many2one(
299
- comodel_name="l10n_br_fiscal.product.genre", string="Fiscal Product Genre"
1176
+ comodel_name="l10n_br_fiscal.product.genre",
1177
+ string="Fiscal Product Genre",
1178
+ compute="_compute_product_fiscal_fields",
1179
+ store=True,
1180
+ readonly=False,
1181
+ precompute=True,
300
1182
  )
301
1183
 
302
1184
  fiscal_genre_code = fields.Char(
@@ -307,10 +1189,27 @@ class FiscalDocumentLineMixin(models.AbstractModel):
307
1189
  comodel_name="l10n_br_fiscal.service.type",
308
1190
  string="Service Type LC 166",
309
1191
  domain="[('internal_type', '=', 'normal')]",
1192
+ compute="_compute_product_fiscal_fields",
1193
+ store=True,
1194
+ readonly=False,
1195
+ precompute=True,
310
1196
  )
311
1197
 
312
1198
  city_taxation_code_id = fields.Many2one(
313
- comodel_name="l10n_br_fiscal.city.taxation.code", string="City Taxation Code"
1199
+ comodel_name="l10n_br_fiscal.city.taxation.code",
1200
+ compute="_compute_city_taxation_code_id",
1201
+ store=True,
1202
+ readonly=False,
1203
+ precompute=True,
1204
+ )
1205
+
1206
+ operation_indicator_id = fields.Many2one(
1207
+ comodel_name="l10n_br_fiscal.operation.indicator",
1208
+ string="Operation Indicator",
1209
+ compute="_compute_product_fiscal_fields",
1210
+ store=True,
1211
+ readonly=False,
1212
+ precompute=True,
314
1213
  )
315
1214
 
316
1215
  partner_order = fields.Char(string="Partner Order (xPed)", size=15)
@@ -330,7 +1229,10 @@ class FiscalDocumentLineMixin(models.AbstractModel):
330
1229
 
331
1230
  issqn_fg_city_id = fields.Many2one(
332
1231
  comodel_name="res.city",
1232
+ related="city_taxation_code_id.city_id",
333
1233
  string="ISSQN City",
1234
+ store=True,
1235
+ precompute=True,
334
1236
  )
335
1237
 
336
1238
  # vDeducao
@@ -479,7 +1381,6 @@ class FiscalDocumentLineMixin(models.AbstractModel):
479
1381
  icms_base_type = fields.Selection(
480
1382
  selection=ICMS_BASE_TYPE,
481
1383
  string="ICMS Base Type",
482
- default=ICMS_BASE_TYPE_DEFAULT,
483
1384
  compute="_compute_tax_fields",
484
1385
  store=True,
485
1386
  precompute=True,
@@ -487,7 +1388,12 @@ class FiscalDocumentLineMixin(models.AbstractModel):
487
1388
  )
488
1389
 
489
1390
  icms_origin = fields.Selection(
490
- selection=ICMS_ORIGIN, string="ICMS Origin", default=ICMS_ORIGIN_DEFAULT
1391
+ selection=ICMS_ORIGIN,
1392
+ string="ICMS Origin",
1393
+ compute="_compute_product_fiscal_fields",
1394
+ store=True,
1395
+ readonly=False,
1396
+ precompute=True,
491
1397
  )
492
1398
 
493
1399
  # vBC - Valor da base de cálculo do ICMS
@@ -561,7 +1467,6 @@ class FiscalDocumentLineMixin(models.AbstractModel):
561
1467
  icmsst_base_type = fields.Selection(
562
1468
  selection=ICMS_ST_BASE_TYPE,
563
1469
  string="ICMS ST Base Type",
564
- default=ICMS_ST_BASE_TYPE_DEFAULT,
565
1470
  compute="_compute_tax_fields",
566
1471
  store=True,
567
1472
  precompute=True,
@@ -857,7 +1762,6 @@ class FiscalDocumentLineMixin(models.AbstractModel):
857
1762
  ipi_base_type = fields.Selection(
858
1763
  selection=TAX_BASE_TYPE,
859
1764
  string="IPI Base Type",
860
- default=TAX_BASE_TYPE_PERCENT,
861
1765
  compute="_compute_tax_fields",
862
1766
  store=True,
863
1767
  precompute=True,
@@ -911,6 +1815,171 @@ class FiscalDocumentLineMixin(models.AbstractModel):
911
1815
 
912
1816
  ipi_devol_value = fields.Monetary(string="Valor do IPI devolvido")
913
1817
 
1818
+ # CBS Fields
1819
+ cbs_tax_id = fields.Many2one(
1820
+ comodel_name="l10n_br_fiscal.tax",
1821
+ string="Tax CBS",
1822
+ domain=(
1823
+ f"[('tax_domain', '=', '{TAX_DOMAIN_CBS}'), '|', "
1824
+ "('cst_in_id.code', 'like', cst_code_prefix_like), "
1825
+ "('cst_out_id.code', 'like', cst_code_prefix_like)]"
1826
+ ),
1827
+ compute="_compute_tax_fields",
1828
+ store=True,
1829
+ precompute=True,
1830
+ readonly=False,
1831
+ )
1832
+
1833
+ cbs_cst_id = fields.Many2one(
1834
+ comodel_name="l10n_br_fiscal.cst",
1835
+ string="CST CBS",
1836
+ domain="[('cst_type', '=', fiscal_operation_type),('tax_domain', '=', 'cbs')]",
1837
+ compute="_compute_tax_fields",
1838
+ store=True,
1839
+ precompute=True,
1840
+ readonly=False,
1841
+ )
1842
+
1843
+ cbs_cst_code = fields.Char(
1844
+ related="cbs_cst_id.code", string="CBS CST Code", store=True
1845
+ )
1846
+
1847
+ cbs_base_type = fields.Selection(
1848
+ selection=TAX_BASE_TYPE,
1849
+ string="CBS Base Type",
1850
+ compute="_compute_tax_fields",
1851
+ store=True,
1852
+ precompute=True,
1853
+ readonly=False,
1854
+ )
1855
+
1856
+ cbs_base = fields.Monetary(
1857
+ string="CBS Base",
1858
+ compute="_compute_tax_fields",
1859
+ store=True,
1860
+ precompute=True,
1861
+ readonly=False,
1862
+ )
1863
+
1864
+ cbs_percent = fields.Float(
1865
+ string="CBS %",
1866
+ compute="_compute_tax_fields",
1867
+ store=True,
1868
+ precompute=True,
1869
+ readonly=False,
1870
+ )
1871
+
1872
+ cbs_reduction = fields.Float(
1873
+ string="CBS % Reduction",
1874
+ compute="_compute_tax_fields",
1875
+ store=True,
1876
+ precompute=True,
1877
+ readonly=False,
1878
+ )
1879
+
1880
+ cbs_value = fields.Monetary(
1881
+ string="CBS Value",
1882
+ compute="_compute_tax_fields",
1883
+ store=True,
1884
+ precompute=True,
1885
+ readonly=False,
1886
+ )
1887
+
1888
+ # IBS Fields
1889
+ ibs_tax_id = fields.Many2one(
1890
+ comodel_name="l10n_br_fiscal.tax",
1891
+ string="Tax IBS",
1892
+ domain=(
1893
+ f"[('tax_domain', '=', '{TAX_DOMAIN_IBS}'), '|', "
1894
+ "('cst_in_id.code', 'like', cst_code_prefix_like), "
1895
+ "('cst_out_id.code', 'like', cst_code_prefix_like)]"
1896
+ ),
1897
+ compute="_compute_tax_fields",
1898
+ store=True,
1899
+ precompute=True,
1900
+ readonly=False,
1901
+ )
1902
+
1903
+ ibs_cst_id = fields.Many2one(
1904
+ comodel_name="l10n_br_fiscal.cst",
1905
+ string="CST IBS",
1906
+ domain="[('cst_type', '=', fiscal_operation_type),('tax_domain', '=', 'ibs')]",
1907
+ compute="_compute_tax_fields",
1908
+ store=True,
1909
+ precompute=True,
1910
+ readonly=False,
1911
+ )
1912
+
1913
+ ibs_cst_code = fields.Char(
1914
+ related="ibs_cst_id.code", string="IBS CST Code", store=True
1915
+ )
1916
+
1917
+ ibs_base_type = fields.Selection(
1918
+ selection=TAX_BASE_TYPE,
1919
+ string="IBS Base Type",
1920
+ compute="_compute_tax_fields",
1921
+ store=True,
1922
+ precompute=True,
1923
+ readonly=False,
1924
+ )
1925
+
1926
+ ibs_base = fields.Monetary(
1927
+ string="IBS Base",
1928
+ compute="_compute_tax_fields",
1929
+ store=True,
1930
+ precompute=True,
1931
+ readonly=False,
1932
+ )
1933
+
1934
+ ibs_percent = fields.Float(
1935
+ string="IBS %",
1936
+ compute="_compute_tax_fields",
1937
+ store=True,
1938
+ precompute=True,
1939
+ readonly=False,
1940
+ )
1941
+
1942
+ ibs_reduction = fields.Float(
1943
+ string="IBS % Reduction",
1944
+ compute="_compute_tax_fields",
1945
+ store=True,
1946
+ precompute=True,
1947
+ readonly=False,
1948
+ )
1949
+
1950
+ ibs_value = fields.Monetary(
1951
+ string="IBS Value",
1952
+ compute="_compute_tax_fields",
1953
+ store=True,
1954
+ precompute=True,
1955
+ readonly=False,
1956
+ )
1957
+
1958
+ # CBS/IBS Tax Classification
1959
+ tax_classification_id = fields.Many2one(
1960
+ comodel_name="l10n_br_fiscal.tax.classification",
1961
+ string="Tax Classification",
1962
+ compute="_compute_fiscal_tax_ids",
1963
+ store=True,
1964
+ precompute=True,
1965
+ readonly=False,
1966
+ )
1967
+
1968
+ cst_code_prefix_like = fields.Char(
1969
+ compute="_compute_cst_code_prefix_like",
1970
+ help="Helper field to filter taxes by CST code prefix (3 chars) using LIKE.",
1971
+ )
1972
+
1973
+ @api.depends("tax_classification_id")
1974
+ def _compute_cst_code_prefix_like(self):
1975
+ for rec in self:
1976
+ code = rec.tax_classification_id.code if rec.tax_classification_id else ""
1977
+ prefix = (code or "")[:3]
1978
+ # Avoid matching all records when the prefix is not available yet.
1979
+ rec.cst_code_prefix_like = (
1980
+ f"{prefix}%" if len(prefix) == 3 else "__no_match__%"
1981
+ )
1982
+
914
1983
  # II Fields
915
1984
  ii_tax_id = fields.Many2one(
916
1985
  comodel_name="l10n_br_fiscal.tax",
@@ -981,7 +2050,6 @@ class FiscalDocumentLineMixin(models.AbstractModel):
981
2050
  cofins_base_type = fields.Selection(
982
2051
  selection=TAX_BASE_TYPE,
983
2052
  string="COFINS Base Type",
984
- default=TAX_BASE_TYPE_PERCENT,
985
2053
  compute="_compute_tax_fields",
986
2054
  store=True,
987
2055
  precompute=True,
@@ -1058,7 +2126,6 @@ class FiscalDocumentLineMixin(models.AbstractModel):
1058
2126
  cofinsst_base_type = fields.Selection(
1059
2127
  selection=TAX_BASE_TYPE,
1060
2128
  string="COFINS ST Base Type",
1061
- default=TAX_BASE_TYPE_PERCENT,
1062
2129
  compute="_compute_tax_fields",
1063
2130
  store=True,
1064
2131
  precompute=True,
@@ -1110,7 +2177,6 @@ class FiscalDocumentLineMixin(models.AbstractModel):
1110
2177
  cofins_wh_base_type = fields.Selection(
1111
2178
  selection=TAX_BASE_TYPE,
1112
2179
  string="COFINS WH Base Type",
1113
- default=TAX_BASE_TYPE_PERCENT,
1114
2180
  compute="_compute_tax_fields",
1115
2181
  store=True,
1116
2182
  precompute=True,
@@ -1179,7 +2245,6 @@ class FiscalDocumentLineMixin(models.AbstractModel):
1179
2245
  pis_base_type = fields.Selection(
1180
2246
  selection=TAX_BASE_TYPE,
1181
2247
  string="PIS Base Type",
1182
- default=TAX_BASE_TYPE_PERCENT,
1183
2248
  compute="_compute_tax_fields",
1184
2249
  store=True,
1185
2250
  precompute=True,
@@ -1256,7 +2321,6 @@ class FiscalDocumentLineMixin(models.AbstractModel):
1256
2321
  pisst_base_type = fields.Selection(
1257
2322
  selection=TAX_BASE_TYPE,
1258
2323
  string="PIS ST Base Type",
1259
- default=TAX_BASE_TYPE_PERCENT,
1260
2324
  compute="_compute_tax_fields",
1261
2325
  store=True,
1262
2326
  precompute=True,
@@ -1308,7 +2372,6 @@ class FiscalDocumentLineMixin(models.AbstractModel):
1308
2372
  pis_wh_base_type = fields.Selection(
1309
2373
  selection=TAX_BASE_TYPE,
1310
2374
  string="PIS WH Base Type",
1311
- default=TAX_BASE_TYPE_PERCENT,
1312
2375
  compute="_compute_tax_fields",
1313
2376
  store=True,
1314
2377
  precompute=True,
@@ -1620,7 +2683,7 @@ class FiscalDocumentLineMixin(models.AbstractModel):
1620
2683
  comodel_name="l10n_br_fiscal.comment",
1621
2684
  string="Comments",
1622
2685
  domain=[("object", "=", FISCAL_COMMENT_LINE)],
1623
- compute="_compute_fiscal_tax_ids",
2686
+ compute="_compute_comment_ids",
1624
2687
  store=True,
1625
2688
  precompute=True,
1626
2689
  readonly=False,
@@ -1630,11 +2693,20 @@ class FiscalDocumentLineMixin(models.AbstractModel):
1630
2693
  help="Additional data manually entered by user"
1631
2694
  )
1632
2695
 
1633
- estimate_tax = fields.Monetary()
2696
+ estimate_tax = fields.Monetary(
2697
+ compute="_compute_tax_fields",
2698
+ store=True,
2699
+ precompute=True,
2700
+ readonly=False,
2701
+ )
1634
2702
 
1635
2703
  cnae_id = fields.Many2one(
2704
+ related="city_taxation_code_id.cnae_id",
1636
2705
  comodel_name="l10n_br_fiscal.cnae",
1637
2706
  string="CNAE Code",
2707
+ store=True,
2708
+ precompute=True,
2709
+ readonly=False,
1638
2710
  )
1639
2711
 
1640
2712
  @api.depends("company_id")