From 05319329c63e79e326c84b62441d3ba84f73aaab Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Tue, 15 Oct 2013 23:29:28 +0200 Subject: [PATCH] First implementation of mandates, but I still have a lot of hesitation about the data model so it may change. Manage different sequence types in the same file ; we just have to separate them in different payment info blocks. --- account_banking_sepa_direct_debit/__init__.py | 6 +- .../__openerp__.py | 2 + .../account_banking_sdd.py | 85 ++++++ .../account_banking_sdd_view.xml | 102 +++++++ .../account_payment_view.xml | 26 ++ account_banking_sepa_direct_debit/company.py | 6 +- .../data/mandate_reference_sequence.xml | 21 ++ .../security/ir.model.access.csv | 2 + .../wizard/__init__.py | 2 +- .../wizard/export_sdd.py | 256 +++++++++++------- 10 files changed, 407 insertions(+), 101 deletions(-) create mode 100644 account_banking_sepa_direct_debit/account_payment_view.xml create mode 100644 account_banking_sepa_direct_debit/data/mandate_reference_sequence.xml diff --git a/account_banking_sepa_direct_debit/__init__.py b/account_banking_sepa_direct_debit/__init__.py index bda7501b7..ce46fda33 100644 --- a/account_banking_sepa_direct_debit/__init__.py +++ b/account_banking_sepa_direct_debit/__init__.py @@ -20,7 +20,7 @@ # ############################################################################## -import company -import wizard -import account_banking_sdd +from . import company +from . import wizard +from . import account_banking_sdd diff --git a/account_banking_sepa_direct_debit/__openerp__.py b/account_banking_sepa_direct_debit/__openerp__.py index fc329a073..2a44e1c06 100644 --- a/account_banking_sepa_direct_debit/__openerp__.py +++ b/account_banking_sepa_direct_debit/__openerp__.py @@ -29,9 +29,11 @@ 'depends': ['account_direct_debit'], 'data': [ 'account_banking_sdd_view.xml', + 'account_payment_view.xml', 'company_view.xml', 'wizard/export_sdd_view.xml', 'data/payment_type_sdd.xml', + 'data/mandate_reference_sequence.xml', 'security/ir.model.access.csv', ], 'description': ''' diff --git a/account_banking_sepa_direct_debit/account_banking_sdd.py b/account_banking_sepa_direct_debit/account_banking_sdd.py index aa08918a2..7e5a5d0f3 100644 --- a/account_banking_sepa_direct_debit/account_banking_sdd.py +++ b/account_banking_sepa_direct_debit/account_banking_sdd.py @@ -75,3 +75,88 @@ class banking_export_sdd(orm.Model): 'generation_date': fields.date.context_today, 'state': 'draft', } + + +class sdd_mandate(orm.Model): + '''SEPA Direct Debit Mandate''' + _name = 'sdd.mandate' + _description = __doc__ + _rec_name = 'unique_mandate_reference' + _order = 'signature_date desc' + + _columns = { + 'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account'), + 'partner_id': fields.related( + 'partner_bank_id', 'partner_id', type='many2one', + relation='res.partner', string='Partner', readonly=True), + 'company_id': fields.many2one('res.company', 'Company', required=True), + 'unique_mandate_reference': fields.char( + 'Unique Mandate Reference', size=35, readonly=True), + 'type': fields.selection([ + ('recurrent', 'Recurrent'), + ('oneoff', 'One-Off'), + ], 'Type of Mandate', required=True), + 'signature_date': fields.date('Date of Signature of the Mandate'), + 'scan': fields.binary('Scan of the mandate'), + 'last_debit_date': fields.date('Date of the Last Debit', + help="For recurrent mandates, this field is used to know if the SDD will be of type 'First' or 'Recurring'. For one-off mandates, this field is used to know if the SDD has already been used or not."), + 'state': fields.selection([ + ('valid', 'Valid'), + ('expired', 'Expired'), + ], 'Mandate Status', + help="For a recurrent mandate, this field indicate if the mandate is still valid or if it has expired (a recurrent mandate expires if it's not used during 36 months). For a one-off mandate, it expires after its first use."), + } + + _sql_constraints = [( + 'mandate_ref_company_uniq', + 'unique(unique_mandate_reference, company_id)', + 'A Mandate with the same reference already exists for this company !' + )] + + _defaults = { + 'company_id': lambda self, cr, uid, context: \ + self.pool['res.users'].browse(cr, uid, uid, context=context).\ + company_id.id, + 'unique_mandate_reference': lambda self, cr, uid, context: \ + self.pool['ir.sequence'].get(cr, uid, 'sdd.mandate.reference'), + 'state': 'valid', + } + + +class res_partner_bank(orm.Model): + _inherit = 'res.partner.bank' + + _columns = { + 'sdd_mandate_ids': fields.one2many( + 'sdd.mandate', 'partner_bank_id', 'SEPA Direct Debit Mandates'), + } + + +class payment_line(orm.Model): + _inherit = 'payment.line' + + _columns = { + 'sdd_mandate_id': fields.many2one( + 'sdd.mandate', 'SEPA Direct Debit Mandate'), + } + + def _check_sdd_mandate(self, cr, uid, ids): + for payline in self.browse(cr, uid, ids): + if payline.sdd_mandate_id and not payline.bank_id: + raise orm.except_orm( + _('Error :'), + _("Missing bank account on the payment line with SEPA\ + Direct Debit Mandate '%s'." + % payline.sdd_mandate_id.unique_mandate_reference)) + elif payline.sdd_mandate_id and payline.bank_id and payline.sdd_mandate_id.partner_bank_id != payline.bank_id.id: + raise orm.except_orm( + _('Error :'), + _("The SEPA Direct Debit Mandate '%s' is not related??")) + + return True + +# _constraints = [ +# (_check_sdd_mandate, "Mandate must be attached to bank account", ['bank_id', 'sdd_mandate_id']), +# ] + + # TODO inherit create to select the first mandate ?? diff --git a/account_banking_sepa_direct_debit/account_banking_sdd_view.xml b/account_banking_sepa_direct_debit/account_banking_sdd_view.xml index 6dcfb903f..32a289081 100644 --- a/account_banking_sepa_direct_debit/account_banking_sdd_view.xml +++ b/account_banking_sepa_direct_debit/account_banking_sdd_view.xml @@ -79,5 +79,107 @@ view_mode="tree,form" /> + + sdd.mandate.form + sdd.mandate + +
+
+ +
+ +
+

+

+
+ + + + + + + + + +
+
+
+
+ + + sdd.mandate.tree + sdd.mandate + + + + + + + + + + + + + + + SEPA Direct Debit Mandate + sdd.mandate + form + tree,form + {'sdd_mandate_main_view': True} + +

+ Click to create a new SEPA Direct Debit Mandate. +

+ The SEPA Direct Debit Mandate is a document signed by your customer that gives you the autorization to do one or several direct debits on his bank account. +

+
+
+ + + + + sdd.mandate.res.partner.bank.form + res.partner.bank + + + + + + + + + + + + sdd.mandate.res.partner.bank.tree + res.partner.bank + + + + + + + + + + + sdd.mandate.partner.form + res.partner + + + + + + + + diff --git a/account_banking_sepa_direct_debit/account_payment_view.xml b/account_banking_sepa_direct_debit/account_payment_view.xml new file mode 100644 index 000000000..795f30222 --- /dev/null +++ b/account_banking_sepa_direct_debit/account_payment_view.xml @@ -0,0 +1,26 @@ + + + + + + + sdd.payment.order.form + payment.order + + + + + + + + + + + + + + diff --git a/account_banking_sepa_direct_debit/company.py b/account_banking_sepa_direct_debit/company.py index d85fc6fd7..aed40f64b 100644 --- a/account_banking_sepa_direct_debit/company.py +++ b/account_banking_sepa_direct_debit/company.py @@ -24,15 +24,16 @@ import logging logger = logging.getLogger(__name__) + class res_company(orm.Model): _inherit = 'res.company' _columns = { - 'sepa_creditor_identifier': fields.char('SEPA Creditor Identifier', size=35, + 'sepa_creditor_identifier': fields.char( + 'SEPA Creditor Identifier', size=35, help="Enter the Creditor Identifier that has been attributed to your company to make SEPA Direct Debits. This identifier is composed of :\n- your country ISO code (2 letters)\n- a 2-digits checkum\n- a 3-letters business code\n- a country-specific identifier"), } - def is_sepa_creditor_identifier_valid(self, cr, uid, sepa_creditor_identifier, context=None): """Check if SEPA Creditor Identifier is valid @param sepa_creditor_identifier: SEPA Creditor Identifier as str or unicode @@ -62,7 +63,6 @@ class res_company(orm.Model): else: return False - def _check_sepa_creditor_identifier(self, cr, uid, ids): for company in self.browse(cr, uid, ids): if company.sepa_creditor_identifier: diff --git a/account_banking_sepa_direct_debit/data/mandate_reference_sequence.xml b/account_banking_sepa_direct_debit/data/mandate_reference_sequence.xml new file mode 100644 index 000000000..6a3143cca --- /dev/null +++ b/account_banking_sepa_direct_debit/data/mandate_reference_sequence.xml @@ -0,0 +1,21 @@ + + + + + + + SDD Mandate Reference + sdd.mandate.reference + + + + SDD Mandate Reference + sdd.mandate.reference + RUM + + + + + + + diff --git a/account_banking_sepa_direct_debit/security/ir.model.access.csv b/account_banking_sepa_direct_debit/security/ir.model.access.csv index 0cd579511..cf78ffb59 100644 --- a/account_banking_sepa_direct_debit/security/ir.model.access.csv +++ b/account_banking_sepa_direct_debit/security/ir.model.access.csv @@ -1,2 +1,4 @@ "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" "access_banking_export_sdd","Full access on banking.export.sdd","model_banking_export_sdd","account_payment.group_account_payment",1,1,1,1 +"access_sdd_mandate","Full access on sdd.mandate","model_sdd_mandate","account_payment.group_account_payment",1,1,1,1 +"access_sdd_mandate_read","Read access on sdd.mandate","model_sdd_mandate","base.group_user",1,0,0,0 diff --git a/account_banking_sepa_direct_debit/wizard/__init__.py b/account_banking_sepa_direct_debit/wizard/__init__.py index 169d0b13d..3830e36d9 100644 --- a/account_banking_sepa_direct_debit/wizard/__init__.py +++ b/account_banking_sepa_direct_debit/wizard/__init__.py @@ -20,4 +20,4 @@ # ############################################################################## -import export_sdd +from . import export_sdd diff --git a/account_banking_sepa_direct_debit/wizard/export_sdd.py b/account_banking_sepa_direct_debit/wizard/export_sdd.py index 60456ab1a..3cc06e845 100644 --- a/account_banking_sepa_direct_debit/wizard/export_sdd.py +++ b/account_banking_sepa_direct_debit/wizard/export_sdd.py @@ -22,10 +22,10 @@ from openerp.osv import orm, fields -import base64 -from datetime import datetime, timedelta from openerp.tools.translate import _ from openerp import tools, netsvc +import base64 +from datetime import datetime, timedelta from lxml import etree import logging @@ -115,7 +115,7 @@ class banking_export_sdd_wizard(orm.TransientModel): else: raise orm.except_orm(_('Error :'), _("Cannot compute the '%s'.") % field_name) if not isinstance(value, (str, unicode)): - raise orm.except_orm(_('Field type error :'), _("The '%s' is a(n) %s. It should be a string or unicode.") % (field_name, type(value))) + raise orm.except_orm(_('Field type error :'), _("The type of the field '%s' is %s. It should be a string or unicode.") % (field_name, type(value))) if not value: raise orm.except_orm(_('Error :'), _("The '%s' is empty or 0. It should have a non-null value.") % field_name) if max_size and len(value) > max_size: @@ -175,90 +175,140 @@ class banking_export_sdd_wizard(orm.TransientModel): initiating_party_name = etree.SubElement(initiating_party_1_8, 'Nm') initiating_party_name.text = my_company_name - # B. Payment info - payment_info_2_0 = etree.SubElement(pain_root, 'PmtInf') - payment_info_identification_2_1 = etree.SubElement(payment_info_2_0, 'PmtInfId') - payment_info_identification_2_1.text = sepa_export.msg_identification - payment_method_2_2 = etree.SubElement(payment_info_2_0, 'PmtMtd') - payment_method_2_2.text = 'DD' - if pain_flavor in ['pain.008.001.02', 'pain.008.001.03', 'pain.008.001.04']: - # batch_booking is in "Payment Info" with pain.008.001.02/03 - batch_booking_2_3 = etree.SubElement(payment_info_2_0, 'BtchBookg') - batch_booking_2_3.text = str(sepa_export.batch_booking).lower() - # It may seem surprising, but the - # "SEPA Core Direct Debit Scheme Customer-to-bank Implementation guidelines" - # v6.0 says that control sum and nb_of_transactions should be present - # at both "group header" level and "payment info" level - if pain_flavor in ['pain.008.001.02', 'pain.008.001.03', 'pain.008.001.04']: - nb_of_transactions_2_4 = etree.SubElement(payment_info_2_0, 'NbOfTxs') - control_sum_2_5 = etree.SubElement(payment_info_2_0, 'CtrlSum') - payment_type_info_2_6 = etree.SubElement(payment_info_2_0, 'PmtTpInf') - service_level_2_8 = etree.SubElement(payment_type_info_2_6, 'SvcLvl') - service_level_code_2_9 = etree.SubElement(service_level_2_8, 'Cd') - service_level_code_2_9.text = 'SEPA' - local_instrument_2_11 = etree.SubElement(payment_type_info_2_6, 'LclInstrm') - local_instr_code_2_12 = etree.SubElement(local_instrument_2_11, 'Cd') - local_instr_code_2_12.text = 'CORE' - # TODO : 2.14 Sequence Type MANDATORY => I set it in section C (2.40) - # not B (2.14) so that we can have several different Sequence Types - # in the same XML file - # the Sample XML files show Seq type at C level - # BUT it may not be possible, - # 1. extract from CIC documentation : - # "Attention, les remises présentées devront être scindées par le créancier - # par type de séquence" - # In the guidelines, they only talk about B level - # If ‘Amendment Indicator’ is ‘true’, - # and ‘Original Debtor Agent’ is set to ‘SMNDA’, - # this message element must indicate ‘FRST - # 'FRST' = First ; 'OOFF' = One Off ; 'RCUR' : Recurring - # 'FNAL' = Final - requested_collec_date_2_18 = etree.SubElement(payment_info_2_0, 'ReqdColltnDt') - requested_collec_date_2_18.text = my_requested_collec_date - creditor_2_19 = etree.SubElement(payment_info_2_0, 'Cdtr') - creditor_name = etree.SubElement(creditor_2_19, 'Nm') - creditor_name.text = my_company_name - creditor_account_2_20 = etree.SubElement(payment_info_2_0, 'CdtrAcct') - creditor_account_id = etree.SubElement(creditor_account_2_20, 'Id') - creditor_account_iban = etree.SubElement(creditor_account_id, 'IBAN') - creditor_account_iban.text = self._validate_iban(cr, uid, - self._prepare_field(cr, uid, 'Company IBAN', - 'sepa_export.payment_order_ids[0].mode.bank_id.acc_number', - sepa_export=sepa_export, context=context), - context=context) - - creditor_agent_2_21 = etree.SubElement(payment_info_2_0, 'CdtrAgt') - creditor_agent_institution = etree.SubElement(creditor_agent_2_21, 'FinInstnId') - creditor_agent_bic = etree.SubElement(creditor_agent_institution, bic_xml_tag) - creditor_agent_bic.text = self._prepare_field(cr, uid, 'Company BIC', - 'sepa_export.payment_order_ids[0].mode.bank_id.bank.bic', - sepa_export=sepa_export, context=context) - - charge_bearer_2_24 = etree.SubElement(payment_info_2_0, 'ChrgBr') - charge_bearer_2_24.text = sepa_export.charge_bearer - - creditor_scheme_identification_2_27 = etree.SubElement(payment_info_2_0, 'CdtrSchmeId') - csi_id = etree.SubElement(creditor_scheme_identification_2_27, 'Id') - csi_orgid = csi_id = etree.SubElement(csi_id, 'OrgId') - csi_other = etree.SubElement(csi_orgid, 'Othr') - csi_other_id = etree.SubElement(csi_other, 'Id') - csi_other_id.text = self._prepare_field(cr, uid, - 'SEPA Creditor Identifier', - 'sepa_export.payment_order_ids[0].company_id.sepa_creditor_identifier', - sepa_export=sepa_export, context=context) - csi_scheme_name = etree.SubElement(csi_other, 'SchmeNm') - csi_scheme_name_proprietary = etree.SubElement(csi_scheme_name, 'Prtry') - csi_scheme_name_proprietary.text = 'SEPA' - - transactions_count = 0 + transactions_count_1_6 = 0 total_amount = 0.0 - amount_control_sum = 0.0 + amount_control_sum_1_7 = 0.0 + first_recur_lines = {} + # key = sequence type ; value = list of lines as objects # Iterate on payment orders for payment_order in sepa_export.payment_order_ids: total_amount = total_amount + payment_order.total # Iterate each payment lines for line in payment_order.line_ids: - transactions_count += 1 + transactions_count_1_6 += 1 + if not line.sdd_mandate_id: + raise orm.except_orm( + _('Error:'), + _("Missing SEPA Direct Debit mandate on the payment line with partner '%s' and Invoice ref '%s'.") + % (line.partner_id.name, + line.ml_inv_ref.number)) + if line.sdd_mandate_id.state != 'valid': + raise orm.except_orm( + _('Error:'), + _("The SEPA Direct Debit mandate with reference '%s' for partner '%s' has expired.") + % (line.sdd_mandate_id.unique_mandate_reference, + line.sdd_mandate_id.partner_id.name)) + + if not line.sdd_mandate_id.signature_date: + raise orm.except_orm( + _('Error:'), + _("Missing signature date on SEPA Direct Debit mandate with reference '%s' for partner '%s'.") + % (line.sdd_mandate_id.unique_mandate_reference, + line.sdd_mandate_id.partner_id.name)) + elif line.sdd_mandate_id.signature_date > datetime.today().strftime('%Y-%m-%d'): + raise orm.except_orm( + _('Error:'), + _("The signature date on SEPA Direct Debit mandate with reference '%s' for partner '%s' is '%s', which is in the future !") + % (line.sdd_mandate_id.unique_mandate_reference, + line.sdd_mandate_id.partner_id.name, + line.sdd_mandate_id.signature_date)) + + if line.sdd_mandate_id.type == 'oneoff': + if not line.sdd_mandate_id.last_debit_date: + if first_recur_lines.get('OOFF'): + first_recur_lines['OOFF'].append(line) + else: + first_recur_lines['OOFF'] = [line] + else: + raise orm.except_orm( + _('Error :'), + _("The mandate with reference '%s' for partner '%s' has type set to 'One-Off' and it has a last debit date set to '%s', so we can't use it.") + % (line.sdd_mandate_id.unique_mandate_reference, + line.sdd_mandate_id.partner_id.name, + line.sdd_mandate_id.last_debit_date)) + elif line.sdd_mandate_id.type == 'recurrent': + if line.sdd_mandate_id.last_debit_date: + if first_recur_lines.get('RCUR'): + first_recur_lines['RCUR'].append(line) + else: + first_recur_lines['RCUR'] = [line] + else: + if first_recur_lines.get('FRST'): + first_recur_lines['FRST'].append(line) + else: + first_recur_lines['FRST'] = [line] + + for sequence_type, lines in first_recur_lines.items(): + # B. Payment info + payment_info_2_0 = etree.SubElement(pain_root, 'PmtInf') + payment_info_identification_2_1 = etree.SubElement(payment_info_2_0, 'PmtInfId') + payment_info_identification_2_1.text = sepa_export.msg_identification + payment_method_2_2 = etree.SubElement(payment_info_2_0, 'PmtMtd') + payment_method_2_2.text = 'DD' + # batch_booking is in "Payment Info" with pain.008.001.02/03 + batch_booking_2_3 = etree.SubElement(payment_info_2_0, 'BtchBookg') + batch_booking_2_3.text = str(sepa_export.batch_booking).lower() + # The "SEPA Core Direct Debit Scheme Customer-to-bank + # Implementation guidelines" v6.0 says that control sum + # and nb_of_transactions should be present + # at both "group header" level and "payment info" level + nb_of_transactions_2_4 = etree.SubElement(payment_info_2_0, 'NbOfTxs') + control_sum_2_5 = etree.SubElement(payment_info_2_0, 'CtrlSum') + payment_type_info_2_6 = etree.SubElement(payment_info_2_0, 'PmtTpInf') + service_level_2_8 = etree.SubElement(payment_type_info_2_6, 'SvcLvl') + service_level_code_2_9 = etree.SubElement(service_level_2_8, 'Cd') + service_level_code_2_9.text = 'SEPA' + local_instrument_2_11 = etree.SubElement(payment_type_info_2_6, 'LclInstrm') + local_instr_code_2_12 = etree.SubElement(local_instrument_2_11, 'Cd') + local_instr_code_2_12.text = 'CORE' + # 2.14 Sequence Type MANDATORY + # this message element must indicate ‘FRST + # 'FRST' = First ; 'OOFF' = One Off ; 'RCUR' : Recurring + # 'FNAL' = Final + sequence_type_2_14 = etree.SubElement(payment_type_info_2_6, 'SeqTp') + sequence_type_2_14.text = sequence_type + + requested_collec_date_2_18 = etree.SubElement(payment_info_2_0, 'ReqdColltnDt') + requested_collec_date_2_18.text = my_requested_collec_date + creditor_2_19 = etree.SubElement(payment_info_2_0, 'Cdtr') + creditor_name = etree.SubElement(creditor_2_19, 'Nm') + creditor_name.text = my_company_name + creditor_account_2_20 = etree.SubElement(payment_info_2_0, 'CdtrAcct') + creditor_account_id = etree.SubElement(creditor_account_2_20, 'Id') + creditor_account_iban = etree.SubElement(creditor_account_id, 'IBAN') + creditor_account_iban.text = self._validate_iban(cr, uid, + self._prepare_field(cr, uid, 'Company IBAN', + 'sepa_export.payment_order_ids[0].mode.bank_id.acc_number', + sepa_export=sepa_export, context=context), + context=context) + + creditor_agent_2_21 = etree.SubElement(payment_info_2_0, 'CdtrAgt') + creditor_agent_institution = etree.SubElement(creditor_agent_2_21, 'FinInstnId') + creditor_agent_bic = etree.SubElement(creditor_agent_institution, bic_xml_tag) + creditor_agent_bic.text = self._prepare_field(cr, uid, 'Company BIC', + 'sepa_export.payment_order_ids[0].mode.bank_id.bank.bic', + sepa_export=sepa_export, context=context) + + charge_bearer_2_24 = etree.SubElement(payment_info_2_0, 'ChrgBr') + charge_bearer_2_24.text = sepa_export.charge_bearer + + creditor_scheme_identification_2_27 = etree.SubElement(payment_info_2_0, 'CdtrSchmeId') + csi_id = etree.SubElement(creditor_scheme_identification_2_27, 'Id') + csi_orgid = csi_id = etree.SubElement(csi_id, 'OrgId') + csi_other = etree.SubElement(csi_orgid, 'Othr') + csi_other_id = etree.SubElement(csi_other, 'Id') + csi_other_id.text = self._prepare_field(cr, uid, + 'SEPA Creditor Identifier', + 'sepa_export.payment_order_ids[0].company_id.sepa_creditor_identifier', + sepa_export=sepa_export, context=context) + csi_scheme_name = etree.SubElement(csi_other, 'SchmeNm') + csi_scheme_name_proprietary = etree.SubElement(csi_scheme_name, 'Prtry') + csi_scheme_name_proprietary.text = 'SEPA' + + transactions_count_2_4 = 0 + amount_control_sum_2_5 = 0.0 + for line in lines: + transactions_count_2_4 += 1 # C. Direct Debit Transaction Info dd_transaction_info_2_28 = etree.SubElement(payment_info_2_0, 'DrctDbtTxInf') payment_identification_2_29 = etree.SubElement(dd_transaction_info_2_28, 'PmtId') @@ -268,20 +318,26 @@ class banking_export_sdd_wizard(orm.TransientModel): 'End to End Identification', 'line.communication', 35, line=line, context=context) payment_type_2_32 = etree.SubElement(dd_transaction_info_2_28, 'PmtTpInf') - # Sequence Type : do we have to set it at Payment Info level ? - #sequence_type_2_40 = etree.SubElement(payment_type_2_32, 'SeqTp') - #sequence_type_2_40.text = 'FRST' # TODO currency_name = self._prepare_field(cr, uid, 'Currency Code', 'line.currency.name', 3, line=line, context=context) instructed_amount_2_44 = etree.SubElement(dd_transaction_info_2_28, 'InstdAmt', Ccy=currency_name) instructed_amount_2_44.text = '%.2f' % line.amount_currency - amount_control_sum += line.amount_currency + amount_control_sum_1_7 += line.amount_currency + amount_control_sum_2_5 += line.amount_currency dd_transaction_2_46 = etree.SubElement(dd_transaction_info_2_28, 'DrctDbtTx') mandate_related_info_2_47 = etree.SubElement(dd_transaction_2_46, 'MndtRltdInf') mandate_identification_2_48 = etree.SubElement(mandate_related_info_2_47, 'MndtId') - mandate_identification_2_48.text = 'RUM1242' # TODO - mandate_signature_date_2_49 = etree.SubElement(mandate_related_info_2_47, 'DtOfSgntr') - mandate_signature_date_2_49.text = '2013-02-20' # TODO + mandate_identification_2_48.text = self._prepare_field( + cr, uid, 'Unique Mandate Reference', + 'line.sdd_mandate_id.unique_mandate_reference', + 35, line=line, context=context) + mandate_signature_date_2_49 = etree.SubElement( + mandate_related_info_2_47, 'DtOfSgntr') + mandate_signature_date_2_49.text = self._prepare_field( + cr, uid, 'Mandate Signature Date', + 'line.sdd_mandate_id.signature_date', 10, + line=line, context=context) + # TODO look at 2.50 "Amendment Indicator debtor_agent_2_70 = etree.SubElement(dd_transaction_info_2_28, 'DbtrAgt') debtor_agent_institution = etree.SubElement(debtor_agent_2_70, 'FinInstnId') @@ -308,10 +364,10 @@ class banking_export_sdd_wizard(orm.TransientModel): remittance_info_unstructured_2_89.text = self._prepare_field(cr, uid, 'Remittance Information', 'line.communication', 140, line=line, context=context) - - if pain_flavor in ['pain.008.001.02', 'pain.008.001.03', 'pain.008.001.04']: - nb_of_transactions_1_6.text = nb_of_transactions_2_4.text = str(transactions_count) - control_sum_1_7.text = control_sum_2_5.text = '%.2f' % amount_control_sum + nb_of_transactions_2_4.text = str(transactions_count_2_4) + control_sum_2_5.text = '%.2f' % amount_control_sum_2_5 + nb_of_transactions_1_6.text = str(transactions_count_1_6) + control_sum_1_7.text = '%.2f' % amount_control_sum_1_7 xml_string = etree.tostring(root, pretty_print=True, encoding='UTF-8', xml_declaration=True) @@ -345,7 +401,7 @@ class banking_export_sdd_wizard(orm.TransientModel): 'charge_bearer': sepa_export.charge_bearer, 'requested_collec_date': sepa_export.requested_collec_date, 'total_amount': total_amount, - 'nb_transactions': transactions_count, + 'nb_transactions': transactions_count_1_6, 'file': base64.encodestring(xml_string), 'payment_order_ids': [ (6, 0, [x.id for x in sepa_export.payment_order_ids]) @@ -374,13 +430,15 @@ class banking_export_sdd_wizard(orm.TransientModel): Cancel the SEPA Direct Debit file: just drop the file ''' sepa_export = self.browse(cr, uid, ids[0], context=context) - self.pool.get('banking.export.sdd').unlink(cr, uid, sepa_export.file_id.id, context=context) + self.pool.get('banking.export.sdd').unlink( + cr, uid, sepa_export.file_id.id, context=context) return {'type': 'ir.actions.act_window_close'} def save_sepa(self, cr, uid, ids, context=None): ''' Save the SEPA Direct Debit file: mark all payments in the file as 'sent'. + Write 'last debit date' on mandate and set oneoff mandate to expired ''' sepa_export = self.browse(cr, uid, ids[0], context=context) sepa_file = self.pool.get('banking.export.sdd').write(cr, uid, @@ -388,4 +446,14 @@ class banking_export_sdd_wizard(orm.TransientModel): wf_service = netsvc.LocalService('workflow') for order in sepa_export.payment_order_ids: wf_service.trg_validate(uid, 'payment.order', order.id, 'sent', cr) + mandate_ids = [line.sdd_mandate_id.id for line in order.line_ids] + self.pool['sdd.mandate'].write( + cr, uid, mandate_ids, { + 'last_debit_date': datetime.today().strftime('%Y-%m-%d') + }, + context=context) + oneoff_mandate_ids = [line.sdd_mandate_id.id for line in order.line_ids if line.sdd_mandate_id.type == 'oneoff'] + self.pool['sdd.mandate'].write( + cr, uid, oneoff_mandate_ids, {'state': 'expired'}, + context=context) return {'type': 'ir.actions.act_window_close'}