~jsancho/sepa-tools/sepa-unpaid-6.0

« 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 16:21:23 UTC
  • Revision ID: ignacio@acysos.com-20140105162123-lkl37ff6lvcu5ewo
[FIX] sepa_pain, account_payment_sepa: improve inheritance

Show diffs side-by-side

added added

removed removed

Lines of Context:
92
92
            value = value[0:max_size]
93
93
        return value
94
94
    
95
 
    def _validate_xml(self, cr, uid, xml_string, pain_flavor):
 
95
    def _validate_xml(self, cr, uid, xml_string, config):
96
96
        xsd_etree_obj = etree.parse(
97
97
            tools.file_open(
98
98
                'sepa_pain/data/%s.xsd'
99
 
                % pain_flavor))
 
99
                % config['pain_flavor']))
100
100
        official_pain_schema = etree.XMLSchema(xsd_etree_obj)
101
101
 
102
102
        try:
116
116
                    problem : %s''') % str(e))
117
117
        return True
118
118
    
119
 
    def create_sepa(self,cr,uid,payment_order_id,
120
 
                    pain_flavor,my_batch_booking,
121
 
                    prefered_exec_date,charge_bearer,context):
122
 
        '''
123
 
        Create the SEPA file
124
 
        '''
125
 
        payment_order_obj = self.pool.get('payment.order')
126
 
        payment_order = payment_order_obj.browse(cr,uid,payment_order_id,
127
 
                                                 context)
128
 
 
 
119
    def prepare_config(self, cr, uid, payment_order, 
 
120
                       pain_flavor, batch_booking, 
 
121
                       prefered_exec_date, charge_bearer, context):
 
122
        
 
123
        config = {}
129
124
        if pain_flavor == 'pain.001.001.02':
130
 
            bic_xml_tag = 'BIC'
131
 
            name_maxsize = 70
132
 
            root_xml_tag = 'pain.001.001.02'
133
 
            company_tag = 'Db'
134
 
            partner_tag = 'Cd'
135
 
            line_tag = 'CdtTrfTxInf'
 
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'
136
131
        elif pain_flavor == 'pain.001.001.03':
137
 
            bic_xml_tag = 'BIC'
 
132
            config['bic_xml_tag'] = 'BIC'
138
133
            # size 70 -> 140 for <Nm> with pain.001.001.03
139
134
            # BUT the European Payment Council, in the document
140
135
            # "SEPA Credit Transfer Scheme Customer-to-bank Implementation guidelines" v6.0
141
136
            # available on http://www.europeanpaymentscouncil.eu/knowledge_bank.cfm
142
137
            # says that 'Nm' should be limited to 70
143
138
            # so we follow the "European Payment Council" and we put 70 and not 140
144
 
            name_maxsize = 70
145
 
            root_xml_tag = 'CstmrCdtTrfInitn'
146
 
            company_tag = 'Db'
147
 
            partner_tag = 'Cd'
148
 
            line_tag = 'CdtTrfTxInf'
 
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'
149
144
        elif pain_flavor == 'pain.001.001.04':
150
 
            bic_xml_tag = 'BICFI'
151
 
            name_maxsize = 140
152
 
            root_xml_tag = 'CstmrCdtTrfInitn'
153
 
            company_tag = 'Db'
154
 
            partner_tag = 'Cd'
155
 
            line_tag = 'CdtTrfTxInf'
 
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'
156
151
        elif pain_flavor == 'pain.001.001.05':
157
 
            bic_xml_tag = 'BICFI'
158
 
            name_maxsize = 140
159
 
            root_xml_tag = 'CstmrCdtTrfInitn'
160
 
            company_tag = 'Db'
161
 
            partner_tag = 'Cd'
162
 
            line_tag = 'CdtTrfTxInf'
 
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'
163
158
        elif pain_flavor == 'pain.008.001.02':
164
 
            bic_xml_tag = 'BIC'
165
 
            name_maxsize = 70
166
 
            root_xml_tag = 'CstmrDrctDbtInitn'
167
 
            company_tag = 'Cd'
168
 
            partner_tag = 'Db'
169
 
            line_tag = 'DrctDbtTxInf'
 
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'
170
165
        else:
171
166
            raise osv.except_osv(
172
167
                _('Error:'),
175
170
                 'pain.001.001.02', 'pain.001.001.03', 'pain.001.001.04', 
176
171
                 'pain.001.001.05' and 'pain.008.001.02.""")
177
172
                % pain_flavor)
178
 
        #if batch_booking:
179
 
        #    my_batch_booking = 'true'
180
 
        #else:
181
 
        #    my_batch_booking = 'false'
182
 
        #my_msg_identification = payment_order.name
 
173
        config['pain_flavor'] = pain_flavor
 
174
        config['batch_booking'] = batch_booking
183
175
        if prefered_exec_date:
184
 
            my_requested_exec_date = prefered_exec_date
 
176
            config['prefered_exec_date'] = config['prefered_exec_date']
185
177
        else:
186
 
            my_requested_exec_date = datetime.strftime(datetime.today() + 
 
178
            config['prefered_exec_date'] = datetime.strftime(datetime.today() + 
187
179
                                           timedelta(days=1), '%Y-%m-%d')
188
 
 
189
 
        pain_ns = {
190
 
            'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
191
 
            None: 'urn:iso:std:iso:20022:tech:xsd:%s' % pain_flavor,
192
 
            }
193
 
 
194
 
        root = etree.Element('Document', nsmap=pain_ns)
195
 
        pain_root = etree.SubElement(root, root_xml_tag)
196
 
        
197
 
        my_company_name = self._prepare_field(
 
180
        config['charge_bearer'] = charge_bearer
 
181
        config['company_name'] = self._prepare_field(
198
182
            cr, uid, 'Company Name',
199
183
            'payment_order.mode.bank_id.partner_id.name', 
200
 
            {'payment_order': payment_order}, name_maxsize, context=context)
 
184
            {'payment_order': payment_order}, config['name_maxsize'], 
 
185
            context=context)
201
186
        
 
187
        return config
 
188
    
 
189
    def get_GrpHdr(self,cr,uid, root, payment_order,config,context):
202
190
        # A. Group header
 
191
        pain_root = root.find(config['root_xml_tag'])
203
192
        group_header_1_0 = etree.SubElement(pain_root, 'GrpHdr')
204
193
        message_identification_1_1 = etree.SubElement(
205
194
            group_header_1_0, 'MsgId')
210
199
        creation_date_time_1_2 = etree.SubElement(group_header_1_0, 'CreDtTm')
211
200
        creation_date_time_1_2.text = datetime.strftime(
212
201
            datetime.today(), '%Y-%m-%dT%H:%M:%S')
213
 
        if pain_flavor == 'pain.001.001.02':
 
202
        if config['pain_flavor'] == 'pain.001.001.02':
214
203
            # batch_booking is in "Group header" with pain.001.001.02
215
204
            # and in "Payment info" in pain.001.001.03/04
216
205
            batch_booking = etree.SubElement(group_header_1_0, 'BtchBookg')
219
208
            group_header_1_0, 'NbOfTxs')
220
209
        control_sum_1_7 = etree.SubElement(group_header_1_0, 'CtrlSum')
221
210
        # Grpg removed in pain.001.001.03
222
 
        if pain_flavor == 'pain.001.001.02':
 
211
        if config['pain_flavor'] == 'pain.001.001.02':
223
212
            grouping = etree.SubElement(group_header_1_0, 'Grpg')
224
213
            grouping.text = 'GRPD'
225
214
        initiating_party_1_8 = etree.SubElement(group_header_1_0, 'InitgPty')
226
215
        initiating_party_name = etree.SubElement(initiating_party_1_8, 'Nm')
227
 
        initiating_party_name.text = my_company_name
 
216
        initiating_party_name.text = config['company_name']
228
217
        initiating_party_id = etree.SubElement(initiating_party_1_8,'Id')
229
218
        initiating_party_orgid = etree.SubElement(initiating_party_id,'OrgId')
230
219
        initiating_party_othr = etree.SubElement(initiating_party_orgid,'Othr')
232
221
                                                    'Id')
233
222
        initiating_party_othr_id.text=payment_order.mode.bank_id.partner_id.vat
234
223
        
 
224
        return root
 
225
    
 
226
    def get_PmtInf_basic(self,cr,uid, root, payment_order,config,context):
235
227
        # B. Payment info
 
228
        pain_root = root.find(config['root_xml_tag'])
236
229
        payment_info_2_0 = etree.SubElement(pain_root, 'PmtInf')
237
230
        payment_info_identification_2_1 = etree.SubElement(
238
231
            payment_info_2_0, 'PmtInfId')
241
234
            "payment_order.reference", 
242
235
            {'payment_order': payment_order}, 35, context=context)
243
236
        payment_method_2_2 = etree.SubElement(payment_info_2_0, 'PmtMtd')
244
 
        if pain_flavor in ['pain.001.001.02','pain.001.001.03', 
 
237
        if config['pain_flavor'] in ['pain.001.001.02','pain.001.001.03', 
245
238
                   'pain.001.001.04', 'pain.001.001.05']:
246
239
            payment_method_2_2.text = 'TRF'
247
 
        elif pain_flavor in ['pain.008.001.02']:
 
240
        elif config['pain_flavor'] in ['pain.008.001.02']:
248
241
            payment_method_2_2.text = 'DD'
249
 
        if pain_flavor in [
 
242
        if config['pain_flavor'] in [
250
243
                'pain.001.001.03', 'pain.001.001.04', 'pain.001.001.05']:
251
244
            # batch_booking is in "Group header" with pain.001.001.02
252
245
            # and in "Payment info" in pain.001.001.03/04
253
246
            batch_booking_2_3 = etree.SubElement(payment_info_2_0, 'BtchBookg')
254
 
            batch_booking_2_3.text = str(my_batch_booking).lower()
 
247
            batch_booking_2_3.text = str(config['batch_booking']).lower()
255
248
        # It may seem surprising, but the
256
249
        # "SEPA Credit Transfer Scheme Customer-to-bank Implementation
257
250
        # guidelines" v6.0 says that control sum and nb_of_transactions
258
251
        # should be present at both "group header" level and "payment info"
259
252
        # level. This seems to be confirmed by the tests carried out at
260
253
        # BNP Paribas in PAIN v001.001.03
261
 
        if pain_flavor in [
 
254
        if config['pain_flavor'] in [
262
255
                'pain.001.001.03', 'pain.001.001.04', 'pain.001.001.05']:
263
256
            nb_of_transactions_2_4 = etree.SubElement(
264
257
                payment_info_2_0, 'NbOfTxs')
267
260
        service_level_2_8 = etree.SubElement(payment_type_info_2_6, 'SvcLvl')
268
261
        service_level_code_2_9 = etree.SubElement(service_level_2_8, 'Cd')
269
262
        service_level_code_2_9.text = 'SEPA'
270
 
        if pain_flavor in ['pain.001.001.02','pain.001.001.03', 
 
263
        if config['pain_flavor'] in ['pain.001.001.02','pain.001.001.03', 
271
264
                   'pain.001.001.04', 'pain.001.001.05']:
272
265
            requested_exec_date_2_17 = etree.SubElement(
273
266
                payment_info_2_0, 'ReqdExctnDt')
274
 
        elif pain_flavor in ['pain.008.001.02']:
 
267
        elif config['pain_flavor'] in ['pain.008.001.02']:
275
268
            requested_exec_date_2_17 = etree.SubElement(
276
269
                payment_info_2_0, 'ReqdColltnDt')
277
 
        requested_exec_date_2_17.text = my_requested_exec_date
278
 
        company_2_19 = etree.SubElement(payment_info_2_0, company_tag+'tr')
 
270
        requested_exec_date_2_17.text = config['prefered_exec_date']
 
271
        company_2_19 = etree.SubElement(payment_info_2_0, config['company_tag']+'tr')
279
272
        company_name = etree.SubElement(company_2_19, 'Nm')
280
 
        company_name.text = my_company_name
 
273
        company_name.text = config['company_name']
281
274
        company_account_2_20 = etree.SubElement(payment_info_2_0, 
282
 
                                               company_tag+'trAcct')
 
275
                                               config['company_tag']+'trAcct')
283
276
        company_account_id = etree.SubElement(company_account_2_20, 'Id')
284
277
        company_account_iban = etree.SubElement(company_account_id, 'IBAN')
285
278
        
295
288
                _("""Company IBAN '%s' is not valid.""")
296
289
                % payment_order.mode.bank_id.iban)
297
290
        company_agent_2_21 = etree.SubElement(payment_info_2_0, 
298
 
                                             company_tag+'trAgt')
 
291
                                             config['company_tag']+'trAgt')
299
292
        company_agent_institution = etree.SubElement(
300
293
            company_agent_2_21, 'FinInstnId')
301
294
        company_agent_bic = etree.SubElement(
302
 
            company_agent_institution, bic_xml_tag)
 
295
            company_agent_institution, config['bic_xml_tag'])
303
296
        # TODO validate BIC with pattern
304
297
        # [A-Z]{6,6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3,3}){0,1}
305
298
        # because OpenERP doesn't have a constraint on BIC
308
301
            'payment_order.mode.bank_id.bank.bic',
309
302
            {'payment_order': payment_order}, context=context)
310
303
        charge_bearer_2_24 = etree.SubElement(payment_info_2_0, 'ChrgBr')
311
 
        charge_bearer_2_24.text = charge_bearer
312
 
 
 
304
        charge_bearer_2_24.text = config['charge_bearer']
 
305
        
 
306
        return root
 
307
    
 
308
    def get_PmtInf_lines(self,cr,uid, root, payment_order,config,context):
 
309
        
313
310
        transactions_count = 0
314
311
        total_amount = 0.0
315
312
        amount_control_sum = 0.0
316
 
 
317
313
        total_amount = total_amount + payment_order.total
 
314
        
 
315
        payment_info_2_0 = root.find(config['root_xml_tag']).find('PmtInf')
318
316
        # Iterate each payment lines
319
317
        for line in payment_order.line_ids:
320
318
            transactions_count += 1
321
 
            # C. Credit Transfer Transaction Info
322
319
            transaction_info_2_27 = etree.SubElement(
323
 
            payment_info_2_0, line_tag)
 
320
            payment_info_2_0, config['line_tag'])
324
321
            payment_identification_2_28 = etree.SubElement(
325
322
                transaction_info_2_27, 'PmtId')
326
323
            end2end_identification_2_30 = etree.SubElement(
332
329
                cr, uid, 'Currency Code', 'line.currency.name',
333
330
                {'line': line}, 3, context=context)
334
331
            
335
 
            if pain_flavor in ['pain.001.001.02','pain.001.001.03', 
 
332
            if config['pain_flavor'] in ['pain.001.001.02','pain.001.001.03', 
336
333
                               'pain.001.001.04', 'pain.001.001.05']:
337
334
                amount_2_42 = etree.SubElement(
338
335
                    transaction_info_2_27, 'Amt')
339
336
                instructed_amount_2_43 = etree.SubElement(
340
337
                    amount_2_42, 'InstdAmt', Ccy=currency_name)
341
 
            elif pain_flavor in ['pain.008.001.02']:
 
338
            elif config['pain_flavor'] in ['pain.008.001.02']:
342
339
                instructed_amount_2_43 = etree.SubElement(
343
340
                    transaction_info_2_27, 
344
341
                    'InstdAmt', Ccy=currency_name)
346
343
            instructed_amount_2_43.text = '%.2f' % line.amount_currency
347
344
            amount_control_sum += line.amount_currency
348
345
            partner_agent_2_77 = etree.SubElement(
349
 
                transaction_info_2_27, partner_tag+'trAgt')
 
346
                transaction_info_2_27, config['partner_tag']+'trAgt')
350
347
            partner_agent_institution = etree.SubElement(
351
348
                partner_agent_2_77, 'FinInstnId')
352
349
            if not line.bank_id:
355
352
                    _("Missing Bank Account on invoice '%s' (payment order line reference '%s').")
356
353
                    % (line.ml_inv_ref.number, line.name))
357
354
            partner_agent_bic = etree.SubElement(
358
 
                partner_agent_institution, bic_xml_tag)
 
355
                partner_agent_institution, config['bic_xml_tag'])
359
356
            partner_agent_bic.text = self._prepare_field(
360
357
                cr, uid, 'Customer BIC', 'line.bank_id.bank.bic',
361
358
                {'line': line}, context=context)
362
359
            partner_2_79 = etree.SubElement(
363
 
                transaction_info_2_27, partner_tag+'tr')
 
360
                transaction_info_2_27, config['partner_tag']+'tr')
364
361
            partner_name = etree.SubElement(partner_2_79, 'Nm')
365
362
            partner_name.text = self._prepare_field(
366
363
                cr, uid, 'Customer Name', 'line.partner_id.name',
367
 
                {'line': line}, name_maxsize, context=context)
 
364
                {'line': line}, config['name_maxsize'], context=context)
368
365
            partner_account_2_80 = etree.SubElement(
369
 
                transaction_info_2_27, partner_tag+'trAcct')
 
366
                transaction_info_2_27, config['partner_tag']+'trAcct')
370
367
            partner_account_id = etree.SubElement(
371
368
                partner_account_2_80, 'Id')
372
369
            partner_account_iban = etree.SubElement(
392
389
            remittance_info_unstructured_2_99.text = self._prepare_field(
393
390
                cr, uid, 'Remittance Information', 'line.communication',
394
391
                {'line': line}, 140, context=context)
395
 
 
396
 
        if pain_flavor in [
 
392
        
 
393
        return root, transactions_count, amount_control_sum, total_amount
 
394
    
 
395
    def create_sepa(self,cr,uid,payment_order, config,context):
 
396
        '''
 
397
        Create the SEPA file
 
398
        '''
 
399
 
 
400
        pain_ns = {
 
401
            'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
 
402
            None: 'urn:iso:std:iso:20022:tech:xsd:%s' % config['pain_flavor'],
 
403
            }
 
404
 
 
405
        root = etree.Element('Document', nsmap=pain_ns)
 
406
        pain_root = etree.SubElement(root, config['root_xml_tag'])
 
407
 
 
408
        root = self.get_GrpHdr(cr, uid, root, payment_order, 
 
409
                        config, context)
 
410
 
 
411
        root = self.get_PmtInf_basic(cr, uid, root, payment_order, 
 
412
                                     config, context)
 
413
 
 
414
        root, transactions_count, amount_control_sum, total_amount = \
 
415
            self.get_PmtInf_lines(cr, uid, root, payment_order, 
 
416
                                  config, context)
 
417
 
 
418
        nb_of_transactions_1_6 = pain_root.find('GrpHdr').find('NbOfTxs')
 
419
        nb_of_transactions_2_4 = pain_root.find('PmtInf').find('NbOfTxs')
 
420
        control_sum_1_7 = pain_root.find('GrpHdr').find('CtrlSum')
 
421
        control_sum_2_5 = pain_root.find('PmtInf').find('CtrlSum')
 
422
        if config['pain_flavor'] in [
397
423
                'pain.001.001.03', 'pain.001.001.04', 'pain.001.001.05']:
398
424
            nb_of_transactions_1_6.text = nb_of_transactions_2_4.text = \
399
425
                str(transactions_count)
402
428
        else:
403
429
            nb_of_transactions_1_6.text = str(transactions_count)
404
430
            control_sum_1_7.text = '%.2f' % amount_control_sum
405
 
        
 
431
            
 
432
        return root, transactions_count, total_amount
 
433
    
 
434
    def create_file(self,cr,uid,root,config,payment_order,context):
 
435
 
406
436
        xml_string = etree.tostring(
407
437
            root, pretty_print=True, encoding='UTF-8', xml_declaration=True)
408
438
        _logger.debug(
409
439
            "Generated SEPA Credit Transfer XML file in format %s below"
410
 
            % pain_flavor)
 
440
            % config['pain_flavor'])
411
441
        _logger.debug(xml_string)
412
 
        self._validate_xml(cr, uid, xml_string, pain_flavor)
 
442
        self._validate_xml(cr, uid, xml_string, config)
413
443
        
414
444
        ##
415
445
        ## Generate the file and save as attachment
416
446
        file = base64.encodestring(xml_string)
417
447
 
418
448
        file_name = _("SEPA_report_%s_%s.xml") % (time.strftime(_("%Y-%m-%d")),
419
 
                                                  pain_flavor)
 
449
                                                  config['pain_flavor'])
420
450
        
421
451
        # Delete old files
422
452
        obj_attachment = self.pool.get('ir.attachment')
436
466
            'res_id' : payment_order.id
437
467
            }, context=context)
438
468
        
439
 
        res = {}
440
 
        res['attach_id'] = attach_id
441
 
        res['total_amount'] = total_amount
442
 
        res['transactions_count'] = transactions_count
443
 
        
444
 
        return res
 
469
        return attach_id
445
470
    
446
471
sepa_pain_export()
 
 
b'\\ No newline at end of file'