More work on mandates : - constraints - display related payment lines - set mandates to expired after 36 months of inactivity (via a cron) - add tracking/chatter - add default draft state + validate button

[FIX] In Debit mode, don't use the partner_bank_id of the customer invoice !

In the wizard 'Select invoices to pay', add maturity date in the view of the account move lines
This commit is contained in:
Alexis de Lattre
2013-11-07 23:22:19 +01:00
committed by Enric Tobella
parent 2144be6408
commit d8245177e2
4 changed files with 189 additions and 37 deletions

View File

@@ -34,6 +34,7 @@
'account_banking_sdd_view.xml',
'account_payment_view.xml',
'company_view.xml',
'mandate_expire_cron.xml',
'wizard/export_sdd_view.xml',
'data/payment_type_sdd.xml',
'data/mandate_reference_sequence.xml',

View File

@@ -23,6 +23,13 @@ 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):
@@ -88,7 +95,16 @@ class sdd_mandate(orm.Model):
_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',
}
}
_columns = {
'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account'),
@@ -108,25 +124,98 @@ class sdd_mandate(orm.Model):
'Date of the Last Debit',
help="For recurrent mandates, this field is used to know if the SDD will be of type 'First' or 'Recurring'. For one-off mandates, this field is used to know if the SDD has already been used or not."),
'state': fields.selection([
('draft', 'Draft'),
('valid', 'Valid'),
('expired', 'Expired'),
# Do we have to handle cancellation of mandate by customer ?
], 'Mandate Status',
help="For a recurrent mandate, this field indicate if the mandate is still valid or if it has expired (a recurrent mandate expires if it's not used during 36 months). For a one-off mandate, it expires after its first use."),
'payment_line_ids': fields.one2many(
'payment.line', 'sdd_mandate_id', "Related Payment Lines"),
}
_defaults = {
'company_id': lambda self, cr, uid, context:
self.pool['res.company'].\
_company_default_get(cr, uid, 'sdd.mandate', context=context),
'unique_mandate_reference': lambda self, cr, uid, ctx:
self.pool['ir.sequence'].get(cr, uid, 'sdd.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 !'
)]
_defaults = {
'company_id': lambda self, cr, uid, ctx:
self.pool['res.users'].browse(cr, uid, uid, ctx=ctx).company_id.id,
'unique_mandate_reference': lambda self, cr, uid, ctx:
self.pool['ir.sequence'].get(cr, uid, 'sdd.mandate.reference'),
'state': 'valid',
}
def _check_sdd_mandate(self, cr, uid, ids, context=None):
for mandate in self.read(cr, uid, ids, [
'last_debit_date', 'signature_date',
'unique_mandate_reference', 'state', 'partner_bank_id'
], context=context):
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 linked 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'])
return True
_constraints = [
(_check_sdd_mandate, "Error msg in raise",
['last_debit_date', 'signature_date', 'state', 'partner_bank_id']),
]
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 _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):
@@ -146,23 +235,20 @@ class payment_line(orm.Model):
'sdd.mandate', 'SEPA Direct Debit Mandate'),
}
def _check_sdd_mandate(self, cr, uid, ids):
for payline in self.browse(cr, uid, ids):
if payline.sdd_mandate_id and not payline.bank_id:
raise orm.except_orm(
_('Error:'),
_("Missing bank account on the payment line with SEPA Direct Debit Mandate '%s'.")
% payline.sdd_mandate_id.unique_mandate_reference)
elif (payline.sdd_mandate_id and payline.bank_id and
payline.sdd_mandate_id.partner_bank_id !=
payline.bank_id.id):
raise orm.except_orm(
_('Error:'),
_("The SEPA Direct Debit Mandate '%s' is not related???"))
return True
# _constraints = [
# (_check_sdd_mandate, "Mandate must be attached to bank account", ['bank_id', 'sdd_mandate_id']),
# ]
# TODO inherit create to select the first mandate ??
def create(self, cr, uid, vals, context=None):
'''Take the first valid mandate of the bank account by default'''
if context is None:
context = {}
if not vals:
vals = {}
partner_bank_id = vals.get('bank_id')
if (context.get('default_payment_order_type') == 'debit'
and 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)

View File

@@ -56,7 +56,7 @@
<record id="action_account_banking_sdd" model="ir.actions.act_window">
<field name="name">Generated SEPA Direct Debit XML files</field>
<field name="name">Generated SEPA Direct Debit Files</field>
<field name="res_model">banking.export.sdd</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
@@ -66,11 +66,11 @@
<menuitem id="menu_account_banking_sdd"
parent="account_payment.menu_main_payment"
action="action_account_banking_sdd"
sequence="15"
sequence="20"
/>
<act_window id="act_banking_export_sdd_payment_order"
name="Generated SEPA Direct Debit files"
name="Generated SEPA Direct Debit Files"
domain="[('payment_order_ids', '=', active_id)]"
res_model="banking.export.sdd"
src_model="payment.order"
@@ -84,25 +84,32 @@
<field name="arch" type="xml">
<form string="SEPA Direct Debit Mandate" version="7.0">
<header>
<button name="validate" type="object" string="Validate" states="draft"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<div class="oe_title">
<h1>
<label string="SEPA Direct Debit Mandate"/>
<field name="unique_mandate_reference" class="oe_inline"/>
</h1>
</div>
<group>
<group name="main">
<field name="company_id" groups="base.group_multi_company"/>
<field name="partner_id" invisible="not context.get('sdd_mandate_main_view', False)"/>
<field name="partner_id" invisible="not context.get('sdd_mandate_main_view')"/>
<field name="type"/>
<field name="signature_date"/>
<field name="scan"/>
<field name="last_debit_date"/>
<field name="partner_bank_id"/>
<field name="partner_bank_id" invisible="not context.get('sdd_mandate_main_view')"/>
</group>
<group name="payment_lines" string="Related Payment Lines">
<field name="payment_line_ids" nolabel="1"/>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
@@ -111,9 +118,9 @@
<field name="name">sdd.mandate.tree</field>
<field name="model">sdd.mandate</field>
<field name="arch" type="xml">
<tree string="SEPA Direct Debit Mandate" colors="blue:state=='expired'">
<tree string="SEPA Direct Debit Mandate" colors="blue:state=='draft';black:state=='expired'">
<field name="company_id" groups="base.group_multi_company"/>
<field name="partner_id" invisible="not context.get('sdd_mandate_main_view', False)"/>
<field name="partner_id" invisible="not context.get('sdd_mandate_main_view')"/>
<field name="unique_mandate_reference" string="Reference"/>
<field name="type" string="Type"/>
<field name="signature_date" string="Signature Date"/>
@@ -123,8 +130,23 @@
</field>
</record>
<record id="sdd_mandate_search" model="ir.ui.view">
<field name="name">sdd.mandate.search</field>
<field name="model">sdd.mandate</field>
<field name="arch" type="xml">
<search string="Search SEPA Direct Debit Mandates">
<field name="partner_id"/>
<filter name="draft" string="Draft" domain="[('state', '=', 'draft')]" />
<filter name="valid" string="Valid" domain="[('state', '=', 'valid')]" />
<filter name="expired" string="Expired" domain="[('state', '=', 'expired')]" />
<filter name="oneoff" string="One-Off" domain="[('type', '=', 'oneoff')]" />
<filter name="recurrent" string="Recurrent" domain="[('type', '=', 'recurrent')]" />
</search>
</field>
</record>
<record id="sdd_mandate_action" model="ir.actions.act_window">
<field name="name">SEPA Direct Debit Mandate</field>
<field name="name">SEPA Direct Debit Mandates</field>
<field name="res_model">sdd.mandate</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
@@ -144,6 +166,23 @@
sequence="20"
/>
<!-- notification in the chatter -->
<record id="mandate_valid" model="mail.message.subtype">
<field name="name">Mandate Validated</field>
<field name="res_model">sdd.mandate</field>
<field name="default" eval="False"/>
<field name="description">SEPA Direct Debit Mandate Validated</field>
</record>
<record id="mandate_expire" model="mail.message.subtype">
<field name="name">Mandate Expired</field>
<field name="res_model">sdd.mandate</field>
<field name="default" eval="False"/>
<field name="description">SEPA Direct Debit Mandate has Expired</field>
</record>
<record id="sdd_mandate_partner_bank_form" model="ir.ui.view">
<field name="name">sdd.mandate.res.partner.bank.form</field>
<field name="model">res.partner.bank</field>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2013 Akretion (http://www.akretion.com/)
@author Alexis de Lattre <alexis.delattre@akretion.com>
The licence is in the file __openerp__.py
-->
<openerp>
<data noupdate="1"> <!-- noupdate = 1 for the 'active' field -->
<record id="sdd_mandate_expire_cron" model="ir.cron">
<field name="name">Set SEPA Direct Debit Mandates to Expired</field>
<field name="active" eval="True"/>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field> <!-- don't limit the number of calls -->
<field name="doall" eval="False"/>
<field name="model" eval="'sdd.mandate'"/>
<field name="function" eval="'_sdd_mandate_set_state_to_expired'" />
<field name="args" eval="'()'"/>
</record>
</data>
</openerp>