Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Yk2kus/cnab refactor tko #39

Open
wants to merge 36 commits into
base: 8.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
eed4ebc
fix duplicate internal_sequence_id from view
yk2kus May 12, 2016
18506a1
add validator on exporting, it should not raise error but helpful war…
yk2kus May 20, 2016
46d4dfb
add validator to generate boletos
yk2kus May 20, 2016
8320fa2
correct warning message on validate payment order
yk2kus May 23, 2016
5447a77
improve validator and pass fix numero_documento variable in header
yk2kus May 26, 2016
f2d3b8f
improve validator
yk2kus May 26, 2016
99c6afb
add validator on invoice number, can't be more than 10 digits, it is …
yk2kus May 26, 2016
b30bb34
improve validator on transaction ref moin move line
yk2kus May 26, 2016
02b849f
add multa in payment modes and use in boleto
yk2kus Jun 6, 2016
8bcd6f0
update view remove duplicate fields in view
yk2kus Jun 6, 2016
6aefc96
correct multa in boleto print
yk2kus Jun 6, 2016
58fb940
Merge pull request #1 from yk2kus/fix/boletos_missing_fields
yk2kus Jun 7, 2016
02d2f59
fix instrucoes on boleto
yk2kus Jun 7, 2016
1c16fd1
boleto_convenio set non-required, and fix multa in description devide…
yk2kus Jun 8, 2016
5c47b84
fix Instruções in boleto
yk2kus Jun 13, 2016
cf7a80c
compute multa in fixed R$ by dayt
yk2kus Jun 13, 2016
26f06a6
compute multa in fixed R$ by dayt
yk2kus Jun 13, 2016
68eb16e
validate bra_number_dig and self.mode.boleto_protesto_prazo must be i…
yk2kus Jun 13, 2016
e3b3943
correct multa computation in boleto
yk2kus Jun 14, 2016
622f2b9
correct multa computation in boleto
yk2kus Jun 14, 2016
499e157
fix header itau
yk2kus Jun 15, 2016
9aaa954
fix segmentop and carteria
yk2kus Jun 15, 2016
fef2840
fix nosso_numero, carteira_numero, nosso_numero_dv in segmentop
yk2kus Jun 16, 2016
d93ce1d
fix trailer quantidade_registros,cobrancasimples_quantidade_titulos
yk2kus Jun 16, 2016
225ba72
fix .trailer.quantidade_registros
yk2kus Jun 22, 2016
2bdf3c5
improve validator partner's district can't be more than 15 digits
yk2kus Jun 24, 2016
b56479e
remove 15 digits constraint from payment order
yk2kus Jul 12, 2016
41544de
validate 8 digits reference only for itau
yk2kus Jul 12, 2016
65e2120
consider only line.move_line_id.transaction_ref[4:12] (8 digits) for …
yk2kus Jul 12, 2016
72ac96b
limit city and district up to 15 chars, for itau segment
yk2kus Jul 12, 2016
45d6c71
attach boleto in invoice as attachment
yk2kus Jul 13, 2016
463c699
fix .trailer.quantidade_registros
yk2kus Jul 14, 2016
25842df
fix account number read from cnab240 returned event
yk2kus Jul 18, 2016
b263006
Merge remote-tracking branch 'yk2kus/cnab_refactor_tko' into merge_yk…
fernandomr Aug 2, 2016
588cf8e
initial fixes for compatibility
fernandomr Aug 5, 2016
1b9a994
some alterations and comments to get analised
fernandomr Aug 5, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,16 @@ def _prepare_header(self):
:param order:
:return:
"""

vals = super(Itau240, self)._prepare_header()
vals['cedente_dv_ag_cc'] = int(
vals['cedente_dv_ag_cc'])
vals['cedente_agencia_dv'] = int(
vals['cedente_agencia_dv']),
vals['cedente_agencia_dv'])

# Company's Razão Social limited to 30 chars
vals['cedente_nome'] = (vals['cedente_nome'][:30])

return vals

def _prepare_segmento(self, line):
Expand All @@ -61,22 +66,56 @@ def _prepare_segmento(self, line):
"""
vals = super(Itau240, self)._prepare_segmento(line)

carteira, nosso_numero, digito = self.nosso_numero(
line.move_line_id.transaction_ref)
if not line.move_line_id.transaction_ref:
raise Warning("No transaction reference set for move %s" % line.move_line_id.name)
ref = line.move_line_id.transaction_ref[4:12]
carteira, nosso_numero, digito = self.nosso_numero(ref)
#=======================================================================
# nº da agência: 1572
# nº da conta corrente, sem o DAC: 22211
# nº da subcarteira: 109 (Neste teste saiu 000, conforme já mencionado acima)
# nosso número: 00000008
# You multiply each char of the number composed with the fields above by the sequence of multipliers - 2 1 2 1 2 1 2 positioned from right to left.
# (agency+account+carteira+nossonumero) (15722221110900000008)
#
#=======================================================================
reference = str(line.order_id.mode.bank_id.bra_number) + str(line.order_id.mode.bank_id.acc_number) + str(self.order.mode.boleto_carteira) + str(ref)
vals['carteira_numero'] = int(line.order_id.mode.boleto_carteira)
vals['nosso_numero'] = int(ref)
vals['nosso_numero_dv'] = int(self.nosso_numero_dv(reference))
vals['sacado_cidade'] = line.partner_id.l10n_br_city_id.name[:15]
vals['sacado_bairro'] = line.partner_id.district[:15]

vals['cedente_dv_ag_cc'] = int(
vals['cedente_dv_ag_cc'])
vals['carteira_numero'] = int(carteira)
vals['nosso_numero'] = int(nosso_numero)
vals['nosso_numero_dv'] = int(digito)
# Partner's legal name 30 chars restriction
vals['sacado_nome'] = vals['sacado_nome'][:30]

return vals

# Override cnab_240.nosso_numero. Diferentes números de dígitos entre
# CEF e Itau
def nosso_numero(self, format):
#should not return digit from this method
# ust use nosso_numero_dv top return digit
digito = format[-1:]
carteira = format[:3]
nosso_numero = re.sub(
'[%s]' % re.escape(string.punctuation), '', format[3:-1] or '')
return carteira, nosso_numero, digito

def nosso_numero_dv(self, format):
i = 1
total = 0
# multiply all digits by 1 and 2 consicutively starting:
# eg: 1st x 1 + 2nd x 2 + 3rd x 1 + 4th x 2 + ........
position = 1
for digit in format:
if int(position) % 2 == 0:
result = int(digit) * 2
else:
result = int(digit) * 1
total = total + sum([int(digit) for digit in str(result)])
position += 1
digit = total % 10
if digit != 0:
digit = 10 - digit
return digit
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def _prepare_header(self):
:return:
"""
return {
'cedente_agencia_conta_dv' : int(self.order.mode.bank_id.acc_number_dig),
'controle_banco': int(self.order.mode.bank_id.bank_bic),
'arquivo_data_de_geracao': self.data_hoje(),
'arquivo_hora_de_geracao': self.hora_agora(),
Expand Down Expand Up @@ -149,17 +150,18 @@ def _prepare_segmento(self, line):
# Era cedente_agencia_conta_dv agora é cedente_dv_ag_cc

return {
'cedente_agencia_conta_dv' : int(self.order.mode.bank_id.acc_number_dig),
'controle_banco': int(self.order.mode.bank_id.bank_bic),
'cedente_agencia': int(self.order.mode.bank_id.bra_number),
'cedente_conta': int(self.order.mode.bank_id.acc_number),
'cedente_conta_dv': self.order.mode.bank_id.acc_number_dig,
'cedente_agencia_dv': self.order.mode.bank_id.bra_number_dig,
# DV ag e cc
'cedente_dv_ag_cc': (self.order.mode.bank_id.bra_acc_dig),
'identificacao_titulo': u'0000000', # TODO
'cedente_dv_ag_cc': (self.order.mode.bank_id.acc_number_dig),
'identificacao_titulo': line.move_line_id.name, # 25 chars limit
'identificacao_titulo_banco': u'0000000', # TODO
'identificacao_titulo_empresa': line.move_line_id.move_id.name,
'numero_documento': line.name,
'identificacao_titulo_empresa': line.move_line_id.move_id.name,
'numero_documento': line.move_line_id.invoice.number, # 10 chars limit
'vencimento_titulo': self.format_date(
line.ml_maturity_date),
'valor_titulo': Decimal(str(line.amount_currency)).quantize(
Expand Down Expand Up @@ -211,9 +213,11 @@ def remessa(self, order):
self.arquivo.lotes[0].header.servico_servico = 1
# TODO: tratar soma de tipos de cobranca
cobrancasimples_valor_titulos += line.amount_currency
#fixed 'quantidade_registros' in trailer to 000001
self.arquivo.lotes[0].trailer.cobrancasimples_valor_titulos = \
Decimal(cobrancasimples_valor_titulos).quantize(
Decimal('1.00'))


remessa = unicode(self.arquivo)
return unicodedata.normalize(
Expand Down
1 change: 1 addition & 0 deletions l10n_br_account_banking_payment_cnab/model/payment_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class PaymentMode(models.Model):
u'Condição Emissão de Papeleta', default='1')
cnab_percent_interest = fields.Float(string=u"Percentual de Juros",
digits=dp.get_precision('Account'))
multa = fields.Float('Multa')
comunicacao_2 = fields.Char("Comunicação para o sacador avalista")
# A exportação CNAB não se encaixa somente nos parâmetros de
# débito e crédito.
Expand Down
72 changes: 72 additions & 0 deletions l10n_br_account_banking_payment_cnab/model/payment_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
##############################################################################

from openerp import models, fields, api
from openerp.exceptions import Warning

# TODO: funcao a ser chamada por ação automatizada para resetar o sufixo
# diariamente
Expand All @@ -37,7 +38,78 @@ class PaymentOrder(models.Model):
sufixo_arquivo = fields.Integer(u'Sufixo do arquivo')
serie_sufixo_arquivo = fields.Many2one(
'l10n_br_cnab_file_sufix.sequence', u'Série do Sufixo do arquivo')

# we will validate here user inputs required to export
# a wrong input shouldn't raise error but should show helpful
# warning message
@api.multi
def validate_order(self):
if not len(self.line_ids):
raise Warning("Please select lines to export")
# code must belong to one of allowed code
if self.mode_type.code not in ['240', '400', '500']:
raise Warning("Payment Type Code must be 240, 400 or 500, found %s" % self.mode_type.code)
# legal name max length is accepted 30 chars
# Yes, but since this is a CNAB file restriction, crop the string at the 30 char. Moved to itau.py

if not self.mode.boleto_protesto:
raise Warning(u"Códigos de Protesto in payment mode not defined")
if not self.mode.boleto_protesto_prazo:
raise Warning(u"Prazo protesto in payment mode not defined")
else:
try:
int(self.mode.boleto_protesto_prazo)
except:
raise Warning("Prazo protesto in payment mode must be integer")
if not self.mode.bank_id.bra_number_dig:
raise Warning("Dígito Agência not defined")
else:
try:
# FIXME: might not be ok for Banco do Brasil. Ok for Itau and Bradesco.
int(self.mode.bank_id.bra_number_dig)
except:
raise Warning("Dígito Agência must be integer")


# move lines must have transaction refernce
for line in self.line_ids:
if not line.partner_id:
raise Warning("Partner not defined for %s" %line.name)
if not line.partner_id.legal_name:
raise Warning("Razão Social not defined for %s" %line.partner_id.name)

# if len(line.partner_id.legal_name) > 30:
# raise Warning("Partner's Razão Social should not be longer than 30 chars") -> moved to itau.py

if not line.partner_id.state_id:
raise Warning("Partner's state not defined")
if not line.partner_id.state_id.code:
raise Warning("Partner's state code not defined")
# max 15 chars
if not line.partner_id.district:
raise Warning("Partner's bairro not defined")
if not line.partner_id.zip:
raise Warning("Partner's CEP not defined")
if not line.partner_id.l10n_br_city_id:
raise Warning("Partner's city not defined")
if not line.partner_id.street:
raise Warning("Partner's street not defined")

# FIXME transaction_ref might not be known at the time of exportation.
# Is that a rule for Itau or one possibilitie? This rules goes to itau.py

# Itau code : 341 supposed not to be larger than 8 digits
if self.mode.bank_id.bank.bic == '341':
try:
int(line.move_line_id.transaction_ref[4:12])
except:
raise Warning("Transaction reference for move line must be integer")
if not line.move_line_id.invoice.number:
raise Warning("Null value in 'numero_documento' number not defined for invoice %s" % line.move_line_id.invoice.number)
if len(line.move_line_id.invoice.number) > 10:
raise Warning("numero_documento can not be more than 10 digits long found %s" %line.move_line_id.invoice.number)


def get_next_number(self, cr, uid, ids, context=None):
if context is None:
context = {}
Expand Down
1 change: 1 addition & 0 deletions l10n_br_account_banking_payment_cnab/view/payment_mode.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<group>
<field name="condicao_emissao_papeleta"/>
<field name="cnab_percent_interest"/>
<field name="multa"/>
<field name="comunicacao_2"/>
</group>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def export(self):
for order_id in self.env.context.get('active_ids', []):

order = self.env['payment.order'].browse(order_id)
order.validate_order()
cnab = Cnab.get_cnab(order.mode.bank_id.bank_bic,
order.mode_type.code)()
remessa = cnab.remessa(order)
Expand All @@ -63,6 +64,17 @@ def export(self):
time.strftime('%d%m'), str(order.file_number))
self.state = 'done'
self.cnab_file = base64.b64encode(remessa)
file_name = 'CB%s%s.REM' % (
time.strftime('%d%m'), str(order.file_number))
# create attachment in payment order
attach_vals = {
'name': file_name,
'datas_fname': file_name,
'datas': base64.b64encode(remessa),
'res_model': 'payment.order',
'res_id' : order.id,
}
self.env['ir.attachment'].create(attach_vals)
workflow.trg_validate(self.env.uid, 'payment.order', order_id,
'done', self.env.cr)

Expand Down
23 changes: 23 additions & 0 deletions l10n_br_account_payment_boleto/boleto/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ def getBranchNumber(self):
return self.branch_number.encode('utf-8')

def _move_line(self, move_line):

# FIXME
# Fields multa and cnab_percent_interest have the same purpose. One of them will disappear, but not now to keep compatibility
boleto_multa_percent = (move_line.debit * move_line.payment_mode_id.multa * 0.01) or (move_line.credit * move_line.payment_mode_id.multa * 0.01)
# Considered boletos, we'll use this routine to charge customers and pay them?
# If it's only to charge, makes sense to keep calculations with move_line.credit?
juros_by_day = (((move_line.debit or move_line.credit )+ boleto_multa_percent ) * move_line.payment_mode_id.cnab_percent_interest * 0.01)/30
instrucoes = ''
# FIXME
# the string below and field comunicacao_2 have the same purpose. One of them will disappear too.
# unless this message changes (besides boleto_multa_percent value)
if move_line.payment_mode_id.instrucoes:
instrucoes = move_line.payment_mode_id.instrucoes
instrucoes = instrucoes + u"\n Após o vencimento cobrar multa de R$ %s e juros de R$ %s ao dia." %(str("%.2f" % boleto_multa_percent) or '', str("%.2f" % juros_by_day or '0.00'))
self._payment_mode(move_line.payment_mode_id)
self.boleto.data_vencimento = datetime.date(datetime.strptime(
move_line.date_maturity, '%Y-%m-%d'))
Expand All @@ -85,17 +99,26 @@ def _move_line(self, move_line):
move_line.currency_id and move_line.currency_id.symbol or 'R$'
self.boleto.quantidade = '' # str("%.2f" % move_line.amount_currency)
self.boleto.numero_documento = move_line.name.encode('utf-8')
self.boleto.instrucoes = instrucoes or ''

def _payment_mode(self, payment_mode_id):
"""
:param payment_mode:
:return:
"""

self.boleto.convenio = payment_mode_id.boleto_convenio
self.boleto.especie_documento = payment_mode_id.boleto_modalidade
self.boleto.aceite = payment_mode_id.boleto_aceite
self.boleto.carteira = payment_mode_id.boleto_carteira

# It's not a good idea to replace by a string on fields that are integers on CNAB files.
# self.boleto.cnab_percent_interest = payment_mode_id.cnab_percent_interest or ' '
# self.boleto.boleto_protesto = payment_mode_id.boleto_protesto or ' '
# self.boleto.boleto_protesto_prazo = payment_mode_id.boleto_protesto_prazo or ' '
self.boleto.boleto_especie = payment_mode_id.boleto_especie or ' '
self.boleto.comunicacao_2 = payment_mode_id.comunicacao_2 or ' '

def _cedente(self, company):
"""
:param company:
Expand Down
78 changes: 46 additions & 32 deletions l10n_br_account_payment_boleto/models/account_move_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from datetime import date
from ..boleto.document import Boleto
from ..boleto.document import BoletoException
from openerp.exceptions import Warning

_logger = logging.getLogger(__name__)

Expand All @@ -36,42 +37,55 @@ class AccountMoveLine(models.Model):
u'Data da criação do pagamento', readonly=True)
boleto_own_number = fields.Char(
u'Nosso Número', readonly=True)


#validate config to generate boletos
@api.multi
def validate_boleto_config(self):
for move_line in self:
if move_line.payment_mode_id.type_payment != '00':
raise Warning(u"In payment mode %s Tipo SPED must be 00 - Duplicata" %move_line.payment_mode_id.name)
if not move_line.payment_mode_id.internal_sequence_id:
raise Warning(u"Please set sequence in payment mode %s" % move_line.payment_mode_id.name)
if move_line.company_id.own_number_type != '2':
raise Warning(u"Tipo de nosso número Sequéncial uniquo por modo de pagamento")
if not move_line.payment_mode_id.boleto_type:
raise Warning(u"Configure o tipo de boleto no modo de pagamento")
if not move_line.payment_mode_id.boleto_carteira:
raise Warning(u"Carteira not set in payment method")
return True


@api.multi
def send_payment(self):
boleto_list = []

self.validate_boleto_config()
for move_line in self:
try:

if move_line.payment_mode_id.type_payment == '00':
number_type = move_line.company_id.own_number_type
if not move_line.boleto_own_number:
if number_type == '0':
nosso_numero = self.env['ir.sequence'].next_by_id(
move_line.company_id.own_number_sequence.id)
elif number_type == '1':
nosso_numero = \
move_line.transaction_ref.replace('/', '')
else:
nosso_numero = self.env['ir.sequence'].next_by_id(
move_line.payment_mode_id.
internal_sequence_id.id)
if move_line.payment_mode_id.type_payment == '00':
number_type = move_line.company_id.own_number_type
if not move_line.boleto_own_number:
if number_type == '0':
nosso_numero = self.env['ir.sequence'].next_by_id(
move_line.company_id.own_number_sequence.id)
elif number_type == '1':
nosso_numero = \
move_line.transaction_ref.replace('/', '')
else:
nosso_numero = move_line.boleto_own_number

boleto = Boleto.getBoleto(move_line, nosso_numero)
if boleto:
move_line.date_payment_created = date.today()
move_line.transaction_ref = \
boleto.boleto.format_nosso_numero()
move_line.boleto_own_number = nosso_numero
nosso_numero = self.env['ir.sequence'].next_by_id(
move_line.payment_mode_id.
internal_sequence_id.id)
else:
nosso_numero = move_line.boleto_own_number
try:
int(nosso_numero)
except:
raise Warning(u"Nosso numero must be integer please check prefix and suffix in payment method sequence")
boleto = Boleto.getBoleto(move_line, nosso_numero)
if boleto:
move_line.date_payment_created = date.today()
move_line.transaction_ref = \
boleto.boleto.format_nosso_numero()
move_line.boleto_own_number = nosso_numero


boleto_list.append(boleto.boleto)
except BoletoException as be:
_logger.error(be.message or be.value, exc_info=True)
continue
except Exception as e:
_logger.error(e.message or e.value, exc_info=True)
continue
boleto_list.append(boleto.boleto)
return boleto_list
Loading