From 2e399d0c3e6ca67016c6074e8d31b2bece8d52d6 Mon Sep 17 00:00:00 2001 From: Renato Lima Date: Thu, 17 Sep 2020 16:56:05 -0300 Subject: [PATCH 001/790] [FIX] fiscal document return --- l10n_br_fiscal/models/document.py | 65 +++++++++++++++++-------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/l10n_br_fiscal/models/document.py b/l10n_br_fiscal/models/document.py index f1bb9ad73208..21bcb40be9c1 100644 --- a/l10n_br_fiscal/models/document.py +++ b/l10n_br_fiscal/models/document.py @@ -176,6 +176,7 @@ def _compute_amount(self): operation_name = fields.Char( string='Operation Name', + copy=False, ) document_electronic = fields.Boolean( @@ -770,38 +771,40 @@ def _onchange_document_section(self): return {'domain': domain} def _create_return(self): - return_ids = self.env[self._name] + return_docs = self.env[self._name] for record in self: - if record.fiscal_operation_id.return_fiscal_operation_id: - new = record.copy() - new.fiscal_operation_id = ( - record.fiscal_operation_id.return_fiscal_operation_id) - if record.fiscal_operation_type == 'out': - new.fiscal_operation_type = 'in' - else: - new.fiscal_operation_type = 'out' - new._onchange_fiscal_operation_id() - new.line_ids.write({'fiscal_operation_id': new.fiscal_operation_id.id}) - - for item in new.line_ids: - item._onchange_fiscal_operation_id() - - return_ids |= new - return return_ids + fsc_op = record.fiscal_operation_id.return_fiscal_operation_id + if not fsc_op: + raise ValidationError(_( + "The fiscal operation {} there is no Return Fiscal " + "Operation definied".format(record.fiscal_operation_id))) + + new_doc = record.copy() + new_doc.fiscal_operation_id = fsc_op + new_doc._onchange_fiscal_operation_id() + + for l in new_doc.line_ids: + fsc_op_line = l.fiscal_operation_id.return_fiscal_operation_id + if not fsc_op_line: + raise ValidationError(_( + "The fiscal operation {} there is no Return Fiscal " + "Operation definied".format(l.fiscal_operation_id))) + l.fiscal_operation_id = fsc_op_line + l._onchange_fiscal_operation_id() + + return_docs |= new_doc + return return_docs + @api.multi def action_create_return(self): - self.ensure_one() - return_id = self._create_return() - if return_id.fiscal_operation_type == 'out': - return_id.fiscal_operation_type = 'in' - action = self.env.ref('l10n_br_fiscal.document_in_action').read()[0] - else: - return_id.fiscal_operation_type = 'out' - action = self.env.ref('l10n_br_fiscal.document_out_action').read()[0] - - action['domain'] = literal_eval(action['domain']) - action['domain'].append(('id', '=', return_id.id)) - return action + action = self.env.ref('l10n_br_fiscal.document_action').read()[0] + return_docs = self._create_return() + + if return_docs: + action['domain'] = literal_eval(action['domain']) + action['domain'].append(('id', 'in', return_docs.ids)) + + return action def _document_comment_vals(self): return { @@ -846,6 +849,10 @@ def _exec_after_SITUACAO_EDOC_A_ENVIAR(self, old_state, new_state): @api.onchange('fiscal_operation_id') def _onchange_fiscal_operation_id(self): super()._onchange_fiscal_operation_id() + if self.fiscal_operation_id: + self.fiscal_operation_type = ( + self.fiscal_operation_id.fiscal_operation_type) + if self.issuer == DOCUMENT_ISSUER_COMPANY: self.document_type_id = self.company_id.document_type_id From b6424810505ed61b77bba5ec53f713decb6565ff Mon Sep 17 00:00:00 2001 From: Renato Lima Date: Thu, 17 Sep 2020 19:49:15 -0300 Subject: [PATCH 002/790] [ADD] fiscal document action --- l10n_br_fiscal/models/document.py | 2 +- l10n_br_fiscal/views/l10n_br_fiscal_action.xml | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/l10n_br_fiscal/models/document.py b/l10n_br_fiscal/models/document.py index 21bcb40be9c1..17c9a0e734bb 100644 --- a/l10n_br_fiscal/models/document.py +++ b/l10n_br_fiscal/models/document.py @@ -801,7 +801,7 @@ def action_create_return(self): return_docs = self._create_return() if return_docs: - action['domain'] = literal_eval(action['domain']) + action['domain'] = literal_eval(action['domain'] or '[]') action['domain'].append(('id', 'in', return_docs.ids)) return action diff --git a/l10n_br_fiscal/views/l10n_br_fiscal_action.xml b/l10n_br_fiscal/views/l10n_br_fiscal_action.xml index 105d3273af20..d25c64366c4d 100644 --- a/l10n_br_fiscal/views/l10n_br_fiscal_action.xml +++ b/l10n_br_fiscal/views/l10n_br_fiscal_action.xml @@ -579,6 +579,24 @@ + + Fiscal Document + ir.actions.act_window + l10n_br_fiscal.document + form + kanban,tree,form + + + +

+ Create a new Document +

+ Odoo helps you easily track all activities + related to a fiscal operation. +

+
+
+ NF-e Receivement From 902ced475a530c7858b103a15a29578c160757ad Mon Sep 17 00:00:00 2001 From: Renato Lima Date: Tue, 22 Sep 2020 12:07:34 -0300 Subject: [PATCH 003/790] [REF] improve document.workflow method --- l10n_br_fiscal/models/document_workflow.py | 129 ++++++++++----------- 1 file changed, 64 insertions(+), 65 deletions(-) diff --git a/l10n_br_fiscal/models/document_workflow.py b/l10n_br_fiscal/models/document_workflow.py index 8e7877696451..3d85bec8dc9f 100644 --- a/l10n_br_fiscal/models/document_workflow.py +++ b/l10n_br_fiscal/models/document_workflow.py @@ -1,34 +1,63 @@ # Copyright (C) 2019 KMEE INFORMATICA LTDA # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html -from odoo import api, fields, models, _ +from odoo import _, api, fields, models from odoo.exceptions import UserError -from ..constants.fiscal import (SITUACAO_EDOC, - SITUACAO_EDOC_A_ENVIAR, - SITUACAO_EDOC_AUTORIZADA, - SITUACAO_EDOC_CANCELADA, - SITUACAO_EDOC_DENEGADA, - SITUACAO_EDOC_EM_DIGITACAO, - SITUACAO_EDOC_ENVIADA, - SITUACAO_EDOC_INUTILIZADA, - SITUACAO_EDOC_REJEITADA, SITUACAO_FISCAL, - SITUACAO_FISCAL_SPED_CONSIDERA_CANCELADO, - WORKFLOW_DOCUMENTO_NAO_ELETRONICO, - WORKFLOW_EDOC, - PROCESSADOR_NENHUM, - PROCESSADOR, - DOCUMENT_ISSUER_COMPANY, - ) +from ..constants.fiscal import ( + SITUACAO_EDOC, + SITUACAO_EDOC_A_ENVIAR, + SITUACAO_EDOC_AUTORIZADA, + SITUACAO_EDOC_CANCELADA, + SITUACAO_EDOC_DENEGADA, + SITUACAO_EDOC_EM_DIGITACAO, + SITUACAO_EDOC_ENVIADA, + SITUACAO_EDOC_INUTILIZADA, + SITUACAO_EDOC_REJEITADA, SITUACAO_FISCAL, + SITUACAO_FISCAL_SPED_CONSIDERA_CANCELADO, + WORKFLOW_DOCUMENTO_NAO_ELETRONICO, + WORKFLOW_EDOC, + PROCESSADOR_NENHUM, + PROCESSADOR, + DOCUMENT_ISSUER_COMPANY, +) -import logging -_logger = logging.getLogger(__name__) +class DocumentWorkflow(models.AbstractModel): + _name = 'l10n_br_fiscal.document.workflow' + _description = 'Fiscal Document Workflow' + state_edoc = fields.Selection( + selection=SITUACAO_EDOC, + string='Situação e-doc', + default=SITUACAO_EDOC_EM_DIGITACAO, + copy=False, + required=True, + track_visibility='onchange', + index=True, + ) -class DocumentWorkflow(models.AbstractModel): - _name = "l10n_br_fiscal.document.workflow" - _description = "Fiscal Document Workflow" + state_fiscal = fields.Selection( + selection=SITUACAO_FISCAL, + string='Situação Fiscal', + copy=False, + track_visibility='onchange', + index=True, + ) + + cancel_reason = fields.Char( + string='Cancel Reason', + ) + + correction_reason = fields.Char( + string='Correction Reason', + ) + + processador_edoc = fields.Selection( + string='Processador', + selection=PROCESSADOR, + default=PROCESSADOR_NENHUM, + ) def _direct_draft_send(self): return False @@ -194,59 +223,29 @@ def _change_state(self, new_state): record.state_edoc = new_state record._after_change_state(old_state, new_state) - state_edoc = fields.Selection( - selection=SITUACAO_EDOC, - string="Situação e-doc", - default=SITUACAO_EDOC_EM_DIGITACAO, - copy=False, - required=True, - track_visibility="onchange", - index=True) - - state_fiscal = fields.Selection( - selection=SITUACAO_FISCAL, - string="Situação Fiscal", - copy=False, - track_visibility="onchange", - index=True) - - cancel_reason = fields.Char( - string="Cancel Reason") - - correction_reason = fields.Char( - string="Correction Reason") - - processador_edoc = fields.Selection( - string="Processador", - selection=PROCESSADOR, - default=PROCESSADOR_NENHUM) - def document_date(self): if not self.date: self.date = self._date_server_format() + def document_check(self): + return True + def _generate_key(self): pass def document_number(self): - for nfe in self: - if not nfe.number and nfe.document_serie_id and nfe.date: - nfe.number = nfe.document_serie_id.next_seq_number() - - if (nfe.issuer == DOCUMENT_ISSUER_COMPANY - and not nfe.operation_name): - nfe.operation_name = ', '.join( - [l.name for l in nfe.line_ids.mapped( - 'fiscal_operation_id')]) + if self.issuer == DOCUMENT_ISSUER_COMPANY: + if not self.number and self.document_serie_id: + self.number = self.document_serie_id.next_seq_number() - if (nfe.issuer == DOCUMENT_ISSUER_COMPANY - and nfe.document_electronic - and not nfe.key): - nfe.document_serie = nfe.document_serie_id.code - nfe.key = nfe._generate_key() + if not self.operation_name: + self.operation_name = ', '.join( + [l.name for l in self.line_ids.mapped( + 'fiscal_operation_id')]) - def document_check(self): - return True + if self.document_electronic and not self.key: + self.document_serie = self.document_serie_id.code + self.key = self._generate_key() def _document_confirm(self): self._change_state(SITUACAO_EDOC_A_ENVIAR) From b8f8b16531458e8ae1d0b1216c677a4051f29aca Mon Sep 17 00:00:00 2001 From: Renato Lima Date: Tue, 22 Sep 2020 12:22:26 -0300 Subject: [PATCH 004/790] [FIX] fiscal document return tests --- l10n_br_fiscal/models/document.py | 11 +++++----- .../document_fiscal_line_mixin_methods.py | 21 ++++++++++++------- .../tests/test_fiscal_document_generic.py | 5 ++--- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/l10n_br_fiscal/models/document.py b/l10n_br_fiscal/models/document.py index 17c9a0e734bb..d546d65ed499 100644 --- a/l10n_br_fiscal/models/document.py +++ b/l10n_br_fiscal/models/document.py @@ -776,8 +776,8 @@ def _create_return(self): fsc_op = record.fiscal_operation_id.return_fiscal_operation_id if not fsc_op: raise ValidationError(_( - "The fiscal operation {} there is no Return Fiscal " - "Operation definied".format(record.fiscal_operation_id))) + "The fiscal operation {} has no return Fiscal " + "Operation defined".format(record.fiscal_operation_id))) new_doc = record.copy() new_doc.fiscal_operation_id = fsc_op @@ -787,10 +787,11 @@ def _create_return(self): fsc_op_line = l.fiscal_operation_id.return_fiscal_operation_id if not fsc_op_line: raise ValidationError(_( - "The fiscal operation {} there is no Return Fiscal " - "Operation definied".format(l.fiscal_operation_id))) + "The fiscal operation {} has no return Fiscal " + "Operation defined".format(l.fiscal_operation_id))) l.fiscal_operation_id = fsc_op_line l._onchange_fiscal_operation_id() + l._onchange_fiscal_operation_line_id() return_docs |= new_doc return return_docs @@ -804,7 +805,7 @@ def action_create_return(self): action['domain'] = literal_eval(action['domain'] or '[]') action['domain'].append(('id', 'in', return_docs.ids)) - return action + return action def _document_comment_vals(self): return { diff --git a/l10n_br_fiscal/models/document_fiscal_line_mixin_methods.py b/l10n_br_fiscal/models/document_fiscal_line_mixin_methods.py index 2967dff1c282..6c9cd7ce9154 100644 --- a/l10n_br_fiscal/models/document_fiscal_line_mixin_methods.py +++ b/l10n_br_fiscal/models/document_fiscal_line_mixin_methods.py @@ -281,16 +281,22 @@ def _update_taxes(self): l.inss_wh_tax_id = tax self._set_fields_inss_wh(computed_tax) - @api.onchange("fiscal_operation_id") + def _get_product_price(self): + price = { + 'sale_price': self.product_id.list_price, + 'cost_price': self.product_id.standard_price, + } + + self.price_unit = price.get( + self.fiscal_operation_id.default_price_unit, 0.00) + + @api.onchange('fiscal_operation_id') def _onchange_fiscal_operation_id(self): if self.fiscal_operation_id: - price = { - "sale_price": self.product_id.list_price, - "cost_price": self.product_id.standard_price, - } + if not self.price_unit: + self._get_product_price() - self.price_unit = price.get(self.fiscal_operation_id.default_price_unit, - 0.00) + self._onchange_commercial_quantity() self.fiscal_operation_line_id = self.fiscal_operation_id.line_definition( company=self.company_id, @@ -351,6 +357,7 @@ def _onchange_product_id_fiscal(self): self.service_type_id = False self.uot_id = False + self._get_product_price() self._onchange_fiscal_operation_id() def _set_fields_issqn(self, tax_dict): diff --git a/l10n_br_fiscal/tests/test_fiscal_document_generic.py b/l10n_br_fiscal/tests/test_fiscal_document_generic.py index a1a74305fc78..ee7edda566fb 100644 --- a/l10n_br_fiscal/tests/test_fiscal_document_generic.py +++ b/l10n_br_fiscal/tests/test_fiscal_document_generic.py @@ -10,7 +10,7 @@ class TestFiscalDocumentGeneric(TransactionCase): def setUp(self): - super(TestFiscalDocumentGeneric, self).setUp() + super().setUp() # Contribuinte self.nfe_same_state = self.env.ref( 'l10n_br_fiscal.demo_nfe_same_state' @@ -775,10 +775,9 @@ def test_nfe_sn_export(self): def test_nfe_return(self): """ Test Fiscal Document Return """ - action = self.nfe_same_state.action_create_return() return_id = self.nfe_same_state.browse( - [i[2] for i in action['domain'] if i[0] == 'id']) + [i[2][0] for i in action['domain'] if i[0] == 'id']) self.assertEquals( return_id.fiscal_operation_id.id, From f242311c4e805b5660f8c83536be1393177fde06 Mon Sep 17 00:00:00 2001 From: Luis Malta Date: Wed, 16 Sep 2020 08:40:56 -0300 Subject: [PATCH 005/790] [FIX] CNAE mask --- l10n_br_nfse/models/document_line.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/l10n_br_nfse/models/document_line.py b/l10n_br_nfse/models/document_line.py index 6794587be7ee..0d756d26c60f 100644 --- a/l10n_br_nfse/models/document_line.py +++ b/l10n_br_nfse/models/document_line.py @@ -1,5 +1,8 @@ # Copyright 2020 KMEE INFORMATICA LTDA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +import re from lxml import etree from odoo import api, fields, models @@ -92,5 +95,6 @@ def prepare_line_servico(self): 'codigo_tributacao_municipio': self.city_taxation_code_id.code or '', 'discriminacao': str(self.name[:120] or ''), - 'codigo_cnae': self.cnae_id.code or '', + 'codigo_cnae': re.sub( + '[^0-9]', '', self.cnae_id.code or '') or None, } From e0b5bc9220e44bcb062f699e740fc425a6ebc3a8 Mon Sep 17 00:00:00 2001 From: Luis Malta Date: Wed, 16 Sep 2020 09:06:04 -0300 Subject: [PATCH 006/790] [REF] misc.punctuation_rm in cnae code --- l10n_br_nfse/models/document_line.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/l10n_br_nfse/models/document_line.py b/l10n_br_nfse/models/document_line.py index 0d756d26c60f..92cc2e0c328a 100644 --- a/l10n_br_nfse/models/document_line.py +++ b/l10n_br_nfse/models/document_line.py @@ -5,6 +5,8 @@ import re from lxml import etree +from erpbrasil.base import misc + from odoo import api, fields, models @@ -95,6 +97,5 @@ def prepare_line_servico(self): 'codigo_tributacao_municipio': self.city_taxation_code_id.code or '', 'discriminacao': str(self.name[:120] or ''), - 'codigo_cnae': re.sub( - '[^0-9]', '', self.cnae_id.code or '') or None, + 'codigo_cnae': misc.punctuation_rm(self.cnae_id.code) or None, } From 39ab3e1024bb4502f853d454fc9251be822f627c Mon Sep 17 00:00:00 2001 From: Luis Malta Date: Wed, 16 Sep 2020 10:04:29 -0300 Subject: [PATCH 007/790] [FIX] Flake8 --- l10n_br_nfse/models/document_line.py | 1 - 1 file changed, 1 deletion(-) diff --git a/l10n_br_nfse/models/document_line.py b/l10n_br_nfse/models/document_line.py index 92cc2e0c328a..fdcc9c1ec2e2 100644 --- a/l10n_br_nfse/models/document_line.py +++ b/l10n_br_nfse/models/document_line.py @@ -2,7 +2,6 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import re from lxml import etree from erpbrasil.base import misc From 3b1021b518f68cb6d8162bc7f32fbb3254525864 Mon Sep 17 00:00:00 2001 From: Luis Malta Date: Fri, 18 Sep 2020 10:54:17 -0300 Subject: [PATCH 008/790] [REM] cnae_id related --- l10n_br_nfse/models/document_line.py | 1 - 1 file changed, 1 deletion(-) diff --git a/l10n_br_nfse/models/document_line.py b/l10n_br_nfse/models/document_line.py index fdcc9c1ec2e2..b66f70d349ad 100644 --- a/l10n_br_nfse/models/document_line.py +++ b/l10n_br_nfse/models/document_line.py @@ -28,7 +28,6 @@ class DocumentLine(models.Model): cnae_id = fields.Many2one( comodel='l10n_br_fiscal.cnae', string='CNAE Code', - related='product_id.cnae_id', store=True, ) From b982f4a1062bb395867a9c8205eb8549a7e9cd6a Mon Sep 17 00:00:00 2001 From: Gabriel Cardoso de Faria Date: Thu, 24 Sep 2020 10:46:58 -0300 Subject: [PATCH 009/790] [ADD] Domain to field cnae_id --- l10n_br_nfse/models/document_line.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/l10n_br_nfse/models/document_line.py b/l10n_br_nfse/models/document_line.py index b66f70d349ad..44b617b01ccb 100644 --- a/l10n_br_nfse/models/document_line.py +++ b/l10n_br_nfse/models/document_line.py @@ -28,7 +28,9 @@ class DocumentLine(models.Model): cnae_id = fields.Many2one( comodel='l10n_br_fiscal.cnae', string='CNAE Code', - store=True, + domain="['|', " + "('id', 'in', document_id.company_id.cnae_secondary_ids), " + "('id', '=', document_id.company_id.cnae_main_id)]", ) @api.onchange("product_id") From 56b96c3eb5b8c8ed494fae7622e296cde1811866 Mon Sep 17 00:00:00 2001 From: Luis Malta Date: Fri, 25 Sep 2020 09:53:47 -0300 Subject: [PATCH 010/790] [REF] Move cnae_id to city_taxation_code --- l10n_br_fiscal/models/city_taxation_code.py | 5 +++++ l10n_br_fiscal/models/product_template.py | 4 ---- l10n_br_fiscal/views/city_taxation_code.xml | 3 +++ l10n_br_fiscal/views/product_template_view.xml | 1 - l10n_br_nfse/views/product_template_view.xml | 1 + 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/l10n_br_fiscal/models/city_taxation_code.py b/l10n_br_fiscal/models/city_taxation_code.py index 9abeff26b59e..4666dfc9c859 100644 --- a/l10n_br_fiscal/models/city_taxation_code.py +++ b/l10n_br_fiscal/models/city_taxation_code.py @@ -27,3 +27,8 @@ class CityTaxationCode(models.Model): comodel_name="res.city", domain="[('state_id', '=', state_id)]", ) + + cnae_id = fields.Many2one( + comodel_name='l10n_br_fiscal.cnae', + string='CNAE Code', + ) diff --git a/l10n_br_fiscal/models/product_template.py b/l10n_br_fiscal/models/product_template.py index 194ea0c32b3a..122814747e3b 100644 --- a/l10n_br_fiscal/models/product_template.py +++ b/l10n_br_fiscal/models/product_template.py @@ -99,10 +99,6 @@ def _get_default_ncm_id(self): string='Tax UoM Factor', default=0.00) - cnae_id = fields.Many2one( - comodel_name='l10n_br_fiscal.cnae', - string='CNAE Code') - # TODO add percent of estimate taxes @api.onchange('fiscal_type') diff --git a/l10n_br_fiscal/views/city_taxation_code.xml b/l10n_br_fiscal/views/city_taxation_code.xml index 903d6a9c22c6..b2b28560e567 100644 --- a/l10n_br_fiscal/views/city_taxation_code.xml +++ b/l10n_br_fiscal/views/city_taxation_code.xml @@ -14,6 +14,7 @@ + @@ -28,6 +29,7 @@ + @@ -46,6 +48,7 @@ + diff --git a/l10n_br_fiscal/views/product_template_view.xml b/l10n_br_fiscal/views/product_template_view.xml index c44199fb2dd3..3849bb1b18fa 100644 --- a/l10n_br_fiscal/views/product_template_view.xml +++ b/l10n_br_fiscal/views/product_template_view.xml @@ -51,7 +51,6 @@ - diff --git a/l10n_br_nfse/views/product_template_view.xml b/l10n_br_nfse/views/product_template_view.xml index d728b0fa3ec5..de1bf7d4bb3a 100644 --- a/l10n_br_nfse/views/product_template_view.xml +++ b/l10n_br_nfse/views/product_template_view.xml @@ -20,6 +20,7 @@ + From 5d4a879b612731f1d10436480e8c7fe860165111 Mon Sep 17 00:00:00 2001 From: Luis Malta Date: Fri, 25 Sep 2020 09:56:10 -0300 Subject: [PATCH 011/790] [REF] Document Line cnae onchange --- l10n_br_nfse/models/document_line.py | 21 ++++++++++++++++++--- l10n_br_nfse/views/document_line_view.xml | 3 +++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/l10n_br_nfse/models/document_line.py b/l10n_br_nfse/models/document_line.py index 44b617b01ccb..ade2c520ab29 100644 --- a/l10n_br_nfse/models/document_line.py +++ b/l10n_br_nfse/models/document_line.py @@ -25,14 +25,29 @@ class DocumentLine(models.Model): comodel_name='l10n_br_fiscal.city.taxation.code' ) + cnae_main_id = fields.Many2one( + comodel_name="l10n_br_fiscal.cnae", + related='company_id.cnae_main_id', + string="Main CNAE") + + cnae_secondary_ids = fields.Many2many( + comodel_name="l10n_br_fiscal.cnae", + related='company_id.cnae_secondary_ids', + string="Secondary CNAE") + cnae_id = fields.Many2one( - comodel='l10n_br_fiscal.cnae', + comodel_name='l10n_br_fiscal.cnae', string='CNAE Code', domain="['|', " - "('id', 'in', document_id.company_id.cnae_secondary_ids), " - "('id', '=', document_id.company_id.cnae_main_id)]", + "('id', 'in', cnae_secondary_ids), " + "('id', '=', cnae_main_id)]", ) + @api.onchange("city_taxation_code_id") + def _onchange_city_taxation_code_id(self): + if self.city_taxation_code_id: + self.cnae_id = self.city_taxation_code_id.cnae_id + @api.onchange("product_id") def _onchange_product_id_fiscal(self): super(DocumentLine, self)._onchange_product_id_fiscal() diff --git a/l10n_br_nfse/views/document_line_view.xml b/l10n_br_nfse/views/document_line_view.xml index 772cbbcf628a..1e8b78f90e44 100644 --- a/l10n_br_nfse/views/document_line_view.xml +++ b/l10n_br_nfse/views/document_line_view.xml @@ -14,6 +14,9 @@ + + + From cfed331cc50a0aed83b1fd045166ec455e9fa05b Mon Sep 17 00:00:00 2001 From: Luis Malta Date: Fri, 25 Sep 2020 10:48:13 -0300 Subject: [PATCH 012/790] [REM] cnae_id from product_template onchange --- l10n_br_nfse/models/document_line.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/l10n_br_nfse/models/document_line.py b/l10n_br_nfse/models/document_line.py index ade2c520ab29..5d3cd504a379 100644 --- a/l10n_br_nfse/models/document_line.py +++ b/l10n_br_nfse/models/document_line.py @@ -60,8 +60,6 @@ def _onchange_product_id_fiscal(self): lambda r: r.city_id == company_city_id) if city_id: self.city_taxation_code_id = city_id - if self.product_id and self.product_id.cnae_id: - self.cnae_id = self.product_id.cnae_id def _compute_taxes(self, taxes, cst=None): discount_value = self.discount_value From 7630dbcf5585fe8929bce8fac596a0df62e86959 Mon Sep 17 00:00:00 2001 From: Luis Malta Date: Fri, 25 Sep 2020 10:48:40 -0300 Subject: [PATCH 013/790] [REF] Move cnae_id demo data --- l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml | 1 + l10n_br_fiscal/demo/product_demo.xml | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml b/l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml index ef308ffa8c9c..f14c1c9c4c79 100644 --- a/l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml +++ b/l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml @@ -28,6 +28,7 @@ out + diff --git a/l10n_br_fiscal/demo/product_demo.xml b/l10n_br_fiscal/demo/product_demo.xml index 7274de5d2aa9..eea4906dd026 100644 --- a/l10n_br_fiscal/demo/product_demo.xml +++ b/l10n_br_fiscal/demo/product_demo.xml @@ -281,7 +281,6 @@ - From db899eda3de28cf69a8e2774d20384e3600d0310 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Fri, 25 Sep 2020 14:12:34 +0000 Subject: [PATCH 014/790] [UPD] Update l10n_br_fiscal.pot --- l10n_br_fiscal/i18n/l10n_br_fiscal.pot | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/l10n_br_fiscal/i18n/l10n_br_fiscal.pot b/l10n_br_fiscal/i18n/l10n_br_fiscal.pot index b3a7870e2782..a6b76a7f5454 100644 --- a/l10n_br_fiscal/i18n/l10n_br_fiscal.pot +++ b/l10n_br_fiscal/i18n/l10n_br_fiscal.pot @@ -1607,7 +1607,7 @@ msgid "Cancelar" msgstr "" #. module: l10n_br_fiscal -#: code:addons/l10n_br_fiscal/models/document.py:920 +#: code:addons/l10n_br_fiscal/models/document.py:928 #, python-format msgid "Canceling the document is not allowed: one or more associated documents have already been authorized." msgstr "" @@ -2081,6 +2081,7 @@ msgid "Create a new Comment" msgstr "" #. module: l10n_br_fiscal +#: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_action #: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_cte_in_action #: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_cte_out_action #: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_in_action @@ -2988,6 +2989,7 @@ msgid "Fiscal Data Product Abstract" msgstr "" #. module: l10n_br_fiscal +#: model:ir.actions.act_window,name:l10n_br_fiscal.document_action #: model:ir.actions.act_window,name:l10n_br_fiscal.document_type_action #: model:ir.model,name:l10n_br_fiscal.model_l10n_br_fiscal_document #: model:ir.model.fields,field_description:l10n_br_fiscal.field_l10n_br_fiscal_document_event__fiscal_document_id @@ -5958,7 +5960,7 @@ msgid "Não é permitido faixas sobrepostas!" msgstr "" #. module: l10n_br_fiscal -#: code:addons/l10n_br_fiscal/models/document_workflow.py:187 +#: code:addons/l10n_br_fiscal/models/document_workflow.py:216 #, python-format msgid "Não é possível realizar esta operação,\n" "esta transição não é permitida:\n" @@ -5969,7 +5971,7 @@ msgid "Não é possível realizar esta operação,\n" msgstr "" #. module: l10n_br_fiscal -#: code:addons/l10n_br_fiscal/models/document_workflow.py:113 +#: code:addons/l10n_br_fiscal/models/document_workflow.py:142 #, python-format msgid "Não é possível retornar o documento para em \n" "digitação, quando o mesmo esta na situação: \n" @@ -6010,6 +6012,7 @@ msgid "Odoo helps you easily track all activities\n" msgstr "" #. module: l10n_br_fiscal +#: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_action #: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_cte_in_action #: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_cte_out_action #: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_in_action @@ -7239,7 +7242,7 @@ msgid "Send Email" msgstr "" #. module: l10n_br_fiscal -#: code:addons/l10n_br_fiscal/models/document.py:948 +#: code:addons/l10n_br_fiscal/models/document.py:956 #, python-format msgid "Send Fiscal Document Email Notification" msgstr "" @@ -7996,6 +7999,13 @@ msgstr "" msgid "The closing must be unique for the company in a period of time." msgstr "" +#. module: l10n_br_fiscal +#: code:addons/l10n_br_fiscal/models/document.py:780 +#: code:addons/l10n_br_fiscal/models/document.py:791 +#, python-format +msgid "The fiscal operation {} has no return Fiscal Operation defined" +msgstr "" + #. module: l10n_br_fiscal #: model:ir.model.fields,help:l10n_br_fiscal.field_l10n_br_fiscal_tax__sequence #: model:ir.model.fields,help:l10n_br_fiscal.field_l10n_br_fiscal_tax_group__sequence @@ -8008,7 +8018,7 @@ msgid "The tax is due at the location of the provider establishment, except in t msgstr "" #. module: l10n_br_fiscal -#: code:addons/l10n_br_fiscal/models/document.py:719 +#: code:addons/l10n_br_fiscal/models/document.py:720 #, python-format msgid "There is already a fiscal document with this Serie: {0}, Number: {1} !" msgstr "" From 37aa56b680ac233880df2a8e00593c3259ef314a Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 25 Sep 2020 14:35:14 +0000 Subject: [PATCH 015/790] l10n_br_fiscal 12.0.3.6.0 --- l10n_br_fiscal/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l10n_br_fiscal/__manifest__.py b/l10n_br_fiscal/__manifest__.py index 57e641074196..b41cda46d584 100644 --- a/l10n_br_fiscal/__manifest__.py +++ b/l10n_br_fiscal/__manifest__.py @@ -8,7 +8,7 @@ "license": "AGPL-3", "author": "Akretion, Odoo Community Association (OCA)", "website": "http://github.com/OCA/l10n-brazil", - "version": "12.0.3.5.1", + "version": "12.0.3.6.0", "depends": [ "uom", "decimal_precision", From 1cdea65cddef13e6cb0043c88b848d391d82f12b Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 25 Sep 2020 14:35:24 +0000 Subject: [PATCH 016/790] [UPD] addons table in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c12b4edb11db..002e388909b2 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ addon | version | summary [l10n_br_coa_simple](l10n_br_coa_simple/) | 12.0.2.0.0 | Brazilian Simple Chart of Account [l10n_br_crm](l10n_br_crm/) | 12.0.1.0.0 | Brazilian Localization CRM [l10n_br_currency_rate_update](l10n_br_currency_rate_update/) | 12.0.1.0.0 | Update exchange rates using OCA modules for Brazil -[l10n_br_fiscal](l10n_br_fiscal/) | 12.0.3.5.1 | Brazilian fiscal core module. +[l10n_br_fiscal](l10n_br_fiscal/) | 12.0.3.6.0 | Brazilian fiscal core module. [l10n_br_hr](l10n_br_hr/) | 12.0.1.0.0 | Brazilian Localization HR [l10n_br_hr_contract](l10n_br_hr_contract/) | 12.0.1.0.0 | Brazilian Localization HR Contract [l10n_br_nfse](l10n_br_nfse/) | 12.0.1.3.0 | NFS-e From 0023ed986a0072cf8287a9cfe9d27a8e88ad10b7 Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Fri, 25 Sep 2020 14:35:38 +0000 Subject: [PATCH 017/790] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: l10n-brazil-12.0/l10n-brazil-12.0-l10n_br_fiscal Translate-URL: https://translation.odoo-community.org/projects/l10n-brazil-12-0/l10n-brazil-12-0-l10n_br_fiscal/ --- l10n_br_fiscal/i18n/pt_BR.po | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/l10n_br_fiscal/i18n/pt_BR.po b/l10n_br_fiscal/i18n/pt_BR.po index 793b66bacef9..15a9b754e65b 100644 --- a/l10n_br_fiscal/i18n/pt_BR.po +++ b/l10n_br_fiscal/i18n/pt_BR.po @@ -1673,7 +1673,7 @@ msgid "Cancelar" msgstr "" #. module: l10n_br_fiscal -#: code:addons/l10n_br_fiscal/models/document.py:920 +#: code:addons/l10n_br_fiscal/models/document.py:928 #, python-format msgid "" "Canceling the document is not allowed: one or more associated documents have " @@ -2153,6 +2153,7 @@ msgid "Create a new Comment" msgstr "Criar uma Observação" #. module: l10n_br_fiscal +#: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_action #: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_cte_in_action #: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_cte_out_action #: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_in_action @@ -3068,6 +3069,7 @@ msgid "Fiscal Data Product Abstract" msgstr "Dados Fiscais Abstratos de Produto" #. module: l10n_br_fiscal +#: model:ir.actions.act_window,name:l10n_br_fiscal.document_action #: model:ir.actions.act_window,name:l10n_br_fiscal.document_type_action #: model:ir.model,name:l10n_br_fiscal.model_l10n_br_fiscal_document #: model:ir.model.fields,field_description:l10n_br_fiscal.field_l10n_br_fiscal_document_event__fiscal_document_id @@ -6050,7 +6052,7 @@ msgid "Não é permitido faixas sobrepostas!" msgstr "" #. module: l10n_br_fiscal -#: code:addons/l10n_br_fiscal/models/document_workflow.py:187 +#: code:addons/l10n_br_fiscal/models/document_workflow.py:216 #, python-format msgid "" "Não é possível realizar esta operação,\n" @@ -6062,7 +6064,7 @@ msgid "" msgstr "" #. module: l10n_br_fiscal -#: code:addons/l10n_br_fiscal/models/document_workflow.py:113 +#: code:addons/l10n_br_fiscal/models/document_workflow.py:142 #, python-format msgid "" "Não é possível retornar o documento para em \n" @@ -6108,6 +6110,7 @@ msgid "" msgstr "" #. module: l10n_br_fiscal +#: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_action #: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_cte_in_action #: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_cte_out_action #: model_terms:ir.actions.act_window,help:l10n_br_fiscal.document_in_action @@ -7349,7 +7352,7 @@ msgid "Send Email" msgstr "" #. module: l10n_br_fiscal -#: code:addons/l10n_br_fiscal/models/document.py:948 +#: code:addons/l10n_br_fiscal/models/document.py:956 #, python-format msgid "Send Fiscal Document Email Notification" msgstr "" @@ -8118,6 +8121,13 @@ msgstr "" msgid "The closing must be unique for the company in a period of time." msgstr "" +#. module: l10n_br_fiscal +#: code:addons/l10n_br_fiscal/models/document.py:780 +#: code:addons/l10n_br_fiscal/models/document.py:791 +#, python-format +msgid "The fiscal operation {} has no return Fiscal Operation defined" +msgstr "" + #. module: l10n_br_fiscal #: model:ir.model.fields,help:l10n_br_fiscal.field_l10n_br_fiscal_tax__sequence #: model:ir.model.fields,help:l10n_br_fiscal.field_l10n_br_fiscal_tax_group__sequence @@ -8135,7 +8145,7 @@ msgid "" msgstr "" #. module: l10n_br_fiscal -#: code:addons/l10n_br_fiscal/models/document.py:719 +#: code:addons/l10n_br_fiscal/models/document.py:720 #, python-format msgid "There is already a fiscal document with this Serie: {0}, Number: {1} !" msgstr "" From d8e34c6a4fceb7b9104ad828b733d3383e516675 Mon Sep 17 00:00:00 2001 From: Luis Malta Date: Fri, 25 Sep 2020 12:14:42 -0300 Subject: [PATCH 018/790] [FIX] Document line cnae_id in nfse module --- l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml | 1 - l10n_br_nfse/demo/fiscal_document_demo.xml | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml b/l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml index f14c1c9c4c79..ef308ffa8c9c 100644 --- a/l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml +++ b/l10n_br_fiscal/demo/fiscal_document_nfse_demo.xml @@ -28,7 +28,6 @@ out - diff --git a/l10n_br_nfse/demo/fiscal_document_demo.xml b/l10n_br_nfse/demo/fiscal_document_demo.xml index de0f045a1932..26fd1b448042 100644 --- a/l10n_br_nfse/demo/fiscal_document_demo.xml +++ b/l10n_br_nfse/demo/fiscal_document_demo.xml @@ -10,6 +10,7 @@ + From 79e2052d12a71bbc2192139da62851427b0c4b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Wed, 24 Jun 2020 17:46:01 -0300 Subject: [PATCH 019/790] usability: view_xml creates the XML file if not generated yet --- l10n_br_fiscal/models/document_eletronic.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/l10n_br_fiscal/models/document_eletronic.py b/l10n_br_fiscal/models/document_eletronic.py index 19f64d8f38f7..43e69fb49075 100644 --- a/l10n_br_fiscal/models/document_eletronic.py +++ b/l10n_br_fiscal/models/document_eletronic.py @@ -185,6 +185,9 @@ def _target_new_tab(self, attachment_id): def view_xml(self): xml_file = self.file_xml_autorizacao_id or self.file_xml_id + if not xml_file: + self._document_export() + xml_file = self.file_xml_autorizacao_id or self.file_xml_id return self._target_new_tab(xml_file) def view_pdf(self): From a9e0f1dbe803fb64028708ae190a73457ddf64c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Uch=C3=B4as=20Borges?= Date: Sat, 29 Feb 2020 15:41:32 -0300 Subject: [PATCH 020/790] [ADD] dfe_mdfe views --- l10n_br_fiscal/__manifest__.py | 6 +- l10n_br_fiscal/models/__init__.py | 3 + l10n_br_fiscal/models/dfe/__init__.py | 3 + l10n_br_fiscal/models/dfe/dfe.py | 562 +++++++++++++++++++ l10n_br_fiscal/models/document.py | 5 + l10n_br_fiscal/models/mdfe/__init__.py | 3 + l10n_br_fiscal/models/mdfe/mdfe.py | 384 +++++++++++++ l10n_br_fiscal/security/ir.model.access.csv | 8 +- l10n_br_fiscal/views/dfe/dfe_views.xml | 168 ++++++ l10n_br_fiscal/views/l10n_br_fiscal_menu.xml | 6 + l10n_br_fiscal/views/mdfe/mdfe_views.xml | 151 +++++ 11 files changed, 1297 insertions(+), 2 deletions(-) create mode 100644 l10n_br_fiscal/models/dfe/__init__.py create mode 100644 l10n_br_fiscal/models/dfe/dfe.py create mode 100644 l10n_br_fiscal/models/mdfe/__init__.py create mode 100644 l10n_br_fiscal/models/mdfe/mdfe.py create mode 100644 l10n_br_fiscal/views/dfe/dfe_views.xml create mode 100644 l10n_br_fiscal/views/mdfe/mdfe_views.xml diff --git a/l10n_br_fiscal/__manifest__.py b/l10n_br_fiscal/__manifest__.py index b41cda46d584..4c4ae713005b 100644 --- a/l10n_br_fiscal/__manifest__.py +++ b/l10n_br_fiscal/__manifest__.py @@ -89,7 +89,6 @@ "views/subsequent_operation_view.xml", "views/subsequent_document_view.xml", "views/l10n_br_fiscal_action.xml", - "views/l10n_br_fiscal_menu.xml", "views/uom_uom.xml", "views/operation_dashboard_view.xml", "views/closing.xml", @@ -98,6 +97,11 @@ "views/document_cancel.xml", "views/document_correction.xml", "views/city_taxation_code.xml", + 'views/mdfe/mdfe_views.xml', + 'views/dfe/dfe_views.xml', + + + "views/l10n_br_fiscal_menu.xml", ], "demo": [ # Some demo data is being loaded via post_init_hook in hook file diff --git a/l10n_br_fiscal/models/__init__.py b/l10n_br_fiscal/models/__init__.py index 866554135ca4..11397d56347d 100644 --- a/l10n_br_fiscal/models/__init__.py +++ b/l10n_br_fiscal/models/__init__.py @@ -72,3 +72,6 @@ from . import subsequent_document from . import document_email from . import city_taxation_code + +from . import dfe +from . import mdfe diff --git a/l10n_br_fiscal/models/dfe/__init__.py b/l10n_br_fiscal/models/dfe/__init__.py new file mode 100644 index 000000000000..f7134b596c66 --- /dev/null +++ b/l10n_br_fiscal/models/dfe/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import dfe diff --git a/l10n_br_fiscal/models/dfe/dfe.py b/l10n_br_fiscal/models/dfe/dfe.py new file mode 100644 index 000000000000..6a89b6774fca --- /dev/null +++ b/l10n_br_fiscal/models/dfe/dfe.py @@ -0,0 +1,562 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2020 KMEE INFORMATICA LTDA +# License AGPL-3 or later (http://www.gnu.org/licenses/agpl) +# + +from __future__ import division, print_function, unicode_literals + +import base64 +import re +import gzip +import io + +from datetime import datetime +from lxml import objectify + +import logging + +from odoo.osv import orm +from odoo import _, api, fields, models +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + + +class DFe(models.Model): + _name = "l10n_br_fiscal.dfe" + _description = 'Consult DF-e' + _order = 'id desc' + _rec_name = 'display_name' + + display_name = fields.Char( + compute='_compute_display_name' + ) + company_id = fields.Many2one( + comodel_name='res.company', + string="Company", + required=True, + ) + last_nsu = fields.Char( + string="Last NSU", + size=25, + default='0', + required=True, + ) + last_query = fields.Datetime( + string="Last query", + ) + + recipient_xml_ids = fields.One2many( + comodel_name='l10n_br_fiscal.dfe_xml', + inverse_name='dfe_id', + string="XML Documents", + ) + + @api.multi + def _compute_display_name(self): + for record in self: + record.display_name = '{} - NSU: {}'.format( + record.company_id.name, record.last_nsu) + + imported_document_ids = fields.One2many( + comodel_name='l10n_br_fiscal.document', + inverse_name='dfe_id', + string="Imported Documents", + ) + + imported_dfe_ids = fields.One2many( + comodel_name='l10n_br_fiscal.mdfe', + inverse_name='dfe_id', + string="Manifestações do Destinatário Importadas", + ) + + use_cron = fields.Boolean( + default=False, + string="Download new documents automatically", + help='If activated, allows new manifestations to be automatically ' + 'searched with a Cron', + ) + + @api.multi + def action_gerencia_manifestacoes(self): + + return { + 'name': self.company_id.razao_social, + 'view_mode': 'tree,form', + 'res_model': 'l10n_br_fiscal.mdfe', + 'type': 'ir.actions.act_window', + 'target': 'current', + 'domain': [('company_id', '=', self.company_id.id)], + 'limit': self.env['l10n_br_fiscal.mdfe'].search_count([ + ('company_id', '=', self.company_id.id)]), + } + + def _format_nsu(self, nsu): + # nsu = long(nsu) + return "%015d" % (nsu,) + + def distribuicao_nfe(self, company, last_nsu): + last_nsu = self._format_nsu(last_nsu) + p = company.processador_nfe() + cnpj_partner = re.sub('[^0-9]', '', company.cnpj_cpf) + result = p.consultar_distribuicao( + cnpj_cpf=cnpj_partner, + last_nsu=last_nsu) + + if result.resposta.status == 200: # Webservice ok + if (result.resposta.cStat.valor == '137' or + result.resposta.cStat.valor == '138'): + + nfe_list = [] + for doc in result.resposta.loteDistDFeInt.docZip: + nfe_list.append({ + 'xml': doc.texto, 'NSU': doc.NSU.valor, + 'schema': doc.schema.valor + }) + + return { + 'result': result, + 'code': result.resposta.cStat.valor, + 'message': result.resposta.xMotivo.valor, + 'list_nfe': nfe_list, 'file_returned': result.resposta.xml + } + else: + return { + 'result': result, + 'code': result.resposta.cStat.valor, + 'message': result.resposta.xMotivo.valor, + 'file_sent': result.envio.xml, + 'file_returned': result.resposta.xml + } + else: + return { + 'result': result, + 'code': result.resposta.status, + 'message': result.resposta.reason, + 'file_sent': result.envio.xml, 'file_returned': None + } + + @staticmethod + def _mask_cnpj(cnpj): + if cnpj: + val = re.sub('[^0-9]', '', cnpj) + if len(val) == 14: + cnpj = "%s.%s.%s/%s-%s" % (val[0:2], val[2:5], val[5:8], + val[8:12], val[12:14]) + return cnpj + + @api.multi + def baixa_documentos(self, manifestos=None): + ''' + - Declara Ciência da Emissão para todas as manifestações já recebidas, + - Realiza Download dos XMLs das NF-e + - Cria um sped_documento para cada XML importado + ''' + + # Coletando os erros para caso seja de importância no futuro + erros = [] + + nfe_ids = [] + if not manifestos or isinstance(manifestos, dict): + manifestos = self.env['l10n_br_fiscal.mdfe']. \ + search([('company_id', '=', self.company_id.id)]) + + for manifesto in manifestos: + + if not manifesto.state in ['pendente', 'ciente']: + continue + + elif manifesto.state == 'pendente': + ''' + Aqui é importante tentar manifestar Ciência da + Emissão duas vezes pois existe a possibilidade de uma + manifestação ser importada com a Ciência de Emissão já + declarada, retornando uma mensagem de erro. + A segunda tentativa de chamar o método alterará o campo state + do mesmo para 'ciente', sincronizando com o estado real da + manifestação na receita federal. + ''' + try: + manifesto.action_ciencia_emissao() + except Exception as e: + erros.append(('manifesto', manifesto.id, e)) + + try: + manifesto.action_ciencia_emissao() + except: + erros.append((manifesto.id, e)) + continue + + self.validate_nfe_configuration(self.company_id) + + nfe_result = self.download_nfe(self.company_id, + manifesto.chave) + + if nfe_result['code'] == '138': + nfe = objectify.fromstring(nfe_result['nfe']) + documento = self.env['l10n_br_fiscal.document'].new() + documento.modelo = nfe.NFe.infNFe.ide.mod.text + nfe = documento.le_nfe(xml=nfe_result['nfe']) + + manifesto.documento_id = nfe + + nfe_ids.append(nfe) + + else: + erros.append(('nfe', False, + nfe_result['code'] + ' - ' + + nfe_result['message'])) + + # TODO: Descomentar + # dados = [nfe.id for nfe in nfe_ids] + self.nfe_importada_ids.ids + + # self.update({'nfe_importada_ids': [(6, False, dados)]}) + + return nfe_ids + + def _cron_busca_documentos(self, context=None): + """ Método chamado pelo agendador do sistema, processa + automaticamente a busca de documentos conforme configuração do + sistema. + :param context: + :return: + """ + + consulta_ids = self.env['l10n_br_fiscal.dfe'].search([]) + + for consulta_id in consulta_ids: + if consulta_id.use_cron: + consulta_id.busca_documentos() + + @api.multi + def busca_documentos(self, raise_error=False): + nfe_mdes = [] + xml_ids = [] + company = self.company_id + for consulta in self: + try: + consulta.validate_nfe_configuration(company) + last_nsu = consulta.last_nsu + nfe_result = consulta.distribuicao_nfe( + company, last_nsu) + consulta.last_query = fields.Datetime.now() + _logger.info('%s.busca_documentos(), lastNSU: %s', + company.name, last_nsu) + except Exception: + _logger.error("Erro ao consultar Manifesto", exc_info=True) + if raise_error: + raise UserError( + u'Atenção', + u'Não foi possivel efetuar a consulta!\n ' + u'Verifique o log') + else: + if nfe_result['code'] == '137' or nfe_result['code'] == '138': + env_mde = self.env['l10n_br_fiscal.mdfe'] + env_mde_xml = self.env[ + 'l10n_br_fiscal.dfe_xml'] + + xml_ids.append( + env_mde_xml.create( + { + 'consulta_id': self.id, + 'xml_type': '0', + 'xml': nfe_result['result'].envio.original + }).id + ) + xml_ids.append( + env_mde_xml.create( + { + 'consulta_id': self.id, + 'xml_type': '1', + 'xml': nfe_result['result'].resposta.original + }).id + ) + xml_ids.append( + env_mde_xml.create( + { + 'consulta_id': self.id, + 'xml_type': '2', + 'xml': nfe_result[ + 'result'].resposta.loteDistDFeInt.xml + }).id + ) + + for nfe in nfe_result['list_nfe']: + exists_nsu = self.env['l10n_br_fiscal.mdfe'] + exists_nsu.search( + [('nsu', '=', nfe['NSU']), + ('company_id', '=', company.id), + ]).id + nfe_xml = nfe['xml'].encode('utf-8') + root = objectify.fromstring(nfe_xml) + self.last_nsu = nfe['NSU'] + + if nfe['schema'] == u'procNFe_v3.10.xsd' and \ + not exists_nsu: + chave_nfe = root.protNFe.infProt.chNFe + exists_chnfe = self.env[ + 'l10n_br_fiscal.mdfe'].search( + [('chave', '=', chave_nfe)]).id + + if not exists_chnfe: + cnpj_forn = self._mask_cnpj( + ('%014d' % root.NFe.infNFe.emit.CNPJ)) + partner = self.env['sped.participante'].search( + [('cnpj_cpf', '=', cnpj_forn)]) + + invoice_eletronic = { + 'numero': root.NFe.infNFe.ide.nNF, + 'chave': chave_nfe, + 'nsu': nfe['NSU'], + # 'fornecedor': root.xNome, + 'tipo_operacao': str(root.NFe.infNFe.ide. + tpNF), + 'valor_documento': + root.NFe.infNFe.total.ICMSTot.vNF, + 'state': 'pendente', + 'data_hora_inclusao': datetime.now(), + 'cnpj_cpf': cnpj_forn, + 'ie': root.NFe.infNFe.emit.IE, + 'participante_id': partner.id, + 'data_hora_emissao': datetime.strptime( + str(root.NFe.infNFe.ide.dhEmi)[:19], + '%Y-%m-%dT%H:%M:%S'), + 'company_id': company.id, + 'dfe_id': consulta.id, + 'forma_inclusao': u'Verificação agendada' + } + obj_nfe = env_mde.create(invoice_eletronic) + file_name = 'resumo_nfe-%s.xml' % nfe['NSU'] + self.env['ir.attachment'].create( + { + 'name': file_name, + 'datas': base64.b64encode(nfe_xml), + 'datas_fname': file_name, + 'description': u'NFe via manifesto', + 'res_model': + 'l10n_br_fiscal.mdfe', + 'res_id': obj_nfe.id + }) + + xml_ids.append( + env_mde_xml.create( + { + 'consulta_id': self.id, + 'xml_type': '3', + 'xml': nfe['xml'] + }).id + ) + else: + manifesto = \ + self.env['l10n_br_fiscal.mdfe'] \ + .browse(exists_chnfe) + + if not manifesto.dfe_id: + manifesto.update({ + 'dfe_id': consulta.id, + }) + + elif nfe['schema'] == 'resNFe_v1.01.xsd' and \ + not exists_nsu: + chave_nfe = root.chNFe + exists_chnfe = self.env[ + 'l10n_br_fiscal.mdfe'].search( + [('chave', '=', chave_nfe)]).id + + if not exists_chnfe: + cnpj_forn = self._mask_cnpj( + ('%014d' % root.CNPJ)) + partner = self.env['sped.participante'].search( + [('cnpj_cpf', '=', cnpj_forn)]) + + invoice_eletronic = { + # 'numero': root.NFe.infNFe.ide.nNF, + 'chave': chave_nfe, + 'nsu': nfe['NSU'], + 'fornecedor': root.xNome, + 'tipo_operacao': str(root.tpNF), + 'valor_documento': root.vNF, + 'situacao_nfe': str(root.cSitNFe), + 'state': 'pendente', + 'data_hora_inclusao': datetime.now(), + 'cnpj_cpf': cnpj_forn, + 'ie': root.IE, + 'participante_id': partner.id, + 'data_hora_emissao': datetime.strptime( + str(root.dhEmi)[:19], + '%Y-%m-%dT%H:%M:%S'), + 'company_id': company.id, + 'dfe_id': consulta.id, + 'forma_inclusao': u'Verificação agendada -' + u' manifestada por ' + u'outro ' + u'app' + } + obj_nfe = env_mde.create(invoice_eletronic) + file_name = 'resumo_nfe-%s.xml' % nfe['NSU'] + self.env['ir.attachment'].create( + { + 'name': file_name, + 'datas': base64.b64encode(nfe_xml), + 'datas_fname': file_name, + 'description': u'NFe via manifesto', + 'res_model': + 'l10n_br_fiscal.mdfe', + 'res_id': obj_nfe.id + }) + + xml_ids.append( + env_mde_xml.create( + { + 'consulta_id': self.id, + 'xml_type': '3', + 'xml': nfe['xml'] + }).id + ) + + else: + manifesto = \ + self.env['l10n_br_fiscal.mdfe'] \ + .browse(exists_chnfe) + + if not manifesto.dfe_id: + manifesto.update({ + 'dfe_id': consulta.id, + }) + + nfe_mdes.append(nfe) + + self.write({'recipient_xml_ids': [(6, 0, xml_ids)]}) + + else: + raise models.ValidationError( + nfe_result['code'] + ' - ' + nfe_result['message']) + + return nfe_mdes + + @staticmethod + def validate_nfe_configuration(company): + error = u'As seguintes configurações estão faltando:\n' + + if not company.certificate_nfe_id.arquivo: + error += u'Empresa - Arquivo NF-e A1\n' + if not company.certificate_nfe_id.senha: + error += u'Empresa - Senha NF-e A1\n' + if error != u'As seguintes configurações estão faltando:\n': + raise orm.except_orm(_(u'Validação !'), _(error)) + + def send_event(self, company, nfe_key, method): + p = company.processador_nfe() + cnpj_partner = re.sub('[^0-9]', '', company.cnpj_cpf) + result = {} + if method == 'ciencia_operacao': + result = p.conhecer_operacao_evento( + cnpj=cnpj_partner, + # CNPJ do destinatário/gerador do evento + chave_nfe=nfe_key) + elif method == 'confirma_operacao': + result = p.confirmar_operacao_evento( + cnpj=cnpj_partner, + chave_nfe=nfe_key) + elif method == 'desconhece_operacao': + result = p.desconhecer_operacao_evento( + cnpj=cnpj_partner, + chave_nfe=nfe_key) + elif method == 'nao_realizar_operacao': + result = p.nao_realizar_operacao_evento( + cnpj=cnpj_partner, + chave_nfe=nfe_key) + + if result.resposta.status == 200: # Webservice ok + if result.resposta.cStat.valor == '128': + inf_evento = result.resposta.retEvento[0].infEvento + return { + 'code': inf_evento.cStat.valor, + 'message': inf_evento.xMotivo.valor, + 'file_sent': result.envio.xml, + 'file_returned': result.resposta.xml + } + else: + return { + 'code': result.resposta.cStat.valor, + 'message': result.resposta.xMotivo.valor, + 'file_sent': result.envio.xml, + 'file_returned': result.resposta.xml + } + else: + return { + 'code': result.resposta.status, + 'message': result.resposta.reason, + 'file_sent': result.envio.xml, + 'file_returned': None + } + + @staticmethod + def download_nfe(company, list_nfe): + p = company.processador_nfe() + cnpj_partner = re.sub('[^0-9]', '', company.cnpj_cpf) + + result = p.consultar_distribuicao( + cnpj_cpf=cnpj_partner, + chave_nfe=list_nfe) + + if result.resposta.status == 200: # Webservice ok + if result.resposta.cStat.valor == '138': + nfe_zip = result.resposta.loteDistDFeInt.docZip[ + 0].docZip.valor + orig_file_desc = gzip.GzipFile( + mode='r', + fileobj=io.StringIO( + base64.b64decode(nfe_zip)) + ) + nfe = orig_file_desc.read() + orig_file_desc.close() + + return { + 'code': result.resposta.cStat.valor, + 'message': result.resposta.xMotivo.valor, + 'file_sent': result.envio.xml, + 'file_returned': result.resposta.xml, + 'nfe': nfe, + } + else: + return { + 'code': result.resposta.cStat.valor, + 'message': result.resposta.xMotivo.valor, + 'file_sent': result.envio.xml, + 'file_returned': result.resposta.xml + } + else: + return { + 'code': result.resposta.status, + 'message': result.resposta.reason, + 'file_sent': result.envio.xml, 'file_returned': None + } + + +class DFeXML(models.Model): + _name = 'l10n_br_fiscal.dfe_xml' + _description = 'DF-e XML Document' + + dfe_id = fields.Many2one( + string=u'DF-e Consult', + comodel_name='l10n_br_fiscal.dfe', + readonly=True, + ) + + xml_type = fields.Selection([ + ('0', 'Envio'), + ('1', 'Resposta'), + ('2', 'Resposta-LoteDistDFeInt'), + ('3', 'Resposta-LoteDistDFeInt-DocZip(NFe)') + ], + string="XML Type", + readonly=True, + ) + + xml = fields.Char( + string="XML", + size=5000, + required=True, + ) diff --git a/l10n_br_fiscal/models/document.py b/l10n_br_fiscal/models/document.py index d546d65ed499..50af20f49cc2 100644 --- a/l10n_br_fiscal/models/document.py +++ b/l10n_br_fiscal/models/document.py @@ -662,6 +662,11 @@ def _compute_amount(self): stored=True, ) + dfe_id = fields.Many2one( + comodel_name='l10n_br_fiscal.dfe', + string='DF-e Consult', + ) + # Você não vai poder fazer isso em modelos que já tem state # TODO Porque não usar o campo state do fiscal.document??? state = fields.Selection( diff --git a/l10n_br_fiscal/models/mdfe/__init__.py b/l10n_br_fiscal/models/mdfe/__init__.py new file mode 100644 index 000000000000..0cae35c24f39 --- /dev/null +++ b/l10n_br_fiscal/models/mdfe/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import mdfe diff --git a/l10n_br_fiscal/models/mdfe/mdfe.py b/l10n_br_fiscal/models/mdfe/mdfe.py new file mode 100644 index 000000000000..ca06ddbb149d --- /dev/null +++ b/l10n_br_fiscal/models/mdfe/mdfe.py @@ -0,0 +1,384 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2020 KMEE INFORMATICA LTDA +# License AGPL-3 or later (http://www.gnu.org/licenses/agpl) +# + +from __future__ import division, print_function, unicode_literals + +import logging + +from odoo import _, api, fields, models +import base64 +from lxml import objectify + +_logger = logging.getLogger(__name__) + +SITUACAO_NFE = [ + ("1", "Autorizada"), + ("2", "Cancelada"), + ("3", "Denegada"), +] + +SITUACAO_MANIFESTACAO = [ + ("pendente", "Pendente"), + ("ciente", 'Ciente da operação'), + ("confirmado", 'Confirmada operação'), + ("desconhecido", "Desconhecimento"), + ("nao_realizado", 'Não realizado'), +] + + +class MDFe(models.Model): + _name = "l10n_br_fiscal.mdfe" + _description = 'Recipient Manifestation' + + @api.multi + def name_get(self): + return [(rec.id, + u"NFº: {0} ({1}): {2}".format( + rec.number, rec.cnpj_cpf, rec.company_id.legal_name) + ) for rec in self] + + company_id = fields.Many2one( + comodel_name="res.company", + string="Company", + required=True, + ) + key = fields.Char( + string="Access Key", + size=44, + required=True, + ) + serie = fields.Char( + string="Serie", + size=3, + index=True, + ) + number = fields.Float( + string="Document Number", + index=True, + digits=(18, 0), + ) + document_id = fields.Many2one( + comodel_name="l10n_br_fiscal.document", + string="Fiscal Document", + ) + emitter = fields.Char( + string="Emitter", + size=60, + ) + cnpj_cpf = fields.Char( + string="CNPJ/CPF", + size=18, + ) + + nsu = fields.Char( + string="NSU", + size=25, + select=True, + ) + + operation_type = fields.Selection( + selection=[ + ("0", "Entrada"), + ("1", "Saída") + ], + string="Operation Type", + readonly=True, + ) + + document_value = fields.Float( + string="Document Total Value", + readonly=True, + digits=(18, 2), + ) + + ie = fields.Char( + string="Inscrição estadual", + size=18, + ) + # TODO: Verificar qual comodel_name utilizar + # partner_id = fields.Many2one( + # comodel_name="l10n_br_fiscal.partner.profile", + # string="Supplier", + # invisible=True, + # ) + partner_id = fields.Many2one( + comodel_name="res.partner", + string="Supplier (partner)", + invisible=True, + ) + + supplier = fields.Char( + string="Supplier", + size=60, + index=True, + ) + + emission_datetime = fields.Datetime( + string="Emission Date", + index=True, + default=fields.Datetime.now, + ) + inclusion_datetime = fields.Datetime( + string="Inclusion Date", + index=True, + default=fields.Datetime.now, + ) + authorization_datetime = fields.Datetime( + string="Authorization Date", + index=True, + ) + cancellation_datetime = fields.Datetime( + string="Cancellation Date", + index=True, + ) + digest_value = fields.Char( + string="Digest Value", + size=28, + ) + inclusion_mode = fields.Char( + string="Inclusion Mode", + size=255, + ) + authorization_protocol = fields.Char( + string="Authorization protocol", + size=60, + ) + cancellation_protocol = fields.Char( + string="Cancellation protocol", + size=60, + ) + + document_state = fields.Selection( + string="Document State", + selection=SITUACAO_NFE, + select=True, + readonly=True, + ) + + state = fields.Selection( + string="Manifestation State", + selection=SITUACAO_MANIFESTACAO, + select=True, + readonly=True, + ) + dfe_id = fields.Many2one( + string="DF-e", + comodel_name="l10n_br_fiscal.dfe", + readonly=True, + ) + + @api.multi + def cria_wizard_gerenciamento(self, state=""): + + dados = { + "manifestacao_ids": [(6, 0, self.ids)], + "state": state, + } + + return self.env["wizard.confirma.acao"].create(dados) + + @api.multi + def action_baixa_documento(self): + self.ensure_one() + documento = self.sped_consulta_dfe_id.baixa_documentos(manifestos=self) + + if len(documento) > 1: + view_id = self.env.ref( + "sped_nfe.sped_documento_ajuste_recebimento_tree").id + view_type = "tree" + + else: + view_id = self.env.ref( + "sped_nfe.sped_documento_ajuste_recebimento_form").id + view_type = "form" + + return { + "name": "Baixa documentos", + "view_mode": "form", + "view_type": view_type, + "view_id": view_id, + "res_id": documento.ids, + "res_model": "sped.documento", + "type": "ir.actions.act_window", + "target": "current", + } + + @api.multi + def action_salva_xml(self): + + return self.baixa_attachment( + self.action_baixa_documento() + ) + + @api.multi + def baixa_attachment(self, attachment=None): + + return { + "type": "ir.actions.report.xml", + "report_type": "controller", + "report_file": + "/web/content/ir.attachment/" + str(attachment.id) + + "/datas/" + attachment.display_name + "?download=true", + } + + @api.multi + def action_ciencia_emissao(self): + for record in self: + + record.sped_consulta_dfe_id.validate_nfe_configuration( + record.company_id) + + nfe_result = record.sped_consulta_dfe_id.send_event( + record.company_id, + record.key, + "ciencia_operacao" + ) + if nfe_result["code"] == "135": + record.state = "ciente" + elif nfe_result["code"] == "573": + record.state = "ciente" + else: + raise models.ValidationError( + nfe_result["code"] + ' - ' + nfe_result["message"]) + + return True + + @api.multi + def action_confirmar_operacacao(self): + for record in self: + record.sped_consulta_dfe_id.validate_nfe_configuration( + record.company_id) + nfe_result = record.sped_consulta_dfe_id.send_event( + record.company_id, + record.key, + "confirma_operacao") + + if nfe_result["code"] == "135": + record.state = "confirmado" + else: + raise models.ValidationError(_( + nfe_result["code"] + ' - ' + nfe_result["message"]) + ) + + return True + + @api.multi + def action_operacao_desconhecida(self): + for record in self: + record.sped_consulta_dfe_id.\ + validate_nfe_configuration(record.company_id) + nfe_result = record.sped_consulta_dfe_id.send_event( + record.company_id, + record.key, + "desconhece_operacao") + + if nfe_result["code"] == "135": + record.state = "desconhecido" + else: + raise models.ValidationError(_( + nfe_result["code"] + ' - ' + nfe_result["message"])) + + return True + + @api.multi + def action_negar_operacao(self): + for record in self: + record.sped_consulta_dfe_id.\ + validate_nfe_configuration(record.company_id) + nfe_result = record.sped_consulta_dfe_id.send_event( + record.company_id, + record.key, + "nao_realizar_operacao") + + if nfe_result["code"] == "135": + record.state = "nap_realizado" + else: + raise models.ValidationError(_( + nfe_result["code"] + ' - ' + nfe_result["message"])) + + return True + + @api.multi + def action_download_xmls(self): + + if len(self) == 1: + if self.state == "pendente": + self.action_ciencia_emissao() + + return self.baixa_attachment(self.action_download_xml()) + + attachments = [] + + for record in self: + attachment = record.action_download_xml() + attachments.append(attachment) + + monta_anexo = self.env["sped.monta.anexo"].create([]) + + attachment_id = monta_anexo.monta_anexo_compactado(attachments) + + return self.baixa_attachment(attachment_id) + + @api.multi + def action_download_xml(self): + for record in self: + record.sped_consulta_dfe_id.\ + validate_nfe_configuration(record.company_id) + nfe_result = record.sped_consulta_dfe_id.\ + download_nfe(record.company_id, record.key) + + if nfe_result["code"] == "138": + + file_name = "NFe%s.xml" % record.key + return record.env["ir.attachment"].create( + { + "name": file_name, + "datas": base64.b64encode(nfe_result["nfe"]), + "datas_fname": file_name, + "description": + u'XML NFe - Download manifesto do destinatário', + "res_model": "l10n_br_fiscal.mdfe", + "res_id": record.id + }) + else: + raise models.ValidationError(_( + nfe_result["code"] + ' - ' + nfe_result["message"]) + ) + + @api.multi + def action_importa_xmls(self): + for record in self: + record.sped_consulta_dfe_id.baixa_documentos(manifestos=self) + + @api.multi + def action_importa_xml(self): + for record in self: + record.sped_consulta_dfe_id.\ + validate_nfe_configuration(record.company_id) + nfe_result = record.sped_consulta_dfe_id.\ + download_nfe(record.company_id, record.key) + + if nfe_result["code"] == "138": + nfe = objectify.fromstring(nfe_result["nfe"]) + documento = self.env["sped.documento"].new() + documento.modelo = nfe.NFe.infNFe.ide.mod.text + dados = documento.le_nfe(xml=nfe_result["nfe"]) + record.document_id = dados + return { + "name": _("Associar Pedido de Compras"), + "view_mode": "form", + "view_type": "form", + "view_id": self.env.ref("sped_nfe.sped_documento_ajuste_recebimento_form").id, + "res_id": dados.id, + "res_model": "sped.documento", + "type": "ir.actions.act_window", + "target": "new", + "flags": {"form": {"action_buttons": True, "options": {"mode": "edit"}}}, + } + else: + raise models.ValidationError(_( + nfe_result["code"] + ' - ' + nfe_result["message"]) + ) diff --git a/l10n_br_fiscal/security/ir.model.access.csv b/l10n_br_fiscal/security/ir.model.access.csv index 18c516d83119..d6eab4aa3a86 100644 --- a/l10n_br_fiscal/security/ir.model.access.csv +++ b/l10n_br_fiscal/security/ir.model.access.csv @@ -104,4 +104,10 @@ "l10n_br_fiscal_closing_user","Fiscal Document Event for User","model_l10n_br_fiscal_closing","l10n_br_fiscal.group_user",1,1,1,0 "l10n_br_fiscal_closing_manager","Fiscal Document Event for User","model_l10n_br_fiscal_closing","l10n_br_fiscal.group_manager",1,1,1,1 "l10n_br_fiscal_city_taxation_code_user","Fiscal City Taxation Code for User","model_l10n_br_fiscal_city_taxation_code","l10n_br_fiscal.group_user",1,1,1,0 -"l10n_br_fiscal_city_taxation_code_manager","Fiscal City Taxation Code for Manager","model_l10n_br_fiscal_city_taxation_code","l10n_br_fiscal.group_user",1,1,1,1 \ No newline at end of file +"l10n_br_fiscal_city_taxation_code_manager","Fiscal City Taxation Code for Manager","model_l10n_br_fiscal_city_taxation_code","l10n_br_fiscal.group_user",1,1,1,1 +"l10n_br_fiscal_dfe_user","Consut DFe for User","model_l10n_br_fiscal_dfe","l10n_br_fiscal.group_user",1,1,1,0 +"l10n_br_fiscal_dfe_manager","Consut DFe for Manager","model_l10n_br_fiscal_dfe","l10n_br_fiscal.group_manager",1,1,1,1 +"l10n_br_fiscal_dfe_xml_user","Consut DFe XML for User","model_l10n_br_fiscal_dfe_xml","l10n_br_fiscal.group_user",1,1,1,0 +"l10n_br_fiscal_dfe_xml_manager","Consut DFe XML for Manager","model_l10n_br_fiscal_dfe_xml","l10n_br_fiscal.group_manager",1,1,1,1 +"l10n_br_fiscal_mdfe_user","MDFe for User","model_l10n_br_fiscal_mdfe","l10n_br_fiscal.group_user",1,1,1,0 +"l10n_br_fiscal_mdfe_manager","MDFe for Manager","model_l10n_br_fiscal_mdfe","l10n_br_fiscal.group_manager",1,1,1,1 diff --git a/l10n_br_fiscal/views/dfe/dfe_views.xml b/l10n_br_fiscal/views/dfe/dfe_views.xml new file mode 100644 index 000000000000..67240fba43e3 --- /dev/null +++ b/l10n_br_fiscal/views/dfe/dfe_views.xml @@ -0,0 +1,168 @@ + + + + + + l10n_br_fiscal.dfe.form + l10n_br_fiscal.dfe + 1 + +
+ + + + + + + + + + + + + +
+ +
+ + + + + + diff --git a/payment_cielo/tests/__init__.py b/payment_cielo/tests/__init__.py new file mode 100644 index 000000000000..228d5775da52 --- /dev/null +++ b/payment_cielo/tests/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import test_stripe diff --git a/payment_cielo/tests/test_stripe.py b/payment_cielo/tests/test_stripe.py new file mode 100644 index 000000000000..2d4f4abd36cb --- /dev/null +++ b/payment_cielo/tests/test_stripe.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +import unittest +import odoo +from odoo import fields +from odoo.addons.payment.tests.common import PaymentAcquirerCommon +from odoo.tools import mute_logger + + +class StripeCommon(PaymentAcquirerCommon): + + def setUp(self): + super(StripeCommon, self).setUp() + self.stripe = self.env.ref('payment.payment_acquirer_stripe') + self.stripe.write({ + 'stripe_secret_key': 'sk_test_KJtHgNwt2KS3xM7QJPr4O5E8', + 'stripe_publishable_key': 'pk_test_QSPnimmb4ZhtkEy3Uhdm4S6J', + }) + + +@odoo.tests.tagged('post_install', '-at_install', '-standard', 'external') +class StripeTest(StripeCommon): + + @unittest.skip("") + def test_10_stripe_s2s(self): + self.assertEqual(self.stripe.environment, 'test', 'test without test environment') + + # Add Stripe credentials + self.stripe.write({ + 'stripe_secret_key': 'sk_test_bldAlqh1U24L5HtRF9mBFpK7', + 'stripe_publishable_key': 'pk_test_0TKSyYSZS9AcS4keZ2cxQQCW', + }) + + # Create payment meethod for Stripe + payment_token = self.env['payment.token'].create({ + 'acquirer_id': self.stripe.id, + 'partner_id': self.buyer_id, + 'cc_number': '4242424242424242', + 'cc_expiry': '02 / 26', + 'cc_brand': 'visa', + 'cvc': '111', + 'cc_holder_name': 'Johndoe', + }) + + # Create transaction + tx = self.env['payment.transaction'].create({ + 'reference': 'test_ref_%s' % fields.date.today(), + 'currency_id': self.currency_euro.id, + 'acquirer_id': self.stripe.id, + 'partner_id': self.buyer_id, + 'payment_token_id': payment_token.id, + 'type': 'server2server', + 'amount': 115.0 + }) + tx.stripe_s2s_do_transaction() + + # Check state + self.assertEqual(tx.state, 'done', 'Stripe: Transcation has been discarded.') + + def test_20_stripe_form_render(self): + self.assertEqual(self.stripe.environment, 'test', 'test without test environment') + + # ---------------------------------------- + # Test: button direct rendering + # ---------------------------------------- + + # render the button + tx = self.env['payment.transaction'].create({ + 'acquirer_id': self.stripe.id, + 'amount': 320.0, + 'reference': 'SO404', + 'currency_id': self.currency_euro.id, + }) + self.stripe.render('SO404', 320.0, self.currency_euro.id, values=self.buyer_values).decode('utf-8') + + @unittest.skip( + "as the test is post-install and because payment_strip_sca changes" + "the code logic and is automatically installed, this test is invalid.") + def test_30_stripe_form_management(self): + self.assertEqual(self.stripe.environment, 'test', 'test without test environment') + + # typical data posted by Stripe after client has successfully paid + stripe_post_data = { + u'amount': 470000, + u'amount_refunded': 0, + u'application_fee': None, + u'balance_transaction': u'txn_172xfnGMfVJxozLwssrsQZyT', + u'captured': True, + u'created': 1446529775, + u'currency': u'eur', + u'customer': None, + u'description': None, + u'destination': None, + u'dispute': None, + u'failure_code': None, + u'failure_message': None, + u'fraud_details': {}, + u'id': u'ch_172xfnGMfVJxozLwEjSfpfxD', + u'invoice': None, + u'livemode': False, + u'metadata': {u'reference': u'SO100-1'}, + u'object': u'charge', + u'paid': True, + u'receipt_email': None, + u'receipt_number': None, + u'refunded': False, + u'refunds': {u'data': [], + u'has_more': False, + u'object': u'list', + u'total_count': 0, + u'url': u'/v1/charges/ch_172xfnGMfVJxozLwEjSfpfxD/refunds'}, + u'shipping': None, + u'source': {u'address_city': None, + u'address_country': None, + u'address_line1': None, + u'address_line1_check': None, + u'address_line2': None, + u'address_state': None, + u'address_zip': None, + u'address_zip_check': None, + u'brand': u'Visa', + u'country': u'US', + u'customer': None, + u'cvc_check': u'pass', + u'dynamic_last4': None, + u'exp_month': 2, + u'exp_year': 2022, + u'fingerprint': u'9tJA9bUEuvEb3Ell', + u'funding': u'credit', + u'id': u'card_172xfjGMfVJxozLw1QO6gYNM', + u'last4': u'4242', + u'metadata': {}, + u'name': u'norbert.buyer@example.com', + u'object': u'card', + u'tokenization_method': None}, + u'statement_descriptor': None, + u'status': u'succeeded'} + + tx = self.env['payment.transaction'].create({ + 'amount': 4700.0, + 'acquirer_id': self.stripe.id, + 'currency_id': self.currency_euro.id, + 'reference': 'SO100-1', + 'partner_name': 'Norbert Buyer', + 'partner_country_id': self.country_france.id}) + + # validate it + tx.form_feedback(stripe_post_data, 'stripe') + self.assertEqual(tx.state, 'done', 'Stripe: validation did not put tx into done state') + self.assertEqual(tx.acquirer_reference, stripe_post_data.get('id'), 'Stripe: validation did not update tx id') + stripe_post_data['metadata']['reference'] = u'SO100-2' + # reset tx + tx = self.env['payment.transaction'].create({ + 'amount': 4700.0, + 'acquirer_id': self.stripe.id, + 'currency_id': self.currency_euro.id, + 'reference': 'SO100-2', + 'partner_name': 'Norbert Buyer', + 'partner_country_id': self.country_france.id}) + # simulate an error + stripe_post_data['status'] = 'error' + stripe_post_data.update({u'error': {u'message': u"Your card's expiration year is invalid.", u'code': u'invalid_expiry_year', u'type': u'card_error', u'param': u'exp_year'}}) + with mute_logger('odoo.addons.payment_stripe.models.payment'): + with mute_logger('odoo.addons.payment_stripe_sca.models.payment'): + tx.form_feedback(stripe_post_data, 'stripe') + # check state + self.assertEqual(tx.state, 'cancel', 'Stipe: erroneous validation did not put tx into error state') diff --git a/payment_cielo/views/payment_cielo_templates.xml b/payment_cielo/views/payment_cielo_templates.xml new file mode 100644 index 000000000000..81928051ace5 --- /dev/null +++ b/payment_cielo/views/payment_cielo_templates.xml @@ -0,0 +1,58 @@ + + + + + + + + diff --git a/payment_cielo/views/payment_views.xml b/payment_cielo/views/payment_views.xml new file mode 100644 index 000000000000..d16bee5d5fd8 --- /dev/null +++ b/payment_cielo/views/payment_views.xml @@ -0,0 +1,21 @@ + + + + payment.acquirer.form.inherit + payment.acquirer + + + + + + + + + + + + + + + + From 4f9f700fc06d7d0b55f1ede46929fd3892d937f4 Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Thu, 27 Aug 2020 17:42:53 -0300 Subject: [PATCH 384/790] [FIX] Cielo icon --- payment_cielo/static/src/img/cielo_icon.png | Bin 3237 -> 5323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/payment_cielo/static/src/img/cielo_icon.png b/payment_cielo/static/src/img/cielo_icon.png index 79e4e3cbab8e0fc58c2ab53e2470eb9a2b2214e6..919c5613fcb13373e6297603efeb2f013c6880c0 100644 GIT binary patch literal 5323 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 Js+BFn{|9{oB~JhV literal 3237 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 NhI%J4={o0p{{uDr(eVHP From 220da001c11420a718899cee327e3c46eb6c848a Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Fri, 28 Aug 2020 15:49:13 -0300 Subject: [PATCH 385/790] [FIX] One class - one file --- payment_cielo/models/__init__.py | 4 +- payment_cielo/models/payment.py | 271 -------------------- payment_cielo/models/payment_acquirer.py | 55 ++++ payment_cielo/models/payment_token.py | 119 +++++++++ payment_cielo/models/payment_transaction.py | 122 +++++++++ 5 files changed, 299 insertions(+), 272 deletions(-) delete mode 100644 payment_cielo/models/payment.py create mode 100644 payment_cielo/models/payment_acquirer.py create mode 100644 payment_cielo/models/payment_token.py create mode 100644 payment_cielo/models/payment_transaction.py diff --git a/payment_cielo/models/__init__.py b/payment_cielo/models/__init__.py index ef12533682ff..034ef40c45a2 100644 --- a/payment_cielo/models/__init__.py +++ b/payment_cielo/models/__init__.py @@ -1,3 +1,5 @@ # -*- coding: utf-8 -*- -from . import payment +from . import payment_acquirer +from . import payment_transaction +from . import payment_token diff --git a/payment_cielo/models/payment.py b/payment_cielo/models/payment.py deleted file mode 100644 index 26b35f1b0623..000000000000 --- a/payment_cielo/models/payment.py +++ /dev/null @@ -1,271 +0,0 @@ -# coding: utf-8 - -import logging -import requests -import pprint - -from odoo import api, fields, models, _ -from odoo.addons.payment.models.payment_acquirer import ValidationError -from odoo.exceptions import UserError -from odoo.tools.safe_eval import safe_eval -from odoo.tools.float_utils import float_round - -_logger = logging.getLogger(__name__) - -# TODO: MerchantId e MerchantKey de hml e produção (pegar valor da produção das configurações do payent acquirer -CIELO_HEADERS = { - 'MerchantId': 'be87a4be-a40d-4a2d-b2c8-b8b6cc19cddd', - 'MerchantKey': 'POHAWRXFBSIXTMTFVBCYSKNWZBMOATDNYUQDGBUE', - 'Content-Type': 'application/json', - } -# TODO: INT_CURRENCIES é necessário? -INT_CURRENCIES = [ - u'BRL', u'XAF', u'XPF', u'CLP', u'KMF', u'DJF', u'GNF', u'JPY', u'MGA', u'PYG', u'RWF', u'KRW', - u'VUV', u'VND', u'XOF' -] - - -class PaymentAcquirerCielo(models.Model): - _inherit = 'payment.acquirer' - - provider = fields.Selection(selection_add=[('cielo', 'Cielo')]) - cielo_secret_key = fields.Char(required_if_provider='cielo', groups='base.group_user') - cielo_publishable_key = fields.Char(required_if_provider='cielo', groups='base.group_user') - cielo_image_url = fields.Char( - "Checkout Image URL", groups='base.group_user') - - @api.multi - def cielo_s2s_form_validate(self, data): - self.ensure_one() - - # mandatory fields - for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand"]: - if not data.get(field_name): - return False - return True - - @api.model - def cielo_s2s_form_process(self, data): - payment_token = self.env['payment.token'].sudo().create({ - 'cc_number': data['cc_number'], - 'cc_holder_name': data['cc_holder_name'], - 'cc_expiry': data['cc_expiry'], - 'cc_brand': data['cc_brand'], - 'cvc': data['cvc'], - 'acquirer_id': int(data['acquirer_id']), - 'partner_id': int(data['partner_id']), - }) - return payment_token - # return True - - @api.model - def _get_cielo_api_url(self): - # TODO: Diferenciar URL de testes e produção - # hml apisandbox.cieloecommerce.cielo.com.br - # produção api.cieloecommerce.cielo.com.br - return 'apisandbox.cieloecommerce.cielo.com.br' - - -class PaymentTransactionCielo(models.Model): - _inherit = 'payment.transaction' - - def _create_cielo_charge(self, acquirer_ref=None, tokenid=None, email=None): - api_url_charge = 'https://%s/1/sales' % (self.acquirer_id._get_cielo_api_url()) - - if self.payment_token_id.card_brand == 'mastercard': - self.payment_token_id.card_brand = 'master' - - charge_params = { - # TODO: MerchantOrderId - Numero de identificação do Pedido. - "MerchantOrderId":"2014111703", - "Customer":{ - "Name": self.partner_id.name - }, - "Payment":{ - "Type":"CreditCard", - "Amount": self.amount * 100, - "Installments":1, - # TODO: SoftDescriptor - Texto impresso na fatura bancaria comprador. Deve ser preenchido de acordo com os dados do sub Merchant. - "SoftDescriptor":"123456789ABCD", - "CreditCard":{ - "CardNumber": self.payment_token_id.card_number, - "Holder": self.payment_token_id.card_holder, - "ExpirationDate":self.payment_token_id.card_exp, - "SecurityCode":self.payment_token_id.card_cvc, - "Brand":self.payment_token_id.card_brand, - "CardOnFile":{ - "Usage": "Used", - "Reason":"Unscheduled" - } - } - } - } - - # charge_params = { - # 'amount': int(self.amount if self.currency_id.name in INT_CURRENCIES else float_round(self.amount * 100, 2)), - # 'currency': self.currency_id.name, - # 'metadata[reference]': self.reference, - # 'description': self.reference, - # } - # if acquirer_ref: - # charge_params['customer'] = acquirer_ref - # if tokenid: - # charge_params['card'] = str(tokenid) - # if email: - # charge_params['receipt_email'] = email.strip() - - _logger.info('_create_cielo_charge: Sending values to URL %s, values:\n%s', api_url_charge, pprint.pformat(charge_params)) - r = requests.post(api_url_charge, - auth=(self.acquirer_id.cielo_secret_key, ''), - json=charge_params, - headers=CIELO_HEADERS) - # TODO: Interpretar modelo de retorno em caso de erro (atualmente uma compra não autorizada da erro pois a resposta r não aceita o método json() ) - # TODO: Salvar todos os dados de retorno em seus respectivos campos (talvez criar novos para maior controle) - # TODO: IMPORTANTE deletar informações do cartão e setar active=false pra não aparecer na lista de cartões salvos - res = r.json() - _logger.info('_create_cielo_charge: Values received:\n%s', pprint.pformat(res)) - return res - - @api.multi - def cielo_s2s_do_transaction(self, **kwargs): - self.ensure_one() - result = self._create_cielo_charge(acquirer_ref=self.payment_token_id.acquirer_ref, email=self.partner_email) - return self._cielo_s2s_validate_tree(result) - - - @api.multi - def _cielo_s2s_validate_tree(self, tree): - self.ensure_one() - if self.state != 'draft': - _logger.info('Cielo: trying to validate an already validated tx (ref %s)', self.reference) - return True - - status = tree.get('Payment').get('Status') - if status == 1: - self.write({ - 'date': fields.datetime.now(), - 'acquirer_reference': tree.get('id'), - }) - self._set_transaction_done() - self.execute_callback() - if self.payment_token_id: - self.payment_token_id.verified = True - return True - else: - error = tree.get('Payment').get('ReturnMessage') - _logger.warn(error) - self.sudo().write({ - 'state_message': error, - 'acquirer_reference': tree.get('id'), - 'date': fields.datetime.now(), - }) - self._set_transaction_cancel() - return False - - -class PaymentTokenCielo(models.Model): - _inherit = 'payment.token' - - card_number = fields.Char( - string="Number", - required=False, - ) - - card_holder = fields.Char( - string="Holder", - required=False, - ) - - card_exp = fields.Char( - string="Expiration date", - required=False, - ) - - card_cvc = fields.Char( - string="cvc", - required=False, - ) - - card_brand = fields.Char( - string="Brand", - required=False, - ) - - - @api.model - def cielo_create(self, values): - token = values.get('cielo_token') - description = None - payment_acquirer = self.env['payment.acquirer'].browse(values.get('acquirer_id')) - # when asking to create a token on Stripe servers - if values.get('cc_number'): - payment_params = { - 'card[number]': values['cc_number'].replace(' ', ''), - 'card[exp_month]': str(values['cc_expiry'][:2]), - 'card[exp_year]': str(values['cc_expiry'][-2:]), - 'card[cvc]': values['cvc'], - 'card[name]': values['cc_holder_name'], - } - description = values['cc_holder_name'] - else: - partner_id = self.env['res.partner'].browse(values['partner_id']) - description = 'Partner: %s (id: %s)' % (partner_id.name, partner_id.id) - partner_id = self.env['res.partner'].browse(values['partner_id']) - - # res = self._cielo_create_customer(token, description, payment_acquirer.id) - - customer_params = { - # 'source': token['id'], - 'description': description or token["card"]["name"] - } - - res = { - 'acquirer_ref': partner_id.id, - 'name': 'XXXXXXXXXXXX%s - %s' % (values['cc_number'][-4:], customer_params["description"]), - 'card_number': values['cc_number'].replace(' ', ''), - 'card_exp': str(values['cc_expiry'][:2]) + '/20' + str(values['cc_expiry'][-2:]), - 'card_cvc': values['cvc'], - 'card_holder': values['cc_holder_name'], - 'card_brand': values['cc_brand'], - } - - # pop credit card info to info sent to create - # for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand", "cielo_token"]: - # values.pop(field_name, None) - return res - - - def _cielo_create_customer(self, token, description=None, acquirer_id=None): - # if token.get('error'): - # _logger.error('payment.token.cielo_create_customer: Token error:\n%s', pprint.pformat(token['error'])) - # raise Exception(token['error']['message']) - # - # if token['object'] != 'token': - # _logger.error('payment.token.cielo_create_customer: Cannot create a customer for object type "%s"', token.get('object')) - # raise Exception('We are unable to process your credit card information.') - # - # if token['type'] != 'card': - # _logger.error('payment.token.cielo_create_customer: Cannot create a customer for token type "%s"', token.get('type')) - # raise Exception('We are unable to process your credit card information.') - - payment_acquirer = self.env['payment.acquirer'].browse(acquirer_id or self.acquirer_id.id) - # url_customer = 'https://%s/customers' % payment_acquirer._get_cielo_api_url() - - customer_params = { - # 'source': token['id'], - 'description': description or token["card"]["name"] - } - - customer = r.json() - - # if customer.get('error'): - # _logger.error('payment.token.cielo_create_customer: Customer error:\n%s', pprint.pformat(customer['error'])) - # raise Exception(customer['error']['message']) - # - values = { - 'acquirer_ref': customer['id'], - 'name': 'XXXXXXXXXXXX%s - %s' % (token['card']['last4'], customer_params["description"]) - } - - return True - diff --git a/payment_cielo/models/payment_acquirer.py b/payment_cielo/models/payment_acquirer.py new file mode 100644 index 000000000000..d66a4d47cfc9 --- /dev/null +++ b/payment_cielo/models/payment_acquirer.py @@ -0,0 +1,55 @@ +# coding: utf-8 + +import logging +import requests +import pprint + +from odoo import api, fields, models, _ +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.exceptions import UserError +from odoo.tools.safe_eval import safe_eval +from odoo.tools.float_utils import float_round + +_logger = logging.getLogger(__name__) + + +class PaymentAcquirerCielo(models.Model): + _inherit = 'payment.acquirer' + + provider = fields.Selection(selection_add=[('cielo', 'Cielo')]) + cielo_secret_key = fields.Char(required_if_provider='cielo', groups='base.group_user') + cielo_publishable_key = fields.Char(required_if_provider='cielo', groups='base.group_user') + cielo_image_url = fields.Char( + "Checkout Image URL", groups='base.group_user') + + @api.multi + def cielo_s2s_form_validate(self, data): + self.ensure_one() + + # mandatory fields + for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand"]: + if not data.get(field_name): + return False + return True + + @api.model + def cielo_s2s_form_process(self, data): + payment_token = self.env['payment.token'].sudo().create({ + 'cc_number': data['cc_number'], + 'cc_holder_name': data['cc_holder_name'], + 'cc_expiry': data['cc_expiry'], + 'cc_brand': data['cc_brand'], + 'cvc': data['cvc'], + 'acquirer_id': int(data['acquirer_id']), + 'partner_id': int(data['partner_id']), + }) + return payment_token + # return True + + @api.model + def _get_cielo_api_url(self): + # TODO: Diferenciar URL de testes e produção + # hml apisandbox.cieloecommerce.cielo.com.br + # produção api.cieloecommerce.cielo.com.br + return 'apisandbox.cieloecommerce.cielo.com.br' + diff --git a/payment_cielo/models/payment_token.py b/payment_cielo/models/payment_token.py new file mode 100644 index 000000000000..afbfbb68d871 --- /dev/null +++ b/payment_cielo/models/payment_token.py @@ -0,0 +1,119 @@ +# coding: utf-8 + +import logging +import requests +import pprint + +from odoo import api, fields, models, _ +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.exceptions import UserError +from odoo.tools.safe_eval import safe_eval +from odoo.tools.float_utils import float_round + +_logger = logging.getLogger(__name__) + +class PaymentTokenCielo(models.Model): + _inherit = 'payment.token' + + card_number = fields.Char( + string="Number", + required=False, + ) + + card_holder = fields.Char( + string="Holder", + required=False, + ) + + card_exp = fields.Char( + string="Expiration date", + required=False, + ) + + card_cvc = fields.Char( + string="cvc", + required=False, + ) + + card_brand = fields.Char( + string="Brand", + required=False, + ) + + + @api.model + def cielo_create(self, values): + token = values.get('cielo_token') + description = None + payment_acquirer = self.env['payment.acquirer'].browse(values.get('acquirer_id')) + # when asking to create a token on Stripe servers + if values.get('cc_number'): + payment_params = { + 'card[number]': values['cc_number'].replace(' ', ''), + 'card[exp_month]': str(values['cc_expiry'][:2]), + 'card[exp_year]': str(values['cc_expiry'][-2:]), + 'card[cvc]': values['cvc'], + 'card[name]': values['cc_holder_name'], + } + description = values['cc_holder_name'] + else: + partner_id = self.env['res.partner'].browse(values['partner_id']) + description = 'Partner: %s (id: %s)' % (partner_id.name, partner_id.id) + partner_id = self.env['res.partner'].browse(values['partner_id']) + + # res = self._cielo_create_customer(token, description, payment_acquirer.id) + + customer_params = { + # 'source': token['id'], + 'description': description or token["card"]["name"] + } + + res = { + 'acquirer_ref': partner_id.id, + 'name': 'XXXXXXXXXXXX%s - %s' % (values['cc_number'][-4:], customer_params["description"]), + 'card_number': values['cc_number'].replace(' ', ''), + 'card_exp': str(values['cc_expiry'][:2]) + '/20' + str(values['cc_expiry'][-2:]), + 'card_cvc': values['cvc'], + 'card_holder': values['cc_holder_name'], + 'card_brand': values['cc_brand'], + } + + # pop credit card info to info sent to create + # for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand", "cielo_token"]: + # values.pop(field_name, None) + return res + + + def _cielo_create_customer(self, token, description=None, acquirer_id=None): + # if token.get('error'): + # _logger.error('payment.token.cielo_create_customer: Token error:\n%s', pprint.pformat(token['error'])) + # raise Exception(token['error']['message']) + # + # if token['object'] != 'token': + # _logger.error('payment.token.cielo_create_customer: Cannot create a customer for object type "%s"', token.get('object')) + # raise Exception('We are unable to process your credit card information.') + # + # if token['type'] != 'card': + # _logger.error('payment.token.cielo_create_customer: Cannot create a customer for token type "%s"', token.get('type')) + # raise Exception('We are unable to process your credit card information.') + + payment_acquirer = self.env['payment.acquirer'].browse(acquirer_id or self.acquirer_id.id) + # url_customer = 'https://%s/customers' % payment_acquirer._get_cielo_api_url() + + customer_params = { + # 'source': token['id'], + 'description': description or token["card"]["name"] + } + + customer = r.json() + + # if customer.get('error'): + # _logger.error('payment.token.cielo_create_customer: Customer error:\n%s', pprint.pformat(customer['error'])) + # raise Exception(customer['error']['message']) + # + values = { + 'acquirer_ref': customer['id'], + 'name': 'XXXXXXXXXXXX%s - %s' % (token['card']['last4'], customer_params["description"]) + } + + return True diff --git a/payment_cielo/models/payment_transaction.py b/payment_cielo/models/payment_transaction.py new file mode 100644 index 000000000000..55c9d50f1663 --- /dev/null +++ b/payment_cielo/models/payment_transaction.py @@ -0,0 +1,122 @@ +# coding: utf-8 + +import logging +import requests +import pprint + +from odoo import api, fields, models, _ +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.exceptions import UserError +from odoo.tools.safe_eval import safe_eval +from odoo.tools.float_utils import float_round + +_logger = logging.getLogger(__name__) + +# TODO: MerchantId e MerchantKey de hml e produção (pegar valor da produção das configurações do payent acquirer +CIELO_HEADERS = { + 'MerchantId': 'be87a4be-a40d-4a2d-b2c8-b8b6cc19cddd', + 'MerchantKey': 'POHAWRXFBSIXTMTFVBCYSKNWZBMOATDNYUQDGBUE', + 'Content-Type': 'application/json', + } +# TODO: INT_CURRENCIES é necessário? +INT_CURRENCIES = [ + u'BRL', u'XAF', u'XPF', u'CLP', u'KMF', u'DJF', u'GNF', u'JPY', u'MGA', u'PYG', u'RWF', u'KRW', + u'VUV', u'VND', u'XOF' +] + + +class PaymentTransactionCielo(models.Model): + _inherit = 'payment.transaction' + + def _create_cielo_charge(self, acquirer_ref=None, tokenid=None, email=None): + api_url_charge = 'https://%s/1/sales' % (self.acquirer_id._get_cielo_api_url()) + + if self.payment_token_id.card_brand == 'mastercard': + self.payment_token_id.card_brand = 'master' + + charge_params = { + # TODO: MerchantOrderId - Numero de identificação do Pedido. + "MerchantOrderId":"2014111703", + "Customer":{ + "Name": self.partner_id.name + }, + "Payment":{ + "Type":"CreditCard", + "Amount": self.amount * 100, + "Installments":1, + # TODO: SoftDescriptor - Texto impresso na fatura bancaria comprador. Deve ser preenchido de acordo com os dados do sub Merchant. + "SoftDescriptor":"123456789ABCD", + "CreditCard":{ + "CardNumber": self.payment_token_id.card_number, + "Holder": self.payment_token_id.card_holder, + "ExpirationDate":self.payment_token_id.card_exp, + "SecurityCode":self.payment_token_id.card_cvc, + "Brand":self.payment_token_id.card_brand, + "CardOnFile":{ + "Usage": "Used", + "Reason":"Unscheduled" + } + } + } + } + + # charge_params = { + # 'amount': int(self.amount if self.currency_id.name in INT_CURRENCIES else float_round(self.amount * 100, 2)), + # 'currency': self.currency_id.name, + # 'metadata[reference]': self.reference, + # 'description': self.reference, + # } + # if acquirer_ref: + # charge_params['customer'] = acquirer_ref + # if tokenid: + # charge_params['card'] = str(tokenid) + # if email: + # charge_params['receipt_email'] = email.strip() + + _logger.info('_create_cielo_charge: Sending values to URL %s, values:\n%s', api_url_charge, pprint.pformat(charge_params)) + r = requests.post(api_url_charge, + auth=(self.acquirer_id.cielo_secret_key, ''), + json=charge_params, + headers=CIELO_HEADERS) + # TODO: Interpretar modelo de retorno em caso de erro (atualmente uma compra não autorizada da erro pois a resposta r não aceita o método json() ) + # TODO: Salvar todos os dados de retorno em seus respectivos campos (talvez criar novos para maior controle) + # TODO: IMPORTANTE deletar informações do cartão e setar active=false pra não aparecer na lista de cartões salvos + res = r.json() + _logger.info('_create_cielo_charge: Values received:\n%s', pprint.pformat(res)) + return res + + @api.multi + def cielo_s2s_do_transaction(self, **kwargs): + self.ensure_one() + result = self._create_cielo_charge(acquirer_ref=self.payment_token_id.acquirer_ref, email=self.partner_email) + return self._cielo_s2s_validate_tree(result) + + + @api.multi + def _cielo_s2s_validate_tree(self, tree): + self.ensure_one() + if self.state != 'draft': + _logger.info('Cielo: trying to validate an already validated tx (ref %s)', self.reference) + return True + + status = tree.get('Payment').get('Status') + if status == 1: + self.write({ + 'date': fields.datetime.now(), + 'acquirer_reference': tree.get('id'), + }) + self._set_transaction_done() + self.execute_callback() + if self.payment_token_id: + self.payment_token_id.verified = True + return True + else: + error = tree.get('Payment').get('ReturnMessage') + _logger.warn(error) + self.sudo().write({ + 'state_message': error, + 'acquirer_reference': tree.get('id'), + 'date': fields.datetime.now(), + }) + self._set_transaction_cancel() + return False From 0ad3e686303d3011dcb666588e1e696590e843bb Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Fri, 28 Aug 2020 16:02:14 -0300 Subject: [PATCH 386/790] [ADD] Test/prod API URL --- payment_cielo/models/payment_acquirer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/payment_cielo/models/payment_acquirer.py b/payment_cielo/models/payment_acquirer.py index d66a4d47cfc9..c74a6fe72115 100644 --- a/payment_cielo/models/payment_acquirer.py +++ b/payment_cielo/models/payment_acquirer.py @@ -48,8 +48,8 @@ def cielo_s2s_form_process(self, data): @api.model def _get_cielo_api_url(self): - # TODO: Diferenciar URL de testes e produção - # hml apisandbox.cieloecommerce.cielo.com.br - # produção api.cieloecommerce.cielo.com.br - return 'apisandbox.cieloecommerce.cielo.com.br' + if self.environment == 'test': + return 'apisandbox.cieloecommerce.cielo.com.br' + if self.environment == 'prod': + return 'api.cieloecommerce.cielo.com.br' From 275407baed3e346cb7033180c51a06743941e7d5 Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Fri, 28 Aug 2020 16:09:23 -0300 Subject: [PATCH 387/790] [ADD] Test/prod cielo headers --- payment_cielo/data/payment_acquirer_data.xml | 4 ++-- payment_cielo/models/payment_acquirer.py | 20 ++++++++++++++++++-- payment_cielo/models/payment_transaction.py | 9 +-------- payment_cielo/views/payment_views.xml | 4 ++-- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/payment_cielo/data/payment_acquirer_data.xml b/payment_cielo/data/payment_acquirer_data.xml index a1d00c7f7656..51c78e91b2c3 100644 --- a/payment_cielo/data/payment_acquirer_data.xml +++ b/payment_cielo/data/payment_acquirer_data.xml @@ -12,8 +12,8 @@ test You will be prompted with Cielo Payment page for payment information.

]]>
- dummy - dummy + dummy + dummy
diff --git a/payment_cielo/models/payment_acquirer.py b/payment_cielo/models/payment_acquirer.py index c74a6fe72115..c35a25302527 100644 --- a/payment_cielo/models/payment_acquirer.py +++ b/payment_cielo/models/payment_acquirer.py @@ -17,8 +17,8 @@ class PaymentAcquirerCielo(models.Model): _inherit = 'payment.acquirer' provider = fields.Selection(selection_add=[('cielo', 'Cielo')]) - cielo_secret_key = fields.Char(required_if_provider='cielo', groups='base.group_user') - cielo_publishable_key = fields.Char(required_if_provider='cielo', groups='base.group_user') + cielo_merchant_key = fields.Char(required_if_provider='cielo', groups='base.group_user') + cielo_merchant_id = fields.Char(string='Cielo Merchant Id', required_if_provider='cielo', groups='base.group_user') cielo_image_url = fields.Char( "Checkout Image URL", groups='base.group_user') @@ -53,3 +53,19 @@ def _get_cielo_api_url(self): if self.environment == 'prod': return 'api.cieloecommerce.cielo.com.br' + @api.multi + def _get_cielo_api_headers(self): + if self.environment == 'test': + CIELO_HEADERS = { + 'MerchantId': 'be87a4be-a40d-4a2d-b2c8-b8b6cc19cddd', + 'MerchantKey': 'POHAWRXFBSIXTMTFVBCYSKNWZBMOATDNYUQDGBUE', + 'Content-Type': 'application/json', + } + if self.environment == 'prod': + CIELO_HEADERS = { + 'MerchantId': self.cielo_merchant_id, + 'MerchantKey': self.cielo_merchant_key, + 'Content-Type': 'application/json', + } + return CIELO_HEADERS + diff --git a/payment_cielo/models/payment_transaction.py b/payment_cielo/models/payment_transaction.py index 55c9d50f1663..c58761ddc81c 100644 --- a/payment_cielo/models/payment_transaction.py +++ b/payment_cielo/models/payment_transaction.py @@ -12,12 +12,6 @@ _logger = logging.getLogger(__name__) -# TODO: MerchantId e MerchantKey de hml e produção (pegar valor da produção das configurações do payent acquirer -CIELO_HEADERS = { - 'MerchantId': 'be87a4be-a40d-4a2d-b2c8-b8b6cc19cddd', - 'MerchantKey': 'POHAWRXFBSIXTMTFVBCYSKNWZBMOATDNYUQDGBUE', - 'Content-Type': 'application/json', - } # TODO: INT_CURRENCIES é necessário? INT_CURRENCIES = [ u'BRL', u'XAF', u'XPF', u'CLP', u'KMF', u'DJF', u'GNF', u'JPY', u'MGA', u'PYG', u'RWF', u'KRW', @@ -75,9 +69,8 @@ def _create_cielo_charge(self, acquirer_ref=None, tokenid=None, email=None): _logger.info('_create_cielo_charge: Sending values to URL %s, values:\n%s', api_url_charge, pprint.pformat(charge_params)) r = requests.post(api_url_charge, - auth=(self.acquirer_id.cielo_secret_key, ''), json=charge_params, - headers=CIELO_HEADERS) + headers=self.acquirer_id._get_cielo_api_headers()) # TODO: Interpretar modelo de retorno em caso de erro (atualmente uma compra não autorizada da erro pois a resposta r não aceita o método json() ) # TODO: Salvar todos os dados de retorno em seus respectivos campos (talvez criar novos para maior controle) # TODO: IMPORTANTE deletar informações do cartão e setar active=false pra não aparecer na lista de cartões salvos diff --git a/payment_cielo/views/payment_views.xml b/payment_cielo/views/payment_views.xml index d16bee5d5fd8..b4e06be258ee 100644 --- a/payment_cielo/views/payment_views.xml +++ b/payment_cielo/views/payment_views.xml @@ -7,8 +7,8 @@ - - + + From d5cd11c1c92c868d468857d87ad2d81228cd3136 Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Fri, 28 Aug 2020 17:50:37 -0300 Subject: [PATCH 388/790] [FIX] Cielo error message interpreter --- payment_cielo/models/payment_transaction.py | 40 +++++++++++++-------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/payment_cielo/models/payment_transaction.py b/payment_cielo/models/payment_transaction.py index c58761ddc81c..97a957904b90 100644 --- a/payment_cielo/models/payment_transaction.py +++ b/payment_cielo/models/payment_transaction.py @@ -71,7 +71,6 @@ def _create_cielo_charge(self, acquirer_ref=None, tokenid=None, email=None): r = requests.post(api_url_charge, json=charge_params, headers=self.acquirer_id._get_cielo_api_headers()) - # TODO: Interpretar modelo de retorno em caso de erro (atualmente uma compra não autorizada da erro pois a resposta r não aceita o método json() ) # TODO: Salvar todos os dados de retorno em seus respectivos campos (talvez criar novos para maior controle) # TODO: IMPORTANTE deletar informações do cartão e setar active=false pra não aparecer na lista de cartões salvos res = r.json() @@ -92,23 +91,34 @@ def _cielo_s2s_validate_tree(self, tree): _logger.info('Cielo: trying to validate an already validated tx (ref %s)', self.reference) return True - status = tree.get('Payment').get('Status') - if status == 1: - self.write({ - 'date': fields.datetime.now(), - 'acquirer_reference': tree.get('id'), - }) - self._set_transaction_done() - self.execute_callback() - if self.payment_token_id: - self.payment_token_id.verified = True - return True - else: - error = tree.get('Payment').get('ReturnMessage') + if type(tree) != list: + status = tree.get('Payment').get('Status') + if status == 1: + self.write({ + 'date': fields.datetime.now(), + 'acquirer_reference': tree.get('id'), + }) + self._set_transaction_done() + self.execute_callback() + if self.payment_token_id: + self.payment_token_id.verified = True + return True + else: + error = tree.get('Payment').get('ReturnMessage') + _logger.warn(error) + self.sudo().write({ + 'state_message': error, + 'acquirer_reference': tree.get('id'), + 'date': fields.datetime.now(), + }) + self._set_transaction_cancel() + return False + + elif type(tree) == list: + error = tree[0].get('Message') _logger.warn(error) self.sudo().write({ 'state_message': error, - 'acquirer_reference': tree.get('id'), 'date': fields.datetime.now(), }) self._set_transaction_cancel() From 026f765c77fc55a7d516fccb6537db29991f710d Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Mon, 31 Aug 2020 12:36:31 -0300 Subject: [PATCH 389/790] [FIX] Copyright --- payment_cielo/__init__.py | 4 ++-- payment_cielo/controllers/__init__.py | 3 ++- payment_cielo/controllers/main.py | 4 +++- payment_cielo/models/__init__.py | 3 ++- payment_cielo/models/payment_acquirer.py | 3 ++- payment_cielo/models/payment_token.py | 3 ++- payment_cielo/models/payment_transaction.py | 3 ++- 7 files changed, 15 insertions(+), 8 deletions(-) diff --git a/payment_cielo/__init__.py b/payment_cielo/__init__.py index a20cff5db4f7..bef4297231dc 100644 --- a/payment_cielo/__init__.py +++ b/payment_cielo/__init__.py @@ -1,5 +1,5 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. +# Copyright 2020 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import models from . import controllers diff --git a/payment_cielo/controllers/__init__.py b/payment_cielo/controllers/__init__.py index 65a8c12013d2..f97bf3f7a146 100644 --- a/payment_cielo/controllers/__init__.py +++ b/payment_cielo/controllers/__init__.py @@ -1,3 +1,4 @@ -# -*- coding: utf-8 -*- +# Copyright 2020 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import main diff --git a/payment_cielo/controllers/main.py b/payment_cielo/controllers/main.py index 16810338700d..889a0dda04b0 100644 --- a/payment_cielo/controllers/main.py +++ b/payment_cielo/controllers/main.py @@ -1,4 +1,6 @@ -# -*- coding: utf-8 -*- +# Copyright 2020 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + import logging import pprint import werkzeug diff --git a/payment_cielo/models/__init__.py b/payment_cielo/models/__init__.py index 034ef40c45a2..0e1f599f1eb8 100644 --- a/payment_cielo/models/__init__.py +++ b/payment_cielo/models/__init__.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +# Copyright 2020 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import payment_acquirer from . import payment_transaction diff --git a/payment_cielo/models/payment_acquirer.py b/payment_cielo/models/payment_acquirer.py index c35a25302527..9423a736079e 100644 --- a/payment_cielo/models/payment_acquirer.py +++ b/payment_cielo/models/payment_acquirer.py @@ -1,4 +1,5 @@ -# coding: utf-8 +# Copyright 2020 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging import requests diff --git a/payment_cielo/models/payment_token.py b/payment_cielo/models/payment_token.py index afbfbb68d871..22e07ff01c2e 100644 --- a/payment_cielo/models/payment_token.py +++ b/payment_cielo/models/payment_token.py @@ -1,4 +1,5 @@ -# coding: utf-8 +# Copyright 2020 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging import requests diff --git a/payment_cielo/models/payment_transaction.py b/payment_cielo/models/payment_transaction.py index 97a957904b90..a4b87d0cebd6 100644 --- a/payment_cielo/models/payment_transaction.py +++ b/payment_cielo/models/payment_transaction.py @@ -1,4 +1,5 @@ -# coding: utf-8 +# Copyright 2020 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging import requests From 421a98ce285216b824d159045cba18af37e3db4e Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Mon, 31 Aug 2020 12:43:16 -0300 Subject: [PATCH 390/790] [REF] Cleaner code --- payment_cielo/controllers/main.py | 4 ++-- payment_cielo/models/payment_token.py | 2 +- payment_cielo/static/src/js/cielo.js | 8 +++----- payment_cielo/tests/__init__.py | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/payment_cielo/controllers/main.py b/payment_cielo/controllers/main.py index 889a0dda04b0..7aa93c73f923 100644 --- a/payment_cielo/controllers/main.py +++ b/payment_cielo/controllers/main.py @@ -12,7 +12,7 @@ _logger = logging.getLogger(__name__) -class StripeController(http.Controller): +class CieloController(http.Controller): @http.route(['/payment/cielo'], type='json', auth='public') def payment_cielo(self, **kwargs): @@ -124,7 +124,7 @@ def cielo_create_charge(self, **post): response = tx._create_cielo_charge(acquirer_ref=payment_token_id.acquirer_ref, email=cielo_token['email']) else: response = tx._create_cielo_charge(tokenid=cielo_token['id'], email=cielo_token['email']) - _logger.info('Stripe: entering form_feedback with post data %s', pprint.pformat(response)) + _logger.info('Cielo: entering form_feedback with post data %s', pprint.pformat(response)) if response: request.env['payment.transaction'].sudo().with_context(lang=None).form_feedback(response, 'cielo') # add the payment transaction into the session to let the page /payment/process to handle it diff --git a/payment_cielo/models/payment_token.py b/payment_cielo/models/payment_token.py index 22e07ff01c2e..fc11f4c45d3e 100644 --- a/payment_cielo/models/payment_token.py +++ b/payment_cielo/models/payment_token.py @@ -47,7 +47,7 @@ def cielo_create(self, values): token = values.get('cielo_token') description = None payment_acquirer = self.env['payment.acquirer'].browse(values.get('acquirer_id')) - # when asking to create a token on Stripe servers + if values.get('cc_number'): payment_params = { 'card[number]': values['cc_number'].replace(' ', ''), diff --git a/payment_cielo/static/src/js/cielo.js b/payment_cielo/static/src/js/cielo.js index b243a19efbcf..ee90d3fe25a5 100644 --- a/payment_cielo/static/src/js/cielo.js +++ b/payment_cielo/static/src/js/cielo.js @@ -10,8 +10,6 @@ odoo.define('payment_cielo.cielo', function(require) { // Request token cielo via backend - // The following currencies are integer only, see - // https://stripe.com/docs/currencies#zero-decimal var int_currencies = [ 'BIF', 'XAF', 'XPF', 'CLP', 'KMF', 'DJF', 'GNF', 'JPY', 'MGA', 'PYG', 'RWF', 'KRW', 'VUV', 'VND', 'XOF' @@ -19,13 +17,13 @@ odoo.define('payment_cielo.cielo', function(require) { if ($.blockUI) { // our message needs to appear above the modal dialog - $.blockUI.defaults.baseZ = 2147483647; //same z-index as StripeCheckout + $.blockUI.defaults.baseZ = 2147483647; $.blockUI.defaults.css.border = '0'; $.blockUI.defaults.css["background-color"] = ''; $.blockUI.defaults.overlayCSS["opacity"] = '0.9'; } var cieloHandler; - function getStripeHandler() + function getCieloHandler() { if (cieloHandler) { return cieloHandler; @@ -125,7 +123,7 @@ odoo.define('payment_cielo.cielo', function(require) { // Restore 'Pay Now' button HTML since data might have changed it. $(provider_form[0]).find('#pay_cielo').replaceWith($pay_cielo); }).done(function () { - getStripeHandler().open({ + getCieloHandler().open({ name: merchant, description: invoice_num, email: email, diff --git a/payment_cielo/tests/__init__.py b/payment_cielo/tests/__init__.py index 228d5775da52..22736056c8df 100644 --- a/payment_cielo/tests/__init__.py +++ b/payment_cielo/tests/__init__.py @@ -1,2 +1,2 @@ # -*- coding: utf-8 -*- -from . import test_stripe +from . import test_cielo From 601e79786e837f0ab3943ed8bbc8c7e108b3fbd5 Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Mon, 31 Aug 2020 15:07:12 -0300 Subject: [PATCH 391/790] [FIX] Manifest version --- payment_cielo/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payment_cielo/__manifest__.py b/payment_cielo/__manifest__.py index 158f0c1f3d2f..d525466e0836 100644 --- a/payment_cielo/__manifest__.py +++ b/payment_cielo/__manifest__.py @@ -5,7 +5,7 @@ 'name': 'Payent Cielo', 'summary': """ Payment Acquirer: Cielo Implementation""", - 'version': '12', + 'version': '12.0.3.2.0', 'license': 'AGPL-3', 'author': 'KMEE INFORMATICA LTDA,Odoo Community Association (OCA)', 'website': 'www.kmee.com.br', From 7ca7a5585e1aded050122e3ffa7749451e1e5849 Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Mon, 31 Aug 2020 15:20:14 -0300 Subject: [PATCH 392/790] [FIX] Delete card information and set active false (not using saved payment information) --- payment_cielo/models/payment_transaction.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/payment_cielo/models/payment_transaction.py b/payment_cielo/models/payment_transaction.py index a4b87d0cebd6..db76ba6dab83 100644 --- a/payment_cielo/models/payment_transaction.py +++ b/payment_cielo/models/payment_transaction.py @@ -55,6 +55,11 @@ def _create_cielo_charge(self, acquirer_ref=None, tokenid=None, email=None): } } + self.payment_token_id.card_number = '' + self.payment_token_id.card_exp = '' + self.payment_token_id.card_cvc = '' + self.payment_token_id.active = False + # charge_params = { # 'amount': int(self.amount if self.currency_id.name in INT_CURRENCIES else float_round(self.amount * 100, 2)), # 'currency': self.currency_id.name, From 901648c9e3539fab61583f171392645c661d1d8b Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Mon, 31 Aug 2020 15:52:18 -0300 Subject: [PATCH 393/790] [ADD] New test structure --- payment_cielo/tests/__init__.py | 4 +- .../tests/{test_stripe.py => test_cielo.py} | 80 +++++++++---------- 2 files changed, 43 insertions(+), 41 deletions(-) rename payment_cielo/tests/{test_stripe.py => test_cielo.py} (65%) diff --git a/payment_cielo/tests/__init__.py b/payment_cielo/tests/__init__.py index 22736056c8df..b4328d2b076c 100644 --- a/payment_cielo/tests/__init__.py +++ b/payment_cielo/tests/__init__.py @@ -1,2 +1,4 @@ -# -*- coding: utf-8 -*- +# Copyright 2020 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + from . import test_cielo diff --git a/payment_cielo/tests/test_stripe.py b/payment_cielo/tests/test_cielo.py similarity index 65% rename from payment_cielo/tests/test_stripe.py rename to payment_cielo/tests/test_cielo.py index 2d4f4abd36cb..c438d1bcead9 100644 --- a/payment_cielo/tests/test_stripe.py +++ b/payment_cielo/tests/test_cielo.py @@ -1,4 +1,6 @@ -# -*- coding: utf-8 -*- +# Copyright 2020 KMEE INFORMATICA LTDA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + import unittest import odoo from odoo import fields @@ -6,35 +8,35 @@ from odoo.tools import mute_logger -class StripeCommon(PaymentAcquirerCommon): +class CieloCommon(PaymentAcquirerCommon): def setUp(self): - super(StripeCommon, self).setUp() - self.stripe = self.env.ref('payment.payment_acquirer_stripe') - self.stripe.write({ - 'stripe_secret_key': 'sk_test_KJtHgNwt2KS3xM7QJPr4O5E8', - 'stripe_publishable_key': 'pk_test_QSPnimmb4ZhtkEy3Uhdm4S6J', + super(CieloCommon, self).setUp() + self.cielo = self.env.ref('payment_cielo.payment_acquirer_cielo') + self.cielo.write({ + 'cielo_merchant_id': 'be87a4be-a40d-4a2d-b2c8-b8b6cc19cddd', + 'cielo_merchant_key': 'POHAWRXFBSIXTMTFVBCYSKNWZBMOATDNYUQDGBUE', }) @odoo.tests.tagged('post_install', '-at_install', '-standard', 'external') -class StripeTest(StripeCommon): +class CieloTest(CieloCommon): @unittest.skip("") - def test_10_stripe_s2s(self): - self.assertEqual(self.stripe.environment, 'test', 'test without test environment') + def test_10_cielo_s2s(self): + self.assertEqual(self.cielo.environment, 'test', 'test without test environment') - # Add Stripe credentials - self.stripe.write({ - 'stripe_secret_key': 'sk_test_bldAlqh1U24L5HtRF9mBFpK7', - 'stripe_publishable_key': 'pk_test_0TKSyYSZS9AcS4keZ2cxQQCW', + # Add Cielo credentials + self.cielo.write({ + 'cielo_merchant_id': 'be87a4be-a40d-4a2d-b2c8-b8b6cc19cddd', + 'cielo_merchant_key': 'POHAWRXFBSIXTMTFVBCYSKNWZBMOATDNYUQDGBUE', }) - # Create payment meethod for Stripe + # Create payment meethod for Cielo payment_token = self.env['payment.token'].create({ - 'acquirer_id': self.stripe.id, + 'acquirer_id': self.cielo.id, 'partner_id': self.buyer_id, - 'cc_number': '4242424242424242', + 'cc_number': '4024007197692931', 'cc_expiry': '02 / 26', 'cc_brand': 'visa', 'cvc': '111', @@ -45,19 +47,17 @@ def test_10_stripe_s2s(self): tx = self.env['payment.transaction'].create({ 'reference': 'test_ref_%s' % fields.date.today(), 'currency_id': self.currency_euro.id, - 'acquirer_id': self.stripe.id, + 'acquirer_id': self.cielo.id, 'partner_id': self.buyer_id, 'payment_token_id': payment_token.id, 'type': 'server2server', 'amount': 115.0 }) - tx.stripe_s2s_do_transaction() + tx.cielo_s2s_do_transaction() - # Check state - self.assertEqual(tx.state, 'done', 'Stripe: Transcation has been discarded.') - def test_20_stripe_form_render(self): - self.assertEqual(self.stripe.environment, 'test', 'test without test environment') + def test_20_cielo_form_render(self): + self.assertEqual(self.cielo.environment, 'test', 'test without test environment') # ---------------------------------------- # Test: button direct rendering @@ -65,21 +65,21 @@ def test_20_stripe_form_render(self): # render the button tx = self.env['payment.transaction'].create({ - 'acquirer_id': self.stripe.id, + 'acquirer_id': self.cielo.id, 'amount': 320.0, 'reference': 'SO404', 'currency_id': self.currency_euro.id, }) - self.stripe.render('SO404', 320.0, self.currency_euro.id, values=self.buyer_values).decode('utf-8') + self.cielo.render('SO404', 320.0, self.currency_euro.id, values=self.buyer_values).decode('utf-8') @unittest.skip( "as the test is post-install and because payment_strip_sca changes" "the code logic and is automatically installed, this test is invalid.") - def test_30_stripe_form_management(self): - self.assertEqual(self.stripe.environment, 'test', 'test without test environment') + def test_30_cielo_form_management(self): + self.assertEqual(self.cielo.environment, 'test', 'test without test environment') - # typical data posted by Stripe after client has successfully paid - stripe_post_data = { + # typical data posted by Cielo after client has successfully paid + cielo_post_data = { u'amount': 470000, u'amount_refunded': 0, u'application_fee': None, @@ -137,30 +137,30 @@ def test_30_stripe_form_management(self): tx = self.env['payment.transaction'].create({ 'amount': 4700.0, - 'acquirer_id': self.stripe.id, + 'acquirer_id': self.cielo.id, 'currency_id': self.currency_euro.id, 'reference': 'SO100-1', 'partner_name': 'Norbert Buyer', 'partner_country_id': self.country_france.id}) # validate it - tx.form_feedback(stripe_post_data, 'stripe') - self.assertEqual(tx.state, 'done', 'Stripe: validation did not put tx into done state') - self.assertEqual(tx.acquirer_reference, stripe_post_data.get('id'), 'Stripe: validation did not update tx id') - stripe_post_data['metadata']['reference'] = u'SO100-2' + tx.form_feedback(cielo_post_data, 'Cielo') + self.assertEqual(tx.state, 'done', 'Cielo: validation did not put tx into done state') + self.assertEqual(tx.acquirer_reference, cielo_post_data.get('id'), 'cielo: validation did not update tx id') + cielo_post_data['metadata']['reference'] = u'SO100-2' # reset tx tx = self.env['payment.transaction'].create({ 'amount': 4700.0, - 'acquirer_id': self.stripe.id, + 'acquirer_id': self.cielo.id, 'currency_id': self.currency_euro.id, 'reference': 'SO100-2', 'partner_name': 'Norbert Buyer', 'partner_country_id': self.country_france.id}) # simulate an error - stripe_post_data['status'] = 'error' - stripe_post_data.update({u'error': {u'message': u"Your card's expiration year is invalid.", u'code': u'invalid_expiry_year', u'type': u'card_error', u'param': u'exp_year'}}) - with mute_logger('odoo.addons.payment_stripe.models.payment'): - with mute_logger('odoo.addons.payment_stripe_sca.models.payment'): - tx.form_feedback(stripe_post_data, 'stripe') + cielo_post_data['status'] = 'error' + cielo_post_data.update({u'error': {u'message': u"Your card's expiration year is invalid.", u'code': u'invalid_expiry_year', u'type': u'card_error', u'param': u'exp_year'}}) + with mute_logger('odoo.addons.payment_cielo.models.payment'): + with mute_logger('odoo.addons.payment_cielo_sca.models.payment'): + tx.form_feedback(cielo_post_data, 'cielo') # check state self.assertEqual(tx.state, 'cancel', 'Stipe: erroneous validation did not put tx into error state') From f84092bf964d7b2b6dec617691cfc91b4df331da Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Mon, 31 Aug 2020 16:35:03 -0300 Subject: [PATCH 394/790] [REF] Clean code --- payment_cielo/README.rst | 2 +- payment_cielo/controllers/main.py | 46 ++++---- payment_cielo/models/payment_acquirer.py | 19 ++-- payment_cielo/models/payment_token.py | 120 +++++++++++--------- payment_cielo/models/payment_transaction.py | 86 +++++++------- payment_cielo/tests/test_cielo.py | 32 ++++-- 6 files changed, 169 insertions(+), 136 deletions(-) diff --git a/payment_cielo/README.rst b/payment_cielo/README.rst index 7a12fa79bf97..697712400353 100644 --- a/payment_cielo/README.rst +++ b/payment_cielo/README.rst @@ -43,7 +43,7 @@ To use this module, you need to: Known issues / Roadmap ====================== -* ... +TODO Bug Tracker =========== diff --git a/payment_cielo/controllers/main.py b/payment_cielo/controllers/main.py index 7aa93c73f923..d6f14c109fcd 100644 --- a/payment_cielo/controllers/main.py +++ b/payment_cielo/controllers/main.py @@ -14,16 +14,13 @@ class CieloController(http.Controller): - @http.route(['/payment/cielo'], type='json', auth='public') - def payment_cielo(self, **kwargs): - print('paying with cielo') - pass - - @http.route(['/payment/cielo/s2s/create_json_3ds'], type='json', auth='public', csrf=False) + @http.route(['/payment/cielo/s2s/create_json_3ds'], type='json', + auth='public', csrf=False) def cielo_s2s_create_json_3ds(self, verify_validity=False, **kwargs): if not kwargs.get('partner_id'): kwargs = dict(kwargs, partner_id=request.env.user.partner_id.id) - token = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id'))).s2s_process(kwargs) + token = request.env['payment.acquirer'].browse( + int(kwargs.get('acquirer_id'))).s2s_process(kwargs) if not token: res = { @@ -65,16 +62,20 @@ def cielo_s2s_create(self, **post): return_url = post.get('return_url', '/') if error: - separator = '?' if werkzeug.urls.url_parse(return_url).query == '' else '&' - return_url += '{}{}'.format(separator, werkzeug.urls.url_encode({'error': error})) + separator = '?' if werkzeug.urls.url_parse( + return_url).query == '' else '&' + return_url += '{}{}'.format(separator, werkzeug.urls.url_encode( + {'error': error})) return werkzeug.utils.redirect(return_url) - @http.route(['/payment/cielo/s2s/create_json_3ds'], type='json', auth='public', csrf=False) + @http.route(['/payment/cielo/s2s/create_json_3ds'], type='json', + auth='public', csrf=False) def cielo_s2s_create_json_3ds(self, verify_validity=False, **kwargs): if not kwargs.get('partner_id'): kwargs = dict(kwargs, partner_id=request.env.user.partner_id.id) - token = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id'))).s2s_process(kwargs) + token = request.env['payment.acquirer'].browse( + int(kwargs.get('acquirer_id'))).s2s_process(kwargs) if not token: res = { @@ -90,7 +91,7 @@ def cielo_s2s_create_json_3ds(self, verify_validity=False, **kwargs): 'verified': False, } - if verify_validity != False: + if verify_validity is not False: token.validate() res['verified'] = token.verified @@ -106,8 +107,9 @@ def cielo_create_charge(self, **post): if post.get('tx_ref'): tx = TX.sudo().search([('reference', '=', post['tx_ref'])]) if not tx: - tx_id = (post.get('tx_id') or request.session.get('sale_transaction_id') or - request.session.get('website_payment_tx_id')) + tx_id = (post.get('tx_id') or request.session.get( + 'sale_transaction_id') or + request.session.get('website_payment_tx_id')) tx = TX.sudo().browse(int(tx_id)) if not tx: raise werkzeug.exceptions.NotFound() @@ -121,12 +123,18 @@ def cielo_create_charge(self, **post): 'cielo_token': cielo_token }) tx.payment_token_id = payment_token_id - response = tx._create_cielo_charge(acquirer_ref=payment_token_id.acquirer_ref, email=cielo_token['email']) + response = tx._create_cielo_charge( + acquirer_ref=payment_token_id.acquirer_ref, + email=cielo_token['email']) else: - response = tx._create_cielo_charge(tokenid=cielo_token['id'], email=cielo_token['email']) - _logger.info('Cielo: entering form_feedback with post data %s', pprint.pformat(response)) + response = tx._create_cielo_charge(tokenid=cielo_token['id'], + email=cielo_token['email']) + _logger.info('Cielo: entering form_feedback with post data %s', + pprint.pformat(response)) if response: - request.env['payment.transaction'].sudo().with_context(lang=None).form_feedback(response, 'cielo') - # add the payment transaction into the session to let the page /payment/process to handle it + request.env['payment.transaction'].sudo().with_context( + lang=None).form_feedback(response, 'cielo') + # add the payment transaction into the session to let the page + # /payment/process to handle it PaymentProcessing.add_payment_transaction(tx) return "/payment/process" diff --git a/payment_cielo/models/payment_acquirer.py b/payment_cielo/models/payment_acquirer.py index 9423a736079e..ef9012ac9717 100644 --- a/payment_cielo/models/payment_acquirer.py +++ b/payment_cielo/models/payment_acquirer.py @@ -2,14 +2,8 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging -import requests -import pprint -from odoo import api, fields, models, _ -from odoo.addons.payment.models.payment_acquirer import ValidationError -from odoo.exceptions import UserError -from odoo.tools.safe_eval import safe_eval -from odoo.tools.float_utils import float_round +from odoo import api, fields, models _logger = logging.getLogger(__name__) @@ -18,8 +12,11 @@ class PaymentAcquirerCielo(models.Model): _inherit = 'payment.acquirer' provider = fields.Selection(selection_add=[('cielo', 'Cielo')]) - cielo_merchant_key = fields.Char(required_if_provider='cielo', groups='base.group_user') - cielo_merchant_id = fields.Char(string='Cielo Merchant Id', required_if_provider='cielo', groups='base.group_user') + cielo_merchant_key = fields.Char(required_if_provider='cielo', + groups='base.group_user') + cielo_merchant_id = fields.Char(string='Cielo Merchant Id', + required_if_provider='cielo', + groups='base.group_user') cielo_image_url = fields.Char( "Checkout Image URL", groups='base.group_user') @@ -28,7 +25,8 @@ def cielo_s2s_form_validate(self, data): self.ensure_one() # mandatory fields - for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand"]: + for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", + "cc_brand"]: if not data.get(field_name): return False return True @@ -69,4 +67,3 @@ def _get_cielo_api_headers(self): 'Content-Type': 'application/json', } return CIELO_HEADERS - diff --git a/payment_cielo/models/payment_token.py b/payment_cielo/models/payment_token.py index fc11f4c45d3e..9afcb7b02fc6 100644 --- a/payment_cielo/models/payment_token.py +++ b/payment_cielo/models/payment_token.py @@ -2,17 +2,12 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging -import requests -import pprint -from odoo import api, fields, models, _ -from odoo.addons.payment.models.payment_acquirer import ValidationError -from odoo.exceptions import UserError -from odoo.tools.safe_eval import safe_eval -from odoo.tools.float_utils import float_round +from odoo import api, fields, models _logger = logging.getLogger(__name__) + class PaymentTokenCielo(models.Model): _inherit = 'payment.token' @@ -41,28 +36,30 @@ class PaymentTokenCielo(models.Model): required=False, ) - @api.model def cielo_create(self, values): token = values.get('cielo_token') description = None - payment_acquirer = self.env['payment.acquirer'].browse(values.get('acquirer_id')) + # payment_acquirer = self.env['payment.acquirer'].browse( + # values.get('acquirer_id')) if values.get('cc_number'): - payment_params = { - 'card[number]': values['cc_number'].replace(' ', ''), - 'card[exp_month]': str(values['cc_expiry'][:2]), - 'card[exp_year]': str(values['cc_expiry'][-2:]), - 'card[cvc]': values['cvc'], - 'card[name]': values['cc_holder_name'], - } + # payment_params = { + # 'card[number]': values['cc_number'].replace(' ', ''), + # 'card[exp_month]': str(values['cc_expiry'][:2]), + # 'card[exp_year]': str(values['cc_expiry'][-2:]), + # 'card[cvc]': values['cvc'], + # 'card[name]': values['cc_holder_name'], + # } description = values['cc_holder_name'] else: partner_id = self.env['res.partner'].browse(values['partner_id']) - description = 'Partner: %s (id: %s)' % (partner_id.name, partner_id.id) + description = 'Partner: %s (id: %s)' % ( + partner_id.name, partner_id.id) partner_id = self.env['res.partner'].browse(values['partner_id']) - # res = self._cielo_create_customer(token, description, payment_acquirer.id) + # res = self._cielo_create_customer(token, description, + # payment_acquirer.id) customer_params = { # 'source': token['id'], @@ -71,50 +68,63 @@ def cielo_create(self, values): res = { 'acquirer_ref': partner_id.id, - 'name': 'XXXXXXXXXXXX%s - %s' % (values['cc_number'][-4:], customer_params["description"]), + 'name': 'XXXXXXXXXXXX%s - %s' % ( + values['cc_number'][-4:], customer_params["description"]), 'card_number': values['cc_number'].replace(' ', ''), - 'card_exp': str(values['cc_expiry'][:2]) + '/20' + str(values['cc_expiry'][-2:]), + 'card_exp': str(values['cc_expiry'][:2]) + '/20' + str( + values['cc_expiry'][-2:]), 'card_cvc': values['cvc'], 'card_holder': values['cc_holder_name'], 'card_brand': values['cc_brand'], } # pop credit card info to info sent to create - # for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand", "cielo_token"]: + # for field_name in ["cc_number", "cvc", "cc_holder_name", + # "cc_expiry", "cc_brand", "cielo_token"]: # values.pop(field_name, None) return res - - def _cielo_create_customer(self, token, description=None, acquirer_id=None): - # if token.get('error'): - # _logger.error('payment.token.cielo_create_customer: Token error:\n%s', pprint.pformat(token['error'])) - # raise Exception(token['error']['message']) - # - # if token['object'] != 'token': - # _logger.error('payment.token.cielo_create_customer: Cannot create a customer for object type "%s"', token.get('object')) - # raise Exception('We are unable to process your credit card information.') - # - # if token['type'] != 'card': - # _logger.error('payment.token.cielo_create_customer: Cannot create a customer for token type "%s"', token.get('type')) - # raise Exception('We are unable to process your credit card information.') - - payment_acquirer = self.env['payment.acquirer'].browse(acquirer_id or self.acquirer_id.id) - # url_customer = 'https://%s/customers' % payment_acquirer._get_cielo_api_url() - - customer_params = { - # 'source': token['id'], - 'description': description or token["card"]["name"] - } - - customer = r.json() - - # if customer.get('error'): - # _logger.error('payment.token.cielo_create_customer: Customer error:\n%s', pprint.pformat(customer['error'])) - # raise Exception(customer['error']['message']) - # - values = { - 'acquirer_ref': customer['id'], - 'name': 'XXXXXXXXXXXX%s - %s' % (token['card']['last4'], customer_params["description"]) - } - - return True + # CURRENTLY UNUSED + # def _cielo_create_customer(self, token, description=None, + # acquirer_id=None): + # # if token.get('error'): + # # _logger.error('payment.token.cielo_create_customer: Token + # error:\n%s', pprint.pformat(token['error'])) + # # raise Exception(token['error']['message']) + # # + # # if token['object'] != 'token': + # # _logger.error('payment.token.cielo_create_customer: Cannot + # create a customer for object type "%s"', token.get('object')) + # # raise Exception('We are unable to process your credit card + # information.') + # # + # # if token['type'] != 'card': + # # _logger.error('payment.token.cielo_create_customer: Cannot + # create a customer for token type "%s"', token.get('type')) + # # raise Exception('We are unable to process your credit card + # information.') + # + # payment_acquirer = self.env['payment.acquirer'].browse(acquirer_id + # or self.acquirer_id.id) + # # url_customer = 'https://%s/customers' % + # payment_acquirer._get_cielo_api_url() + # + # customer_params = { + # # 'source': token['id'], + # 'description': description or token["card"]["name"] + # } + # + # customer = r.json() + # + # # if customer.get('error'): + # # _logger.error('payment.token.cielo_create_customer: Customer + # error:\n%s', pprint.pformat(customer['error'])) + # # raise Exception(customer['error']['message']) + # # + # values = { + # 'acquirer_ref': customer['id'], + # 'name': 'XXXXXXXXXXXX%s - %s' % (token['card']['last4'], + # customer_params["description"]) + # } + # + # return True diff --git a/payment_cielo/models/payment_transaction.py b/payment_cielo/models/payment_transaction.py index db76ba6dab83..eb2a02befa4d 100644 --- a/payment_cielo/models/payment_transaction.py +++ b/payment_cielo/models/payment_transaction.py @@ -5,17 +5,14 @@ import requests import pprint -from odoo import api, fields, models, _ -from odoo.addons.payment.models.payment_acquirer import ValidationError -from odoo.exceptions import UserError -from odoo.tools.safe_eval import safe_eval -from odoo.tools.float_utils import float_round +from odoo import api, fields, models _logger = logging.getLogger(__name__) # TODO: INT_CURRENCIES é necessário? INT_CURRENCIES = [ - u'BRL', u'XAF', u'XPF', u'CLP', u'KMF', u'DJF', u'GNF', u'JPY', u'MGA', u'PYG', u'RWF', u'KRW', + u'BRL', u'XAF', u'XPF', u'CLP', u'KMF', u'DJF', u'GNF', u'JPY', u'MGA', + u'PYG', u'RWF', u'KRW', u'VUV', u'VND', u'XOF' ] @@ -23,36 +20,40 @@ class PaymentTransactionCielo(models.Model): _inherit = 'payment.transaction' - def _create_cielo_charge(self, acquirer_ref=None, tokenid=None, email=None): - api_url_charge = 'https://%s/1/sales' % (self.acquirer_id._get_cielo_api_url()) + def _create_cielo_charge(self, acquirer_ref=None, tokenid=None, + email=None): + api_url_charge = 'https://%s/1/sales' % ( + self.acquirer_id._get_cielo_api_url()) if self.payment_token_id.card_brand == 'mastercard': self.payment_token_id.card_brand = 'master' charge_params = { - # TODO: MerchantOrderId - Numero de identificação do Pedido. - "MerchantOrderId":"2014111703", - "Customer":{ - "Name": self.partner_id.name - }, - "Payment":{ - "Type":"CreditCard", - "Amount": self.amount * 100, - "Installments":1, - # TODO: SoftDescriptor - Texto impresso na fatura bancaria comprador. Deve ser preenchido de acordo com os dados do sub Merchant. - "SoftDescriptor":"123456789ABCD", - "CreditCard":{ - "CardNumber": self.payment_token_id.card_number, - "Holder": self.payment_token_id.card_holder, - "ExpirationDate":self.payment_token_id.card_exp, - "SecurityCode":self.payment_token_id.card_cvc, - "Brand":self.payment_token_id.card_brand, - "CardOnFile":{ - "Usage": "Used", - "Reason":"Unscheduled" - } - } - } + # TODO: MerchantOrderId - Numero de identificação do Pedido. + "MerchantOrderId": "2014111703", + "Customer": { + "Name": self.partner_id.name + }, + "Payment": { + "Type": "CreditCard", + "Amount": self.amount * 100, + "Installments": 1, + # TODO: SoftDescriptor - Texto impresso na fatura bancaria + # comprador. Deve ser preenchido de acordo com os dados do + # sub Merchant. + "SoftDescriptor": "123456789ABCD", + "CreditCard": { + "CardNumber": self.payment_token_id.card_number, + "Holder": self.payment_token_id.card_holder, + "ExpirationDate": self.payment_token_id.card_exp, + "SecurityCode": self.payment_token_id.card_cvc, + "Brand": self.payment_token_id.card_brand, + "CardOnFile": { + "Usage": "Used", + "Reason": "Unscheduled" + } + } + } } self.payment_token_id.card_number = '' @@ -61,7 +62,8 @@ def _create_cielo_charge(self, acquirer_ref=None, tokenid=None, email=None): self.payment_token_id.active = False # charge_params = { - # 'amount': int(self.amount if self.currency_id.name in INT_CURRENCIES else float_round(self.amount * 100, 2)), + # 'amount': int(self.amount if self.currency_id.name in + # INT_CURRENCIES else float_round(self.amount * 100, 2)), # 'currency': self.currency_id.name, # 'metadata[reference]': self.reference, # 'description': self.reference, @@ -73,28 +75,34 @@ def _create_cielo_charge(self, acquirer_ref=None, tokenid=None, email=None): # if email: # charge_params['receipt_email'] = email.strip() - _logger.info('_create_cielo_charge: Sending values to URL %s, values:\n%s', api_url_charge, pprint.pformat(charge_params)) + _logger.info( + '_create_cielo_charge: Sending values to URL %s, values:\n%s', + api_url_charge, pprint.pformat(charge_params)) r = requests.post(api_url_charge, json=charge_params, headers=self.acquirer_id._get_cielo_api_headers()) - # TODO: Salvar todos os dados de retorno em seus respectivos campos (talvez criar novos para maior controle) - # TODO: IMPORTANTE deletar informações do cartão e setar active=false pra não aparecer na lista de cartões salvos + # TODO: Salvar todos os dados de retorno em seus respectivos campos + # (talvez criar novos para maior controle) res = r.json() - _logger.info('_create_cielo_charge: Values received:\n%s', pprint.pformat(res)) + _logger.info('_create_cielo_charge: Values received:\n%s', + pprint.pformat(res)) return res @api.multi def cielo_s2s_do_transaction(self, **kwargs): self.ensure_one() - result = self._create_cielo_charge(acquirer_ref=self.payment_token_id.acquirer_ref, email=self.partner_email) + result = self._create_cielo_charge( + acquirer_ref=self.payment_token_id.acquirer_ref, + email=self.partner_email) return self._cielo_s2s_validate_tree(result) - @api.multi def _cielo_s2s_validate_tree(self, tree): self.ensure_one() if self.state != 'draft': - _logger.info('Cielo: trying to validate an already validated tx (ref %s)', self.reference) + _logger.info( + 'Cielo: trying to validate an already validated tx (ref %s)', + self.reference) return True if type(tree) != list: diff --git a/payment_cielo/tests/test_cielo.py b/payment_cielo/tests/test_cielo.py index c438d1bcead9..0bcd09f200db 100644 --- a/payment_cielo/tests/test_cielo.py +++ b/payment_cielo/tests/test_cielo.py @@ -24,7 +24,8 @@ class CieloTest(CieloCommon): @unittest.skip("") def test_10_cielo_s2s(self): - self.assertEqual(self.cielo.environment, 'test', 'test without test environment') + self.assertEqual(self.cielo.environment, 'test', + 'test without test environment') # Add Cielo credentials self.cielo.write({ @@ -55,28 +56,30 @@ def test_10_cielo_s2s(self): }) tx.cielo_s2s_do_transaction() - def test_20_cielo_form_render(self): - self.assertEqual(self.cielo.environment, 'test', 'test without test environment') + self.assertEqual(self.cielo.environment, 'test', + 'test without test environment') # ---------------------------------------- # Test: button direct rendering # ---------------------------------------- # render the button - tx = self.env['payment.transaction'].create({ + self.env['payment.transaction'].create({ 'acquirer_id': self.cielo.id, 'amount': 320.0, 'reference': 'SO404', 'currency_id': self.currency_euro.id, }) - self.cielo.render('SO404', 320.0, self.currency_euro.id, values=self.buyer_values).decode('utf-8') + self.cielo.render('SO404', 320.0, self.currency_euro.id, + values=self.buyer_values).decode('utf-8') @unittest.skip( "as the test is post-install and because payment_strip_sca changes" "the code logic and is automatically installed, this test is invalid.") def test_30_cielo_form_management(self): - self.assertEqual(self.cielo.environment, 'test', 'test without test environment') + self.assertEqual(self.cielo.environment, 'test', + 'test without test environment') # typical data posted by Cielo after client has successfully paid cielo_post_data = { @@ -107,7 +110,8 @@ def test_30_cielo_form_management(self): u'has_more': False, u'object': u'list', u'total_count': 0, - u'url': u'/v1/charges/ch_172xfnGMfVJxozLwEjSfpfxD/refunds'}, + u'url': + u'/v1/charges/ch_172xfnGMfVJxozLwEjSfpfxD/refunds'}, u'shipping': None, u'source': {u'address_city': None, u'address_country': None, @@ -145,8 +149,10 @@ def test_30_cielo_form_management(self): # validate it tx.form_feedback(cielo_post_data, 'Cielo') - self.assertEqual(tx.state, 'done', 'Cielo: validation did not put tx into done state') - self.assertEqual(tx.acquirer_reference, cielo_post_data.get('id'), 'cielo: validation did not update tx id') + self.assertEqual(tx.state, 'done', + 'Cielo: validation did not put tx into done state') + self.assertEqual(tx.acquirer_reference, cielo_post_data.get('id'), + 'cielo: validation did not update tx id') cielo_post_data['metadata']['reference'] = u'SO100-2' # reset tx tx = self.env['payment.transaction'].create({ @@ -158,9 +164,13 @@ def test_30_cielo_form_management(self): 'partner_country_id': self.country_france.id}) # simulate an error cielo_post_data['status'] = 'error' - cielo_post_data.update({u'error': {u'message': u"Your card's expiration year is invalid.", u'code': u'invalid_expiry_year', u'type': u'card_error', u'param': u'exp_year'}}) + cielo_post_data.update({u'error': { + u'message': u"Your card's expiration year is invalid.", + u'code': u'invalid_expiry_year', u'type': u'card_error', + u'param': u'exp_year'}}) with mute_logger('odoo.addons.payment_cielo.models.payment'): with mute_logger('odoo.addons.payment_cielo_sca.models.payment'): tx.form_feedback(cielo_post_data, 'cielo') # check state - self.assertEqual(tx.state, 'cancel', 'Stipe: erroneous validation did not put tx into error state') + self.assertEqual(tx.state, 'cancel', + 'Stipe: erroneous validation did not put tx into error state') From e760698caddd3a42d778740afe44249a5cdbb525 Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Fri, 18 Sep 2020 13:14:02 -0300 Subject: [PATCH 395/790] [ADD] Website object to cielo route --- payment_cielo/controllers/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payment_cielo/controllers/main.py b/payment_cielo/controllers/main.py index d6f14c109fcd..2724863a8875 100644 --- a/payment_cielo/controllers/main.py +++ b/payment_cielo/controllers/main.py @@ -15,7 +15,7 @@ class CieloController(http.Controller): @http.route(['/payment/cielo/s2s/create_json_3ds'], type='json', - auth='public', csrf=False) + auth='public', csrf=False, website=True) def cielo_s2s_create_json_3ds(self, verify_validity=False, **kwargs): if not kwargs.get('partner_id'): kwargs = dict(kwargs, partner_id=request.env.user.partner_id.id) From 9ad4221bb04132acf24cc5aa9177ea6b82a0b0b5 Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Tue, 6 Oct 2020 17:09:46 -0300 Subject: [PATCH 396/790] [ADD] Tokenize --- payment_cielo/models/payment_token.py | 64 +++++++++++++++++---- payment_cielo/models/payment_transaction.py | 15 ++--- 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/payment_cielo/models/payment_token.py b/payment_cielo/models/payment_token.py index 9afcb7b02fc6..10107182cd50 100644 --- a/payment_cielo/models/payment_token.py +++ b/payment_cielo/models/payment_token.py @@ -2,6 +2,8 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging +import pprint +import requests from odoo import api, fields, models @@ -14,31 +16,72 @@ class PaymentTokenCielo(models.Model): card_number = fields.Char( string="Number", required=False, - ) + ) card_holder = fields.Char( string="Holder", required=False, - ) + ) card_exp = fields.Char( string="Expiration date", required=False, - ) + ) card_cvc = fields.Char( string="cvc", required=False, - ) + ) card_brand = fields.Char( string="Brand", required=False, - ) + ) + + cielo_token = fields.Char( + string="Token", + required=False, + ) + + def _cielo_tokenize(self, values): + aquirer_id = self.env.ref('payment_cielo.payment_acquirer_cielo') + api_url_create_card = 'https://%s/1/card' % ( + aquirer_id._get_cielo_api_url()) + + partner_id = self.env['res.partner'].browse(values['partner_id']) + cielo_expiry = str(values['cc_expiry'][:2]) + '/20' + str( + values['cc_expiry'][-2:]) + + if values['cc_brand'] == 'mastercard': + values['cc_brand'] = 'master' + + tokenize_params = { + "CustomerName": partner_id.name, + "CardNumber": values['cc_number'].replace(' ', ''), + "Holder": values['cc_holder_name'], + "ExpirationDate": cielo_expiry, + "Brand": values['cc_brand'], + } + + _logger.info( + '_cielo_tokenize: Sending values to URL %s, values:\n%s', + api_url_create_card, pprint.pformat(tokenize_params)) + r = requests.post(api_url_create_card, + json=tokenize_params, + headers=aquirer_id._get_cielo_api_headers()) + # TODO: Salvar token + res = r.json() + _logger.info('_create_cielo_charge: Values received:\n%s', + pprint.pformat(res)) + + return res @api.model def cielo_create(self, values): token = values.get('cielo_token') + + token = self._cielo_tokenize(values) + values['card_token'] = token['CardToken'] description = None # payment_acquirer = self.env['payment.acquirer'].browse( # values.get('acquirer_id')) @@ -64,7 +107,7 @@ def cielo_create(self, values): customer_params = { # 'source': token['id'], 'description': description or token["card"]["name"] - } + } res = { 'acquirer_ref': partner_id.id, @@ -76,12 +119,13 @@ def cielo_create(self, values): 'card_cvc': values['cvc'], 'card_holder': values['cc_holder_name'], 'card_brand': values['cc_brand'], - } + 'cielo_token': values['card_token'], + } # pop credit card info to info sent to create - # for field_name in ["cc_number", "cvc", "cc_holder_name", - # "cc_expiry", "cc_brand", "cielo_token"]: - # values.pop(field_name, None) + for field_name in ["card_number", "card_cvc", "card_holder", + "card_exp"]: + res.pop(field_name, None) return res # CURRENTLY UNUSED diff --git a/payment_cielo/models/payment_transaction.py b/payment_cielo/models/payment_transaction.py index eb2a02befa4d..8009f17b79c6 100644 --- a/payment_cielo/models/payment_transaction.py +++ b/payment_cielo/models/payment_transaction.py @@ -28,6 +28,8 @@ def _create_cielo_charge(self, acquirer_ref=None, tokenid=None, if self.payment_token_id.card_brand == 'mastercard': self.payment_token_id.card_brand = 'master' + # self.payment_token_id.cielo_token + charge_params = { # TODO: MerchantOrderId - Numero de identificação do Pedido. "MerchantOrderId": "2014111703", @@ -43,22 +45,13 @@ def _create_cielo_charge(self, acquirer_ref=None, tokenid=None, # sub Merchant. "SoftDescriptor": "123456789ABCD", "CreditCard": { - "CardNumber": self.payment_token_id.card_number, - "Holder": self.payment_token_id.card_holder, - "ExpirationDate": self.payment_token_id.card_exp, - "SecurityCode": self.payment_token_id.card_cvc, + "CardToken": self.payment_token_id.cielo_token, "Brand": self.payment_token_id.card_brand, - "CardOnFile": { - "Usage": "Used", - "Reason": "Unscheduled" - } + "SaveCard": "true" } } } - self.payment_token_id.card_number = '' - self.payment_token_id.card_exp = '' - self.payment_token_id.card_cvc = '' self.payment_token_id.active = False # charge_params = { From ad52ca6f6fd690d255cb0aebb051f4472f3009ca Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Wed, 7 Oct 2020 13:44:04 -0300 Subject: [PATCH 397/790] [RMV] Unused client side JS --- payment_cielo/static/src/js/cielo.js | 140 ------------------ .../views/payment_cielo_templates.xml | 6 - 2 files changed, 146 deletions(-) delete mode 100644 payment_cielo/static/src/js/cielo.js diff --git a/payment_cielo/static/src/js/cielo.js b/payment_cielo/static/src/js/cielo.js deleted file mode 100644 index ee90d3fe25a5..000000000000 --- a/payment_cielo/static/src/js/cielo.js +++ /dev/null @@ -1,140 +0,0 @@ -odoo.define('payment_cielo.cielo', function(require) { - "use strict"; - - var ajax = require('web.ajax'); - var core = require('web.core'); - var _t = core._t; - var qweb = core.qweb; - ajax.loadXML('/payment_cielo/static/src/xml/cielo_templates.xml', qweb); - - // Request token cielo via backend - - - var int_currencies = [ - 'BIF', 'XAF', 'XPF', 'CLP', 'KMF', 'DJF', 'GNF', 'JPY', 'MGA', 'PYG', - 'RWF', 'KRW', 'VUV', 'VND', 'XOF' - ]; - - if ($.blockUI) { - // our message needs to appear above the modal dialog - $.blockUI.defaults.baseZ = 2147483647; - $.blockUI.defaults.css.border = '0'; - $.blockUI.defaults.css["background-color"] = ''; - $.blockUI.defaults.overlayCSS["opacity"] = '0.9'; - } - var cieloHandler; - function getCieloHandler() - { - if (cieloHandler) { - return cieloHandler; - } - var handler = cieloHandler = CieloCheckout.configure({ - key: $("input[name='cielo_key']").val(), - image: $("input[name='cielo_image']").val(), - locale: 'auto', - token: function(token, args) { - handler.isTokenGenerate = true; - if ($.blockUI) { - var msg = _t("Just one more second, confirming your payment..."); - $.blockUI({ - 'message': '

' + - '
' + msg + - '

' - }); - } - ajax.jsonRpc("/payment/cielo/create_charge", 'call', { - tokenid: token.id, // TBE TODO: for backward compatibility, remove on master - email: token.email, // TBE TODO: for backward compatibility, remove on master - token: token, - amount: $("input[name='amount']").val(), - acquirer_id: $("#acquirer_cielo").val(), - currency: $("input[name='currency']").val(), - invoice_num: $("input[name='invoice_num']").val(), - tx_ref: $("input[name='invoice_num']").val(), - return_url: $("input[name='return_url']").val() - }).always(function(){ - if ($.blockUI) { - $.unblockUI(); - } - }).done(function(data){ - handler.isTokenGenerate = false; - window.location.href = data; - }).fail(function(data){ - var msg = data && data.data && data.data.message; - var wizard = $(qweb.render('cielo.error', {'msg': msg || _t('Payment error')})); - wizard.appendTo($('body')).modal({'keyboard': true}); - }); - }, - }); - return handler; - } - - require('web.dom_ready'); - if (!$('.o_payment_form').length) { - return $.Deferred().reject("DOM doesn't contain '.o_payment_form'"); - } - - var observer = new MutationObserver(function(mutations, observer) { - for(var i=0; i'); - payment_form.attr('disabled','disabled'); - - - var payment_tx_url = payment_form.find('input[name="prepare_tx_url"]').val(); - var access_token = $("input[name='access_token']").val() || $("input[name='token']").val() || ''; - - var get_input_value = function(name) { - return provider_form.find('input[name="' + name + '"]').val(); - } - - var acquirer_id = parseInt(provider_form.find('#acquirer_cielo').val()); - var amount = parseFloat(get_input_value("amount") || '0.0'); - var currency = get_input_value("currency"); - var email = get_input_value("email"); - var invoice_num = get_input_value("invoice_num"); - var merchant = get_input_value("merchant"); - - // Search if the user wants to save the credit card information - var form_save_token = false; - var acquirer_form = $('#o_payment_form_acq_' + acquirer_id); - if (acquirer_form.length) { - form_save_token = acquirer_form.find('input[name="o_payment_form_save_token"]').prop('checked'); - } - - ajax.jsonRpc(payment_tx_url, 'call', { - acquirer_id: acquirer_id, - access_token: access_token, - save_token: form_save_token, - }).then(function(data) { - var $pay_cielo = $('#pay_cielo').detach(); - try { provider_form[0].innerHTML = data; } catch (e) {} - // Restore 'Pay Now' button HTML since data might have changed it. - $(provider_form[0]).find('#pay_cielo').replaceWith($pay_cielo); - }).done(function () { - getCieloHandler().open({ - name: merchant, - description: invoice_num, - email: email, - currency: currency, - amount: _.contains(int_currencies, currency) ? amount : amount * 100, - }); - }); - } - - $.getScript("https://checkout.cielo.com/checkout.js", function(data, textStatus, jqxhr) { - observer.observe(document.body, {childList: true}); - display_cielo_form($('form[provider="cielo"]')); - }); -}); diff --git a/payment_cielo/views/payment_cielo_templates.xml b/payment_cielo/views/payment_cielo_templates.xml index 81928051ace5..b5473c9a3904 100644 --- a/payment_cielo/views/payment_cielo_templates.xml +++ b/payment_cielo/views/payment_cielo_templates.xml @@ -21,12 +21,6 @@ -