Files
bank-payment/account_banking_fr_lcr/wizard/export_lcr.py
Alexis de Lattre fdc7d91987 Fix spelling mistakes
Simplify code using str.ljust and .join()
Better name for columns in m2m field
Add docstrings.
2014-03-21 00:21:25 +01:00

356 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- encoding: utf-8 -*-
##############################################################################
#
# French Letter of Change module for OpenERP
# Copyright (C) 2014 Akretion (http://www.akretion.com)
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
from openerp.tools.translate import _
from openerp import netsvc
from datetime import datetime
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
from unidecode import unidecode
import base64
LCR_DATE_FORMAT = '%d%m%y'
class banking_export_lcr_wizard(orm.TransientModel):
_name = 'banking.export.lcr.wizard'
_description = 'Export French LCR File'
_columns = {
'state': fields.selection([
('create', 'Create'),
('finish', 'Finish'),
], 'State', readonly=True),
'nb_transactions': fields.related(
'file_id', 'nb_transactions', type='integer',
string='Number of Transactions', readonly=True),
'total_amount': fields.related(
'file_id', 'total_amount', type='float', string='Total Amount',
readonly=True),
'file_id': fields.many2one(
'banking.export.lcr', 'LCR CFONB File', readonly=True),
'file': fields.related(
'file_id', 'file', string="File", type='binary', readonly=True),
'filename': fields.related(
'file_id', 'filename', string="Filename", type='char', size=256,
readonly=True),
'payment_order_ids': fields.many2many(
'payment.order', 'wiz_lcr_payorders_rel', 'wizard_id',
'payment_order_id', 'Payment Orders', readonly=True),
}
_defaults = {
'state': 'create',
}
def create(self, cr, uid, vals, context=None):
payment_order_ids = context.get('active_ids', [])
vals.update({
'payment_order_ids': [[6, 0, payment_order_ids]],
})
return super(banking_export_lcr_wizard, self).create(
cr, uid, vals, context=context)
def _prepare_export_lcr(
self, cr, uid, lcr_export, total_amount, transactions_count,
cfonb_string, context=None):
return {
'total_amount': total_amount,
'nb_transactions': transactions_count,
'file': base64.encodestring(cfonb_string),
'payment_order_ids': [(
6, 0, [x.id for x in lcr_export.payment_order_ids]
)],
}
def _prepare_field(
self, cr, uid, field_name, field_value, size, context=None):
'''This function is designed to be inherited.'''
if not field_value:
raise orm.except_orm(
_('Error:'),
_("The field '%s' is empty or 0. It should have a non-null "
"value.")
% field_name)
try:
value = unidecode(field_value)
unallowed_ascii_chars = [
'"', '#', '$', '%', '&', ';', '<', '>', '=', '@',
'[', ']', '^', '_', '`', '{', '}', '|', '~', '\\', '!',
]
for unallowed_ascii_char in unallowed_ascii_chars:
value = value.replace(unallowed_ascii_char, '-')
except:
raise orm.except_orm(
_('Error:'),
_("Cannot convert the field '%s' to ASCII")
% field_name)
value = value.upper()
# Cut if too long
value = value[0:size]
# enlarge if too small
if len(value) < size:
value = value.ljust(size, ' ')
assert len(value) == size, 'The length of the field is wrong'
return value
def _get_rib_from_iban(self, cr, uid, partner_bank, context=None):
# I do NOT want to add a dependancy on l10n_fr_rib, because
# we plan to remove the module in the near future
# So I consider that IBAN MUST be given in the res.partner.bank
# of type 'rib'
if partner_bank.state == 'rib' and not partner_bank.acc_number:
raise orm.except_orm(
_('Error:'),
_("For the bank account '%s' of partner '%s', the bank "
"account type is 'RIB and optional IBAN' and the IBAN "
"field is empty, but, starting from 2014, we consider "
"that the IBAN is required. Please write the IBAN on "
"this bank account.")
% (self.pool['res.partner.bank'].name_get(
cr, uid, [partner_bank.id], context=context)[0][1],
partner_bank.partner_id.name))
elif partner_bank.state != 'iban':
raise orm.except_orm(
_('Error:'),
_("For the bank account '%s' of partner '%s', the Bank "
"Account Type should be 'IBAN'.")
% (self.pool['res.partner.bank'].name_get(
cr, uid, [partner_bank.id], context=context)[0][1],
partner_bank.partner_id.name))
iban = partner_bank.acc_number.replace(' ', '')
if iban[0:2] != 'FR':
raise orm.except_orm(
_('Error:'),
_("LCR are only for French bank accounts. The IBAN '%s' "
"of partner '%s' is not a French IBAN.")
% (partner_bank.acc_number, partner_bank.partner_id.name))
assert len(iban) == 27, 'French IBANs must have 27 caracters'
return {
'code_banque': iban[4:9],
'code_guichet': iban[9:14],
'numero_compte': iban[14:25],
'cle_rib': iban[25:27],
}
def _prepare_first_cfonb_line(
self, cr, uid, lcr_export, context=None):
'''Generate the header line of the CFONB file'''
code_enregistrement = '03'
code_operation = '60'
numero_enregistrement = '00000001'
numero_emetteur = '000000' # It is not needed for LCR
# this number is only required for old national direct debits
today_str = fields.date.context_today(self, cr, uid, context=context)
today_dt = datetime.strptime(today_str, DEFAULT_SERVER_DATE_FORMAT)
date_remise = today_dt.strftime(LCR_DATE_FORMAT)
raison_sociale_cedant = self._prepare_field(
cr, uid, u'Raison sociale du cédant',
lcr_export.payment_order_ids[0].company_id.name, 24,
context=context)
domiciliation_bancaire_cedant = self._prepare_field(
cr, uid, u'Domiciliation bancaire du cédant',
lcr_export.payment_order_ids[0].mode.bank_id.bank.name, 24,
context=context)
code_entree = '3'
code_dailly = ' '
code_monnaie = 'E'
rib = self._get_rib_from_iban(
cr, uid, lcr_export.payment_order_ids[0].mode.bank_id,
context=context)
ref_remise = self._prepare_field(
cr, uid, u'Référence de la remise',
lcr_export.payment_order_ids[0].reference, 11, context=context)
cfonb_line = ''.join([
code_enregistrement,
code_operation,
numero_enregistrement,
numero_emetteur,
' ' * 6,
date_remise,
raison_sociale_cedant,
domiciliation_bancaire_cedant,
code_entree,
code_dailly,
code_monnaie,
rib['code_banque'],
rib['code_guichet'],
rib['numero_compte'],
' ' * (16 + 6 + 10 + 15),
# Date de valeur is left empty because it is only for
# "remise à l'escompte" and we do
# "Encaissement, crédit forfaitaire après léchéance"
ref_remise,
])
assert len(cfonb_line) == 160, 'LCR CFONB line must have 160 chars'
cfonb_line += '\r\n'
return cfonb_line
def _prepare_cfonb_line(
self, cr, uid, line, requested_date, transactions_count,
context=None):
'''Generate each debit line of the CFONB file'''
# I use French variable names because the specs are in French
code_enregistrement = '06'
code_operation = '60'
numero_enregistrement = '%08d' % (transactions_count + 1)
reference_tire = self._prepare_field(
cr, uid, u'Référence tiré', line.communication, 10,
context=context)
rib = self._get_rib_from_iban(cr, uid, line.bank_id, context=context)
nom_tire = self._prepare_field(
cr, uid, u'Nom tiré', line.partner_id.name, 24, context=context)
nom_banque = self._prepare_field(
cr, uid, u'Nom banque', line.bank_id.bank.name, 24,
context=context)
code_acceptation = '0'
montant_centimes = int(line.amount_currency * 100)
zero_montant_centimes = ('%012d' % montant_centimes)
today_str = fields.date.context_today(self, cr, uid, context=context)
today_dt = datetime.strptime(today_str, DEFAULT_SERVER_DATE_FORMAT)
date_creation = today_dt.strftime(LCR_DATE_FORMAT)
requested_date_dt = datetime.strptime(
requested_date, DEFAULT_SERVER_DATE_FORMAT)
date_echeance = requested_date_dt.strftime(LCR_DATE_FORMAT)
reference_tireur = reference_tire
cfonb_line = ''.join([
code_enregistrement,
code_operation,
numero_enregistrement,
' ' * (6 + 2),
reference_tire,
nom_tire,
nom_banque,
code_acceptation,
' ' * 2,
rib['code_banque'],
rib['code_guichet'],
rib['numero_compte'],
zero_montant_centimes,
' ' * 4,
date_echeance,
date_creation,
' ' * (4 + 1 + 3 + 3 + 9),
reference_tireur,
])
assert len(cfonb_line) == 160, 'LCR CFONB line must have 160 chars'
cfonb_line += '\r\n'
return cfonb_line
def _prepare_final_cfonb_line(
self, cr, uid, total_amount, transactions_count, context=None):
'''Generate the last line of the CFONB file'''
code_enregistrement = '08'
code_operation = '60'
numero_enregistrement = '%08d' % (transactions_count + 2)
montant_total_centimes = int(total_amount * 100)
zero_montant_total_centimes = ('%012d' % montant_total_centimes)
cfonb_line = ''.join([
code_enregistrement,
code_operation,
numero_enregistrement,
' ' * (6 + 12 + 24 + 24 + 1 + 2 + 5 + 5 + 11),
zero_montant_total_centimes,
' ' * (4 + 6 + 10 + 15 + 5 + 6),
])
assert len(cfonb_line) == 160, 'LCR CFONB line must have 160 chars'
return cfonb_line
def create_lcr(self, cr, uid, ids, context=None):
'''Creates the LCR CFONB file.'''
lcr_export = self.browse(cr, uid, ids[0], context=context)
today = fields.date.context_today(self, cr, uid, context=context)
cfonb_string = self._prepare_first_cfonb_line(
cr, uid, lcr_export, context=context)
transactions_count = 0
total_amount = 0.0
for payment_order in lcr_export.payment_order_ids:
total_amount = total_amount + payment_order.total
# Iterate each payment lines
for line in payment_order.line_ids:
if line.currency.name != 'EUR':
raise orm.except_orm(
_('Error:'),
_("The currency of payment line '%s' is '%s'. "
"To be included in a French LCR, the currency "
"must be EUR.")
% (line.name, line.currency.name))
transactions_count += 1
if payment_order.date_prefered == 'due':
requested_date = line.ml_maturity_date or today
elif payment_order.date_prefered == 'fixed':
requested_date = payment_order.date_scheduled or today
else:
requested_date = today
cfonb_string += self._prepare_cfonb_line(
cr, uid, line, requested_date, transactions_count,
context=context)
cfonb_string += self._prepare_final_cfonb_line(
cr, uid, total_amount, transactions_count,
context=context)
file_id = self.pool['banking.export.lcr'].create(
cr, uid, self._prepare_export_lcr(
cr, uid, lcr_export, total_amount, transactions_count,
cfonb_string, context=context),
context=context)
self.write(
cr, uid, ids, {
'file_id': file_id,
'state': 'finish',
}, context=context)
action = {
'name': 'LCR File',
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form,tree',
'res_model': self._name,
'res_id': ids[0],
'target': 'new',
}
return action
def cancel_lcr(self, cr, uid, ids, context=None):
'''Cancel the CFONB file: just drop the file'''
lcr_export = self.browse(cr, uid, ids[0], context=context)
self.pool['banking.export.lcr'].unlink(
cr, uid, lcr_export.file_id.id, context=context)
return {'type': 'ir.actions.act_window_close'}
def save_lcr(self, cr, uid, ids, context=None):
'''Mark the LCR file as 'sent' and the payment order as 'done'.'''
lcr_export = self.browse(cr, uid, ids[0], context=context)
self.pool['banking.export.lcr'].write(
cr, uid, lcr_export.file_id.id, {'state': 'sent'},
context=context)
wf_service = netsvc.LocalService('workflow')
for order in lcr_export.payment_order_ids:
wf_service.trg_validate(uid, 'payment.order', order.id, 'done', cr)
return {'type': 'ir.actions.act_window_close'}