mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
Finalise the wizard of selection of move lines to pay
Add button "Add to payment/debit order" on invoice form view Started to integrate payment transfer in account_payment_order (not finished at all though) Various fixes/changes/improvements/...
This commit is contained in:
@@ -3,50 +3,22 @@
|
||||
# © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from openerp import api, models, _
|
||||
from lxml import etree
|
||||
from openerp import models, fields, api, _
|
||||
from openerp.exceptions import UserError
|
||||
|
||||
|
||||
class AccountInvoice(models.Model):
|
||||
_inherit = 'account.invoice'
|
||||
|
||||
payment_order_ok = fields.Boolean(
|
||||
related='payment_mode_id.payment_order_ok', readonly=True)
|
||||
|
||||
@api.model
|
||||
def _get_reference_type(self):
|
||||
rt = super(AccountInvoice, self)._get_reference_type()
|
||||
rt.append(('structured', _('Structured Reference')))
|
||||
return rt
|
||||
|
||||
@api.model
|
||||
def fields_view_get(self, view_id=None, view_type=False, toolbar=False,
|
||||
submenu=False):
|
||||
"""This adds the field 'reference_type' only if the view doesn't
|
||||
contain this field (this is for customer invoice and with
|
||||
l10n_be_invoice_bba not installed).
|
||||
"""
|
||||
res = super(AccountInvoice, self).fields_view_get(
|
||||
view_id=view_id, view_type=view_type, toolbar=toolbar,
|
||||
submenu=submenu)
|
||||
if view_type != 'form':
|
||||
return res
|
||||
field_name = 'reference_type'
|
||||
doc = etree.XML(res['arch'])
|
||||
if not doc.xpath("//field[@name='%s']" % field_name):
|
||||
nodes = doc.xpath("//field[@name='origin']")
|
||||
if nodes:
|
||||
field = self.fields_get([field_name])[field_name]
|
||||
field_xml = etree.Element(
|
||||
'field', {'name': field_name,
|
||||
'widget': 'selection',
|
||||
'states': str(field['states']),
|
||||
'selection': str(field['selection']),
|
||||
'required': '1' if field['required'] else '0',
|
||||
'string': field['string'],
|
||||
'nolabel': '0'})
|
||||
nodes[0].addnext(field_xml)
|
||||
res['arch'] = etree.tostring(doc)
|
||||
res['fields'][field_name] = field
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def line_get_convert(self, line, part):
|
||||
"""Copy supplier bank account from invoice to account move line"""
|
||||
@@ -56,3 +28,72 @@ class AccountInvoice(models.Model):
|
||||
if invoice.type in ('in_invoice', 'in_refund'):
|
||||
res['partner_bank_id'] = invoice.partner_bank_id.id or False
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def _prepare_new_payment_order(self):
|
||||
self.ensure_one()
|
||||
vals = {
|
||||
'payment_mode_id': self.payment_mode_id.id,
|
||||
'payment_type': self.payment_mode_id.payment_type,
|
||||
}
|
||||
if self.payment_mode_id.bank_account_link == 'fixed':
|
||||
vals['journal_id'] = self.payment_mode_id.fixed_journal_id.id
|
||||
# TODO : else: no filter on allowed bank accounts, because onchange not played ??
|
||||
return vals
|
||||
|
||||
@api.multi
|
||||
def create_account_payment_line(self):
|
||||
apoo = self.env['account.payment.order']
|
||||
aplo = self.env['account.payment.line']
|
||||
action = {}
|
||||
for inv in self:
|
||||
if not inv.payment_mode_id:
|
||||
raise UserError(_(
|
||||
"No Payment Mode on invoice %s") % inv.number)
|
||||
if not inv.move_id:
|
||||
raise UserError(_(
|
||||
"No Journal Entry on invoice %s") % inv.number)
|
||||
payorders = apoo.search([
|
||||
('payment_mode_id', '=', inv.payment_mode_id.id),
|
||||
('state', '=', 'draft')])
|
||||
if payorders:
|
||||
payorder = payorders[0]
|
||||
new_payorder = False
|
||||
else:
|
||||
payorder = apoo.create(inv._prepare_new_payment_order())
|
||||
new_payorder = True
|
||||
count = 0
|
||||
for line in inv.move_id.line_ids:
|
||||
if line.account_id == inv.account_id and not line.reconciled:
|
||||
paylines = aplo.search([
|
||||
('move_line_id', '=', line.id),
|
||||
('state', '!=', 'cancel')])
|
||||
if paylines:
|
||||
continue
|
||||
line.create_payment_line_from_move_line(payorder)
|
||||
count += 1
|
||||
if count:
|
||||
if new_payorder:
|
||||
inv.message_post(_(
|
||||
'%d payment lines added to the new draft payment '
|
||||
'order %s which has been automatically created.')
|
||||
% (count, payorder.name))
|
||||
else:
|
||||
inv.message_post(_(
|
||||
'%d payment lines added to the existing draft '
|
||||
'payment order %s.')
|
||||
% (count, payorder.name))
|
||||
else:
|
||||
raise UserError(_(
|
||||
'No Payment Line created for invoice %s because '
|
||||
'it already exists or because this invoice is '
|
||||
'already paid.') % inv.number)
|
||||
action = self.env['ir.actions.act_window'].for_xml_id(
|
||||
'account_payment_order',
|
||||
'account_payment_order_%s_action' % payorder.payment_type)
|
||||
action.update({
|
||||
'view_mode': 'form,tree,pivot,graph',
|
||||
'res_id': payorder.id,
|
||||
'views': False,
|
||||
})
|
||||
return action
|
||||
|
||||
@@ -45,17 +45,22 @@ class AccountMoveLine(models.Model):
|
||||
self.ensure_one()
|
||||
assert payment_order, 'Missing payment order'
|
||||
aplo = self.env['account.payment.line']
|
||||
# default values for communication_type and communication
|
||||
communication_type = 'normal'
|
||||
communication = self.move_id.name or '-'
|
||||
ref2comm_type = aplo.invoice_reference_type2communication_type()
|
||||
if (
|
||||
self.invoice_id and
|
||||
self.invoice_id.type in ('in_invoice', 'in_refund') and
|
||||
self.invoice_id.reference):
|
||||
communication = self.invoice_id.reference
|
||||
if self.invoice_id.reference_type:
|
||||
# change these default values if move line is linked to an invoice
|
||||
if self.invoice_id:
|
||||
if self.invoice_id.reference_type != 'none':
|
||||
communication = self.invoice_id.reference
|
||||
ref2comm_type =\
|
||||
aplo.invoice_reference_type2communication_type()
|
||||
communication_type =\
|
||||
ref2comm_type[self.invoice_id.reference_type]
|
||||
else:
|
||||
if (
|
||||
self.invoice_id.type in ('in_invoice', 'in_refund')
|
||||
and self.invoice_id.reference):
|
||||
communication = self.invoice_id.reference
|
||||
if self.currency_id:
|
||||
currency_id = self.currency_id.id
|
||||
amount_currency = self.amount_residual_currency
|
||||
@@ -66,7 +71,6 @@ class AccountMoveLine(models.Model):
|
||||
precision = self.env['decimal.precision'].precision_get('Account')
|
||||
if payment_order.payment_type == 'outbound':
|
||||
amount_currency *= -1
|
||||
# TODO : inherit for mandate
|
||||
vals = {
|
||||
'order_id': payment_order.id,
|
||||
'partner_bank_id': self.partner_bank_id.id,
|
||||
|
||||
@@ -20,6 +20,9 @@ class AccountPaymentLine(models.Model):
|
||||
related='order_id.company_currency_id', store=True, readonly=True)
|
||||
payment_type = fields.Selection(
|
||||
related='order_id.payment_type', store=True, readonly=True)
|
||||
state = fields.Selection(
|
||||
related='order_id.state', string='State',
|
||||
readonly=True, store=True)
|
||||
move_line_id = fields.Many2one(
|
||||
'account.move.line', string='Journal Item')
|
||||
ml_maturity_date = fields.Date(
|
||||
@@ -83,6 +86,11 @@ class AccountPaymentLine(models.Model):
|
||||
values = []
|
||||
for field in bplo.same_fields_payment_line_and_bank_payment_line():
|
||||
values.append(unicode(self[field]))
|
||||
# Don't group the payment lines that are attached to the same supplier
|
||||
# but to move lines with different accounts (very unlikely),
|
||||
# for easier generation/comprehension of the transfer move
|
||||
# TODO Alexis : but this is for ????
|
||||
values.append(unicode(self.move_line_id.account_id or False))
|
||||
hashcode = '-'.join(values)
|
||||
return hashcode
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
# © 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 import models, fields, api, _
|
||||
from openerp.exceptions import ValidationError
|
||||
|
||||
|
||||
class AccountPaymentMode(models.Model):
|
||||
@@ -42,6 +43,38 @@ class AccountPaymentMode(models.Model):
|
||||
"* Payment Date\n"
|
||||
"(other modules can set additional fields to restrict the "
|
||||
"grouping.)")
|
||||
transfer_move = fields.Boolean(
|
||||
'Generate Accounting Entries On File Upload')
|
||||
transfer_account_id = fields.Many2one(
|
||||
'account.account', string='Transfer Account',
|
||||
domain=[('internal_type', '=', 'other'), ('reconcile', '=', True)],
|
||||
help="Pay off lines in 'file uploaded' payment orders with a move on "
|
||||
"this account. You can only select accounts of type regular "
|
||||
"that are marked for reconciliation")
|
||||
transfer_journal_id = fields.Many2one(
|
||||
'account.journal', string='Transfer Journal',
|
||||
help='Journal to write payment entries when confirming '
|
||||
'payment/debit orders of this mode')
|
||||
transfer_move_option = fields.Selection([
|
||||
('date', 'One move per payment date'),
|
||||
('line', 'One move per payment line'),
|
||||
], string='Transfer Move Option', default='date')
|
||||
|
||||
@api.multi
|
||||
@api.constrains(
|
||||
'transfer_move', 'transfer_account_id', 'transfer_journal_id',
|
||||
'transfer_move_option')
|
||||
def transfer_move_constrains(self):
|
||||
for mode in self:
|
||||
if mode.transfer_move and (
|
||||
not mode.transfer_account_id or
|
||||
not mode.transfer_journal_id or
|
||||
not mode.transfer_move_option):
|
||||
raise ValidationError(_(
|
||||
"The option 'Generate Accounting Entries On File Upload' "
|
||||
"is active on payment mode '%s', so the three parameters "
|
||||
"'Transfer Account', 'Transfer Journal' and "
|
||||
"'Transfer Move Option' must be set.") % mode.name)
|
||||
|
||||
@api.onchange('payment_method_id')
|
||||
def payment_method_id_change(self):
|
||||
|
||||
@@ -35,7 +35,9 @@ class AccountPaymentOrder(models.Model):
|
||||
related='payment_mode_id.bank_account_link', readonly=True)
|
||||
journal_id = fields.Many2one(
|
||||
'account.journal', string='Bank Journal',
|
||||
required=True)
|
||||
readonly=True, states={'draft': [('readonly', False)]})
|
||||
# The journal_id field is only required at confirm step, to
|
||||
# allow auto-creation of payment order from invoice
|
||||
company_partner_bank_id = fields.Many2one(
|
||||
related='journal_id.bank_account_id', string='Company Bank Account',
|
||||
readonly=True)
|
||||
@@ -174,6 +176,9 @@ class AccountPaymentOrder(models.Model):
|
||||
bplo = self.env['bank.payment.line']
|
||||
today = fields.Date.context_today(self)
|
||||
for order in self:
|
||||
if not order.journal_id:
|
||||
raise UserError(_(
|
||||
'Missing Bank Journal on payment order %s') % order.name)
|
||||
# Delete existing bank payment lines
|
||||
order.bank_line_ids.unlink()
|
||||
# Create the bank payment lines from the payment lines
|
||||
@@ -257,5 +262,144 @@ class AccountPaymentOrder(models.Model):
|
||||
|
||||
@api.multi
|
||||
def generated2uploaded(self):
|
||||
for order in self:
|
||||
if order.payment_mode_id.transfer_move:
|
||||
order.generate_transfer_move()
|
||||
self.write({'state': 'uploaded'})
|
||||
return True
|
||||
|
||||
# Generation of transfer move
|
||||
@api.multi
|
||||
def _prepare_transfer_move(self):
|
||||
vals = {
|
||||
'journal_id': self.mode.transfer_journal_id.id,
|
||||
'ref': '%s %s' % (
|
||||
self.payment_order_type[:3].upper(), self.reference)
|
||||
}
|
||||
return vals
|
||||
|
||||
@api.multi
|
||||
def _prepare_move_line_transfer_account(
|
||||
self, amount, move, bank_payment_lines, labels):
|
||||
if len(bank_payment_lines) == 1:
|
||||
partner_id = bank_payment_lines[0].partner_id.id
|
||||
name = _('%s bank line %s') % (labels[self.payment_order_type],
|
||||
bank_payment_lines[0].name)
|
||||
else:
|
||||
partner_id = False
|
||||
name = '%s %s' % (
|
||||
labels[self.payment_order_type], self.reference)
|
||||
date_maturity = bank_payment_lines[0].date
|
||||
vals = {
|
||||
'name': name,
|
||||
'move_id': move.id,
|
||||
'partner_id': partner_id,
|
||||
'account_id': self.mode.transfer_account_id.id,
|
||||
'credit': (self.payment_order_type == 'payment' and
|
||||
amount or 0.0),
|
||||
'debit': (self.payment_order_type == 'debit' and
|
||||
amount or 0.0),
|
||||
'date_maturity': date_maturity,
|
||||
}
|
||||
return vals
|
||||
|
||||
@api.multi
|
||||
def _prepare_move_line_partner_account(self, bank_line, move, labels):
|
||||
# TODO : ALEXIS check don't group if move_line_id.account_id
|
||||
# is not the same
|
||||
if bank_line.payment_line_ids[0].move_line_id:
|
||||
account_id =\
|
||||
bank_line.payment_line_ids[0].move_line_id.account_id.id
|
||||
else:
|
||||
if self.payment_order_type == 'debit':
|
||||
account_id =\
|
||||
bank_line.partner_id.property_account_receivable.id
|
||||
else:
|
||||
account_id = bank_line.partner_id.property_account_payable.id
|
||||
vals = {
|
||||
'name': _('%s line %s') % (
|
||||
labels[self.payment_order_type], bank_line.name),
|
||||
'move_id': move.id,
|
||||
'partner_id': bank_line.partner_id.id,
|
||||
'account_id': account_id,
|
||||
'credit': (self.payment_order_type == 'debit' and
|
||||
bank_line.amount_currency or 0.0),
|
||||
'debit': (self.payment_order_type == 'payment' and
|
||||
bank_line.amount_currency or 0.0),
|
||||
}
|
||||
return vals
|
||||
|
||||
@api.multi
|
||||
def action_sent_no_move_line_hook(self, pay_line):
|
||||
"""This function is designed to be inherited"""
|
||||
return
|
||||
|
||||
@api.multi
|
||||
def _create_move_line_partner_account(self, bank_line, move, labels):
|
||||
"""This method is designed to be inherited in a custom module"""
|
||||
# TODO: take multicurrency into account
|
||||
company_currency = self.env.user.company_id.currency_id
|
||||
if bank_line.currency != company_currency:
|
||||
raise UserError(_(
|
||||
"Cannot generate the transfer move when "
|
||||
"the currency of the payment (%s) is not the "
|
||||
"same as the currency of the company (%s). This "
|
||||
"is not supported for the moment.")
|
||||
% (bank_line.currency.name, company_currency.name))
|
||||
aml_obj = self.env['account.move.line']
|
||||
# create the payment/debit counterpart move line
|
||||
# on the partner account
|
||||
partner_ml_vals = self._prepare_move_line_partner_account(
|
||||
bank_line, move, labels)
|
||||
partner_move_line = aml_obj.create(partner_ml_vals)
|
||||
|
||||
# register the payment/debit move line
|
||||
# on the payment line and call reconciliation on it
|
||||
bank_line.write({'transit_move_line_id': partner_move_line.id})
|
||||
|
||||
@api.multi
|
||||
def _reconcile_payment_lines(self, bank_payment_lines):
|
||||
for bline in bank_payment_lines:
|
||||
if all([pline.move_line_id for pline in bline.payment_line_ids]):
|
||||
bline.debit_reconcile()
|
||||
else:
|
||||
self.action_sent_no_move_line_hook(bline)
|
||||
|
||||
@api.multi
|
||||
def generate_transfer_move(self):
|
||||
"""
|
||||
Create the moves that pay off the move lines from
|
||||
the debit order.
|
||||
"""
|
||||
self.ensure_one()
|
||||
am_obj = self.env['account.move']
|
||||
aml_obj = self.env['account.move.line']
|
||||
labels = {
|
||||
'outbound': _('Payment'),
|
||||
'inbound': _('Direct debit'),
|
||||
}
|
||||
# prepare a dict "trfmoves" that can be used when
|
||||
# self.mode.transfer_move_option = date or line
|
||||
# key = unique identifier (date or True or line.id)
|
||||
# value = [pay_line1, pay_line2, ...]
|
||||
trfmoves = {}
|
||||
for bline in self.bank_line_ids:
|
||||
hashcode = bline.move_line_transfer_account_hashcode()
|
||||
if hashcode in trfmoves:
|
||||
trfmoves[hashcode].append(bline)
|
||||
else:
|
||||
trfmoves[hashcode] = [bline]
|
||||
|
||||
for hashcode, blines in trfmoves.iteritems():
|
||||
mvals = self._prepare_transfer_move()
|
||||
move = am_obj.create(mvals)
|
||||
total_amount = 0
|
||||
for bline in blines:
|
||||
total_amount += bline.amount_currency
|
||||
self._create_move_line_partner_account(bline, move, labels)
|
||||
# create the payment/debit move line on the transfer account
|
||||
trf_ml_vals = self._prepare_move_line_transfer_account(
|
||||
total_amount, move, blines, labels)
|
||||
aml_obj.create(trf_ml_vals)
|
||||
self._reconcile_payment_lines(blines)
|
||||
move.post()
|
||||
|
||||
@@ -29,6 +29,7 @@ class BankPaymentLine(models.Model):
|
||||
readonly=True)
|
||||
# Function Float fields are sometimes badly displayed in tree view,
|
||||
# see bug report https://github.com/odoo/odoo/issues/8632
|
||||
# But is it still true in v9 ?
|
||||
amount_currency = fields.Monetary(
|
||||
string='Amount', currency_field='currency_id',
|
||||
compute='_compute_amount', store=True, readonly=True)
|
||||
@@ -48,6 +49,28 @@ class BankPaymentLine(models.Model):
|
||||
company_id = fields.Many2one(
|
||||
related='order_id.payment_mode_id.company_id', store=True,
|
||||
readonly=True)
|
||||
# TODO : not shown in view ?
|
||||
# why on bank payment line and not on payment line ?
|
||||
transit_move_line_id = fields.Many2one(
|
||||
'account.move.line', string='Transfer Move Line', readonly=True,
|
||||
help="Move line through which the payment/debit order "
|
||||
"pays the invoice")
|
||||
transfer_move_line_id = fields.Many2one(
|
||||
'account.move.line', compute='_get_transfer_move_line',
|
||||
string='Transfer move line counterpart',
|
||||
help="Counterpart move line on the transfer account")
|
||||
|
||||
@api.multi
|
||||
def _get_transfer_move_line(self):
|
||||
for bank_line in self:
|
||||
if bank_line.transit_move_line_id:
|
||||
payment_type = bank_line.payment_type
|
||||
trf_lines = bank_line.transit_move_line_id.move_id.line_id
|
||||
for move_line in trf_lines:
|
||||
if payment_type == 'inbound' and move_line.debit > 0:
|
||||
bank_line.transfer_move_line_id = move_line
|
||||
elif payment_type == 'outbound' and move_line.credit > 0:
|
||||
bank_line.transfer_move_line_id = move_line
|
||||
|
||||
@api.model
|
||||
def same_fields_payment_line_and_bank_payment_line(self):
|
||||
@@ -76,3 +99,17 @@ class BankPaymentLine(models.Model):
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code(
|
||||
'bank.payment.line') or 'New'
|
||||
return super(BankPaymentLine, self).create(vals)
|
||||
|
||||
@api.multi
|
||||
def move_line_transfer_account_hashcode(self):
|
||||
"""
|
||||
This method is inherited in the module
|
||||
account_banking_sepa_direct_debit
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.order_id.payment_mode_id.transfer_move_option == 'date':
|
||||
hashcode = self.date
|
||||
else:
|
||||
hashcode = unicode(self.id)
|
||||
return hashcode
|
||||
|
||||
|
||||
Reference in New Issue
Block a user