~santiago-pexego/sepa-tools/make_payments_6.1

« back to all changes in this revision

Viewing changes to sepa_pain/sepa_pain.py

  • Committer: Ignacio Ibeas - Acysos S.L.
  • Date: 2014-01-05 17:45:54 UTC
  • Revision ID: ignacio@acysos.com-20140105174554-q5namikd89xbanyk
[ADD] sepa_pain: generic sepa xml files

Show diffs side-by-side

added added

removed removed

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