odoo-addon-l10n-br-fiscal 18.0.2.0.0.11__py3-none-any.whl → 18.0.3.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 (26) hide show
  1. odoo/addons/l10n_br_fiscal/README.rst +1 -1
  2. odoo/addons/l10n_br_fiscal/__manifest__.py +1 -1
  3. odoo/addons/l10n_br_fiscal/demo/fiscal_document_demo.xml +2 -90
  4. odoo/addons/l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml +0 -4
  5. odoo/addons/l10n_br_fiscal/demo/fiscal_operation_demo.xml +2 -2
  6. odoo/addons/l10n_br_fiscal/i18n/l10n_br_fiscal.pot +24 -24
  7. odoo/addons/l10n_br_fiscal/migrations/18.0.3.0.0/pre-migration.py +30 -0
  8. odoo/addons/l10n_br_fiscal/models/document.py +1 -6
  9. odoo/addons/l10n_br_fiscal/models/document_line.py +35 -5
  10. odoo/addons/l10n_br_fiscal/models/document_line_mixin.py +63 -12
  11. odoo/addons/l10n_br_fiscal/models/document_line_mixin_methods.py +74 -52
  12. odoo/addons/l10n_br_fiscal/models/document_mixin.py +3 -3
  13. odoo/addons/l10n_br_fiscal/models/document_mixin_methods.py +28 -142
  14. odoo/addons/l10n_br_fiscal/models/document_related.py +1 -1
  15. odoo/addons/l10n_br_fiscal/security/fiscal_security.xml +6 -16
  16. odoo/addons/l10n_br_fiscal/static/description/index.html +1 -1
  17. odoo/addons/l10n_br_fiscal/tests/test_document_edition.py +113 -0
  18. odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_generic.py +0 -11
  19. odoo/addons/l10n_br_fiscal/tests/test_fiscal_document_nfse.py +0 -1
  20. odoo/addons/l10n_br_fiscal/tests/test_tax_benefit.py +0 -1
  21. odoo/addons/l10n_br_fiscal/views/document_line_view.xml +3 -3
  22. odoo/addons/l10n_br_fiscal/views/document_view.xml +6 -6
  23. {odoo_addon_l10n_br_fiscal-18.0.2.0.0.11.dist-info → odoo_addon_l10n_br_fiscal-18.0.3.1.0.dist-info}/METADATA +2 -2
  24. {odoo_addon_l10n_br_fiscal-18.0.2.0.0.11.dist-info → odoo_addon_l10n_br_fiscal-18.0.3.1.0.dist-info}/RECORD +26 -25
  25. {odoo_addon_l10n_br_fiscal-18.0.2.0.0.11.dist-info → odoo_addon_l10n_br_fiscal-18.0.3.1.0.dist-info}/WHEEL +0 -0
  26. {odoo_addon_l10n_br_fiscal-18.0.2.0.0.11.dist-info → odoo_addon_l10n_br_fiscal-18.0.3.1.0.dist-info}/top_level.txt +0 -0
@@ -7,8 +7,12 @@ from lxml import etree
7
7
 
8
8
  from odoo import Command, api, models
9
9
 
10
- from ..constants.fiscal import CFOP_DESTINATION_EXPORT, FISCAL_IN
11
- from ..constants.icms import ICMS_BASE_TYPE_DEFAULT, ICMS_ST_BASE_TYPE_DEFAULT
10
+ from ..constants.fiscal import CFOP_DESTINATION_EXPORT, FISCAL_IN, TAX_DOMAIN_ICMS
11
+ from ..constants.icms import (
12
+ ICMS_BASE_TYPE_DEFAULT,
13
+ ICMS_ORIGIN_DEFAULT,
14
+ ICMS_ST_BASE_TYPE_DEFAULT,
15
+ )
12
16
 
13
17
  FISCAL_TAX_ID_FIELDS = [
14
18
  "cofins_tax_id",
@@ -171,11 +175,11 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
171
175
  # Total value of products or services
172
176
  record.price_gross = round_curr.round(record.price_unit * record.quantity)
173
177
  record.amount_fiscal = record.price_gross - record.discount_value
174
- record.amount_tax = record.amount_tax_not_included
178
+ record.fiscal_amount_tax = record.amount_tax_not_included
175
179
 
176
180
  add_to_amount = sum(record[a] for a in record._add_fields_to_amount())
177
181
  rm_to_amount = sum(record[r] for r in record._rm_fields_to_amount())
178
- record.amount_untaxed = (
182
+ record.fiscal_amount_untaxed = (
179
183
  record.price_gross
180
184
  - record.discount_value
181
185
  + add_to_amount
@@ -183,13 +187,17 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
183
187
  )
184
188
 
185
189
  # Valor do documento (NF)
186
- record.amount_total = record.amount_untaxed + record.amount_tax
190
+ record.fiscal_amount_total = (
191
+ record.fiscal_amount_untaxed + record.fiscal_amount_tax
192
+ )
187
193
 
188
194
  # Valor Liquido (TOTAL + IMPOSTOS - RETENÇÕES)
189
- record.amount_taxed = record.amount_total - record.amount_tax_withholding
195
+ record.amount_taxed = (
196
+ record.fiscal_amount_total - record.amount_tax_withholding
197
+ )
190
198
 
191
199
  # Valor do documento (NF) - RETENÇÕES
192
- record.amount_total = record.amount_taxed
200
+ record.fiscal_amount_total = record.amount_taxed
193
201
 
194
202
  # Valor financeiro
195
203
  if (
@@ -305,11 +313,16 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
305
313
  for tax in mapping_result["taxes"].values():
306
314
  taxes |= tax
307
315
  line.fiscal_tax_ids = taxes
308
- line.comment_ids = line.fiscal_operation_line_id.comment_ids
309
-
310
316
  else:
311
317
  line.fiscal_tax_ids = [Command.clear()]
312
318
 
319
+ @api.depends("fiscal_operation_line_id")
320
+ def _compute_comment_ids(self):
321
+ for line in self:
322
+ line.comment_ids = [
323
+ Command.set(line.fiscal_operation_line_id.comment_ids.ids)
324
+ ]
325
+
313
326
  @api.model
314
327
  def _build_null_mask_dict(self) -> dict:
315
328
  """
@@ -465,48 +478,50 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
465
478
  self.ensure_one()
466
479
  return self.partner_id
467
480
 
468
- @api.onchange("product_id")
469
- def _onchange_product_id_fiscal(self):
470
- if not self.fiscal_operation_id:
481
+ @api.depends("product_id")
482
+ def _compute_product_fiscal_fields(self):
483
+ if self._context.get("skip_compute_product_fiscal_fields"):
471
484
  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
485
+ for line in self:
486
+ if not line.product_id:
487
+ # reset to default values:
488
+ line.fiscal_type = False
489
+ line.ncm_id = False
490
+ line.nbm_id = False
491
+ line.tax_icms_or_issqn = TAX_DOMAIN_ICMS
492
+ line.icms_origin = ICMS_ORIGIN_DEFAULT
493
+ line.cest_id = False
494
+ line.nbs_id = False
495
+ line.fiscal_genre_id = False
496
+ line.service_type_id = False
497
+ line.city_taxation_code_id = False
498
+ line.issqn_fg_city_id = False
499
+ continue
500
+
501
+ product = line.product_id
502
+ line.fiscal_type = product.fiscal_type
503
+ line.ncm_id = product.ncm_id
504
+ line.nbm_id = product.nbm_id
505
+ line.tax_icms_or_issqn = product.tax_icms_or_issqn
506
+ line.icms_origin = product.icms_origin
507
+ line.cest_id = product.cest_id
508
+ line.nbs_id = product.nbs_id
509
+ line.fiscal_genre_id = product.fiscal_genre_id
510
+ line.service_type_id = product.service_type_id
511
+ if product.city_taxation_code_ids and line.company_id:
512
+ city = product.city_taxation_code_ids.filtered(
513
+ lambda r, current_line=line: r.city_id
514
+ == current_line.company_id.city_id
489
515
  )
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()
516
+ if city:
517
+ line.city_taxation_code_id = city
518
+ line.issqn_fg_city_id = line.company_id.city_id
519
+ else:
520
+ line.city_taxation_code_id = False
521
+ line.issqn_fg_city_id = False
522
+ else:
523
+ line.city_taxation_code_id = False
524
+ line.issqn_fg_city_id = False
510
525
 
511
526
  def _prepare_fields_issqn(self, tax_dict):
512
527
  self.ensure_one()
@@ -762,11 +777,18 @@ class FiscalDocumentLineMixinMethods(models.AbstractModel):
762
777
  )
763
778
  line.fiscal_tax_ids = fiscal_taxes + taxes
764
779
 
765
- @api.depends("uom_id")
780
+ @api.depends("product_id", "uom_id")
766
781
  def _compute_uot_id(self):
767
782
  for line in self:
768
- if not line.uot_id:
769
- line.uot_id = line.uom_id
783
+ if line.uom_id:
784
+ line.uot_id = line.uom_id.id
785
+ elif line.product_id:
786
+ if line.product_id.uot_id:
787
+ line.uot_id = line.product_id.uot_id.id
788
+ else:
789
+ line.uot_id = line.product_id.uom_id.id
790
+ else:
791
+ line.uot_id = False
770
792
 
771
793
  @api.onchange("price_unit")
772
794
  def _onchange_price_unit_fiscal(self):
@@ -126,7 +126,7 @@ class FiscalDocumentMixin(models.AbstractModel):
126
126
  help="Amount without discount.",
127
127
  )
128
128
 
129
- amount_untaxed = fields.Monetary(
129
+ fiscal_amount_untaxed = fields.Monetary(
130
130
  compute="_compute_fiscal_amount",
131
131
  store=True,
132
132
  )
@@ -394,12 +394,12 @@ class FiscalDocumentMixin(models.AbstractModel):
394
394
  store=True,
395
395
  )
396
396
 
397
- amount_tax = fields.Monetary(
397
+ fiscal_amount_tax = fields.Monetary(
398
398
  compute="_compute_fiscal_amount",
399
399
  store=True,
400
400
  )
401
401
 
402
- amount_total = fields.Monetary(
402
+ fiscal_amount_total = fields.Monetary(
403
403
  compute="_compute_fiscal_amount",
404
404
  store=True,
405
405
  )
@@ -73,7 +73,8 @@ class FiscalDocumentMixinMethods(models.AbstractModel):
73
73
  def _get_amount_fields(self):
74
74
  """Get all fields with 'amount_' prefix"""
75
75
  fields = self.env["l10n_br_fiscal.document.mixin"]._fields.keys()
76
- amount_fields = [f for f in fields if f.startswith("amount_")]
76
+ prefixes = ("amount_", "fiscal_amount_")
77
+ amount_fields = [f for f in fields if f.startswith(prefixes)]
77
78
  return amount_fields
78
79
 
79
80
  @api.depends("document_serie_id", "issuer")
@@ -198,152 +199,37 @@ class FiscalDocumentMixinMethods(models.AbstractModel):
198
199
  elif doc.comment_ids is None:
199
200
  doc.comment_ids = []
200
201
 
201
- def _inverse_amount_freight(self):
202
- for record in self.filtered(lambda doc: doc._get_product_amount_lines()):
203
- if (
202
+ def _distribute_amount_to_lines(self, amount_field_name, line_field_name):
203
+ for record in self:
204
+ if not (
204
205
  record.delivery_costs == "total"
205
206
  or record.force_compute_delivery_costs_by_total
206
207
  ):
207
- amount_freight_value = record.amount_freight_value
208
- if all(record._get_product_amount_lines().mapped("freight_value")):
209
- amount_freight_old = sum(
210
- record._get_product_amount_lines().mapped("freight_value")
211
- )
212
- for line in record._get_product_amount_lines()[:-1]:
213
- line.freight_value = amount_freight_value * (
214
- line.freight_value / amount_freight_old
215
- )
216
- record._get_product_amount_lines()[-1].freight_value = (
217
- amount_freight_value
218
- - sum(
219
- line.freight_value
220
- for line in record._get_product_amount_lines()[:-1]
221
- )
222
- )
223
- else:
224
- amount_total = sum(
225
- record._get_product_amount_lines().mapped("price_gross")
208
+ continue
209
+ lines = record._get_product_amount_lines()
210
+ if not lines:
211
+ continue
212
+ amount_to_distribute = record[amount_field_name]
213
+ total_gross = sum(lines.mapped("price_gross"))
214
+ if total_gross > 0:
215
+ distributed_amount = 0
216
+ for line in lines[:-1]:
217
+ proportional_amount = record.currency_id.round(
218
+ amount_to_distribute * (line.price_gross / total_gross)
226
219
  )
227
- for line in record._get_product_amount_lines()[:-1]:
228
- if line.price_gross and amount_total:
229
- line.freight_value = amount_freight_value * (
230
- line.price_gross / amount_total
231
- )
232
- record._get_product_amount_lines()[-1].freight_value = (
233
- amount_freight_value
234
- - sum(
235
- line.freight_value
236
- for line in record._get_product_amount_lines()[:-1]
237
- )
238
- )
239
- for line in record._get_product_amount_lines():
240
- line._onchange_fiscal_taxes()
241
- record._fields["amount_total"].compute_value(record)
242
- record.write(
243
- {
244
- name: value
245
- for name, value in record._cache.items()
246
- if record._fields[name].compute == "_amount_all"
247
- and not record._fields[name].inverse
248
- }
249
- )
220
+ line[line_field_name] = proportional_amount
221
+ distributed_amount += proportional_amount
222
+ lines[-1][line_field_name] = amount_to_distribute - distributed_amount
223
+ else:
224
+ lines.write({line_field_name: 0.0})
225
+ if lines:
226
+ lines[0][line_field_name] = amount_to_distribute
227
+
228
+ def _inverse_amount_freight(self):
229
+ self._distribute_amount_to_lines("amount_freight_value", "freight_value")
250
230
 
251
231
  def _inverse_amount_insurance(self):
252
- for record in self.filtered(lambda doc: doc._get_product_amount_lines()):
253
- if (
254
- record.delivery_costs == "total"
255
- or record.force_compute_delivery_costs_by_total
256
- ):
257
- amount_insurance_value = record.amount_insurance_value
258
- if all(record._get_product_amount_lines().mapped("insurance_value")):
259
- amount_insurance_old = sum(
260
- record._get_product_amount_lines().mapped("insurance_value")
261
- )
262
- for line in record._get_product_amount_lines()[:-1]:
263
- line.insurance_value = amount_insurance_value * (
264
- line.insurance_value / amount_insurance_old
265
- )
266
- record._get_product_amount_lines()[-1].insurance_value = (
267
- amount_insurance_value
268
- - sum(
269
- line.insurance_value
270
- for line in record._get_product_amount_lines()[:-1]
271
- )
272
- )
273
- else:
274
- amount_total = sum(
275
- record._get_product_amount_lines().mapped("price_gross")
276
- )
277
- for line in record._get_product_amount_lines()[:-1]:
278
- if line.price_gross and amount_total:
279
- line.insurance_value = amount_insurance_value * (
280
- line.price_gross / amount_total
281
- )
282
- record._get_product_amount_lines()[-1].insurance_value = (
283
- amount_insurance_value
284
- - sum(
285
- line.insurance_value
286
- for line in record._get_product_amount_lines()[:-1]
287
- )
288
- )
289
- for line in record._get_product_amount_lines():
290
- line._onchange_fiscal_taxes()
291
- record._fields["amount_total"].compute_value(record)
292
- record.write(
293
- {
294
- name: value
295
- for name, value in record._cache.items()
296
- if record._fields[name].compute == "_amount_all"
297
- and not record._fields[name].inverse
298
- }
299
- )
232
+ self._distribute_amount_to_lines("amount_insurance_value", "insurance_value")
300
233
 
301
234
  def _inverse_amount_other(self):
302
- for record in self.filtered(lambda doc: doc._get_product_amount_lines()):
303
- if (
304
- record.delivery_costs == "total"
305
- or record.force_compute_delivery_costs_by_total
306
- ):
307
- amount_other_value = record.amount_other_value
308
- if all(record._get_product_amount_lines().mapped("other_value")):
309
- amount_other_old = sum(
310
- record._get_product_amount_lines().mapped("other_value")
311
- )
312
- for line in record._get_product_amount_lines()[:-1]:
313
- line.other_value = amount_other_value * (
314
- line.other_value / amount_other_old
315
- )
316
- record._get_product_amount_lines()[-1].other_value = (
317
- amount_other_value
318
- - sum(
319
- line.other_value
320
- for line in record._get_product_amount_lines()[:-1]
321
- )
322
- )
323
- else:
324
- amount_total = sum(
325
- record._get_product_amount_lines().mapped("price_gross")
326
- )
327
- for line in record._get_product_amount_lines()[:-1]:
328
- if line.price_gross and amount_total:
329
- line.other_value = amount_other_value * (
330
- line.price_gross / amount_total
331
- )
332
- record._get_product_amount_lines()[-1].other_value = (
333
- amount_other_value
334
- - sum(
335
- line.other_value
336
- for line in record._get_product_amount_lines()[:-1]
337
- )
338
- )
339
- for line in record._get_product_amount_lines():
340
- line._onchange_fiscal_taxes()
341
- record._fields["amount_total"].compute_value(record)
342
- record.write(
343
- {
344
- name: value
345
- for name, value in record._cache.items()
346
- if record._fields[name].compute == "_amount_all"
347
- and not record._fields[name].inverse
348
- }
349
- )
235
+ self._distribute_amount_to_lines("amount_other_value", "other_value")
@@ -104,7 +104,7 @@ class DocumentRelated(models.Model):
104
104
  return False
105
105
 
106
106
  self.document_type_id = related.document_type_id
107
- self.document_total_amount = related.amount_total
107
+ self.document_total_amount = related.fiscal_amount_total
108
108
  self.document_total_weight = related.total_weight
109
109
 
110
110
  if related.document_type_id.electronic:
@@ -38,44 +38,34 @@
38
38
  <field name="name">Fiscal Tax Estimate multi-company</field>
39
39
  <field name="model_id" ref="model_l10n_br_fiscal_tax_estimate" />
40
40
  <field eval="True" name="global" />
41
- <field
42
- name="domain_force"
43
- >['|',('company_id','=',False),('company_id','in',company_ids)]</field>
41
+ <field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
44
42
  </record>
45
43
 
46
- <record id="l10n_br_fiscal_operation_line_rule" model="ir.rule">
44
+ <record id="l10n_br_fiscal_operation_rule" model="ir.rule">
47
45
  <field name="name">Fiscal Operation multi-company</field>
48
46
  <field name="model_id" ref="model_l10n_br_fiscal_operation" />
49
47
  <field eval="True" name="global" />
50
- <field
51
- name="domain_force"
52
- >['|',('company_id','=',False),('company_id','in',company_ids)]</field>
48
+ <field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
53
49
  </record>
54
50
 
55
51
  <record id="l10n_br_fiscal_document_serie_rule" model="ir.rule">
56
52
  <field name="name">Fiscal Document Serie multi-company</field>
57
53
  <field name="model_id" ref="model_l10n_br_fiscal_document_serie" />
58
54
  <field eval="True" name="global" />
59
- <field
60
- name="domain_force"
61
- >['|',('company_id','=',False),('company_id','in',company_ids)]</field>
55
+ <field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
62
56
  </record>
63
57
 
64
58
  <record id="l10n_br_fiscal_document_rule" model="ir.rule">
65
59
  <field name="name">Fiscal Document multi-company</field>
66
60
  <field name="model_id" ref="model_l10n_br_fiscal_document" />
67
61
  <field eval="True" name="global" />
68
- <field
69
- name="domain_force"
70
- >['|',('company_id','=',False),('company_id','in',company_ids)]</field>
62
+ <field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
71
63
  </record>
72
64
 
73
65
  <record id="l10n_br_fiscal_document_line_rule" model="ir.rule">
74
66
  <field name="name">Fiscal Document line multi-company</field>
75
67
  <field name="model_id" ref="model_l10n_br_fiscal_document_line" />
76
68
  <field eval="True" name="global" />
77
- <field
78
- name="domain_force"
79
- >['|',('company_id','=',False),('company_id','in',company_ids)]</field>
69
+ <field name="domain_force">[('company_id', 'in', company_ids + [False])]</field>
80
70
  </record>
81
71
  </odoo>
@@ -372,7 +372,7 @@ ul.auto-toc {
372
372
  !! This file is generated by oca-gen-addon-readme !!
373
373
  !! changes will be overwritten. !!
374
374
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
375
- !! source digest: sha256:b7471fd6bd126d628ba34ff9f474314e408b56c0cde25924e2312b4a1c047a5c
375
+ !! source digest: sha256:306bfd80e3ccb9e7d199cfffc21bda642881eece97f1763ad8f6009904d198f4
376
376
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
377
377
  <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/l10n-brazil/tree/18.0/l10n_br_fiscal"><img alt="OCA/l10n-brazil" src="https://img.shields.io/badge/github-OCA%2Fl10n--brazil-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/l10n-brazil-18-0/l10n-brazil-18-0-l10n_br_fiscal"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/l10n-brazil&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
378
378
  <p><img alt="image" src="https://raw.githubusercontent.com/OCA/l10n-brazil/18.0/l10n_br_fiscal/static/img/fiscal_dashboard.png" /></p>
@@ -35,6 +35,10 @@ class TestDocumentEdition(TransactionCase):
35
35
  cls.env = cls.env(
36
36
  user=cls.user, context=dict(cls.env.context, tracking_disable=True)
37
37
  )
38
+ cls.user = cls.env.user
39
+ cls.company = cls.env.ref("l10n_br_base.empresa_lucro_presumido")
40
+ cls.user.company_ids |= cls.company
41
+ cls.user.company_id = cls.company.id
38
42
 
39
43
  def test_basic_doc_edition(self):
40
44
  doc_form = Form(
@@ -162,3 +166,112 @@ class TestDocumentEdition(TransactionCase):
162
166
  self.assertEqual(doc.fiscal_line_ids[0].fiscal_price, 112)
163
167
  self.assertEqual(doc.fiscal_line_ids[0].quantity, 10)
164
168
  self.assertEqual(doc.fiscal_line_ids[0].fiscal_quantity, 5)
169
+
170
+ def test_landed_costs_by_line_and_by_total(self):
171
+ """
172
+ Tests both landed cost scenarios: 'by line' and 'by total'.
173
+ 1. By Line: Enters costs on lines and verifies the header totals.
174
+ 2. By Total: Enters costs on the header and verifies lines distribution.
175
+ """
176
+ self.env.user.groups_id |= self.env.ref("l10n_br_fiscal.group_user")
177
+ product1 = self.env.ref("product.product_product_6")
178
+ product2 = self.env.ref("product.product_product_7")
179
+
180
+ # Part 1: Test with delivery_costs = 'line'
181
+ # ----------------------------------------------------
182
+ self.company.delivery_costs = "line"
183
+ doc_form = Form(self.env["l10n_br_fiscal.document"])
184
+ doc_form.company_id = self.company
185
+ doc_form.partner_id = self.env.ref("l10n_br_base.res_partner_cliente1_sp")
186
+ doc_form.fiscal_operation_id = self.env.ref("l10n_br_fiscal.fo_venda")
187
+
188
+ with doc_form.fiscal_line_ids.new() as line1:
189
+ line1.product_id = product1
190
+ line1.fiscal_operation_line_id = self.env.ref(
191
+ "l10n_br_fiscal.fo_venda_venda"
192
+ )
193
+ line1.price_unit = 1000.0
194
+ line1.quantity = 2.0 # Gross: 2000
195
+ line1.freight_value = 10.0
196
+ line1.insurance_value = 20.0
197
+ line1.other_value = 5.0
198
+
199
+ with doc_form.fiscal_line_ids.new() as line2:
200
+ line2.product_id = product2
201
+ line2.fiscal_operation_line_id = self.env.ref(
202
+ "l10n_br_fiscal.fo_venda_venda"
203
+ )
204
+ line2.price_unit = 500.0
205
+ line2.quantity = 1.0 # Gross: 500
206
+ line2.freight_value = 4.0
207
+ line2.insurance_value = 6.0
208
+ line2.other_value = 2.0
209
+
210
+ doc = doc_form.save()
211
+
212
+ self.assertEqual(doc.company_id.delivery_costs, "line")
213
+ # Assert header totals are the SUM of line values
214
+ self.assertAlmostEqual(doc.amount_freight_value, 14.0) # 10.0 + 4.0
215
+ self.assertAlmostEqual(doc.amount_insurance_value, 26.0) # 20.0 + 6.0
216
+ self.assertAlmostEqual(doc.amount_other_value, 7.0) # 5.0 + 2.0
217
+
218
+ # Assert final fiscal totals (bottom-up calculation)
219
+ # price_gross = (1000*2) + (500*1) = 2500
220
+ # landed_costs = 14 + 26 + 7 = 47
221
+ # fiscal_amount_untaxed (IPI Base) = 2500 + 47 = 2547
222
+ self.assertAlmostEqual(doc.fiscal_amount_untaxed, 2547.00)
223
+ # fiscal_amount_tax (IPI) = (2035 * 3.25%) + (512 * 5%) = 66.14 + 25.60 = 91.74
224
+ self.assertAlmostEqual(doc.fiscal_amount_tax, 91.74, places=2)
225
+ # fiscal_amount_total = 2547.00 + 91.74 = 2638.74
226
+ self.assertAlmostEqual(doc.fiscal_amount_total, 2638.74, places=2)
227
+
228
+ # Part 2: Test with delivery_costs = 'total'
229
+ # ----------------------------------------------------
230
+ self.company.delivery_costs = "total"
231
+ doc_form_edit = Form(doc)
232
+ # Set new header totals, which should trigger inverse methods to distribute
233
+ doc_form_edit.amount_freight_value = 30.0
234
+ doc_form_edit.amount_insurance_value = 60.0
235
+ doc_form_edit.amount_other_value = 90.0
236
+ doc_after_total_update = doc_form_edit.save()
237
+
238
+ line1 = doc_after_total_update.fiscal_line_ids[0]
239
+ line2 = doc_after_total_update.fiscal_line_ids[1]
240
+
241
+ # Assert values were distributed proportionally to price_gross
242
+ # (2000 vs 500 -> 80% vs 20%)
243
+ # Freight: 30.0 * 0.8 = 24.0 | 30.0 * 0.2 = 6.0
244
+ self.assertAlmostEqual(line1.freight_value, 24.0)
245
+ self.assertAlmostEqual(line2.freight_value, 6.0)
246
+ # Insurance: 60.0 * 0.8 = 48.0 | 60.0 * 0.2 = 12.0
247
+ self.assertAlmostEqual(line1.insurance_value, 48.0)
248
+ self.assertAlmostEqual(line2.insurance_value, 12.0)
249
+ # Other: 90.0 * 0.8 = 72.0 | 90.0 * 0.2 = 18.0
250
+ self.assertAlmostEqual(line1.other_value, 72.0)
251
+ self.assertAlmostEqual(line2.other_value, 18.0)
252
+
253
+ # Assert final fiscal totals are recomputed correctly (top-down calculation)
254
+ # price_gross = 2500
255
+ # landed_costs = 30 + 60 + 90 = 180
256
+ # fiscal_amount_untaxed (IPI Base) = 2500 + 180 = 2680
257
+ self.assertAlmostEqual(doc_after_total_update.fiscal_amount_untaxed, 2680.00)
258
+ # Line 1 IPI Base = 2000 (product) + 24 (freight) + 48 (insurance)
259
+ # + 72 (other) = 2144
260
+ # Line 1 IPI Value = 2144 * 3.25% = 69.68
261
+ self.assertAlmostEqual(line1.ipi_base, 2144.00)
262
+ self.assertAlmostEqual(line1.ipi_value, 69.68, places=2)
263
+
264
+ # Line 2 IPI Base = 500 (product) + 6 (freight) + 12 (insurance)
265
+ # + 18 (other) = 536
266
+ # Line 2 IPI Value = 536 * 5% = 26.80
267
+ self.assertAlmostEqual(line2.ipi_base, 536.00)
268
+ self.assertAlmostEqual(line2.ipi_value, 26.80, places=2)
269
+
270
+ # fiscal_amount_tax (IPI) = 69.68 + 26.80 = 96.48
271
+ self.assertAlmostEqual(
272
+ doc_after_total_update.fiscal_amount_tax, 96.48, places=2
273
+ )
274
+ # fiscal_amount_total = 2680.00 + 96.48 = 2776.48
275
+ self.assertAlmostEqual(
276
+ doc_after_total_update.fiscal_amount_total, 2776.48, places=2
277
+ )