From a6bc2e12b332009c794e6ed8c1ecf675a60ddb3e Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Sat, 30 Apr 2016 01:46:34 +0200 Subject: [PATCH] Start to port bank-payment to v9 (with a lot of improvements) during the Sorrento Code sprint 2016 Improvements include: - full re-organisation of modules and big re-organisation of the code - simplification of the code related to the fact that support for direct debit is now in t he base module, not added by an optional module account_direct_debit (module was removed) - new design of the wizard to select move lines to pay - support for non-SEPA file transfer- - support for German direct debit SEPA files (fixes bug #129) - remove workflow of payment.order This port to v9 is not finished... there is still a lot of work: - finish the code of account_payment_order/wizard/account_payment_line_create.py - port account_banking_payment_transfer and integrate it inside account_payment_order - fix bugs - clean-up code, remove dead code - test in several complex scenarios --- account_banking_sepa_direct_debit/__init__.py | 3 - .../__openerp__.py | 13 +- .../data/account_payment_method.xml | 15 + .../data/mandate_expire_cron.xml | 2 +- .../data/pain.008.003.02.xsd | 614 ++++++++++++++++++ .../data/payment_type_sdd.xml | 35 - .../demo/sepa_direct_debit_demo.xml | 14 +- .../models/__init__.py | 4 +- .../models/account_banking_mandate.py | 70 +- .../models/account_payment_method.py | 27 + ...ayment_mode.py => account_payment_mode.py} | 19 +- .../models/account_payment_order.py | 298 +++++++++ .../models/bank_payment_line.py | 2 +- .../models/common.py | 2 +- .../models/res_company.py | 11 +- .../original_mandate_required_security.xml | 17 - .../views/account_banking_mandate_view.xml | 74 +-- .../views/account_payment_mode.xml | 23 + .../views/payment_mode_view.xml | 24 - .../views/res_company_view.xml | 5 +- .../wizard/__init__.py | 23 - .../wizard/export_sdd.py | 394 ----------- .../wizard/export_sdd_view.xml | 36 - 23 files changed, 1022 insertions(+), 703 deletions(-) create mode 100644 account_banking_sepa_direct_debit/data/account_payment_method.xml create mode 100644 account_banking_sepa_direct_debit/data/pain.008.003.02.xsd delete mode 100644 account_banking_sepa_direct_debit/data/payment_type_sdd.xml create mode 100644 account_banking_sepa_direct_debit/models/account_payment_method.py rename account_banking_sepa_direct_debit/models/{payment_mode.py => account_payment_mode.py} (70%) create mode 100644 account_banking_sepa_direct_debit/models/account_payment_order.py delete mode 100644 account_banking_sepa_direct_debit/security/original_mandate_required_security.xml create mode 100644 account_banking_sepa_direct_debit/views/account_payment_mode.xml delete mode 100644 account_banking_sepa_direct_debit/views/payment_mode_view.xml delete mode 100644 account_banking_sepa_direct_debit/wizard/__init__.py delete mode 100644 account_banking_sepa_direct_debit/wizard/export_sdd.py delete mode 100644 account_banking_sepa_direct_debit/wizard/export_sdd_view.xml diff --git a/account_banking_sepa_direct_debit/__init__.py b/account_banking_sepa_direct_debit/__init__.py index b4a69d367..cde864bae 100644 --- a/account_banking_sepa_direct_debit/__init__.py +++ b/account_banking_sepa_direct_debit/__init__.py @@ -1,6 +1,3 @@ # -*- coding: utf-8 -*- -# © 2013 Akretion (www.akretion.com) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import models -from . import wizard diff --git a/account_banking_sepa_direct_debit/__openerp__.py b/account_banking_sepa_direct_debit/__openerp__.py index 7f7de6752..4898c90d5 100644 --- a/account_banking_sepa_direct_debit/__openerp__.py +++ b/account_banking_sepa_direct_debit/__openerp__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# © 2013-2015 Akretion (www.akretion.com) +# © 2013-2016 Akretion (www.akretion.com) # © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza # © 2016 Antiun Ingenieria S.L. - Antonio Espinosa # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). @@ -7,7 +7,7 @@ { 'name': 'Account Banking SEPA Direct Debit', 'summary': 'Create SEPA files for Direct Debit', - 'version': '8.0.0.5.0', + 'version': '9.0.1.0.0', 'license': 'AGPL-3', 'author': "Akretion, " "Serv. Tecnol. Avanzados - Pedro M. Baeza, " @@ -16,22 +16,19 @@ 'website': 'https://github.com/OCA/bank-payment', 'category': 'Banking addons', 'depends': [ - 'account_direct_debit', 'account_banking_pain_base', 'account_banking_mandate', ], 'data': [ 'views/account_banking_mandate_view.xml', 'views/res_company_view.xml', - 'views/payment_mode_view.xml', - 'wizard/export_sdd_view.xml', + 'views/account_payment_mode.xml', 'data/mandate_expire_cron.xml', - 'data/payment_type_sdd.xml', + 'data/account_payment_method.xml', 'data/report_paperformat.xml', 'reports/sepa_direct_debit_mandate.xml', 'views/report_sepa_direct_debit_mandate.xml', - 'security/original_mandate_required_security.xml', ], 'demo': ['demo/sepa_direct_debit_demo.xml'], - 'installable': False, + 'installable': True, } diff --git a/account_banking_sepa_direct_debit/data/account_payment_method.xml b/account_banking_sepa_direct_debit/data/account_payment_method.xml new file mode 100644 index 000000000..d2ef66bfd --- /dev/null +++ b/account_banking_sepa_direct_debit/data/account_payment_method.xml @@ -0,0 +1,15 @@ + + + + + + + SEPA Direct Debit for customers + sepa_direct_debit + inbound + pain.008.001.02 + + + + + diff --git a/account_banking_sepa_direct_debit/data/mandate_expire_cron.xml b/account_banking_sepa_direct_debit/data/mandate_expire_cron.xml index 48fe6fc63..fc411dacf 100644 --- a/account_banking_sepa_direct_debit/data/mandate_expire_cron.xml +++ b/account_banking_sepa_direct_debit/data/mandate_expire_cron.xml @@ -1,7 +1,7 @@ diff --git a/account_banking_sepa_direct_debit/data/pain.008.003.02.xsd b/account_banking_sepa_direct_debit/data/pain.008.003.02.xsd new file mode 100644 index 000000000..ed2dd930b --- /dev/null +++ b/account_banking_sepa_direct_debit/data/pain.008.003.02.xsd @@ -0,0 +1,614 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Mandatory if changes occur in ‘Mandate Identification’, otherwise not to be used. + + + + + Mandatory if changes occur in 'Creditor Scheme Identification', otherwise not to be used. + + + + + To be used only for changes of accounts within the same bank. + + + + + To use 'Identification’ under 'Other' under 'Financial Institution Identifier with code ‘SMNDA’ to indicate same mandate with new Debtor Agent. To be used with the ‘FRST’ indicator in the ‘Sequence Type’. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + If a Creditor Reference contains a check digit, the receiving bank is not required to validate this. +If the receiving bank validates the check digit and if this validation fails, the bank may continue its processing and send the transaction to the next party in the chain. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + It is recommended that all transactions within the same ‘Payment Information’ block have the same ‘Creditor Scheme Identification’. +This data element must be present at either ‘Payment Information’ or ‘Direct Debit +Transaction’ level. + + + + + + + + + + + It is recommended that this element be specified at ‘Payment Information’ level. + + + + + + This data element may be present either at ‘Payment Information’ or at ‘Direct Debit Transaction Information’ level. + + + + + + + + Mandatory if provided by the debtor in the mandate. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Mandatory if 'Amendment Indicator' is 'TRUE' +The reason code from the Rulebook is indicated using one of the following message subelements. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Either ‘BIC or BEI’ or one +occurrence of ‘Other’ is allowed. + + + + + Either ‘Date and Place of Birth’ or one occurrence of ‘Other’ is allowed + + + + + + + + + + Private Identification is used to identify either an organisation or a private +person. + + + + + + + + + ‘Name’ is limited to 70 characters in length. + + + + + + + + + + ‘Name’ is limited to 70 characters in length. + + + + + + + + + + + + + + + + If present the new’ Name’ must be specified under ‘Creditor’. ‘Name’ is limited to 70 characters in length. + + + + + + + + + + ‘Name’ is limited to 70 characters in length. + + + + + + + + + + + + + + + + + + If present and contains ‘true’, batch booking is requested. If present and contains ‘false’, booking per transaction is requested. If element is not present, pre-agreed customer-to-bank conditions apply. + + + + + + + + + + + + This data element may be present either at ‘Payment Information’ or at ‘Direct Debit Transaction Information’ level. + + + + + It is recommended that this element be specified at ‘Payment Information’ level. + + + + + It is recommended that all transactions within the same ‘Payment Information’ block have the same ‘Creditor Scheme Identification’. +This data element must be present at either ‘Payment Information’ or ‘Direct Debit +Transaction’ level. + + + + + + + + + + + + + + + + Only ‘B2B’, 'CORE' or 'COR1' is allowed. The mixing of different Local Instrument values is not allowed in the same message. + + + + + If 'Amendment Indicator' is 'true' and 'Original Debtor Agent' is set to 'SMNDA' this message element must indicate 'FRST' + + + + + Depending on the agreement between the Creditor and the Creditor Bank, ‘Category Purpose’ may be forwarded to the Debtor Bank. + + + + + + + + + + + + + + + + + Only one occurrence of ‘Other’ is allowed, and no other sub-elements are allowed. +Identification must be used with an identifier described in General Message Element Specifications, Chapter 1.5.2 of the Implementation Guide. +Scheme Name’ under ‘Other’ must specify ‘SEPA’ under ‘Proprietary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Only codes from the ISO 20022 ExternalPurposeCode list are allowed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + When present, the receiving bank is not obliged to validate the reference information. + + + + + + + + + + + + + + + + + + diff --git a/account_banking_sepa_direct_debit/data/payment_type_sdd.xml b/account_banking_sepa_direct_debit/data/payment_type_sdd.xml deleted file mode 100644 index a17b3b21c..000000000 --- a/account_banking_sepa_direct_debit/data/payment_type_sdd.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - SEPA Direct Debit v02 (recommended) - pain.008.001.02 - - - debit - - - - SEPA Direct Debit v03 - pain.008.001.03 - - - debit - - - - SEPA Direct Debit v04 - pain.008.001.04 - - - debit - - - - - diff --git a/account_banking_sepa_direct_debit/demo/sepa_direct_debit_demo.xml b/account_banking_sepa_direct_debit/demo/sepa_direct_debit_demo.xml index bcbeb7fb8..8c312cde5 100644 --- a/account_banking_sepa_direct_debit/demo/sepa_direct_debit_demo.xml +++ b/account_banking_sepa_direct_debit/demo/sepa_direct_debit_demo.xml @@ -3,13 +3,11 @@ - - SEPA Direct Debit La Banque Postale - - + + SEPA Direct Debit of customers - - + variable + @@ -18,7 +16,7 @@ - + sepa recurrent first @@ -27,7 +25,7 @@ - + sepa recurrent first diff --git a/account_banking_sepa_direct_debit/models/__init__.py b/account_banking_sepa_direct_debit/models/__init__.py index 93fb91cc8..5c9be9f71 100644 --- a/account_banking_sepa_direct_debit/models/__init__.py +++ b/account_banking_sepa_direct_debit/models/__init__.py @@ -3,4 +3,6 @@ from . import res_company from . import account_banking_mandate from . import bank_payment_line -from . import payment_mode +from . import account_payment_mode +from . import account_payment_method +from . import account_payment_order diff --git a/account_banking_sepa_direct_debit/models/account_banking_mandate.py b/account_banking_sepa_direct_debit/models/account_banking_mandate.py index ca2381204..2c065b4ef 100644 --- a/account_banking_sepa_direct_debit/models/account_banking_mandate.py +++ b/account_banking_sepa_direct_debit/models/account_banking_mandate.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# © 2013 Akretion - Alexis de Lattre +# © 2013-2016 Akretion - Alexis de Lattre # © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). @@ -16,29 +16,13 @@ logger = logging.getLogger(__name__) class AccountBankingMandate(models.Model): """SEPA Direct Debit Mandate""" _inherit = 'account.banking.mandate' - _track = { - 'recurrent_sequence_type': { - 'account_banking_sepa_direct_debit.recurrent_sequence_type_first': - lambda self, cr, uid, obj, ctx=None: - obj['recurrent_sequence_type'] == 'first', - 'account_banking_sepa_direct_debit.' - 'recurrent_sequence_type_recurring': - lambda self, cr, uid, obj, ctx=None: - obj['recurrent_sequence_type'] == 'recurring', - 'account_banking_sepa_direct_debit.recurrent_sequence_type_final': - lambda self, cr, uid, obj, ctx=None: - obj['recurrent_sequence_type'] == 'final', - } - } format = fields.Selection( - selection_add=[('sepa', _('Sepa Mandate'))], - default='sepa', - ) + selection_add=[('sepa', 'Sepa Mandate')], default='sepa') type = fields.Selection([('recurrent', 'Recurrent'), ('oneoff', 'One-Off')], string='Type of Mandate', - track_visibility='always') + track_visibility='onchange') recurrent_sequence_type = fields.Selection( [('first', 'First'), ('recurring', 'Recurring'), @@ -46,24 +30,10 @@ class AccountBankingMandate(models.Model): string='Sequence Type for Next Debit', track_visibility='onchange', help="This field is only used for Recurrent mandates, not for " "One-Off mandates.", default="first") - sepa_migrated = fields.Boolean( - string='Migrated to SEPA', track_visibility='onchange', - help="If this field is not active, the mandate section of the next " - "direct debit file that include this mandate will contain the " - "'Original Mandate Identification' and the 'Original Creditor " - "Scheme Identification'. This is required in a few countries " - "(Belgium for instance), but not in all countries. If this is " - "not required in your country, you should keep this field always " - "active.", default=True) - original_mandate_identification = fields.Char( - string='Original Mandate Identification', track_visibility='onchange', - size=35, - help="When the field 'Migrated to SEPA' is not active, this field " - "will be used as the Original Mandate Identification in the " - "Direct Debit file.") - scheme = fields.Selection([('CORE', 'Basic (CORE)'), - ('B2B', 'Enterprise (B2B)')], - string='Scheme', default="CORE") + scheme = fields.Selection([ + ('CORE', 'Basic (CORE)'), + ('B2B', 'Enterprise (B2B)')], + string='Scheme', default="CORE", track_visibility='onchange') unique_mandate_reference = fields.Char(size=35) # cf ISO 20022 @api.multi @@ -76,30 +46,6 @@ class AccountBankingMandate(models.Model): _("The recurrent mandate '%s' must have a sequence type.") % mandate.unique_mandate_reference) - @api.multi - @api.constrains('type', 'recurrent_sequence_type', 'sepa_migrated') - def _check_migrated_to_sepa(self): - for mandate in self: - if (mandate.type == 'recurrent' and not mandate.sepa_migrated and - mandate.recurrent_sequence_type != 'first'): - raise exceptions.Warning( - _("The recurrent mandate '%s' which is not marked as " - "'Migrated to SEPA' must have its recurrent sequence " - "type set to 'First'.") - % mandate.unique_mandate_reference) - - @api.multi - @api.constrains('type', 'original_mandate_identification', 'sepa_migrated') - def _check_original_mandate_identification(self): - for mandate in self: - if (mandate.type == 'recurrent' and not mandate.sepa_migrated and - not mandate.original_mandate_identification): - raise exceptions.Warning( - _("You must set the 'Original Mandate Identification' on " - "the recurrent mandate '%s' which is not marked as " - "'Migrated to SEPA'.") - % mandate.unique_mandate_reference) - @api.multi @api.onchange('partner_bank_id') def mandate_partner_bank_change(self): @@ -137,5 +83,5 @@ class AccountBankingMandate(models.Model): 'The following SDD Mandate IDs has been set to expired: %s' % expired_mandates.ids) else: - logger.info('0 SDD Mandates must be set to Expired') + logger.info('0 SDD Mandates had to be set to Expired') return True diff --git a/account_banking_sepa_direct_debit/models/account_payment_method.py b/account_banking_sepa_direct_debit/models/account_payment_method.py new file mode 100644 index 000000000..5a9327b19 --- /dev/null +++ b/account_banking_sepa_direct_debit/models/account_payment_method.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# © 2016 Akretion (Alexis de Lattre ) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, fields, api + + +class AccountPaymentMethod(models.Model): + _inherit = 'account.payment.method' + + pain_version = fields.Selection(selection_add=[ + ('pain.008.001.02', 'pain.008.001.02 (recommended)'), + ('pain.008.001.03', 'pain.008.001.03'), + ('pain.008.001.04', 'pain.008.001.04'), + ('pain.008.003.02', 'pain.008.003.02 (used in Germany)'), + ]) + + @api.multi + def get_xsd_file_path(self): + self.ensure_one() + if self.pain_version in [ + 'pain.008.001.02', 'pain.008.001.03', 'pain.008.001.04', + 'pain.008.003.02']: + path = 'account_banking_sepa_direct_debit/data/%s.xsd'\ + % self.pain_version + return path + return super(AccountPaymentMethod, self).get_xsd_file_path() diff --git a/account_banking_sepa_direct_debit/models/payment_mode.py b/account_banking_sepa_direct_debit/models/account_payment_mode.py similarity index 70% rename from account_banking_sepa_direct_debit/models/payment_mode.py rename to account_banking_sepa_direct_debit/models/account_payment_mode.py index f3c1ac1b6..0650736d0 100644 --- a/account_banking_sepa_direct_debit/models/payment_mode.py +++ b/account_banking_sepa_direct_debit/models/account_payment_mode.py @@ -2,12 +2,13 @@ # © 2016 Antiun Ingenieria S.L. - Antonio Espinosa # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp import models, fields, api, exceptions, _ +from openerp import models, fields, api, _ from .common import is_sepa_creditor_identifier_valid +from openerp.exceptions import ValidationError -class PaymentMode(models.Model): - _inherit = 'payment.mode' +class AccountPaymentMode(models.Model): + _inherit = 'account.payment.mode' sepa_creditor_identifier = fields.Char( string='SEPA Creditor Identifier', size=35, @@ -19,13 +20,9 @@ class PaymentMode(models.Model): "- a 2-digits checkum\n" "- a 3-letters business code\n" "- a country-specific identifier") - original_creditor_identifier = fields.Char( - string='Original Creditor Identifier', size=70, - help="If not defined, Original Creditor Identifier from company " - "will be used.") def _sepa_type_get(self): - res = super(PaymentMode, self)._sepa_type_get() + res = super(AccountPaymentMode, self)._sepa_type_get() if not res: if self.type.code and self.type.code.startswith('pain.008'): res = 'sepa_direct_debit' @@ -38,6 +35,6 @@ class PaymentMode(models.Model): if payment_mode.sepa_creditor_identifier: if not is_sepa_creditor_identifier_valid( payment_mode.sepa_creditor_identifier): - raise exceptions.Warning( - _('Error'), - _("Invalid SEPA Creditor Identifier.")) + raise ValidationError( + _("The SEPA Creditor Identifier '%s' is invalid.") + % payment_mode.sepa_creditor_identifier) diff --git a/account_banking_sepa_direct_debit/models/account_payment_order.py b/account_banking_sepa_direct_debit/models/account_payment_order.py new file mode 100644 index 000000000..fdea2b052 --- /dev/null +++ b/account_banking_sepa_direct_debit/models/account_payment_order.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- +# © 2016 Akretion (Alexis de Lattre ) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import models, fields, api, _ +from openerp.exceptions import UserError +from lxml import etree + + +class AccountPaymentOrder(models.Model): + _inherit = 'account.payment.order' + + def _get_previous_bank(self, payline): + previous_bank = False + older_lines = self.env['account.payment.line'].search([ + ('mandate_id', '=', payline.mandate_id.id), + ('partner_bank_id', '!=', payline.partner_bank_id.id)]) + if older_lines: + previous_date = False + previous_payline = False + for older_line in older_lines: + if hasattr(older_line.order_id, 'date_sent'): + older_line_date = older_line.order_id.date_sent + else: + older_line_date = older_line.order_id.date_done + if (older_line_date and + older_line_date > previous_date): + previous_date = older_line_date + previous_payline = older_line + if previous_payline: + previous_bank = previous_payline.partner_bank_id + return previous_bank + + @api.multi + def generate_payment_file(self): + """Creates the SEPA Direct Debit file. That's the important code !""" + self.ensure_one() + if ( + self.payment_mode_id.payment_method_id.code != + 'sepa_direct_debit'): + return super(AccountPaymentOrder, self).generate_payment_file() + pain_flavor = self.payment_mode_id.payment_method_id.pain_version + if pain_flavor == 'pain.008.001.02': + bic_xml_tag = 'BIC' + name_maxsize = 70 + root_xml_tag = 'CstmrDrctDbtInitn' + elif pain_flavor == 'pain.008.003.02': + bic_xml_tag = 'BIC' + name_maxsize = 70 + root_xml_tag = 'CstmrDrctDbtInitn' + elif pain_flavor == 'pain.008.001.03': + bic_xml_tag = 'BICFI' + name_maxsize = 140 + root_xml_tag = 'CstmrDrctDbtInitn' + elif pain_flavor == 'pain.008.001.04': + bic_xml_tag = 'BICFI' + name_maxsize = 140 + root_xml_tag = 'CstmrDrctDbtInitn' + else: + raise UserError( + _("Payment Type Code '%s' is not supported. The only " + "Payment Type Code supported for SEPA Direct Debit are " + "'pain.008.001.02', 'pain.008.001.03' and " + "'pain.008.001.04'.") % pain_flavor) + xsd_file = self.payment_mode_id.payment_method_id.get_xsd_file_path() + gen_args = { + 'bic_xml_tag': bic_xml_tag, + 'name_maxsize': name_maxsize, + 'convert_to_ascii': self.payment_mode_id.convert_to_ascii, + 'payment_method': 'DD', + 'file_prefix': 'sdd_', + 'pain_flavor': pain_flavor, + 'pain_xsd_file': xsd_file, + } + pain_ns = { + 'xsi': 'http://www.w3.org/2001/XMLSchema-instance', + None: 'urn:iso:std:iso:20022:tech:xsd:%s' % pain_flavor, + } + xml_root = etree.Element('Document', nsmap=pain_ns) + pain_root = etree.SubElement(xml_root, root_xml_tag) + # A. Group header + group_header_1_0, nb_of_transactions_1_6, control_sum_1_7 = \ + self.generate_group_header_block(pain_root, gen_args) + transactions_count_1_6 = 0 + amount_control_sum_1_7 = 0.0 + lines_per_group = {} + # key = (requested_date, priority, sequence type) + # value = list of lines as objects + for line in self.bank_line_ids: + transactions_count_1_6 += 1 + priority = line.priority + # The field line.date is the requested payment date + # taking into account the 'date_prefered' setting + # cf account_banking_payment_export/models/account_payment.py + # in the inherit of action_open() + if not line.mandate_id: + raise UserError( + _("Missing SEPA Direct Debit mandate on the " + "bank payment line with partner '%s' " + "(reference '%s').") + % (line.partner_id.name, line.name)) + scheme = line.mandate_id.scheme + if line.mandate_id.state != 'valid': + raise Warning( + _("The SEPA Direct Debit mandate with reference '%s' " + "for partner '%s' has expired.") + % (line.mandate_id.unique_mandate_reference, + line.mandate_id.partner_id.name)) + if line.mandate_id.type == 'oneoff': + seq_type = 'OOFF' + if line.mandate_id.last_debit_date: + raise Warning( + _("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.mandate_id.unique_mandate_reference, + line.mandate_id.partner_id.name, + line.mandate_id.last_debit_date)) + elif line.mandate_id.type == 'recurrent': + seq_type_map = { + 'recurring': 'RCUR', + 'first': 'FRST', + 'final': 'FNAL', + } + seq_type_label = \ + line.mandate_id.recurrent_sequence_type + assert seq_type_label is not False + seq_type = seq_type_map[seq_type_label] + key = (line.date, priority, seq_type, scheme) + if key in lines_per_group: + lines_per_group[key].append(line) + else: + lines_per_group[key] = [line] + + for (requested_date, priority, sequence_type, scheme), lines in \ + lines_per_group.items(): + # B. Payment info + payment_info_2_0, nb_of_transactions_2_4, control_sum_2_5 = \ + self.generate_start_payment_info_block( + pain_root, + "self.name + '-' + " + "sequence_type + '-' + requested_date.replace('-', '') " + "+ '-' + priority", + priority, scheme, sequence_type, requested_date, { + 'self': self, + 'sequence_type': sequence_type, + 'priority': priority, + 'requested_date': requested_date, + }, gen_args) + + self.generate_party_block( + payment_info_2_0, 'Cdtr', 'B', + 'self.company_partner_bank_id.partner_id.name', + 'self.company_partner_bank_id.sanitized_acc_number', + 'self.company_partner_bank_id.bank_bic', + {'self': self}, gen_args) + charge_bearer_2_24 = etree.SubElement(payment_info_2_0, 'ChrgBr') + if self.sepa: + charge_bearer = 'SLEV' + else: + charge_bearer = self.charge_bearer + charge_bearer_2_24.text = charge_bearer + creditor_scheme_identification_2_27 = etree.SubElement( + payment_info_2_0, 'CdtrSchmeId') + self.generate_creditor_scheme_identification( + creditor_scheme_identification_2_27, + 'self.payment_mode_id.sepa_creditor_identifier or ' + 'self.company_id.sepa_creditor_identifier', + 'SEPA Creditor Identifier', {'self': self}, 'SEPA', gen_args) + 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') + end2end_identification_2_31 = etree.SubElement( + payment_identification_2_29, 'EndToEndId') + end2end_identification_2_31.text = self._prepare_field( + 'End to End Identification', 'line.name', + {'line': line}, 35, gen_args=gen_args) + currency_name = self._prepare_field( + 'Currency Code', 'line.currency_id.name', + {'line': line}, 3, gen_args=gen_args) + 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_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 = self._prepare_field( + 'Unique Mandate Reference', + 'line.mandate_id.unique_mandate_reference', + {'line': line}, 35, gen_args=gen_args) + mandate_signature_date_2_49 = etree.SubElement( + mandate_related_info_2_47, 'DtOfSgntr') + mandate_signature_date_2_49.text = self._prepare_field( + 'Mandate Signature Date', + 'line.mandate_id.signature_date', + {'line': line}, 10, gen_args=gen_args) + if sequence_type == 'FRST' and line.mandate_id.last_debit_date: + previous_bank = self._get_previous_bank(line) + if previous_bank: + amendment_indicator_2_50 = etree.SubElement( + mandate_related_info_2_47, 'AmdmntInd') + amendment_indicator_2_50.text = 'true' + amendment_info_details_2_51 = etree.SubElement( + mandate_related_info_2_47, 'AmdmntInfDtls') + if ( + previous_bank.bank_bic == + line.partner_bank_id.bank_bic): + ori_debtor_account_2_57 = etree.SubElement( + amendment_info_details_2_51, 'OrgnlDbtrAcct') + ori_debtor_account_id = etree.SubElement( + ori_debtor_account_2_57, 'Id') + ori_debtor_account_iban = etree.SubElement( + ori_debtor_account_id, 'IBAN') + ori_debtor_account_iban.text = self._validate_iban( + self._prepare_field( + 'Original Debtor Account', + 'previous_bank.sanitized_acc_number', + {'previous_bank': previous_bank}, + gen_args=gen_args)) + else: + ori_debtor_agent_2_58 = etree.SubElement( + amendment_info_details_2_51, 'OrgnlDbtrAgt') + ori_debtor_agent_institution = etree.SubElement( + ori_debtor_agent_2_58, 'FinInstnId') + ori_debtor_agent_bic = etree.SubElement( + ori_debtor_agent_institution, bic_xml_tag) + ori_debtor_agent_bic.text = self._prepare_field( + 'Original Debtor Agent', + 'previous_bank.bank_bic', + {'previous_bank': previous_bank}, + gen_args=gen_args) + ori_debtor_agent_other = etree.SubElement( + ori_debtor_agent_institution, 'Othr') + ori_debtor_agent_other_id = etree.SubElement( + ori_debtor_agent_other, 'Id') + ori_debtor_agent_other_id.text = 'SMNDA' + # SMNDA = Same Mandate New Debtor Agent + + self.generate_party_block( + dd_transaction_info_2_28, 'Dbtr', 'C', + 'line.partner_id.name', + 'line.partner_bank_id.sanitized_acc_number', + 'line.partner_bank_id.bank_bic', + {'line': line}, gen_args) + + self.generate_remittance_info_block( + dd_transaction_info_2_28, line, gen_args) + + nb_of_transactions_2_4.text = unicode(transactions_count_2_4) + control_sum_2_5.text = '%.2f' % amount_control_sum_2_5 + nb_of_transactions_1_6.text = unicode(transactions_count_1_6) + control_sum_1_7.text = '%.2f' % amount_control_sum_1_7 + + return self.finalize_sepa_file_creation( + xml_root, gen_args) + + @api.multi + def finalize_sepa_file_creation(self, xml_root, gen_args): + """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. + """ + abmo = self.env['account.banking.mandate'] + to_expire_mandates = abmo.browse([]) + first_mandates = abmo.browse([]) + all_mandates = abmo.browse([]) + for bline in self.bank_line_ids: + if bline.mandate_id in all_mandates: + continue + all_mandates += bline.mandate_id + if bline.mandate_id.type == 'oneoff': + to_expire_mandates += bline.mandate_id + elif bline.mandate_id.type == 'recurrent': + seq_type = bline.mandate_id.recurrent_sequence_type + if seq_type == 'final': + to_expire_mandates += bline.mandate_id + elif seq_type == 'first': + first_mandates += bline.mandate_id + all_mandates.write( + {'last_debit_date': fields.Date.context_today(self)}) + to_expire_mandates.write({'state': 'expired'}) + first_mandates.write({ + 'recurrent_sequence_type': 'recurring', + }) + return super(AccountPaymentOrder, self).finalize_sepa_file_creation( + xml_root, gen_args) diff --git a/account_banking_sepa_direct_debit/models/bank_payment_line.py b/account_banking_sepa_direct_debit/models/bank_payment_line.py index e28207ebc..5eb0b26b7 100644 --- a/account_banking_sepa_direct_debit/models/bank_payment_line.py +++ b/account_banking_sepa_direct_debit/models/bank_payment_line.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# © 2015 Akretion - Alexis de Lattre +# © 2015-2016 Akretion - Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from openerp import models, api diff --git a/account_banking_sepa_direct_debit/models/common.py b/account_banking_sepa_direct_debit/models/common.py index dd507a4db..83f7bfa03 100644 --- a/account_banking_sepa_direct_debit/models/common.py +++ b/account_banking_sepa_direct_debit/models/common.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# © 2013 Akretion (www.akretion.com) +# © 2013-2016 Akretion (Alexis de Lattre ) # © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza # © 2016 Antiun Ingenieria S.L. - Antonio Espinosa # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/account_banking_sepa_direct_debit/models/res_company.py b/account_banking_sepa_direct_debit/models/res_company.py index c57dc8b04..ac58eb42f 100644 --- a/account_banking_sepa_direct_debit/models/res_company.py +++ b/account_banking_sepa_direct_debit/models/res_company.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- -# © 2013 Akretion (www.akretion.com) +# © 2013-2016 Akretion (Alexis de Lattre ) # © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza # © 2016 Antiun Ingenieria S.L. - Antonio Espinosa # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp import models, fields, api, exceptions, _ +from openerp import models, fields, api, _ from .common import is_sepa_creditor_identifier_valid +from openerp.exceptions import ValidationError class ResCompany(models.Model): @@ -28,6 +29,6 @@ class ResCompany(models.Model): if company.sepa_creditor_identifier: if not is_sepa_creditor_identifier_valid( company.sepa_creditor_identifier): - raise exceptions.Warning( - _('Error'), - _("Invalid SEPA Creditor Identifier.")) + raise ValidationError( + _("The SEPA Creditor Identifier '%s' is invalid.") + % company.sepa_creditor_identifier) diff --git a/account_banking_sepa_direct_debit/security/original_mandate_required_security.xml b/account_banking_sepa_direct_debit/security/original_mandate_required_security.xml deleted file mode 100644 index e6a8570cf..000000000 --- a/account_banking_sepa_direct_debit/security/original_mandate_required_security.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - Original Mandate Required (SEPA) - - - - - diff --git a/account_banking_sepa_direct_debit/views/account_banking_mandate_view.xml b/account_banking_sepa_direct_debit/views/account_banking_mandate_view.xml index 667cedb54..7d8d73312 100644 --- a/account_banking_sepa_direct_debit/views/account_banking_mandate_view.xml +++ b/account_banking_sepa_direct_debit/views/account_banking_mandate_view.xml @@ -1,6 +1,6 @@ @@ -11,7 +11,7 @@ - + sdd.mandate.form account.banking.mandate @@ -25,14 +25,10 @@ - - - - - + sdd.mandate.tree account.banking.mandate @@ -45,7 +41,7 @@ - + sdd.mandate.search account.banking.mandate @@ -66,67 +62,5 @@ - - SEPA Direct Debit Mandates - account.banking.mandate - tree,form - -

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

- A 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.tree - res.partner.bank - - - - SDD Mandates - - - - - - sdd.mandate.partner.form - res.partner - - - - SDD Mandates - - - - - - Sequence Type set to First - account.banking.mandate - - Sequence Type set to First - - - - Sequence Type set to Recurring - account.banking.mandate - - Sequence Type set to Recurring - - - - Sequence Type set to Final - account.banking.mandate - - Sequence Type set to Final - -
diff --git a/account_banking_sepa_direct_debit/views/account_payment_mode.xml b/account_banking_sepa_direct_debit/views/account_payment_mode.xml new file mode 100644 index 000000000..5f41be2f3 --- /dev/null +++ b/account_banking_sepa_direct_debit/views/account_payment_mode.xml @@ -0,0 +1,23 @@ + + + + + + + Add SEPA identifiers on payment mode form + account.payment.mode + + + + + + + + + + + diff --git a/account_banking_sepa_direct_debit/views/payment_mode_view.xml b/account_banking_sepa_direct_debit/views/payment_mode_view.xml deleted file mode 100644 index 9bba891d6..000000000 --- a/account_banking_sepa_direct_debit/views/payment_mode_view.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - Add SEPA identifiers - payment.mode - - - - - - - - - - - - - diff --git a/account_banking_sepa_direct_debit/views/res_company_view.xml b/account_banking_sepa_direct_debit/views/res_company_view.xml index e4c9e0b93..6b4d544ca 100644 --- a/account_banking_sepa_direct_debit/views/res_company_view.xml +++ b/account_banking_sepa_direct_debit/views/res_company_view.xml @@ -1,20 +1,19 @@ - + sepa_direct_debit.res.company.form res.company - diff --git a/account_banking_sepa_direct_debit/wizard/__init__.py b/account_banking_sepa_direct_debit/wizard/__init__.py deleted file mode 100644 index 3830e36d9..000000000 --- a/account_banking_sepa_direct_debit/wizard/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# SEPA Direct Debit module for OpenERP -# Copyright (C) 2013 Akretion (http://www.akretion.com) -# @author: Alexis de Lattre -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -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 deleted file mode 100644 index 9b4022d46..000000000 --- a/account_banking_sepa_direct_debit/wizard/export_sdd.py +++ /dev/null @@ -1,394 +0,0 @@ -# -*- encoding: utf-8 -*- -############################################################################## -# -# SEPA Direct Debit module for Odoo -# Copyright (C) 2013-2015 Akretion (http://www.akretion.com) -# @author: Alexis de Lattre -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - - -from openerp import models, fields, api, _ -from openerp.exceptions import Warning -from openerp import workflow -from lxml import etree - - -class BankingExportSddWizard(models.TransientModel): - _name = 'banking.export.sdd.wizard' - _inherit = ['banking.export.pain'] - _description = 'Export SEPA Direct Debit File' - - state = fields.Selection([ - ('create', 'Create'), - ('finish', 'Finish'), - ], string='State', readonly=True, default='create') - batch_booking = fields.Boolean( - string='Batch Booking', - help="If true, the bank statement will display only one credit " - "line for all the direct debits of the SEPA file ; if false, " - "the bank statement will display one credit line per direct " - "debit of the SEPA file.") - charge_bearer = fields.Selection([ - ('SLEV', 'Following Service Level'), - ('SHAR', 'Shared'), - ('CRED', 'Borne by Creditor'), - ('DEBT', 'Borne by Debtor'), - ], string='Charge Bearer', required=True, default='SLEV', - help="Following service level : transaction charges are to be " - "applied following the rules agreed in the service level " - "and/or scheme (SEPA Core messages must use this). Shared : " - "transaction charges on the creditor side are to be borne " - "by the creditor, transaction charges on the debtor side are " - "to be borne by the debtor. Borne by creditor : all " - "transaction charges are to be borne by the creditor. Borne " - "by debtor : all transaction charges are to be borne by the debtor.") - nb_transactions = fields.Integer( - string='Number of Transactions', readonly=True) - total_amount = fields.Float(string='Total Amount', readonly=True) - file = fields.Binary(string="File", readonly=True) - filename = fields.Char(string="Filename", readonly=True) - payment_order_ids = fields.Many2many( - 'payment.order', 'wiz_sdd_payorders_rel', 'wizard_id', - 'payment_order_id', string='Payment Orders', readonly=True) - - @api.model - def create(self, vals): - payment_order_ids = self._context.get('active_ids', []) - vals.update({ - 'payment_order_ids': [[6, 0, payment_order_ids]], - }) - return super(BankingExportSddWizard, self).create(vals) - - def _get_previous_bank(self, payline): - previous_bank = False - older_lines = self.env['payment.line'].search([ - ('mandate_id', '=', payline.mandate_id.id), - ('bank_id', '!=', payline.bank_id.id)]) - if older_lines: - previous_date = False - previous_payline = False - for older_line in older_lines: - if hasattr(older_line.order_id, 'date_sent'): - older_line_date = older_line.order_id.date_sent - else: - older_line_date = older_line.order_id.date_done - if (older_line_date and - older_line_date > previous_date): - previous_date = older_line_date - previous_payline = older_line - if previous_payline: - previous_bank = previous_payline.bank_id - return previous_bank - - @api.multi - def create_sepa(self): - """Creates the SEPA Direct Debit file. That's the important code !""" - pain_flavor = self.payment_order_ids[0].mode.type.code - convert_to_ascii = \ - self.payment_order_ids[0].mode.convert_to_ascii - if pain_flavor == 'pain.008.001.02': - bic_xml_tag = 'BIC' - name_maxsize = 70 - root_xml_tag = 'CstmrDrctDbtInitn' - elif pain_flavor == 'pain.008.001.03': - bic_xml_tag = 'BICFI' - name_maxsize = 140 - root_xml_tag = 'CstmrDrctDbtInitn' - elif pain_flavor == 'pain.008.001.04': - bic_xml_tag = 'BICFI' - name_maxsize = 140 - root_xml_tag = 'CstmrDrctDbtInitn' - else: - raise Warning( - _("Payment Type Code '%s' is not supported. The only " - "Payment Type Code supported for SEPA Direct Debit are " - "'pain.008.001.02', 'pain.008.001.03' and " - "'pain.008.001.04'.") % pain_flavor) - gen_args = { - 'bic_xml_tag': bic_xml_tag, - 'name_maxsize': name_maxsize, - 'convert_to_ascii': convert_to_ascii, - 'payment_method': 'DD', - 'file_prefix': 'sdd_', - 'pain_flavor': pain_flavor, - 'pain_xsd_file': - 'account_banking_sepa_direct_debit/data/%s.xsd' % pain_flavor, - } - pain_ns = { - 'xsi': 'http://www.w3.org/2001/XMLSchema-instance', - None: 'urn:iso:std:iso:20022:tech:xsd:%s' % pain_flavor, - } - xml_root = etree.Element('Document', nsmap=pain_ns) - pain_root = etree.SubElement(xml_root, root_xml_tag) - # A. Group header - group_header_1_0, nb_of_transactions_1_6, control_sum_1_7 = \ - self.generate_group_header_block(pain_root, gen_args) - transactions_count_1_6 = 0 - total_amount = 0.0 - amount_control_sum_1_7 = 0.0 - lines_per_group = {} - # key = (requested_date, priority, sequence type) - # value = list of lines as objects - # Iterate on payment orders - for payment_order in self.payment_order_ids: - total_amount = total_amount + payment_order.total - # Iterate each payment lines - for line in payment_order.bank_line_ids: - transactions_count_1_6 += 1 - priority = line.priority - # The field line.date is the requested payment date - # taking into account the 'date_prefered' setting - # cf account_banking_payment_export/models/account_payment.py - # in the inherit of action_open() - if not line.mandate_id: - raise Warning( - _("Missing SEPA Direct Debit mandate on the " - "bank payment line with partner '%s' " - "(reference '%s').") - % (line.partner_id.name, line.name)) - scheme = line.mandate_id.scheme - if line.mandate_id.state != 'valid': - raise Warning( - _("The SEPA Direct Debit mandate with reference '%s' " - "for partner '%s' has expired.") - % (line.mandate_id.unique_mandate_reference, - line.mandate_id.partner_id.name)) - if line.mandate_id.type == 'oneoff': - seq_type = 'OOFF' - if line.mandate_id.last_debit_date: - raise Warning( - _("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.mandate_id.unique_mandate_reference, - line.mandate_id.partner_id.name, - line.mandate_id.last_debit_date)) - elif line.mandate_id.type == 'recurrent': - seq_type_map = { - 'recurring': 'RCUR', - 'first': 'FRST', - 'final': 'FNAL', - } - seq_type_label = \ - line.mandate_id.recurrent_sequence_type - assert seq_type_label is not False - seq_type = seq_type_map[seq_type_label] - key = (line.date, priority, seq_type, scheme) - if key in lines_per_group: - lines_per_group[key].append(line) - else: - lines_per_group[key] = [line] - - for (requested_date, priority, sequence_type, scheme), lines in \ - lines_per_group.items(): - # B. Payment info - payment_info_2_0, nb_of_transactions_2_4, control_sum_2_5 = \ - self.generate_start_payment_info_block( - pain_root, - "self.payment_order_ids[0].reference + '-' + " - "sequence_type + '-' + requested_date.replace('-', '') " - "+ '-' + priority", - priority, scheme, sequence_type, requested_date, { - 'self': self, - 'sequence_type': sequence_type, - 'priority': priority, - 'requested_date': requested_date, - }, gen_args) - - self.generate_party_block( - payment_info_2_0, 'Cdtr', 'B', - 'self.payment_order_ids[0].mode.bank_id.partner_id.' - 'name', - 'self.payment_order_ids[0].mode.bank_id.acc_number', - 'self.payment_order_ids[0].mode.bank_id.bank.bic or ' - 'self.payment_order_ids[0].mode.bank_id.bank_bic', - {'self': self}, gen_args) - charge_bearer_2_24 = etree.SubElement(payment_info_2_0, 'ChrgBr') - charge_bearer_2_24.text = self.charge_bearer - creditor_scheme_identification_2_27 = etree.SubElement( - payment_info_2_0, 'CdtrSchmeId') - self.generate_creditor_scheme_identification( - creditor_scheme_identification_2_27, - 'self.payment_order_ids[0].mode.' - 'sepa_creditor_identifier or ' - 'self.payment_order_ids[0].company_id.' - 'sepa_creditor_identifier', - 'SEPA Creditor Identifier', {'self': self}, 'SEPA', gen_args) - 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') - end2end_identification_2_31 = etree.SubElement( - payment_identification_2_29, 'EndToEndId') - end2end_identification_2_31.text = self._prepare_field( - 'End to End Identification', 'line.name', - {'line': line}, 35, gen_args=gen_args) - currency_name = self._prepare_field( - 'Currency Code', 'line.currency.name', - {'line': line}, 3, gen_args=gen_args) - 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_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 = self._prepare_field( - 'Unique Mandate Reference', - 'line.mandate_id.unique_mandate_reference', - {'line': line}, 35, gen_args=gen_args) - mandate_signature_date_2_49 = etree.SubElement( - mandate_related_info_2_47, 'DtOfSgntr') - mandate_signature_date_2_49.text = self._prepare_field( - 'Mandate Signature Date', - 'line.mandate_id.signature_date', - {'line': line}, 10, gen_args=gen_args) - if sequence_type == 'FRST' and ( - line.mandate_id.last_debit_date or - not line.mandate_id.sepa_migrated): - previous_bank = self._get_previous_bank(line) - if previous_bank or not line.mandate_id.sepa_migrated: - amendment_indicator_2_50 = etree.SubElement( - mandate_related_info_2_47, 'AmdmntInd') - amendment_indicator_2_50.text = 'true' - amendment_info_details_2_51 = etree.SubElement( - mandate_related_info_2_47, 'AmdmntInfDtls') - if previous_bank: - if (previous_bank.bank.bic or - previous_bank.bank_bic) == \ - (line.bank_id.bank.bic or - line.bank_id.bank_bic): - ori_debtor_account_2_57 = etree.SubElement( - amendment_info_details_2_51, 'OrgnlDbtrAcct') - ori_debtor_account_id = etree.SubElement( - ori_debtor_account_2_57, 'Id') - ori_debtor_account_iban = etree.SubElement( - ori_debtor_account_id, 'IBAN') - ori_debtor_account_iban.text = self._validate_iban( - self._prepare_field( - 'Original Debtor Account', - 'previous_bank.acc_number', - {'previous_bank': previous_bank}, - gen_args=gen_args)) - else: - ori_debtor_agent_2_58 = etree.SubElement( - amendment_info_details_2_51, 'OrgnlDbtrAgt') - ori_debtor_agent_institution = etree.SubElement( - ori_debtor_agent_2_58, 'FinInstnId') - ori_debtor_agent_bic = etree.SubElement( - ori_debtor_agent_institution, bic_xml_tag) - ori_debtor_agent_bic.text = self._prepare_field( - 'Original Debtor Agent', - 'previous_bank.bank.bic or ' - 'previous_bank.bank_bic', - {'previous_bank': previous_bank}, - gen_args=gen_args) - ori_debtor_agent_other = etree.SubElement( - ori_debtor_agent_institution, 'Othr') - ori_debtor_agent_other_id = etree.SubElement( - ori_debtor_agent_other, 'Id') - ori_debtor_agent_other_id.text = 'SMNDA' - # SMNDA = Same Mandate New Debtor Agent - elif not line.mandate_id.sepa_migrated: - ori_mandate_identification_2_52 = etree.SubElement( - amendment_info_details_2_51, 'OrgnlMndtId') - ori_mandate_identification_2_52.text = \ - self._prepare_field( - 'Original Mandate Identification', - 'line.mandate_id.' - 'original_mandate_identification', - {'line': line}, - gen_args=gen_args) - ori_creditor_scheme_id_2_53 = etree.SubElement( - amendment_info_details_2_51, 'OrgnlCdtrSchmeId') - self.generate_creditor_scheme_identification( - ori_creditor_scheme_id_2_53, - 'self.payment_order_ids[0].mode.' - 'original_creditor_identifier or ' - 'self.payment_order_ids[0].company_id.' - 'original_creditor_identifier', - 'Original Creditor Identifier', - {'self': self}, 'SEPA', gen_args) - - self.generate_party_block( - dd_transaction_info_2_28, 'Dbtr', 'C', - 'line.partner_id.name', - 'line.bank_id.acc_number', - 'line.bank_id.bank.bic or ' - 'line.bank_id.bank_bic', - {'line': line}, gen_args) - - self.generate_remittance_info_block( - dd_transaction_info_2_28, line, gen_args) - - nb_of_transactions_2_4.text = unicode(transactions_count_2_4) - control_sum_2_5.text = '%.2f' % amount_control_sum_2_5 - nb_of_transactions_1_6.text = unicode(transactions_count_1_6) - control_sum_1_7.text = '%.2f' % amount_control_sum_1_7 - - return self.finalize_sepa_file_creation( - xml_root, total_amount, transactions_count_1_6, gen_args) - - @api.multi - def save_sepa(self): - """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. - """ - abmo = self.env['account.banking.mandate'] - for order in self.payment_order_ids: - workflow.trg_validate( - self._uid, 'payment.order', order.id, 'done', self._cr) - self.env['ir.attachment'].create({ - 'res_model': 'payment.order', - 'res_id': order.id, - 'name': self.filename, - 'datas': self.file, - }) - to_expire_mandates = abmo.browse([]) - first_mandates = abmo.browse([]) - all_mandates = abmo.browse([]) - for bline in order.bank_line_ids: - if bline.mandate_id in all_mandates: - continue - all_mandates += bline.mandate_id - if bline.mandate_id.type == 'oneoff': - to_expire_mandates += bline.mandate_id - elif bline.mandate_id.type == 'recurrent': - seq_type = bline.mandate_id.recurrent_sequence_type - if seq_type == 'final': - to_expire_mandates += bline.mandate_id - elif seq_type == 'first': - first_mandates += bline.mandate_id - all_mandates.write( - {'last_debit_date': fields.Date.context_today(self)}) - to_expire_mandates.write({'state': 'expired'}) - first_mandates.write({ - 'recurrent_sequence_type': 'recurring', - 'sepa_migrated': True, - }) - return True diff --git a/account_banking_sepa_direct_debit/wizard/export_sdd_view.xml b/account_banking_sepa_direct_debit/wizard/export_sdd_view.xml deleted file mode 100644 index 95117e270..000000000 --- a/account_banking_sepa_direct_debit/wizard/export_sdd_view.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - banking.export.sdd.wizard.view - banking.export.sdd.wizard - -
- - - - - - - - - - - - - -
-
- -
-