~factorlibre/sepa-tools/sepa-tools

« back to all changes in this revision

Viewing changes to sepa_pain/sepa_pain.py

  • Committer: Ignacio Ibeas - Acysos S.L.
  • Date: 2014-02-09 15:05:50 UTC
  • Revision ID: ignacio@acysos.com-20140209150550-uug8yon3vcuh3rlj
[ADD] New SEPA modules for account_payment_extension

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- encoding: utf-8 -*-
2
 
########################################################################
3
 
#
4
 
# Copyright (C) 2013 Acysos S.L.
5
 
# Copyright (C) 2010-2013 Akretion (http://www.akretion.com)
6
 
# @authors: Ignacio Ibeas <ignacio@acysos.com>
7
 
# @authors: Alexis de Lattre <alexis.delattre@akretion.com>
8
 
#
9
 
#This program is free software: you can redistribute it and/or modify
10
 
#it under the terms of the GNU General Public License as published by
11
 
#the Free Software Foundation, either version 3 of the License, or
12
 
#(at your option) any later version.
13
 
#
14
 
# This module is GPLv3 or newer and incompatible
15
 
# with OpenERP SA "AGPL + Private Use License"!
16
 
#
17
 
#This program is distributed in the hope that it will be useful,
18
 
#but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 
#GNU General Public License for more details.
21
 
#
22
 
#You should have received a copy of the GNU General Public License
23
 
#along with this program.  If not, see http://www.gnu.org/licenses.
24
 
########################################################################
25
 
 
26
 
from osv import osv, fields
27
 
import base64
28
 
import time
29
 
from datetime import datetime, timedelta
30
 
from tools.translate import _
31
 
from tools.safe_eval import safe_eval
32
 
import tools
33
 
from lxml import etree
34
 
import logging
35
 
import netsvc
36
 
from unidecode import unidecode
37
 
 
38
 
_logger = logging.getLogger(__name__)
39
 
 
40
 
class sepa_pain_export(osv.osv):
41
 
    _name = 'sepa.pain.export'
42
 
    _description = 'Export to SEPA XML format'
43
 
    _auto = False
44
 
    
45
 
    def _validate_iban(self, cr, uid, bank, context=None):
46
 
        '''if IBAN is valid, returns IBAN
47
 
        if IBAN is NOT valid, raises an error message'''
48
 
        partner_bank_obj = self.pool.get('res.partner.bank')
49
 
        iban = bank.iban
50
 
        if partner_bank_obj.check_iban(cr, uid, [bank.id], context=context):
51
 
            return iban.replace(' ', '')
52
 
        else:
53
 
            raise osv.except_osv(
54
 
                _('Error:'), _("This IBAN is not valid : %s") % iban)
55
 
    
56
 
    def _prepare_field(
57
 
            self, cr, uid, field_name, field_value, eval_ctx, max_size=0,
58
 
            context=None):
59
 
        '''This function is designed to be inherited !'''
60
 
        assert isinstance(eval_ctx, dict), 'eval_ctx must contain a dict'
61
 
        try:
62
 
            # SEPA uses XML ; XML = UTF-8 ; UTF-8 = support for all characters
63
 
            # But we are dealing with banks...
64
 
            # and many banks don't want non-ASCCI characters !
65
 
            # cf section 1.4 "Character set" of the SEPA Credit Transfer
66
 
            # Scheme Customer-to-bank guidelines
67
 
            value = unidecode(safe_eval(field_value, eval_ctx))
68
 
        except:
69
 
            line = eval_ctx.get('line')
70
 
            if line:
71
 
                raise osv.except_osv(
72
 
                    _('Error:'),
73
 
                    _("Cannot compute the '%s' of the Payment Line with Invoice Reference '%s'.")
74
 
                    % (field_name, 
75
 
                       self.pool['account.invoice'].name_get(
76
 
                             cr, uid, [line.ml_inv_ref.id], 
77
 
                             context=context)[0][1]))
78
 
            else:
79
 
                raise osv.except_osv(
80
 
                    _('Error:'),
81
 
                    _("Cannot compute the '%s'.") % field_name)
82
 
        if not isinstance(value, (str, unicode)):
83
 
            raise osv.except_osv(
84
 
                _('Field type error:'),
85
 
                _('''The type of the field '%s' is %s. 
86
 
                    It should be a string or unicode.''')
87
 
                % (field_name, type(value)))
88
 
        if not value:
89
 
            raise osv.except_osv(
90
 
                _('Error:'),
91
 
                _("The '%s' is empty or 0. It should have a non-null value.")
92
 
                % field_name)
93
 
        if max_size and len(value) > max_size:
94
 
            value = value[0:max_size]
95
 
        return value
96
 
    
97
 
    def _validate_xml(self, cr, uid, xml_string, config):
98
 
        xsd_etree_obj = etree.parse(
99
 
            tools.file_open(
100
 
                'sepa_pain/data/%s.xsd'
101
 
                % config['pain_flavor']))
102
 
        official_pain_schema = etree.XMLSchema(xsd_etree_obj)
103
 
 
104
 
        try:
105
 
            root_to_validate = etree.fromstring(xml_string)
106
 
            official_pain_schema.assertValid(root_to_validate)
107
 
        except Exception, e:
108
 
            _logger.warning(
109
 
                "The XML file is invalid against the XML Schema Definition")
110
 
            _logger.warning(xml_string)
111
 
            _logger.warning(e)
112
 
            raise osv.except_osv(
113
 
                _('Error:'),
114
 
                _('''The generated XML file is not valid against the official 
115
 
                    XML Schema Definition. The generated XML file and the full 
116
 
                    error have been written in the server logs. Here is the 
117
 
                    error, which may give you an idea on the cause of the 
118
 
                    problem : %s''') % str(e))
119
 
        return True
120
 
    
121
 
    def prepare_config(self, cr, uid, payment_order, 
122
 
                       pain_flavor, batch_booking, 
123
 
                       prefered_exec_date, charge_bearer, context):
124
 
        
125
 
        config = {}
126
 
        if pain_flavor == 'pain.001.001.02':
127
 
            config['bic_xml_tag'] = 'BIC'
128
 
            config['name_maxsize'] = 70
129
 
            config['root_xml_tag'] = 'CstmrCdtTrfInitn'
130
 
            config['company_tag'] = 'Db'
131
 
            config['partner_tag'] = 'Cd'
132
 
            config['line_tag'] = 'CdtTrfTxInf'
133
 
        elif pain_flavor == 'pain.001.001.03':
134
 
            config['bic_xml_tag'] = 'BIC'
135
 
            # size 70 -> 140 for <Nm> with pain.001.001.03
136
 
            # BUT the European Payment Council, in the document
137
 
            # "SEPA Credit Transfer Scheme Customer-to-bank Implementation guidelines" v6.0
138
 
            # available on http://www.europeanpaymentscouncil.eu/knowledge_bank.cfm
139
 
            # says that 'Nm' should be limited to 70
140
 
            # so we follow the "European Payment Council" and we put 70 and not 140
141
 
            config['name_maxsize'] = 70
142
 
            config['root_xml_tag'] = 'CstmrCdtTrfInitn'
143
 
            config['company_tag'] = 'Db'
144
 
            config['partner_tag'] = 'Cd'
145
 
            config['line_tag'] = 'CdtTrfTxInf'
146
 
        elif pain_flavor == 'pain.001.001.04':
147
 
            config['bic_xml_tag'] = 'BICFI'
148
 
            config['name_maxsize'] = 70
149
 
            config['root_xml_tag'] = 'CstmrCdtTrfInitn'
150
 
            config['company_tag'] = 'Db'
151
 
            config['partner_tag'] = 'Cd'
152
 
            config['line_tag'] = 'CdtTrfTxInf'
153
 
        elif pain_flavor == 'pain.001.001.05':
154
 
            config['bic_xml_tag'] = 'BICFI'
155
 
            config['name_maxsize'] = 140
156
 
            config['root_xml_tag'] = 'CstmrCdtTrfInitn'
157
 
            config['company_tag'] = 'Db'
158
 
            config['partner_tag'] = 'Cd'
159
 
            config['line_tag'] = 'CdtTrfTxInf'
160
 
        elif pain_flavor == 'pain.008.001.02':
161
 
            config['bic_xml_tag'] = 'BIC'
162
 
            config['name_maxsize'] = 70
163
 
            config['root_xml_tag'] = 'CstmrDrctDbtInitn'
164
 
            config['company_tag'] = 'Cd'
165
 
            config['partner_tag'] = 'Db'
166
 
            config['line_tag'] = 'DrctDbtTxInf'
167
 
        else:
168
 
            raise osv.except_osv(
169
 
                _('Error:'),
170
 
                _("""Payment Type Code '%s' is not supported.
171
 
                 Type Codes supported for SEPA are 
172
 
                 'pain.001.001.02', 'pain.001.001.03', 'pain.001.001.04', 
173
 
                 'pain.001.001.05' and 'pain.008.001.02.""")
174
 
                % pain_flavor)
175
 
        config['pain_flavor'] = pain_flavor
176
 
        config['batch_booking'] = batch_booking
177
 
        if prefered_exec_date:
178
 
            config['prefered_exec_date'] = config['prefered_exec_date']
179
 
        else:
180
 
            config['prefered_exec_date'] = datetime.strftime(datetime.today() + 
181
 
                                           timedelta(days=1), '%Y-%m-%d')
182
 
        config['charge_bearer'] = charge_bearer
183
 
        config['company_name'] = self._prepare_field(
184
 
            cr, uid, 'Company Name',
185
 
            'payment_order.mode.bank_id.partner_id.name', 
186
 
            {'payment_order': payment_order}, config['name_maxsize'], 
187
 
            context=context)
188
 
        
189
 
        return config
190
 
    
191
 
    def get_GrpHdr(self,cr,uid, root, payment_order,config,context):
192
 
        # A. Group header
193
 
        pain_root = root.find(config['root_xml_tag'])
194
 
        group_header_1_0 = etree.SubElement(pain_root, 'GrpHdr')
195
 
        message_identification_1_1 = etree.SubElement(
196
 
            group_header_1_0, 'MsgId')
197
 
        message_identification_1_1.text = self._prepare_field(
198
 
            cr, uid, 'Message Identification',
199
 
            'payment_order.reference', 
200
 
            {'payment_order': payment_order}, 35, context=context)
201
 
        creation_date_time_1_2 = etree.SubElement(group_header_1_0, 'CreDtTm')
202
 
        creation_date_time_1_2.text = datetime.strftime(
203
 
            datetime.today(), '%Y-%m-%dT%H:%M:%S')
204
 
        if config['pain_flavor'] == 'pain.001.001.02':
205
 
            # batch_booking is in "Group header" with pain.001.001.02
206
 
            # and in "Payment info" in pain.001.001.03/04
207
 
            batch_booking = etree.SubElement(group_header_1_0, 'BtchBookg')
208
 
            batch_booking.text = str(sepa_export.batch_booking).lower()
209
 
        nb_of_transactions_1_6 = etree.SubElement(
210
 
            group_header_1_0, 'NbOfTxs')
211
 
        control_sum_1_7 = etree.SubElement(group_header_1_0, 'CtrlSum')
212
 
        # Grpg removed in pain.001.001.03
213
 
        if config['pain_flavor'] == 'pain.001.001.02':
214
 
            grouping = etree.SubElement(group_header_1_0, 'Grpg')
215
 
            grouping.text = 'GRPD'
216
 
        initiating_party_1_8 = etree.SubElement(group_header_1_0, 'InitgPty')
217
 
        initiating_party_name = etree.SubElement(initiating_party_1_8, 'Nm')
218
 
        initiating_party_name.text = config['company_name']
219
 
        if payment_order.mode.bank_id.partner_id.vat:
220
 
            initiating_party_id = etree.SubElement(initiating_party_1_8,'Id')
221
 
            initiating_party_orgid = etree.SubElement(initiating_party_id,
222
 
                                                      'OrgId')
223
 
            initiating_party_othr = etree.SubElement(initiating_party_orgid,
224
 
                                                     'Othr')
225
 
            initiating_party_othr_id = etree.SubElement(initiating_party_othr,
226
 
                                                        'Id')
227
 
            initiating_party_othr_id.text = \
228
 
                payment_order.mode.bank_id.partner_id.vat
229
 
        
230
 
        return root
231
 
    
232
 
    def get_PmtInf_basic(self,cr,uid, root, payment_order,config,context):
233
 
        # B. Payment info
234
 
        pain_root = root.find(config['root_xml_tag'])
235
 
        payment_info_2_0 = etree.SubElement(pain_root, 'PmtInf')
236
 
        payment_info_identification_2_1 = etree.SubElement(
237
 
            payment_info_2_0, 'PmtInfId')
238
 
        payment_info_identification_2_1.text = self._prepare_field(
239
 
            cr, uid, 'Payment Information Identification',
240
 
            "payment_order.reference", 
241
 
            {'payment_order': payment_order}, 35, context=context)
242
 
        payment_method_2_2 = etree.SubElement(payment_info_2_0, 'PmtMtd')
243
 
        if config['pain_flavor'] in ['pain.001.001.02','pain.001.001.03', 
244
 
                   'pain.001.001.04', 'pain.001.001.05']:
245
 
            payment_method_2_2.text = 'TRF'
246
 
        elif config['pain_flavor'] in ['pain.008.001.02']:
247
 
            payment_method_2_2.text = 'DD'
248
 
        if config['pain_flavor'] in [
249
 
                'pain.001.001.03', 'pain.001.001.04', 'pain.001.001.05']:
250
 
            # batch_booking is in "Group header" with pain.001.001.02
251
 
            # and in "Payment info" in pain.001.001.03/04
252
 
            batch_booking_2_3 = etree.SubElement(payment_info_2_0, 'BtchBookg')
253
 
            batch_booking_2_3.text = str(config['batch_booking']).lower()
254
 
        # It may seem surprising, but the
255
 
        # "SEPA Credit Transfer Scheme Customer-to-bank Implementation
256
 
        # guidelines" v6.0 says that control sum and nb_of_transactions
257
 
        # should be present at both "group header" level and "payment info"
258
 
        # level. This seems to be confirmed by the tests carried out at
259
 
        # BNP Paribas in PAIN v001.001.03
260
 
        if config['pain_flavor'] in [
261
 
                'pain.001.001.03', 'pain.001.001.04', 'pain.001.001.05']:
262
 
            nb_of_transactions_2_4 = etree.SubElement(
263
 
                payment_info_2_0, 'NbOfTxs')
264
 
            control_sum_2_5 = etree.SubElement(payment_info_2_0, 'CtrlSum')
265
 
        payment_type_info_2_6 = etree.SubElement(payment_info_2_0, 'PmtTpInf')
266
 
        service_level_2_8 = etree.SubElement(payment_type_info_2_6, 'SvcLvl')
267
 
        service_level_code_2_9 = etree.SubElement(service_level_2_8, 'Cd')
268
 
        service_level_code_2_9.text = 'SEPA'
269
 
        if config['pain_flavor'] in ['pain.001.001.02','pain.001.001.03', 
270
 
                   'pain.001.001.04', 'pain.001.001.05']:
271
 
            requested_exec_date_2_17 = etree.SubElement(
272
 
                payment_info_2_0, 'ReqdExctnDt')
273
 
        elif config['pain_flavor'] in ['pain.008.001.02']:
274
 
            requested_exec_date_2_17 = etree.SubElement(
275
 
                payment_info_2_0, 'ReqdColltnDt')
276
 
        requested_exec_date_2_17.text = config['prefered_exec_date']
277
 
        company_2_19 = etree.SubElement(payment_info_2_0, config['company_tag']+'tr')
278
 
        company_name = etree.SubElement(company_2_19, 'Nm')
279
 
        company_name.text = config['company_name']
280
 
        company_account_2_20 = etree.SubElement(payment_info_2_0, 
281
 
                                               config['company_tag']+'trAcct')
282
 
        company_account_id = etree.SubElement(company_account_2_20, 'Id')
283
 
        company_account_iban = etree.SubElement(company_account_id, 'IBAN')
284
 
        
285
 
        if (self._validate_iban(cr,uid,payment_order.mode.bank_id, 
286
 
                                context)):
287
 
            company_account_iban.text = self._prepare_field(
288
 
                    cr, uid, 'Company IBAN',
289
 
                    'payment_order.mode.bank_id.iban',  
290
 
                    {'payment_order': payment_order}, 
291
 
                    context=context).replace(' ', '')
292
 
        else:
293
 
            raise osv.except_osv(
294
 
                _('Error:'),
295
 
                _("""Company IBAN '%s' is not valid.""")
296
 
                % payment_order.mode.bank_id.iban)
297
 
        company_agent_2_21 = etree.SubElement(payment_info_2_0, 
298
 
                                             config['company_tag']+'trAgt')
299
 
        company_agent_institution = etree.SubElement(
300
 
            company_agent_2_21, 'FinInstnId')
301
 
        company_agent_bic = etree.SubElement(
302
 
            company_agent_institution, config['bic_xml_tag'])
303
 
        # TODO validate BIC with pattern
304
 
        # [A-Z]{6,6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3,3}){0,1}
305
 
        # because OpenERP doesn't have a constraint on BIC
306
 
        company_agent_bic.text = self._prepare_field(
307
 
            cr, uid, 'Company BIC',
308
 
            'payment_order.mode.bank_id.bank.bic',
309
 
            {'payment_order': payment_order}, context=context)
310
 
        charge_bearer_2_24 = etree.SubElement(payment_info_2_0, 'ChrgBr')
311
 
        charge_bearer_2_24.text = config['charge_bearer']
312
 
        
313
 
        return root
314
 
    
315
 
    def get_PmtInf_lines(self,cr,uid, root, payment_order,config,context):
316
 
        
317
 
        transactions_count = 0
318
 
        total_amount = 0.0
319
 
        amount_control_sum = 0.0
320
 
        total_amount = total_amount + payment_order.total
321
 
        
322
 
        payment_info_2_0 = root.find(config['root_xml_tag']).find('PmtInf')
323
 
        # Iterate each payment lines
324
 
        for line in payment_order.line_ids:
325
 
            transactions_count += 1
326
 
            transaction_info_2_27 = etree.SubElement(
327
 
            payment_info_2_0, config['line_tag'])
328
 
            payment_identification_2_28 = etree.SubElement(
329
 
                transaction_info_2_27, 'PmtId')
330
 
            end2end_identification_2_30 = etree.SubElement(
331
 
                payment_identification_2_28, 'EndToEndId')
332
 
            end2end_identification_2_30.text = self._prepare_field(
333
 
                cr, uid, 'End to End Identification', 'line.name',
334
 
                {'line': line}, 35, context=context)
335
 
            currency_name = self._prepare_field(
336
 
                cr, uid, 'Currency Code', 'line.currency.name',
337
 
                {'line': line}, 3, context=context)
338
 
            
339
 
            if config['pain_flavor'] in ['pain.001.001.02','pain.001.001.03', 
340
 
                               'pain.001.001.04', 'pain.001.001.05']:
341
 
                amount_2_42 = etree.SubElement(
342
 
                    transaction_info_2_27, 'Amt')
343
 
                instructed_amount_2_43 = etree.SubElement(
344
 
                    amount_2_42, 'InstdAmt', Ccy=currency_name)
345
 
            elif config['pain_flavor'] in ['pain.008.001.02']:
346
 
                instructed_amount_2_43 = etree.SubElement(
347
 
                    transaction_info_2_27, 
348
 
                    'InstdAmt', Ccy=currency_name)
349
 
                
350
 
            instructed_amount_2_43.text = '%.2f' % line.amount_currency
351
 
            amount_control_sum += line.amount_currency
352
 
            partner_agent_2_77 = etree.SubElement(
353
 
                transaction_info_2_27, config['partner_tag']+'trAgt')
354
 
            partner_agent_institution = etree.SubElement(
355
 
                partner_agent_2_77, 'FinInstnId')
356
 
            if not line.bank_id:
357
 
                raise osv.except_osv(
358
 
                    _('Error:'),
359
 
                    _("Missing Bank Account on invoice '%s' (payment order line reference '%s').")
360
 
                    % (line.ml_inv_ref.number, line.name))
361
 
            partner_agent_bic = etree.SubElement(
362
 
                partner_agent_institution, config['bic_xml_tag'])
363
 
            partner_agent_bic.text = self._prepare_field(
364
 
                cr, uid, 'Customer BIC', 'line.bank_id.bank.bic',
365
 
                {'line': line}, context=context)
366
 
            partner_2_79 = etree.SubElement(
367
 
                transaction_info_2_27, config['partner_tag']+'tr')
368
 
            partner_name = etree.SubElement(partner_2_79, 'Nm')
369
 
            partner_name.text = self._prepare_field(
370
 
                cr, uid, 'Customer Name', 'line.partner_id.name',
371
 
                {'line': line}, config['name_maxsize'], context=context)
372
 
            partner_account_2_80 = etree.SubElement(
373
 
                transaction_info_2_27, config['partner_tag']+'trAcct')
374
 
            partner_account_id = etree.SubElement(
375
 
                partner_account_2_80, 'Id')
376
 
            partner_account_iban = etree.SubElement(
377
 
                partner_account_id, 'IBAN')
378
 
            
379
 
            if (self._validate_iban(cr, uid, line.bank_id,context)):
380
 
                partner_account_iban.text =  self._prepare_field(
381
 
                        cr, uid, 'Customer IBAN',
382
 
                        'line.bank_id.iban', {'line': line},
383
 
                        context=context).replace(' ', '')
384
 
            else:
385
 
                raise osv.except_osv(
386
 
                    _('Error:'),
387
 
                    _("""Partner IBAN '%s' is not valid.""")
388
 
                    % line.bank_id.iban)
389
 
            remittance_info_2_91 = etree.SubElement(
390
 
                transaction_info_2_27, 'RmtInf')
391
 
            # switch to Structured (Strdr) ?
392
 
            # If we do it, beware that the format is not the same
393
 
            # between pain 02 and pain 03
394
 
            remittance_info_unstructured_2_99 = etree.SubElement(
395
 
                remittance_info_2_91, 'Ustrd')
396
 
            remittance_info_unstructured_2_99.text = self._prepare_field(
397
 
                cr, uid, 'Remittance Information', 'line.communication',
398
 
                {'line': line}, 140, context=context)
399
 
        
400
 
        return root, transactions_count, amount_control_sum, total_amount
401
 
    
402
 
    def create_sepa(self,cr,uid,payment_order, config,context):
403
 
        '''
404
 
        Create the SEPA file
405
 
        '''
406
 
 
407
 
        pain_ns = {
408
 
            'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
409
 
            None: 'urn:iso:std:iso:20022:tech:xsd:%s' % config['pain_flavor'],
410
 
            }
411
 
 
412
 
        root = etree.Element('Document', nsmap=pain_ns)
413
 
        pain_root = etree.SubElement(root, config['root_xml_tag'])
414
 
 
415
 
        root = self.get_GrpHdr(cr, uid, root, payment_order, 
416
 
                        config, context)
417
 
 
418
 
        root = self.get_PmtInf_basic(cr, uid, root, payment_order, 
419
 
                                     config, context)
420
 
 
421
 
        root, transactions_count, amount_control_sum, total_amount = \
422
 
            self.get_PmtInf_lines(cr, uid, root, payment_order, 
423
 
                                  config, context)
424
 
 
425
 
        nb_of_transactions_1_6 = pain_root.find('GrpHdr').find('NbOfTxs')
426
 
        nb_of_transactions_2_4 = pain_root.find('PmtInf').find('NbOfTxs')
427
 
        control_sum_1_7 = pain_root.find('GrpHdr').find('CtrlSum')
428
 
        control_sum_2_5 = pain_root.find('PmtInf').find('CtrlSum')
429
 
        if config['pain_flavor'] in [
430
 
                'pain.001.001.03', 'pain.001.001.04', 'pain.001.001.05']:
431
 
            nb_of_transactions_1_6.text = nb_of_transactions_2_4.text = \
432
 
                str(transactions_count)
433
 
            control_sum_1_7.text = control_sum_2_5.text = \
434
 
                '%.2f' % amount_control_sum
435
 
        else:
436
 
            nb_of_transactions_1_6.text = str(transactions_count)
437
 
            control_sum_1_7.text = '%.2f' % amount_control_sum
438
 
            
439
 
        return root, transactions_count, total_amount
440
 
    
441
 
    def create_file(self,cr,uid,root,config,payment_order,context):
442
 
 
443
 
        xml_string = etree.tostring(
444
 
            root, pretty_print=True, encoding='UTF-8', xml_declaration=True)
445
 
        _logger.debug(
446
 
            "Generated SEPA Credit Transfer XML file in format %s below"
447
 
            % config['pain_flavor'])
448
 
        _logger.debug(xml_string)
449
 
        self._validate_xml(cr, uid, xml_string, config)
450
 
        
451
 
        ##
452
 
        ## Generate the file and save as attachment
453
 
        file = base64.encodestring(xml_string)
454
 
 
455
 
        file_name = _("SEPA_report_%s_%s.xml") % (time.strftime(_("%Y-%m-%d")),
456
 
                                                  config['pain_flavor'])
457
 
        
458
 
        # Delete old files
459
 
        obj_attachment = self.pool.get('ir.attachment')
460
 
        attachment_ids = obj_attachment.search(cr, uid,
461
 
            [('name', '=', file_name), 
462
 
             ('res_model', '=', 'payment.order'),
463
 
             ('res_id', '=' , payment_order.id)])
464
 
        
465
 
        if len(attachment_ids):
466
 
            obj_attachment.unlink(cr, uid, attachment_ids)
467
 
        
468
 
        attach_id = obj_attachment.create(cr, uid, {
469
 
            'name' : file_name,
470
 
            'datas' : file,
471
 
            'datas_fname' : file_name,
472
 
            'res_model' : 'payment.order',
473
 
            'res_id' : payment_order.id
474
 
            }, context=context)
475
 
        
476
 
        return attach_id
477
 
    
478
 
sepa_pain_export()
 
 
b'\\ No newline at end of file'