~txerpa-openerp/sepa-tools/6.0

« back to all changes in this revision

Viewing changes to account_payment_sepa_direct_debit/wizard/export_sdd.py

  • Committer: Ignacio Ibeas - Acysos S.L.
  • Date: 2014-02-11 18:29:05 UTC
  • Revision ID: ignacio@acysos.com-20140211182905-bzlhkh052cnc0qfw
[ADD] Account Payment SEPA: modules SEPA for account_paymnt_extension adapted from banking addons

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- encoding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    SEPA Direct Debit module for OpenERP
 
5
#    Copyright (C) 2013 Akretion (http://www.akretion.com)
 
6
#    @author: Alexis de Lattre <alexis.delattre@akretion.com>
 
7
#
 
8
#    This program is free software: you can redistribute it and/or modify
 
9
#    it under the terms of the GNU Affero General Public License as
 
10
#    published by the Free Software Foundation, either version 3 of the
 
11
#    License, or (at your option) any later version.
 
12
#
 
13
#    This program is distributed in the hope that it will be useful,
 
14
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
#    GNU Affero General Public License for more details.
 
17
#
 
18
#    You should have received a copy of the GNU Affero General Public License
 
19
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
20
#
 
21
##############################################################################
 
22
 
 
23
 
 
24
from osv import fields, osv
 
25
from tools.translate import _
 
26
import netsvc
 
27
from datetime import datetime
 
28
from lxml import etree
 
29
 
 
30
 
 
31
class banking_export_sdd_wizard(osv.osv_memory):
 
32
    _name = 'banking.export.sdd.wizard'
 
33
    _inherit = 'banking.export.pain'
 
34
    _description = 'Export SEPA Direct Debit File'
 
35
    _columns = {
 
36
        'state': fields.selection([
 
37
            ('create', 'Create'),
 
38
            ('finish', 'Finish'),
 
39
            ], 'State', readonly=True),
 
40
        'batch_booking': fields.boolean(
 
41
            'Batch Booking',
 
42
            help="If true, the bank statement will display only one credit "
 
43
            "line for all the direct debits of the SEPA file ; if false, "
 
44
            "the bank statement will display one credit line per direct "
 
45
            "debit of the SEPA file."),
 
46
        'charge_bearer': fields.selection([
 
47
            ('SLEV', 'Following Service Level'),
 
48
            ('SHAR', 'Shared'),
 
49
            ('CRED', 'Borne by Creditor'),
 
50
            ('DEBT', 'Borne by Debtor'),
 
51
            ], 'Charge Bearer', required=True,
 
52
            help="Following service level : transaction charges are to be "
 
53
            "applied following the rules agreed in the service level and/or "
 
54
            "scheme (SEPA Core messages must use this). Shared : transaction "
 
55
            "charges on the creditor side are to be borne by the creditor, "
 
56
            "transaction charges on the debtor side are to be borne by the "
 
57
            "debtor. Borne by creditor : all transaction charges are to be "
 
58
            "borne by the creditor. Borne by debtor : all transaction "
 
59
            "charges are to be borne by the debtor."),
 
60
        'nb_transactions': fields.related(
 
61
            'file_id', 'nb_transactions', type='integer',
 
62
            string='Number of Transactions', readonly=True),
 
63
        'total_amount': fields.related(
 
64
            'file_id', 'total_amount', type='float', string='Total Amount',
 
65
            readonly=True),
 
66
        'file_id': fields.many2one(
 
67
            'banking.export.sdd', 'SDD File', readonly=True),
 
68
        'file': fields.related(
 
69
            'file_id', 'file', string="File", type='binary', readonly=True),
 
70
        'filename': fields.related(
 
71
            'file_id', 'filename', string="Filename", type='char', size=256,
 
72
            readonly=True),
 
73
        'payment_order_ids': fields.many2many(
 
74
            'payment.order', 'wiz_sdd_payorders_rel', 'wizard_id',
 
75
            'payment_order_id', 'Payment Orders', readonly=True),
 
76
        }
 
77
 
 
78
    _defaults = {
 
79
        'charge_bearer': 'SLEV',
 
80
        'state': 'create',
 
81
        }
 
82
 
 
83
    def create(self, cr, uid, vals, context=None):
 
84
        payment_order_ids = context.get('active_ids', [])
 
85
        vals.update({
 
86
            'payment_order_ids': [[6, 0, payment_order_ids]],
 
87
        })
 
88
        return super(banking_export_sdd_wizard, self).create(
 
89
            cr, uid, vals, context=context)
 
90
 
 
91
    def _get_previous_bank(self, cr, uid, payline, context=None):
 
92
        payline_obj = self.pool.get('payment.line')
 
93
        previous_bank = False
 
94
        payline_ids = payline_obj.search(
 
95
            cr, uid, [
 
96
                ('sdd_mandate_id', '=', payline.sdd_mandate_id.id),
 
97
                ('bank_id', '!=', payline.bank_id.id),
 
98
            ],
 
99
            context=context)
 
100
        if payline_ids:
 
101
            older_lines = payline_obj.browse(
 
102
                cr, uid, payline_ids, context=context)
 
103
            previous_date = False
 
104
            previous_payline_id = False
 
105
            for older_line in older_lines:
 
106
                older_line_date_sent = older_line.order_id.date_sent
 
107
                if (older_line_date_sent
 
108
                        and older_line_date_sent > previous_date):
 
109
                    previous_date = older_line_date_sent
 
110
                    previous_payline_id = older_line.id
 
111
            if previous_payline_id:
 
112
                previous_payline = payline_obj.browse(
 
113
                    cr, uid, previous_payline_id, context=context)
 
114
                previous_bank = previous_payline.bank_id
 
115
        return previous_bank
 
116
 
 
117
    def create_sepa(self, cr, uid, ids, context=None):
 
118
        '''
 
119
        Creates the SEPA Direct Debit file. That's the important code !
 
120
        '''
 
121
        sepa_export = self.browse(cr, uid, ids[0], context=context)
 
122
 
 
123
        pain_flavor = sepa_export.payment_order_ids[0].mode.type.code
 
124
        convert_to_ascii = \
 
125
            sepa_export.payment_order_ids[0].mode.convert_to_ascii
 
126
        if pain_flavor == 'pain.008.001.02':
 
127
            bic_xml_tag = 'BIC'
 
128
            name_maxsize = 70
 
129
            root_xml_tag = 'CstmrDrctDbtInitn'
 
130
        elif pain_flavor == 'pain.008.001.03':
 
131
            bic_xml_tag = 'BICFI'
 
132
            name_maxsize = 140
 
133
            root_xml_tag = 'CstmrDrctDbtInitn'
 
134
        elif pain_flavor == 'pain.008.001.04':
 
135
            bic_xml_tag = 'BICFI'
 
136
            name_maxsize = 140
 
137
            root_xml_tag = 'CstmrDrctDbtInitn'
 
138
        else:
 
139
            raise osv.except_osv(
 
140
                _('Error:'),
 
141
                _("Payment Type Code '%s' is not supported. The only "
 
142
                    "Payment Type Code supported for SEPA Direct Debit "
 
143
                    "are 'pain.008.001.02', 'pain.008.001.03' and "
 
144
                    "'pain.008.001.04'.") % pain_flavor)
 
145
 
 
146
        gen_args = {
 
147
            'bic_xml_tag': bic_xml_tag,
 
148
            'name_maxsize': name_maxsize,
 
149
            'convert_to_ascii': convert_to_ascii,
 
150
            'payment_method': 'DD',
 
151
            'pain_flavor': pain_flavor,
 
152
            'sepa_export': sepa_export,
 
153
            'file_obj': self.pool.get('banking.export.sdd'),
 
154
            'pain_xsd_file':
 
155
            'account_payment_sepa_direct_debit/data/%s.xsd' % pain_flavor,
 
156
        }
 
157
 
 
158
        pain_ns = {
 
159
            'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
 
160
            None: 'urn:iso:std:iso:20022:tech:xsd:%s' % pain_flavor,
 
161
            }
 
162
 
 
163
        xml_root = etree.Element('Document', nsmap=pain_ns)
 
164
        pain_root = etree.SubElement(xml_root, root_xml_tag)
 
165
 
 
166
        # A. Group header
 
167
        group_header_1_0, nb_of_transactions_1_6, control_sum_1_7 = \
 
168
            self.generate_group_header_block(
 
169
                cr, uid, pain_root, gen_args, context=context)
 
170
 
 
171
        transactions_count_1_6 = 0
 
172
        total_amount = 0.0
 
173
        amount_control_sum_1_7 = 0.0
 
174
        lines_per_group = {}
 
175
        # key = (requested_date, priority, sequence type)
 
176
        # value = list of lines as objects
 
177
        # Iterate on payment orders
 
178
        today = fields.date.today(self, cr, uid)
 
179
        for payment_order in sepa_export.payment_order_ids:
 
180
            total_amount = total_amount + payment_order.total
 
181
            # Iterate each payment lines
 
182
            for line in payment_order.line_ids:
 
183
                transactions_count_1_6 += 1
 
184
                priority = line.priority
 
185
                if payment_order.date_prefered == 'due':
 
186
                    requested_date = line.ml_maturity_date or today
 
187
                elif payment_order.date_prefered == 'fixed':
 
188
                    requested_date = payment_order.date_scheduled or today
 
189
                else:
 
190
                    requested_date = today
 
191
                if not line.sdd_mandate_id:
 
192
                    raise osv.except_osv(
 
193
                        _('Error:'),
 
194
                        _("Missing SEPA Direct Debit mandate on the payment "
 
195
                            "line with partner '%s' and Invoice ref '%s'.")
 
196
                        % (line.partner_id.name,
 
197
                            line.ml_inv_ref.number))
 
198
                if line.sdd_mandate_id.state != 'valid':
 
199
                    raise osv.except_osv(
 
200
                        _('Error:'),
 
201
                        _("The SEPA Direct Debit mandate with reference '%s' "
 
202
                            "for partner '%s' has expired.")
 
203
                        % (line.sdd_mandate_id.unique_mandate_reference,
 
204
                            line.sdd_mandate_id.partner_id.name))
 
205
                if line.sdd_mandate_id.type == 'oneoff':
 
206
                    if not line.sdd_mandate_id.last_debit_date:
 
207
                        seq_type = 'OOFF'
 
208
                    else:
 
209
                        raise osv.except_osv(
 
210
                            _('Error:'),
 
211
                            _("The mandate with reference '%s' for partner "
 
212
                                "'%s' has type set to 'One-Off' and it has a "
 
213
                                "last debit date set to '%s', so we can't use "
 
214
                                "it.")
 
215
                            % (line.sdd_mandate_id.unique_mandate_reference,
 
216
                                line.sdd_mandate_id.partner_id.name,
 
217
                                line.sdd_mandate_id.last_debit_date))
 
218
                elif line.sdd_mandate_id.type == 'recurrent':
 
219
                    seq_type_map = {
 
220
                        'recurring': 'RCUR',
 
221
                        'first': 'FRST',
 
222
                        'final': 'FNAL',
 
223
                        }
 
224
                    seq_type_label = \
 
225
                        line.sdd_mandate_id.recurrent_sequence_type
 
226
                    assert seq_type_label is not False
 
227
                    seq_type = seq_type_map[seq_type_label]
 
228
 
 
229
                key = (requested_date, priority, seq_type)
 
230
                if key in lines_per_group:
 
231
                    lines_per_group[key].append(line)
 
232
                else:
 
233
                    lines_per_group[key] = [line]
 
234
                # Write requested_exec_date on 'Payment date' of the pay line
 
235
                if requested_date != line.date:
 
236
                    self.pool.get('payment.line').write(
 
237
                        cr, uid, line.id,
 
238
                        {'date': requested_date}, context=context)
 
239
 
 
240
        for (requested_date, priority, sequence_type), lines in \
 
241
                lines_per_group.items():
 
242
            # B. Payment info
 
243
            payment_info_2_0, nb_of_transactions_2_4, control_sum_2_5 = \
 
244
                self.generate_start_payment_info_block(
 
245
                    cr, uid, pain_root,
 
246
                    "sepa_export.payment_order_ids[0].reference + '-' + "
 
247
                    "sequence_type + '-' + requested_date.replace('-', '')  "
 
248
                    "+ '-' + priority",
 
249
                    priority, 'CORE', sequence_type, requested_date, {
 
250
                        'sepa_export': sepa_export,
 
251
                        'sequence_type': sequence_type,
 
252
                        'priority': priority,
 
253
                        'requested_date': requested_date,
 
254
                    }, gen_args, context=context)
 
255
 
 
256
            self.generate_party_block(
 
257
                cr, uid, payment_info_2_0, 'Cdtr', 'B',
 
258
                'sepa_export.payment_order_ids[0].mode.bank_id.partner_id.'
 
259
                'name',
 
260
                'sepa_export.payment_order_ids[0].mode.bank_id.iban',
 
261
                'sepa_export.payment_order_ids[0].mode.bank_id.bank.bic',
 
262
                sepa_export.payment_order_ids[0].mode.bank_id.id,
 
263
                {'sepa_export': sepa_export},
 
264
                gen_args, context=context)
 
265
 
 
266
            charge_bearer_2_24 = etree.SubElement(payment_info_2_0, 'ChrgBr')
 
267
            charge_bearer_2_24.text = sepa_export.charge_bearer
 
268
 
 
269
            creditor_scheme_identification_2_27 = etree.SubElement(
 
270
                payment_info_2_0, 'CdtrSchmeId')
 
271
            self.generate_creditor_scheme_identification(
 
272
                cr, uid, creditor_scheme_identification_2_27,
 
273
                'sepa_export.payment_order_ids[0].mode.company_id.'
 
274
                'sepa_creditor_identifier',
 
275
                'SEPA Creditor Identifier', {'sepa_export': sepa_export},
 
276
                'SEPA', gen_args, context=context)
 
277
 
 
278
            transactions_count_2_4 = 0
 
279
            amount_control_sum_2_5 = 0.0
 
280
            for line in lines:
 
281
                transactions_count_2_4 += 1
 
282
                # C. Direct Debit Transaction Info
 
283
                dd_transaction_info_2_28 = etree.SubElement(
 
284
                    payment_info_2_0, 'DrctDbtTxInf')
 
285
                payment_identification_2_29 = etree.SubElement(
 
286
                    dd_transaction_info_2_28, 'PmtId')
 
287
                end2end_identification_2_31 = etree.SubElement(
 
288
                    payment_identification_2_29, 'EndToEndId')
 
289
                end2end_identification_2_31.text = self._prepare_field(
 
290
                    cr, uid, 'End to End Identification', 'line.name',
 
291
                    {'line': line}, 35,
 
292
                    gen_args=gen_args, context=context)
 
293
                currency_name = self._prepare_field(
 
294
                    cr, uid, 'Currency Code', 'line.currency.name',
 
295
                    {'line': line}, 3, gen_args=gen_args,
 
296
                    context=context)
 
297
                instructed_amount_2_44 = etree.SubElement(
 
298
                    dd_transaction_info_2_28, 'InstdAmt', Ccy=currency_name)
 
299
                instructed_amount_2_44.text = '%.2f' % line.amount_currency
 
300
                amount_control_sum_1_7 += line.amount_currency
 
301
                amount_control_sum_2_5 += line.amount_currency
 
302
                dd_transaction_2_46 = etree.SubElement(
 
303
                    dd_transaction_info_2_28, 'DrctDbtTx')
 
304
                mandate_related_info_2_47 = etree.SubElement(
 
305
                    dd_transaction_2_46, 'MndtRltdInf')
 
306
                mandate_identification_2_48 = etree.SubElement(
 
307
                    mandate_related_info_2_47, 'MndtId')
 
308
                mandate_identification_2_48.text = self._prepare_field(
 
309
                    cr, uid, 'Unique Mandate Reference',
 
310
                    'line.sdd_mandate_id.unique_mandate_reference',
 
311
                    {'line': line}, 35,
 
312
                    gen_args=gen_args, context=context)
 
313
                mandate_signature_date_2_49 = etree.SubElement(
 
314
                    mandate_related_info_2_47, 'DtOfSgntr')
 
315
                mandate_signature_date_2_49.text = self._prepare_field(
 
316
                    cr, uid, 'Mandate Signature Date',
 
317
                    'line.sdd_mandate_id.signature_date',
 
318
                    {'line': line}, 10,
 
319
                    gen_args=gen_args, context=context)
 
320
                if sequence_type == 'FRST' and (
 
321
                        line.sdd_mandate_id.last_debit_date or
 
322
                        not line.sdd_mandate_id.sepa_migrated):
 
323
                    previous_bank = self._get_previous_bank(
 
324
                        cr, uid, line, context=context)
 
325
                    if previous_bank or not line.sdd_mandate_id.sepa_migrated:
 
326
                        amendment_indicator_2_50 = etree.SubElement(
 
327
                            mandate_related_info_2_47, 'AmdmntInd')
 
328
                        amendment_indicator_2_50.text = 'true'
 
329
                        amendment_info_details_2_51 = etree.SubElement(
 
330
                            mandate_related_info_2_47, 'AmdmntInfDtls')
 
331
                    if previous_bank:
 
332
                        if previous_bank.bank.bic == line.bank_id.bank.bic:
 
333
                            ori_debtor_account_2_57 = etree.SubElement(
 
334
                                amendment_info_details_2_51, 'OrgnlDbtrAcct')
 
335
                            ori_debtor_account_id = etree.SubElement(
 
336
                                ori_debtor_account_2_57, 'Id')
 
337
                            ori_debtor_account_iban = etree.SubElement(
 
338
                                ori_debtor_account_id, 'IBAN')
 
339
                            ori_debtor_account_iban.text = self._validate_iban(
 
340
                                cr, uid, self._prepare_field(
 
341
                                    cr, uid, 'Original Debtor Account',
 
342
                                    'previous_bank.iban',
 
343
                                    {'previous_bank': previous_bank},
 
344
                                    gen_args=gen_args,
 
345
                                    context=context),
 
346
                                context=context)
 
347
                        else:
 
348
                            ori_debtor_agent_2_58 = etree.SubElement(
 
349
                                amendment_info_details_2_51, 'OrgnlDbtrAgt')
 
350
                            ori_debtor_agent_institution = etree.SubElement(
 
351
                                ori_debtor_agent_2_58, 'FinInstnId')
 
352
                            ori_debtor_agent_bic = etree.SubElement(
 
353
                                ori_debtor_agent_institution, bic_xml_tag)
 
354
                            ori_debtor_agent_bic.text = self._prepare_field(
 
355
                                cr, uid, 'Original Debtor Agent',
 
356
                                'previous_bank.bank.bic',
 
357
                                {'previous_bank': previous_bank},
 
358
                                gen_args=gen_args,
 
359
                                context=context)
 
360
                            ori_debtor_agent_other = etree.SubElement(
 
361
                                ori_debtor_agent_institution, 'Othr')
 
362
                            ori_debtor_agent_other_id = etree.SubElement(
 
363
                                ori_debtor_agent_other, 'Id')
 
364
                            ori_debtor_agent_other_id.text = 'SMNDA'
 
365
                            # SMNDA = Same Mandate New Debtor Agent
 
366
                    elif not line.sdd_mandate_id.sepa_migrated:
 
367
                        ori_mandate_identification_2_52 = etree.SubElement(
 
368
                            amendment_info_details_2_51, 'OrgnlMndtId')
 
369
                        ori_mandate_identification_2_52.text = \
 
370
                            self._prepare_field(
 
371
                                cr, uid, 'Original Mandate Identification',
 
372
                                'line.sdd_mandate_id.'
 
373
                                'original_mandate_identification',
 
374
                                {'line': line},
 
375
                                gen_args=gen_args,
 
376
                                context=context)
 
377
                        ori_creditor_scheme_id_2_53 = etree.SubElement(
 
378
                            amendment_info_details_2_51, 'OrgnlCdtrSchmeId')
 
379
                        self.generate_creditor_scheme_identification(
 
380
                            cr, uid, ori_creditor_scheme_id_2_53,
 
381
                            'sepa_export.payment_order_ids[0].mode.company_id.'
 
382
                            'original_creditor_identifier',
 
383
                            'Original Creditor Identifier',
 
384
                            {'sepa_export': sepa_export},
 
385
                            'SEPA', gen_args, context=context)
 
386
 
 
387
                self.generate_party_block(
 
388
                    cr, uid, dd_transaction_info_2_28, 'Dbtr', 'C',
 
389
                    'line.partner_id.name',
 
390
                    'line.bank_id.iban',
 
391
                    'line.bank_id.bank.bic',
 
392
                    line.bank_id.id,
 
393
                    {'line': line}, gen_args, context=context)
 
394
 
 
395
                self.generate_remittance_info_block(
 
396
                    cr, uid, dd_transaction_info_2_28,
 
397
                    line, gen_args, context=context)
 
398
 
 
399
            nb_of_transactions_2_4.text = str(transactions_count_2_4)
 
400
            control_sum_2_5.text = '%.2f' % amount_control_sum_2_5
 
401
        nb_of_transactions_1_6.text = str(transactions_count_1_6)
 
402
        control_sum_1_7.text = '%.2f' % amount_control_sum_1_7
 
403
        
 
404
        return self.finalize_sepa_file_creation(
 
405
            cr, uid, ids, xml_root, total_amount, transactions_count_1_6,
 
406
            gen_args, context=context)
 
407
 
 
408
    def cancel_sepa(self, cr, uid, ids, context=None):
 
409
        '''
 
410
        Cancel the SEPA file: just drop the file
 
411
        '''
 
412
        sepa_export = self.browse(cr, uid, ids[0], context=context)
 
413
        self.pool.get('banking.export.sdd').unlink(
 
414
            cr, uid, sepa_export.file_id.id, context=context)
 
415
        return {'type': 'ir.actions.act_window_close'}
 
416
 
 
417
    def save_sepa(self, cr, uid, ids, context=None):
 
418
        '''
 
419
        Save the SEPA Direct Debit file: mark all payments in the file
 
420
        as 'sent'. Write 'last debit date' on mandate and set oneoff
 
421
        mandate to expired
 
422
        '''
 
423
        sepa_export = self.browse(cr, uid, ids[0], context=context)
 
424
        self.pool.get('banking.export.sdd').write(
 
425
            cr, uid, sepa_export.file_id.id, {'state': 'sent'},
 
426
            context=context)
 
427
        wf_service = netsvc.LocalService('workflow')
 
428
        for order in sepa_export.payment_order_ids:
 
429
            wf_service.trg_validate(uid, 'payment.order', order.id, 'done', cr)
 
430
            mandate_ids = [line.sdd_mandate_id.id for line in order.line_ids]
 
431
            self.pool.get('sdd.mandate').write(
 
432
                cr, uid, mandate_ids,
 
433
                {'last_debit_date': datetime.today().strftime('%Y-%m-%d')},
 
434
                context=context)
 
435
            to_expire_ids = []
 
436
            first_mandate_ids = []
 
437
            for line in order.line_ids:
 
438
                if line.sdd_mandate_id.type == 'oneoff':
 
439
                    to_expire_ids.append(line.sdd_mandate_id.id)
 
440
                elif line.sdd_mandate_id.type == 'recurrent':
 
441
                    seq_type = line.sdd_mandate_id.recurrent_sequence_type
 
442
                    if seq_type == 'final':
 
443
                        to_expire_ids.append(line.sdd_mandate_id.id)
 
444
                    elif seq_type == 'first':
 
445
                        first_mandate_ids.append(line.sdd_mandate_id.id)
 
446
            self.pool.get('sdd.mandate').write(
 
447
                cr, uid, to_expire_ids, {'state': 'expired'}, context=context)
 
448
            self.pool.get('sdd.mandate').write(
 
449
                cr, uid, first_mandate_ids, {
 
450
                    'recurrent_sequence_type': 'recurring',
 
451
                    'sepa_migrated': True,
 
452
                }, context=context)
 
453
        return {'type': 'ir.actions.act_window_close'}
 
454
banking_export_sdd_wizard()
 
 
b'\\ No newline at end of file'