-
-
Notifications
You must be signed in to change notification settings - Fork 244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[12.0] Edição do Documento Fiscal e Fatura #1146
Changes from all commits
b9b453b
1e92765
3b66ee9
ca793c1
0284b62
75e89e6
f0764b4
cf7bcef
6e98995
809f3c2
2d94086
641ff18
f10ee10
5d7ad8c
e8e3b11
780b1cf
687c9dd
c349681
806598a
cda3e38
df6bc50
0f09bce
ab498ca
929f3a1
7c212d0
cb9b7b3
9aea698
a223c7c
2b15e02
44b9904
fbf8b81
d6030cb
766a249
236b1a7
11c1b42
3b8ef36
927677d
18ebe20
223f9c5
982a8ff
c25ac34
e3c5415
3dee7ab
5d295e0
69514b5
8ae5d0c
6ce404d
312d213
437959e
b2c196b
14e49dc
aff4b9b
c9ef785
300d011
b5e2f9f
535abb0
4e23dc8
e2eda56
961dfa9
25295a2
aafcf5f
098250c
2b06db1
c94a676
a82fa0b
8271ac6
34f31d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,19 @@ | |
# Copyright (C) 2019 - TODAY Raphaël Valyi - Akretion | ||
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html | ||
|
||
from odoo import api, fields, models | ||
from lxml import etree | ||
|
||
from odoo import _, api, fields, models | ||
from odoo.exceptions import UserError | ||
|
||
from odoo.addons.l10n_br_fiscal.constants.fiscal import ( | ||
FISCAL_OUT, | ||
DOCUMENT_ISSUER_COMPANY, | ||
DOCUMENT_ISSUER_PARTNER, | ||
SITUACAO_EDOC_AUTORIZADA, | ||
SITUACAO_EDOC_CANCELADA, | ||
SITUACAO_EDOC_EM_DIGITACAO, | ||
) | ||
|
||
INVOICE_TO_OPERATION = { | ||
'out_invoice': 'out', | ||
|
@@ -23,12 +35,21 @@ | |
'in': ['sale_return', 'out_return'], | ||
} | ||
|
||
SHADOWED_FIELDS = ['partner_id', 'company_id', 'date', 'currency_id'] | ||
SHADOWED_FIELDS = [ | ||
'partner_id', | ||
'company_id', | ||
'date', | ||
'currency_id', | ||
'partner_shipping_id', | ||
] | ||
|
||
|
||
class AccountInvoice(models.Model): | ||
_name = 'account.invoice' | ||
_inherit = 'account.invoice' | ||
_inherit = [ | ||
_name, | ||
'l10n_br_fiscal.document.mixin.methods', | ||
'l10n_br_fiscal.document.invoice.mixin'] | ||
_inherits = {'l10n_br_fiscal.document': 'fiscal_document_id'} | ||
_order = 'date_invoice DESC, number DESC' | ||
|
||
|
@@ -63,6 +84,16 @@ class AccountInvoice(models.Model): | |
compute='_compute_financial', | ||
) | ||
|
||
document_electronic = fields.Boolean( | ||
related='document_type_id.electronic', | ||
string='Electronic?', | ||
) | ||
|
||
fiscal_number = fields.Char( | ||
string='Fiscal Number', | ||
copy=False, | ||
) | ||
|
||
# this default should be overwritten to False in a module pretending to | ||
# create fiscal documents from the invoices. But this default here | ||
# allows to install the l10n_br_account module without creating issues | ||
|
@@ -71,9 +102,8 @@ class AccountInvoice(models.Model): | |
comodel_name='l10n_br_fiscal.document', | ||
string='Fiscal Document', | ||
required=True, | ||
copy=False, | ||
ondelete='cascade', | ||
default=lambda self: self.env.ref( | ||
'l10n_br_fiscal.fiscal_document_dummy'), | ||
) | ||
|
||
@api.multi | ||
|
@@ -99,9 +129,60 @@ def _prepare_shadowed_fields_dict(self, default=False): | |
return {'default_%s' % (k,): vals[k] for k in vals.keys()} | ||
return vals | ||
|
||
@api.model | ||
def fields_view_get(self, view_id=None, view_type="form", | ||
toolbar=False, submenu=False): | ||
|
||
order_view = super().fields_view_get( | ||
view_id, view_type, toolbar, submenu | ||
) | ||
|
||
if view_type == 'form': | ||
view = self.env['ir.ui.view'] | ||
|
||
if (view_id == self.env.ref( | ||
'l10n_br_account.fiscal_invoice_form').id): | ||
invoice_line_form_id = self.env.ref( | ||
'l10n_br_account.fiscal_invoice_line_form').id | ||
else: | ||
invoice_line_form_id = self.env.ref( | ||
'l10n_br_account.invoice_line_form').id | ||
|
||
sub_form_view = self.env['account.invoice.line'].fields_view_get( | ||
view_id=invoice_line_form_id, view_type='form')['arch'] | ||
|
||
sub_form_node = etree.fromstring( | ||
self.env['account.invoice.line'].fiscal_form_view( | ||
sub_form_view)) | ||
|
||
sub_arch, sub_fields = view.postprocess_and_fields( | ||
'account.invoice.line', sub_form_node, None) | ||
|
||
order_view['fields']['invoice_line_ids']['views']['form'] = {} | ||
|
||
order_view['fields']['invoice_line_ids']['views']['form'][ | ||
'fields'] = sub_fields | ||
order_view['fields']['invoice_line_ids']['views']['form'][ | ||
'arch'] = sub_arch | ||
|
||
return order_view | ||
|
||
@api.model | ||
def default_get(self, fields_list): | ||
defaults = super().default_get(fields_list) | ||
invoice_type = self.env.context.get('type', 'out_invoice') | ||
defaults['fiscal_operation_type'] = INVOICE_TO_OPERATION[invoice_type] | ||
if defaults['fiscal_operation_type'] == FISCAL_OUT: | ||
defaults['issuer'] = DOCUMENT_ISSUER_COMPANY | ||
else: | ||
defaults['issuer'] = DOCUMENT_ISSUER_PARTNER | ||
return defaults | ||
|
||
@api.model | ||
def create(self, values): | ||
dummy_doc = self.env.ref('l10n_br_fiscal.fiscal_document_dummy') | ||
if not values.get('document_type_id'): | ||
values.update({'fiscal_document_id': dummy_doc.id}) | ||
invoice = super().create(values) | ||
if invoice.fiscal_document_id != dummy_doc: | ||
shadowed_fiscal_vals = invoice._prepare_shadowed_fields_dict() | ||
|
@@ -118,6 +199,14 @@ def write(self, values): | |
invoice.fiscal_document_id.write(shadowed_fiscal_vals) | ||
return result | ||
|
||
@api.multi | ||
def unlink(self): | ||
"""Allows delete a draft or cancelled invoices""" | ||
self.filtered(lambda i: i.state in ('draft', 'cancel')).write( | ||
{'move_name': False} | ||
) | ||
return super().unlink() | ||
|
||
@api.one | ||
@api.depends( | ||
'invoice_line_ids.price_total', | ||
|
@@ -128,6 +217,7 @@ def write(self, values): | |
'date_invoice', | ||
'type') | ||
def _compute_amount(self): | ||
self.fiscal_document_id._compute_amount() | ||
for inv_line in self.invoice_line_ids: | ||
if inv_line.cfop_id: | ||
if inv_line.cfop_id.finance_move: | ||
|
@@ -137,7 +227,10 @@ def _compute_amount(self): | |
self.amount_untaxed += inv_line.price_subtotal | ||
self.amount_tax += inv_line.price_tax | ||
|
||
self.amount_total = self.amount_untaxed + self.amount_tax | ||
self.amount_total = ( | ||
self.amount_untaxed + self.amount_tax - | ||
self.amount_tax_withholding) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the point of setting this value in self.amount_total if you're going to change this in the very next line of code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I need remove line 234 |
||
|
||
amount_total_company_signed = self.amount_total | ||
amount_untaxed_signed = self.amount_untaxed | ||
if (self.currency_id and self.company_id and | ||
|
@@ -182,6 +275,26 @@ def tax_line_move_line_get(self): | |
# new_tax_lines_dict.append(new_tax) | ||
return tax_lines_dict | ||
|
||
@api.multi | ||
def finalize_invoice_move_lines(self, move_lines): | ||
lines = super().finalize_invoice_move_lines(move_lines) | ||
dummy_doc = self.env.ref('l10n_br_fiscal.fiscal_document_dummy') | ||
financial_lines = [ | ||
l for l in lines if l[2]['account_id'] == self.account_id.id] | ||
|
||
count = 1 | ||
|
||
for l in financial_lines: | ||
if l[2]['debit'] or l[2]['credit']: | ||
if self.fiscal_document_id != dummy_doc: | ||
l[2]['name'] = '{}/{}-{}'.format( | ||
self.fiscal_number, | ||
count, | ||
len(financial_lines) | ||
) | ||
count += 1 | ||
return lines | ||
|
||
@api.multi | ||
def get_taxes_values(self): | ||
# uncomment these lines | ||
|
@@ -229,3 +342,115 @@ def get_taxes_values(self): | |
tax_grouped[key]['amount'] += val['amount'] | ||
tax_grouped[key]['base'] += round_curr(val['base']) | ||
return tax_grouped | ||
|
||
@api.onchange('fiscal_operation_id') | ||
def _onchange_fiscal_operation_id(self): | ||
super()._onchange_fiscal_operation_id() | ||
if self.fiscal_operation_id and self.fiscal_operation_id.journal_id: | ||
self.journal_id = self.fiscal_operation_id.journal_id | ||
|
||
@api.multi | ||
def open_fiscal_document(self): | ||
if self.env.context.get('type', '') == 'out_invoice': | ||
action = self.env.ref( | ||
'l10n_br_account.fiscal_invoice_out_action').read()[0] | ||
elif self.env.context.get('type', '') == 'in_invoice': | ||
action = self.env.ref( | ||
'l10n_br_account.fiscal_invoice_in_action').read()[0] | ||
else: | ||
action = self.env.ref( | ||
'l10n_br_account.fiscal_invoice_all_action').read()[0] | ||
form_view = [ | ||
(self.env.ref('l10n_br_account.fiscal_invoice_form').id, 'form')] | ||
if 'views' in action: | ||
action['views'] = form_view + [ | ||
(state, view) for state, view in action['views'] | ||
if view != 'form'] | ||
else: | ||
action['views'] = form_view | ||
action['res_id'] = self.id | ||
return action | ||
|
||
@api.multi | ||
def action_date_assign(self): | ||
"""Usamos esse método para definir a data de emissão do documento | ||
fiscal e numeração do documento fiscal para ser usado nas linhas | ||
dos lançamentos contábeis.""" | ||
super().action_date_assign() | ||
dummy_doc = self.env.ref('l10n_br_fiscal.fiscal_document_dummy') | ||
for invoice in self: | ||
if invoice.fiscal_document_id != dummy_doc: | ||
if invoice.issuer == DOCUMENT_ISSUER_COMPANY: | ||
invoice.fiscal_document_id.document_date() | ||
invoice.fiscal_document_id.document_number() | ||
invoice.fiscal_number = invoice.fiscal_document_id.number | ||
else: | ||
invoice.fiscal_document_id.number = invoice.fiscal_number | ||
|
||
@api.multi | ||
def action_move_create(self): | ||
result = super().action_move_create() | ||
dummy_doc = self.env.ref('l10n_br_fiscal.fiscal_document_dummy') | ||
self.mapped('fiscal_document_id').filtered( | ||
lambda d: d != dummy_doc).action_document_confirm() | ||
return result | ||
Comment on lines
+391
to
+396
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. O lançamento contábil só deve ser realizado caso o documento fiscal seja autorizado. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. O fluxo contábil não alterou neste PR, para enviar o documento fiscal você precisa ter a account.invoice confirmada primeiro e com o status em aberto para ser enviado os valores e duplicatas por exemplo. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Atualmente você consegue até fazer um documento fiscal, transmitir e depois confirmar a fatura, mas isso depende do usuário depois confirmar a fatura. Eu diria que hoje pode ser configurado para o lançamento não ser postado, ficar como draft e depois que confirmado ele ser postado. Eu queria melhorar esse fluxo depois do PR #1125 porque umas das razões de validar a fatura e ter os lançamentos é pegar os vencimentos para incluir no documento fiscal. que trabalhando no PR de lançamentos podemos mudar isso, mas hoje dá para deixar os lançamentos da fatura não postado e posta-lo depois que o documento fiscal é transmitido. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mileo para melhorar o fluxo, quando uma fatura é criada é gerada a contabilização mas não é postada, e quando é transmitida e autorizada é postada a contabilização, desta forma resolvemos o problema de ser uma contabilização postada somente quando o documento fiscal é autorizado. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. eu tb vi isso com o @renatonlima e accredito que seja a melhor forma: o move do invoice fica de rascunho ate vc transmitir e depois de transmitir ele é postado. Nisso o move ja pega o numero dele (porque senao daria problema se vc deixar varias Nfe's e transmitir elas numa ordem diferente) e tb isso faz algum checagem basica que o lançamento é possivel e depois ele é apenas postado. Tb o codigo disso fica razoavel. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ping @mileo There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Vou revisar esse e o dos totais hj There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No #806 foi implementado boa parte desses fluxos, de confirmação do lançamento contábil e mais uns outros detalhes. Tudo bem que tinha aquela funcionalidade embutida do motor de lançamentos contábeis, vou dar uma relembrada do que foi feito lá e ver lembro e mais algum detalhe e comento aqui. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Eu havia visto o PR #806 e ele mantém o fluxo separado entre documento fiscal e invoice e o fluxo esta errado porque edita o documento fiscal e nesse PR eu uso o recurso do "inherits" e o documento fiscal através da fatura (account.invoice). Um outro ponto, sobre o motor de lançamentos contábeis eu não vejo lógica em fazer uma implementação tão grande e complexa já que o módulo do core do Odoo já faz a contabilização e com pouco código localizado é possível fazer a contabilização de partida dobrada 100% compatível com a legislação brasileira e mantendo a compatibilidade da localização com outros módulos do eco-sistema. Esse PR também esta com algumas implementações do PR #820, que ainda tem bastante coisas para revisar e corrigir, depois deste PR e do PR dos eventos eu pretendo trabalhar no PR #820, mas antes de lidar com as particularidades dos recebiveis/pagaveis é importante a gente acertar o fluxo da edição do documento fiscal/fatura. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mileo eu acho que na v12 a gente tem um l10n_br_base e l10n_br_fiscal limpo suficente para vc usar caso a KMEE quiser partir para essa forma de fazer de novo como vcs haviam feito no fork das v10 e v11 de vcs. Ai ja seria melhor do que dar uma viajada 100% fora da OCA como era o caso desse fork v10/v11. Se precisar de alguns hooks para facilitar isso, na medida do possivel a gente bote botar alguns. Mas nao ao ponto de copiar 700 linhas de codigo do account com copyright Odoo SA no modulo fiscal como no PR 820 e que se torna 100% inutil logo que vc instala o modulo account e usa esse tipo de PR aqui que integra bem o fiscal com o account. E mesmo que seja legitimo refazer milhares de coisas fora do account, eu nao acho que tem lugar para esse tipo de implementaçao na OCA. Em outros paises tem pessoas que fizeram contabilidade ou payroll 100% fora dos modulos do Odoo, usando apenas o Odoo como framework. Porem isso nunca entrou na OCA em repo nehnum porque a filosofia da OCA é de se integrar com os modulos Odoo e ecosistema OCA e como o Renato falou nao é tao dificil, pelo menos a gente ta conseguindo legal. Se essa forma de vcs era tao boa assim, fica porem a pergunta: porque sera que esse fork da Kmee não vingou? Cade o roadmap a enchação de boca toda para ficar falando desse fork de como isso era tao melhor, onde isso vai e cade os "good first issues" para a turma do Telegram ir resolver nesse fork ai? Como ta a turma que te seguiu nessa hoje? Ta com suporte legal? Tem muitos iniciantes ajudando ai? Ou talvez nao era tao bom assim... Cada uma tire as conclusões. |
||
|
||
@api.multi | ||
def action_invoice_draft(self): | ||
dummy_doc = self.env.ref('l10n_br_fiscal.fiscal_document_dummy') | ||
for i in self.filtered(lambda d: d.fiscal_document_id != dummy_doc): | ||
if i.state_edoc == SITUACAO_EDOC_CANCELADA: | ||
if i.issuer == DOCUMENT_ISSUER_COMPANY: | ||
raise UserError(_( | ||
"You can't set this document number: {} to draft " | ||
"because this document is cancelled in SEFAZ".format( | ||
i.fiscal_number))) | ||
if i.state_edoc != SITUACAO_EDOC_EM_DIGITACAO: | ||
i.fiscal_document_id.action_document_back2draft() | ||
return super().action_invoice_draft() | ||
|
||
@api.multi | ||
def action_document_send(self): | ||
dummy_doc = self.env.ref('l10n_br_fiscal.fiscal_document_dummy') | ||
invoices = self.filtered(lambda d: d.fiscal_document_id != dummy_doc) | ||
if invoices: | ||
invoices.mapped('fiscal_document_id').action_document_send() | ||
for invoice in invoices: | ||
invoice.move_id.post(invoice=invoice) | ||
|
||
@api.multi | ||
def action_document_cancel(self): | ||
dummy_doc = self.env.ref('l10n_br_fiscal.fiscal_document_dummy') | ||
for i in self.filtered(lambda d: d.fiscal_document_id != dummy_doc): | ||
if i.state_edoc == SITUACAO_EDOC_AUTORIZADA: | ||
return i.fiscal_document_id.action_document_cancel() | ||
|
||
@api.multi | ||
def action_document_correction(self): | ||
dummy_doc = self.env.ref('l10n_br_fiscal.fiscal_document_dummy') | ||
for i in self.filtered(lambda d: d.fiscal_document_id != dummy_doc): | ||
if i.state_edoc in SITUACAO_EDOC_AUTORIZADA: | ||
if i.issuer == DOCUMENT_ISSUER_COMPANY: | ||
return i.fiscal_document_id.action_document_correction() | ||
else: | ||
raise UserError(_( | ||
"You cannot create a fiscal correction document if " | ||
"this fical document you are not the document issuer" | ||
)) | ||
|
||
@api.multi | ||
def action_document_back2draft(self): | ||
"""Sets fiscal document to draft state and cancel and set to draft | ||
the related invoice for both documents remain equivalent state.""" | ||
dummy_doc = self.env.ref('l10n_br_fiscal.fiscal_document_dummy') | ||
for i in self.filtered(lambda d: d.fiscal_document_id != dummy_doc): | ||
i.action_cancel() | ||
i.action_invoice_draft() | ||
|
||
def view_xml(self): | ||
self.ensure_one() | ||
return self.fiscal_document_id.view_xml() | ||
|
||
def view_pdf(self): | ||
self.ensure_one() | ||
return self.fiscal_document_id.view_pdf() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic could be more clear here. I would suggest:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created this logic using two ifs because there are invoice without CFOP like service invoices or an account invoice without fiscal document (only linked with dummy fiscal document). Your suggestion will not work cases...