[12.0][REF] l10n_br_nfe: fiscal document module integration standard document line
Pessoal,
Recentemente eu havia feito um PR #1924 para padronizar a integração entre os campos dos objetos de negócio e os objetos da NF-e, Eu estou criando esse PR para fazer a mesma padronização no objeto que integra a linha do documento fiscal com a linha da NF-e.
Basicamente Esse PR implementa a mesma padronização:
class NFeLine():
# class metadata
##########################
# NF-e spec related fields
##########################
######################################
# NF-e tag: prod
# Grupo I. Produtos e Serviços da NF-e
######################################
# nfe40_cProd = fields.Char(related="product_id.default_code")
nfe40_cEAN = fields.Char(related="product_id.barcode")
# nfe40_xProd = fields.Char(related="name") TODO
nfe40_NCM = fields.Char(related="ncm_id.code_unmasked")
# NVE TODO
nfe40_CEST = fields.Char(related="cest_id.code_unmasked")
# indEscala TODO
# CNPJFab TODO
# cBenef TODO
##########################
# NF-e tag: prod
# Compute Methods
##########################
def _compute_nfe40_prod(self, xsd_fields, class_obj, export_dict):
...
##########################
# NF-e tag: id
# Inverse Methods
##########################
def _inverse_nfe40_prod(self):
...
################################
# Framework Spec model's methods
################################
def _export_fields_nfe_40_prod(self, xsd_fields, class_obj, export_dict):
...
################################
# Business Model Methods
################################
def _onchange_product_id_fiscal(self):
...
Alterações
A mudança considerável neste PR foi o refatoramento dos métodos _export_fields e _export_field que são método utilizados para remover tags e alterar os valores de algumas tags. Esses métodos estavam muito grandes o que iria gerar uma dificuldade em mante-los futuramente, hoje basicamente esses métodos são responsáveis:
- _export_fields: controlar o fluxo das tags a ser removidas ou substituídas em determinadas condições.
- _export_field: Atribuir ou remover valores das tags de dados.
Para melhorar esse implementação eu removi o método _export_field pois apesar de ser util esse método teria algumas limitações para reutiliza-lo através de outros métodos. Como o objeto da linha da NF-e tem uma complexidade maior o método que deveria ser utilizado seria o _export_fields pois com esse método é possível controlar o fluxo das tags exportadas e atribuir ou remover valores de qualquer tag. Eu defini uma convenção para quebrar a lógica dentro desses métodos em outros método menores, nessa convenção caso você queria alterar algum valor de alguma tag você deve criar um método com a mesma assinatura do _export_fields mas com o nome _export_fields_CLASS_SPEC, por exemplo: se quiser alterar algum valor dentro da tag prod representada pelo objeto nfe.40.prod, você deve criar um método:
def _export_fields_nfe_40_prod(self, xsd_fields, class_obj, export_dict):
Para implementar essa convenção eu implementei a seguinte lógica no método método herdado pelo spec model:
def _export_fields(self, xsd_fields, class_obj, export_dict):
"""Método utilizado para mapear os campos do documento fiscal com as
tags da NF-e. Esse método"""
xsd_fields = [i for i in xsd_fields]
try:
class_name = class_obj._name.replace(".", "_")
export_method_name = "_export_fields_%s" % class_name
if hasattr(self, export_method_name):
export_method = getattr(self, export_method_name)
export_method(xsd_fields, class_obj, export_dict)
except AttributeError:
pass
return super()._export_fields(xsd_fields, class_obj, export_dict)
Eu diria que essa funcionalidade poderia ser transferida para o spec_model no spec_driven_model adicionando neste método essa lógica.
Outras alterações em andamento
- Organização das tags na sequencia que são definidas no XSD do schema da NF-e;
- Criar linhas de NF-e sem o produto (product_id) apenas preenchendo o código (cProd) e descrição (xProd);
- Adicionado mapeamento para os campos vSeg;
- Adicionado mapeamento para os campos vOutro;
- Adicionar testes.
Devido a arquitetura da localização por essas alterações serem feitas no módulo l10n_br_nfe essas mudanças serão facilmente portadas para a versão 14.0 para mante-las compatíveis. Com o tempo a P&D vai naturalmente ser focada nas versões 14.0, 15.0 e 16.0.
@rvalyi
Seria bom melhorar a mensagem desse log:

Poderia trazer o nome do objeto, o nome campo, o comodel_name do campo (caso seja relacional) e o valor que esta sendo importado para esse campo (os valores dos campos _nfe_search_keys) ou o domínio que foi utilizado na busca, para ficar mais clara a mensagem.
@rvalyi,
Um caso que me deparei também foi com esse método na classe NFeLine:
def _export_float_monetary(self, field_name, member_spec, class_obj, xsd_required):
if not self[field_name] and not xsd_required:
if not (
class_obj._name == "nfe.40.imposto" and field_name == "nfe40_vTotTrib"
) and not (class_obj._name == "nfe.40.fat"):
self[field_name] = False
return False
return super()._export_float_monetary(
field_name, member_spec, class_obj, xsd_required
)
Os campos moeda por padrão no Odoo não ficam nulos tendo o valor padrão 0,00 esse método _export_float_monetary remove as tags que tenha xsd_required=False e não seja campos dos objetos: nfe.40.imposto, nfe.40.fat.
Eu removi esse método e tive alguns erros de validação no XML, porque o valor de alguns campos era 0.00 e esse campo era xsd_required=False e esse campo foi exportado para a tag da NF-e
Na minha opinião no método _export_float_monetary do módulo spec_driven_model deveria ter essa lógica, porque por exemplo, eu vi em alguns XMLs de NF-e geradas que existem tags que estão zeradas que não precisaria ser informada, por exemplo:
A tag vICMSUFRemet esta definida no XSD como xsd_required=False mas esta sendo exportada no XML:

Esse campo assim como os: vICMSDeson, vFCPUFDest, vICMSUFDest não precisariam ser exportados, só ocupa espaço de armazenamento, apesar de ser pouco, mas levando em consideração milhares de NF-es geradas gera um impacto considerável.
Apenas para deixar explicito, com o PR atual, temos 2 esses diffs em duas NFe's de teste. Na esquerda como ficou com o PR e na direita como deveria ser:

This PR has the approved label and has been created more than 5 days ago. It should therefore be ready to merge by a maintainer (or a PSC member if the concerned addon has no declared maintainer). 🤖
/ocabot merge major
This PR looks fantastic, let's merge it! Prepared branch 12.0-ocabot-merge-pr-2000-by-rvalyi-bump-major, awaiting test results.
@rvalyi your merge command was aborted due to failed check(s), which you can inspect on this commit of 12.0-ocabot-merge-pr-2000-by-rvalyi-bump-major.
After fixing the problem, you can re-issue a merge command. Please refrain from merging manually as it will most probably make the target branch red.
/ocabot merge major
Hey, thanks for contributing! Proceeding to merge this for you. Prepared branch 12.0-ocabot-merge-pr-2000-by-rvalyi-bump-major, awaiting test results.
Congratulations, your PR was merged at ca2b008fbfe08a65f057394a6baf7dc39c021844. Thanks a lot for contributing to OCA. ❤️