From ec23ee2291dc8f4285c1cdd29d5792bc239194b7 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Wed, 10 Sep 2014 12:19:20 +0200 Subject: [PATCH] [MIG] account_banking_sepa_direct_debit: Migration to 8.0 --- account_banking_sepa_direct_debit/__init__.py | 3 +- .../__openerp__.py | 50 +- .../account_banking_sdd.py | 440 ------------------ .../account_invoice_view.xml | 22 - .../account_payment_view.xml | 26 -- .../data/mandate_expire_cron.xml | 24 + .../data/mandate_reference_sequence.xml | 21 - .../{ => demo}/sepa_direct_debit_demo.xml | 2 +- .../mandate_expire_cron.xml | 26 -- .../models/__init__.py | 25 + .../models/account_banking_mandate.py | 145 ++++++ .../models/banking_export_sdd.py | 82 ++++ .../{company.py => models/res_company.py} | 56 +-- .../res_partner_bank_view.xml | 48 -- .../sdd_mandate_view.xml | 152 ------ .../security/ir.model.access.csv | 2 - .../static/description/icon.png | Bin 0 -> 7840 bytes .../static/description/icon.svg | 92 ++++ .../static/src/img/icon.png | Bin 6892 -> 0 bytes .../views/account_banking_mandate_view.xml | 117 +++++ .../{ => views}/account_banking_sdd_view.xml | 0 .../res_company_view.xml} | 0 .../wizard/export_sdd.py | 13 +- 23 files changed, 538 insertions(+), 808 deletions(-) delete mode 100644 account_banking_sepa_direct_debit/account_banking_sdd.py delete mode 100644 account_banking_sepa_direct_debit/account_invoice_view.xml delete mode 100644 account_banking_sepa_direct_debit/account_payment_view.xml create mode 100644 account_banking_sepa_direct_debit/data/mandate_expire_cron.xml delete mode 100644 account_banking_sepa_direct_debit/data/mandate_reference_sequence.xml rename account_banking_sepa_direct_debit/{ => demo}/sepa_direct_debit_demo.xml (92%) delete mode 100644 account_banking_sepa_direct_debit/mandate_expire_cron.xml create mode 100644 account_banking_sepa_direct_debit/models/__init__.py create mode 100644 account_banking_sepa_direct_debit/models/account_banking_mandate.py create mode 100644 account_banking_sepa_direct_debit/models/banking_export_sdd.py rename account_banking_sepa_direct_debit/{company.py => models/res_company.py} (60%) delete mode 100644 account_banking_sepa_direct_debit/res_partner_bank_view.xml delete mode 100644 account_banking_sepa_direct_debit/sdd_mandate_view.xml create mode 100644 account_banking_sepa_direct_debit/static/description/icon.png create mode 100644 account_banking_sepa_direct_debit/static/description/icon.svg delete mode 100644 account_banking_sepa_direct_debit/static/src/img/icon.png create mode 100644 account_banking_sepa_direct_debit/views/account_banking_mandate_view.xml rename account_banking_sepa_direct_debit/{ => views}/account_banking_sdd_view.xml (100%) rename account_banking_sepa_direct_debit/{company_view.xml => views/res_company_view.xml} (100%) diff --git a/account_banking_sepa_direct_debit/__init__.py b/account_banking_sepa_direct_debit/__init__.py index f852fb7bd..096fe8ad3 100644 --- a/account_banking_sepa_direct_debit/__init__.py +++ b/account_banking_sepa_direct_debit/__init__.py @@ -20,6 +20,5 @@ # ############################################################################## -from . import company +from . import models from . import wizard -from . import account_banking_sdd diff --git a/account_banking_sepa_direct_debit/__openerp__.py b/account_banking_sepa_direct_debit/__openerp__.py index 5ea5dadb4..6a95f0255 100644 --- a/account_banking_sepa_direct_debit/__openerp__.py +++ b/account_banking_sepa_direct_debit/__openerp__.py @@ -22,51 +22,45 @@ { 'name': 'Account Banking SEPA Direct Debit', 'summary': 'Create SEPA files for Direct Debit', - 'version': '0.1', + 'version': '8.0.0.2.0', 'license': 'AGPL-3', - 'author': 'Akretion', - 'website': 'http://www.akretion.com', + 'author': "Akretion, " + "Serv. Tecnol. Avanzados - Pedro M. Baeza, " + "Odoo Community Association (OCA)", + 'website': 'https://github.com/OCA/bank-payment', 'category': 'Banking addons', - 'depends': ['account_direct_debit', 'account_banking_pain_base'], + 'depends': [ + 'account_direct_debit', + 'account_banking_pain_base', + 'account_banking_mandate', + ], 'external_dependencies': { 'python': ['unidecode', 'lxml'], }, 'data': [ - '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', + 'views/account_banking_sdd_view.xml', + 'views/account_banking_mandate_view.xml', + 'views/res_company_view.xml', 'wizard/export_sdd_view.xml', + 'data/mandate_expire_cron.xml', 'data/payment_type_sdd.xml', - 'data/mandate_reference_sequence.xml', + 'security/original_mandate_required_security.xml', 'security/ir.model.access.csv', ], - 'demo': ['sepa_direct_debit_demo.xml'], + 'demo': ['demo/sepa_direct_debit_demo.xml'], 'description': ''' Module to export direct debit payment orders in SEPA XML file format. SEPA PAIN (PAyment INitiation) is the new european standard for -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. +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://www.github.com/OCA/banking-addons - -Please contact Alexis de Lattre from Akretion -for any help or question about this module. +version 008.001.02. So if you don't know which version your bank supports, you +should try version 008.001.02 first. ''', - '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 deleted file mode 100644 index 87e50111b..000000000 --- a/account_banking_sepa_direct_debit/account_banking_sdd.py +++ /dev/null @@ -1,440 +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 openerp.osv import orm, fields -from openerp.tools.translate import _ -from openerp.addons.decimal_precision import decimal_precision as dp -from unidecode import unidecode -from datetime import datetime -from dateutil.relativedelta import relativedelta -import logging - -NUMBER_OF_UNUSED_MONTHS_BEFORE_EXPIRY = 36 - -logger = logging.getLogger(__name__) - - -class banking_export_sdd(orm.Model): - '''SEPA Direct Debit export''' - _name = 'banking.export.sdd' - _description = __doc__ - _rec_name = 'filename' - - def _generate_filename(self, cr, uid, ids, name, arg, context=None): - res = {} - for sepa_file in self.browse(cr, uid, ids, context=context): - ref = sepa_file.payment_order_ids[0].reference - if ref: - label = unidecode(ref.replace('/', '-')) - else: - label = 'error' - res[sepa_file.id] = 'sdd_%s.xml' % label - return res - - _columns = { - 'payment_order_ids': fields.many2many( - 'payment.order', - 'account_payment_order_sdd_rel', - 'banking_export_sepa_id', 'account_order_id', - 'Payment Orders', - readonly=True), - 'nb_transactions': fields.integer( - 'Number of Transactions', readonly=True), - 'total_amount': fields.float( - 'Total Amount', digits_compute=dp.get_precision('Account'), - readonly=True), - 'batch_booking': fields.boolean( - 'Batch Booking', readonly=True, - 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'), - ], 'Charge Bearer', readonly=True, - 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."), - 'create_date': fields.datetime('Generation Date', readonly=True), - 'file': fields.binary('SEPA File', readonly=True), - 'filename': fields.function( - _generate_filename, type='char', size=256, - string='Filename', readonly=True, store=True), - 'state': fields.selection([ - ('draft', 'Draft'), - ('sent', 'Sent'), - ], 'State', readonly=True), - } - - _defaults = { - 'state': 'draft', - } - - -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' - _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: - 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', - } - } - - _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'), - ], 'Type of Mandate', required=True, track_visibility='always'), - 'recurrent_sequence_type': fields.selection([ - ('first', 'First'), - ('recurring', 'Recurring'), - ('final', 'Final'), - ], '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 " - "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."), - 'original_mandate_identification': fields.char( - 'Original Mandate Identification', size=35, - track_visibility='onchange', - 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."), - } - - _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( - _('Error:'), - _("The recurrent mandate '%s' must have a sequence type.") - % mandate.unique_mandate_reference) - if (mandate.type == 'recurrent' and not mandate.sepa_migrated - and mandate.recurrent_sequence_type != 'first'): - raise orm.except_orm( - _('Error:'), - _("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) - if (mandate.type == 'recurrent' and not mandate.sepa_migrated - and not mandate.original_mandate_identification): - raise orm.except_orm( - _('Error:'), - _("You must set the 'Original Mandate Identification' " - "on the recurrent mandate '%s' which is not marked " - "as 'Migrated to SEPA'.") - % mandate.unique_mandate_reference) - return True - - _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 mandate_type_change(self, cr, uid, ids, type): - if type == 'recurrent': - recurrent_sequence_type = 'first' - else: - recurrent_sequence_type = False - res = {'value': {'recurrent_sequence_type': recurrent_sequence_type}} - return res - - 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] - if (state == 'valid' and partner_bank_id - and type == 'recurrent' - and recurrent_sequence_type != 'first'): - res['value']['recurrent_sequence_type'] = 'first' - res['warning'] = { - 'title': _('Mandate update'), - 'message': _( - "As you changed the bank account attached to this " - "mandate, the 'Sequence Type' has been set back to " - "'First'."), - } - 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() + \ - relativedelta(months=-NUMBER_OF_UNUSED_MONTHS_BEFORE_EXPIRY) - expire_limit_date_str = expire_limit_date.strftime('%Y-%m-%d') - expired_mandate_ids = self.search(cr, uid, [ - '|', - ('last_debit_date', '=', False), - ('last_debit_date', '<=', expire_limit_date_str), - ('state', '=', 'valid'), - ('signature_date', '<=', expire_limit_date_str), - ], context=context) - if expired_mandate_ids: - self.write( - cr, uid, expired_mandate_ids, {'state': 'expired'}, - context=context) - logger.info( - 'The following SDD Mandate IDs has been set to expired: %s' - % expired_mandate_ids) - 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/account_invoice_view.xml b/account_banking_sepa_direct_debit/account_invoice_view.xml deleted file mode 100644 index 2ca480e54..000000000 --- a/account_banking_sepa_direct_debit/account_invoice_view.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - add.sdd.mandate.on.customer.invoice.form - account.invoice - - - - - - - - - - diff --git a/account_banking_sepa_direct_debit/account_payment_view.xml b/account_banking_sepa_direct_debit/account_payment_view.xml deleted file mode 100644 index 74098c44e..000000000 --- a/account_banking_sepa_direct_debit/account_payment_view.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - sdd.payment.order.form - payment.order - - - - - - - - - - - - - - diff --git a/account_banking_sepa_direct_debit/data/mandate_expire_cron.xml b/account_banking_sepa_direct_debit/data/mandate_expire_cron.xml new file mode 100644 index 000000000..79aa05689 --- /dev/null +++ b/account_banking_sepa_direct_debit/data/mandate_expire_cron.xml @@ -0,0 +1,24 @@ + + + + + + + + Set SEPA Direct Debit Mandates to Expired + + + 1 + days + -1 + + + + + + + diff --git a/account_banking_sepa_direct_debit/data/mandate_reference_sequence.xml b/account_banking_sepa_direct_debit/data/mandate_reference_sequence.xml deleted file mode 100644 index 68075d526..000000000 --- a/account_banking_sepa_direct_debit/data/mandate_reference_sequence.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - SDD Mandate Reference - sdd.mandate.reference - - - - SDD Mandate Reference - sdd.mandate.reference - RUM - - - - - - - diff --git a/account_banking_sepa_direct_debit/sepa_direct_debit_demo.xml b/account_banking_sepa_direct_debit/demo/sepa_direct_debit_demo.xml similarity index 92% rename from account_banking_sepa_direct_debit/sepa_direct_debit_demo.xml rename to account_banking_sepa_direct_debit/demo/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/demo/sepa_direct_debit_demo.xml @@ -15,7 +15,7 @@ FR78ZZZ424242 - + recurrent first diff --git a/account_banking_sepa_direct_debit/mandate_expire_cron.xml b/account_banking_sepa_direct_debit/mandate_expire_cron.xml deleted file mode 100644 index 4cb0693d2..000000000 --- a/account_banking_sepa_direct_debit/mandate_expire_cron.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - Set SEPA Direct Debit Mandates to Expired - - - 1 - days - -1 - - - - - - - - diff --git a/account_banking_sepa_direct_debit/models/__init__.py b/account_banking_sepa_direct_debit/models/__init__.py new file mode 100644 index 000000000..274855e14 --- /dev/null +++ b/account_banking_sepa_direct_debit/models/__init__.py @@ -0,0 +1,25 @@ +# -*- 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 banking_export_sdd +from . import res_company +from . import account_banking_mandate diff --git a/account_banking_sepa_direct_debit/models/account_banking_mandate.py b/account_banking_sepa_direct_debit/models/account_banking_mandate.py new file mode 100644 index 000000000..4df706378 --- /dev/null +++ b/account_banking_sepa_direct_debit/models/account_banking_mandate.py @@ -0,0 +1,145 @@ +# -*- 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 openerp import models, fields, api, exceptions, _ +from datetime import datetime +from dateutil.relativedelta import relativedelta +import logging + +NUMBER_OF_UNUSED_MONTHS_BEFORE_EXPIRY = 36 + +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', + } + } + + type = fields.Selection([('recurrent', 'Recurrent'), + ('oneoff', 'One-Off')], + string='Type of Mandate', required=True, + track_visibility='always') + recurrent_sequence_type = fields.Selection( + [('first', 'First'), + ('recurring', 'Recurring'), + ('final', 'Final')], + 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', + 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', required=True, default="CORE") + + @api.one + @api.constrains('type', 'recurrent_sequence_type') + def _check_recurring_type(self): + if (self.type == 'recurrent' + and not self.recurrent_sequence_type): + raise exceptions.Warning( + _("The recurrent mandate '%s' must have a sequence type.") + % self.unique_mandate_reference) + + @api.one + @api.constrains('type', 'recurrent_sequence_type', 'sepa_migrated') + def _check_migrated_to_sepa(self): + if (self.type == 'recurrent' and not self.sepa_migrated + and self.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'.") % self.unique_mandate_reference) + + @api.one + @api.constrains('type', 'original_mandate_identification', 'sepa_migrated') + def _check_original_mandate_identification(self): + if (self.type == 'recurrent' and not self.sepa_migrated + and not self.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'.") % self.unique_mandate_reference) + + @api.one + @api.onchange('partner_bank_id') + def mandate_partner_bank_change(self): + super(AccountBankingMandate, self).mandate_partner_bank_change() + res = {} + if (self.state == 'valid' and self.partner_bank_id + and self.type == 'recurrent' + and self.recurrent_sequence_type != 'first'): + self.recurrent_sequence_type = 'first' + res['warning'] = { + 'title': _('Mandate update'), + 'message': _("As you changed the bank account attached to " + "this mandate, the 'Sequence Type' has been set " + "back to 'First'."), + } + return res + + @api.multi + def _sdd_mandate_set_state_to_expired(self): + logger.info('Searching for SDD Mandates that must be set to Expired') + expire_limit_date = datetime.today() + \ + relativedelta(months=-NUMBER_OF_UNUSED_MONTHS_BEFORE_EXPIRY) + expire_limit_date_str = expire_limit_date.strftime('%Y-%m-%d') + expired_mandates = self.search( + ['|', + ('last_debit_date', '=', False), + ('last_debit_date', '<=', expire_limit_date_str), + ('state', '=', 'valid'), + ('signature_date', '<=', expire_limit_date_str)]) + if expired_mandates: + expired_mandates.write({'state': 'expired'}) + logger.info( + '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') + return True diff --git a/account_banking_sepa_direct_debit/models/banking_export_sdd.py b/account_banking_sepa_direct_debit/models/banking_export_sdd.py new file mode 100644 index 000000000..80536aa03 --- /dev/null +++ b/account_banking_sepa_direct_debit/models/banking_export_sdd.py @@ -0,0 +1,82 @@ +# -*- 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 openerp import models, fields, api +from openerp.addons.decimal_precision import decimal_precision as dp + +try: + from unidecode import unidecode +except ImportError: + unidecode = None + + +class BankingExportSdd(models.Model): + """SEPA Direct Debit export""" + _name = 'banking.export.sdd' + _description = __doc__ + _rec_name = 'filename' + + @api.one + def _generate_filename(self): + filename = '' + if self.payment_order_ids: + ref = self.payment_order_ids[0].reference + label = unidecode(ref.replace('/', '-')) if ref else 'error' + filename = 'sdd_%s.xml' % label + self.filename = filename + + payment_order_ids = fields.Many2many( + comodel_name='payment.order', + relation='account_payment_order_sdd_rel', + column1='banking_export_sepa_id', column2='account_order_id', + string='Payment Orders', + readonly=True) + nb_transactions = fields.Integer( + string='Number of Transactions', readonly=True) + total_amount = fields.Float( + string='Total Amount', digits_compute=dp.get_precision('Account'), + readonly=True) + batch_booking = fields.Boolean( + 'Batch Booking', readonly=True, + 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')], 'Charge Bearer', readonly=True, + 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.") + create_date = fields.Datetime(string='Generation Date', readonly=True) + file = fields.Binary(string='SEPA File', readonly=True) + filename = fields.Char(compute=_generate_filename, size=256, + string='Filename', readonly=True, store=True) + state = fields.Selection([('draft', 'Draft'), ('sent', 'Sent')], + string='State', readonly=True, default='draft') diff --git a/account_banking_sepa_direct_debit/company.py b/account_banking_sepa_direct_debit/models/res_company.py similarity index 60% rename from account_banking_sepa_direct_debit/company.py rename to account_banking_sepa_direct_debit/models/res_company.py index 6d54ba8e6..327edc401 100644 --- a/account_banking_sepa_direct_debit/company.py +++ b/account_banking_sepa_direct_debit/models/res_company.py @@ -20,29 +20,27 @@ # ############################################################################## -from openerp.osv import orm, fields +from openerp import models, fields, api, exceptions, _ import logging logger = logging.getLogger(__name__) -class res_company(orm.Model): +class ResCompany(models.Model): _inherit = 'res.company' - _columns = { - 'sepa_creditor_identifier': fields.char( - 'SEPA Creditor Identifier', size=35, - help="Enter the Creditor Identifier that has been attributed " - "to your company to make SEPA Direct Debits. This identifier " - "is composed of :\n- your country ISO code (2 letters)\n- a " - "2-digits checkum\n- a 3-letters business code\n- a " - "country-specific identifier"), - 'original_creditor_identifier': fields.char( - 'Original Creditor Identifier', size=70), - } + sepa_creditor_identifier = fields.Char( + string='SEPA Creditor Identifier', size=35, + help="Enter the Creditor Identifier that has been attributed to your " + "company to make SEPA Direct Debits. This identifier is composed " + "of :\n- your country ISO code (2 letters)\n- a 2-digits " + "checkum\n- a 3-letters business code\n- a country-specific " + "identifier") + original_creditor_identifier = fields.Char( + string='Original Creditor Identifier', size=70) def is_sepa_creditor_identifier_valid( - self, cr, uid, sepa_creditor_identifier, context=None): + self, sepa_creditor_identifier): """Check if SEPA Creditor Identifier is valid @param sepa_creditor_identifier: SEPA Creditor Identifier as str or unicode @@ -51,12 +49,11 @@ class res_company(orm.Model): if not isinstance(sepa_creditor_identifier, (str, unicode)): return False try: - sci_str = str(sepa_creditor_identifier) + sci = str(sepa_creditor_identifier).lower() except: logger.warning( "SEPA Creditor ID should contain only ASCII caracters.") return False - sci = sci_str.lower() if len(sci) < 9: return False before_replacement = sci[7:] + sci[0:2] + '00' @@ -70,21 +67,14 @@ class res_company(orm.Model): after_replacement += char logger.debug( "SEPA ID check after_replacement = %s" % after_replacement) - if int(sci[2:4]) == (98 - (int(after_replacement) % 97)): - return True - else: - return False + return int(sci[2:4]) == (98 - (int(after_replacement) % 97)) - def _check_sepa_creditor_identifier(self, cr, uid, ids): - for company in self.browse(cr, uid, ids): - if company.sepa_creditor_identifier: - if not self.is_sepa_creditor_identifier_valid( - cr, uid, company.sepa_creditor_identifier): - return False - return True - - _constraints = [ - (_check_sepa_creditor_identifier, - "Invalid SEPA Creditor Identifier.", - ['sepa_creditor_identifier']), - ] + @api.one + @api.constrains('sepa_creditor_identifier') + def _check_sepa_creditor_identifier(self): + if self.sepa_creditor_identifier: + if not self.is_sepa_creditor_identifier_valid( + self.sepa_creditor_identifier): + raise exceptions.Warning( + _('Error'), + _("Invalid SEPA Creditor Identifier.")) diff --git a/account_banking_sepa_direct_debit/res_partner_bank_view.xml b/account_banking_sepa_direct_debit/res_partner_bank_view.xml deleted file mode 100644 index 0b32e9f1c..000000000 --- a/account_banking_sepa_direct_debit/res_partner_bank_view.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - sdd.mandate.res.partner.bank.form - res.partner.bank - - - - - - - - - - - - sdd.mandate.res.partner.bank.tree - res.partner.bank - - - - - - - - - - - sdd.mandate.partner.form - res.partner - - - - - - - - - - diff --git a/account_banking_sepa_direct_debit/sdd_mandate_view.xml b/account_banking_sepa_direct_debit/sdd_mandate_view.xml deleted file mode 100644 index bd1dd6e79..000000000 --- a/account_banking_sepa_direct_debit/sdd_mandate_view.xml +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - sdd.mandate.form - sdd.mandate - -
-
-
- -
-

- -

-
- - - - - - - - - - - - - - - -
-
- - -
-
-
-
- - - sdd.mandate.tree - sdd.mandate - - - - - - - - - - - - - - - sdd.mandate.search - sdd.mandate - - - - - - - - - - - - - - - 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. -

-
-
- - - - - - Mandate Validated - sdd.mandate - - SEPA Direct Debit Mandate Validated - - - - Mandate Expired - sdd.mandate - - SEPA Direct Debit Mandate has Expired - - - - Mandate Cancelled - sdd.mandate - - SEPA Direct Debit Mandate Cancelled - - - - 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 - - -
-
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..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_sdd_mandate","account_payment.group_account_payment",1,1,1,1 -"access_sdd_mandate_read","Read access on sdd.mandate","model_sdd_mandate","base.group_user",1,0,0,0 diff --git a/account_banking_sepa_direct_debit/static/description/icon.png b/account_banking_sepa_direct_debit/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dca4d23f2d85309ed0527e32e30be6ceb09fd5db GIT binary patch literal 7840 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 literal 0 HcmV?d00001 diff --git a/account_banking_sepa_direct_debit/static/description/icon.svg b/account_banking_sepa_direct_debit/static/description/icon.svg new file mode 100644 index 000000000..c3bc242a4 --- /dev/null +++ b/account_banking_sepa_direct_debit/static/description/icon.svg @@ -0,0 +1,92 @@ + + + +image/svg+xmlDIRECTDEBIT + \ No newline at end of file diff --git a/account_banking_sepa_direct_debit/static/src/img/icon.png b/account_banking_sepa_direct_debit/static/src/img/icon.png deleted file mode 100644 index 6d1d923b6506b33ca0825af17ed0ae36c9bd0dc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6892 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 + + + + + + sdd.mandate.form + account.banking.mandate + + + + + + + + + + + + + + + + + sdd.mandate.tree + account.banking.mandate + + + + + + + + + + sdd.mandate.search + account.banking.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. +

+
+
+ + + + + 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/account_banking_sdd_view.xml b/account_banking_sepa_direct_debit/views/account_banking_sdd_view.xml similarity index 100% rename from account_banking_sepa_direct_debit/account_banking_sdd_view.xml rename to account_banking_sepa_direct_debit/views/account_banking_sdd_view.xml diff --git a/account_banking_sepa_direct_debit/company_view.xml b/account_banking_sepa_direct_debit/views/res_company_view.xml similarity index 100% rename from account_banking_sepa_direct_debit/company_view.xml rename to account_banking_sepa_direct_debit/views/res_company_view.xml diff --git a/account_banking_sepa_direct_debit/wizard/export_sdd.py b/account_banking_sepa_direct_debit/wizard/export_sdd.py index 19520d318..60dce7653 100644 --- a/account_banking_sepa_direct_debit/wizard/export_sdd.py +++ b/account_banking_sepa_direct_debit/wizard/export_sdd.py @@ -23,7 +23,7 @@ from openerp.osv import orm, fields from openerp.tools.translate import _ -from openerp import netsvc +from openerp import workflow from datetime import datetime from lxml import etree @@ -195,6 +195,7 @@ class banking_export_sdd_wizard(orm.TransientModel): "line with partner '%s' and Invoice ref '%s'.") % (line.partner_id.name, line.ml_inv_ref.number)) + scheme = line.sdd_mandate_id.scheme if line.sdd_mandate_id.state != 'valid': raise orm.except_orm( _('Error:'), @@ -225,8 +226,7 @@ class banking_export_sdd_wizard(orm.TransientModel): line.sdd_mandate_id.recurrent_sequence_type assert seq_type_label is not False seq_type = seq_type_map[seq_type_label] - - key = (requested_date, priority, seq_type) + key = (requested_date, priority, seq_type, scheme) if key in lines_per_group: lines_per_group[key].append(line) else: @@ -237,7 +237,7 @@ class banking_export_sdd_wizard(orm.TransientModel): cr, uid, line.id, {'date': requested_date}, context=context) - for (requested_date, priority, sequence_type), lines in \ + 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 = \ @@ -246,7 +246,7 @@ class banking_export_sdd_wizard(orm.TransientModel): "sepa_export.payment_order_ids[0].reference + '-' + " "sequence_type + '-' + requested_date.replace('-', '') " "+ '-' + priority", - priority, 'CORE', sequence_type, requested_date, { + priority, scheme, sequence_type, requested_date, { 'sepa_export': sepa_export, 'sequence_type': sequence_type, 'priority': priority, @@ -422,9 +422,8 @@ class banking_export_sdd_wizard(orm.TransientModel): self.pool.get('banking.export.sdd').write( cr, uid, sepa_export.file_id.id, {'state': 'sent'}, context=context) - wf_service = netsvc.LocalService('workflow') for order in sepa_export.payment_order_ids: - wf_service.trg_validate(uid, 'payment.order', order.id, 'done', cr) + workflow.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( cr, uid, mandate_ids,