From 38a586173feba76caf13546debbf81815667daee Mon Sep 17 00:00:00 2001 From: Cyril Sester Date: Thu, 14 Aug 2014 17:08:02 +0200 Subject: [PATCH 1/9] Refactoring to get a generic mandate --- account_banking_mandate/__init__.py | 22 ++ account_banking_mandate/__openerp__.py | 48 ++++ .../data/mandate_reference_sequence.xml | 21 ++ account_banking_mandate/model/__init__.py | 26 ++ .../model/account_banking_mandate.py | 185 ++++++++++++++ account_banking_mandate/model/invoice.py | 35 +++ account_banking_mandate/model/partner_bank.py | 33 +++ account_banking_mandate/model/payment_line.py | 93 +++++++ .../view/account_banking_mandate_view.xml | 122 +++++++++ .../view}/account_invoice_view.xml | 4 +- .../view}/account_payment_view.xml | 8 +- .../view}/res_partner_bank_view.xml | 20 +- .../__openerp__.py | 27 +- .../account_banking_sdd.py | 225 +---------------- .../mandate_expire_cron.xml | 2 +- .../sdd_mandate_view.xml | 231 ++++++++---------- .../security/ir.model.access.csv | 4 +- .../sepa_direct_debit_demo.xml | 2 +- .../wizard/export_sdd.py | 58 ++--- 19 files changed, 769 insertions(+), 397 deletions(-) create mode 100644 account_banking_mandate/__init__.py create mode 100644 account_banking_mandate/__openerp__.py create mode 100644 account_banking_mandate/data/mandate_reference_sequence.xml create mode 100644 account_banking_mandate/model/__init__.py create mode 100644 account_banking_mandate/model/account_banking_mandate.py create mode 100644 account_banking_mandate/model/invoice.py create mode 100644 account_banking_mandate/model/partner_bank.py create mode 100644 account_banking_mandate/model/payment_line.py create mode 100644 account_banking_mandate/view/account_banking_mandate_view.xml rename {account_banking_sepa_direct_debit => account_banking_mandate/view}/account_invoice_view.xml (69%) rename {account_banking_sepa_direct_debit => account_banking_mandate/view}/account_payment_view.xml (59%) rename {account_banking_sepa_direct_debit => account_banking_mandate/view}/res_partner_bank_view.xml (60%) diff --git a/account_banking_mandate/__init__.py b/account_banking_mandate/__init__.py new file mode 100644 index 000000000..6a33af43c --- /dev/null +++ b/account_banking_mandate/__init__.py @@ -0,0 +1,22 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Mandate module for openERP +# Copyright (C) 2014 Compassion CH (http://www.compassion.ch) +# @author: Cyril Sester +# +# 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 model diff --git a/account_banking_mandate/__openerp__.py b/account_banking_mandate/__openerp__.py new file mode 100644 index 000000000..e57018c96 --- /dev/null +++ b/account_banking_mandate/__openerp__.py @@ -0,0 +1,48 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Mandate module for openERP +# Copyright (C) 2014 Compassion CH (http://www.compassion.ch) +# @author: Cyril Sester , +# 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 . +# +############################################################################## +{ + 'name': 'Account Banking Mandate', + 'summary': 'Banking mandates', + 'version': '0.1', + 'license': 'AGPL-3', + 'author': 'Compassion CH', + 'website': 'http://www.compassion.ch', + 'category': 'Banking addons', + 'depends': ['account_payment'], + 'external_dependencies': {}, + 'data': [ + 'view/account_banking_mandate_view.xml', + 'view/account_invoice_view.xml', + 'view/account_payment_view.xml', + 'view/res_partner_bank_view.xml', + 'data/mandate_reference_sequence.xml', + ], + 'demo': [], + 'description': ''' + This module adds a generic model for banking mandates. + These mandates can be specialized to fit any banking mandates (such as sepa + or lsv). +''', + 'active': False, + 'installable': True, +} diff --git a/account_banking_mandate/data/mandate_reference_sequence.xml b/account_banking_mandate/data/mandate_reference_sequence.xml new file mode 100644 index 000000000..f24d823d6 --- /dev/null +++ b/account_banking_mandate/data/mandate_reference_sequence.xml @@ -0,0 +1,21 @@ + + + + + + + DD Mandate Reference + account.banking.mandate + + + + DD Mandate Reference + account.banking.mandate + RUM + + + + + + + diff --git a/account_banking_mandate/model/__init__.py b/account_banking_mandate/model/__init__.py new file mode 100644 index 000000000..247282be5 --- /dev/null +++ b/account_banking_mandate/model/__init__.py @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Mandate module for openERP +# Copyright (C) 2014 Compassion CH (http://www.compassion.ch) +# @author: Cyril Sester , +# 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 account_banking_mandate +from . import invoice +from . import partner_bank +from . import payment_line diff --git a/account_banking_mandate/model/account_banking_mandate.py b/account_banking_mandate/model/account_banking_mandate.py new file mode 100644 index 000000000..ae4354fe0 --- /dev/null +++ b/account_banking_mandate/model/account_banking_mandate.py @@ -0,0 +1,185 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Mandate module for openERP +# Copyright (C) 2014 Compassion CH (http://www.compassion.ch) +# @author: Cyril Sester , +# 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 datetime import datetime +from openerp.osv import orm, fields +from openerp.tools.translate import _ + + +class mandate(orm.Model): + ''' The banking mandate is attached to a bank account and represents an + authorization that the bank account owner gives to a company for a + specific operation (such as direct debit) + ''' + _name = 'account.banking.mandate' + _description = "A generic banking mandate" + _rec_name = 'unique_mandate_reference' + _inherit = ['mail.thread'] + _order = 'signature_date desc' + _track = { + 'state': { + 'account_banking_mandate.mandate_valid': + lambda self, cr, uid, obj, ctx=None: + obj['state'] == 'valid', + 'account_banking_mandate.mandate_expired': + lambda self, cr, uid, obj, ctx=None: + obj['state'] == 'expired', + 'account_banking_mandate.mandate_cancel': + lambda self, cr, uid, obj, ctx=None: + obj['state'] == 'cancel', + }, + } + + def _get_states(self, cr, uid, context=None): + return ( + ('draft', 'Draft'), + ('valid', 'Valid'), + ('expired', 'Expired'), + ('cancel', 'Cancelled'),) + + _columns = { + 'partner_bank_id': fields.many2one( + 'res.partner.bank', 'Bank Account', track_visibility='onchange'), + '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, + track_visibility='always'), + 'signature_date': fields.date( + 'Date of Signature of the Mandate', track_visibility='onchange'), + 'scan': fields.binary('Scan of the Mandate'), + 'last_debit_date': fields.date( + 'Date of the Last Debit', readonly=True), + 'state': fields.selection( + _get_states, 'Status', + help="Only valid mandates can be used in a payment line. A " + "cancelled mandate is a mandate that has been cancelled by " + "the customer."), + 'payment_line_ids': fields.one2many( + 'payment.line', 'mandate_id', "Related Payment Lines"), + } + + _defaults = { + 'company_id': lambda self, cr, uid, context: + self.pool['res.company']._company_default_get( + cr, uid, 'account.banking.mandate', context=context), + 'unique_mandate_reference': '/', + 'state': 'draft', + } + + _sql_constraints = [( + 'mandate_ref_company_uniq', + 'unique(unique_mandate_reference, company_id)', + 'A Mandate with the same reference already exists for this company !' + )] + + def create(self, cr, uid, vals, context=None): + if vals.get('unique_mandate_reference', '/') == '/': + vals['unique_mandate_reference'] = \ + self.pool['ir.sequence'].next_by_code( + cr, uid, 'account.banking.mandate', context=context) + return super(mandate, self).create(cr, uid, vals, context=context) + + def _check_dates(self, cr, uid, ids): + for mandate in self.browse(cr, uid, ids): + if (mandate.signature_date and + mandate.signature_date > + datetime.today().strftime('%Y-%m-%d')): + raise orm.except_orm( + _('Error:'), + _("The date of signature of mandate '%s' is in the " + "future !") + % mandate.unique_mandate_reference) + + if (mandate.signature_date and mandate.last_debit_date and + mandate.signature_date > mandate.last_debit_date): + raise orm.except_orm( + _('Error:'), + _("The mandate '%s' can't have a date of last debit " + "before the date of signature.") + % mandate.unique_mandate_reference) + return True + + def _check_valid_state(self, cr, uid, ids): + for mandate in self.browse(cr, uid, ids): + if mandate.state == 'valid' and not mandate.signature_date: + raise orm.except_orm( + _('Error:'), + _("Cannot validate the mandate '%s' without a date of " + "signature.") + % mandate.unique_mandate_reference) + if mandate.state == 'valid' and not mandate.partner_bank_id: + raise orm.except_orm( + _('Error:'), + _("Cannot validate the mandate '%s' because it is not " + "attached to a bank account.") + % mandate.unique_mandate_reference) + return True + + _constraints = [ + (_check_dates, "Error msg in raise", + ['signature_date', 'last_debit_date']), + (_check_valid_state, "Error msg in raise", + ['state', 'partner_bank_id']), + ] + + def mandate_partner_bank_change( + self, cr, uid, ids, partner_bank_id, last_debit_date, state): + res = {'value': {}} + if partner_bank_id: + partner_bank_read = self.pool['res.partner.bank'].read( + cr, uid, partner_bank_id, ['partner_id'])['partner_id'] + if partner_bank_read: + res['value']['partner_id'] = partner_bank_read[0] + return res + + def validate(self, cr, uid, ids, context=None): + to_validate_ids = [] + for mandate in self.browse(cr, uid, ids, context=context): + assert mandate.state == 'draft', 'Mandate should be in draft state' + to_validate_ids.append(mandate.id) + self.write( + cr, uid, to_validate_ids, {'state': 'valid'}, context=context) + return True + + def cancel(self, cr, uid, ids, context=None): + to_cancel_ids = [] + for mandate in self.browse(cr, uid, ids, context=context): + assert mandate.state in ('draft', 'valid'),\ + 'Mandate should be in draft or valid state' + to_cancel_ids.append(mandate.id) + self.write( + cr, uid, to_cancel_ids, {'state': 'cancel'}, context=context) + return True + + def back2draft(self, cr, uid, ids, context=None): + to_draft_ids = [] + for mandate in self.browse(cr, uid, ids, context=context): + assert mandate.state == 'cancel',\ + 'Mandate should be in cancel state' + to_draft_ids.append(mandate.id) + self.write( + cr, uid, to_draft_ids, {'state': 'draft'}, context=context) + return True diff --git a/account_banking_mandate/model/invoice.py b/account_banking_mandate/model/invoice.py new file mode 100644 index 000000000..e3f4920cd --- /dev/null +++ b/account_banking_mandate/model/invoice.py @@ -0,0 +1,35 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Mandate module for openERP +# Copyright (C) 2014 Compassion CH (http://www.compassion.ch) +# @author: Cyril Sester , +# 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.osv import orm, fields + + +class account_invoice(orm.Model): + _inherit = 'account.invoice' + + _columns = { + 'mandate_id': fields.many2one( + 'account.banking.mandate', 'Direct Debit Mandate', + domain=[('state', '=', 'valid')], readonly=True, + states={'draft': [('readonly', False)]}) + } diff --git a/account_banking_mandate/model/partner_bank.py b/account_banking_mandate/model/partner_bank.py new file mode 100644 index 000000000..4043c02d0 --- /dev/null +++ b/account_banking_mandate/model/partner_bank.py @@ -0,0 +1,33 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Mandate module for openERP +# Copyright (C) 2014 Compassion CH (http://www.compassion.ch) +# @author: Cyril Sester , +# 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.osv import orm, fields + + +class res_partner_bank(orm.Model): + _inherit = 'res.partner.bank' + + _columns = { + 'mandate_ids': fields.one2many( + 'account.banking.mandate', 'partner_bank_id', 'Banking Mandates'), + } diff --git a/account_banking_mandate/model/payment_line.py b/account_banking_mandate/model/payment_line.py new file mode 100644 index 000000000..7a39c19d4 --- /dev/null +++ b/account_banking_mandate/model/payment_line.py @@ -0,0 +1,93 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Mandate module for openERP +# Copyright (C) 2014 Compassion CH (http://www.compassion.ch) +# @author: Cyril Sester , +# 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.osv import orm, fields +from openerp.tools.translate import _ + + +class payment_line(orm.Model): + _inherit = 'payment.line' + + _columns = { + 'mandate_id': fields.many2one( + 'account.banking.mandate', 'Direct Debit Mandate', + domain=[('state', '=', 'valid')]), + } + + def create(self, cr, uid, vals, context=None): + ''' If the customer invoice has a mandate, take it + otherwise, take the first valid mandate of the bank account + ''' + if context is None: + context = {} + if not vals: + vals = {} + partner_bank_id = vals.get('bank_id') + move_line_id = vals.get('move_line_id') + if (context.get('search_payment_order_type') == 'debit' + and 'mandate_id' not in vals): + if move_line_id: + line = self.pool['account.move.line'].browse( + cr, uid, move_line_id, context=context) + if (line.invoice and line.invoice.type == 'out_invoice' + and line.invoice.mandate_id): + vals.update({ + 'mandate_id': line.invoice.mandate_id.id, + 'bank_id': + line.invoice.mandate_id.partner_bank_id.id, + }) + if partner_bank_id and 'mandate_id' not in vals: + mandate_ids = self.pool['account.banking.mandate'].search( + cr, uid, [ + ('partner_bank_id', '=', partner_bank_id), + ('state', '=', 'valid'), + ], context=context) + if mandate_ids: + vals['mandate_id'] = mandate_ids[0] + return super(payment_line, self).create(cr, uid, vals, context=context) + + def _check_mandate_bank_link(self, cr, uid, ids): + for payline in self.browse(cr, uid, ids): + if (payline.mandate_id and payline.bank_id + and payline.mandate_id.partner_bank_id.id != + payline.bank_id.id): + raise orm.except_orm( + _('Error:'), + _("The payment line with reference '%s' has the bank " + "account '%s' which is not attached to the mandate " + "'%s' (this mandate is attached to the bank account " + "'%s').") % ( + payline.name, + self.pool['res.partner.bank'].name_get( + cr, uid, [payline.bank_id.id])[0][1], + payline.mandate_id.unique_mandate_reference, + self.pool['res.partner.bank'].name_get( + cr, uid, + [payline.mandate_id.partner_bank_id.id])[0][1], + )) + return True + + _constraints = [ + (_check_mandate_bank_link, 'Error msg in raise', + ['mandate_id', 'bank_id']), + ] diff --git a/account_banking_mandate/view/account_banking_mandate_view.xml b/account_banking_mandate/view/account_banking_mandate_view.xml new file mode 100644 index 000000000..3d0cfd73f --- /dev/null +++ b/account_banking_mandate/view/account_banking_mandate_view.xml @@ -0,0 +1,122 @@ + + + + + + view.mandate.form + account.banking.mandate + +
+
+
+ +
+

+ +

+
+ + + + + + + + + + + +
+
+ + +
+
+
+
+ + + view.mandate.tree + account.banking.mandate + + + + + + + + + + + + + + view.mandate.search + account.banking.mandate + + + + + + + + + + + + + Banking Mandates + account.banking.mandate + form + tree,form + +

+ Click to create a new Banking Mandate. +

+ A Banking Mandate is a document signed by your customer that gives you the autorization to do one or several operations on his bank account. +

+
+
+ + + + + + Mandate Validated + account.banking.mandate + + Banking Mandate Validated + + + + Mandate Expired + account.banking.mandate + + Banking Mandate has Expired + + + + Mandate Cancelled + account.banking.mandate + + Banking Mandate Cancelled + +
+
\ No newline at end of file diff --git a/account_banking_sepa_direct_debit/account_invoice_view.xml b/account_banking_mandate/view/account_invoice_view.xml similarity index 69% rename from account_banking_sepa_direct_debit/account_invoice_view.xml rename to account_banking_mandate/view/account_invoice_view.xml index 2ca480e54..a21e83857 100644 --- a/account_banking_sepa_direct_debit/account_invoice_view.xml +++ b/account_banking_mandate/view/account_invoice_view.xml @@ -8,12 +8,12 @@ - add.sdd.mandate.on.customer.invoice.form + add.mandate.on.customer.invoice.form account.invoice - + diff --git a/account_banking_sepa_direct_debit/account_payment_view.xml b/account_banking_mandate/view/account_payment_view.xml similarity index 59% rename from account_banking_sepa_direct_debit/account_payment_view.xml rename to account_banking_mandate/view/account_payment_view.xml index 74098c44e..39ec86953 100644 --- a/account_banking_sepa_direct_debit/account_payment_view.xml +++ b/account_banking_mandate/view/account_payment_view.xml @@ -7,17 +7,17 @@ - - sdd.payment.order.form + + mandate.payment.order.form payment.order - + - + diff --git a/account_banking_sepa_direct_debit/res_partner_bank_view.xml b/account_banking_mandate/view/res_partner_bank_view.xml similarity index 60% rename from account_banking_sepa_direct_debit/res_partner_bank_view.xml rename to account_banking_mandate/view/res_partner_bank_view.xml index 0b32e9f1c..572fa766e 100644 --- a/account_banking_sepa_direct_debit/res_partner_bank_view.xml +++ b/account_banking_mandate/view/res_partner_bank_view.xml @@ -7,39 +7,39 @@ - - sdd.mandate.res.partner.bank.form + + mandate.res.partner.bank.form res.partner.bank - - + + - - sdd.mandate.res.partner.bank.tree + + mandate.res.partner.bank.tree res.partner.bank - + - - sdd.mandate.partner.form + + mandate.partner.form res.partner - + diff --git a/account_banking_sepa_direct_debit/__openerp__.py b/account_banking_sepa_direct_debit/__openerp__.py index 5ea5dadb4..3d774a34d 100644 --- a/account_banking_sepa_direct_debit/__openerp__.py +++ b/account_banking_sepa_direct_debit/__openerp__.py @@ -27,7 +27,11 @@ 'author': 'Akretion', 'website': 'http://www.akretion.com', 'category': 'Banking addons', - 'depends': ['account_direct_debit', 'account_banking_pain_base'], + 'depends': [ + 'account_banking_mandate', + 'account_direct_debit', + 'account_banking_pain_base' + ], 'external_dependencies': { 'python': ['unidecode', 'lxml'], }, @@ -35,11 +39,8 @@ 'security/original_mandate_required_security.xml', 'account_banking_sdd_view.xml', 'sdd_mandate_view.xml', - 'res_partner_bank_view.xml', - 'account_payment_view.xml', 'company_view.xml', 'mandate_expire_cron.xml', - 'account_invoice_view.xml', 'wizard/export_sdd_view.xml', 'data/payment_type_sdd.xml', 'data/mandate_reference_sequence.xml', @@ -50,6 +51,7 @@ Module to export direct debit payment orders in SEPA XML file format. SEPA PAIN (PAyment INitiation) is the new european standard for +<<<<<<< HEAD Customer-to-Bank payment instructions. This module implements SEPA Direct Debit (SDD), more specifically PAIN @@ -66,6 +68,23 @@ cf https://www.github.com/OCA/banking-addons Please contact Alexis de Lattre from Akretion for any help or question about this module. +======= + Customer-to-Bank payment instructions. This module implements SEPA Direct + Debit (SDD), more specifically PAIN versions 008.001.02, 008.001.03 and + 008.001.04. It is part of the ISO 20022 standard, available on + http://www.iso20022.org. + +The Implementation Guidelines for SEPA Direct Debit published by the European + Payments Council (http://http://www.europeanpaymentscouncil.eu) use PAIN + version 008.001.02. So if you don't know which version your bank supports, + you should try version 008.001.02 first. + +This module uses the framework provided by the banking addons, + cf https://launchpad.net/banking-addons + +Please contact Alexis de Lattre from Akretion + for any help or question about this module. +>>>>>>> Refactoring to get a generic mandate ''', 'active': False, 'installable': True, diff --git a/account_banking_sepa_direct_debit/account_banking_sdd.py b/account_banking_sepa_direct_debit/account_banking_sdd.py index 87e50111b..d33b51730 100644 --- a/account_banking_sepa_direct_debit/account_banking_sdd.py +++ b/account_banking_sepa_direct_debit/account_banking_sdd.py @@ -36,7 +36,7 @@ logger = logging.getLogger(__name__) class banking_export_sdd(orm.Model): '''SEPA Direct Debit export''' _name = 'banking.export.sdd' - _description = __doc__ + _description = 'SEPA Direct Debit export' _rec_name = 'filename' def _generate_filename(self, cr, uid, ids, name, arg, context=None): @@ -100,23 +100,10 @@ class banking_export_sdd(orm.Model): class sdd_mandate(orm.Model): '''SEPA Direct Debit Mandate''' - _name = 'sdd.mandate' - _description = __doc__ - _rec_name = 'unique_mandate_reference' - _inherit = ['mail.thread'] - _order = 'signature_date desc' + _name = 'account.banking.mandate' + _description = 'SEPA Direct Debit Mandate' + _inherit = 'account.banking.mandate' _track = { - 'state': { - 'account_banking_sepa_direct_debit.mandate_valid': - lambda self, cr, uid, obj, ctx=None: - obj['state'] == 'valid', - 'account_banking_sepa_direct_debit.mandate_expired': - lambda self, cr, uid, obj, ctx=None: - obj['state'] == 'expired', - 'account_banking_sepa_direct_debit.mandate_cancel': - lambda self, cr, uid, obj, ctx=None: - obj['state'] == 'cancel', - }, 'recurrent_sequence_type': { 'account_banking_sepa_direct_debit.recurrent_sequence_type_first': lambda self, cr, uid, obj, ctx=None: @@ -132,15 +119,6 @@ class sdd_mandate(orm.Model): } _columns = { - 'partner_bank_id': fields.many2one( - 'res.partner.bank', 'Bank Account', track_visibility='onchange'), - '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, - track_visibility='always'), 'type': fields.selection([ ('recurrent', 'Recurrent'), ('oneoff', 'One-Off'), @@ -152,24 +130,6 @@ class sdd_mandate(orm.Model): ], 'Sequence Type for Next Debit', track_visibility='onchange', help="This field is only used for Recurrent mandates, not for " "One-Off mandates."), - 'signature_date': fields.date( - 'Date of Signature of the Mandate', track_visibility='onchange'), - 'scan': fields.binary('Scan of the Mandate'), - 'last_debit_date': fields.date( - 'Date of the Last Debit', readonly=True), - 'state': fields.selection([ - ('draft', 'Draft'), - ('valid', 'Valid'), - ('expired', 'Expired'), - ('cancel', 'Cancelled'), - ], 'Status', - help="Only valid mandates can be used in a payment line. A " - "cancelled mandate is a mandate that has been cancelled by " - "the customer. A one-off mandate expires after its first use. " - "A recurrent mandate expires after it's final use or if it " - "hasn't been used for 36 months."), - 'payment_line_ids': fields.one2many( - 'payment.line', 'sdd_mandate_id', "Related Payment Lines"), 'sepa_migrated': fields.boolean( 'Migrated to SEPA', track_visibility='onchange', help="If this field is not active, the mandate section of the " @@ -188,57 +148,11 @@ class sdd_mandate(orm.Model): } _defaults = { - 'company_id': lambda self, cr, uid, context: - self.pool['res.company']._company_default_get( - cr, uid, 'sdd.mandate', context=context), - 'unique_mandate_reference': '/', - 'state': 'draft', 'sepa_migrated': True, } - _sql_constraints = [( - 'mandate_ref_company_uniq', - 'unique(unique_mandate_reference, company_id)', - 'A Mandate with the same reference already exists for this company !' - )] - - def create(self, cr, uid, vals, context=None): - if vals.get('unique_mandate_reference', '/') == '/': - vals['unique_mandate_reference'] = \ - self.pool['ir.sequence'].next_by_code( - cr, uid, 'sdd.mandate.reference', context=context) - return super(sdd_mandate, self).create(cr, uid, vals, context=context) - def _check_sdd_mandate(self, cr, uid, ids): for mandate in self.browse(cr, uid, ids): - if (mandate.signature_date and - mandate.signature_date > - datetime.today().strftime('%Y-%m-%d')): - raise orm.except_orm( - _('Error:'), - _("The date of signature of mandate '%s' is in the " - "future !") - % mandate.unique_mandate_reference) - if mandate.state == 'valid' and not mandate.signature_date: - raise orm.except_orm( - _('Error:'), - _("Cannot validate the mandate '%s' without a date of " - "signature.") - % mandate.unique_mandate_reference) - if mandate.state == 'valid' and not mandate.partner_bank_id: - raise orm.except_orm( - _('Error:'), - _("Cannot validate the mandate '%s' because it is not " - "attached to a bank account.") - % mandate.unique_mandate_reference) - - if (mandate.signature_date and mandate.last_debit_date and - mandate.signature_date > mandate.last_debit_date): - raise orm.except_orm( - _('Error:'), - _("The mandate '%s' can't have a date of last debit " - "before the date of signature.") - % mandate.unique_mandate_reference) if (mandate.type == 'recurrent' and not mandate.recurrent_sequence_type): raise orm.except_orm( @@ -265,12 +179,18 @@ class sdd_mandate(orm.Model): _constraints = [ (_check_sdd_mandate, "Error msg in raise", [ - 'last_debit_date', 'signature_date', 'state', 'partner_bank_id', 'type', 'recurrent_sequence_type', 'sepa_migrated', 'original_mandate_identification', ]), ] + def create(self, cr, uid, vals, context=None): + if vals.get('unique_mandate_reference', '/') == '/': + vals['unique_mandate_reference'] = \ + self.pool['ir.sequence'].next_by_code( + cr, uid, 'sdd.mandate.reference', context=context) + return super(sdd_mandate, self).create(cr, uid, vals, context=context) + def mandate_type_change(self, cr, uid, ids, type): if type == 'recurrent': recurrent_sequence_type = 'first' @@ -282,12 +202,8 @@ class sdd_mandate(orm.Model): def mandate_partner_bank_change( self, cr, uid, ids, partner_bank_id, type, recurrent_sequence_type, last_debit_date, state): - res = {'value': {}} - if partner_bank_id: - partner_bank_read = self.pool['res.partner.bank'].read( - cr, uid, partner_bank_id, ['partner_id'])['partner_id'] - if partner_bank_read: - res['value']['partner_id'] = partner_bank_read[0] + res = super(sdd_mandate, self).mandate_partner_bank_change( + cr, uid, ids, partner_bank_id, last_debit_date, state) if (state == 'valid' and partner_bank_id and type == 'recurrent' and recurrent_sequence_type != 'first'): @@ -301,35 +217,6 @@ class sdd_mandate(orm.Model): } return res - def validate(self, cr, uid, ids, context=None): - to_validate_ids = [] - for mandate in self.browse(cr, uid, ids, context=context): - assert mandate.state == 'draft', 'Mandate should be in draft state' - to_validate_ids.append(mandate.id) - self.write( - cr, uid, to_validate_ids, {'state': 'valid'}, context=context) - return True - - def cancel(self, cr, uid, ids, context=None): - to_cancel_ids = [] - for mandate in self.browse(cr, uid, ids, context=context): - assert mandate.state in ('draft', 'valid'),\ - 'Mandate should be in draft or valid state' - to_cancel_ids.append(mandate.id) - self.write( - cr, uid, to_cancel_ids, {'state': 'cancel'}, context=context) - return True - - def back2draft(self, cr, uid, ids, context=None): - to_draft_ids = [] - for mandate in self.browse(cr, uid, ids, context=context): - assert mandate.state == 'cancel',\ - 'Mandate should be in cancel state' - to_draft_ids.append(mandate.id) - self.write( - cr, uid, to_draft_ids, {'state': 'draft'}, context=context) - return True - def _sdd_mandate_set_state_to_expired(self, cr, uid, context=None): logger.info('Searching for SDD Mandates that must be set to Expired') expire_limit_date = datetime.today() + \ @@ -352,89 +239,3 @@ class sdd_mandate(orm.Model): else: logger.info('0 SDD Mandates must be set to Expired') return True - - -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', - domain=[('state', '=', 'valid')]), - } - - def create(self, cr, uid, vals, context=None): - '''If the customer invoice has a mandate, take it - otherwise, take the first valid mandate of the bank account''' - if context is None: - context = {} - if not vals: - vals = {} - partner_bank_id = vals.get('bank_id') - move_line_id = vals.get('move_line_id') - if (context.get('default_payment_order_type') == 'debit' - and 'sdd_mandate_id' not in vals): - if move_line_id: - line = self.pool['account.move.line'].browse( - cr, uid, move_line_id, context=context) - if (line.invoice and line.invoice.type == 'out_invoice' - and line.invoice.sdd_mandate_id): - vals.update({ - 'sdd_mandate_id': line.invoice.sdd_mandate_id.id, - 'bank_id': - line.invoice.sdd_mandate_id.partner_bank_id.id, - }) - if partner_bank_id and 'sdd_mandate_id' not in vals: - mandate_ids = self.pool['sdd.mandate'].search(cr, uid, [ - ('partner_bank_id', '=', partner_bank_id), - ('state', '=', 'valid'), - ], context=context) - if mandate_ids: - vals['sdd_mandate_id'] = mandate_ids[0] - return super(payment_line, self).create(cr, uid, vals, context=context) - - def _check_mandate_bank_link(self, cr, uid, ids): - for payline in self.browse(cr, uid, ids): - if (payline.sdd_mandate_id and payline.bank_id - and payline.sdd_mandate_id.partner_bank_id.id != - payline.bank_id.id): - raise orm.except_orm( - _('Error:'), - _("The payment line with reference '%s' has the bank " - "account '%s' which is not attached to the mandate " - "'%s' (this mandate is attached to the bank account " - "'%s').") % ( - payline.name, - self.pool['res.partner.bank'].name_get( - cr, uid, [payline.bank_id.id])[0][1], - payline.sdd_mandate_id.unique_mandate_reference, - self.pool['res.partner.bank'].name_get( - cr, uid, - [payline.sdd_mandate_id.partner_bank_id.id])[0][1], - )) - return True - - _constraints = [ - (_check_mandate_bank_link, 'Error msg in raise', - ['sdd_mandate_id', 'bank_id']), - ] - - -class account_invoice(orm.Model): - _inherit = 'account.invoice' - - _columns = { - 'sdd_mandate_id': fields.many2one( - 'sdd.mandate', 'SEPA Direct Debit Mandate', - domain=[('state', '=', 'valid')], readonly=True, - states={'draft': [('readonly', False)]}) - } diff --git a/account_banking_sepa_direct_debit/mandate_expire_cron.xml b/account_banking_sepa_direct_debit/mandate_expire_cron.xml index 4cb0693d2..ac70f7896 100644 --- a/account_banking_sepa_direct_debit/mandate_expire_cron.xml +++ b/account_banking_sepa_direct_debit/mandate_expire_cron.xml @@ -17,7 +17,7 @@ days -1 - + diff --git a/account_banking_sepa_direct_debit/sdd_mandate_view.xml b/account_banking_sepa_direct_debit/sdd_mandate_view.xml index bd1dd6e79..b5c475a3d 100644 --- a/account_banking_sepa_direct_debit/sdd_mandate_view.xml +++ b/account_banking_sepa_direct_debit/sdd_mandate_view.xml @@ -4,149 +4,116 @@ @author: Alexis de Lattre The licence is in the file __openerp__.py --> + + + sdd.mandate.form + account.banking.mandate + + + + + + + + + mandate_partner_bank_change(partner_bank_id, type, recurrent_sequence_type, last_debit_date, state) + + + + + + + + - - sdd.mandate.form - sdd.mandate - -
-
-
- -
-

- -

-
- - - - - - - - - - - - - - - -
-
- - -
-
-
-
+ + sdd.mandate.tree + account.banking.mandate + + + + + + + - - sdd.mandate.tree - sdd.mandate - - - - - - - - - - - - + + sdd.mandate.search + account.banking.mandate + + + + + + + - - sdd.mandate.search - sdd.mandate - - - - - - - - - - - - + + SEPA Direct Debit Mandates + account.banking.mandate + form + 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. +

+
+
- - SEPA Direct Debit Mandates - sdd.mandate - form - 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 + + + - - - Mandate Validated - sdd.mandate - - SEPA Direct Debit Mandate Validated - + + sdd.mandate.partner.form + res.partner + + + + SDD Mandates + + + - - Mandate Expired - sdd.mandate - - SEPA Direct Debit Mandate has Expired - + + Sequence Type set to First + account.banking.mandate + + Sequence Type set to First + - - Mandate Cancelled - sdd.mandate - - SEPA Direct Debit Mandate Cancelled - + + Sequence Type set to Recurring + account.banking.mandate + + Sequence Type set to Recurring + - - Sequence Type set to First - sdd.mandate - - Sequence Type set to First - - - - Sequence Type set to Recurring - sdd.mandate - - Sequence Type set to Recurring - - - - Sequence Type set to Final - sdd.mandate - - Sequence Type set to Final - + + Sequence Type set to Final + account.banking.mandate + + Sequence Type set to Final +
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 cf78ffb59..307bdfb65 100644 --- a/account_banking_sepa_direct_debit/security/ir.model.access.csv +++ b/account_banking_sepa_direct_debit/security/ir.model.access.csv @@ -1,4 +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 +"access_sdd_mandate","Full access on sdd.mandate","model_account_banking_mandate","account_payment.group_account_payment",1,1,1,1 +"access_sdd_mandate_read","Read access on sdd.mandate","model_account_banking_mandate","base.group_user",1,0,0,0 diff --git a/account_banking_sepa_direct_debit/sepa_direct_debit_demo.xml b/account_banking_sepa_direct_debit/sepa_direct_debit_demo.xml index 220261088..041082d76 100644 --- a/account_banking_sepa_direct_debit/sepa_direct_debit_demo.xml +++ b/account_banking_sepa_direct_debit/sepa_direct_debit_demo.xml @@ -15,7 +15,7 @@ FR78ZZZ424242
- + recurrent first diff --git a/account_banking_sepa_direct_debit/wizard/export_sdd.py b/account_banking_sepa_direct_debit/wizard/export_sdd.py index 19520d318..535a2e9a8 100644 --- a/account_banking_sepa_direct_debit/wizard/export_sdd.py +++ b/account_banking_sepa_direct_debit/wizard/export_sdd.py @@ -93,7 +93,7 @@ class banking_export_sdd_wizard(orm.TransientModel): previous_bank = False payline_ids = payline_obj.search( cr, uid, [ - ('sdd_mandate_id', '=', payline.sdd_mandate_id.id), + ('mandate_id', '=', payline.mandate_id.id), ('bank_id', '!=', payline.bank_id.id), ], context=context) @@ -188,22 +188,22 @@ class banking_export_sdd_wizard(orm.TransientModel): requested_date = payment_order.date_scheduled or today else: requested_date = today - if not line.sdd_mandate_id: + if not line.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': + if line.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 line.sdd_mandate_id.type == 'oneoff': - if not line.sdd_mandate_id.last_debit_date: + % (line.mandate_id.unique_mandate_reference, + line.mandate_id.partner_id.name)) + if line.mandate_id.type == 'oneoff': + if not line.mandate_id.last_debit_date: seq_type = 'OOFF' else: raise orm.except_orm( @@ -212,17 +212,17 @@ class banking_export_sdd_wizard(orm.TransientModel): "'%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': + % (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.sdd_mandate_id.recurrent_sequence_type + line.mandate_id.recurrent_sequence_type assert seq_type_label is not False seq_type = seq_type_map[seq_type_label] @@ -306,22 +306,22 @@ class banking_export_sdd_wizard(orm.TransientModel): mandate_related_info_2_47, 'MndtId') mandate_identification_2_48.text = self._prepare_field( cr, uid, 'Unique Mandate Reference', - 'line.sdd_mandate_id.unique_mandate_reference', + 'line.mandate_id.unique_mandate_reference', {'line': line}, 35, gen_args=gen_args, 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', + 'line.mandate_id.signature_date', {'line': line}, 10, gen_args=gen_args, context=context) if sequence_type == 'FRST' and ( - line.sdd_mandate_id.last_debit_date or - not line.sdd_mandate_id.sepa_migrated): + line.mandate_id.last_debit_date or + not line.mandate_id.sepa_migrated): previous_bank = self._get_previous_bank( cr, uid, line, context=context) - if previous_bank or not line.sdd_mandate_id.sepa_migrated: + 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' @@ -362,13 +362,13 @@ class banking_export_sdd_wizard(orm.TransientModel): ori_debtor_agent_other, 'Id') ori_debtor_agent_other_id.text = 'SMNDA' # SMNDA = Same Mandate New Debtor Agent - elif not line.sdd_mandate_id.sepa_migrated: + 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( cr, uid, 'Original Mandate Identification', - 'line.sdd_mandate_id.' + 'line.mandate_id.' 'original_mandate_identification', {'line': line}, gen_args=gen_args, @@ -425,25 +425,25 @@ 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, 'done', cr) - mandate_ids = [line.sdd_mandate_id.id for line in order.line_ids] - self.pool['sdd.mandate'].write( + mandate_ids = [line.mandate_id.id for line in order.line_ids] + self.pool['account.banking.mandate'].write( cr, uid, mandate_ids, {'last_debit_date': datetime.today().strftime('%Y-%m-%d')}, context=context) to_expire_ids = [] first_mandate_ids = [] for line in order.line_ids: - if line.sdd_mandate_id.type == 'oneoff': - to_expire_ids.append(line.sdd_mandate_id.id) - elif line.sdd_mandate_id.type == 'recurrent': - seq_type = line.sdd_mandate_id.recurrent_sequence_type + if line.mandate_id.type == 'oneoff': + to_expire_ids.append(line.mandate_id.id) + elif line.mandate_id.type == 'recurrent': + seq_type = line.mandate_id.recurrent_sequence_type if seq_type == 'final': - to_expire_ids.append(line.sdd_mandate_id.id) + to_expire_ids.append(line.mandate_id.id) elif seq_type == 'first': - first_mandate_ids.append(line.sdd_mandate_id.id) - self.pool['sdd.mandate'].write( + first_mandate_ids.append(line.mandate_id.id) + self.pool['account.banking.mandate'].write( cr, uid, to_expire_ids, {'state': 'expired'}, context=context) - self.pool['sdd.mandate'].write( + self.pool['account.banking.mandate'].write( cr, uid, first_mandate_ids, { 'recurrent_sequence_type': 'recurring', 'sepa_migrated': True, From b4672a34c402fbe79e527fd462b5710bff75d5a5 Mon Sep 17 00:00:00 2001 From: Cyril Sester Date: Fri, 15 Aug 2014 11:38:19 +0200 Subject: [PATCH 2/9] Add migration field from 0.1 to 0.2 --- .../__openerp__.py | 2 +- .../migrations/0.2/post-consolidation.py | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 account_banking_sepa_direct_debit/migrations/0.2/post-consolidation.py diff --git a/account_banking_sepa_direct_debit/__openerp__.py b/account_banking_sepa_direct_debit/__openerp__.py index 3d774a34d..30148fb1d 100644 --- a/account_banking_sepa_direct_debit/__openerp__.py +++ b/account_banking_sepa_direct_debit/__openerp__.py @@ -22,7 +22,7 @@ { 'name': 'Account Banking SEPA Direct Debit', 'summary': 'Create SEPA files for Direct Debit', - 'version': '0.1', + 'version': '0.2', 'license': 'AGPL-3', 'author': 'Akretion', 'website': 'http://www.akretion.com', diff --git a/account_banking_sepa_direct_debit/migrations/0.2/post-consolidation.py b/account_banking_sepa_direct_debit/migrations/0.2/post-consolidation.py new file mode 100644 index 000000000..f183f33cc --- /dev/null +++ b/account_banking_sepa_direct_debit/migrations/0.2/post-consolidation.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2014 Compassion CH () +# +# 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 . +# +############################################################################## + +""" +This script covers the migration of the 0.1 to 0.2 version of sepa mandates. +As table names changed, we want to migrate values from sdd_mandate table to +newly created account_banking_mandate. We also copy foreign keys in invoice +and payment lines. +Finally, we remove useless fields (sdd_mandate_id) and obsolete table +(sdd_mandate). +""" + +__name__ = "account.banking.sepa.direct_debit:: Move sdd_mandate data to account_banking_mandate" + +def migrate(cr, installed_version): + query = "INSERT INTO account_banking_mandate " \ + "SELECT id, create_uid, create_date, write_date, write_uid, " \ + "partner_bank_id, last_debit_date, scan, company_id, state, " \ + "unique_mandate_reference, signature_date, sepa_migrated, " \ + "original_mandate_identification, recurrent_sequence_type, type " \ + "FROM sdd_mandate" + cr.execute(query) + query2 = "UPDATE account_invoice SET mandate_id=sdd_mandate_id" + cr.execute(query2) + query3 = "UPDATE payment_line SET mandate_id=sdd_mandate_id" + cr.execute(query3) + query4 = "ALTER TABLE account_invoice DROP COLUMN IF EXISTS sdd_mandate_id" + cr.execute(query4) + query5 = "ALTER TABLE payment_line DROP COLUMN IF EXISTS sdd_mandate_id" + cr.execute(query5) + query6 = "DROP TABLE IF EXISTS sdd_mandate" + cr.execute(query6) From 4965c0c0f7ce8607ee20b2fe1f007764c532f212 Mon Sep 17 00:00:00 2001 From: Cyril Sester Date: Fri, 15 Aug 2014 11:44:24 +0200 Subject: [PATCH 3/9] PEP8 fixes on migration script --- .../migrations/0.2/post-consolidation.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/account_banking_sepa_direct_debit/migrations/0.2/post-consolidation.py b/account_banking_sepa_direct_debit/migrations/0.2/post-consolidation.py index f183f33cc..2e93953dd 100644 --- a/account_banking_sepa_direct_debit/migrations/0.2/post-consolidation.py +++ b/account_banking_sepa_direct_debit/migrations/0.2/post-consolidation.py @@ -4,8 +4,8 @@ # Copyright (C) 2014 Compassion CH () # # 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 +# 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, @@ -18,7 +18,7 @@ # ############################################################################## -""" +""" This script covers the migration of the 0.1 to 0.2 version of sepa mandates. As table names changed, we want to migrate values from sdd_mandate table to newly created account_banking_mandate. We also copy foreign keys in invoice @@ -27,7 +27,9 @@ Finally, we remove useless fields (sdd_mandate_id) and obsolete table (sdd_mandate). """ -__name__ = "account.banking.sepa.direct_debit:: Move sdd_mandate data to account_banking_mandate" +__name__ = "account.banking.sepa.direct_debit:: Move sdd_mandate data to " \ + "account_banking_mandate" + def migrate(cr, installed_version): query = "INSERT INTO account_banking_mandate " \ From 580bad6df0f2c5811f39cff8ca9bd93474b3b3c0 Mon Sep 17 00:00:00 2001 From: Cyril Sester Date: Mon, 15 Sep 2014 15:28:11 +0200 Subject: [PATCH 4/9] [ADD] Access rights on account.banking.mandate --- account_banking_mandate/__openerp__.py | 1 + account_banking_mandate/security/ir.model.access.csv | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 account_banking_mandate/security/ir.model.access.csv diff --git a/account_banking_mandate/__openerp__.py b/account_banking_mandate/__openerp__.py index e57018c96..d87d73648 100644 --- a/account_banking_mandate/__openerp__.py +++ b/account_banking_mandate/__openerp__.py @@ -36,6 +36,7 @@ 'view/account_payment_view.xml', 'view/res_partner_bank_view.xml', 'data/mandate_reference_sequence.xml', + 'security/ir.model.access.csv', ], 'demo': [], 'description': ''' diff --git a/account_banking_mandate/security/ir.model.access.csv b/account_banking_mandate/security/ir.model.access.csv new file mode 100644 index 000000000..37e8c8f83 --- /dev/null +++ b/account_banking_mandate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +"access_account_banking_mandate","Full access on account.banking.mandate","model_account_banking_mandate","account_payment.group_account_payment",1,1,1,1 +"access_account_banking_mandate_read","Read access on account.banking.mandate","model_account_banking_mandate","base.group_user",1,0,0,0 \ No newline at end of file From c6c15915f72067f95d8c3e0373d8c4abbb7d21d3 Mon Sep 17 00:00:00 2001 From: Cyril Sester Date: Mon, 15 Sep 2014 15:42:37 +0200 Subject: [PATCH 5/9] [FIX] Syntax for security file --- account_banking_mandate/security/ir.model.access.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/account_banking_mandate/security/ir.model.access.csv b/account_banking_mandate/security/ir.model.access.csv index 37e8c8f83..f89130b3f 100644 --- a/account_banking_mandate/security/ir.model.access.csv +++ b/account_banking_mandate/security/ir.model.access.csv @@ -1,2 +1,3 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" "access_account_banking_mandate","Full access on account.banking.mandate","model_account_banking_mandate","account_payment.group_account_payment",1,1,1,1 "access_account_banking_mandate_read","Read access on account.banking.mandate","model_account_banking_mandate","base.group_user",1,0,0,0 \ No newline at end of file From 6273cf300b7552e97d0f77f349f516ac10d7cd68 Mon Sep 17 00:00:00 2001 From: Cyril Sester Date: Thu, 18 Sep 2014 09:49:16 +0200 Subject: [PATCH 6/9] Add falsy version check in migration file and removed useless security lines --- .../__openerp__.py | 18 ------------------ .../migrations/0.2/post-consolidation.py | 3 +++ .../security/ir.model.access.csv | 2 -- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/account_banking_sepa_direct_debit/__openerp__.py b/account_banking_sepa_direct_debit/__openerp__.py index 30148fb1d..2f12e4d1d 100644 --- a/account_banking_sepa_direct_debit/__openerp__.py +++ b/account_banking_sepa_direct_debit/__openerp__.py @@ -51,7 +51,6 @@ Module to export direct debit payment orders in SEPA XML file format. SEPA PAIN (PAyment INitiation) is the new european standard for -<<<<<<< HEAD Customer-to-Bank payment instructions. This module implements SEPA Direct Debit (SDD), more specifically PAIN @@ -68,23 +67,6 @@ cf https://www.github.com/OCA/banking-addons Please contact Alexis de Lattre from Akretion for any help or question about this module. -======= - Customer-to-Bank payment instructions. This module implements SEPA Direct - Debit (SDD), more specifically PAIN versions 008.001.02, 008.001.03 and - 008.001.04. It is part of the ISO 20022 standard, available on - http://www.iso20022.org. - -The Implementation Guidelines for SEPA Direct Debit published by the European - Payments Council (http://http://www.europeanpaymentscouncil.eu) use PAIN - version 008.001.02. So if you don't know which version your bank supports, - you should try version 008.001.02 first. - -This module uses the framework provided by the banking addons, - cf https://launchpad.net/banking-addons - -Please contact Alexis de Lattre from Akretion - for any help or question about this module. ->>>>>>> Refactoring to get a generic mandate ''', 'active': False, 'installable': True, diff --git a/account_banking_sepa_direct_debit/migrations/0.2/post-consolidation.py b/account_banking_sepa_direct_debit/migrations/0.2/post-consolidation.py index 2e93953dd..b761728d3 100644 --- a/account_banking_sepa_direct_debit/migrations/0.2/post-consolidation.py +++ b/account_banking_sepa_direct_debit/migrations/0.2/post-consolidation.py @@ -32,6 +32,9 @@ __name__ = "account.banking.sepa.direct_debit:: Move sdd_mandate data to " \ def migrate(cr, installed_version): + if not installed_version: + return + query = "INSERT INTO account_banking_mandate " \ "SELECT id, create_uid, create_date, write_date, write_uid, " \ "partner_bank_id, last_debit_date, scan, company_id, state, " \ 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 307bdfb65..0cd579511 100644 --- a/account_banking_sepa_direct_debit/security/ir.model.access.csv +++ b/account_banking_sepa_direct_debit/security/ir.model.access.csv @@ -1,4 +1,2 @@ "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_account_banking_mandate","account_payment.group_account_payment",1,1,1,1 -"access_sdd_mandate_read","Read access on sdd.mandate","model_account_banking_mandate","base.group_user",1,0,0,0 From 3a2eba3b5e72ed1f7fe3222c78dd0cc14eb47994 Mon Sep 17 00:00:00 2001 From: Cyril Sester Date: Fri, 19 Sep 2014 14:56:40 +0200 Subject: [PATCH 7/9] Transformed asserts to exceptions, improved docstrings, add mandate tooltip help --- account_banking_mandate/__openerp__.py | 10 ++++++-- .../model/account_banking_mandate.py | 24 ++++++++++++------- account_banking_mandate/model/partner_bank.py | 7 +++++- .../view/account_banking_mandate_view.xml | 2 +- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/account_banking_mandate/__openerp__.py b/account_banking_mandate/__openerp__.py index d87d73648..4553a0b01 100644 --- a/account_banking_mandate/__openerp__.py +++ b/account_banking_mandate/__openerp__.py @@ -41,8 +41,14 @@ 'demo': [], 'description': ''' This module adds a generic model for banking mandates. - These mandates can be specialized to fit any banking mandates (such as sepa - or lsv). + These mandates can be specialized to fit any banking mandates (such as + sepa or lsv). + + A banking mandate is attached to a bank account and represents an + authorization that the bank account owner gives to a company for a + specific operation (such as direct debit). + You can setup mandates from the accounting menu or directly from a bank + account. ''', 'active': False, 'installable': True, diff --git a/account_banking_mandate/model/account_banking_mandate.py b/account_banking_mandate/model/account_banking_mandate.py index ae4354fe0..a13b1bc69 100644 --- a/account_banking_mandate/model/account_banking_mandate.py +++ b/account_banking_mandate/model/account_banking_mandate.py @@ -21,7 +21,6 @@ # ############################################################################## -from datetime import datetime from openerp.osv import orm, fields from openerp.tools.translate import _ @@ -106,7 +105,7 @@ class mandate(orm.Model): for mandate in self.browse(cr, uid, ids): if (mandate.signature_date and mandate.signature_date > - datetime.today().strftime('%Y-%m-%d')): + fields.date.context_today(self, cr, uid)): raise orm.except_orm( _('Error:'), _("The date of signature of mandate '%s' is in the " @@ -151,14 +150,15 @@ class mandate(orm.Model): if partner_bank_id: partner_bank_read = self.pool['res.partner.bank'].read( cr, uid, partner_bank_id, ['partner_id'])['partner_id'] - if partner_bank_read: - res['value']['partner_id'] = partner_bank_read[0] + res['value']['partner_id'] = partner_bank_read[0] return res def validate(self, cr, uid, ids, context=None): to_validate_ids = [] for mandate in self.browse(cr, uid, ids, context=context): - assert mandate.state == 'draft', 'Mandate should be in draft state' + if mandate.state != 'draft': + raise orm.except_orm('StateError', + _('Mandate should be in draft state')) to_validate_ids.append(mandate.id) self.write( cr, uid, to_validate_ids, {'state': 'valid'}, context=context) @@ -167,18 +167,24 @@ class mandate(orm.Model): def cancel(self, cr, uid, ids, context=None): to_cancel_ids = [] for mandate in self.browse(cr, uid, ids, context=context): - assert mandate.state in ('draft', 'valid'),\ - 'Mandate should be in draft or valid state' + if mandate.state not in ('draft', 'valid'): + raise orm.except_orm('StateError', + _('Mandate should be in draft or valid ' + 'state')) to_cancel_ids.append(mandate.id) self.write( cr, uid, to_cancel_ids, {'state': 'cancel'}, context=context) return True def back2draft(self, cr, uid, ids, context=None): + ''' Allows to set the mandate back to the draft state. + This is for mandates cancelled by mistake + ''' to_draft_ids = [] for mandate in self.browse(cr, uid, ids, context=context): - assert mandate.state == 'cancel',\ - 'Mandate should be in cancel state' + if mandate.state != 'cancel': + raise orm.except_orm('StateError', + _('Mandate should be in cancel state')) to_draft_ids.append(mandate.id) self.write( cr, uid, to_draft_ids, {'state': 'draft'}, context=context) diff --git a/account_banking_mandate/model/partner_bank.py b/account_banking_mandate/model/partner_bank.py index 4043c02d0..8fdd4287a 100644 --- a/account_banking_mandate/model/partner_bank.py +++ b/account_banking_mandate/model/partner_bank.py @@ -22,6 +22,7 @@ ############################################################################## from openerp.osv import orm, fields +from openerp.tools.translate import _ class res_partner_bank(orm.Model): @@ -29,5 +30,9 @@ class res_partner_bank(orm.Model): _columns = { 'mandate_ids': fields.one2many( - 'account.banking.mandate', 'partner_bank_id', 'Banking Mandates'), + 'account.banking.mandate', 'partner_bank_id', + _('Banking Mandates'), + help=_('Banking mandates represents an authorization that the ' + 'bank account owner gives to a company for a specific ' + 'operation')), } diff --git a/account_banking_mandate/view/account_banking_mandate_view.xml b/account_banking_mandate/view/account_banking_mandate_view.xml index 3d0cfd73f..bd93eb127 100644 --- a/account_banking_mandate/view/account_banking_mandate_view.xml +++ b/account_banking_mandate/view/account_banking_mandate_view.xml @@ -119,4 +119,4 @@ Banking Mandate Cancelled
-
\ No newline at end of file +
From 7f4693d4abbd3521b192d048c11e46ac912dca4e Mon Sep 17 00:00:00 2001 From: Cyril Sester Date: Tue, 23 Sep 2014 12:30:13 +0200 Subject: [PATCH 8/9] [ADD] mandate tests --- account_banking_mandate/__openerp__.py | 1 + .../test/banking_mandate.yml | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 account_banking_mandate/test/banking_mandate.yml diff --git a/account_banking_mandate/__openerp__.py b/account_banking_mandate/__openerp__.py index 4553a0b01..c8272d1a0 100644 --- a/account_banking_mandate/__openerp__.py +++ b/account_banking_mandate/__openerp__.py @@ -39,6 +39,7 @@ 'security/ir.model.access.csv', ], 'demo': [], + 'test': ['test/banking_mandate.yml'], 'description': ''' This module adds a generic model for banking mandates. These mandates can be specialized to fit any banking mandates (such as diff --git a/account_banking_mandate/test/banking_mandate.yml b/account_banking_mandate/test/banking_mandate.yml new file mode 100644 index 000000000..d7291cc99 --- /dev/null +++ b/account_banking_mandate/test/banking_mandate.yml @@ -0,0 +1,36 @@ +- + In order to test mandate, I create a partner with a bank account. + Then, I create a mandate, validate it, cancel it and the set it back to draft + + I create a partner +- + !record {model: res.partner, id: mandate_partner, view: False}: + name: "Mandate test" +- + I create a partner bank account +- + !record {model: res.partner.bank, id: mandate_partner_bank, view: False}: + state: 'bank' + acc_number: '1234' + partner_id: mandate_partner +- + I create a mandate on 1st January +- + !record {model: account.banking.mandate, id: test_mandate, view: False}: + partner_bank_id: mandate_partner_bank + signature_date: "2014-01-01" + +- + I check that the state field is automatically set by default +- + !assert {model: account.banking.mandate, id: test_mandate}: + - state == 'draft' +- + I go through all states by clicking on buttons and check that cancel state is reached +- + !python {model: account.banking.mandate}: | + self.validate(cr, uid, [ref('test_mandate')]) + self.cancel(cr, uid, [ref('test_mandate')]) + mandate = self.browse(cr, uid, ref('test_mandate')) + assert mandate.state == 'cancel', 'Mandate is not in cancel state' + self.back2draft(cr, uid, [ref('test_mandate')]) From 5dd1ab59b88259ab44ecf76eaadca883188f5bdf Mon Sep 17 00:00:00 2001 From: Cyril Sester Date: Tue, 23 Sep 2014 13:55:55 +0200 Subject: [PATCH 9/9] [FIX] generic mandate and sepa mandate reference conflict --- account_banking_mandate/data/mandate_reference_sequence.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account_banking_mandate/data/mandate_reference_sequence.xml b/account_banking_mandate/data/mandate_reference_sequence.xml index f24d823d6..6bfb55096 100644 --- a/account_banking_mandate/data/mandate_reference_sequence.xml +++ b/account_banking_mandate/data/mandate_reference_sequence.xml @@ -11,7 +11,7 @@ DD Mandate Reference account.banking.mandate - RUM + BM