Skip to content

Commit

Permalink
[IMP] composite account.move
Browse files Browse the repository at this point in the history
  • Loading branch information
rvalyi committed Mar 17, 2024
1 parent f777d73 commit 43e6c91
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 17 deletions.
152 changes: 139 additions & 13 deletions l10n_br_account/models/account_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.tests.common import Form
from odoo.tools import mute_logger

from odoo.addons.l10n_br_fiscal.constants.fiscal import (
Expand Down Expand Up @@ -48,13 +49,16 @@
"in_refund": "purchase",
}

# l10n_br_fiscal.document field names that are shadowed
# by account.move fields:
SHADOWED_FIELDS = ["company_id", "currency_id", "user_id", "partner_id"]


class InheritsCheckMuteLogger(mute_logger):
"""
Mute the Model#_inherits_check warning
because the _inherits field is not required.
(some account.move may have no fiscal document)
"""

def filter(self, record):
Expand All @@ -70,7 +74,18 @@ class AccountMove(models.Model):
_name,
"l10n_br_fiscal.document.move.mixin",
]

# an account.move has normally 0 or 1 related fiscal document:
# - 0 when it is not related to a Brazilian company for instance.
# - usually 1 otherwise. In this case the _inherits system
# makes it easy to edit all the fiscal document (lines) fields
# through the account.move form.
# in some rare cases an account.move may have several fiscal
# documents (1 on each account.move.line). In this case
# fiscal_document_id is not used and fiscal_document_ids is
# used instead.
_inherits = {"l10n_br_fiscal.document": "fiscal_document_id"}

_order = "date DESC, name DESC"

document_electronic = fields.Boolean(
Expand All @@ -85,6 +100,15 @@ class AccountMove(models.Model):
ondelete="cascade",
)

fiscal_document_ids = fields.One2many(
comodel_name="l10n_br_fiscal.document",
string="Fiscal Documents",
compute="_compute_fiscal_document_ids",
help="""In some rare cases (NFS-e, CT-e...) a single account.move
may have several different fiscal documents related to its account.move.lines.
""",
)

fiscal_operation_type = fields.Selection(
selection=FISCAL_IN_OUT_ALL,
related=None,
Expand All @@ -101,6 +125,15 @@ def _check_fiscal_document_type(self):
)
)

@api.depends("line_ids", "invoice_line_ids")
def _compute_fiscal_document_ids(self):
for move in self:
docs = set()

Check warning on line 131 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L131

Added line #L131 was not covered by tests
for line in move.invoice_line_ids:
docs.add(line.document_id.id)
move.fiscal_document_ids = list(docs)

Check warning on line 134 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L133-L134

Added lines #L133 - L134 were not covered by tests

@api.depends("move_type", "fiscal_operation_id")
def _compute_fiscal_operation_type(self):
for inv in self:
if inv.move_type == "entry":
Expand All @@ -122,7 +155,7 @@ def _get_amount_lines(self):
def _inherits_check(self):
"""
Overriden to avoid the super method to set the fiscal_document_id
field as required.
field as required (because some account.move may not have any fiscal document).
"""
with InheritsCheckMuteLogger("odoo.models"): # mute spurious warnings
res = super()._inherits_check()
Expand All @@ -143,6 +176,17 @@ def _inject_shadowed_fields(self, vals_list):
if field in vals:
vals["fiscal_%s" % (field,)] = vals[field]

def ensure_one_doc(self):
self.ensure_one()

Check warning on line 180 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L180

Added line #L180 was not covered by tests
if len(self.fiscal_document_ids) > 1:
raise UserError(

Check warning on line 182 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L182

Added line #L182 was not covered by tests
_(
"More than 1 fiscal document!"
"You should open the fiscal view"
"and perform the action on each document!"
)
)

@api.model
def fields_view_get(
self, view_id=None, view_type="form", toolbar=False, submenu=False
Expand Down Expand Up @@ -282,14 +326,42 @@ def default_get(self, fields_list):

@api.model
def _move_autocomplete_invoice_lines_create(self, vals_list):
fiscal_document_line_ids = {}
for idx1, move_val in enumerate(vals_list):
if "invoice_line_ids" in move_val:
fiscal_document_line_ids[idx1] = {}
for idx2, line_val in enumerate(move_val["invoice_line_ids"]):
if (
line_val[0] == 0
and line_val[1] == 0
and isinstance(line_val[2], dict)
):
fiscal_document_line_ids[idx1][idx2] = line_val[2].get(
"fiscal_document_line_id", False
)

new_vals_list = super(
AccountMove, self.with_context(lines_compute_amounts=True)
)._move_autocomplete_invoice_lines_create(vals_list)
for vals in new_vals_list:
if not vals.get("document_type_id"):
vals[
"fiscal_document_id"
] = False # self.env.company.fiscal_dummy_id.id
vals["fiscal_document_id"] = False

for idx1, move_val in enumerate(new_vals_list):
if "line_ids" in move_val:
if fiscal_document_line_ids.get(idx1):
idx2 = 0
for line_val in move_val["line_ids"]:
if (
line_val[0] == 0
and line_val[1] == 0
and isinstance(line_val[2], dict)
):
line_val[2][
"fiscal_document_line_id"
] = fiscal_document_line_ids[idx1].get(idx2)
idx2 += 1

return new_vals_list

def _move_autocomplete_invoice_lines_values(self):
Expand All @@ -308,6 +380,8 @@ def create(self, vals_list):

def write(self, values):
self._inject_shadowed_fields([values])
# TODO if values.keys() in fiscal doc fields
# and len(fiscal_document_ids) > 1 raise or write in other fiscal docs
result = super().write(values)
return result

Expand Down Expand Up @@ -380,7 +454,6 @@ def _compute_taxes_mapped(self, base_line):
icms_origin=base_line.icms_origin,
ind_final=base_line.ind_final,
)

return balance_taxes_res

def _preprocess_taxes_map(self, taxes_map):
Expand Down Expand Up @@ -477,14 +550,14 @@ def button_draft(self):
"because this document is cancelled in SEFAZ"
).format(move.document_number)
)
if move.state_edoc != SITUACAO_EDOC_EM_DIGITACAO:
move.fiscal_document_id.action_document_back2draft()
move.fiscal_document_ids.filtered(
lambda d: d.state_edoc != SITUACAO_EDOC_EM_DIGITACAO
).action_document_back2draft()
return super().button_draft()

def action_document_send(self):
invoices = self.filtered(lambda d: d.document_type_id)
if invoices:
invoices.mapped("fiscal_document_id").action_document_send()
for invoice in self.filtered(lambda d: d.document_type_id):
invoice.fiscal_document_ids.action_document_send()

Check warning on line 560 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L560

Added line #L560 was not covered by tests
# FIXME: na migração para a v14 foi permitido o post antes do envio
# para destravar a migração, mas poderia ser cogitado de obrigar a
# transmissão antes do post novamente como na v12.
Expand All @@ -493,14 +566,17 @@ def action_document_send(self):

def action_document_cancel(self):
for move in self.filtered(lambda d: d.document_type_id):
move.ensure_one_doc()
return move.fiscal_document_id.action_document_cancel()

Check warning on line 570 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L569-L570

Added lines #L569 - L570 were not covered by tests

def action_document_correction(self):
for move in self.filtered(lambda d: d.document_type_id):
move.ensure_one_doc()
return move.fiscal_document_id.action_document_correction()

Check warning on line 575 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L574-L575

Added lines #L574 - L575 were not covered by tests

def action_document_invalidate(self):
for move in self.filtered(lambda d: d.document_type_id):
move.ensure_one_doc()
return move.fiscal_document_id.action_document_invalidate()

Check warning on line 580 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L579-L580

Added lines #L579 - L580 were not covered by tests

def action_document_back2draft(self):
Expand All @@ -517,15 +593,15 @@ def _post(self, soft=True):
return super()._post(soft=soft)

def view_xml(self):
self.ensure_one()
self.ensure_one_doc()

Check warning on line 596 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L596

Added line #L596 was not covered by tests
return self.fiscal_document_id.view_xml()

def view_pdf(self):
self.ensure_one()
self.ensure_one_doc()

Check warning on line 600 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L600

Added line #L600 was not covered by tests
return self.fiscal_document_id.view_pdf()

def action_send_email(self):
self.ensure_one()
self.ensure_one_doc()

Check warning on line 604 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L604

Added line #L604 was not covered by tests
return self.fiscal_document_id.action_send_email()

@api.onchange("document_type_id")
Expand Down Expand Up @@ -693,3 +769,53 @@ def _get_integrity_hash_fields_and_subfields(self):
f"line_ids.{subfield}"
for subfield in self.env["account.move.line"]._get_integrity_hash_fields()
]

@api.model
def import_fiscal_document(
self,
fiscal_document,
move_id=None,
move_type="in_invoice",
):
"""
Import the data from an existing fiscal document into a new
invoice or into an existing invoice.
First it transfers the "shadowed" fields and fill the other
mandatory invoice fields.
The account.move onchanges of these fields are properly
triggered as if the invoice was filled manually.
Then it creates each account.move.line and fill them using
their fiscal_document_id onchange.
"""
if move_id:
target_move = self.env["account.move"].browse(move_id)

Check warning on line 791 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L791

Added line #L791 was not covered by tests
else:
target_move = self.env["account.move"]
move_form = Form(

Check warning on line 794 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L793-L794

Added lines #L793 - L794 were not covered by tests
target_move.with_context(
default_move_type=move_type,
account_predictive_bills_disable_prediction=True,
)
)
if not move_id or not move_id.fiscal_document_id:
move_form.invoice_date = fiscal_document.document_date
move_form.date = fiscal_document.document_date

Check warning on line 802 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L801-L802

Added lines #L801 - L802 were not covered by tests
for field in self._shadowed_fields():
if field in ("company_id", "user_id"): # (readonly fields)
continue

Check warning on line 805 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L805

Added line #L805 was not covered by tests
if not move_form._view["fields"].get(field):
continue
setattr(move_form, field, getattr(fiscal_document, field))
move_form.document_type_id = fiscal_document.document_type_id
move_form.fiscal_document_id = fiscal_document
move_form.fiscal_operation_id = fiscal_document.fiscal_operation_id

Check warning on line 811 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L807-L811

Added lines #L807 - L811 were not covered by tests

for line in fiscal_document.fiscal_line_ids:
with move_form.invoice_line_ids.new() as line_form:
line_form.cfop_id = (

Check warning on line 815 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L814-L815

Added lines #L814 - L815 were not covered by tests
line.cfop_id
) # required if we disable some fiscal tax updates
line_form.fiscal_operation_id = self.fiscal_operation_id
line_form.fiscal_document_line_id = line
move_form.save()
return move_form

Check warning on line 821 in l10n_br_account/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move.py#L818-L821

Added lines #L818 - L821 were not covered by tests
36 changes: 33 additions & 3 deletions l10n_br_account/models/account_move_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
# Fields that are related in l10n_br_fiscal.document.line like partner_id or company_id
# don't need to be written through the account.move.line write.
SHADOWED_FIELDS = [
"name",
"product_id",
"name",
"quantity",
"price_unit",
]
Expand Down Expand Up @@ -151,10 +151,26 @@ def _inject_shadowed_fields(self, vals_list):
@api.model_create_multi
def create(self, vals_list):
for values in vals_list:
if values.get("fiscal_document_line_id"):
fiscal_line_data = (

Check warning on line 155 in l10n_br_account/models/account_move_line.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move_line.py#L155

Added line #L155 was not covered by tests
self.env["l10n_br_fiscal.document.line"]
.browse(values["fiscal_document_line_id"])
.read(self._shadowed_fields())[0]
)
for k, v in fiscal_line_data.items():
if isinstance(v, tuple): # m2o
values[k] = v[0]

Check warning on line 162 in l10n_br_account/models/account_move_line.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move_line.py#L162

Added line #L162 was not covered by tests
else:
values[k] = v
continue

Check warning on line 165 in l10n_br_account/models/account_move_line.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move_line.py#L164-L165

Added lines #L164 - L165 were not covered by tests

if values.get("exclude_from_invoice_tab"):
continue

move_id = self.env["account.move"].browse(values["move_id"])
fiscal_doc_id = move_id.fiscal_document_id.id

if not fiscal_doc_id or values.get("exclude_from_invoice_tab"):
if not fiscal_doc_id:
continue

values.update(
Expand Down Expand Up @@ -216,7 +232,7 @@ def create(self, vals_list):
# of the remaining fiscal document lines with their proper aml. That's why we
# remove the useless fiscal document lines here.
for line in results:
if not fiscal_doc_id or line.exclude_from_invoice_tab:
if not line.move_id.fiscal_document_id or line.exclude_from_invoice_tab:
fiscal_line_to_delete = line.fiscal_document_line_id
line.fiscal_document_line_id = False
fiscal_line_to_delete.sudo().unlink()
Expand Down Expand Up @@ -441,6 +457,20 @@ def _get_price_total_and_subtotal_model(

return result

@api.onchange("fiscal_document_line_id")
def _onchange_fiscal_document_line_id(self):
if self.fiscal_document_line_id:
for field in self._shadowed_fields():
if field == "uom_id":
continue # FIXME https://github.com/OCA/l10n-brazil/pull/2731

Check warning on line 465 in l10n_br_account/models/account_move_line.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move_line.py#L465

Added line #L465 was not covered by tests
value = getattr(self.fiscal_document_line_id, field)
if isinstance(value, tuple): # m2o
setattr(self, field, value[0])

Check warning on line 468 in l10n_br_account/models/account_move_line.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_account/models/account_move_line.py#L468

Added line #L468 was not covered by tests
else:
setattr(self, field, value)
# override the default product uom (set by the onchange):
self.product_uom_id = self.fiscal_document_line_id.uom_id.id

@api.onchange("fiscal_tax_ids")
def _onchange_fiscal_tax_ids(self):
"""Ao alterar o campo fiscal_tax_ids que contém os impostos fiscais,
Expand Down
2 changes: 1 addition & 1 deletion l10n_br_account/views/account_invoice_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@
name="fiscal_price"
attrs="{'invisible': [('document_type_id', '=', False)]}"
/>

<field name="fiscal_document_line_id" />
<field name="amount_currency" invisible="1" />
<field name="date" invisible="1" />
<field name="date_maturity" invisible="1" />
Expand Down

0 comments on commit 43e6c91

Please sign in to comment.