Merge pull request #520 from Tecnativa/12.0-mig-account_payment_order

[12.0][MIG] account_payment_order: Migration to v12.0
This commit is contained in:
Pedro M. Baeza
2018-12-13 18:34:57 +01:00
committed by GitHub
79 changed files with 45281 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
=====================
Account Payment Order
=====================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fbank--payment-lightgray.png?logo=github
:target: https://github.com/OCA/bank-payment/tree/12.0/account_payment_order
:alt: OCA/bank-payment
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/bank-payment-12-0/bank-payment-12-0-account_payment_order
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/97/12.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
This module adds support for payment orders and debit orders.
**Table of contents**
.. contents::
:local:
Installation
============
This module depends on:
* account_payment_partner
* base_iban
* document
This modules is part of the OCA/bank-payment suite.
Configuration
=============
This module adds several options on Payment Modes, cf Invoicing/Accounting >
Configuration > Management > Payment Modes.
Usage
=====
You can create a Payment order via the menu Invoicing/Accounting > Payments > Payment Orders and then select the move lines to pay.
You can create a Debit order via the menu Invoicing/Accounting > Payments > Debit Orders and then select the move lines to debit.
This module also adds a button *Add to Payment Order* on supplier invoices and a button *Add to Debit Order* on customer invoices.
You can print a Payment order via the menu Invoicing/Accounting > Payments > Payment Orders and then select the payment oder to print.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/bank-payment/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/bank-payment/issues/new?body=module:%20account_payment_order%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* ACSONE SA/NV
* Therp BV
* Tecnativa
* Akretion
Contributors
~~~~~~~~~~~~
* Stéphane Bidoul <stephane.bidoul@acsone.eu>
* Alexis de Lattre <alexis.delattre@akretion.com>
* Pedro M. Baeza
* Adrien Peiffer <adrien.peiffer@acsone.eu>
* Stefan Rijnhart
* Laurent Mignon <laurent.mignon@acsone.eu>
* Alexandre Fayolle
* Danimar Ribeiro
* Erwin van der Ploeg
* Raphaël Valyi
* Sandy Carter
* Angel Moya <angel.moya@domatix.com>
* Jose María Alzaga <jose.alzaga@aselcis.com>
* Meyomesse Gilles <meyomesse.gilles@gmail.com>
* Carlos Dauden
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/bank-payment <https://github.com/OCA/bank-payment/tree/12.0/account_payment_order>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,3 @@
from . import models
from . import report
from . import wizard

View File

@@ -0,0 +1,42 @@
# © 2009 EduSense BV (<http://www.edusense.nl>)
# © 2011-2013 Therp BV (<https://therp.nl>)
# © 2013-2014 ACSONE SA (<https://acsone.eu>).
# © 2014-2016 Tecnativa - Pedro M. Baeza
# © 2016 Akretion (<https://www.akretion.com>).
# © 2016 Aselcis (<https://www.aselcis.com>).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
'name': 'Account Payment Order',
'version': '12.0.1.0.0',
'license': 'AGPL-3',
'author': "ACSONE SA/NV, "
"Therp BV, "
"Tecnativa, "
"Akretion, "
"Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/bank-payment',
'category': 'Banking addons',
'depends': [
'account_payment_partner',
'base_iban', # for manual_bank_tranfer
],
'data': [
'security/payment_security.xml',
'security/ir.model.access.csv',
'wizard/account_payment_line_create_view.xml',
'wizard/account_invoice_payment_line_multi_view.xml',
'views/account_payment_mode.xml',
'views/account_payment_order.xml',
'views/account_payment_line.xml',
'views/bank_payment_line.xml',
'views/account_move_line.xml',
'views/ir_attachment.xml',
'views/account_invoice_view.xml',
'data/payment_seq.xml',
'report/print_account_payment_order.xml',
'report/account_payment_order.xml',
],
'demo': ['demo/payment_demo.xml'],
'installable': True,
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2015-2016 Akretion (https://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="bank_payment_line_seq" model="ir.sequence">
<field name="name">Bank Payment Line</field>
<field name="code">bank.payment.line</field>
<field name="prefix">L</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="account_payment_line_seq" model="ir.sequence">
<field name="name">Payment Line</field>
<field name="code">account.payment.line</field>
<field name="prefix">P</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="account_payment_order_seq" model="ir.sequence">
<field name="name">Payment Order</field>
<field name="code">account.payment.order</field>
<field name="prefix">PAY</field>
<field name="padding">4</field>
<field name="company_id" eval="False"/>
</record>
</odoo>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="account_payment_mode.payment_mode_outbound_dd1" model="account.payment.mode">
<field name="payment_order_ok" eval="False"/>
</record>
<record id="account_payment_mode.payment_mode_outbound_dd2" model="account.payment.mode">
<field name="payment_order_ok" eval="False"/>
</record>
<record id="account_payment_mode.payment_mode_inbound_ct1" model="account.payment.mode">
<field name="payment_order_ok" eval="False"/>
</record>
<record id="account_payment_mode.payment_mode_inbound_ct2" model="account.payment.mode">
<field name="payment_order_ok" eval="False"/>
</record>
<record id="account_payment_mode.payment_mode_outbound_ct1" model="account.payment.mode">
<!-- Credit Transfer to Suppliers -->
<field name="default_journal_ids" search="[('type', 'in', ('purchase', 'purchase_refund'))]"/>
</record>
<record id="account_payment_mode.payment_mode_inbound_dd1" model="account.payment.mode">
<!-- Direct Debit of customers -->
<field name="default_journal_ids" search="[('type', 'in', ('sale', 'sale_refund'))]"/>
</record>
</odoo>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
from . import account_payment_mode
from . import account_payment_order
from . import account_payment_line
from . import bank_payment_line
from . import account_move
from . import account_move_line
from . import account_invoice
from . import res_bank

View File

@@ -0,0 +1,138 @@
# © 2013-2014 ACSONE SA (<https://acsone.eu>).
# © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza
# © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class AccountInvoice(models.Model):
_inherit = 'account.invoice'
payment_order_ok = fields.Boolean(
compute="_compute_payment_order_ok",
)
# we restore this field from <=v11 for now for preserving behavior
# TODO: Check if we can remove it and base everything in something at
# payment mode or company level
reference_type = fields.Selection(
selection=[
('none', 'Free Reference'),
('structured', 'Structured Reference'),
],
string='Payment Reference',
required=True,
readonly=True,
states={'draft': [('readonly', False)]},
default='none',
)
@api.depends('payment_mode_id', 'move_id', 'move_id.line_ids',
'move_id.line_ids.payment_mode_id')
def _compute_payment_order_ok(self):
for invoice in self:
payment_mode = (
invoice.move_id.line_ids.filtered(
lambda x: not x.reconciled
).mapped('payment_mode_id')[:1]
)
if not payment_mode:
payment_mode = invoice.payment_mode_id
invoice.payment_order_ok = payment_mode.payment_order_ok
@api.model
def line_get_convert(self, line, part):
"""Copy supplier bank account 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 ('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, payment_mode=None):
self.ensure_one()
if payment_mode is None:
payment_mode = self.env['account.payment.mode']
vals = {
'payment_mode_id': payment_mode.id or self.payment_mode_id.id,
}
# other important fields are set by the inherit of create
# in account_payment_order.py
return vals
@api.multi
def create_account_payment_line(self):
apoo = self.env['account.payment.order']
result_payorder_ids = []
action_payment_type = 'debit'
for inv in self:
if inv.state != 'open':
raise UserError(_(
"The invoice %s is not in Open state") % inv.number)
if not inv.move_id:
raise UserError(_(
"No Journal Entry on invoice %s") % inv.number)
applicable_lines = inv.move_id.line_ids.filtered(
lambda x: (
not x.reconciled and x.payment_mode_id.payment_order_ok and
x.account_id.internal_type in ('receivable', 'payable') and
not x.payment_line_ids
)
)
if not applicable_lines:
raise UserError(_(
'No Payment Line created for invoice %s because '
'it already exists or because this invoice is '
'already paid.') % inv.number)
payment_modes = applicable_lines.mapped('payment_mode_id')
if not payment_modes:
raise UserError(_(
"No Payment Mode on invoice %s") % inv.number)
for payment_mode in payment_modes:
payorder = apoo.search([
('payment_mode_id', '=', payment_mode.id),
('state', '=', 'draft')
], limit=1)
new_payorder = False
if not payorder:
payorder = apoo.create(inv._prepare_new_payment_order(
payment_mode
))
new_payorder = True
result_payorder_ids.append(payorder.id)
action_payment_type = payorder.payment_type
count = 0
for line in applicable_lines.filtered(
lambda x: x.payment_mode_id == payment_mode
):
line.create_payment_line_from_move_line(payorder)
count += 1
if new_payorder:
inv.message_post(body=_(
'%d payment lines added to the new draft payment '
'order %s which has been automatically created.')
% (count, payorder.name))
else:
inv.message_post(body=_(
'%d payment lines added to the existing draft '
'payment order %s.')
% (count, payorder.name))
action = self.env['ir.actions.act_window'].for_xml_id(
'account_payment_order',
'account_payment_order_%s_action' % action_payment_type)
if len(result_payorder_ids) == 1:
action.update({
'view_mode': 'form,tree,pivot,graph',
'res_id': payorder.id,
'views': False,
})
else:
action.update({
'view_mode': 'tree,form,pivot,graph',
'domain': "[('id', 'in', %s)]" % result_payorder_ids,
'views': False,
})
return action

View File

@@ -0,0 +1,12 @@
# © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields
class AccountMove(models.Model):
_inherit = 'account.move'
payment_order_id = fields.Many2one(
'account.payment.order', string='Payment Order', copy=False,
readonly=True)

View File

@@ -0,0 +1,135 @@
# © 2014-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from lxml import etree
from odoo import models, fields, api
from odoo.osv import orm
class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
partner_bank_id = fields.Many2one(
'res.partner.bank', string='Partner Bank Account',
help='Bank account on which we should pay the supplier')
bank_payment_line_id = fields.Many2one(
'bank.payment.line', string='Bank Payment Line',
readonly=True)
payment_line_ids = fields.One2many(
comodel_name='account.payment.line',
inverse_name='move_line_id',
string="Payment lines",
)
@api.multi
def _prepare_payment_line_vals(self, payment_order):
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.ref or self.move_id.name
# 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
else:
currency_id = self.company_id.currency_id.id
amount_currency = self.amount_residual
# TODO : check that self.amount_residual_currency is 0
# in this case
if payment_order.payment_type == 'outbound':
amount_currency *= -1
partner_bank_id = False
if not self.partner_bank_id:
# Select partner bank account automatically if there is only one
if len(self.partner_id.bank_ids) == 1:
partner_bank_id = self.partner_id.bank_ids[0].id
else:
partner_bank_id = self.partner_bank_id.id
vals = {
'order_id': payment_order.id,
'partner_bank_id': partner_bank_id,
'partner_id': self.partner_id.id,
'move_line_id': self.id,
'communication': communication,
'communication_type': communication_type,
'currency_id': currency_id,
'amount_currency': amount_currency,
# date is set when the user confirms the payment order
}
return vals
@api.multi
def create_payment_line_from_move_line(self, payment_order):
aplo = self.env['account.payment.line']
for mline in self:
aplo.create(mline._prepare_payment_line_vals(payment_order))
return
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False,
submenu=False):
# When the user looks for open payables or receivables, in the
# context of payment orders, she should focus primarily on amount that
# is due to be paid, and secondarily on the total amount. In this
# method we are forcing to display both the amount due in company and
# in the invoice currency.
# We then hide the fields debit and credit, because they add no value.
result = super(AccountMoveLine, self).fields_view_get(view_id,
view_type,
toolbar=toolbar,
submenu=submenu)
doc = etree.XML(result['arch'])
if view_type == 'tree' and self._module == 'account_payment_order':
if not doc.xpath("//field[@name='balance']"):
for placeholder in doc.xpath(
"//field[@name='amount_currency']"):
elem = etree.Element(
'field', {
'name': 'balance',
'readonly': 'True'
})
orm.setup_modifiers(elem)
placeholder.addprevious(elem)
if not doc.xpath("//field[@name='amount_residual_currency']"):
for placeholder in doc.xpath(
"//field[@name='amount_currency']"):
elem = etree.Element(
'field', {
'name': 'amount_residual_currency',
'readonly': 'True'
})
orm.setup_modifiers(elem)
placeholder.addnext(elem)
if not doc.xpath("//field[@name='amount_residual']"):
for placeholder in doc.xpath(
"//field[@name='amount_currency']"):
elem = etree.Element(
'field', {
'name': 'amount_residual',
'readonly': 'True'
})
orm.setup_modifiers(elem)
placeholder.addnext(elem)
# Remove credit and debit data - which is irrelevant in this case
for elem in doc.xpath("//field[@name='debit']"):
doc.remove(elem)
for elem in doc.xpath("//field[@name='credit']"):
doc.remove(elem)
result['arch'] = etree.tostring(doc)
return result

View File

@@ -0,0 +1,138 @@
# © 2015-2016 Akretion - Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class AccountPaymentLine(models.Model):
_name = 'account.payment.line'
_description = 'Payment Lines'
name = fields.Char(string='Payment Reference', readonly=True, copy=False)
order_id = fields.Many2one(
'account.payment.order', string='Payment Order',
ondelete='cascade', index=True)
company_id = fields.Many2one(
related='order_id.company_id', store=True, readonly=True)
company_currency_id = fields.Many2one(
related='order_id.company_currency_id', store=True, readonly=True)
payment_type = fields.Selection(
related='order_id.payment_type', store=True, readonly=True)
bank_account_required = fields.Boolean(
related='order_id.payment_method_id.bank_account_required',
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',
ondelete='restrict')
ml_maturity_date = fields.Date(
related='move_line_id.date_maturity', readonly=True)
currency_id = fields.Many2one(
'res.currency', string='Currency of the Payment Transaction',
required=True,
default=lambda self: self.env.user.company_id.currency_id)
# v8 field : currency
amount_currency = fields.Monetary(
string="Amount", currency_field='currency_id')
amount_company_currency = fields.Monetary(
compute='_compute_amount_company_currency',
string='Amount in Company Currency', readonly=True,
currency_field='company_currency_id') # v8 field : amount
partner_id = fields.Many2one(
'res.partner', string='Partner', required=True,
domain=[('parent_id', '=', False)])
partner_bank_id = fields.Many2one(
'res.partner.bank', string='Partner Bank Account', required=False,
ondelete='restrict') # v8 field : bank_id
date = fields.Date(string='Payment Date')
communication = fields.Char(
string='Communication', required=True,
help="Label of the payment that will be seen by the destinee")
communication_type = fields.Selection([
('normal', 'Free'),
], string='Communication Type', required=True, default='normal')
# v8 field : state
bank_line_id = fields.Many2one(
'bank.payment.line', string='Bank Payment Line', readonly=True)
_sql_constraints = [(
'name_company_unique',
'unique(name, company_id)',
'A payment line already exists with this reference '
'in the same company!'
)]
@api.model
def create(self, vals):
if vals.get('name', 'New') == 'New':
vals['name'] = self.env['ir.sequence'].next_by_code(
'account.payment.line') or 'New'
return super(AccountPaymentLine, self).create(vals)
@api.multi
@api.depends(
'amount_currency', 'currency_id', 'company_currency_id', 'date')
def _compute_amount_company_currency(self):
for line in self:
if line.currency_id and line.company_currency_id:
line.amount_company_currency = line.currency_id._convert(
line.amount_currency, line.company_currency_id,
line.company_id, line.date or fields.Date.today(),
)
@api.multi
def payment_line_hashcode(self):
self.ensure_one()
bplo = self.env['bank.payment.line']
values = []
for field in bplo.same_fields_payment_line_and_bank_payment_line():
values.append(str(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
values.append(str(self.move_line_id.account_id or False))
# Don't group the payment lines that use a structured communication
# otherwise it would break the structured communication system !
if self.communication_type != 'normal':
values.append(str(self.id))
hashcode = '-'.join(values)
return hashcode
@api.onchange('partner_id')
def partner_id_change(self):
partner_bank = False
if self.partner_id.bank_ids:
partner_bank = self.partner_id.bank_ids[0]
self.partner_bank_id = partner_bank
@api.onchange('move_line_id')
def move_line_id_change(self):
if self.move_line_id:
vals = self.move_line_id._prepare_payment_line_vals(self.order_id)
vals.pop('order_id')
for field, value in vals.items():
self[field] = value
else:
self.partner_id = False
self.partner_bank_id = False
self.amount_currency = 0.0
self.currency_id = self.env.user.company_id.currency_id
self.communication = False
def invoice_reference_type2communication_type(self):
"""This method is designed to be inherited by
localization modules"""
# key = value of 'reference_type' field on account_invoice
# value = value of 'communication_type' field on account_payment_line
res = {'none': 'normal', 'structured': 'structured'}
return res
@api.multi
def draft2open_payment_line_check(self):
self.ensure_one()
if self.bank_account_required and not self.partner_bank_id:
raise UserError(_(
'Missing Partner Bank Account on payment line %s') % self.name)

View File

@@ -0,0 +1,148 @@
# © 2009 EduSense BV (<http://www.edusense.nl>)
# © 2011-2013 Therp BV (<https://therp.nl>)
# © 2014-2016 Serv. Tecnol. Avanzados - Pedro M. Baeza
# © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class AccountPaymentMode(models.Model):
"""This corresponds to the object payment.mode of v8 with some
important changes"""
_inherit = "account.payment.mode"
payment_order_ok = fields.Boolean(
string='Selectable in Payment Orders', default=True)
no_debit_before_maturity = fields.Boolean(
string="Disallow Debit Before Maturity Date",
help="If you activate this option on an Inbound payment mode, "
"you will have an error message when you confirm a debit order "
"that has a payment line with a payment date before the maturity "
"date.")
# Default options for the "payment.order.create" wizard
default_payment_mode = fields.Selection([
('same', 'Same'),
('same_or_null', 'Same or empty'),
('any', 'Any'),
], string='Payment Mode on Invoice', default='same')
default_journal_ids = fields.Many2many(
'account.journal',
string="Journals Filter",
domain="[('company_id', '=', company_id)]"
)
default_invoice = fields.Boolean(
string='Linked to an Invoice or Refund', default=False)
default_target_move = fields.Selection([
('posted', 'All Posted Entries'),
('all', 'All Entries'),
], string='Target Moves', default='posted')
default_date_type = fields.Selection([
('due', 'Due'),
('move', 'Move'),
], default='due', string="Type of Date Filter")
# default option for account.payment.order
default_date_prefered = fields.Selection([
('now', 'Immediately'),
('due', 'Due Date'),
('fixed', 'Fixed Date'),
], string='Default Payment Execution Date')
group_lines = fields.Boolean(
string="Group Transactions in Payment Orders", default=True,
help="If this mark is checked, the transaction lines of the "
"payment order will be grouped upon confirmation of the payment "
"order.The grouping will be done only if the following "
"fields matches:\n"
"* Partner\n"
"* Currency\n"
"* Destination Bank Account\n"
"* Payment Date\n"
"and if the 'Communication Type' is 'Free'\n"
"(other modules can set additional fields to restrict the "
"grouping.)")
generate_move = fields.Boolean(
string='Generate Accounting Entries On File Upload', default=True)
offsetting_account = fields.Selection([
('bank_account', 'Bank Account'),
('transfer_account', 'Transfer Account'),
], string='Offsetting Account', default='bank_account')
transfer_account_id = fields.Many2one(
'account.account', string='Transfer Account',
domain=[('reconcile', '=', True)],
help="Pay off lines in 'file uploaded' payment orders with a move on "
"this account. You can only select accounts "
"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')
move_option = fields.Selection([
('date', 'One move per payment date'),
('line', 'One move per payment line'),
], string='Move Option', default='date')
post_move = fields.Boolean(string='Post Move', default=True)
@api.multi
@api.constrains(
'generate_move', 'offsetting_account',
'transfer_account_id', 'transfer_journal_id', 'move_option')
def transfer_move_constrains(self):
for mode in self:
if mode.generate_move:
if not mode.offsetting_account:
raise ValidationError(_(
"On the payment mode '%s', you must select an "
"option for the 'Offsetting Account' parameter")
% mode.name)
elif mode.offsetting_account == 'transfer_account':
if not mode.transfer_account_id:
raise ValidationError(_(
"On the payment mode '%s', you must "
"select a value for the 'Transfer Account'.")
% mode.name)
if not mode.transfer_journal_id:
raise ValidationError(_(
"On the payment mode '%s', you must "
"select a value for the 'Transfer Journal'.")
% mode.name)
if not mode.move_option:
raise ValidationError(_(
"On the payment mode '%s', you must "
"choose an option for the 'Move Option' "
"parameter.") % mode.name)
@api.onchange('payment_method_id')
def payment_method_id_change(self):
if self.payment_method_id:
ajo = self.env['account.journal']
aj_ids = []
if self.payment_method_id.payment_type == 'outbound':
aj_ids = ajo.search([
('type', 'in', ('purchase_refund', 'purchase')),
('company_id', '=', self.company_id.id)
]).ids
elif self.payment_method_id.payment_type == 'inbound':
aj_ids = ajo.search([
('type', 'in', ('sale_refund', 'sale')),
('company_id', '=', self.company_id.id)
]).ids
self.default_journal_ids = [(6, 0, aj_ids)]
@api.onchange('generate_move')
def generate_move_change(self):
if self.generate_move:
# default values
self.offsetting_account = 'bank_account'
self.move_option = 'date'
else:
self.offsetting_account = False
self.transfer_account_id = False
self.transfer_journal_id = False
self.move_option = False
@api.onchange('offsetting_account')
def offsetting_account_change(self):
if self.offsetting_account == 'bank_account':
self.transfer_account_id = False
self.transfer_journal_id = False

View File

@@ -0,0 +1,500 @@
# © 2009 EduSense BV (<http://www.edusense.nl>)
# © 2011-2013 Therp BV (<https://therp.nl>)
# © 2016 Serv. Tecnol. Avanzados - Pedro M. Baeza
# © 2016 Akretion (Alexis de Lattre - alexis.delattre@akretion.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import base64
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
class AccountPaymentOrder(models.Model):
_name = 'account.payment.order'
_description = 'Payment Order'
_inherit = ['mail.thread']
_order = 'id desc'
def domain_journal_id(self):
if not self.payment_mode_id:
return [('id', '=', False)]
if self.payment_mode_id.bank_account_link == 'fixed':
return [('id', '=', self.payment_mode_id.fixed_journal_id.id)]
elif self.payment_mode_id.bank_account_link == 'variable':
jrl_ids = self.payment_mode_id.variable_journal_ids.ids
return [('id', 'in', jrl_ids)]
name = fields.Char(
string='Number', readonly=True, copy=False) # v8 field : name
payment_mode_id = fields.Many2one(
'account.payment.mode', 'Payment Mode', required=True,
ondelete='restrict', track_visibility='onchange',
readonly=True, states={'draft': [('readonly', False)]})
payment_type = fields.Selection([
('inbound', 'Inbound'),
('outbound', 'Outbound'),
], string='Payment Type', readonly=True, required=True)
payment_method_id = fields.Many2one(
'account.payment.method', related='payment_mode_id.payment_method_id',
readonly=True, store=True)
company_id = fields.Many2one(
related='payment_mode_id.company_id', store=True, readonly=True)
company_currency_id = fields.Many2one(
related='payment_mode_id.company_id.currency_id', store=True,
readonly=True)
bank_account_link = fields.Selection(
related='payment_mode_id.bank_account_link', readonly=True)
journal_id = fields.Many2one(
'account.journal', string='Bank Journal', ondelete='restrict',
readonly=True, states={'draft': [('readonly', False)]},
domain=domain_journal_id,
track_visibility='onchange')
# 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)
state = fields.Selection(
[
('draft', 'Draft'),
('open', 'Confirmed'),
('generated', 'File Generated'),
('uploaded', 'File Uploaded'),
('done', 'Done'),
('cancel', 'Cancel'),
], string='Status', readonly=True, copy=False, default='draft',
track_visibility='onchange')
date_prefered = fields.Selection([
('now', 'Immediately'),
('due', 'Due Date'),
('fixed', 'Fixed Date'),
], string='Payment Execution Date Type', required=True, default='due',
track_visibility='onchange', readonly=True,
states={'draft': [('readonly', False)]})
date_scheduled = fields.Date(
string='Payment Execution Date', readonly=True,
states={'draft': [('readonly', False)]}, track_visibility='onchange',
help="Select a requested date of execution if you selected 'Due Date' "
"as the Payment Execution Date Type.")
date_generated = fields.Date(string='File Generation Date', readonly=True)
date_uploaded = fields.Date(string='File Upload Date', readonly=True)
date_done = fields.Date(string='Done Date', readonly=True)
generated_user_id = fields.Many2one(
'res.users', string='Generated by', readonly=True, ondelete='restrict',
copy=False)
payment_line_ids = fields.One2many(
'account.payment.line', 'order_id', string='Transaction Lines',
readonly=True, states={'draft': [('readonly', False)]})
# v8 field : line_ids
bank_line_ids = fields.One2many(
'bank.payment.line', 'order_id', string="Bank Payment Lines",
readonly=True,
help="The bank payment lines are used to generate the payment file. "
"They are automatically created from transaction lines upon "
"confirmation of the payment order: one bank payment line can "
"group several transaction lines if the option "
"'Group Transactions in Payment Orders' is active on the payment "
"mode.")
total_company_currency = fields.Monetary(
compute='_compute_total', store=True, readonly=True,
currency_field='company_currency_id')
bank_line_count = fields.Integer(
compute='_compute_bank_line_count', string='Number of Bank Lines',
readonly=True)
move_ids = fields.One2many(
'account.move', 'payment_order_id', string='Journal Entries',
readonly=True)
description = fields.Char()
@api.multi
def unlink(self):
for order in self:
if order.state == 'uploaded':
raise UserError(_(
"You cannot delete an uploaded payment order. You can "
"cancel it in order to do so."))
return super(AccountPaymentOrder, self).unlink()
@api.multi
@api.constrains('payment_type', 'payment_mode_id')
def payment_order_constraints(self):
for order in self:
if (
order.payment_mode_id.payment_type and
order.payment_mode_id.payment_type != order.payment_type):
raise ValidationError(_(
"The payment type (%s) is not the same as the payment "
"type of the payment mode (%s)") % (
order.payment_type,
order.payment_mode_id.payment_type))
@api.multi
@api.constrains('date_scheduled')
def check_date_scheduled(self):
today = fields.Date.context_today(self)
for order in self:
if order.date_scheduled:
if order.date_scheduled < today:
raise ValidationError(_(
"On payment order %s, the Payment Execution Date "
"is in the past (%s).")
% (order.name, order.date_scheduled))
@api.multi
@api.depends(
'payment_line_ids', 'payment_line_ids.amount_company_currency')
def _compute_total(self):
for rec in self:
rec.total_company_currency = sum(
rec.mapped('payment_line_ids.amount_company_currency') or
[0.0])
@api.multi
@api.depends('bank_line_ids')
def _compute_bank_line_count(self):
for order in self:
order.bank_line_count = len(order.bank_line_ids)
@api.model
def create(self, vals):
if vals.get('name', 'New') == 'New':
vals['name'] = self.env['ir.sequence'].next_by_code(
'account.payment.order') or 'New'
if vals.get('payment_mode_id'):
payment_mode = self.env['account.payment.mode'].browse(
vals['payment_mode_id'])
vals['payment_type'] = payment_mode.payment_type
if payment_mode.bank_account_link == 'fixed':
vals['journal_id'] = payment_mode.fixed_journal_id.id
if (
not vals.get('date_prefered') and
payment_mode.default_date_prefered):
vals['date_prefered'] = payment_mode.default_date_prefered
return super(AccountPaymentOrder, self).create(vals)
@api.onchange('payment_mode_id')
def payment_mode_id_change(self):
domain = self.domain_journal_id()
res = {'domain': {
'journal_id': domain
}}
journals = self.env['account.journal'].search(domain)
if len(journals) == 1:
self.journal_id = journals
if self.payment_mode_id.default_date_prefered:
self.date_prefered = self.payment_mode_id.default_date_prefered
return res
@api.multi
def action_done(self):
self.write({
'date_done': fields.Date.context_today(self),
'state': 'done',
})
return True
@api.multi
def action_done_cancel(self):
for move in self.move_ids:
move.button_cancel()
for move_line in move.line_ids:
move_line.remove_move_reconcile()
move.unlink()
self.action_cancel()
return True
@api.multi
def cancel2draft(self):
self.write({'state': 'draft'})
return True
@api.multi
def action_cancel(self):
for order in self:
order.write({'state': 'cancel'})
order.bank_line_ids.unlink()
return True
@api.model
def _prepare_bank_payment_line(self, paylines):
return {
'order_id': paylines[0].order_id.id,
'payment_line_ids': [(6, 0, paylines.ids)],
'communication': '-'.join(
[line.communication for line in paylines]),
}
@api.multi
def draft2open(self):
"""
Called when you click on the 'Confirm' button
Set the 'date' on payment line depending on the 'date_prefered'
setting of the payment.order
Re-generate the bank payment lines
"""
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)
if (
order.payment_method_id.bank_account_required and
not order.journal_id.bank_account_id):
raise UserError(_(
"Missing bank account on bank journal '%s'.")
% order.journal_id.display_name)
if not order.payment_line_ids:
raise UserError(_(
'There are no transactions 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
group_paylines = {} # key = hashcode
for payline in order.payment_line_ids:
payline.draft2open_payment_line_check()
# Compute requested payment date
if order.date_prefered == 'due':
requested_date = payline.ml_maturity_date or today
elif order.date_prefered == 'fixed':
requested_date = order.date_scheduled or today
else:
requested_date = today
# No payment date in the past
if requested_date < today:
requested_date = today
# inbound: check option no_debit_before_maturity
if (
order.payment_type == 'inbound' and
order.payment_mode_id.no_debit_before_maturity and
payline.ml_maturity_date and
requested_date < payline.ml_maturity_date):
raise UserError(_(
"The payment mode '%s' has the option "
"'Disallow Debit Before Maturity Date'. The "
"payment line %s has a maturity date %s "
"which is after the computed payment date %s.") % (
order.payment_mode_id.name,
payline.name,
payline.ml_maturity_date,
requested_date))
# Write requested_date on 'date' field of payment line
payline.date = requested_date
# Group options
if order.payment_mode_id.group_lines:
hashcode = payline.payment_line_hashcode()
else:
# Use line ID as hascode, which actually means no grouping
hashcode = payline.id
if hashcode in group_paylines:
group_paylines[hashcode]['paylines'] += payline
group_paylines[hashcode]['total'] +=\
payline.amount_currency
else:
group_paylines[hashcode] = {
'paylines': payline,
'total': payline.amount_currency,
}
# Create bank payment lines
for paydict in list(group_paylines.values()):
# Block if a bank payment line is <= 0
if paydict['total'] <= 0:
raise UserError(_(
"The amount for Partner '%s' is negative "
"or null (%.2f) !")
% (paydict['paylines'][0].partner_id.name,
paydict['total']))
vals = self._prepare_bank_payment_line(paydict['paylines'])
bplo.create(vals)
self.write({'state': 'open'})
return True
@api.multi
def generate_payment_file(self):
"""Returns (payment file as string, filename)"""
self.ensure_one()
if self.payment_method_id.code == 'manual':
return (False, False)
else:
raise UserError(_(
"No handler for this payment method. Maybe you haven't "
"installed the related Odoo module."))
@api.multi
def open2generated(self):
self.ensure_one()
payment_file_str, filename = self.generate_payment_file()
action = {}
if payment_file_str and filename:
attachment = self.env['ir.attachment'].create({
'res_model': 'account.payment.order',
'res_id': self.id,
'name': filename,
'datas': base64.b64encode(payment_file_str),
'datas_fname': filename,
})
simplified_form_view = self.env.ref(
'account_payment_order.view_attachment_simplified_form')
action = {
'name': _('Payment File'),
'view_mode': 'form',
'view_id': simplified_form_view.id,
'res_model': 'ir.attachment',
'type': 'ir.actions.act_window',
'target': 'current',
'res_id': attachment.id,
}
self.write({
'date_generated': fields.Date.context_today(self),
'state': 'generated',
'generated_user_id': self._uid,
})
return action
@api.multi
def generated2uploaded(self):
for order in self:
if order.payment_mode_id.generate_move:
order.generate_move()
self.write({
'state': 'uploaded',
'date_uploaded': fields.Date.context_today(self),
})
return True
@api.multi
def _prepare_move(self, bank_lines=None):
if self.payment_type == 'outbound':
ref = _('Payment order %s') % self.name
else:
ref = _('Debit order %s') % self.name
if bank_lines and len(bank_lines) == 1:
ref += " - " + bank_lines.name
if self.payment_mode_id.offsetting_account == 'bank_account':
journal_id = self.journal_id.id
elif self.payment_mode_id.offsetting_account == 'transfer_account':
journal_id = self.payment_mode_id.transfer_journal_id.id
vals = {
'journal_id': journal_id,
'ref': ref,
'payment_order_id': self.id,
'line_ids': [],
}
return vals
@api.multi
def _prepare_move_line_offsetting_account(
self, amount_company_currency, amount_payment_currency,
bank_lines):
vals = {}
if self.payment_type == 'outbound':
name = _('Payment order %s') % self.name
else:
name = _('Debit order %s') % self.name
if self.payment_mode_id.offsetting_account == 'bank_account':
vals.update({'date': bank_lines[0].date})
else:
vals.update({'date_maturity': bank_lines[0].date})
if self.payment_mode_id.offsetting_account == 'bank_account':
account_id = self.journal_id.default_debit_account_id.id
elif self.payment_mode_id.offsetting_account == 'transfer_account':
account_id = self.payment_mode_id.transfer_account_id.id
partner_id = False
for index, bank_line in enumerate(bank_lines):
if index == 0:
partner_id = bank_line.payment_line_ids[0].partner_id.id
elif bank_line.payment_line_ids[0].partner_id.id != partner_id:
# we have different partners in the grouped move
partner_id = False
break
vals.update({
'name': name,
'partner_id': partner_id,
'account_id': account_id,
'credit': (self.payment_type == 'outbound' and
amount_company_currency or 0.0),
'debit': (self.payment_type == 'inbound' and
amount_company_currency or 0.0),
})
if bank_lines[0].currency_id != bank_lines[0].company_currency_id:
sign = self.payment_type == 'outbound' and -1 or 1
vals.update({
'currency_id': bank_lines[0].currency_id.id,
'amount_currency': amount_payment_currency * sign,
})
return vals
@api.multi
def _prepare_move_line_partner_account(self, bank_line):
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_type == 'inbound':
account_id =\
bank_line.partner_id.property_account_receivable_id.id
else:
account_id =\
bank_line.partner_id.property_account_payable_id.id
if self.payment_type == 'outbound':
name = _('Payment bank line %s') % bank_line.name
else:
name = _('Debit bank line %s') % bank_line.name
vals = {
'name': name,
'bank_payment_line_id': bank_line.id,
'partner_id': bank_line.partner_id.id,
'account_id': account_id,
'credit': (self.payment_type == 'inbound' and
bank_line.amount_company_currency or 0.0),
'debit': (self.payment_type == 'outbound' and
bank_line.amount_company_currency or 0.0),
}
if bank_line.currency_id != bank_line.company_currency_id:
sign = self.payment_type == 'inbound' and -1 or 1
vals.update({
'currency_id': bank_line.currency_id.id,
'amount_currency': bank_line.amount_currency * sign,
})
return vals
@api.multi
def generate_move(self):
"""
Create the moves that pay off the move lines from
the payment/debit order.
"""
self.ensure_one()
am_obj = self.env['account.move']
post_move = self.payment_mode_id.post_move
# prepare a dict "trfmoves" that can be used when
# self.payment_mode_id.move_option = date or line
# key = unique identifier (date or True or line.id)
# value = bank_pay_lines (recordset that can have several entries)
trfmoves = {}
for bline in self.bank_line_ids:
hashcode = bline.move_line_offsetting_account_hashcode()
if hashcode in trfmoves:
trfmoves[hashcode] += bline
else:
trfmoves[hashcode] = bline
for hashcode, blines in trfmoves.items():
mvals = self._prepare_move(blines)
total_company_currency = total_payment_currency = 0
for bline in blines:
total_company_currency += bline.amount_company_currency
total_payment_currency += bline.amount_currency
partner_ml_vals = self._prepare_move_line_partner_account(
bline)
mvals['line_ids'].append((0, 0, partner_ml_vals))
trf_ml_vals = self._prepare_move_line_offsetting_account(
total_company_currency, total_payment_currency, blines)
mvals['line_ids'].append((0, 0, trf_ml_vals))
move = am_obj.create(mvals)
blines.reconcile_payment_lines()
if post_move:
move.post()

View File

@@ -0,0 +1,170 @@
# © 2015-2016 Akretion - Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class BankPaymentLine(models.Model):
_name = 'bank.payment.line'
_description = 'Bank Payment Lines'
name = fields.Char(
string='Bank Payment Line Ref', required=True,
readonly=True)
order_id = fields.Many2one(
'account.payment.order', string='Order', ondelete='cascade',
index=True, readonly=True)
payment_type = fields.Selection(
related='order_id.payment_type', string="Payment Type",
readonly=True, store=True)
state = fields.Selection(
related='order_id.state', string='State',
readonly=True, store=True)
payment_line_ids = fields.One2many(
'account.payment.line', 'bank_line_id', string='Payment Lines',
readonly=True)
partner_id = fields.Many2one(
'res.partner', related='payment_line_ids.partner_id',
readonly=True, store=True) # store=True for groupby
# 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)
amount_company_currency = fields.Monetary(
string='Amount in Company Currency',
currency_field='company_currency_id',
compute='_compute_amount', store=True, readonly=True)
currency_id = fields.Many2one(
'res.currency', required=True, readonly=True,
related='payment_line_ids.currency_id') # v8 field: currency
partner_bank_id = fields.Many2one(
'res.partner.bank', string='Bank Account', readonly=True,
related='payment_line_ids.partner_bank_id') # v8 field: bank_id
date = fields.Date(
related='payment_line_ids.date', readonly=True)
communication_type = fields.Selection(
related='payment_line_ids.communication_type', readonly=True)
communication = fields.Char(
string='Communication', required=True,
readonly=True)
company_id = fields.Many2one(
'res.company',
related='order_id.payment_mode_id.company_id', store=True,
readonly=True)
company_currency_id = fields.Many2one(
'res.currency',
related='order_id.payment_mode_id.company_id.currency_id',
readonly=True, store=True)
@api.model
def same_fields_payment_line_and_bank_payment_line(self):
"""
This list of fields is used both to compute the grouping
hashcode and to copy the values from payment line
to bank payment line
The fields must have the same name on the 2 objects
"""
same_fields = [
'currency_id', 'partner_id',
'partner_bank_id', 'date', 'communication_type']
return same_fields
@api.multi
@api.depends('payment_line_ids', 'payment_line_ids.amount_currency')
def _compute_amount(self):
for bline in self:
amount_currency = sum(
bline.mapped('payment_line_ids.amount_currency'))
amount_company_currency = bline.currency_id._convert(
amount_currency, bline.company_currency_id, bline.company_id,
bline.date or fields.Date.today(),
)
bline.amount_currency = amount_currency
bline.amount_company_currency = amount_company_currency
@api.model
@api.returns('self')
def create(self, vals):
if vals.get('name', 'New') == 'New':
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_offsetting_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.move_option == 'date':
hashcode = self.date
else:
hashcode = str(self.id)
return hashcode
@api.multi
def reconcile_payment_lines(self):
for bline in self:
if all([pline.move_line_id for pline in bline.payment_line_ids]):
bline.reconcile()
else:
bline.no_reconcile_hook()
@api.multi
def no_reconcile_hook(self):
"""This method is designed to be inherited if needed"""
return
@api.multi
def reconcile(self):
self.ensure_one()
amlo = self.env['account.move.line']
transit_mlines = amlo.search([('bank_payment_line_id', '=', self.id)])
assert len(transit_mlines) == 1, 'We should have only 1 move'
transit_mline = transit_mlines[0]
assert not transit_mline.reconciled,\
'Transit move should not be reconciled'
lines_to_rec = transit_mline
for payment_line in self.payment_line_ids:
if not payment_line.move_line_id:
raise UserError(_(
"Can not reconcile: no move line for "
"payment line %s of partner '%s'.") % (
payment_line.name,
payment_line.partner_id.name))
if payment_line.move_line_id.reconciled:
raise UserError(_(
"Move line '%s' of partner '%s' has already "
"been reconciled") % (
payment_line.move_line_id.name,
payment_line.partner_id.name))
if (
payment_line.move_line_id.account_id !=
transit_mline.account_id):
raise UserError(_(
"For partner '%s', the account of the account "
"move line to pay (%s) is different from the "
"account of of the transit move line (%s).") % (
payment_line.move_line_id.partner_id.name,
payment_line.move_line_id.account_id.code,
transit_mline.account_id.code))
lines_to_rec += payment_line.move_line_id
lines_to_rec.reconcile()
@api.multi
def unlink(self):
for line in self:
order_state = line.order_id.state
if order_state == 'uploaded':
raise UserError(_(
'Cannot delete a payment order line whose payment order is'
' in state \'%s\'. You need to cancel it first.')
% order_state)
return super(BankPaymentLine, self).unlink()

View File

@@ -0,0 +1,21 @@
# © 2015-2016 Akretion - Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, api, _
from odoo.exceptions import ValidationError
class ResBank(models.Model):
_inherit = 'res.bank'
@api.multi
@api.constrains('bic')
def check_bic_length(self):
for bank in self:
if bank.bic and len(bank.bic) not in (8, 11):
raise ValidationError(_(
"A valid BIC contains 8 or 11 characters. The BIC '%s' "
"contains %d characters, so it is not valid.")
% (bank.bic, len(bank.bic)))
# starting from v9, on res.partner.bank bank_bic is a related of bank_id.bic

View File

@@ -0,0 +1,2 @@
This module adds several options on Payment Modes, cf Invoicing/Accounting >
Configuration > Management > Payment Modes.

View File

@@ -0,0 +1,15 @@
* Stéphane Bidoul <stephane.bidoul@acsone.eu>
* Alexis de Lattre <alexis.delattre@akretion.com>
* Pedro M. Baeza
* Adrien Peiffer <adrien.peiffer@acsone.eu>
* Stefan Rijnhart
* Laurent Mignon <laurent.mignon@acsone.eu>
* Alexandre Fayolle
* Danimar Ribeiro
* Erwin van der Ploeg
* Raphaël Valyi
* Sandy Carter
* Angel Moya <angel.moya@domatix.com>
* Jose María Alzaga <jose.alzaga@aselcis.com>
* Meyomesse Gilles <meyomesse.gilles@gmail.com>
* Carlos Dauden

View File

@@ -0,0 +1 @@
This module adds support for payment orders and debit orders.

View File

@@ -0,0 +1,7 @@
This module depends on:
* account_payment_partner
* base_iban
* document
This modules is part of the OCA/bank-payment suite.

View File

@@ -0,0 +1,7 @@
You can create a Payment order via the menu Invoicing/Accounting > Payments > Payment Orders and then select the move lines to pay.
You can create a Debit order via the menu Invoicing/Accounting > Payments > Debit Orders and then select the move lines to debit.
This module also adds a button *Add to Payment Order* on supplier invoices and a button *Add to Debit Order* on customer invoices.
You can print a Payment order via the menu Invoicing/Accounting > Payments > Payment Orders and then select the payment oder to print.

View File

@@ -0,0 +1 @@
from . import account_payment_order

View File

@@ -0,0 +1,46 @@
# © 2017 Acsone SA/NV (<https://www.acsone.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, models
from odoo.tools.misc import formatLang
class AccountPaymentOrderReport(models.AbstractModel):
_name = 'report.account_payment_order.print_account_payment_order_main'
_description = 'Technical model for printing payment order'
@api.model
def get_report_values(self, docids, data=None):
AccountPaymentOrderObj = self.env['account.payment.order']
docs = AccountPaymentOrderObj.browse(docids)
return {
'doc_ids': docids,
'doc_model': 'account.payment.order',
'docs': docs,
'data': data,
'env': self.env,
'get_bank_account_name': self.get_bank_account_name,
'formatLang': formatLang,
}
@api.model
def get_bank_account_name(self, partner_bank):
"""
:param partner_bank:
:return:
"""
if partner_bank:
name = ''
if partner_bank.bank_name:
name = '%s: ' % partner_bank.bank_id.name
if partner_bank.acc_number:
name = '%s %s' % (name, partner_bank.acc_number)
if partner_bank.bank_bic:
name = '%s - ' % (name)
if partner_bank.bank_bic:
name = '%s BIC %s' % (name, partner_bank.bank_bic)
return name
else:
return False

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 ACSONE SA/NV
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>
<template id="print_account_payment_order_document">
<t t-set="doc" t-value="doc.with_context({'lang':doc.generated_user_id.lang})" />
<t t-call="web.external_layout">
<div class="page">
<div class="oe_structure"/>
<div class="row">
<div class="col-xs-4 col-xs-offset-8">
<span t-raw="'%s &lt;br&gt;' % doc.journal_id.bank_id.name if doc.journal_id.bank_id.name else ''"/>
<span t-raw="'%s &lt;br&gt;' % doc.journal_id.bank_id.street if doc.journal_id.bank_id.street else ''"/>
<span t-raw="'%s &lt;br&gt;' % doc.journal_id.bank_id.street2 if doc.journal_id.bank_id.street2 else ''"/>
<span t-raw="'%s &lt;br&gt;' % doc.journal_id.bank_id.zip if doc.journal_id.bank_id.zip else ''"/>
<span t-raw="'%s &lt;br&gt;' % doc.journal_id.bank_id.city if doc.journal_id.bank_id.city else ''"/>
<span t-raw="'%s &lt;br&gt;' % doc.journal_id.bank_id.state.name if doc.journal_id.bank_id.state.name else ''"/>
<span t-raw="'%s &lt;br&gt;' % doc.journal_id.bank_id.country.name if doc.journal_id.bank_id.country.name else ''"/>
</div>
</div>
<h2>Payment Order / Payment</h2>
<div class="row mt32 mb32">
<div t-if="doc.payment_mode_id.name" class="col-xs-2">
<strong>Payment Type:</strong>
<p t-field="doc.payment_mode_id.name"/>
</div>
<div t-if="doc.name" class="col-xs-2">
<strong>Reference</strong>
<p t-field="doc.name"/>
</div>
<div t-if="doc.company_partner_bank_id.bank_id.id" class="col-xs-2">
<strong>Used Account:</strong>
<p>
<span t-esc="get_bank_account_name(doc.company_partner_bank_id)"/>
</p>
</div>
<div t-if="doc.date_prefered" class="col-xs-2">
<strong>Execution:</strong>
<p t-field="doc.date_prefered"/>
</div>
<div t-if="doc.company_id.currency_id.name" class="col-xs-2">
<strong>Company Currency:</strong>
<p t-field="doc.company_id.currency_id.name"/>
</div>
</div>
<table class="table table-condensed">
<thead>
<tr>
<th>Partner</th>
<th class="text-center">Bank Account</th>
<th class="text-center">Invoice Ref</th>
<th class="text-center">Value Date</th>
<th class="text-right">Amount</th>
<th class="text-right">Currency</th>
</tr>
</thead>
<tbody>
<!-- Total amount on lines
see _get_amount_total v8
-->
<t t-set="total_amount" t-value="0"/>
<tr t-foreach="doc.payment_line_ids" t-as="line">
<!-- compute total amount -->
<t t-set="total_amount" t-value="total_amount+line.amount_currency"/>
<td>
<span t-field="line.partner_id.name"/>
</td>
<td class="text-center">
<span t-esc="get_bank_account_name(line.partner_bank_id)"/>
</td>
<td class="text-center">
<span t-raw="'%s &lt;br&gt;' % line.move_line_id.invoice_id.number or line.move_line_id.move_id.name or ''"/>
</td>
<td class="text-center">
<span t-field="line.date"/>
</td>
<td class="text-right">
<span t-field="line.amount_currency"/>
</td>
<td class="text-right">
<span t-field="line.amount_company_currency"/>
</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-xs-4 pull-right">
<table class="table table-condensed">
<tr class="border-black">
<td><strong>Total</strong></td>
<td class="text-right">
<span t-esc="formatLang(env, total_amount, currency_obj=doc.company_currency_id)"/>
</td>
</tr>
<tr>
<td>Total (Currency)</td>
<td class="text-right">
<span t-field="doc.total_company_currency"/>
</td>
</tr>
</table>
</div>
</div>
<div class="oe_structure"/>
</div>
</t>
</template>
<template id="print_account_payment_order_main">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="account_payment_order.print_account_payment_order_document" t-lang="doc.generated_user_id.lang"/>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 ACSONE SA/NV
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>
<!-- QWeb Report -->
<report
id="action_print_payment_order"
model="account.payment.order"
string="Payment Order"
report_type="qweb-pdf"
name="account_payment_order.print_account_payment_order_main"
file="account_payment_order.print_account_payment_order_main"
/>
</odoo>

View File

@@ -0,0 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_payment_order,Full access on account.payment.order to Payment Manager,model_account_payment_order,group_account_payment,1,1,1,1
access_account_payment_line,Full access on account.payment.line to Payment Manager,model_account_payment_line,group_account_payment,1,1,1,1
access_bank_payment_line,Full access on bank.payment.line to Payment Manager,model_bank_payment_line,group_account_payment,1,1,1,1
base.access_res_partner_bank_group_partner_manager,Full access on res.partner.bank to Account Payment group,base.model_res_partner_bank,group_account_payment,1,1,1,1
base.access_res_bank_group_partner_manager,Full access on res.bank to Account Payment group,base.model_res_bank,group_account_payment,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_payment_order Full access on account.payment.order to Payment Manager model_account_payment_order group_account_payment 1 1 1 1
3 access_account_payment_line Full access on account.payment.line to Payment Manager model_account_payment_line group_account_payment 1 1 1 1
4 access_bank_payment_line Full access on bank.payment.line to Payment Manager model_bank_payment_line group_account_payment 1 1 1 1
5 base.access_res_partner_bank_group_partner_manager Full access on res.partner.bank to Account Payment group base.model_res_partner_bank group_account_payment 1 1 1 1
6 base.access_res_bank_group_partner_manager Full access on res.bank to Account Payment group base.model_res_bank group_account_payment 1 1 1 1

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<record id="group_account_payment" model="res.groups">
<field name="name">Accounting / Payments</field>
<field name="users" eval="[(4, ref('base.user_root'))]"/>
<field name="category_id" ref="base.module_category_extra"/>
</record>
</data>
<data noupdate="1">
<record id="account_payment_order_company_rule" model="ir.rule">
<field name="name">Payment order multi-company rule</field>
<field name="model_id" ref="model_account_payment_order"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
<record id="account_payment_line_company_rule" model="ir.rule">
<field name="name">Payment line multi-company rule</field>
<field name="model_id" ref="model_account_payment_line"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
<record id="bank_payment_line_company_rule" model="ir.rule">
<field name="name">Bank payment line multi-company rule</field>
<field name="model_id" ref="model_bank_payment_line"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field>
</record>
</data>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,461 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" />
<title>Account Payment Order</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="account-payment-order">
<h1 class="title">Account Payment Order</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/bank-payment/tree/12.0/account_payment_order"><img alt="OCA/bank-payment" src="https://img.shields.io/badge/github-OCA%2Fbank--payment-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/bank-payment-12-0/bank-payment-12-0-account_payment_order"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/97/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This module adds support for payment orders and debit orders.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#installation" id="id1">Installation</a></li>
<li><a class="reference internal" href="#configuration" id="id2">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id3">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="installation">
<h1><a class="toc-backref" href="#id1">Installation</a></h1>
<p>This module depends on:</p>
<ul class="simple">
<li>account_payment_partner</li>
<li>base_iban</li>
<li>document</li>
</ul>
<p>This modules is part of the OCA/bank-payment suite.</p>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id2">Configuration</a></h1>
<p>This module adds several options on Payment Modes, cf Invoicing/Accounting &gt;
Configuration &gt; Management &gt; Payment Modes.</p>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id3">Usage</a></h1>
<p>You can create a Payment order via the menu Invoicing/Accounting &gt; Payments &gt; Payment Orders and then select the move lines to pay.</p>
<p>You can create a Debit order via the menu Invoicing/Accounting &gt; Payments &gt; Debit Orders and then select the move lines to debit.</p>
<p>This module also adds a button <em>Add to Payment Order</em> on supplier invoices and a button <em>Add to Debit Order</em> on customer invoices.</p>
<p>You can print a Payment order via the menu Invoicing/Accounting &gt; Payments &gt; Payment Orders and then select the payment oder to print.</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id4">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/bank-payment/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/bank-payment/issues/new?body=module:%20account_payment_order%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id5">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id6">Authors</a></h2>
<ul class="simple">
<li>ACSONE SA/NV</li>
<li>Therp BV</li>
<li>Tecnativa</li>
<li>Akretion</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id7">Contributors</a></h2>
<ul class="simple">
<li>Stéphane Bidoul &lt;<a class="reference external" href="mailto:stephane.bidoul&#64;acsone.eu">stephane.bidoul&#64;acsone.eu</a>&gt;</li>
<li>Alexis de Lattre &lt;<a class="reference external" href="mailto:alexis.delattre&#64;akretion.com">alexis.delattre&#64;akretion.com</a>&gt;</li>
<li>Pedro M. Baeza</li>
<li>Adrien Peiffer &lt;<a class="reference external" href="mailto:adrien.peiffer&#64;acsone.eu">adrien.peiffer&#64;acsone.eu</a>&gt;</li>
<li>Stefan Rijnhart</li>
<li>Laurent Mignon &lt;<a class="reference external" href="mailto:laurent.mignon&#64;acsone.eu">laurent.mignon&#64;acsone.eu</a>&gt;</li>
<li>Alexandre Fayolle</li>
<li>Danimar Ribeiro</li>
<li>Erwin van der Ploeg</li>
<li>Raphaël Valyi</li>
<li>Sandy Carter</li>
<li>Angel Moya &lt;<a class="reference external" href="mailto:angel.moya&#64;domatix.com">angel.moya&#64;domatix.com</a>&gt;</li>
<li>Jose María Alzaga &lt;<a class="reference external" href="mailto:jose.alzaga&#64;aselcis.com">jose.alzaga&#64;aselcis.com</a>&gt;</li>
<li>Meyomesse Gilles &lt;<a class="reference external" href="mailto:meyomesse.gilles&#64;gmail.com">meyomesse.gilles&#64;gmail.com</a>&gt;</li>
<li>Carlos Dauden</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id8">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/bank-payment/tree/12.0/account_payment_order">OCA/bank-payment</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,4 @@
from . import test_payment_mode
from . import test_bank
from . import test_payment_order_inbound
from . import test_payment_order_outbound

View File

@@ -0,0 +1,14 @@
# © 2017 Creu Blanca
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.tests.common import TransactionCase
from odoo.exceptions import ValidationError
class TestBank(TransactionCase):
def test_bank(self):
bank = self.env['res.bank'].search([], limit=1)
self.assertTrue(bank)
with self.assertRaises(ValidationError):
bank.bic = "TEST"

View File

@@ -0,0 +1,102 @@
# © 2017 Creu Blanca
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.tests.common import TransactionCase
from odoo.exceptions import ValidationError
class TestPaymentMode(TransactionCase):
def setUp(self):
super(TestPaymentMode, self).setUp()
# Company
self.company = self.env.ref('base.main_company')
self.journal_c1 = self.env['account.journal'].create({
'name': 'Journal 1',
'code': 'J1',
'type': 'bank',
'company_id': self.company.id,
})
self.account = self.env['account.account'].search([
('reconcile', '=', True),
('company_id', '=', self.company.id)
], limit=1)
self.manual_out = self.env.ref(
'account.account_payment_method_manual_out')
self.manual_in = self.env.ref(
'account.account_payment_method_manual_in')
self.electronic_out = self.env['account.payment.method'].create({
'name': 'Electronic Out',
'code': 'electronic_out',
'payment_type': 'outbound',
})
self.payment_mode_c1 = self.env['account.payment.mode'].create({
'name': 'Direct Debit of suppliers from Bank 1',
'bank_account_link': 'variable',
'payment_method_id': self.manual_out.id,
'company_id': self.company.id,
'fixed_journal_id': self.journal_c1.id,
'variable_journal_ids': [(6, 0, [self.journal_c1.id])]
})
def test_constrains(self):
with self.assertRaises(ValidationError):
self.payment_mode_c1.write({
'generate_move': True,
'offsetting_account': False
})
with self.assertRaises(ValidationError):
self.payment_mode_c1.write({
'generate_move': True,
'offsetting_account': 'bank_account',
'move_option': False
})
with self.assertRaises(ValidationError):
self.payment_mode_c1.write({
'generate_move': True,
'offsetting_account': 'transfer_account',
'transfer_account_id': False
})
with self.assertRaises(ValidationError):
self.payment_mode_c1.write({
'generate_move': True,
'offsetting_account': 'transfer_account',
'transfer_account_id': self.account.id,
'transfer_journal_id': False
})
def test_onchange_generate_move(self):
self.payment_mode_c1.generate_move = True
self.payment_mode_c1.generate_move_change()
self.assertEqual(self.payment_mode_c1.move_option, 'date')
self.payment_mode_c1.generate_move = False
self.payment_mode_c1.generate_move_change()
self.assertFalse(self.payment_mode_c1.move_option)
def test_onchange_offsetting_account(self):
self.payment_mode_c1.offsetting = 'bank_account'
self.payment_mode_c1.offsetting_account_change()
self.assertFalse(self.payment_mode_c1.transfer_account_id)
def test_onchange_payment_type(self):
self.payment_mode_c1.payment_method_id = self.manual_in
self.payment_mode_c1.payment_method_id_change()
self.assertTrue(all([
journal.type in [
'sale_refund', 'sale'
] for journal in self.payment_mode_c1.default_journal_ids
]))
self.payment_mode_c1.payment_method_id = self.manual_out
self.payment_mode_c1.payment_method_id_change()
self.assertTrue(all([
journal.type in [
'purchase_refund', 'purchase'
] for journal in self.payment_mode_c1.default_journal_ids
]))

View File

@@ -0,0 +1,111 @@
# © 2017 Camptocamp SA
# © 2017 Creu Blanca
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.tests.common import TransactionCase
from odoo.exceptions import ValidationError, UserError
from datetime import date, timedelta
class TestPaymentOrderInbound(TransactionCase):
def setUp(self):
super(TestPaymentOrderInbound, self).setUp()
self.inbound_mode = self.browse_ref(
'account_payment_mode.payment_mode_inbound_dd1'
)
self.invoice_line_account = self.env['account.account'].search(
[('user_type_id', '=', self.env.ref(
'account.data_account_type_revenue').id)],
limit=1).id
self.journal = self.env['account.journal'].search(
[('type', '=', 'bank')], limit=1
)
self.inbound_mode.variable_journal_ids = self.journal
self.inbound_order = self.env['account.payment.order'].create({
'payment_type': 'inbound',
'payment_mode_id': self.inbound_mode.id,
'journal_id': self.journal.id,
})
self.invoice = self._create_customer_invoice()
def _create_customer_invoice(self):
invoice_account = self.env['account.account'].search(
[('user_type_id', '=', self.env.ref(
'account.data_account_type_receivable').id)],
limit=1).id
invoice = self.env['account.invoice'].create({
'partner_id': self.env.ref('base.res_partner_4').id,
'account_id': invoice_account,
'type': 'out_invoice',
'payment_mode_id': self.inbound_mode.id
})
self.env['account.invoice.line'].create({
'product_id': self.env.ref('product.product_product_4').id,
'quantity': 1.0,
'price_unit': 100.0,
'invoice_id': invoice.id,
'name': 'product that cost 100',
'account_id': self.invoice_line_account,
})
return invoice
def test_constrains_type(self):
with self.assertRaises(ValidationError):
order = self.env['account.payment.order'].create({
'payment_mode_id': self.inbound_mode.id,
'journal_id': self.journal.id,
})
order.payment_type = 'outbound'
def test_constrains_date(self):
with self.assertRaises(ValidationError):
self.inbound_order.date_scheduled = date.today() - timedelta(
days=1)
def test_creation(self):
# Open invoice
self.invoice.action_invoice_open()
# Add to payment order using the wizard
self.env['account.invoice.payment.line.multi'].with_context(
active_model='account.invoice',
active_ids=self.invoice.ids
).create({}).run()
payment_order = self.env['account.payment.order'].search([])
self.assertEqual(len(payment_order.ids), 1)
bank_journal = self.env['account.journal'].search(
[('type', '=', 'bank')], limit=1)
# Set journal to allow cancelling entries
bank_journal.update_posted = True
payment_order.write({
'journal_id': bank_journal.id,
})
self.assertEqual(len(payment_order.payment_line_ids), 1)
self.assertEqual(len(payment_order.bank_line_ids), 0)
# Open payment order
payment_order.draft2open()
self.assertEqual(payment_order.bank_line_count, 1)
# Generate and upload
payment_order.open2generated()
payment_order.generated2uploaded()
self.assertEqual(payment_order.state, 'uploaded')
with self.assertRaises(UserError):
payment_order.unlink()
bank_line = payment_order.bank_line_ids
with self.assertRaises(UserError):
bank_line.unlink()
payment_order.action_done_cancel()
self.assertEqual(payment_order.state, 'cancel')
payment_order.cancel2draft()
payment_order.unlink()
self.assertEqual(len(self.env['account.payment.order'].search([])), 0)

View File

@@ -0,0 +1,184 @@
# © 2017 Camptocamp SA
# © 2017 Creu Blanca
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from datetime import date, datetime, timedelta
from odoo.exceptions import UserError, ValidationError
from odoo.tests.common import TransactionCase
class TestPaymentOrderOutbound(TransactionCase):
def setUp(self):
super(TestPaymentOrderOutbound, self).setUp()
self.journal = self.env['account.journal'].search(
[('type', '=', 'bank')], limit=1
)
self.invoice_line_account = self.env['account.account'].search(
[('user_type_id', '=', self.env.ref(
'account.data_account_type_expenses').id)],
limit=1).id
self.invoice = self._create_supplier_invoice()
self.invoice_02 = self._create_supplier_invoice()
self.mode = self.env.ref(
'account_payment_mode.payment_mode_outbound_ct1')
self.creation_mode = self.env.ref(
'account_payment_mode.payment_mode_outbound_dd1')
self.bank_journal = self.env['account.journal'].search(
[('type', '=', 'bank')], limit=1)
def _create_supplier_invoice(self):
invoice_account = self.env['account.account'].search(
[('user_type_id', '=', self.env.ref(
'account.data_account_type_payable').id)],
limit=1).id
invoice = self.env['account.invoice'].create({
'partner_id': self.env.ref('base.res_partner_4').id,
'account_id': invoice_account,
'type': 'in_invoice',
'payment_mode_id': self.env.ref(
'account_payment_mode.payment_mode_outbound_ct1').id
})
self.env['account.invoice.line'].create({
'product_id': self.env.ref('product.product_product_4').id,
'quantity': 1.0,
'price_unit': 100.0,
'invoice_id': invoice.id,
'name': 'product that cost 100',
'account_id': self.invoice_line_account,
})
return invoice
def test_creation_due_date(self):
self.mode.variable_journal_ids = self.bank_journal
self.mode.group_lines = False
self.order_creation('due')
def test_creation_no_date(self):
self.mode.group_lines = True
self.creation_mode.write({
'group_lines': False,
'bank_account_link': 'fixed',
'default_date_prefered': 'due',
'fixed_journal_id': self.bank_journal.id,
})
self.mode.variable_journal_ids = self.bank_journal
self.order_creation(False)
def test_creation_fixed_date(self):
self.mode.write({
'bank_account_link': 'fixed',
'default_date_prefered': 'fixed',
'fixed_journal_id': self.bank_journal.id,
})
self.invoice_02.action_invoice_open()
self.order_creation('fixed')
def order_creation(self, date_prefered):
# Open invoice
self.invoice.action_invoice_open()
order_vals = {
'payment_type': 'outbound',
'payment_mode_id': self.creation_mode.id,
}
if date_prefered:
order_vals['date_prefered'] = date_prefered
order = self.env['account.payment.order'].create(order_vals)
with self.assertRaises(UserError):
order.draft2open()
order.payment_mode_id = self.mode.id
order.payment_mode_id_change()
self.assertEqual(order.journal_id.id, self.bank_journal.id)
self.assertEqual(len(order.payment_line_ids), 0)
if date_prefered:
self.assertEqual(order.date_prefered, date_prefered)
with self.assertRaises(UserError):
order.draft2open()
line_create = self.env['account.payment.line.create'].with_context(
active_model='account.payment.order',
active_id=order.id
).create({
'date_type': 'move',
'move_date': datetime.now()
})
line_create.payment_mode = 'any'
line_create.move_line_filters_change()
line_create.populate()
line_create.create_payment_lines()
line_created_due = self.env[
'account.payment.line.create'].with_context(
active_model='account.payment.order',
active_id=order.id
).create({
'date_type': 'due',
'due_date': datetime.now()
})
line_created_due.populate()
line_created_due.create_payment_lines()
self.assertGreater(len(order.payment_line_ids), 0)
order.draft2open()
order.open2generated()
order.generated2uploaded()
order.action_done()
self.assertEqual(order.state, 'done')
def test_cancel_payment_order(self):
# Open invoice
self.invoice.action_invoice_open()
# Add to payment order using the wizard
self.env['account.invoice.payment.line.multi'].with_context(
active_model='account.invoice',
active_ids=self.invoice.ids
).create({}).run()
payment_order = self.env['account.payment.order'].search([])
self.assertEqual(len(payment_order.ids), 1)
bank_journal = self.env['account.journal'].search(
[('type', '=', 'bank')], limit=1)
# Set journal to allow cancelling entries
bank_journal.update_posted = True
payment_order.write({
'journal_id': bank_journal.id,
})
self.assertEqual(len(payment_order.payment_line_ids), 1)
self.assertEqual(len(payment_order.bank_line_ids), 0)
# Open payment order
payment_order.draft2open()
self.assertEqual(payment_order.bank_line_count, 1)
# Generate and upload
payment_order.open2generated()
payment_order.generated2uploaded()
self.assertEqual(payment_order.state, 'uploaded')
with self.assertRaises(UserError):
payment_order.unlink()
bank_line = payment_order.bank_line_ids
with self.assertRaises(UserError):
bank_line.unlink()
payment_order.action_done_cancel()
self.assertEqual(payment_order.state, 'cancel')
payment_order.cancel2draft()
payment_order.unlink()
self.assertEqual(len(self.env['account.payment.order'].search([])), 0)
def test_constrains(self):
outbound_order = self.env['account.payment.order'].create({
'payment_type': 'outbound',
'payment_mode_id': self.mode.id,
'journal_id': self.journal.id,
})
with self.assertRaises(ValidationError):
outbound_order.date_scheduled = date.today() - timedelta(
days=1)

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="invoice_form" model="ir.ui.view">
<field name="name">account_payment_order.invoice_form</field>
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account_payment_partner.invoice_form" />
<field name="arch" type="xml">
<button name="%(account.action_account_invoice_payment)d" type="action" position="after">
<button name="create_account_payment_line" type="object"
string="Add to Debit Order"
groups="account_payment_order.group_account_payment"
attrs="{'invisible': ['|', ('payment_order_ok', '=', False), ('state', '!=', 'open')]}"/>
<!-- For customer refunds:
'Add to Direct Debit Order' will deduct the refund from a customer invoice
We could also need a button 'Add to Payment Order' to reimburse
a customer via wire transfer... but I prefer to keep things
simple ; to do that, the user should manually create a payment order
and select the move lines -->
</button>
<field name="payment_mode_id" position="after">
<field name="payment_order_ok" invisible="1"/>
</field>
<field name="reference" position="before">
<field name="reference_type" required="1" nolabel="1" attrs="{'readonly':[('state','!=','draft')]}"/>
</field>
</field>
</record>
<record id="invoice_supplier_form" model="ir.ui.view">
<field name="name">account_payment_order.invoice_supplier_form</field>
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account_payment_partner.invoice_supplier_form" />
<field name="arch" type="xml">
<button name="%(account.action_account_invoice_payment)d" type="action" position="after">
<button name="create_account_payment_line" type="object"
string="Add to Payment Order"
groups="account_payment_order.group_account_payment"
attrs="{'invisible': ['|', ('payment_order_ok', '=', False), ('state', '!=', 'open')]}"/>
</button>
<field name="payment_mode_id" position="after">
<field name="payment_order_ok" invisible="1"/>
</field>
</field>
</record>
<act_window id="account_invoice_create_account_payment_line_action"
multi="True"
key2="client_action_multi"
name="Add to Payment/Debit Order"
res_model="account.invoice.payment.line.multi"
src_model="account.invoice"
view_mode="form"
target="new"/>
</odoo>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2016 Akretion (https://www.akretion.com/)
@author Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_move_line_form" model="ir.ui.view">
<field name="name">account_payment_order.move_line_form</field>
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account_payment_partner.view_move_line_form" />
<field name="arch" type="xml">
<group name="payments" position="inside">
<field name="partner_bank_id"
domain="[('partner_id', '=', partner_id), '|', ('company_id', '=', company_id), ('company_id', '=', False)]"/>
<field name="bank_payment_line_id"/>
</group>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_payment_line_form" model="ir.ui.view">
<field name="name">account.payment.line.form</field>
<field name="model">account.payment.line</field>
<field name="arch" type="xml">
<form string="Payment Lines">
<group name="main" col="2">
<group name="left">
<field name="order_id" invisible="not context.get('account_payment_line_main_view')"/>
<field name="name"/>
<field name="move_line_id"
domain="[('reconciled','=', False), ('account_id.reconcile', '=', True)] "/> <!-- we removed the filter on amount_to_pay, because we want to be able to select refunds -->
<field name="date"/>
<field name="ml_maturity_date" readonly="1"/>
<field name="amount_currency"/>
<field name="currency_id"/>
<field name="partner_id"/>
<field name="partner_bank_id"
context="{'default_partner_id': partner_id}"
domain="[('partner_id', '=', partner_id), '|', ('company_id', '=', company_id), ('company_id', '=', False)]"
attrs="{'required': [('bank_account_required', '=', True)]}"
/>
<field name="bank_account_required" invisible="1"/>
<field name="communication_type"/>
<field name="communication"/>
</group>
<group name="right">
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
<field name="amount_company_currency"/>
<field name="company_currency_id" invisible="1"/>
<field name="bank_line_id"/>
<field name="payment_type" invisible="1"/>
</group>
</group>
</form>
</field>
</record>
<record id="account_payment_line_tree" model="ir.ui.view">
<field name="name">account.payment.line.tree</field>
<field name="model">account.payment.line</field>
<field name="arch" type="xml">
<tree string="Payment Lines">
<field name="order_id" invisible="not context.get('account_payment_line_main_view')"/>
<field name="partner_id"/>
<field name="communication"/>
<field name="partner_bank_id"/>
<field name="move_line_id" invisible="1"/>
<field name="ml_maturity_date"/>
<field name="date"/>
<field name="amount_currency" string="Amount"/>
<field name="currency_id"/>
<field name="name"/>
<field name="amount_company_currency" sum="Total in Company Currency" invisible="1"/>
<field name="payment_type" invisible="1"/>
</tree>
</field>
</record>
<record id="account_payment_line_action" model="ir.actions.act_window">
<field name="name">Payment Lines</field>
<field name="res_model">account.payment.line</field>
<field name="view_mode">tree,form</field>
<field name="context">{'account_payment_line_main_view': True}</field>
</record>
</odoo>

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_payment_mode_form" model="ir.ui.view">
<field name="name">account_payment_order.account.payment.mode.form</field>
<field name="model">account.payment.mode</field>
<field name="inherit_id" ref="account_payment_mode.account_payment_mode_form"/>
<field name="arch" type="xml">
<field name="payment_type" position="after">
<field name="payment_order_ok"/>
</field>
<group name="main" position="after">
<group name="payment_order_options"
string="Options for Payment Orders"
attrs="{'invisible': [('payment_order_ok', '=', False)]}">
<field name="no_debit_before_maturity"
attrs="{'invisible': ['|', ('payment_type', '!=', 'inbound'), ('payment_order_ok', '!=', True)]}"/>
<field name="default_date_prefered"/>
<field name="group_lines"/>
</group>
<group name="payment_order_create_defaults"
string="Select Move Lines to Pay - Default Values"
attrs="{'invisible': [('payment_order_ok', '=', False)]}">
<field name="default_journal_ids" widget="many2many_tags"/>
<field name="default_payment_mode"/>
<field name="default_target_move" widget="radio"/>
<field name="default_invoice"/>
<field name="default_date_type"/>
</group>
<group name="accounting-config"
string="Accounting Entries Options"
attrs="{'invisible': [('payment_order_ok', '=', False)]}">
<field name="generate_move"/>
<field name="offsetting_account" widget="radio"
attrs="{'required': [('generate_move', '=', True)], 'invisible': [('generate_move', '=', False)]}"/>
<field name="transfer_account_id"
attrs="{'invisible': [('offsetting_account', '!=', 'transfer_account')], 'required': [('offsetting_account', '=', 'transfer_account')]}"
context="{'default_reconcile': True, 'default_company_id': company_id}"/> <!-- We can't put a default vue to user_type_id... -->
<field name="transfer_journal_id"
attrs="{'invisible': [('offsetting_account', '!=', 'transfer_account')], 'required': [('offsetting_account', '=', 'transfer_account')]}"/>
<field name="move_option"
attrs="{'invisible': [('generate_move', '=', False)], 'required': [('generate_move', '=', True)]}"/>
<field name="post_move"/>
</group>
</group>
</field>
</record>
<record id="account_payment_mode_tree" model="ir.ui.view">
<field name="name">account_payment_order.account.payment.mode.tree</field>
<field name="model">account.payment.mode</field>
<field name="inherit_id" ref="account_payment_mode.account_payment_mode_tree"/>
<field name="arch" type="xml">
<field name="payment_type" position="after">
<field name="payment_order_ok"/>
</field>
</field>
</record>
<record id="account_payment_mode_search" model="ir.ui.view">
<field name="name">account_payment_order.account.payment.mode.search</field>
<field name="model">account.payment.mode</field>
<field name="inherit_id" ref="account_payment_mode.account_payment_mode_search"/>
<field name="arch" type="xml">
<filter name="outbound" position="after">
<filter name="payment_order_ok"
string="Selectable in Payment Orders"
domain="[('payment_order_ok', '=', 1)]"/>
</filter>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,166 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_payment_order_form" model="ir.ui.view">
<field name="name">account.payment.order.form</field>
<field name="model">account.payment.order</field>
<field name="arch" type="xml">
<form string="Payment Order">
<header>
<button name="%(account_payment_line_create_action)d" type="action"
string="Create Payment Lines from Journal Items"
states="draft" class="oe_highlight" />
<button name="draft2open" type="object" states="draft"
string="Confirm Payments" class="oe_highlight"/>
<button name="open2generated" type="object" states="open"
string="Generate Payment File" class="oe_highlight"/>
<button name="generated2uploaded" type="object" states="generated"
string="File Successfully Uploaded" class="oe_highlight"/>
<button name="cancel2draft" type="object" states="cancel"
string="Back to Draft" />
<button name="action_cancel" type="object" states="draft,open,generated"
string="Cancel Payments"/>
<button name="action_done_cancel" type="object" states="uploaded"
string="Cancel Payments"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name"/></h1>
</div>
<group name="head" col="2">
<group name="head-left">
<field name="payment_mode_id"
domain="[('payment_order_ok', '=', True), ('payment_type', '=', payment_type)]"
widget="selection"/>
<field name="journal_id" widget="selection"/>
<field name="bank_account_link" invisible="1"/>
<field name="company_partner_bank_id"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="payment_type" invisible="0"/>
<field name="bank_line_count"/>
</group>
<group name="head-right">
<field name="date_prefered"/>
<field name="date_scheduled"
attrs="{'invisible': [('date_prefered', '!=', 'fixed')], 'required': [('date_prefered', '=', 'fixed')]}"/>
<field name="date_generated"/>
<field name="generated_user_id"/>
<field name="date_uploaded"/>
<field name="description"/>
</group>
</group>
<notebook>
<page name="payment-lines" string="Transactions">
<field name="payment_line_ids"
context="{'default_payment_type': payment_type}"/>
</page>
<page name="bank-payment-lines" string="Bank Payment Lines">
<field name="bank_line_ids"
context="{'default_payment_type': payment_type}"/>
</page>
<page name="moves" string="Transfer Journal Entries">
<field name="move_ids"/>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record id="account_payment_order_tree" model="ir.ui.view">
<field name="name">account.payment.order.tree</field>
<field name="model">account.payment.order</field>
<field name="arch" type="xml">
<tree string="Payment Orders" colors="blue: state=='draft'; green: state=='generated'; gray: state=='cancel'; red: state=='open'" decoration-muted="state=='cancel'">
<field name="name"/>
<field name="payment_mode_id"/>
<field name="journal_id"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="date_uploaded"/>
<field name="description"/>
<field name="total_company_currency" sum="Total Company Currency"/>
<field name="company_currency_id" invisible="1"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="account_payment_order_search" model="ir.ui.view">
<field name="name">account.payment.order.search</field>
<field name="model">account.payment.order</field>
<field name="arch" type="xml">
<search string="Search Payment Orders">
<field name="description" filter_domain="['|', ('name', 'ilike', self), ('description', 'ilike', self)]" string="Name or Description"/>
<field name="journal_id"/>
<filter name="draft" string="Draft" domain="[('state', '=', 'draft')]"/>
<filter name="open" string="Confirmed" domain="[('state', '=', 'open')]"/>
<filter name="generated" string="File Generated" domain="[('state', '=', 'generated')]"/>
<filter name="uploaded" string="File Uploaded" domain="[('state', '=', 'uploaded')]"/>
<group string="Group By" name="groupby">
<filter name="payment_mode_groupby" string="Payment Mode" context="{'group_by': 'payment_mode_id'}"/>
<filter name="journal_groupby" string="Bank Journal" context="{'group_by': 'journal_id'}"/>
<filter name="date_generated_groupby" string="File Generation Date" context="{'group_by': 'date_generated'}"/>
<filter name="date_uploaded_groupby" string="File Upload Date" context="{'group_by': 'date_uploaded'}"/>
<filter name="state_groupby" string="State" context="{'group_by': 'state'}"/>
</group>
</search>
</field>
</record>
<record id="account_payment_order_graph" model="ir.ui.view">
<field name="name">account.payment.order.graph</field>
<field name="model">account.payment.order</field>
<field name="arch" type="xml">
<graph string="Payment Orders">
<field name="date_uploaded" type="row" interval="month"/>
<field name="total_company_currency" type="measure"/>
</graph>
</field>
</record>
<record id="account_payment_order_pivot" model="ir.ui.view">
<field name="name">account.payment.order.pivot</field>
<field name="model">account.payment.order</field>
<field name="arch" type="xml">
<pivot string="Payment Orders">
<field name="date_uploaded" type="row" interval="month"/>
<field name="total_company_currency" type="measure"/>
</pivot>
</field>
</record>
<record id="account_payment_order_outbound_action" model="ir.actions.act_window">
<field name="name">Payment Orders</field>
<field name="res_model">account.payment.order</field>
<field name="view_mode">tree,form,pivot,graph</field>
<field name="domain">[('payment_type', '=', 'outbound')]</field>
<field name="context">{'default_payment_type': 'outbound'}</field>
</record>
<record id="account_payment_order_inbound_action" model="ir.actions.act_window">
<field name="name">Debit Orders</field>
<field name="res_model">account.payment.order</field>
<field name="view_mode">tree,form,pivot,graph</field>
<field name="domain">[('payment_type', '=', 'inbound')]</field>
<field name="context">{'default_payment_type': 'inbound'}</field>
</record>
<menuitem id="payment_root" name="Payments" parent="account.menu_finance"
sequence="7"/>
<menuitem id="account_payment_order_outbound_menu" action="account_payment_order_outbound_action"
parent="payment_root" sequence="10"/>
<menuitem id="account_payment_order_inbound_menu" action="account_payment_order_inbound_action"
parent="payment_root" sequence="20"/>
</odoo>

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!--
© 2015-2016 Akretion (https://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<record id="bank_payment_line_form" model="ir.ui.view">
<field name="name">bank.payment.line.form</field>
<field name="model">bank.payment.line</field>
<field name="arch" type="xml">
<form string="Bank Payment Line" create="false">
<group name="main">
<field name="order_id"
invisible="not context.get('bank_payment_line_main_view')"/>
<field name="name"/>
<field name="company_id" groups="base.group_multi_company"
invisible="not context.get('bank_payment_line_main_view')"/>
<field name="partner_id"/>
<field name="date"/>
<field name="amount_currency"/>
<field name="currency_id" invisible="1"/>
<field name="partner_bank_id"/>
<field name="communication_type"/>
<field name="communication"/>
</group>
<group string="Related Payment Lines" name="payment-lines">
<field name="payment_line_ids" nolabel="1"/>
</group>
</form>
</field>
</record>
<record id="bank_payment_line_tree" model="ir.ui.view">
<field name="name">bank.payment.line.tree</field>
<field name="model">bank.payment.line</field>
<field name="arch" type="xml">
<tree string="Bank Payment Lines" create="false">
<field name="order_id"
invisible="not context.get('bank_payment_line_main_view')"/>
<field name="partner_id"/>
<field name="communication"/>
<field name="partner_bank_id"/>
<field name="date"/>
<field name="amount_currency" sum="Total Amount"/>
<field name="currency_id"/>
<field name="name"/>
<field name="company_id" groups="base.group_multi_company"
invisible="not context.get('bank_payment_line_main_view')"/>
</tree>
</field>
</record>
<record id="bank_payment_line_search" model="ir.ui.view">
<field name="name">bank.payment.line.search</field>
<field name="model">bank.payment.line</field>
<field name="arch" type="xml">
<search string="Search Bank Payment Lines">
<field name="partner_id"/>
<filter name="inbound" string="Inbound" domain="[('payment_type', '=', 'inbound')]" />
<filter name="outbound" string="Outbound" domain="[('payment_type', '=', 'outbound')]" />
<group string="Group By" name="groupby">
<filter name="state_groupby" string="State" context="{'group_by': 'state'}"/>
<filter name="partner_groupby" string="Partner" context="{'group_by': 'partner_id'}"/>
</group>
</search>
</field>
</record>
<record id="bank_payment_line_action" model="ir.actions.act_window">
<field name="name">Bank Payment Lines</field>
<field name="res_model">bank.payment.line</field>
<field name="view_mode">tree,form</field>
<field name="context">{'bank_payment_line_main_view': True}</field>
</record>
<menuitem id="bank_payment_line_menu" action="bank_payment_line_action"
parent="payment_root" sequence="50" groups="group_account_payment"/>
</odoo>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>) -->
<odoo>
<record id="view_attachment_simplified_form" model="ir.ui.view">
<field name="name">ir.attachment.simplified.form</field>
<field name="model">ir.attachment</field>
<field name="arch" type="xml">
<form string="Attachments">
<sheet>
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name"/>
</h1>
<group name="main">
<field name="datas" filename="datas_fname"
string="Generated File"/>
<field name="datas_fname" invisible="1"/>
<label for="create_uid" string="Created by"/>
<div name="creation_div">
<field name="create_uid" readonly="1" class="oe_inline"/> on
<field name="create_date" readonly="1" class="oe_inline"/>
</div>
</group>
</sheet>
</form>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import account_payment_line_create
from . import account_invoice_payment_line_multi

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# © 2016 Akretion (<https://www.akretion.com>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, api
class AccountInvoicePaymentLineMulti(models.TransientModel):
_name = 'account.invoice.payment.line.multi'
_description = 'Create payment lines from invoice tree view'
@api.multi
def run(self):
self.ensure_one()
assert self._context['active_model'] == 'account.invoice',\
'Active model should be account.invoice'
invoices = self.env['account.invoice'].browse(
self._context['active_ids'])
action = invoices.create_account_payment_line()
return action

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="account_invoice_payment_line_multi_form" model="ir.ui.view">
<field name="name">account_invoice_payment_line_multi.form</field>
<field name="model">account.invoice.payment.line.multi</field>
<field name="arch" type="xml">
<form string="Create Payment Lines">
<p>This wizard will create payment lines for the selected invoices:</p>
<ul>
<li>if there are existing draft payment orders for the payment modes of the invoices, the payment lines will be added to those payment orders</li>
<li>otherwise, new payment orders will be created (one per payment mode).</li>
</ul>
<footer>
<button type="object" name="run" string="Create" class="oe_highlight"/>
<button special="cancel" string="Cancel" class="oe_link"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,153 @@
# -*- coding: utf-8 -*-
# © 2009 EduSense BV (<http://www.edusense.nl>)
# © 2011-2013 Therp BV (<https://therp.nl>)
# © 2014-2015 ACSONE SA/NV (<https://acsone.eu>)
# © 2015-2016 Akretion (<https://www.akretion.com>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
class AccountPaymentLineCreate(models.TransientModel):
_name = 'account.payment.line.create'
_description = 'Wizard to create payment lines'
order_id = fields.Many2one(
'account.payment.order', string='Payment Order')
journal_ids = fields.Many2many(
'account.journal', string='Journals Filter')
partner_ids = fields.Many2many(
'res.partner', string='Partners', domain=[('parent_id', '=', False)])
target_move = fields.Selection([
('posted', 'All Posted Entries'),
('all', 'All Entries'),
], string='Target Moves')
allow_blocked = fields.Boolean(
string='Allow Litigation Move Lines')
invoice = fields.Boolean(
string='Linked to an Invoice or Refund')
date_type = fields.Selection([
('due', 'Due Date'),
('move', 'Move Date'),
], string="Type of Date Filter", required=True)
due_date = fields.Date(string="Due Date")
move_date = fields.Date(
string='Move Date', default=fields.Date.context_today)
payment_mode = fields.Selection([
('same', 'Same'),
('same_or_null', 'Same or Empty'),
('any', 'Any'),
], string='Payment Mode')
move_line_ids = fields.Many2many(
'account.move.line', string='Move Lines')
@api.model
def default_get(self, field_list):
res = super(AccountPaymentLineCreate, self).default_get(field_list)
context = self.env.context
assert context.get('active_model') == 'account.payment.order',\
'active_model should be payment.order'
assert context.get('active_id'), 'Missing active_id in context !'
order = self.env['account.payment.order'].browse(context['active_id'])
mode = order.payment_mode_id
res.update({
'journal_ids': mode.default_journal_ids.ids or False,
'target_move': mode.default_target_move,
'invoice': mode.default_invoice,
'date_type': mode.default_date_type,
'payment_mode': mode.default_payment_mode,
'order_id': order.id,
})
return res
@api.multi
def _prepare_move_line_domain(self):
self.ensure_one()
domain = [('reconciled', '=', False),
('company_id', '=', self.order_id.company_id.id)]
if self.journal_ids:
domain += [('journal_id', 'in', self.journal_ids.ids)]
if self.partner_ids:
domain += [('partner_id', 'in', self.partner_ids.ids)]
if self.target_move == 'posted':
domain += [('move_id.state', '=', 'posted')]
if not self.allow_blocked:
domain += [('blocked', '!=', True)]
if self.date_type == 'due':
domain += [
'|',
('date_maturity', '<=', self.due_date),
('date_maturity', '=', False)]
elif self.date_type == 'move':
domain.append(('date', '<=', self.move_date))
if self.invoice:
domain.append(('invoice_id', '!=', False))
if self.payment_mode:
if self.payment_mode == 'same':
domain.append(
('payment_mode_id', '=', self.order_id.payment_mode_id.id))
elif self.payment_mode == 'same_or_null':
domain += [
'|',
('payment_mode_id', '=', False),
('payment_mode_id', '=', self.order_id.payment_mode_id.id)]
if self.order_id.payment_type == 'outbound':
# For payables, propose all unreconciled credit lines,
# including partially reconciled ones.
# If they are partially reconciled with a supplier refund,
# the residual will be added to the payment order.
#
# For receivables, propose all unreconciled credit lines.
# (ie customer refunds): they can be refunded with a payment.
# Do not propose partially reconciled credit lines,
# as they are deducted from a customer invoice, and
# will not be refunded with a payment.
domain += [
('credit', '>', 0),
('account_id.internal_type', 'in', ['payable', 'receivable'])]
elif self.order_id.payment_type == 'inbound':
domain += [
('debit', '>', 0),
('account_id.internal_type', 'in', ['receivable', 'payable'])]
# Exclude lines that are already in a non-cancelled
# and non-uploaded payment order; lines that are in a
# uploaded payment order are proposed if they are not reconciled,
paylines = self.env['account.payment.line'].search([
('state', 'in', ('draft', 'open', 'generated')),
('move_line_id', '!=', False)])
if paylines:
move_lines_ids = [payline.move_line_id.id for payline in paylines]
domain += [('id', 'not in', move_lines_ids)]
return domain
@api.multi
def populate(self):
domain = self._prepare_move_line_domain()
lines = self.env['account.move.line'].search(domain)
self.move_line_ids = lines
action = {
'name': _('Select Move Lines to Create Transactions'),
'type': 'ir.actions.act_window',
'res_model': 'account.payment.line.create',
'view_mode': 'form',
'target': 'new',
'res_id': self.id,
'context': self._context,
}
return action
@api.onchange(
'date_type', 'move_date', 'due_date', 'journal_ids', 'invoice',
'target_move', 'allow_blocked', 'payment_mode', 'partner_ids')
def move_line_filters_change(self):
domain = self._prepare_move_line_domain()
res = {'domain': {'move_line_ids': domain}}
return res
@api.multi
def create_payment_lines(self):
if self.move_line_ids:
self.move_line_ids.create_payment_line_from_move_line(
self.order_id)
return True

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2013-2016 Akretion (https://www.akretion.com)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="account_payment_line_create_form" model="ir.ui.view">
<field name="name">account_payment_line_create.form</field>
<field name="model">account.payment.line.create</field>
<field name="arch" type="xml">
<form string="Choose Move Lines Filter Options">
<group name="main">
<field name="order_id" invisible="1"/>
<field name="date_type"/>
<field name="move_date" attrs="{'required': [('date_type', '=', 'move')], 'invisible': [('date_type', '!=', 'move')]}"/>
<field name="due_date" attrs="{'required': [('date_type', '=', 'due')], 'invisible': [('date_type', '!=', 'due')]}"/>
<field name="journal_ids"
widget="many2many_tags"
placeholder="Keep empty for using all journals"/>
<field name="partner_ids"
widget="many2many_tags"
placeholder="Keep empty to use all partners"/>
<field name="payment_mode"/>
<field name="target_move" widget="radio"/>
<field name="invoice"/>
<field name="allow_blocked"/>
<label for="populate" string="Click on Add All Move Lines to auto-select the move lines matching the above criteria or click on Add an item to manually select the move lines filtered by the above criteria." colspan="2"/>
<button name="populate" type="object" string="Add All Move Lines"/>
</group>
<group name="move_lines" string="Selected Move Lines to Create Transactions">
<field name="move_line_ids" nolabel="1">
<tree>
<field name="date"/>
<field name="move_id" required="0"/>
<field name="journal_id"/>
<field name="partner_id"/>
<field name="account_id"/>
<field name="date_maturity"/>
<field name="debit"/>
<field name="credit"/>
<field name="amount_residual" sum="Total Residual"/>
<field name="amount_currency"/>
<field name="amount_residual_currency"/>
<field name="company_currency_id" invisible="1"/>
</tree>
</field>
</group>
<footer>
<button name="create_payment_lines" type="object"
string="Create Transactions" class="oe_highlight"/>
<button string="Cancel" special="cancel" class="oe_link"/>
</footer>
</form>
</field>
</record>
<record id="account_payment_line_create_action" model="ir.actions.act_window">
<field name="name">Create Transactions from Move Lines</field>
<field name="res_model">account.payment.line.create</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>