Merge PR #1056 into 16.0

Signed-off-by pedrobaeza
This commit is contained in:
OCA-git-bot
2023-03-04 20:19:26 +00:00
89 changed files with 59668 additions and 0 deletions

View File

@@ -0,0 +1,133 @@
=====================
Account Payment Order
=====================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Mature-brightgreen.png
:target: https://odoo-community.org/page/development-status
:alt: Mature
.. |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/15.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-15-0/bank-payment-15-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/173/15.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 > Vendors > Payment Orders and then select the move lines to pay.
You can create a Debit order via the menu Invoicing/Accounting > Customers > Debit Orders and then select the move lines to debit.
This module also adds an action *Add to Payment Order* on supplier invoices and *Add to Debit Order* on customer invoices.
You can print a Payment order via the menu Invoicing/Accounting > Vendors > 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:%2015.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>
* 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>
* `DynApps <https://www.dynapps.be>`_:
* Raf Ven <raf.ven@dynapps.be>
* Andrea Stirpe <a.stirpe@onestein.nl>
* `Jarsa <https://www.jarsa.com.mx>`_:
* Alan Ramos <alan.ramos@jarsa.com.mx>
* `Tecnativa <https://www.tecnativa.com>`_:
* Pedro M. Baeza
* Carlos Dauden
* Carlos Roca
* `Open Source Integrators <https://www.opensourceintegrators.com>`_:
* Ammar Officewala <aofficewala@opensourceintegrators.com>
* Marçal Isern <marsal.isern@qubiq.es>
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/15.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,4 @@
from .hooks import pre_init_hook
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>).
# © 2016 Akretion (<https://www.akretion.com>).
# © 2016 Aselcis (<https://www.aselcis.com>).
# © 2014-2023 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
"name": "Account Payment Order",
"version": "16.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",
"development_status": "Mature",
"category": "Banking addons",
"depends": ["account_payment_partner", "base_iban"], # for manual_bank_tranfer
"data": [
"views/account_payment_method.xml",
"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/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,
"pre_init_hook": "pre_init_hook",
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2015-2016 Akretion - Alexis de Lattre
Copyright 2019-2022 Tecnativa - Pedro M. Baeza
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo noupdate="1">
<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,47 @@
<?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>

View File

@@ -0,0 +1,16 @@
from odoo.tools import sql
def pre_init_hook(cr):
"""Prepare new partner_bank_id computed field.
Add column to avoid MemoryError on an existing Odoo instance
with lots of data.
partner_bank_id on account.move.line requires payment_order_ok to be True
which it won't be as it's newly introduced - nothing to compute.
(see AccountMoveLine._compute_partner_bank_id() in models/account_move_line.py
and AccountMove._compute_payment_order_ok() in models/account_move.py)
"""
if not sql.column_exists(cr, "account_move_line", "partner_bank_id"):
sql.create_column(cr, "account_move_line", "partner_bank_id", "int4")

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

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,9 @@
from . import account_payment_mode
from . import account_payment_order
from . import account_payment_line
from . import account_move
from . import account_move_line
from . import res_bank
from . import account_payment_method
from . import account_journal
from . import account_payment

View File

@@ -0,0 +1,33 @@
# Copyright 2019 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class AccountJournal(models.Model):
_inherit = "account.journal"
inbound_payment_order_only = fields.Boolean(
compute="_compute_inbound_payment_order_only", readonly=True, store=True
)
outbound_payment_order_only = fields.Boolean(
compute="_compute_outbound_payment_order_only", readonly=True, store=True
)
@api.depends("inbound_payment_method_line_ids.payment_method_id.payment_order_only")
def _compute_inbound_payment_order_only(self):
for rec in self:
rec.inbound_payment_order_only = all(
p.payment_order_only
for p in rec.inbound_payment_method_line_ids.payment_method_id
)
@api.depends(
"outbound_payment_method_line_ids.payment_method_id.payment_order_only"
)
def _compute_outbound_payment_order_only(self):
for rec in self:
rec.outbound_payment_order_only = all(
p.payment_order_only
for p in rec.outbound_payment_method_line_ids.payment_method_id
)

View File

@@ -0,0 +1,146 @@
# © 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 _, api, fields, models
from odoo.exceptions import UserError
class AccountMove(models.Model):
_inherit = "account.move"
payment_order_id = fields.Many2one(
comodel_name="account.payment.order",
string="Payment Order",
copy=False,
readonly=True,
check_company=True,
)
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")],
readonly=True,
states={"draft": [("readonly", False)]},
default="none",
)
@api.depends("payment_mode_id", "line_ids", "line_ids.payment_mode_id")
def _compute_payment_order_ok(self):
for move in self:
payment_mode = move.line_ids.filtered(lambda x: not x.reconciled).mapped(
"payment_mode_id"
)[:1]
if not payment_mode:
payment_mode = move.payment_mode_id
move.payment_order_ok = payment_mode.payment_order_ok
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
def get_account_payment_domain(self, payment_mode):
return [("payment_mode_id", "=", payment_mode.id), ("state", "=", "draft")]
def create_account_payment_line(self):
apoo = self.env["account.payment.order"]
result_payorder_ids = set()
action_payment_type = "debit"
for move in self:
if move.state != "posted":
raise UserError(_("The invoice %s is not in Posted state") % move.name)
applicable_lines = move.line_ids.filtered(
lambda x: (
not x.reconciled
and x.payment_mode_id.payment_order_ok
and x.account_id.account_type
in ("asset_receivable", "liability_payable")
and not any(
p_state in ("draft", "open", "generated")
for p_state in x.payment_line_ids.mapped("state")
)
)
)
if not applicable_lines:
raise UserError(
_(
"No Payment Line created for invoice %s because "
"it already exists or because this invoice is "
"already paid."
)
% move.name
)
payment_modes = applicable_lines.mapped("payment_mode_id")
if not payment_modes:
raise UserError(_("No Payment Mode on invoice %s") % move.name)
for payment_mode in payment_modes:
payorder = apoo.search(
move.get_account_payment_domain(payment_mode), limit=1
)
new_payorder = False
if not payorder:
payorder = apoo.create(
move._prepare_new_payment_order(payment_mode)
)
new_payorder = True
result_payorder_ids.add(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:
move.message_post(
body=_(
"%(count)d payment lines added to the new draft payment "
"order <a href=# data-oe-model=account.payment.order "
"data-oe-id=%(order_id)d>%(name)s</a>, which has been "
"automatically created.",
count=count,
order_id=payorder.id,
name=payorder.name,
)
)
else:
move.message_post(
body=_(
"%(count)d payment lines added to the existing draft "
"payment order "
"<a href=# data-oe-model=account.payment.order "
"data-oe-id=%(order_id)d>%(name)s</a>.",
count=count,
order_id=payorder.id,
name=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,142 @@
# © 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 odoo import api, fields, models
from odoo.fields import first
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
partner_bank_id = fields.Many2one(
comodel_name="res.partner.bank",
string="Partner Bank Account",
compute="_compute_partner_bank_id",
readonly=False,
store=True,
help="Bank account on which we should pay the supplier",
check_company=True,
)
payment_line_ids = fields.One2many(
comodel_name="account.payment.line",
inverse_name="move_line_id",
string="Payment lines",
check_company=True,
)
@api.depends("move_id", "move_id.partner_bank_id", "move_id.payment_mode_id")
def _compute_partner_bank_id(self):
for ml in self:
if (
ml.move_id.move_type in ("in_invoice", "in_refund")
and not ml.reconciled
and ml.payment_mode_id.payment_order_ok
and ml.account_id.account_type
in ("asset_receivable", "liability_payable")
and not any(
p_state in ("draft", "open", "generated")
for p_state in ml.payment_line_ids.mapped("state")
)
):
ml.partner_bank_id = ml.move_id.partner_bank_id.id
else:
ml.partner_bank_id = ml.partner_bank_id
def _get_linked_move_communication(self):
"""
This will collect the references from referral moves:
- Reversal moves
- Partial payments
"""
self.ensure_one()
references = []
# Build a recordset to gather moves from which references have already
# taken in order to avoid duplicates
reference_moves = self.env["account.move"].browse()
# If we have credit note(s) - reversal_move_id is a one2many
if self.move_id.reversal_move_id:
references.extend(
[
move.payment_reference or move.ref
for move in self.move_id.reversal_move_id
if move.payment_reference or move.ref
]
)
reference_moves |= self.move_id.reversal_move_id
# Retrieve partial payments - e.g.: manual credit notes
(
invoice_partials,
exchange_diff_moves,
) = self.move_id._get_reconciled_invoices_partials()
for (_, _, payment_move_line,) in (
invoice_partials + exchange_diff_moves
):
payment_move = payment_move_line.move_id
if payment_move not in reference_moves and (
payment_move.payment_reference or payment_move.ref
):
references.append(payment_move.payment_reference or payment_move.ref)
return references
def _get_communication(self):
"""
Retrieve the communication string for the payment order
"""
aplo = self.env["account.payment.line"]
# default values for communication_type and communication
communication_type = "normal"
communication = self.ref or self.name
# change these default values if move line is linked to an invoice
if self.move_id.is_invoice():
if (self.move_id.reference_type or "none") != "none":
communication = self.move_id.ref
ref2comm_type = aplo.invoice_reference_type2communication_type()
communication_type = ref2comm_type[self.move_id.reference_type]
else:
if (
self.move_id.move_type in ("in_invoice", "in_refund")
and self.move_id.ref
):
communication = self.move_id.payment_reference or self.move_id.ref
elif "out" in self.move_id.move_type:
# Force to only put invoice number here
communication = self.move_id.payment_reference or self.move_id.name
references = self._get_linked_move_communication()
if references:
communication += " " + " ".join(references)
return communication_type, communication
def _prepare_payment_line_vals(self, payment_order):
self.ensure_one()
communication_type, communication = self._get_communication()
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 = self.partner_bank_id.id or first(self.partner_id.bank_ids).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": False,
# date is set when the user confirms the payment order
}
return vals
def create_payment_line_from_move_line(self, payment_order):
vals_list = []
for mline in self:
vals_list.append(mline._prepare_payment_line_vals(payment_order))
return self.env["account.payment.line"].create(vals_list)

View File

@@ -0,0 +1,35 @@
# Copyright 2019 ACSONE SA/NV
# Copyright 2022 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class AccountPayment(models.Model):
_inherit = "account.payment"
payment_order_id = fields.Many2one(comodel_name="account.payment.order")
payment_line_ids = fields.Many2many(comodel_name="account.payment.line")
@api.depends("payment_type", "journal_id")
def _compute_payment_method_line_fields(self):
res = super()._compute_payment_method_line_fields()
for pay in self:
if pay.payment_order_id:
pay.available_payment_method_line_ids = (
pay.journal_id._get_available_payment_method_lines(pay.payment_type)
)
else:
pay.available_payment_method_line_ids = (
pay.journal_id._get_available_payment_method_lines(
pay.payment_type
).filtered(lambda x: not x.payment_method_id.payment_order_only)
)
to_exclude = pay._get_payment_method_codes_to_exclude()
if to_exclude:
pay.available_payment_method_line_ids = (
pay.available_payment_method_line_ids.filtered(
lambda x: x.code not in to_exclude
)
)
return res

View File

@@ -0,0 +1,221 @@
# © 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 _, api, fields, models
from odoo.exceptions import UserError
class AccountPaymentLine(models.Model):
_name = "account.payment.line"
_description = "Payment Lines"
_check_company_auto = True
name = fields.Char(string="Payment Reference", readonly=True, copy=False)
order_id = fields.Many2one(
comodel_name="account.payment.order",
string="Payment Order",
ondelete="cascade",
index=True,
check_company=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(
comodel_name="account.move.line",
string="Journal Item",
ondelete="restrict",
check_company=True,
)
ml_maturity_date = fields.Date(related="move_line_id.date_maturity", readonly=True)
currency_id = fields.Many2one(
comodel_name="res.currency",
string="Currency of the Payment Transaction",
required=True,
default=lambda self: self.env.user.company_id.currency_id,
)
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",
currency_field="company_currency_id",
)
partner_id = fields.Many2one(
comodel_name="res.partner",
string="Partner",
required=True,
domain=[("parent_id", "=", False)],
check_company=True,
)
partner_bank_id = fields.Many2one(
comodel_name="res.partner.bank",
string="Partner Bank Account",
required=False,
ondelete="restrict",
check_company=True,
)
date = fields.Date(string="Payment Date")
# communication field is required=False because we don't want to block
# the creation of lines from move/invoices when communication is empty
# This field is required in the form view and there is an error message
# when going from draft to confirm if the field is empty
communication = fields.Char(
required=False, help="Label of the payment that will be seen by the destinee"
)
communication_type = fields.Selection(
selection=[("normal", "Free")], required=True, default="normal"
)
payment_ids = fields.Many2many(
comodel_name="account.payment",
string="Payment transaction",
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_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get("name", "New") == "New":
vals["name"] = (
self.env["ir.sequence"].next_by_code("account.payment.line")
or "New"
)
return super().create(vals_list)
@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(),
)
else:
line.amount_company_currency = 0
@api.model
def _get_payment_line_grouping_fields(self):
"""This list of fields is used o compute the grouping hashcode."""
return [
"currency_id",
"partner_id",
"partner_bank_id",
"date",
"communication_type",
]
def payment_line_hashcode(self):
self.ensure_one()
values = []
for field in self._get_payment_line_grouping_fields():
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))
return "-".join(values)
@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
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
)
if not self.communication:
raise UserError(_("Communication is empty on payment line %s.") % self.name)
def _prepare_account_payment_vals(self):
"""Prepare the dictionary to create an account payment record from a set of
payment lines.
"""
journal = self.order_id.journal_id
vals = {
"payment_type": self.order_id.payment_type,
"partner_id": self.partner_id.id,
"destination_account_id": self.move_line_id.account_id.id,
"company_id": self.order_id.company_id.id,
"amount": sum(self.mapped("amount_currency")),
"date": self[:1].date,
"currency_id": self.currency_id.id,
"ref": self.order_id.name,
"payment_reference": "-".join([line.communication for line in self]),
"journal_id": journal.id,
"partner_bank_id": self.partner_bank_id.id,
"payment_order_id": self.order_id.id,
"payment_method_id": self.order_id.payment_mode_id.payment_method_id.id,
"payment_line_ids": [(6, 0, self.ids)],
}
# Determine partner_type
move_type = self[:1].move_line_id.move_id.move_type
if move_type in {"out_invoice", "out_refund"}:
vals["partner_type"] = "customer"
elif move_type in {"in_invoice", "in_refund"}:
vals["partner_type"] = "supplier"
else:
p_type = "customer" if vals["payment_type"] == "inbound" else "supplier"
vals["partner_type"] = p_type
# Fill destination account if manual payment line with no linked journal item
if not vals["destination_account_id"]:
if vals["partner_type"] == "customer":
vals[
"destination_account_id"
] = self.partner_id.property_account_receivable_id.id
else:
vals[
"destination_account_id"
] = self.partner_id.property_account_payable_id.id
return vals

View File

@@ -0,0 +1,15 @@
# Copyright 2019 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class AccountPaymentMethod(models.Model):
_inherit = "account.payment.method"
payment_order_only = fields.Boolean(
string="Only for payment orders",
help="This option helps enforcing the use of payment orders for "
"some payment methods.",
default=False,
)

View File

@@ -0,0 +1,94 @@
# © 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 api, fields, models
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(
selection=[("same", "Same"), ("same_or_null", "Same or empty"), ("any", "Any")],
string="Payment Mode on Invoice",
default="same",
)
default_journal_ids = fields.Many2many(
comodel_name="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(
selection=[("posted", "All Posted Entries"), ("all", "All Entries")],
string="Target Moves",
default="posted",
)
default_date_type = fields.Selection(
selection=[("due", "Due"), ("move", "Move")],
default="due",
string="Type of Date Filter",
)
# default option for account.payment.order
default_date_prefered = fields.Selection(
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.)",
)
@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)]

View File

@@ -0,0 +1,446 @@
# © 2009 EduSense BV (<http://www.edusense.nl>)
# © 2011-2013 Therp BV (<https://therp.nl>)
# © 2016 Akretion (Alexis de Lattre - alexis.delattre@akretion.com)
# Copyright 2016-2022 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import base64
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
class AccountPaymentOrder(models.Model):
_name = "account.payment.order"
_description = "Payment Order"
_inherit = ["mail.thread", "mail.activity.mixin"]
_order = "id desc"
_check_company_auto = True
name = fields.Char(string="Number", readonly=True, copy=False)
payment_mode_id = fields.Many2one(
comodel_name="account.payment.mode",
required=True,
ondelete="restrict",
tracking=True,
readonly=True,
states={"draft": [("readonly", False)]},
check_company=True,
)
payment_type = fields.Selection(
selection=[("inbound", "Inbound"), ("outbound", "Outbound")],
readonly=True,
required=True,
)
payment_method_id = fields.Many2one(
comodel_name="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
)
allowed_journal_ids = fields.Many2many(
comodel_name="account.journal",
compute="_compute_allowed_journal_ids",
string="Allowed journals",
)
journal_id = fields.Many2one(
comodel_name="account.journal",
string="Bank Journal",
ondelete="restrict",
readonly=True,
states={"draft": [("readonly", False)]},
tracking=True,
check_company=True,
)
# 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(
selection=[
("draft", "Draft"),
("open", "Confirmed"),
("generated", "File Generated"),
("uploaded", "File Uploaded"),
("cancel", "Cancel"),
],
string="Status",
readonly=True,
copy=False,
default="draft",
tracking=True,
)
date_prefered = fields.Selection(
selection=[
("now", "Immediately"),
("due", "Due Date"),
("fixed", "Fixed Date"),
],
string="Payment Execution Date Type",
required=True,
default="due",
tracking=True,
readonly=True,
states={"draft": [("readonly", False)]},
)
date_scheduled = fields.Date(
string="Payment Execution Date",
readonly=True,
states={"draft": [("readonly", False)]},
tracking=True,
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)
generated_user_id = fields.Many2one(
comodel_name="res.users",
string="Generated by",
readonly=True,
ondelete="restrict",
copy=False,
check_company=True,
)
payment_line_ids = fields.One2many(
comodel_name="account.payment.line",
inverse_name="order_id",
string="Transactions",
readonly=True,
states={"draft": [("readonly", False)]},
)
payment_ids = fields.One2many(
comodel_name="account.payment",
inverse_name="payment_order_id",
string="Payment Transactions",
readonly=True,
)
payment_count = fields.Integer(
compute="_compute_payment_count",
string="Number of Payment Transactions",
)
total_company_currency = fields.Monetary(
compute="_compute_total", store=True, currency_field="company_currency_id"
)
move_ids = fields.One2many(
comodel_name="account.move",
inverse_name="payment_order_id",
string="Journal Entries",
readonly=True,
)
move_count = fields.Integer(
compute="_compute_move_count", string="Number of Journal Entries"
)
description = fields.Char()
@api.depends("payment_mode_id")
def _compute_allowed_journal_ids(self):
for record in self:
if record.payment_mode_id.bank_account_link == "fixed":
record.allowed_journal_ids = record.payment_mode_id.fixed_journal_id
elif record.payment_mode_id.bank_account_link == "variable":
record.allowed_journal_ids = record.payment_mode_id.variable_journal_ids
else:
record.allowed_journal_ids = False
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.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 (%(ptype)s) is not the same as the payment "
"type of the payment mode (%(pmode)s)",
ptype=order.payment_type,
pmode=order.payment_mode_id.payment_type,
)
)
@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 %(porder)s, the Payment Execution Date "
"is in the past (%(exedate)s).",
porder=order.name,
exedate=order.date_scheduled,
)
)
@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.depends("payment_ids")
def _compute_payment_count(self):
for order in self:
order.payment_count = len(order.payment_ids)
@api.depends("move_ids")
def _compute_move_count(self):
rg_res = self.env["account.move"].read_group(
[("payment_order_id", "in", self.ids)],
["payment_order_id"],
["payment_order_id"],
)
mapped_data = {
x["payment_order_id"][0]: x["payment_order_id_count"] for x in rg_res
}
for order in self:
order.move_count = mapped_data.get(order.id, 0)
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
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_list)
@api.onchange("payment_mode_id")
def payment_mode_id_change(self):
if len(self.allowed_journal_ids) == 1:
self.journal_id = self.allowed_journal_ids
if self.payment_mode_id.default_date_prefered:
self.date_prefered = self.payment_mode_id.default_date_prefered
def action_uploaded_cancel(self):
self.action_cancel()
return True
def cancel2draft(self):
self.write({"state": "draft"})
return True
def action_cancel(self):
# Unreconcile and cancel payments
self.payment_ids.action_draft()
self.payment_ids.action_cancel()
self.write({"state": "cancel"})
return True
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 account payments.
"""
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
)
# Unreconcile, cancel and delete existing account payments
order.payment_ids.action_draft()
order.payment_ids.action_cancel()
order.payment_ids.unlink()
# Prepare account payments 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 payline.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 '%(pmode)s' has the option "
"'Disallow Debit Before Maturity Date'. The "
"payment line %(pline)s has a maturity date %(mdate)s "
"which is after the computed payment date %(pdate)s.",
pmode=order.payment_mode_id.name,
pline=payline.name,
mdate=payline.ml_maturity_date,
pdate=requested_date,
)
)
# Write requested_date on 'date' field of payment line
# norecompute is for avoiding a chained recomputation
# payment_line_ids.date
# > payment_line_ids.amount_company_currency
# > total_company_currency
with self.env.norecompute():
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,
}
order.env.flush_all()
# Create account payments
payment_vals = []
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 '%(partner)s' is negative "
"or null (%(amount).2f) !",
partner=paydict["paylines"][0].partner_id.name,
amount=paydict["total"],
)
)
payment_vals.append(paydict["paylines"]._prepare_account_payment_vals())
self.env["account.payment"].create(payment_vals)
self.write({"state": "open"})
return True
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."
)
)
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),
}
)
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
def generated2uploaded(self):
self.payment_ids.action_post()
# Perform the reconciliation of payments and source journal items
for payment in self.payment_ids:
(
payment.payment_line_ids.move_line_id
+ payment.move_id.line_ids.filtered(
lambda x: x.account_id == payment.destination_account_id
)
).reconcile()
self.write(
{"state": "uploaded", "date_uploaded": fields.Date.context_today(self)}
)
return True
def action_move_journal_line(self):
self.ensure_one()
action = self.env.ref("account.action_move_journal_line").sudo().read()[0]
if self.move_count == 1:
action.update(
{
"view_mode": "form,tree,kanban",
"views": False,
"view_id": False,
"res_id": self.move_ids[0].id,
}
)
else:
action["domain"] = [("id", "in", self.move_ids.ids)]
ctx = self.env.context.copy()
ctx.update({"search_default_misc_filter": 0})
action["context"] = ctx
return action

View File

@@ -0,0 +1,25 @@
# © 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 _, api, models
from odoo.exceptions import ValidationError
class ResBank(models.Model):
_inherit = "res.bank"
@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 '%(bic)s' "
"contains %(num)d characters, so it is not valid.",
bic=bank.bic,
num=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,32 @@
* Stéphane Bidoul <stephane.bidoul@acsone.eu>
* Alexis de Lattre <alexis.delattre@akretion.com>
* 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>
* Denis Roussel <denis.roussel@acsone.eu>
* `DynApps <https://www.dynapps.be>`_:
* Raf Ven <raf.ven@dynapps.be>
* Andrea Stirpe <a.stirpe@onestein.nl>
* `Jarsa <https://www.jarsa.com.mx>`_:
* Alan Ramos <alan.ramos@jarsa.com.mx>
* `Tecnativa <https://www.tecnativa.com>`_:
* Pedro M. Baeza
* Carlos Dauden
* Carlos Roca
* `Open Source Integrators <https://www.opensourceintegrators.com>`_:
* Ammar Officewala <aofficewala@opensourceintegrators.com>
* Marçal Isern <marsal.isern@qubiq.es>

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 > Vendors > Payment Orders and then select the move lines to pay.
You can create a Debit order via the menu Invoicing/Accounting > Customers > Debit Orders and then select the move lines to debit.
This module also adds an action *Add to Payment Order* on supplier invoices and *Add to Debit Order* on customer invoices.
You can print a Payment order via the menu Invoicing/Accounting > Vendors > 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 = "{} {}".format(name, partner_bank.acc_number)
if partner_bank.bank_bic:
name = "%s - " % (name)
if partner_bank.bank_bic:
name = "{} BIC {}".format(name, partner_bank.bank_bic)
return name
else:
return False

View File

@@ -0,0 +1,146 @@
<?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 and doc.generated_user_id.lang or user.lang})"
/>
<t t-call="web.external_layout">
<div class="page">
<div class="oe_structure" />
<div class="row">
<div class="col-4 offset-8">
<div t-field="doc.journal_id.bank_id.name" />
<div t-field="doc.journal_id.bank_id.street" />
<div t-field="doc.journal_id.bank_id.treet2" />
<div t-field="doc.journal_id.bank_id.zip" />
<div t-field="doc.journal_id.bank_id.city" />
<div t-field="doc.journal_id.bank_id.state.name" />
<div t-field="doc.journal_id.bank_id.country.name" />
</div>
</div>
<t t-if="doc.payment_type == 'inbound'">
<h2>Debit Order</h2>
</t>
<t t-else="">
<h2>Payment Order</h2>
</t>
<div class="row mt32 mb32">
<div t-if="doc.payment_mode_id.name" class="col-2">
<strong>Payment Type:</strong>
<p t-field="doc.payment_mode_id.name" />
</div>
<div t-if="doc.name" class="col-2">
<strong>Reference</strong>
<p t-field="doc.name" />
</div>
<div t-if="doc.company_partner_bank_id.bank_id.id" class="col-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-2">
<strong>Execution:</strong>
<p t-field="doc.date_prefered" />
</div>
<div t-if="doc.company_id.currency_id.name" class="col-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">
<t
t-if="line.move_line_id.move_id and 'in_' in line.move_line_id.move_id.move_type"
>
<span
t-esc="line.move_line_id.move_id.ref or line.move_line_id.move_id.payment_reference"
/>
</t>
<t t-else="">
<span t-esc="line.move_line_id.move_id.name" />
</t>
</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 pull-right">
<div class="col-4">
<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,23 @@
<?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 -->
<record id="action_print_payment_order" model="ir.actions.report">
<field name="name">Payment Order</field>
<field name="model">account.payment.order</field>
<field name="report_type">qweb-pdf</field>
<field
name="report_name"
>account_payment_order.print_account_payment_order_main</field>
<field
name="report_file"
>account_payment_order.print_account_payment_order_main</field>
<field
name="binding_model_id"
ref="account_payment_order.model_account_payment_order"
/>
<field name="binding_type">report</field>
</record>
</odoo>

View File

@@ -0,0 +1,8 @@
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 account.payment to Payment Manager,account.model_account_payment,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
access_account_payment_line_create,access_account_payment_line_create,model_account_payment_line_create,group_account_payment,1,1,1,1
access_account_invoice_payment_line_multi,access_account_invoice_payment_line_multi,model_account_invoice_payment_line_multi,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 account.payment to Payment Manager account.model_account_payment 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
7 access_account_payment_line_create access_account_payment_line_create model_account_payment_line_create group_account_payment 1 1 1 1
8 access_account_invoice_payment_line_multi access_account_invoice_payment_line_multi model_account_invoice_payment_line_multi group_account_payment 1 1 1 1

View File

@@ -0,0 +1,29 @@
<?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')), (4, ref('base.user_admin'))]"
/>
<field name="category_id" ref="base.module_category_usability" />
</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', 'in', company_ids)]</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', 'in', company_ids)]</field>
</record>
</data>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,479 @@
<?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.15.1: 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="Mature" src="https://img.shields.io/badge/maturity-Mature-brightgreen.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/15.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-15-0/bank-payment-15-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/173/15.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; Vendors &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; Customers &gt; Debit Orders and then select the move lines to debit.</p>
<p>This module also adds an action <em>Add to Payment Order</em> on supplier invoices and <em>Add to Debit Order</em> on customer invoices.</p>
<p>You can print a Payment order via the menu Invoicing/Accounting &gt; Vendors &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:%2015.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>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><a class="reference external" href="https://www.dynapps.be">DynApps</a>:<ul>
<li>Raf Ven &lt;<a class="reference external" href="mailto:raf.ven&#64;dynapps.be">raf.ven&#64;dynapps.be</a>&gt;</li>
</ul>
</li>
<li>Andrea Stirpe &lt;<a class="reference external" href="mailto:a.stirpe&#64;onestein.nl">a.stirpe&#64;onestein.nl</a>&gt;</li>
<li><a class="reference external" href="https://www.jarsa.com.mx">Jarsa</a>:<ul>
<li>Alan Ramos &lt;<a class="reference external" href="mailto:alan.ramos&#64;jarsa.com.mx">alan.ramos&#64;jarsa.com.mx</a>&gt;</li>
</ul>
</li>
<li><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:<ul>
<li>Pedro M. Baeza</li>
<li>Carlos Dauden</li>
<li>Carlos Roca</li>
</ul>
</li>
<li><a class="reference external" href="https://www.opensourceintegrators.com">Open Source Integrators</a>:<ul>
<li>Ammar Officewala &lt;<a class="reference external" href="mailto:aofficewala&#64;opensourceintegrators.com">aofficewala&#64;opensourceintegrators.com</a>&gt;</li>
</ul>
</li>
<li>Marçal Isern &lt;<a class="reference external" href="mailto:marsal.isern&#64;qubiq.es">marsal.isern&#64;qubiq.es</a>&gt;</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/15.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,5 @@
from . import test_payment_mode
from . import test_bank
from . import test_payment_order_inbound
from . import test_payment_order_outbound
from . import test_account_payment

View File

@@ -0,0 +1,155 @@
# Copyright 2019 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from unittest.mock import patch
from odoo.tests import tagged
from odoo.addons.account.models.account_payment_method import AccountPaymentMethod
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
@tagged("-at_install", "post_install")
class TestAccountPayment(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
Method_get_payment_method_information = (
AccountPaymentMethod._get_payment_method_information
)
def _get_payment_method_information(self):
res = Method_get_payment_method_information(self)
res["IN"] = {"mode": "multi", "domain": [("type", "=", "bank")]}
res["IN2"] = {"mode": "multi", "domain": [("type", "=", "bank")]}
res["OUT"] = {"mode": "multi", "domain": [("type", "=", "bank")]}
return res
cls.company = cls.company_data["company"]
cls.env.user.company_ids += cls.company
# MODELS
cls.account_payment_model = cls.env["account.payment"]
cls.account_journal_model = cls.env["account.journal"]
cls.payment_method_model = cls.env["account.payment.method"]
# INSTANCES
# Payment methods
with patch.object(
AccountPaymentMethod,
"_get_payment_method_information",
_get_payment_method_information,
):
cls.inbound_payment_method_01 = cls.payment_method_model.create(
{
"name": "inbound",
"code": "IN",
"payment_type": "inbound",
}
)
cls.inbound_payment_method_02 = cls.inbound_payment_method_01.copy(
{
"name": "inbound 2",
"code": "IN2",
"payment_type": "inbound",
}
)
cls.outbound_payment_method_01 = cls.payment_method_model.create(
{
"name": "outbound",
"code": "OUT",
"payment_type": "outbound",
}
)
# Journals
cls.manual_in = cls.env.ref("account.account_payment_method_manual_in")
cls.manual_out = cls.env.ref("account.account_payment_method_manual_out")
cls.bank_journal = cls.company_data["default_journal_bank"]
def test_account_payment_01(self):
self.assertFalse(self.inbound_payment_method_01.payment_order_only)
self.assertFalse(self.inbound_payment_method_02.payment_order_only)
self.assertFalse(self.bank_journal.inbound_payment_order_only)
self.inbound_payment_method_01.payment_order_only = True
self.assertTrue(self.inbound_payment_method_01.payment_order_only)
self.assertFalse(self.inbound_payment_method_02.payment_order_only)
self.assertFalse(self.bank_journal.inbound_payment_order_only)
for p in self.bank_journal.inbound_payment_method_line_ids.payment_method_id:
p.payment_order_only = True
self.assertTrue(self.bank_journal.inbound_payment_order_only)
def test_account_payment_02(self):
self.assertFalse(self.outbound_payment_method_01.payment_order_only)
self.assertFalse(self.bank_journal.outbound_payment_order_only)
self.outbound_payment_method_01.payment_order_only = True
self.assertTrue(self.outbound_payment_method_01.payment_order_only)
payment_method_id = (
self.bank_journal.outbound_payment_method_line_ids.payment_method_id
)
payment_method_id.payment_order_only = True
self.assertTrue(self.bank_journal.outbound_payment_order_only)
def test_account_payment_03(self):
self.assertFalse(self.inbound_payment_method_01.payment_order_only)
self.assertFalse(self.inbound_payment_method_02.payment_order_only)
self.assertFalse(self.bank_journal.inbound_payment_order_only)
new_account_payment = self.account_payment_model.with_context(
default_company_id=self.company.id
).new(
{
"journal_id": self.bank_journal.id,
"payment_type": "inbound",
"amount": 1,
"company_id": self.company.id,
}
)
# check payment methods
payment_methods = (
new_account_payment.available_payment_method_line_ids.filtered(
lambda x: x.payment_type == "inbound"
)
.mapped("payment_method_id")
.ids
)
self.assertIn(self.inbound_payment_method_01.id, payment_methods)
self.assertIn(self.inbound_payment_method_02.id, payment_methods)
# Set one payment method of the bank journal 'payment order only'
self.inbound_payment_method_01.payment_order_only = True
# check payment methods
new_account_payment2 = self.account_payment_model.with_context(
default_company_id=self.company.id
).new(
{
"journal_id": self.bank_journal.id,
"payment_type": "inbound",
"amount": 1,
"company_id": self.company.id,
}
)
payment_methods = new_account_payment2.available_payment_method_line_ids.mapped(
"payment_method_id"
).ids
self.assertNotIn(self.inbound_payment_method_01.id, payment_methods)
self.assertIn(self.inbound_payment_method_02.id, payment_methods)
# Set all payment methods of the bank journal 'payment order only'
for p in self.bank_journal.inbound_payment_method_line_ids.payment_method_id:
p.payment_order_only = True
self.assertTrue(self.bank_journal.inbound_payment_order_only)
# check payment methods
new_account_payment3 = self.account_payment_model.with_context(
default_company_id=self.company.id
).new(
{
"journal_id": self.bank_journal.id,
"payment_type": "inbound",
"amount": 1,
"company_id": self.company.id,
}
)
payment_methods = new_account_payment3.available_payment_method_line_ids.mapped(
"payment_method_id"
).ids
self.assertNotIn(self.inbound_payment_method_01.id, payment_methods)
self.assertNotIn(self.inbound_payment_method_02.id, payment_methods)

View File

@@ -0,0 +1,13 @@
# © 2017 Creu Blanca
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase
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,91 @@
# © 2017 Creu Blanca
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from unittest.mock import patch
from odoo.tests.common import TransactionCase
from odoo.addons.account.models.account_payment_method import AccountPaymentMethod
class TestPaymentMode(TransactionCase):
def setUp(self):
super(TestPaymentMode, self).setUp()
Method_get_payment_method_information = (
AccountPaymentMethod._get_payment_method_information
)
def _get_payment_method_information(self):
res = Method_get_payment_method_information(self)
res["IN"] = {"mode": "multi", "domain": [("type", "=", "bank")]}
res["IN2"] = {"mode": "multi", "domain": [("type", "=", "bank")]}
res["electronic_out"] = {"mode": "multi", "domain": [("type", "=", "bank")]}
return res
# 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")
with patch.object(
AccountPaymentMethod,
"_get_payment_method_information",
_get_payment_method_information,
):
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_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,121 @@
# Copyright 2017 Camptocamp SA
# Copyright 2017 Creu Blanca
# Copyright 2019-2022 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from datetime import date, timedelta
from odoo.exceptions import UserError, ValidationError
from odoo.tests import tagged
from odoo.tests.common import Form
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
@tagged("-at_install", "post_install")
class TestPaymentOrderInboundBase(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
cls.company = cls.company_data["company"]
cls.env.user.company_id = cls.company.id
cls.partner = cls.env["res.partner"].create(
{
"name": "Test Partner",
}
)
cls.inbound_mode = cls.env["account.payment.mode"].create(
{
"name": "Test Direct Debit of customers",
"bank_account_link": "variable",
"payment_method_id": cls.env.ref(
"account.account_payment_method_manual_in"
).id,
"company_id": cls.company.id,
}
)
cls.invoice_line_account = cls.company_data["default_account_revenue"]
cls.journal = cls.company_data["default_journal_bank"]
cls.inbound_mode.variable_journal_ids = cls.journal
# Make sure no others orders are present
cls.domain = [
("state", "=", "draft"),
("payment_type", "=", "inbound"),
("company_id", "=", cls.env.user.company_id.id),
]
cls.payment_order_obj = cls.env["account.payment.order"]
cls.payment_order_obj.search(cls.domain).unlink()
# Create payment order
cls.inbound_order = cls.env["account.payment.order"].create(
{
"payment_type": "inbound",
"payment_mode_id": cls.inbound_mode.id,
"journal_id": cls.journal.id,
}
)
# Open invoice
cls.invoice = cls._create_customer_invoice(cls)
cls.invoice.action_post()
# Add to payment order using the wizard
cls.env["account.invoice.payment.line.multi"].with_context(
active_model="account.move", active_ids=cls.invoice.ids
).create({}).run()
def _create_customer_invoice(self):
with Form(
self.env["account.move"].with_context(default_move_type="out_invoice")
) as invoice_form:
invoice_form.partner_id = self.partner
with invoice_form.invoice_line_ids.new() as invoice_line_form:
invoice_line_form.product_id = self.env.ref("product.product_product_4")
invoice_line_form.name = "product that cost 100"
invoice_line_form.quantity = 1
invoice_line_form.price_unit = 100.0
invoice_line_form.account_id = self.invoice_line_account
invoice_line_form.tax_ids.clear()
invoice = invoice_form.save()
invoice_form = Form(invoice)
invoice_form.payment_mode_id = self.inbound_mode
return invoice_form.save()
@tagged("-at_install", "post_install")
class TestPaymentOrderInbound(TestPaymentOrderInboundBase):
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):
payment_order = self.inbound_order
self.assertEqual(len(payment_order.ids), 1)
payment_order.write({"journal_id": self.journal.id})
self.assertEqual(len(payment_order.payment_line_ids), 1)
self.assertFalse(payment_order.payment_ids)
# Open payment order
payment_order.draft2open()
self.assertEqual(payment_order.payment_count, 1)
# Generate and upload
payment_order.open2generated()
payment_order.generated2uploaded()
self.assertEqual(payment_order.state, "uploaded")
with self.assertRaises(UserError):
payment_order.unlink()
payment_order.action_uploaded_cancel()
self.assertEqual(payment_order.state, "cancel")
payment_order.cancel2draft()
payment_order.unlink()
self.assertEqual(len(self.payment_order_obj.search(self.domain)), 0)

View File

@@ -0,0 +1,416 @@
# © 2017 Camptocamp SA
# © 2017 Creu Blanca
# Copyright 2022 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from datetime import date, datetime, timedelta
from odoo import fields
from odoo.exceptions import UserError, ValidationError
from odoo.tests import Form, tagged
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
@tagged("-at_install", "post_install")
class TestPaymentOrderOutboundBase(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
cls.company = cls.company_data["company"]
cls.env.user.company_id = cls.company.id
cls.partner = cls.env["res.partner"].create(
{
"name": "Test Partner",
}
)
cls.invoice_line_account = cls.env["account.account"].create(
{
"name": "Test account",
"code": "TEST1",
"account_type": "expense",
}
)
cls.mode = cls.env["account.payment.mode"].create(
{
"name": "Test Credit Transfer to Suppliers",
"company_id": cls.company.id,
"bank_account_link": "variable",
"payment_method_id": cls.env.ref(
"account.account_payment_method_manual_out"
).id,
}
)
cls.creation_mode = cls.env["account.payment.mode"].create(
{
"name": "Test Direct Debit of suppliers from Société Générale",
"company_id": cls.company.id,
"bank_account_link": "variable",
"payment_method_id": cls.env.ref(
"account.account_payment_method_manual_out"
).id,
}
)
cls.invoice = cls._create_supplier_invoice(cls, "F1242")
cls.invoice_02 = cls._create_supplier_invoice(cls, "F1243")
cls.bank_journal = cls.company_data["default_journal_bank"]
# Make sure no other payment orders are in the DB
cls.domain = [
("state", "=", "draft"),
("payment_type", "=", "outbound"),
("company_id", "=", cls.env.user.company_id.id),
]
cls.env["account.payment.order"].search(cls.domain).unlink()
def _create_supplier_invoice(self, ref):
invoice = self.env["account.move"].create(
{
"partner_id": self.partner.id,
"move_type": "in_invoice",
"ref": ref,
"payment_mode_id": self.mode.id,
"invoice_date": fields.Date.today(),
"invoice_line_ids": [
(
0,
None,
{
"product_id": self.env.ref("product.product_product_4").id,
"quantity": 1.0,
"price_unit": 100.0,
"name": "product that cost 100",
"account_id": self.invoice_line_account.id,
},
)
],
}
)
return invoice
def _create_supplier_refund(self, move, manual=False):
if manual:
# Do the supplier refund manually
vals = {
"partner_id": self.partner.id,
"move_type": "in_refund",
"ref": move.ref,
"payment_mode_id": self.mode.id,
"invoice_date": fields.Date.today(),
"invoice_line_ids": [
(
0,
None,
{
"product_id": self.env.ref("product.product_product_4").id,
"quantity": 1.0,
"price_unit": 90.0,
"name": "refund of 90.0",
"account_id": self.invoice_line_account.id,
},
)
],
}
move = self.env["account.move"].create(vals)
return move
wizard = (
self.env["account.move.reversal"]
.with_context(active_model="account.move", active_ids=move.ids)
.create(
{
"date_mode": "entry",
"refund_method": "refund",
"journal_id": move.journal_id.id,
}
)
)
wizard.reverse_moves()
return wizard.new_move_ids
@tagged("-at_install", "post_install")
class TestPaymentOrderOutbound(TestPaymentOrderOutboundBase):
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_post()
self.order_creation("fixed")
def order_creation(self, date_prefered):
# Open invoice
self.invoice.action_post()
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() + timedelta(days=1)}
)
)
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() + timedelta(days=1)}
)
)
line_created_due.populate()
line_created_due.create_payment_lines()
self.assertGreater(len(order.payment_line_ids), 0)
order.draft2open()
order.open2generated()
order.generated2uploaded()
self.assertEqual(order.move_ids[0].date, order.payment_ids[0].date)
self.assertEqual(order.state, "uploaded")
def test_cancel_payment_order(self):
# Open invoice
self.invoice.action_post()
# Add to payment order using the wizard
self.env["account.invoice.payment.line.multi"].with_context(
active_model="account.move", active_ids=self.invoice.ids
).create({}).run()
payment_order = self.env["account.payment.order"].search(self.domain)
self.assertEqual(len(payment_order), 1)
payment_order.write({"journal_id": self.bank_journal.id})
self.assertEqual(len(payment_order.payment_line_ids), 1)
self.assertFalse(payment_order.payment_ids)
# Open payment order
payment_order.draft2open()
self.assertEqual(payment_order.payment_count, 1)
# Generate and upload
payment_order.open2generated()
payment_order.generated2uploaded()
self.assertEqual(payment_order.state, "uploaded")
with self.assertRaises(UserError):
payment_order.unlink()
payment_order.action_uploaded_cancel()
self.assertEqual(payment_order.state, "cancel")
payment_order.cancel2draft()
payment_order.unlink()
self.assertEqual(len(self.env["account.payment.order"].search(self.domain)), 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.bank_journal.id,
}
)
with self.assertRaises(ValidationError):
outbound_order.date_scheduled = date.today() - timedelta(days=2)
def test_manual_line_and_manual_date(self):
# Create payment order
outbound_order = self.env["account.payment.order"].create(
{
"date_prefered": "due",
"payment_type": "outbound",
"payment_mode_id": self.mode.id,
"journal_id": self.bank_journal.id,
"description": "order with manual line",
}
)
self.assertEqual(len(outbound_order.payment_line_ids), 0)
# Create a manual payment order line with custom date
vals = {
"order_id": outbound_order.id,
"partner_id": self.partner.id,
"communication": "manual line and manual date",
"currency_id": outbound_order.payment_mode_id.company_id.currency_id.id,
"amount_currency": 192.38,
"date": date.today() + timedelta(days=8),
}
self.env["account.payment.line"].create(vals)
self.assertEqual(len(outbound_order.payment_line_ids), 1)
self.assertEqual(
outbound_order.payment_line_ids[0].date, date.today() + timedelta(days=8)
)
# Create a manual payment order line with normal date
vals = {
"order_id": outbound_order.id,
"partner_id": self.partner.id,
"communication": "manual line",
"currency_id": outbound_order.payment_mode_id.company_id.currency_id.id,
"amount_currency": 200.38,
}
self.env["account.payment.line"].create(vals)
self.assertEqual(len(outbound_order.payment_line_ids), 2)
self.assertEqual(outbound_order.payment_line_ids[1].date, False)
# Open payment order
self.assertFalse(outbound_order.payment_ids)
outbound_order.draft2open()
self.assertEqual(outbound_order.payment_count, 2)
self.assertEqual(
outbound_order.payment_line_ids[0].date,
outbound_order.payment_line_ids[0].payment_ids.date,
)
self.assertEqual(outbound_order.payment_line_ids[1].date, date.today())
self.assertEqual(
outbound_order.payment_line_ids[1].date,
fields.Date.context_today(outbound_order),
)
self.assertEqual(
outbound_order.payment_line_ids[1].payment_ids.date,
fields.Date.context_today(outbound_order),
)
def test_supplier_refund(self):
"""
Confirm the supplier invoice
Create a credit note based on that one with an inferior amount
Confirm the credit note
Create the payment order
The communication should be a combination of the invoice reference
and the credit note one
"""
self.invoice.action_post()
self.refund = self._create_supplier_refund(self.invoice)
with Form(self.refund) as refund_form:
refund_form.ref = "R1234"
with refund_form.invoice_line_ids.edit(0) as line_form:
line_form.price_unit = 75.0
self.refund.action_post()
self.env["account.invoice.payment.line.multi"].with_context(
active_model="account.move", active_ids=self.invoice.ids
).create({}).run()
payment_order = self.env["account.payment.order"].search(self.domain)
self.assertEqual(len(payment_order), 1)
payment_order.write({"journal_id": self.bank_journal.id})
self.assertEqual(len(payment_order.payment_line_ids), 1)
self.assertEqual("F1242 R1234", payment_order.payment_line_ids.communication)
def test_supplier_refund_reference(self):
"""
Confirm the supplier invoice
Set a payment referece
Create a credit note based on that one with an inferior amount
Confirm the credit note
Create the payment order
The communication should be a combination of the invoice payment reference
and the credit note one
"""
self.invoice.payment_reference = "F/1234"
self.invoice.action_post()
self.refund = self._create_supplier_refund(self.invoice)
with Form(self.refund) as refund_form:
refund_form.ref = "R1234"
with refund_form.invoice_line_ids.edit(0) as line_form:
line_form.price_unit = 75.0
self.refund.action_post()
# The user add the outstanding payment to the invoice
invoice_line = self.invoice.line_ids.filtered(
lambda line: line.account_type == "liability_payable"
)
refund_line = self.refund.line_ids.filtered(
lambda line: line.account_type == "liability_payable"
)
(invoice_line | refund_line).reconcile()
self.env["account.invoice.payment.line.multi"].with_context(
active_model="account.move", active_ids=self.invoice.ids
).create({}).run()
payment_order = self.env["account.payment.order"].search(self.domain)
self.assertEqual(len(payment_order), 1)
payment_order.write({"journal_id": self.bank_journal.id})
self.assertEqual(len(payment_order.payment_line_ids), 1)
self.assertEqual("F/1234 R1234", payment_order.payment_line_ids.communication)
def test_supplier_manual_refund(self):
"""
Confirm the supplier invoice with reference
Create a credit note manually
Confirm the credit note
Reconcile move lines together
Create the payment order
The communication should be a combination of the invoice payment reference
and the credit note one
"""
self.invoice.action_post()
self.refund = self._create_supplier_refund(self.invoice, manual=True)
with Form(self.refund) as refund_form:
refund_form.ref = "R1234"
self.refund.action_post()
(self.invoice.line_ids + self.refund.line_ids).filtered(
lambda line: line.account_type == "liability_payable"
).reconcile()
self.env["account.invoice.payment.line.multi"].with_context(
active_model="account.move", active_ids=self.invoice.ids
).create({}).run()
payment_order = self.env["account.payment.order"].search(self.domain)
self.assertEqual(len(payment_order), 1)
payment_order.write({"journal_id": self.bank_journal.id})
self.assertEqual(len(payment_order.payment_line_ids), 1)
self.assertEqual("F1242 R1234", payment_order.payment_line_ids.communication)

View File

@@ -0,0 +1,82 @@
<?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="view_move_form" model="ir.ui.view">
<field name="name">account_payment_order.view_move_form</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account_payment_partner.view_move_form" />
<field name="arch" type="xml">
<button id="account_invoice_payment_btn" position="after">
<!-- 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
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', '!=', 'posted'),
('move_type', 'not in', ('out_invoice', 'out_refund'))
]}"
/>
<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', '!=', 'posted'),
('move_type', 'not in', ('in_invoice', 'in_refund'))
]}"
/>
</button>
<field name="payment_mode_id" position="after">
<field name="payment_order_ok" invisible="1" />
</field>
<field name="payment_reference" position="before">
<field
name="reference_type"
attrs="{'readonly':[('state','!=','draft')],
'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))],
'required': [('move_type', 'in', ('out_invoice', 'out_refund'))]}"
/>
</field>
</field>
</record>
<record
id="account_invoice_create_account_payment_line_action"
model="ir.actions.act_window"
>
<field name="name">Add to Payment/Debit Order</field>
<field name="res_model">account.invoice.payment.line.multi</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="binding_model_id" ref="account.model_account_move" />
</record>
<record id="view_invoice_tree" model="ir.ui.view">
<field name="name">account_payment_order.view_invoice_tree</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_invoice_tree" />
<field name="arch" type="xml">
<button name="action_register_payment" position="after">
<button
name="%(account_invoice_create_account_payment_line_action)d"
type="action"
string="Add to Payment/Debit Order"
groups="account_payment_order.group_account_payment"
invisible="context.get('default_move_type') not in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund')"
/>
</button>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,38 @@
<?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)]"
/>
</group>
</field>
</record>
<record id="view_move_line_tree" model="ir.ui.view">
<field name="name">account_payment_order.add.move_line_tree</field>
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_tree" />
<field name="mode">primary</field>
<field name="priority">99</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='amount_currency']" position="after">
<field name="balance" readonly="1" />
<field name="amount_residual_currency" readonly="1" />
<field name="amount_residual" readonly="1" />
</xpath>
<xpath expr="//field[@name='debit']" position="replace" />
<xpath expr="//field[@name='credit']" position="replace" />
<xpath expr="//field[@name='tax_ids']" position="replace" />
</field>
</record>
</odoo>

View File

@@ -0,0 +1,84 @@
<?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="company_id" invisible="1" />
<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)]"
attrs="{'required': [('bank_account_required', '=', True)]}"
/>
<field name="bank_account_required" invisible="1" />
<field name="communication_type" />
<field name="communication" required="1" />
</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="payment_ids" />
<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>
<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" optional="hide" />
<field name="ml_maturity_date" optional="show" />
<field name="date" />
<field name="amount_currency" string="Amount" />
<field name="currency_id" invisible="1" />
<field name="name" optional="show" />
<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,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2019 ACSONE SA/NV
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="ir.ui.view" id="account_payment_method_form_view">
<field
name="name"
>account.payment.method.form (in account_payment_order)</field>
<field name="model">account.payment.method</field>
<field
name="inherit_id"
ref="account_payment_mode.account_payment_method_form"
/>
<field name="arch" type="xml">
<field name="active" position="before">
<field name="payment_order_only" />
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,65 @@
<?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>
</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,278 @@
<?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_uploaded_cancel"
type="object"
states="uploaded"
string="Cancel Payments"
/>
<field
name="state"
widget="statusbar"
statusbar_visible="draft,open,generated,uploaded"
/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button
class="oe_stat_button"
name="action_move_journal_line"
type="object"
icon="fa-bars"
>
<field
string="Journal Entries"
name="move_count"
widget="statinfo"
/>
</button>
</div>
<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)]"
/>
<field name="allowed_journal_ids" invisible="1" />
<field
name="journal_id"
domain="[('id', 'in', allowed_journal_ids)]"
/>
<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="payment_count"
attrs="{'invisible': [('state', 'in', ('draft', 'cancel'))]}"
/>
</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="payment-lines"
string="Payment Transactions"
attrs="{'invisible': [('state', 'in', ('draft', 'cancel'))]}"
>
<field name="payment_ids" edit="0" create="0" />
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" />
<field name="activity_ids" widget="mail_activity" />
<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>
<field name="name" decoration-bf="1" />
<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" optional="show" />
<field
name="payment_count"
optional="hide"
string="Payment Transactions"
/>
<field name="total_company_currency" sum="Total Company Currency" />
<field name="company_currency_id" invisible="1" />
<field
name="state"
decoration-info="state == 'draft'"
decoration-success="state == 'uploaded'"
decoration-warning="state == 'open'"
decoration-danger="state == 'generated'"
decoration-muted="state == 'cancel'"
widget="badge"
/>
</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="account_payment_order_outbound_menu"
action="account_payment_order_outbound_action"
parent="account.menu_finance_payables"
sequence="21"
/>
<menuitem
id="account_payment_order_inbound_menu"
action="account_payment_order_inbound_action"
parent="account.menu_finance_receivables"
sequence="18"
/>
</odoo>

View File

@@ -0,0 +1,28 @@
<?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="priority" eval="25" />
<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="name" string="Generated File" />
<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,2 @@
from . import account_payment_line_create
from . import account_invoice_payment_line_multi

View File

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

View File

@@ -0,0 +1,31 @@
<?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,185 @@
# © 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 _, api, fields, models
class AccountPaymentLineCreate(models.TransientModel):
_name = "account.payment.line.create"
_description = "Wizard to create payment lines"
order_id = fields.Many2one(
comodel_name="account.payment.order", string="Payment Order"
)
journal_ids = fields.Many2many(
comodel_name="account.journal", string="Journals Filter"
)
partner_ids = fields.Many2many(
comodel_name="res.partner",
string="Partners",
domain=[("parent_id", "=", False)],
)
target_move = fields.Selection(
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(
selection=[("due", "Due Date"), ("move", "Move Date")],
string="Type of Date Filter",
required=True,
)
due_date = fields.Date()
move_date = fields.Date(default=fields.Date.context_today)
payment_mode = fields.Selection(
selection=[("same", "Same"), ("same_or_null", "Same or Empty"), ("any", "Any")],
)
move_line_ids = fields.Many2many(
comodel_name="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
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(
(
"move_id.move_type",
"in",
("in_invoice", "out_invoice", "in_refund", "out_refund"),
)
)
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.account_type",
"in",
["liability_payable", "asset_receivable"],
),
]
elif self.order_id.payment_type == "inbound":
domain += [
("debit", ">", 0),
(
"account_id.account_type",
"in",
["asset_receivable", "liability_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
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
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,88 @@
<?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"
context="{'tree_view_ref': 'account_payment_order.view_move_line_tree'}"
>
<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>

View File

@@ -0,0 +1 @@
../../../../account_payment_order

View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)