diff --git a/br_account_einvoice/models/invoice_eletronic.py b/br_account_einvoice/models/invoice_eletronic.py index 4335ca2c7..e5ff86e86 100644 --- a/br_account_einvoice/models/invoice_eletronic.py +++ b/br_account_einvoice/models/invoice_eletronic.py @@ -87,7 +87,7 @@ class InvoiceEletronic(models.Model): partner_shipping_id = fields.Many2one( 'res.partner', string=u'Entrega', readonly=True, states=STATE) payment_term_id = fields.Many2one( - 'account.payment.term', string=u'Forma pagamento', + 'account.payment.term', string=u'Condição pagamento', readonly=True, states=STATE) fiscal_position_id = fields.Many2one( 'account.fiscal.position', string=u'Posição Fiscal', @@ -436,7 +436,7 @@ def unlink(self): def log_exception(self, exc): self.codigo_retorno = -1 - self.mensagem_retorno = exc.message + self.mensagem_retorno = str(exc) def _get_state_to_send(self): return ('draft',) diff --git a/br_base/models/res_partner.py b/br_base/models/res_partner.py index 48e90e5a7..66113dd08 100644 --- a/br_base/models/res_partner.py +++ b/br_base/models/res_partner.py @@ -198,47 +198,41 @@ def action_check_sefaz(self): resposta = consulta_cadastro(certificado, obj=obj, ambiente=1, estado=self.state_id.ibge_code) - obj = resposta['object'] - if "Body" in dir(obj) and \ - "consultaCadastro2Result" in dir(obj.Body): - info = obj.Body.consultaCadastro2Result.retConsCad.infCons - if info.cStat == 111 or info.cStat == 112: - if not self.inscr_est: - self.inscr_est = info.infCad.IE - if not self.cnpj_cpf: - self.cnpj_cpf = info.infCad.IE - - def get_value(obj, prop): - if prop not in dir(obj): - return None - return getattr(obj, prop) - self.legal_name = get_value(info.infCad, 'xNome') - if "ender" not in dir(info.infCad): - return - cep = get_value(info.infCad.ender, 'CEP') or '' - self.zip = str(cep).zfill(8) if cep else '' - self.street = get_value(info.infCad.ender, 'xLgr') - self.number = get_value(info.infCad.ender, 'nro') - self.street2 = get_value(info.infCad.ender, 'xCpl') - self.district = get_value(info.infCad.ender, 'xBairro') - cMun = get_value(info.infCad.ender, 'cMun') - xMun = get_value(info.infCad.ender, 'xMun') - city = None - if cMun: - city = self.env['res.state.city'].search( - [('ibge_code', '=', str(cMun)[2:]), - ('state_id', '=', self.state_id.id)]) - if not city and xMun: - city = self.env['res.state.city'].search( - [('name', 'ilike', xMun), - ('state_id', '=', self.state_id.id)]) - if city: - self.city_id = city.id - else: - msg = "%s - %s" % (info.cStat, info.xMotivo) - raise UserError(msg) + info = resposta['object'].getchildren()[0].infCons + if info.cStat == 111 or info.cStat == 112: + if not self.inscr_est: + self.inscr_est = info.infCad.IE.text + if not self.cnpj_cpf: + self.cnpj_cpf = info.infCad.IE.text + + def get_value(obj, prop): + if prop not in dir(obj): + return None + return getattr(obj, prop) + self.legal_name = get_value(info.infCad, 'xNome') + if "ender" not in dir(info.infCad): + return + cep = get_value(info.infCad.ender, 'CEP') or '' + self.zip = str(cep).zfill(8) if cep else '' + self.street = get_value(info.infCad.ender, 'xLgr') + self.number = get_value(info.infCad.ender, 'nro') + self.street2 = get_value(info.infCad.ender, 'xCpl') + self.district = get_value(info.infCad.ender, 'xBairro') + cMun = get_value(info.infCad.ender, 'cMun') + xMun = get_value(info.infCad.ender, 'xMun') + city = None + if cMun: + city = self.env['res.state.city'].search( + [('ibge_code', '=', str(cMun)[2:]), + ('state_id', '=', self.state_id.id)]) + if not city and xMun: + city = self.env['res.state.city'].search( + [('name', 'ilike', xMun), + ('state_id', '=', self.state_id.id)]) + if city: + self.city_id = city.id else: - raise UserError(u"Nenhuma resposta - verificou se seu \ - certificado é válido?") + msg = "%s - %s" % (info.cStat, info.xMotivo) + raise UserError(msg) else: raise UserError(u'Preencha o estado e o CNPJ para pesquisar') diff --git a/br_base/tests/xml/consulta_cadastro.xml b/br_base/tests/xml/consulta_cadastro.xml index 5a5c1c957..df99f3805 100644 --- a/br_base/tests/xml/consulta_cadastro.xml +++ b/br_base/tests/xml/consulta_cadastro.xml @@ -1,46 +1,35 @@ - - - - - 42 - 2.00 - - - - - - - SC_NFE_PL_008i2 - 111 - Consulta cadastro com uma ocorrencia - SC - 22814429000155 - 2016-11-03T08:33:34-02:00 - 35 - - 112632165 - 22814429000155 - SC - 1 - 1 - 4 - Empresa de Teste SC - SIMPLES NACIONAL - 4789099 - 2014-11-03 - 2014-11-03 - - RUA PADRE JOAO - 000 - SL - Centro - 4205407 - FLORIANOPOLIS - 88032050 - - - - - - - + + + + SC_NFE_PL_008i2 + 111 + Consulta cadastro com uma ocorrencia + SC + 22814429000155 + 2016-11-03T08:33:34-02:00 + 35 + + 112632165 + 22814429000155 + SC + 1 + 1 + 4 + Empresa de Teste SC + SIMPLES NACIONAL + 4789099 + 2014-11-03 + 2014-11-03 + + RUA PADRE JOAO + 000 + SL + Centro + 4205407 + FLORIANOPOLIS + 88032050 + + + + + diff --git a/br_nfe/__manifest__.py b/br_nfe/__manifest__.py index f3edcbd80..b9814f78b 100644 --- a/br_nfe/__manifest__.py +++ b/br_nfe/__manifest__.py @@ -17,6 +17,7 @@ ], 'depends': [ 'br_account_einvoice', + 'br_account_payment' ], 'external_dependencies': { 'python': [ @@ -34,6 +35,7 @@ 'views/invoice_eletronic_item.xml', 'views/inutilized_nfe.xml', 'views/br_nfe.xml', + 'views/payment_mode.xml', 'reports/br_nfe_reports.xml', 'wizard/cancel_nfe.xml', 'wizard/carta_correcao_eletronica.xml', diff --git a/br_nfe/models/__init__.py b/br_nfe/models/__init__.py index d6a5fee59..6549f108f 100644 --- a/br_nfe/models/__init__.py +++ b/br_nfe/models/__init__.py @@ -11,3 +11,4 @@ from . import res_partner from . import invoice_eletronic_item from . import inutilized_nfe +from . import payment_mode diff --git a/br_nfe/models/account_fiscal_position.py b/br_nfe/models/account_fiscal_position.py index 53bbaa62d..c7cc05c36 100644 --- a/br_nfe/models/account_fiscal_position.py +++ b/br_nfe/models/account_fiscal_position.py @@ -19,6 +19,7 @@ class AccountFiscalPositionTemplate(models.Model): ('2', u'Operação não presencial, pela Internet'), ('3', u'Operação não presencial, Teleatendimento'), ('4', u'NFC-e em operação com entrega em domicílio'), + ('5', u'Operação presencial, fora do estabelecimento'), ('9', u'Operação não presencial, outros'), ], u'Tipo de operação', help=u'Indicador de presença do comprador no\n' @@ -48,6 +49,7 @@ class AccountFiscalPosition(models.Model): ('2', u'Operação não presencial, pela Internet'), ('3', u'Operação não presencial, Teleatendimento'), ('4', u'NFC-e em operação com entrega em domicílio'), + ('5', u'Operação presencial, fora do estabelecimento'), ('9', u'Operação não presencial, outros'), ], u'Tipo de operação', help=u'Indicador de presença do comprador no\n' diff --git a/br_nfe/models/account_invoice.py b/br_nfe/models/account_invoice.py index 3963c76b3..1cd79f2cc 100644 --- a/br_nfe/models/account_invoice.py +++ b/br_nfe/models/account_invoice.py @@ -96,6 +96,7 @@ def _return_pdf_invoice(self, doc): def _prepare_edoc_vals(self, inv): res = super(AccountInvoice, self)._prepare_edoc_vals(inv) + res['payment_mode_id'] = inv.payment_mode_id.id res['ind_pres'] = inv.fiscal_position_id.ind_pres res['finalidade_emissao'] = inv.fiscal_position_id.finalidade_emissao res['informacoes_legais'] = inv.fiscal_comment @@ -148,7 +149,7 @@ def _prepare_edoc_vals(self, inv): count = 1 for parcela in inv.receivable_move_line_ids.sorted(lambda x: x.name): duplicatas.append((0, None, { - 'numero_duplicata': "%s/%02d" % (inv.internal_number, count), + 'numero_duplicata': "%03d" % count, 'data_vencimento': parcela.date_maturity, 'valor': parcela.credit or parcela.debit, })) diff --git a/br_nfe/models/inutilized_nfe.py b/br_nfe/models/inutilized_nfe.py index 66f460c71..3c3a21924 100644 --- a/br_nfe/models/inutilized_nfe.py +++ b/br_nfe/models/inutilized_nfe.py @@ -44,6 +44,8 @@ class InutilizedNfe(models.Model): string=u'Modelo', required=True, readonly=True, states=STATE) serie = fields.Many2one('br_account.document.serie', string=u'Série', required=True, readonly=True, states=STATE) + code = fields.Char(string="Código", size=10) + motive = fields.Char(string="Motivo", size=300) @api.model def create(self, vals): @@ -105,21 +107,22 @@ def _prepare_obj(self, company, estado, ambiente): 'justificativa': self.justificativa, } - def _handle_resposta(self, resposta): - self._create_attachment('inutilizacao-envio', self, - resposta['sent_xml']) - self._create_attachment('inutilizacao-recibo', self, - resposta['received_xml']) - if hasattr(resposta['object'].Body, 'Fault'): - raise UserError(u'Não foi possível concluir a operação.') - inf_inut = resposta['object'].Body.nfeInutilizacaoNF2Result.\ - retInutNFe.infInut + def _handle_response(self, response): + self._create_attachment( + 'inutilizacao-envio', self, response['sent_xml']) + self._create_attachment( + 'inutilizacao-recibo', self, response['received_xml']) + inf_inut = response['object'].getchildren()[0].infInut status = inf_inut.cStat if status == 102: - self.state = 'done' + self.write({ + 'state': 'done', + 'code': inf_inut.cStat, + 'motive': inf_inut.xMotivo + }) else: - self.state = 'error' - self.erro = inf_inut.xMotivo + msg = '%s - %s' % (inf_inut.cStat, inf_inut.xMotivo) + raise UserError(msg) def send_sefaz(self): company = self.env.user.company_id @@ -134,8 +137,8 @@ def send_sefaz(self): certificado = Certificado(cert_pfx, company.nfe_a1_password) resposta = inutilizar_nfe(certificado, obj=obj, estado=estado, - ambiente=int(ambiente)) - self._handle_resposta(resposta=resposta) + ambiente=int(ambiente), modelo=obj['modelo']) + self._handle_response(response=resposta) @api.multi def action_send_inutilization(self): diff --git a/br_nfe/models/invoice_eletronic.py b/br_nfe/models/invoice_eletronic.py index caf0210a5..a4605a569 100644 --- a/br_nfe/models/invoice_eletronic.py +++ b/br_nfe/models/invoice_eletronic.py @@ -52,6 +52,9 @@ def generate_correction_letter(self): "context": {'default_eletronic_doc_id': self.id}, } + payment_mode_id = fields.Many2one( + 'payment.mode', string='Modo de Pagamento', + readonly=True, states=STATE) state = fields.Selection(selection_add=[('denied', 'Denegado')]) ambiente_nfe = fields.Selection( string=u"Ambiente NFe", related="company_id.tipo_ambiente", @@ -67,6 +70,7 @@ def generate_correction_letter(self): ('2', u'Operação não presencial, pela Internet'), ('3', u'Operação não presencial, Teleatendimento'), ('4', u'NFC-e em operação com entrega em domicílio'), + ('5', u'Operação presencial, fora do estabelecimento'), ('9', u'Operação não presencial, outros'), ], u'Indicador de Presença', readonly=True, states=STATE, required=False, help=u'Indicador de presença do comprador no\n' @@ -98,10 +102,12 @@ def generate_correction_letter(self): # Transporte modalidade_frete = fields.Selection( - [('0', u'0 - Emitente'), - ('1', u'1 - Destinatário'), - ('2', u'2 - Terceiros'), - ('9', u'9 - Sem Frete')], + [('0', u'0 - Contratação do Frete por conta do Remetente (CIF)'), + ('1', u'1 - Contratação do Frete por conta do Destinatário (FOB)'), + ('2', u'2 - Contratação do Frete por conta de Terceiros'), + ('3', u'3 - Transporte Próprio por conta do Remetente'), + ('4', u'4 - Transporte Próprio por conta do Destinatário'), + ('9', u'9 - Sem Ocorrência de Transporte')], string=u'Modalidade do frete', default="9", readonly=True, states=STATE) transportadora_id = fields.Many2one( @@ -249,7 +255,7 @@ def _prepare_eletronic_invoice_item(self, item, invoice): product_uom_format = '{0:.' + str(product_uom_precision) + 'f}' prod = { 'cProd': item.product_id.default_code, - 'cEAN': item.product_id.barcode or '', + 'cEAN': item.product_id.barcode or 'SEM GTIN', 'xProd': item.product_id.with_context( display_default_code=False).name_get()[0][1], 'NCM': re.sub('[^0-9]', '', item.ncm or '')[:8], @@ -259,7 +265,7 @@ def _prepare_eletronic_invoice_item(self, item, invoice): 'qCom': product_uom_format.format(item.quantidade), 'vUnCom': product_price_format.format(item.preco_unitario), 'vProd': "%.02f" % (item.preco_unitario * item.quantidade), - 'cEANTrib': item.product_id.barcode or '', + 'cEANTrib': item.product_id.barcode or 'SEM GTIN', 'uTrib': '{:.6}'.format(item.uom_id.name or ''), 'qTrib': product_uom_format.format(item.quantidade), 'vUnTrib': product_price_format.format(item.preco_unitario), @@ -526,14 +532,18 @@ def _prepare_eletronic_invoice_values(self): 'vBC': "%.02f" % self.valor_bc_icms, 'vICMS': "%.02f" % self.valor_icms, 'vICMSDeson': '0.00', + 'vFCP': '0.00', 'vBCST': "%.02f" % self.valor_bc_icmsst, 'vST': "%.02f" % self.valor_icmsst, + 'vFCPST': '0.00', + 'vFCPSTRet': '0.00', 'vProd': "%.02f" % self.valor_bruto, 'vFrete': "%.02f" % self.valor_frete, 'vSeg': "%.02f" % self.valor_seguro, 'vDesc': "%.02f" % self.valor_desconto, 'vII': "%.02f" % self.valor_ii, 'vIPI': "%.02f" % self.valor_ipi, + 'vIPIDevol': '0.00', 'vPIS': "%.02f" % self.valor_pis, 'vCOFINS': "%.02f" % self.valor_cofins, 'vOutro': "%.02f" % self.valor_despesas, @@ -612,15 +622,18 @@ def _prepare_eletronic_invoice_values(self): cobr = { 'fat': { 'nFat': self.numero_fatura or '', - 'vOrig': "%.02f" % self.fatura_bruto - if self.fatura_bruto else '', - 'vDesc': "%.02f" % self.fatura_desconto - if self.fatura_desconto else '', - 'vLiq': "%.02f" % self.fatura_liquido - if self.fatura_liquido else '', + 'vOrig': "%.02f" % ( + self.fatura_liquido + self.fatura_desconto), + 'vDesc': "%.02f" % self.fatura_desconto, + 'vLiq': "%.02f" % self.fatura_liquido, }, 'dup': duplicatas } + pag = [{ + 'indPag': self.payment_term_id.indPag or '0', + 'tPag': self.payment_mode_id.tipo_pagamento or '15', + 'vPag': "%.02f" % self.valor_final + }] if self.informacoes_complementares: self.informacoes_complementares = self.informacoes_complementares.\ replace('\n', '
') @@ -646,6 +659,7 @@ def _prepare_eletronic_invoice_values(self): 'autXML': autorizados, 'detalhes': eletronic_items, 'total': total, + 'pag': pag, 'transp': transp, 'infAdic': infAdic, 'exporta': exporta, @@ -662,6 +676,7 @@ def _prepare_lote(self, lote, nfe_values): 'indSinc': 0, 'estado': self.company_id.partner_id.state_id.ibge_code, 'ambiente': 1 if self.ambiente == 'producao' else 2, + 'modelo': self.model, 'NFes': [{ 'infNFe': nfe_values }] @@ -752,12 +767,12 @@ def action_send_eletronic_invoice(self): resposta_recibo = None resposta = autorizar_nfe(certificado, **lote) - retorno = resposta['object'].Body.nfeAutorizacaoLoteResult - retorno = retorno.getchildren()[0] + retorno = resposta['object'].getchildren()[0] if retorno.cStat == 103: obj = { 'estado': self.company_id.partner_id.state_id.ibge_code, 'ambiente': 1 if self.ambiente == 'producao' else 2, + 'modelo': '55', 'obj': { 'ambiente': 1 if self.ambiente == 'producao' else 2, 'numero_recibo': retorno.infRec.nRec @@ -768,8 +783,7 @@ def action_send_eletronic_invoice(self): while True: time.sleep(2) resposta_recibo = retorno_autorizar_nfe(certificado, **obj) - retorno = resposta_recibo['object'].Body.\ - nfeRetAutorizacaoLoteResult.retConsReciNFe + retorno = resposta_recibo['object'].getchildren()[0] if retorno.cStat != 105: break @@ -867,72 +881,51 @@ def action_cancel_document(self, context=None, justificativa=None): cert_pfx = base64.decodestring(cert) certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password) - consulta = { + tz = pytz.timezone(self.env.user.partner_id.tz) or pytz.utc + dt_evento = datetime.utcnow() + dt_evento = pytz.utc.localize(dt_evento).astimezone(tz) + + id_canc = "ID110111%s%02d" % ( + self.chave_nfe, self.sequencial_evento) + cancelamento = { + 'idLote': self.id, 'estado': self.company_id.state_id.ibge_code, 'ambiente': 2 if self.ambiente == 'homologacao' else 1, - 'chave_nfe': self.chave_nfe, + 'eventos': [{ + 'Id': id_canc, + 'cOrgao': self.company_id.state_id.ibge_code, + 'tpAmb': 2 if self.ambiente == 'homologacao' else 1, + 'CNPJ': re.sub('[^0-9]', '', self.company_id.cnpj_cpf), + 'chNFe': self.chave_nfe, + 'dhEvento': dt_evento.strftime('%Y-%m-%dT%H:%M:%S-03:00'), + 'nSeqEvento': self.sequencial_evento, + 'nProt': self.protocolo_nfe, + 'xJust': justificativa, + 'tpEvento': '110111', + 'descEvento': 'Cancelamento', + }], + 'modelo': self.model, } - - resp = consultar_protocolo_nfe(certificado, **consulta) - # Retorno específico para o estado da Bahia - if self.company_id.state_id.ibge_code == '29': - retorno_consulta = \ - resp['object'].Body.nfeConsultaNFResult.retConsSitNFe - else: - retorno_consulta = \ - resp['object'].Body.nfeConsultaNF2Result.retConsSitNFe - if retorno_consulta.cStat == 101: + resp = recepcao_evento_cancelamento(certificado, **cancelamento) + resposta = resp['object'].getchildren()[0] + if resposta.cStat == 128 and \ + resposta.retEvento.infEvento.cStat in (135, 136, 155): self.state = 'cancel' - self.codigo_retorno = retorno_consulta.cStat - self.mensagem_retorno = retorno_consulta.xMotivo + self.codigo_retorno = resposta.retEvento.infEvento.cStat + self.mensagem_retorno = resposta.retEvento.infEvento.xMotivo self.sequencial_evento += 1 - # Retorno específico para o estado da Bahia - if self.company_id.state_id.ibge_code == '29': - resp['received_xml'] = etree.tostring( - retorno_consulta.procEventoNFe.retEvento) - else: - resp['received_xml'] = etree.tostring( - retorno_consulta.retCancNFe) - resp['sent_xml'] = etree.tostring(retorno_consulta.procEventoNFe) else: - tz = pytz.timezone(self.env.user.partner_id.tz) or pytz.utc - dt_evento = datetime.utcnow() - dt_evento = pytz.utc.localize(dt_evento).astimezone(tz) - - id_canc = "ID110111%s%02d" % ( - self.chave_nfe, self.sequencial_evento) - cancelamento = { - 'idLote': self.id, - 'estado': self.company_id.state_id.ibge_code, - 'ambiente': 2 if self.ambiente == 'homologacao' else 1, - 'eventos': [{ - 'Id': id_canc, - 'cOrgao': self.company_id.state_id.ibge_code, - 'tpAmb': 2 if self.ambiente == 'homologacao' else 1, - 'CNPJ': re.sub('[^0-9]', '', self.company_id.cnpj_cpf), - 'chNFe': self.chave_nfe, - 'dhEvento': dt_evento.strftime('%Y-%m-%dT%H:%M:%S-03:00'), - 'nSeqEvento': self.sequencial_evento, - 'nProt': self.protocolo_nfe, - 'xJust': justificativa - }] - } - resp = recepcao_evento_cancelamento(certificado, **cancelamento) - resposta = resp['object'].Body.nfeRecepcaoEventoResult.retEnvEvento - if resposta.cStat == 128 and \ - resposta.retEvento.infEvento.cStat in (135, 136, 155): - self.state = 'cancel' - self.codigo_retorno = resposta.retEvento.infEvento.cStat - self.mensagem_retorno = resposta.retEvento.infEvento.xMotivo - self.sequencial_evento += 1 + code, motive = None, None + if resposta.cStat == 128: + code = resposta.retEvento.infEvento.cStat + motive = resposta.retEvento.infEvento.xMotivo else: - if resposta.cStat == 128: - self.codigo_retorno = resposta.retEvento.infEvento.cStat - self.mensagem_retorno = \ - resposta.retEvento.infEvento.xMotivo - else: - self.codigo_retorno = resposta.cStat - self.mensagem_retorno = resposta.xMotivo + code = resposta.cStat + motive = resposta.xMotivo + if code == 573: # Duplicidade, já cancelado + return self.action_get_status() + return self._create_response_cancel( + code, motive, resp, justificativa) self.env['invoice.eletronic.event'].create({ 'code': self.codigo_retorno, @@ -946,3 +939,65 @@ def action_cancel_document(self, context=None, justificativa=None): resp['received_xml']) self.nfe_processada = base64.encodestring(nfe_proc_cancel) self.nfe_processada_name = "NFe%08d.xml" % self.numero + + def action_get_status(self): + cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file + cert_pfx = base64.decodestring(cert) + certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password) + consulta = { + 'estado': self.company_id.state_id.ibge_code, + 'ambiente': 2 if self.ambiente == 'homologacao' else 1, + 'modelo': self.model, + 'obj': { + 'chave_nfe': self.chave_nfe, + 'ambiente': 2 if self.ambiente == 'homologacao' else 1, + } + } + resp = consultar_protocolo_nfe(certificado, **consulta) + retorno_consulta = resp['object'].getchildren()[0] + if retorno_consulta.cStat == 101: + self.state = 'cancel' + self.codigo_retorno = retorno_consulta.cStat + self.mensagem_retorno = retorno_consulta.xMotivo + resp['received_xml'] = etree.tostring( + retorno_consulta, encoding=str) + self.env['invoice.eletronic.event'].create({ + 'code': self.codigo_retorno, + 'name': self.mensagem_retorno, + 'invoice_eletronic_id': self.id, + }) + self._create_attachment('canc', self, resp['sent_xml']) + self._create_attachment('canc-ret', self, resp['received_xml']) + nfe_processada = base64.decodestring(self.nfe_processada) + nfe_proc_cancel = gerar_nfeproc_cancel( + nfe_processada, resp['received_xml'].encode()) + if nfe_proc_cancel: + self.nfe_processada = base64.encodestring(nfe_proc_cancel) + else: + message = "%s - %s" % (retorno_consulta.cStat, + retorno_consulta.xMotivo) + raise UserError(message) + + def _create_response_cancel(self, code, motive, response, justificativa): + message = "%s - %s" % (code, motive) + wiz = self.env['wizard.cancel.nfe'].create({ + 'edoc_id': self.id, + 'justificativa': justificativa, + 'state': 'error', + 'message': message, + 'sent_xml': base64.b64encode( + response['sent_xml'].encode('utf-8')), + 'sent_xml_name': 'cancelamento-envio.xml', + 'received_xml': base64.b64encode( + response['received_xml'].encode('utf-8')), + 'received_xml_name': 'cancelamento-retorno.xml', + }) + return { + 'name': 'Cancelamento NFe', + 'type': 'ir.actions.act_window', + 'res_model': 'wizard.cancel.nfe', + 'res_id': wiz.id, + 'view_type': 'form', + 'view_mode': 'form', + 'target': 'new', + } diff --git a/br_nfe/models/payment_mode.py b/br_nfe/models/payment_mode.py new file mode 100644 index 000000000..8ffd09471 --- /dev/null +++ b/br_nfe/models/payment_mode.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# © 2018 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class PaymentMode(models.Model): + _inherit = "payment.mode" + + tipo_pagamento = fields.Selection( + [('01', u'Dinheiro'), + ('02', u'Cheque'), + ('03', u'Catão de Crédito'), + ('04', u'Cartão de Débito'), + ('05', u'Crédito Loja'), + ('10', u'Vale Alimentação'), + ('11', u'Vale Refeição'), + ('12', u'Vale Presente'), + ('13', u'Vale Combustível'), + ('14', u'Duplicata Mercantil'), + ('90', u'Sem pagamento'), + ('99', u'Outros')], + string=u"Forma de Pagamento", default="14") diff --git a/br_nfe/tests/test_inutilizacao.py b/br_nfe/tests/test_inutilizacao.py index 91290537b..b0b52916c 100644 --- a/br_nfe/tests/test_inutilizacao.py +++ b/br_nfe/tests/test_inutilizacao.py @@ -144,12 +144,12 @@ def tearDown(self): ]) @patch('odoo.addons.br_nfe.models.inutilized_nfe.inutilizar_nfe') - def test_inutilizacao_ok(self, inutilizar): + def test_inutilizacao_falha_schema(self, inutilizar): with open(os.path.join(self.caminho, 'xml/inutilizacao_sent_xml.xml')) as f: sent_xml = f.read() with open(os.path.join(self.caminho, - 'xml/inutilizacao_received_xml.xml')) as f: + 'xml/inutilizacao_falha_schema.xml')) as f: received_xml = f.read() _, obj = sanitize_response(received_xml) inutilizar.return_value = {'received_xml': received_xml, @@ -163,24 +163,8 @@ def test_inutilizacao_ok(self, inutilizar): modelo='55', justificativa=justif )) - wizard.action_inutilize_nfe() - inut_inv = self.env['invoice.eletronic.inutilized'].search([]) - self.assertEqual(len(inut_inv), 1) - self.assertEqual(inut_inv.numeration_start, 0) - self.assertEqual(inut_inv.numeration_end, 5) - self.assertEqual(inut_inv.serie, self.serie) - self.assertEqual(inut_inv.name, u'Série Inutilizada 0 - 5') - self.assertEqual(inut_inv.justificativa, justif) - self.assertEqual(inut_inv.state, 'error') - invoice = self.env['account.invoice'].create(dict( - self.default_invoice.items(), - partner_id=self.partner_fisica.id, - document_serie_id=self.serie.id - )) - invoice.action_invoice_open() - inv_eletr = self.env['invoice.eletronic'].search( - [('invoice_id', '=', invoice.id)]) - self.assertEqual(inv_eletr.numero, 6) + with self.assertRaises(UserError): + wizard.action_inutilize_nfe() @patch('odoo.addons.br_nfe.models.inutilized_nfe.inutilizar_nfe') def test_inutilizacao_2_sequences(self, inutilizar): @@ -188,7 +172,7 @@ def test_inutilizacao_2_sequences(self, inutilizar): 'xml/inutilizacao_sent_xml.xml')) as f: sent_xml = f.read() with open(os.path.join(self.caminho, - 'xml/inutilizacao_received_xml.xml')) as f: + 'xml/inutilizacao_received_ok_xml.xml')) as f: received_xml = f.read() _, obj = sanitize_response(received_xml) inutilizar.return_value = {'received_xml': received_xml, diff --git a/br_nfe/tests/test_nfe.py b/br_nfe/tests/test_nfe.py index e1cc1feff..457783685 100644 --- a/br_nfe/tests/test_nfe.py +++ b/br_nfe/tests/test_nfe.py @@ -328,39 +328,6 @@ def test_send_nfe(self): with self.assertRaises(Exception): invoice_eletronic.action_send_eletronic_invoice() - @patch('odoo.addons.br_nfe.models.invoice_eletronic.retorno_autorizar_nfe') - @patch('odoo.addons.br_nfe.models.invoice_eletronic.autorizar_nfe') - def test_wrong_xml_schema(self, autorizar, ret_autorizar): - for invoice in self.invoices: - # Confirmando a fatura deve gerar um documento eletrônico - invoice.action_invoice_open() - - # Lote recebido com sucesso - xml_recebido = open(os.path.join( - self.caminho, 'xml/lote-recebido-sucesso.xml'), 'r').read() - resp = sanitize_response(xml_recebido) - autorizar.return_value = { - 'object': resp[1], - 'sent_xml': '', - 'received_xml': xml_recebido - } - - # Consultar recibo com erro 225 - xml_recebido = open(os.path.join( - self.caminho, 'xml/recibo-erro-schema-225.xml'), 'r').read() - resp_ret = sanitize_response(xml_recebido) - ret_autorizar.return_value = { - 'object': resp_ret[1], - 'sent_xml': '', - 'received_xml': xml_recebido - } - - invoice_eletronic = self.env['invoice.eletronic'].search( - [('invoice_id', '=', invoice.id)]) - invoice_eletronic.action_send_eletronic_invoice() - self.assertEquals(invoice_eletronic.state, 'error') - self.assertEquals(invoice_eletronic.codigo_retorno, '225') - @patch('odoo.addons.br_nfe.models.invoice_eletronic.retorno_autorizar_nfe') @patch('odoo.addons.br_nfe.models.invoice_eletronic.autorizar_nfe') def test_nfe_with_concept_error(self, autorizar, ret_autorizar): @@ -432,6 +399,6 @@ def test_nfe_cancelamento_ok(self, cancelar, consulta): justificativa="Cancelamento de teste") self.assertEquals(invoice_eletronic.state, 'cancel') - self.assertEquals(invoice_eletronic.codigo_retorno, "155") + self.assertEquals(invoice_eletronic.codigo_retorno, "135") self.assertEquals(invoice_eletronic.mensagem_retorno, - "Cancelamento homologado fora de prazo") + "Evento registrado e vinculado a NF-e") diff --git a/br_nfe/tests/xml/cancelamento-sucesso.xml b/br_nfe/tests/xml/cancelamento-sucesso.xml index 02c0ac7af..7c8e29fbe 100644 --- a/br_nfe/tests/xml/cancelamento-sucesso.xml +++ b/br_nfe/tests/xml/cancelamento-sucesso.xml @@ -1,36 +1,25 @@ - -
- - 35 - 1.00 - -
- - - - 33 - 2 - SP_EVENTOS_PL_100 - 35 - 128 - Lote de Evento Processado - - - 2 - SP_EVENTOS_PL_100 - 35 - 155 - Cancelamento homologado fora de prazo - 35161021332917000163550010000000321243582205 - 110111 - Cancelamento registrado - 1 - 06621204930 - 2016-11-03T20:11:37-02:00 - 135160008042297 - - - - - -
+ + + 30 + 2 + SVRS201806290759 + 42 + 128 + Lote de Evento Processado + + + 2 + SVRS201806290759 + 42 + 135 + Evento registrado e vinculado a NF-e + 42180780164221000215550010000006071576626530 + 110111 + 1 + 00010349910944 + 2018-07-31T11:12:28-03:00 + 342180004306498 + + + + diff --git a/br_nfe/tests/xml/cce-retorno.xml b/br_nfe/tests/xml/cce-retorno.xml index 577ed39e3..1a24f4532 100644 --- a/br_nfe/tests/xml/cce-retorno.xml +++ b/br_nfe/tests/xml/cce-retorno.xml @@ -1,37 +1,25 @@ - - - - - 35 - 1.00 - - - - - - 15 - 2 - SP_EVENTOS_PL_100 - 35 - 128 - Lote de Evento Processado - - - 2 - SP_EVENTOS_PL_100 - 35 - 135 - Evento registrado e vinculado a NF-e - 35161221332917000163550010000000041158176721 - 110110 - Carta de Correcao registrada - 3 - 14452933000124 - 2016-12-19T12:50:49-02:00 - 135160008802236 - - - - - - + + + 1 + 2 + SVRS201806290759 + 42 + 128 + Lote de Evento Processado + + + 2 + SVRS201806290759 + 42 + 135 + Evento registrado e vinculado a NF-e + 42180780164221000215550010000006071576626530 + 110110 + 1 + 00010349910944 + 2018-07-31T11:05:58-03:00 + 135160008802236 + + + + diff --git a/br_nfe/tests/xml/inutilizacao_falha_schema.xml b/br_nfe/tests/xml/inutilizacao_falha_schema.xml new file mode 100644 index 000000000..f3c6359e9 --- /dev/null +++ b/br_nfe/tests/xml/inutilizacao_falha_schema.xml @@ -0,0 +1,12 @@ + + + + 2 + SVRS201806282144 + 215 + Rejeicao: Falha no schema XML + 42 + 2018-07-31T10:06:33-03:00 + + + diff --git a/br_nfe/tests/xml/inutilizacao_received_ok_xml.xml b/br_nfe/tests/xml/inutilizacao_received_ok_xml.xml index 5d42159e1..4cd40aa1f 100644 --- a/br_nfe/tests/xml/inutilizacao_received_ok_xml.xml +++ b/br_nfe/tests/xml/inutilizacao_received_ok_xml.xml @@ -1 +1,19 @@ -113.102SVRS201601161002102Rejeicao: Falha no schema XML112017-01-17T17:32:18-04:00 + + + + 2 + SVRS201806282144 + 102 + Inutilizacao de numero homologado + 42 + 18 + 80164221006215 + 55 + 1 + 600 + 601 + 2018-07-31T10:00:38-03:00 + 342180004306034 + + + diff --git a/br_nfe/tests/xml/inutilizacao_received_xml.xml b/br_nfe/tests/xml/inutilizacao_received_xml.xml deleted file mode 100644 index be69eb582..000000000 --- a/br_nfe/tests/xml/inutilizacao_received_xml.xml +++ /dev/null @@ -1 +0,0 @@ -113.102SVRS201601161002215Rejeicao: Falha no schema XML112017-01-17T17:32:18-04:00 diff --git a/br_nfe/tests/xml/lote-recebido-sucesso.xml b/br_nfe/tests/xml/lote-recebido-sucesso.xml index 202544ac5..4f198947f 100644 --- a/br_nfe/tests/xml/lote-recebido-sucesso.xml +++ b/br_nfe/tests/xml/lote-recebido-sucesso.xml @@ -1,25 +1,14 @@ - - - - - 42 - 3.10 - - - - - - 2 - SP_NFE_PL_008i2 - 103 - Lote recebido com sucesso - 42 - 2016-10-12T11:46:08-03:00 - - 351000104734849 - 1 - - - - - + + + 2 + SVRS201807191353 + 103 + Lote recebido com sucesso + 42 + 2018-07-31T10:53:25-03:00 + + 423002181689172 + 1 + + + diff --git a/br_nfe/tests/xml/recibo-erro-694.xml b/br_nfe/tests/xml/recibo-erro-694.xml index 3a129255f..5e23b18c9 100644 --- a/br_nfe/tests/xml/recibo-erro-694.xml +++ b/br_nfe/tests/xml/recibo-erro-694.xml @@ -1,32 +1,24 @@ - - - - - 42 - 3.10 - - - - - - 2 - SP_NFE_PL_008i2 - 351000104734849 - 104 - Lote processado - 42 - 2016-10-12T11:46:08-03:00 - - - 2 - SP_NFE_PL_008i2 - 35161021332917000163550010000000011178586888 - 2016-10-12T11:46:08-03:00 - 694 - Rejeicao: Nao informado o grupo de ICMS para a UF de destino [nItem:1] - - - - - - + + + 2 + SVRS201807240822 + 423002181690719 + 104 + Lote processado + 42 + 2018-07-31T11:28:59-03:00 + + + 2 + SVRS201807240822 + 42180780164221000215550010000006081271576374 + 2018-07-31T11:28:59-03:00 + oIIfUW/HcKFT2tZAaqI17rSBzmU= + 694 + + Rejeicao: CFOP de operacao interestadual e idDest diferente de 2 + + + + + diff --git a/br_nfe/tests/xml/recibo-erro-schema-225.xml b/br_nfe/tests/xml/recibo-erro-schema-225.xml deleted file mode 100644 index 917ec9518..000000000 --- a/br_nfe/tests/xml/recibo-erro-schema-225.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - 42 - 3.10 - - - - - - 2 - SP_NFE_PL_008i2 - 351000105223918 - 225 - Rejeicao: Falha no Schema XML do lote de NFe - 42 - 2016-10-22T08:18:04-02:00 - - - - diff --git a/br_nfe/views/inutilized_nfe.xml b/br_nfe/views/inutilized_nfe.xml index 1c442ea3c..de985f13a 100644 --- a/br_nfe/views/inutilized_nfe.xml +++ b/br_nfe/views/inutilized_nfe.xml @@ -43,6 +43,10 @@ + + + + diff --git a/br_nfe/views/invoice_eletronic.xml b/br_nfe/views/invoice_eletronic.xml index 5d67e2daf..cc208e1d6 100644 --- a/br_nfe/views/invoice_eletronic.xml +++ b/br_nfe/views/invoice_eletronic.xml @@ -18,6 +18,9 @@ string="Carta de Correção" attrs="{'invisible': ['|', ('state', '!=', 'done'), ('model', 'not in', ('55', '65'))]}"> + + + diff --git a/br_nfe/views/payment_mode.xml b/br_nfe/views/payment_mode.xml new file mode 100644 index 000000000..5e6b8636e --- /dev/null +++ b/br_nfe/views/payment_mode.xml @@ -0,0 +1,15 @@ + + + + + view.br.nfe.payment.mode.form + payment.mode + + + + + + + + + diff --git a/br_nfe/wizard/cancel_nfe.py b/br_nfe/wizard/cancel_nfe.py index acb42b8e4..b364194db 100644 --- a/br_nfe/wizard/cancel_nfe.py +++ b/br_nfe/wizard/cancel_nfe.py @@ -11,11 +11,19 @@ class CancelNFe(models.TransientModel): edoc_id = fields.Many2one('invoice.eletronic', string="Documento") justificativa = fields.Text('Justificativa', size=255, required=True) + state = fields.Selection([('drat', u'Provisório'), ('error', u'Erro')], + string="Situação") + message = fields.Char(string=u"Mensagem", size=300, readonly=True) + sent_xml = fields.Binary(string="Xml Envio", readonly=True) + sent_xml_name = fields.Char(string=u"Xml Envio", size=30, readonly=True) + received_xml = fields.Binary(string=u"Xml Recebimento", readonly=True) + received_xml_name = fields.Char( + string=u"Xml Recebimento", size=30, readonly=True) @api.multi def action_cancel_nfe(self): if self.edoc_id and len(self.justificativa) > 15: - self.edoc_id.action_cancel_document( + return self.edoc_id.action_cancel_document( justificativa=self.justificativa) else: raise UserError(u"Justificativa deve ter mais de 15 caracteres") diff --git a/br_nfe/wizard/cancel_nfe.xml b/br_nfe/wizard/cancel_nfe.xml index 6edab6a03..55ee025d3 100644 --- a/br_nfe/wizard/cancel_nfe.xml +++ b/br_nfe/wizard/cancel_nfe.xml @@ -6,9 +6,17 @@
+ + + + + + + +
Regras para cancelamento !
    diff --git a/br_nfe/wizard/carta_correcao_eletronica.py b/br_nfe/wizard/carta_correcao_eletronica.py index b4e22bd2b..79d7e5b02 100644 --- a/br_nfe/wizard/carta_correcao_eletronica.py +++ b/br_nfe/wizard/carta_correcao_eletronica.py @@ -17,13 +17,28 @@ except ImportError: _logger.debug('Cannot import pytrustnfe', exc_info=True) +COND_USO = "A Carta de Correcao e disciplinada pelo paragrafo 1o-A do art. 7o \ +do Convenio S/N, de 15 de dezembro de 1970 e pode ser utilizada para \ +regularizacao de erro ocorrido na emissao de documento fiscal, desde que o \ +erro nao esteja relacionado com: I - as variaveis que determinam o valor do \ +imposto tais como: base de calculo, aliquota, diferenca de preco, quantidade, \ +valor da operacao ou da prestacao; II - a correcao de dados cadastrais que \ +implique mudanca do remetente ou do destinatario; III - a data de \ +emissao ou de saida." + class WizardCartaCorrecaoEletronica(models.TransientModel): _name = 'wizard.carta.correcao.eletronica' + @api.depends('eletronic_doc_id') + def _default_sequence_number(self): + return len(self.eletronic_doc_id.cartas_correcao_ids) + 1 + state = fields.Selection([('drat', u'Provisório'), ('error', u'Erro')], string=u"Situação") correcao = fields.Text(string=u"Correção", max_length=1000, required=True) + sequential = fields.Integer( + string="Sequência Evento", default=_default_sequence_number) eletronic_doc_id = fields.Many2one( 'invoice.eletronic', string=u"Documento Eletrônico") message = fields.Char(string=u"Mensagem", size=300, readonly=True) @@ -48,24 +63,27 @@ def send_letter(self): tz = pytz.timezone(self.env.user.partner_id.tz) or pytz.utc dt_evento = datetime.utcnow() dt_evento = pytz.utc.localize(dt_evento).astimezone(tz) - numero_evento = len(self.eletronic_doc_id.cartas_correcao_ids) + 1 self.correcao = self.correcao.replace('\n', '
    ') carta = { - 'invoice_id': self.eletronic_doc_id.id, - 'CNPJ': re.sub( - "[^0-9]", "", self.eletronic_doc_id.company_id.cnpj_cpf or ''), - 'cOrgao': self.eletronic_doc_id.company_id.state_id.ibge_code, - 'tpAmb': self.eletronic_doc_id.company_id.tipo_ambiente, + 'idLote': self.id, 'estado': self.eletronic_doc_id.company_id.state_id.ibge_code, 'ambiente': int(self.eletronic_doc_id.company_id.tipo_ambiente), - 'dhEvento': dt_evento.strftime('%Y-%m-%dT%H:%M:%S-03:00'), - 'chNFe': self.eletronic_doc_id.chave_nfe, - 'xCorrecao': self.correcao, - 'tpEvento': '110110', - 'nSeqEvento': numero_evento, - 'idLote': self.id, - 'Id': "ID110110%s%02d" % (self.eletronic_doc_id.chave_nfe, - numero_evento) + 'modelo': self.eletronic_doc_id.model, + 'eventos': [{ + 'invoice_id': self.eletronic_doc_id.id, + 'CNPJ': re.sub( + "[^0-9]", "", self.eletronic_doc_id.company_id.cnpj_cpf), + 'cOrgao': self.eletronic_doc_id.company_id.state_id.ibge_code, + 'tpAmb': self.eletronic_doc_id.company_id.tipo_ambiente, + + 'dhEvento': dt_evento.strftime('%Y-%m-%dT%H:%M:%S-03:00'), + 'chNFe': self.eletronic_doc_id.chave_nfe, + 'xCorrecao': self.correcao, + 'tpEvento': '110110', + 'nSeqEvento': self.sequential, + 'Id': "ID110110%s%02d" % ( + self.eletronic_doc_id.chave_nfe, self.sequential) + }], } cert = self.eletronic_doc_id.company_id.with_context( {'bin_size': False}).nfe_a1_file @@ -74,18 +92,18 @@ def send_letter(self): cert_pfx, self.eletronic_doc_id.company_id.nfe_a1_password) resposta = recepcao_evento_carta_correcao(certificado, **carta) - retorno = resposta['object'].Body.nfeRecepcaoEventoResult.retEnvEvento + retorno = resposta['object'].getchildren()[0] if retorno.cStat == 128 and retorno.retEvento.infEvento.cStat in (135, 136): eventos = self.env['carta.correcao.eletronica.evento'] eventos.create({ - 'id_cce': carta['Id'], + 'id_cce': carta['eventos'][0]['Id'], 'eletronic_doc_id': self.eletronic_doc_id.id, 'datahora_evento': datetime.now(), - 'tipo_evento': carta['tpEvento'], - 'sequencial_evento': carta['nSeqEvento'], - 'correcao': carta['xCorrecao'], + 'tipo_evento': carta['eventos'][0]['tpEvento'], + 'sequencial_evento': carta['eventos'][0]['nSeqEvento'], + 'correcao': carta['eventos'][0]['xCorrecao'], 'message': retorno.retEvento.infEvento.xMotivo, 'protocolo': retorno.retEvento.infEvento.nProt, }) diff --git a/br_nfe/wizard/carta_correcao_eletronica.xml b/br_nfe/wizard/carta_correcao_eletronica.xml index 18d940004..a88557df0 100644 --- a/br_nfe/wizard/carta_correcao_eletronica.xml +++ b/br_nfe/wizard/carta_correcao_eletronica.xml @@ -10,6 +10,7 @@
+ diff --git a/br_nfe/wizard/inutilize_nfe_numeration.py b/br_nfe/wizard/inutilize_nfe_numeration.py index 634612b8d..aa2229fb1 100644 --- a/br_nfe/wizard/inutilize_nfe_numeration.py +++ b/br_nfe/wizard/inutilize_nfe_numeration.py @@ -34,4 +34,4 @@ def action_inutilize_nfe(self): serie=self.serie.id, state='error', )) - inut_inv.action_send_inutilization() + return inut_inv.action_send_inutilization() diff --git a/br_stock_account/models/account_invoice.py b/br_stock_account/models/account_invoice.py index fdcb6e3d2..cea13c8a7 100644 --- a/br_stock_account/models/account_invoice.py +++ b/br_stock_account/models/account_invoice.py @@ -40,10 +40,12 @@ def _compute_amount(self): # Transporte freight_responsibility = fields.Selection( - [('0', u'0 - Emitente'), - ('1', u'1 - Destinatário'), - ('2', u'2 - Terceiros'), - ('9', u'9 - Sem Frete')], + [('0', u'0 - Contratação do Frete por conta do Remetente (CIF)'), + ('1', u'1 - Contratação do Frete por conta do Destinatário (FOB)'), + ('2', u'2 - Contratação do Frete por conta de Terceiros'), + ('3', u'3 - Transporte Próprio por conta do Remetente'), + ('4', u'4 - Transporte Próprio por conta do Destinatário'), + ('9', u'9 - Sem Ocorrência de Transporte')], u'Modalidade do frete', default="9") carrier_id = fields.Many2one('delivery.carrier', 'Método de Entrega') shipping_supplier_id = fields.Many2one('res.partner', 'Transportadora')