Copy payment mode and mandate from invoice to refund

Run dos2unix on the few files that had DOS carriage return
This commit is contained in:
Alexis de Lattre
2016-05-07 22:02:49 +02:00
parent 882e0e5292
commit 3b10598b36
7 changed files with 217 additions and 186 deletions

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
# © 2014 Compassion CH - Cyril Sester <csester@compassion.ch>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models
# -*- coding: utf-8 -*-
from . import models

View File

@@ -1,134 +1,134 @@
# -*- coding: utf-8 -*-
# © 2014 Compassion CH - Cyril Sester <csester@compassion.ch>
# © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza
# © 2015-2016 Akretion - Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields, api, _
from openerp.exceptions import UserError, ValidationError
class AccountBankingMandate(models.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'
format = fields.Selection(
[('basic', 'Basic Mandate')], default='basic', required=True,
string='Mandate Format', track_visibility='onchange')
partner_bank_id = fields.Many2one(
comodel_name='res.partner.bank', string='Bank Account',
track_visibility='onchange')
partner_id = fields.Many2one(
comodel_name='res.partner', related='partner_bank_id.partner_id',
string='Partner', store=True)
company_id = fields.Many2one(
comodel_name='res.company', string='Company', required=True,
default=lambda self: self.env['res.company']._company_default_get(
'account.banking.mandate'))
unique_mandate_reference = fields.Char(
string='Unique Mandate Reference', track_visibility='onchange')
signature_date = fields.Date(string='Date of Signature of the Mandate',
track_visibility='onchange')
scan = fields.Binary(string='Scan of the Mandate')
last_debit_date = fields.Date(string='Date of the Last Debit',
readonly=True)
state = fields.Selection([
('draft', 'Draft'),
('valid', 'Valid'),
('expired', 'Expired'),
('cancel', 'Cancelled'),
], string='Status', default='draft', track_visibility='onchange',
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(
comodel_name='account.payment.line', inverse_name='mandate_id',
string="Related Payment Lines")
_sql_constraints = [(
'mandate_ref_company_uniq',
'unique(unique_mandate_reference, company_id)',
'A Mandate with the same reference already exists for this company !')]
@api.multi
@api.constrains('signature_date', 'last_debit_date')
def _check_dates(self):
for mandate in self:
if (mandate.signature_date and
mandate.signature_date > fields.Date.context_today(
mandate)):
raise ValidationError(
_("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 ValidationError(
_("The mandate '%s' can't have a date of last debit "
"before the date of signature."
) % mandate.unique_mandate_reference)
@api.multi
@api.constrains('state', 'partner_bank_id')
def _check_valid_state(self):
for mandate in self:
if mandate.state == 'valid':
if not mandate.signature_date:
raise ValidationError(
_("Cannot validate the mandate '%s' without a date of "
"signature.") % mandate.unique_mandate_reference)
if not mandate.partner_bank_id:
raise ValidationError(
_("Cannot validate the mandate '%s' because it is not "
"attached to a bank account.") %
mandate.unique_mandate_reference)
@api.model
def create(self, vals=None):
if vals.get('unique_mandate_reference', 'New') == 'New':
vals['unique_mandate_reference'] = \
self.env['ir.sequence'].next_by_code(
'account.banking.mandate') or 'New'
return super(AccountBankingMandate, self).create(vals)
@api.multi
@api.onchange('partner_bank_id')
def mandate_partner_bank_change(self):
for mandate in self:
mandate.partner_id = mandate.partner_bank_id.partner_id
@api.multi
def validate(self):
for mandate in self:
if mandate.state != 'draft':
raise UserError(
_('Mandate should be in draft state'))
self.write({'state': 'valid'})
return True
@api.multi
def cancel(self):
for mandate in self:
if mandate.state not in ('draft', 'valid'):
raise UserError(
_('Mandate should be in draft or valid state'))
self.write({'state': 'cancel'})
return True
@api.multi
def back2draft(self):
"""Allows to set the mandate back to the draft state.
This is for mandates cancelled by mistake.
"""
for mandate in self:
if mandate.state != 'cancel':
raise UserError(
_('Mandate should be in cancel state'))
self.write({'state': 'draft'})
return True
# -*- coding: utf-8 -*-
# © 2014 Compassion CH - Cyril Sester <csester@compassion.ch>
# © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza
# © 2015-2016 Akretion - Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields, api, _
from openerp.exceptions import UserError, ValidationError
class AccountBankingMandate(models.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'
format = fields.Selection(
[('basic', 'Basic Mandate')], default='basic', required=True,
string='Mandate Format', track_visibility='onchange')
partner_bank_id = fields.Many2one(
comodel_name='res.partner.bank', string='Bank Account',
track_visibility='onchange')
partner_id = fields.Many2one(
comodel_name='res.partner', related='partner_bank_id.partner_id',
string='Partner', store=True)
company_id = fields.Many2one(
comodel_name='res.company', string='Company', required=True,
default=lambda self: self.env['res.company']._company_default_get(
'account.banking.mandate'))
unique_mandate_reference = fields.Char(
string='Unique Mandate Reference', track_visibility='onchange')
signature_date = fields.Date(string='Date of Signature of the Mandate',
track_visibility='onchange')
scan = fields.Binary(string='Scan of the Mandate')
last_debit_date = fields.Date(string='Date of the Last Debit',
readonly=True)
state = fields.Selection([
('draft', 'Draft'),
('valid', 'Valid'),
('expired', 'Expired'),
('cancel', 'Cancelled'),
], string='Status', default='draft', track_visibility='onchange',
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(
comodel_name='account.payment.line', inverse_name='mandate_id',
string="Related Payment Lines")
_sql_constraints = [(
'mandate_ref_company_uniq',
'unique(unique_mandate_reference, company_id)',
'A Mandate with the same reference already exists for this company !')]
@api.multi
@api.constrains('signature_date', 'last_debit_date')
def _check_dates(self):
for mandate in self:
if (mandate.signature_date and
mandate.signature_date > fields.Date.context_today(
mandate)):
raise ValidationError(
_("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 ValidationError(
_("The mandate '%s' can't have a date of last debit "
"before the date of signature."
) % mandate.unique_mandate_reference)
@api.multi
@api.constrains('state', 'partner_bank_id')
def _check_valid_state(self):
for mandate in self:
if mandate.state == 'valid':
if not mandate.signature_date:
raise ValidationError(
_("Cannot validate the mandate '%s' without a date of "
"signature.") % mandate.unique_mandate_reference)
if not mandate.partner_bank_id:
raise ValidationError(
_("Cannot validate the mandate '%s' because it is not "
"attached to a bank account.") %
mandate.unique_mandate_reference)
@api.model
def create(self, vals=None):
if vals.get('unique_mandate_reference', 'New') == 'New':
vals['unique_mandate_reference'] = \
self.env['ir.sequence'].next_by_code(
'account.banking.mandate') or 'New'
return super(AccountBankingMandate, self).create(vals)
@api.multi
@api.onchange('partner_bank_id')
def mandate_partner_bank_change(self):
for mandate in self:
mandate.partner_id = mandate.partner_bank_id.partner_id
@api.multi
def validate(self):
for mandate in self:
if mandate.state != 'draft':
raise UserError(
_('Mandate should be in draft state'))
self.write({'state': 'valid'})
return True
@api.multi
def cancel(self):
for mandate in self:
if mandate.state not in ('draft', 'valid'):
raise UserError(
_('Mandate should be in draft or valid state'))
self.write({'state': 'cancel'})
return True
@api.multi
def back2draft(self):
"""Allows to set the mandate back to the draft state.
This is for mandates cancelled by mistake.
"""
for mandate in self:
if mandate.state != 'cancel':
raise UserError(
_('Mandate should be in cancel state'))
self.write({'state': 'draft'})
return True

View File

@@ -1,27 +1,42 @@
# -*- coding: utf-8 -*-
# © 2014 Compassion CH - Cyril Sester <csester@compassion.ch>
# © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza
# © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields, api
class AccountInvoice(models.Model):
_inherit = 'account.invoice'
mandate_id = fields.Many2one(
'account.banking.mandate', string='Direct Debit Mandate',
ondelete='restrict',
readonly=True, states={'draft': [('readonly', False)]})
@api.model
def line_get_convert(self, line, part):
"""Copy mandate from invoice to account move line"""
res = super(AccountInvoice, self).line_get_convert(line, part)
if line.get('type') == 'dest' and line.get('invoice_id'):
invoice = self.browse(line['invoice_id'])
if invoice.type in ('out_invoice', 'out_refund'):
res['mandate_id'] = invoice.mandate_id.id or False
return res
# -*- coding: utf-8 -*-
# © 2014 Compassion CH - Cyril Sester <csester@compassion.ch>
# © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza
# © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields, api
class AccountInvoice(models.Model):
_inherit = 'account.invoice'
mandate_id = fields.Many2one(
'account.banking.mandate', string='Direct Debit Mandate',
ondelete='restrict',
readonly=True, states={'draft': [('readonly', False)]})
@api.model
def line_get_convert(self, line, part):
"""Copy mandate from invoice to account move line"""
res = super(AccountInvoice, self).line_get_convert(line, part)
if line.get('type') == 'dest' and line.get('invoice_id'):
invoice = self.browse(line['invoice_id'])
if invoice.type in ('out_invoice', 'out_refund'):
res['mandate_id'] = invoice.mandate_id.id or False
return res
# If a customer pays via direct debit, it's refunds should
# be deducted form the next debit by default. The module
# account_payment_partner copies payment_mode_id from invoice
# to refund, and we also need to copy mandate from invoice to refund
@api.model
def _prepare_refund(
self, invoice, date_invoice=None, date=None, description=None,
journal_id=None):
vals = super(AccountInvoice, self)._prepare_refund(
invoice, date_invoice=date_invoice, date=date,
description=description, journal_id=journal_id)
if invoice.type == 'out_invoice':
vals['mandate_id'] = invoice.mandate_id.id
return vals

View File

@@ -1,16 +1,16 @@
# -*- coding: utf-8 -*-
# © 2014 Compassion CH - Cyril Sester <csester@compassion.ch>
# © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields
class ResPartnerBank(models.Model):
_inherit = 'res.partner.bank'
mandate_ids = fields.One2many(
comodel_name='account.banking.mandate', inverse_name='partner_bank_id',
string='Direct Debit Mandates',
help='Banking mandates represents an authorization that the bank '
'account owner gives to a company for a specific operation')
# -*- coding: utf-8 -*-
# © 2014 Compassion CH - Cyril Sester <csester@compassion.ch>
# © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields
class ResPartnerBank(models.Model):
_inherit = 'res.partner.bank'
mandate_ids = fields.One2many(
comodel_name='account.banking.mandate', inverse_name='partner_bank_id',
string='Direct Debit Mandates',
help='Banking mandates represents an authorization that the bank '
'account owner gives to a company for a specific operation')

View File

@@ -13,7 +13,8 @@
<field name="inherit_id" ref="account_payment_partner.invoice_form"/>
<field name="arch" type="xml">
<field name="partner_bank_id" position="after">
<field name="mandate_id" domain="[('partner_id', '=', commercial_partner_id), ('state', '=', 'valid')]" attrs="{'invisible': [('type', '=', 'out_refund')]}"/>
<field name="mandate_id"
domain="[('partner_id', '=', commercial_partner_id), ('state', '=', 'valid')]"/>
</field>
</field>
</record>

View File

@@ -119,10 +119,11 @@ class AccountPaymentOrder(models.Model):
for order in self:
allowed_journal_ids = False
if order.payment_mode_id:
if order.payment_mode_id.bank_account_link == 'fixed':
allowed_journal_ids = order.payment_mode_id.fixed_journal_id
mode = order.payment_mode_id
if mode.bank_account_link == 'fixed':
allowed_journal_ids = mode.fixed_journal_id
else:
allowed_journal_ids = order.payment_mode_id.variable_journal_ids
allowed_journal_ids = mode.variable_journal_ids
order.allowed_journal_ids = allowed_journal_ids
@api.model

View File

@@ -37,3 +37,19 @@ class AccountInvoice(models.Model):
invoice = self.browse(line['invoice_id'])
res['payment_mode_id'] = invoice.payment_mode_id.id or False
return res
# I think copying payment mode from invoice to refund by default
# is a good idea because the most common way of "paying" a refund is to
# deduct it on the payment of the next invoice (and OCA/bank-payment
# allows to have negative payment lines since March 2016)
@api.model
def _prepare_refund(
self, invoice, date_invoice=None, date=None, description=None,
journal_id=None):
vals = super(AccountInvoice, self)._prepare_refund(
invoice, date_invoice=date_invoice, date=date,
description=description, journal_id=journal_id)
vals['payment_mode_id'] = invoice.payment_mode_id.id
if invoice.type == 'in_invoice':
vals['partner_bank_id'] = invoice.partner_bank_id.id
return vals