mirror of
https://github.com/bringout/oca-payment.git
synced 2026-04-22 06:21:58 +02:00
Restructure: move packages from packages/ subdirectory to root
Flattened directory structure by moving payment packages from redundant packages/ subdirectory to the root level of oca-payment repository. Changes: - Moved odoo-bringout-oca-payment-* from packages/ to root - Updated CLAUDE.md to reflect new flat structure - Removed redundant packages/ directory 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
99c650f4f5
commit
7f7e88ab3d
202 changed files with 1 additions and 1 deletions
|
|
@ -0,0 +1,147 @@
|
|||
.. image:: https://odoo-community.org/readme-banner-image
|
||||
:target: https://odoo-community.org/get-involved?utm_source=readme
|
||||
:alt: Odoo Community Association
|
||||
|
||||
=====================
|
||||
Account Payment Order
|
||||
=====================
|
||||
|
||||
..
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:5aa18416d19f69cbd29ab1890094e747c155970880ff4947978f5bce5603e159
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
.. |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/license-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/16.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-16-0/bank-payment-16-0-account_payment_order
|
||||
:alt: Translate me on Weblate
|
||||
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
|
||||
:target: https://runboat.odoo-community.org/builds?repo=OCA/bank-payment&target_branch=16.0
|
||||
:alt: Try me on Runboat
|
||||
|
||||
|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 order to print.
|
||||
|
||||
You can set a transfer journal via Accounting Settings or on the Payment Mode.
|
||||
If there is no transfer journal, the bank journal itself will be used for the journal entry
|
||||
created when confirming a payment order.
|
||||
|
||||
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 to smash it by providing a detailed and welcomed
|
||||
`feedback <https://github.com/OCA/bank-payment/issues/new?body=module:%20account_payment_order%0Aversion:%2016.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>
|
||||
* 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>
|
||||
* Luc De Meyer <luc.demeyer@noviat.com> (https://noviat.com)
|
||||
|
||||
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/16.0/account_payment_order>`_ project on GitHub.
|
||||
|
||||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
from .hooks import pre_init_hook
|
||||
from . import models
|
||||
from . import report
|
||||
from . import wizard
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# © 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.13.2",
|
||||
"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",
|
||||
"wizard/account_payment_update_views.xml",
|
||||
"views/account_payment_mode.xml",
|
||||
"views/account_payment_views.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",
|
||||
"wizard/res_config_settings.xml",
|
||||
],
|
||||
"demo": ["demo/payment_demo.xml"],
|
||||
"installable": True,
|
||||
"pre_init_hook": "pre_init_hook",
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
|
@ -0,0 +1,10 @@
|
|||
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
|
||||
from . import res_company
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
# © 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",
|
||||
)
|
||||
payment_line_count = fields.Integer(compute="_compute_payment_line_count")
|
||||
|
||||
@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 _compute_payment_line_count(self):
|
||||
for move in self:
|
||||
move.payment_line_count = len(
|
||||
self.env["account.payment.line"]._search(
|
||||
[("move_line_id", "in", self.line_ids.ids)]
|
||||
)
|
||||
)
|
||||
|
||||
def _get_payment_order_communication_direct(self):
|
||||
"""Retrieve the communication string for this direct item."""
|
||||
communication = self.payment_reference or self.ref or self.name
|
||||
if self.is_invoice():
|
||||
if self.is_purchase_document():
|
||||
communication = self.payment_reference or self.ref
|
||||
else:
|
||||
communication = self.payment_reference or self.name
|
||||
return communication or ""
|
||||
|
||||
def _get_payment_order_communication_full(self):
|
||||
"""Retrieve the full communication string for the payment order.
|
||||
Reversal moves and partial payments references added.
|
||||
Avoid having everything in the same method to avoid infinite recursion
|
||||
with partial payments.
|
||||
"""
|
||||
communication = self._get_payment_order_communication_direct()
|
||||
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.reversal_move_id:
|
||||
references.extend(
|
||||
[
|
||||
move._get_payment_order_communication_direct()
|
||||
for move in self.reversal_move_id
|
||||
]
|
||||
)
|
||||
reference_moves |= self.reversal_move_id
|
||||
# Retrieve partial payments - e.g.: manual credit notes
|
||||
(
|
||||
invoice_partials,
|
||||
exchange_diff_moves,
|
||||
) = self._get_reconciled_invoices_partials()
|
||||
for (_x, _y, payment_move_line,) in (
|
||||
invoice_partials + exchange_diff_moves
|
||||
):
|
||||
payment_move = payment_move_line.move_id
|
||||
if payment_move not in reference_moves:
|
||||
references.append(
|
||||
payment_move._get_payment_order_communication_direct()
|
||||
)
|
||||
# Add references to communication from lines move
|
||||
if references:
|
||||
communication += " " + " ".join(references)
|
||||
return communication
|
||||
|
||||
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)
|
||||
pre_applicable_lines = move.line_ids.filtered(
|
||||
lambda x: (
|
||||
not x.reconciled
|
||||
and x.account_id.account_type
|
||||
in ("asset_receivable", "liability_payable")
|
||||
)
|
||||
)
|
||||
if not pre_applicable_lines:
|
||||
raise UserError(_("No pending AR/AP lines to add on %s") % move.name)
|
||||
payment_modes = pre_applicable_lines.mapped("payment_mode_id")
|
||||
if not payment_modes:
|
||||
raise UserError(_("No Payment Mode on invoice %s") % move.name)
|
||||
applicable_lines = pre_applicable_lines.filtered(
|
||||
lambda x: x.payment_mode_id.payment_order_ok
|
||||
)
|
||||
if not applicable_lines:
|
||||
raise UserError(
|
||||
_(
|
||||
"No Payment Line created for invoice %s because "
|
||||
"its payment mode is not intended for payment orders."
|
||||
)
|
||||
% move.name
|
||||
)
|
||||
payment_lines = applicable_lines.payment_line_ids.filtered(
|
||||
lambda l: l.state in ("draft", "open", "generated")
|
||||
)
|
||||
if payment_lines:
|
||||
raise UserError(
|
||||
_(
|
||||
"The invoice %(move)s is already added in the payment "
|
||||
"order(s) %(order)s."
|
||||
)
|
||||
% {
|
||||
"move": move.name,
|
||||
"order": payment_lines.order_id.mapped("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)]" % list(result_payorder_ids),
|
||||
"views": False,
|
||||
}
|
||||
)
|
||||
return action
|
||||
|
||||
def action_payment_lines(self):
|
||||
self.ensure_one()
|
||||
action = self.env["ir.actions.act_window"]._for_xml_id(
|
||||
"account_payment_order.account_payment_line_action"
|
||||
)
|
||||
action.update(
|
||||
{
|
||||
"domain": [("move_line_id", "in", self.line_ids.ids)],
|
||||
"context": dict(
|
||||
self.env.context,
|
||||
account_payment_line_main_view=1,
|
||||
form_view_ref="account_payment_order.account_payment_line_form_readonly",
|
||||
),
|
||||
}
|
||||
)
|
||||
return action
|
||||
|
||||
@api.model
|
||||
def _get_invoice_in_payment_state(self):
|
||||
"""Called from _compute_payment_state method.
|
||||
Consider in_payment all the moves that are included in a payment order.
|
||||
"""
|
||||
if self.line_ids.payment_line_ids:
|
||||
return "in_payment"
|
||||
return super()._get_invoice_in_payment_state()
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
# © 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 or not ml.payment_mode_id)
|
||||
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_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.move_id._get_payment_order_communication_full()
|
||||
# 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":
|
||||
ref2comm_type = aplo.invoice_reference_type2communication_type()
|
||||
communication_type = ref2comm_type[self.move_id.reference_type]
|
||||
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)
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
# Copyright 2019 ACSONE SA/NV
|
||||
# Copyright 2022 Tecnativa - Pedro M. Baeza
|
||||
# Copyright 2023 Noviat
|
||||
# Copyright 2024 Tecnativa - Víctor Martínez
|
||||
# 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")
|
||||
order_state = fields.Selection(
|
||||
related="payment_order_id.state", string="Payment Order State"
|
||||
)
|
||||
payment_line_date = fields.Date(compute="_compute_payment_line_date")
|
||||
|
||||
@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.payment_order_id.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
|
||||
|
||||
@api.depends("payment_line_ids", "payment_line_ids.date")
|
||||
def _compute_payment_line_date(self):
|
||||
for item in self:
|
||||
item.payment_line_date = item.payment_line_ids[:1].date
|
||||
|
||||
@api.depends("payment_line_ids")
|
||||
def _compute_partner_bank_id(self):
|
||||
# Force the payment line bank account. The grouping function has already
|
||||
# assured that there's no more than one bank account in the group
|
||||
order_pays = self.filtered("payment_line_ids")
|
||||
for pay in order_pays:
|
||||
pay.partner_bank_id = pay.payment_line_ids.partner_bank_id
|
||||
return super(AccountPayment, self - order_pays)._compute_partner_bank_id()
|
||||
|
||||
@api.constrains("payment_method_line_id")
|
||||
def _check_payment_method_line_id(self):
|
||||
for pay in self:
|
||||
transfer_journal = (
|
||||
pay.payment_order_id.payment_mode_id.transfer_journal_id
|
||||
or pay.company_id.transfer_journal_id
|
||||
)
|
||||
if pay.journal_id == transfer_journal:
|
||||
continue
|
||||
else:
|
||||
super(AccountPayment, pay)._check_payment_method_line_id()
|
||||
return
|
||||
|
||||
def update_payment_reference(self):
|
||||
view = self.env.ref("account_payment_order.account_payment_update_view_form")
|
||||
return {
|
||||
"name": _("Update Payment Reference"),
|
||||
"view_type": "form",
|
||||
"view_mode": "form",
|
||||
"res_model": "account.payment.update",
|
||||
"view_id": view.id,
|
||||
"target": "new",
|
||||
"type": "ir.actions.act_window",
|
||||
"context": dict(
|
||||
self.env.context, default_payment_reference=self.payment_reference
|
||||
),
|
||||
}
|
||||
|
||||
def _prepare_move_line_default_vals(self, write_off_line_vals=None):
|
||||
"""Overwrite date_maturity of the move_lines that are generated when related
|
||||
to a payment order.
|
||||
"""
|
||||
vals_list = super()._prepare_move_line_default_vals(
|
||||
write_off_line_vals=write_off_line_vals
|
||||
)
|
||||
if not self.payment_order_id:
|
||||
return vals_list
|
||||
for vals in vals_list:
|
||||
vals["date_maturity"] = self.payment_line_ids[0].date
|
||||
return vals_list
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
# © 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,
|
||||
)
|
||||
partner_bank_acc_type = fields.Selection(
|
||||
related="partner_bank_id.acc_type", string="Bank Account Type"
|
||||
)
|
||||
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"), ("structured", "Structured")],
|
||||
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
|
||||
payment_mode = self.order_id.payment_mode_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": fields.Date.context_today(self),
|
||||
"currency_id": self.currency_id.id,
|
||||
"ref": self.order_id.name,
|
||||
# Put the name as the wildcard for forcing a unique name. If not, Odoo gets
|
||||
# the sequence for all the payment at the same time
|
||||
"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_line_ids": [(6, 0, self.ids)],
|
||||
}
|
||||
# Determine payment method line according payment method and journal
|
||||
line = self.env["account.payment.method.line"].search(
|
||||
[
|
||||
("payment_method_id", "=", payment_mode.payment_method_id.id),
|
||||
("journal_id", "=", journal.id),
|
||||
],
|
||||
limit=1,
|
||||
)
|
||||
if line:
|
||||
vals["payment_method_line_id"] = line.id
|
||||
# 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
|
||||
|
||||
transfer_journal = (
|
||||
self.order_id.payment_mode_id.transfer_journal_id
|
||||
or self.company_id.transfer_journal_id
|
||||
)
|
||||
if transfer_journal:
|
||||
vals["journal_id"] = transfer_journal.id
|
||||
return vals
|
||||
|
||||
def action_open_business_doc(self):
|
||||
if not self.move_line_id:
|
||||
return False
|
||||
return self.move_line_id.action_open_business_doc()
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
# © 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.)",
|
||||
)
|
||||
transfer_journal_id = fields.Many2one(
|
||||
comodel_name="account.journal",
|
||||
string="Transfer journal on payment/debit orders",
|
||||
domain="[('type', '=', 'general')]",
|
||||
help="Journal to write payment entries when confirming payment/debit orders",
|
||||
)
|
||||
|
||||
@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)]
|
||||
|
|
@ -0,0 +1,494 @@
|
|||
# © 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,
|
||||
)
|
||||
partner_banks_archive_msg = fields.Html(
|
||||
compute="_compute_partner_banks_archive_msg",
|
||||
)
|
||||
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_line_ids.partner_bank_id", "payment_line_ids.partner_bank_id.active"
|
||||
)
|
||||
def _compute_partner_banks_archive_msg(self):
|
||||
"""Information message to show archived bank accounts and to be able
|
||||
to act on them before confirmation (avoid duplicates)."""
|
||||
for item in self:
|
||||
msg_lines = []
|
||||
for partner_bank in item.payment_line_ids.filtered(
|
||||
lambda x: x.partner_bank_id and not x.partner_bank_id.active
|
||||
).mapped("partner_bank_id"):
|
||||
msg_line = _(
|
||||
"<b>Account Number</b>: %(number)s - <b>Partner</b>: %(name)s"
|
||||
) % {
|
||||
"number": partner_bank.acc_number,
|
||||
"name": partner_bank.partner_id.display_name,
|
||||
}
|
||||
msg_lines.append(msg_line)
|
||||
item.partner_banks_archive_msg = (
|
||||
"<br/>".join(msg_lines) if len(msg_lines) > 0 else False
|
||||
)
|
||||
|
||||
@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.constrains("payment_line_ids")
|
||||
def _check_payment_lines(self):
|
||||
for order in self:
|
||||
move_line_ids = [
|
||||
x.move_line_id.id for x in order.payment_line_ids if x.move_line_id
|
||||
]
|
||||
if len(move_line_ids) != len(set(move_line_ids)):
|
||||
raise ValidationError(
|
||||
_(
|
||||
"There are several lines pointing to the same pending "
|
||||
"balance. This is probably caused by a manual line creation. "
|
||||
"Please remove this duplication for being able to save the "
|
||||
"order."
|
||||
)
|
||||
)
|
||||
|
||||
@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
|
||||
payline_err_text = []
|
||||
group_paylines = {} # key = hashcode
|
||||
for payline in order.payment_line_ids:
|
||||
try:
|
||||
payline.draft2open_payment_line_check()
|
||||
except UserError as e:
|
||||
payline_err_text.append(e.args[0])
|
||||
# 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
|
||||
requested_date = max(today, requested_date)
|
||||
# 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
|
||||
):
|
||||
payline_err_text.append(
|
||||
_(
|
||||
"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
|
||||
hashcode = (
|
||||
payline.payment_line_hashcode()
|
||||
if order.payment_mode_id.group_lines
|
||||
else 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,
|
||||
}
|
||||
# Raise errors that happened on the validation process
|
||||
if payline_err_text:
|
||||
raise UserError(
|
||||
_("There's at least one validation error:\n")
|
||||
+ "\n".join(payline_err_text)
|
||||
)
|
||||
|
||||
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).
|
||||
|
||||
By default, any method not specifically intercepted by extra modules will do
|
||||
nothing, including the existing manual one.
|
||||
"""
|
||||
self.ensure_one()
|
||||
return (False, False)
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# Copyright 2023 Noviat
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = "res.company"
|
||||
|
||||
transfer_journal_id = fields.Many2one(
|
||||
comodel_name="account.journal",
|
||||
string="Transfer journal on payment/debit orders",
|
||||
domain="[('type', '=', 'general')]",
|
||||
help="Journal to write payment entries when confirming payment/debit orders",
|
||||
)
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
This module adds several options on Payment Modes, cf Invoicing/Accounting >
|
||||
Configuration > Management > Payment Modes.
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
* 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>
|
||||
* Luc De Meyer <luc.demeyer@noviat.com> (https://noviat.com)
|
||||
|
|
@ -0,0 +1 @@
|
|||
This module adds support for payment orders and debit orders.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
This module depends on:
|
||||
|
||||
* account_payment_partner
|
||||
* base_iban
|
||||
* document
|
||||
|
||||
This modules is part of the OCA/bank-payment suite.
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
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 order to print.
|
||||
|
||||
You can set a transfer journal via Accounting Settings or on the Payment Mode.
|
||||
If there is no transfer journal, the bank journal itself will be used for the journal entry
|
||||
created when confirming a payment order.
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import account_payment_order
|
||||
|
|
@ -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
|
||||
|
|
@ -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.street2" />
|
||||
<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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
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_order_readonly,Readonly access on account.payment.order to group_account_readonly,model_account_payment_order,account.group_account_readonly,1,0,0,0
|
||||
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_account_payment_line_readonly,Readonly access on account.payment.line to group_account_readonly,model_account_payment_line,account.group_account_readonly,1,0,0,0
|
||||
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
|
||||
access_account_payment_update,access_account_payment_update,model_account_payment_update,group_account_payment,1,1,1,1
|
||||
|
|
|
@ -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 |
|
|
@ -0,0 +1,494 @@
|
|||
<!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: https://docutils.sourceforge.io/" />
|
||||
<title>README.rst</title>
|
||||
<style type="text/css">
|
||||
|
||||
/*
|
||||
:Author: David Goodger (goodger@python.org)
|
||||
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
|
||||
:Copyright: This stylesheet has been placed in the public domain.
|
||||
|
||||
Default cascading style sheet for the HTML output of Docutils.
|
||||
Despite the name, some widely supported CSS2 features are used.
|
||||
|
||||
See https://docutils.sourceforge.io/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: gray; } /* 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, pre.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">
|
||||
|
||||
|
||||
<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
|
||||
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
|
||||
</a>
|
||||
<div class="section" id="account-payment-order">
|
||||
<h1>Account Payment Order</h1>
|
||||
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:5aa18416d19f69cbd29ab1890094e747c155970880ff4947978f5bce5603e159
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||
<p><a class="reference external image-reference" 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 image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/bank-payment/tree/16.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 image-reference" href="https://translation.odoo-community.org/projects/bank-payment-16-0/bank-payment-16-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 image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/bank-payment&target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-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="toc-entry-1">Installation</a></li>
|
||||
<li><a class="reference internal" href="#configuration" id="toc-entry-2">Configuration</a></li>
|
||||
<li><a class="reference internal" href="#usage" id="toc-entry-3">Usage</a></li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-4">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="toc-entry-5">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="toc-entry-6">Authors</a></li>
|
||||
<li><a class="reference internal" href="#contributors" id="toc-entry-7">Contributors</a></li>
|
||||
<li><a class="reference internal" href="#maintainers" id="toc-entry-8">Maintainers</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="installation">
|
||||
<h2><a class="toc-backref" href="#toc-entry-1">Installation</a></h2>
|
||||
<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">
|
||||
<h2><a class="toc-backref" href="#toc-entry-2">Configuration</a></h2>
|
||||
<p>This module adds several options on Payment Modes, cf Invoicing/Accounting >
|
||||
Configuration > Management > Payment Modes.</p>
|
||||
</div>
|
||||
<div class="section" id="usage">
|
||||
<h2><a class="toc-backref" href="#toc-entry-3">Usage</a></h2>
|
||||
<p>You can create a Payment order via the menu Invoicing/Accounting > Vendors > Payment Orders and then select the move lines to pay.</p>
|
||||
<p>You can create a Debit order via the menu Invoicing/Accounting > Customers > 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 > Vendors > Payment Orders and then select the payment order to print.</p>
|
||||
<p>You can set a transfer journal via Accounting Settings or on the Payment Mode.
|
||||
If there is no transfer journal, the bank journal itself will be used for the journal entry
|
||||
created when confirming a payment order.</p>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
<h2><a class="toc-backref" href="#toc-entry-4">Bug Tracker</a></h2>
|
||||
<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 to smash 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:%2016.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">
|
||||
<h2><a class="toc-backref" href="#toc-entry-5">Credits</a></h2>
|
||||
<div class="section" id="authors">
|
||||
<h3><a class="toc-backref" href="#toc-entry-6">Authors</a></h3>
|
||||
<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">
|
||||
<h3><a class="toc-backref" href="#toc-entry-7">Contributors</a></h3>
|
||||
<ul class="simple">
|
||||
<li>Stéphane Bidoul <<a class="reference external" href="mailto:stephane.bidoul@acsone.eu">stephane.bidoul@acsone.eu</a>></li>
|
||||
<li>Alexis de Lattre <<a class="reference external" href="mailto:alexis.delattre@akretion.com">alexis.delattre@akretion.com</a>></li>
|
||||
<li>Adrien Peiffer <<a class="reference external" href="mailto:adrien.peiffer@acsone.eu">adrien.peiffer@acsone.eu</a>></li>
|
||||
<li>Stefan Rijnhart</li>
|
||||
<li>Laurent Mignon <<a class="reference external" href="mailto:laurent.mignon@acsone.eu">laurent.mignon@acsone.eu</a>></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 <<a class="reference external" href="mailto:angel.moya@domatix.com">angel.moya@domatix.com</a>></li>
|
||||
<li>Jose María Alzaga <<a class="reference external" href="mailto:jose.alzaga@aselcis.com">jose.alzaga@aselcis.com</a>></li>
|
||||
<li>Meyomesse Gilles <<a class="reference external" href="mailto:meyomesse.gilles@gmail.com">meyomesse.gilles@gmail.com</a>></li>
|
||||
<li>Denis Roussel <<a class="reference external" href="mailto:denis.roussel@acsone.eu">denis.roussel@acsone.eu</a>></li>
|
||||
<li><a class="reference external" href="https://www.dynapps.be">DynApps</a>:<ul>
|
||||
<li>Raf Ven <<a class="reference external" href="mailto:raf.ven@dynapps.be">raf.ven@dynapps.be</a>></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Andrea Stirpe <<a class="reference external" href="mailto:a.stirpe@onestein.nl">a.stirpe@onestein.nl</a>></li>
|
||||
<li><a class="reference external" href="https://www.jarsa.com.mx">Jarsa</a>:<ul>
|
||||
<li>Alan Ramos <<a class="reference external" href="mailto:alan.ramos@jarsa.com.mx">alan.ramos@jarsa.com.mx</a>></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 <<a class="reference external" href="mailto:aofficewala@opensourceintegrators.com">aofficewala@opensourceintegrators.com</a>></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Marçal Isern <<a class="reference external" href="mailto:marsal.isern@qubiq.es">marsal.isern@qubiq.es</a>></li>
|
||||
<li>Luc De Meyer <<a class="reference external" href="mailto:luc.demeyer@noviat.com">luc.demeyer@noviat.com</a>> (<a class="reference external" href="https://noviat.com">https://noviat.com</a>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
<h3><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h3>
|
||||
<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/16.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>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
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
|
||||
from . import test_payment_order_transfer_journal
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
# 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
|
||||
from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT
|
||||
|
||||
|
||||
@tagged("-at_install", "post_install")
|
||||
class TestAccountPayment(AccountTestInvoicingCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
cls.env = cls.env(context=dict(cls.env.context, **DISABLED_MAIL_CONTEXT))
|
||||
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)
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# © 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
|
||||
|
||||
from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT
|
||||
|
||||
|
||||
class TestBank(TransactionCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env = cls.env(context=dict(cls.env.context, **DISABLED_MAIL_CONTEXT))
|
||||
|
||||
def test_bank(self):
|
||||
bank = self.env["res.bank"].search([], limit=1)
|
||||
if not bank:
|
||||
# This should only happen if we don't have demo data
|
||||
bank = (
|
||||
self.env["res.bank"]
|
||||
.env["res.bank"]
|
||||
.create(
|
||||
{
|
||||
"name": "Fiducial Banque",
|
||||
"bic": "FIDCFR21XXX",
|
||||
"street": "38 rue Sergent Michel Berthet",
|
||||
"zip": "69009",
|
||||
"city": "Lyon",
|
||||
"country": self.env.ref("base.fr").id,
|
||||
}
|
||||
)
|
||||
)
|
||||
with self.assertRaises(ValidationError):
|
||||
bank.bic = "TEST"
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
# © 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
|
||||
from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT
|
||||
|
||||
|
||||
class TestPaymentMode(TransactionCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env = cls.env(context=dict(cls.env.context, **DISABLED_MAIL_CONTEXT))
|
||||
Method_get_payment_method_information = (
|
||||
AccountPaymentMethod._get_payment_method_information
|
||||
)
|
||||
|
||||
def _get_payment_method_information(cls):
|
||||
res = Method_get_payment_method_information(cls)
|
||||
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
|
||||
cls.company = cls.env.ref("base.main_company")
|
||||
|
||||
cls.journal_c1 = cls.env["account.journal"].create(
|
||||
{
|
||||
"name": "Journal 1",
|
||||
"code": "J1",
|
||||
"type": "bank",
|
||||
"company_id": cls.company.id,
|
||||
}
|
||||
)
|
||||
|
||||
cls.account = cls.env["account.account"].search(
|
||||
[("reconcile", "=", True), ("company_id", "=", cls.company.id)], limit=1
|
||||
)
|
||||
|
||||
cls.manual_out = cls.env.ref("account.account_payment_method_manual_out")
|
||||
|
||||
cls.manual_in = cls.env.ref("account.account_payment_method_manual_in")
|
||||
|
||||
with patch.object(
|
||||
AccountPaymentMethod,
|
||||
"_get_payment_method_information",
|
||||
_get_payment_method_information,
|
||||
):
|
||||
|
||||
cls.electronic_out = cls.env["account.payment.method"].create(
|
||||
{
|
||||
"name": "Electronic Out",
|
||||
"code": "electronic_out",
|
||||
"payment_type": "outbound",
|
||||
}
|
||||
)
|
||||
|
||||
cls.payment_mode_c1 = cls.env["account.payment.mode"].create(
|
||||
{
|
||||
"name": "Direct Debit of suppliers from Bank 1",
|
||||
"bank_account_link": "variable",
|
||||
"payment_method_id": cls.manual_out.id,
|
||||
"company_id": cls.company.id,
|
||||
"fixed_journal_id": cls.journal_c1.id,
|
||||
"variable_journal_ids": [(6, 0, [cls.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
|
||||
]
|
||||
)
|
||||
)
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
# Copyright 2017 Camptocamp SA
|
||||
# Copyright 2017 Creu Blanca
|
||||
# Copyright 2019-2022 Tecnativa - Pedro M. Baeza
|
||||
# Copyright 2024 Tecnativa - Víctor Martínez
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
from datetime import date, timedelta
|
||||
|
||||
from freezegun import freeze_time
|
||||
|
||||
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
|
||||
from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT
|
||||
|
||||
|
||||
@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.env = cls.env(context=dict(cls.env.context, **DISABLED_MAIL_CONTEXT))
|
||||
cls.company = cls.company_data["company"]
|
||||
cls.env.user.company_id = cls.company.id
|
||||
cls.product = cls.env["product.product"].create(
|
||||
{
|
||||
"name": "Test product",
|
||||
"type": "service",
|
||||
}
|
||||
)
|
||||
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.product
|
||||
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_form.reference_type = "structured"
|
||||
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 _line_creation(self, inbound_order):
|
||||
vals = {
|
||||
"order_id": inbound_order.id,
|
||||
"partner_id": self.partner.id,
|
||||
"currency_id": inbound_order.payment_mode_id.company_id.currency_id.id,
|
||||
"amount_currency": 200.38,
|
||||
"move_line_id": self.invoice.invoice_line_ids[0].id,
|
||||
}
|
||||
return self.env["account.payment.line"].create(vals)
|
||||
|
||||
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_invoice_communication_01(self):
|
||||
self.assertEqual(
|
||||
self.invoice.name, self.invoice._get_payment_order_communication_direct()
|
||||
)
|
||||
self.invoice.ref = "R1234"
|
||||
self.assertEqual(
|
||||
self.invoice.name, self.invoice._get_payment_order_communication_direct()
|
||||
)
|
||||
|
||||
def test_invoice_communication_02(self):
|
||||
self.invoice.payment_reference = "R1234"
|
||||
self.assertEqual(
|
||||
"R1234", self.invoice._get_payment_order_communication_direct()
|
||||
)
|
||||
|
||||
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")
|
||||
self.assertEqual(self.invoice.payment_state, "in_payment")
|
||||
with self.assertRaises(UserError):
|
||||
payment_order.unlink()
|
||||
# Cancel order
|
||||
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)
|
||||
|
||||
def test_constrains_payment_line(self):
|
||||
inbound_order = self.env["account.payment.order"].create(
|
||||
{"payment_mode_id": self.inbound_mode.id, "journal_id": self.journal.id}
|
||||
)
|
||||
# Create two payment lines with the same
|
||||
# move line id and try to assign them to inbound order
|
||||
payment_line_1 = self._line_creation(inbound_order)
|
||||
inbound_order.payment_line_ids |= payment_line_1
|
||||
with self.assertRaises(ValidationError):
|
||||
payment_line_2 = self._line_creation(inbound_order)
|
||||
inbound_order.payment_line_ids |= payment_line_2
|
||||
|
||||
@freeze_time("2024-04-01")
|
||||
def test_creation_transfer_move_date_01(self):
|
||||
self.inbound_order.date_prefered = "fixed"
|
||||
self.inbound_order.date_scheduled = "2024-06-01"
|
||||
self.inbound_order.draft2open()
|
||||
payment = self.inbound_order.payment_ids
|
||||
self.assertEqual(payment.payment_line_date, date(2024, 6, 1))
|
||||
payment_move = payment.move_id
|
||||
self.assertEqual(payment_move.date, date(2024, 4, 1)) # now
|
||||
self.assertEqual(
|
||||
payment_move.line_ids.mapped("date_maturity"),
|
||||
[date(2024, 6, 1), date(2024, 6, 1)],
|
||||
)
|
||||
self.assertEqual(self.inbound_order.payment_count, 1)
|
||||
self.inbound_order.open2generated()
|
||||
self.inbound_order.generated2uploaded()
|
||||
self.assertEqual(self.inbound_order.state, "uploaded")
|
||||
payment = self.inbound_order.payment_ids
|
||||
self.assertEqual(payment.payment_line_date, date(2024, 6, 1))
|
||||
payment_move = payment.move_id
|
||||
self.assertEqual(payment_move.date, date(2024, 4, 1)) # now
|
||||
self.assertEqual(
|
||||
payment_move.line_ids.mapped("date_maturity"),
|
||||
[date(2024, 6, 1), date(2024, 6, 1)],
|
||||
)
|
||||
|
||||
@freeze_time("2024-04-01")
|
||||
def test_creation_transfer_move_date_02(self):
|
||||
# Simulate that the invoice had a different due date
|
||||
self.inbound_order.payment_line_ids.ml_maturity_date = "2024-06-01"
|
||||
self.inbound_order.draft2open()
|
||||
payment = self.inbound_order.payment_ids
|
||||
self.assertEqual(payment.payment_line_date, date(2024, 6, 1))
|
||||
payment_move = payment.move_id
|
||||
self.assertEqual(payment_move.date, date(2024, 4, 1)) # now
|
||||
self.assertEqual(
|
||||
payment_move.line_ids.mapped("date_maturity"),
|
||||
[date(2024, 6, 1), date(2024, 6, 1)],
|
||||
)
|
||||
self.assertEqual(self.inbound_order.payment_count, 1)
|
||||
self.inbound_order.open2generated()
|
||||
self.inbound_order.generated2uploaded()
|
||||
self.assertEqual(self.inbound_order.state, "uploaded")
|
||||
payment = self.inbound_order.payment_ids
|
||||
self.assertEqual(payment.payment_line_date, date(2024, 6, 1))
|
||||
payment_move = payment.move_id
|
||||
self.assertEqual(payment_move.date, date(2024, 4, 1)) # now
|
||||
self.assertEqual(
|
||||
payment_move.line_ids.mapped("date_maturity"),
|
||||
[date(2024, 6, 1), date(2024, 6, 1)],
|
||||
)
|
||||
|
|
@ -0,0 +1,575 @@
|
|||
# © 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.fields import Command
|
||||
from odoo.tests import Form, tagged
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT
|
||||
|
||||
|
||||
@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.env = cls.env(context=dict(cls.env.context, **DISABLED_MAIL_CONTEXT))
|
||||
cls.company = cls.company_data["company"]
|
||||
cls.env.user.company_id = cls.company.id
|
||||
cls.partner = cls.env["res.partner"].create(
|
||||
{
|
||||
"name": "Test Partner",
|
||||
"bank_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"acc_number": "TEST-NUMBER",
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
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.product = cls.env["product.product"].create(
|
||||
{
|
||||
"name": "Test product",
|
||||
"type": "service",
|
||||
}
|
||||
)
|
||||
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.product.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.product.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)
|
||||
self.assertFalse(order.partner_banks_archive_msg)
|
||||
order.payment_line_ids.partner_bank_id.action_archive()
|
||||
self.assertTrue(order.partner_banks_archive_msg)
|
||||
order.payment_line_ids.partner_bank_id.action_unarchive()
|
||||
self.assertFalse(order.partner_banks_archive_msg)
|
||||
order.draft2open()
|
||||
self.assertEqual(order.payment_ids[0].partner_bank_id, self.partner.bank_ids)
|
||||
order.open2generated()
|
||||
order.generated2uploaded()
|
||||
self.assertEqual(order.move_ids[0].date, order.payment_ids[0].date)
|
||||
self.assertEqual(order.state, "uploaded")
|
||||
|
||||
def _line_creation(self, outbound_order):
|
||||
vals = {
|
||||
"order_id": outbound_order.id,
|
||||
"partner_id": self.partner.id,
|
||||
"currency_id": outbound_order.payment_mode_id.company_id.currency_id.id,
|
||||
"amount_currency": 200.38,
|
||||
"move_line_id": self.invoice.invoice_line_ids[0].id,
|
||||
}
|
||||
return self.env["account.payment.line"].create(vals)
|
||||
|
||||
def test_account_payment_line_creation_without_payment_mode(self):
|
||||
self.invoice.payment_mode_id = False
|
||||
self.invoice.action_post()
|
||||
with self.assertRaises(UserError):
|
||||
self.env["account.invoice.payment.line.multi"].with_context(
|
||||
active_model="account.move", active_ids=self.invoice.ids
|
||||
).create({}).run()
|
||||
|
||||
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)
|
||||
# Create two payment lines with the same
|
||||
# move line id and try to assign them to outbound order
|
||||
payment_line_1 = self._line_creation(outbound_order)
|
||||
outbound_order.payment_line_ids |= payment_line_1
|
||||
with self.assertRaises(ValidationError):
|
||||
payment_line_2 = self._line_creation(outbound_order)
|
||||
outbound_order.payment_line_ids |= payment_line_2
|
||||
|
||||
def test_invoice_communication_01(self):
|
||||
self.assertEqual(
|
||||
"F1242", self.invoice._get_payment_order_communication_direct()
|
||||
)
|
||||
self.invoice.ref = "F1243"
|
||||
self.assertEqual(
|
||||
"F1243", self.invoice._get_payment_order_communication_direct()
|
||||
)
|
||||
|
||||
def test_invoice_communication_02(self):
|
||||
self.assertEqual(
|
||||
"F1242", self.invoice._get_payment_order_communication_direct()
|
||||
)
|
||||
self.invoice.payment_reference = "R1234"
|
||||
self.assertEqual(
|
||||
"R1234", self.invoice._get_payment_order_communication_direct()
|
||||
)
|
||||
|
||||
def test_invoice_communication_03(self):
|
||||
self.invoice.ref = False
|
||||
self.invoice.action_post()
|
||||
self.assertEqual("", self.invoice._get_payment_order_communication_direct())
|
||||
reverse_wizard = Form(
|
||||
self.env["account.move.reversal"].with_context(
|
||||
active_ids=self.invoice.ids, active_model=self.invoice._name
|
||||
)
|
||||
)
|
||||
reverse = reverse_wizard.save()
|
||||
reverse_res = reverse.reverse_moves()
|
||||
reverse_move = self.env[reverse_res["res_model"]].browse(reverse_res["res_id"])
|
||||
self.assertEqual(
|
||||
" %s" % reverse_move.ref,
|
||||
self.invoice._get_payment_order_communication_full(),
|
||||
)
|
||||
self.invoice.ref = "ref"
|
||||
self.assertEqual(
|
||||
"ref %s" % reverse_move.ref,
|
||||
self.invoice._get_payment_order_communication_full(),
|
||||
)
|
||||
|
||||
def test_supplier_invoice_payment_reference(self):
|
||||
self.invoice.payment_reference = "+++F1234+++"
|
||||
self.invoice.action_post()
|
||||
self.assertEqual(
|
||||
"+++F1234+++", self.invoice._get_payment_order_communication_full()
|
||||
)
|
||||
|
||||
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].payment_ids.date, fields.Date.today()
|
||||
)
|
||||
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.assertEqual(
|
||||
"F1242", self.invoice._get_payment_order_communication_direct()
|
||||
)
|
||||
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.assertEqual("R1234", self.refund._get_payment_order_communication_direct())
|
||||
|
||||
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.assertEqual(
|
||||
"F/1234", self.invoice._get_payment_order_communication_direct()
|
||||
)
|
||||
self.refund = self._create_supplier_refund(self.invoice)
|
||||
with Form(self.refund) as refund_form:
|
||||
refund_form.ref = "R1234"
|
||||
refund_form.payment_reference = "FR/1234"
|
||||
with refund_form.invoice_line_ids.edit(0) as line_form:
|
||||
line_form.price_unit = 75.0
|
||||
|
||||
self.refund.action_post()
|
||||
self.assertEqual(
|
||||
"FR/1234", self.refund._get_payment_order_communication_direct()
|
||||
)
|
||||
|
||||
# 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 FR/1234", payment_order.payment_line_ids.communication)
|
||||
|
||||
def test_multiple_lines_without_move_line(self):
|
||||
"""Test that a payment order with multiple lines without related
|
||||
journal items can be created"""
|
||||
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",
|
||||
"payment_line_ids": [
|
||||
Command.create(
|
||||
{
|
||||
"partner_id": self.partner.id,
|
||||
"amount_currency": 101.00,
|
||||
}
|
||||
),
|
||||
Command.create(
|
||||
{
|
||||
"partner_id": self.partner.id,
|
||||
"amount_currency": 101.00,
|
||||
}
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
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.assertEqual(
|
||||
"F1242", self.invoice._get_payment_order_communication_direct()
|
||||
)
|
||||
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.assertEqual("R1234", self.refund._get_payment_order_communication_direct())
|
||||
|
||||
(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)
|
||||
|
||||
def test_action_open_business_document(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()
|
||||
order = self.env["account.payment.order"].search(self.domain)
|
||||
# Create payment line without move line
|
||||
vals = {
|
||||
"order_id": order.id,
|
||||
"partner_id": self.partner.id,
|
||||
"currency_id": order.payment_mode_id.company_id.currency_id.id,
|
||||
"amount_currency": 200.38,
|
||||
}
|
||||
self.env["account.payment.line"].create(vals)
|
||||
invoice_action = order.payment_line_ids[0].action_open_business_doc()
|
||||
self.assertEqual(invoice_action["res_model"], "account.move")
|
||||
self.assertEqual(invoice_action["res_id"], self.invoice.id)
|
||||
manual_line_action = order.payment_line_ids[1].action_open_business_doc()
|
||||
self.assertFalse(manual_line_action)
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# Copyright 2023 Noviat
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
import odoo.tests
|
||||
from odoo import fields
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
|
||||
|
||||
@odoo.tests.tagged("post_install", "-at_install")
|
||||
class TestPaymentOrderTranserJournal(AccountTestInvoicingCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
today = fields.Date.today()
|
||||
cls.in_invoice = cls.init_invoice(
|
||||
"in_invoice", invoice_date=today, products=cls.product_a
|
||||
)
|
||||
cls.bank_journal = cls.company_data["default_journal_bank"]
|
||||
cls.misc_journal = cls.company_data["default_journal_misc"]
|
||||
cls.payment_mode = cls.env["account.payment.mode"].create(
|
||||
{
|
||||
"name": "Test Credit Transfer to Suppliers",
|
||||
"company_id": cls.env.company.id,
|
||||
"payment_method_id": cls.env.ref(
|
||||
"account.account_payment_method_manual_out"
|
||||
).id,
|
||||
"fixed_journal_id": cls.bank_journal.id,
|
||||
"bank_account_link": "fixed",
|
||||
}
|
||||
)
|
||||
|
||||
def test_payment_order_transfer_journal(self):
|
||||
self.in_invoice._post()
|
||||
self.payment_mode.transfer_journal_id = self.misc_journal
|
||||
ap_aml = self.in_invoice.line_ids.filtered(
|
||||
lambda r: r.account_type == "liability_payable"
|
||||
)
|
||||
payline_vals = {
|
||||
"move_line_id": ap_aml.id,
|
||||
"partner_id": self.in_invoice.partner_id.id,
|
||||
"communication": "F0123",
|
||||
"amount_currency": -ap_aml.amount_currency,
|
||||
}
|
||||
order_vals = {
|
||||
"payment_type": "outbound",
|
||||
"payment_mode_id": self.payment_mode.id,
|
||||
"payment_line_ids": [(0, 0, payline_vals)],
|
||||
}
|
||||
order = self.env["account.payment.order"].create(order_vals)
|
||||
order.draft2open()
|
||||
self.assertEqual(order.mapped("move_ids.journal_id"), self.misc_journal)
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
<?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>
|
||||
<div name="button_box" position="inside">
|
||||
<button
|
||||
name="action_payment_lines"
|
||||
type="object"
|
||||
attrs="{'invisible': [('payment_line_count', '=', 0)]}"
|
||||
icon="fa-bars"
|
||||
help="Payment Order Lines"
|
||||
groups="account_payment_order.group_account_payment"
|
||||
>
|
||||
<field
|
||||
string="Payment Lines"
|
||||
name="payment_line_count"
|
||||
widget="statinfo"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<field name="payment_mode_id" position="after">
|
||||
<field name="payment_order_ok" invisible="1" />
|
||||
</field>
|
||||
<!-- First we place the rest of the elements for the new reference distribution -->
|
||||
<field name="payment_reference" position="before">
|
||||
<label for="payment_reference" />
|
||||
<div name="payment_reference_div" class="d-flex">
|
||||
<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'))]}"
|
||||
/>
|
||||
</div>
|
||||
</field>
|
||||
<!-- Then we move the std field, as this can only be on a first level -->
|
||||
<field name="reference_type" position="after">
|
||||
<field name="payment_reference" position="move" />
|
||||
</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>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?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>
|
||||
<!-- Extra form view only to avoid open the recod in edit mode !-->
|
||||
<record id="view_move_line_form_no_edit" model="ir.ui.view">
|
||||
<field name="name">account.move.line.form.no_edit</field>
|
||||
<field name="model">account.move.line</field>
|
||||
<field name="inherit_id" ref="account.view_move_line_form" />
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority">999</field>
|
||||
<field name="arch" type="xml">
|
||||
<form position="attributes">
|
||||
<attribute name="edit">0</attribute>
|
||||
</form>
|
||||
</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">999</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>
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
<?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="state" invisible="1" />
|
||||
<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" readonly="1" force_save="1" />
|
||||
<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_type" invisible="1" />
|
||||
</group>
|
||||
</group>
|
||||
<group col="12" string="Payment Transaction">
|
||||
<field name="payment_ids" colspan="12" nolabel="1" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="account_payment_line_form_readonly" model="ir.ui.view">
|
||||
<field name="name">account.payment.line.form.readonly</field>
|
||||
<field name="model">account.payment.line</field>
|
||||
<field
|
||||
name="inherit_id"
|
||||
ref="account_payment_order.account_payment_line_form"
|
||||
/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<form position="attributes">
|
||||
<attribute name="create">0</attribute>
|
||||
<attribute name="edit">0</attribute>
|
||||
<attribute name="delete">0</attribute>
|
||||
<attribute name="duplicate">0</attribute>
|
||||
</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="partner_bank_acc_type" optional="hide" />
|
||||
<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" />
|
||||
<button
|
||||
name="action_open_business_doc"
|
||||
type="object"
|
||||
string="View"
|
||||
class="btn btn-secondary"
|
||||
attrs="{'invisible': [('move_line_id', '=', False)]}"
|
||||
/>
|
||||
</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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?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>
|
||||
<field name="variable_journal_ids" position="after">
|
||||
<field name="transfer_journal_id" />
|
||||
</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>
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
<?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>
|
||||
<div
|
||||
class="alert alert-warning"
|
||||
role="alert"
|
||||
attrs="{'invisible': [('partner_banks_archive_msg', '=', False)]}"
|
||||
>
|
||||
The following bank accounts are archived: <field
|
||||
name="partner_banks_archive_msg"
|
||||
readonly="1"
|
||||
/>
|
||||
</div>
|
||||
<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"
|
||||
context="{'tree_view_ref': 'account_payment_order.view_account_payment_tree_payment_order'}"
|
||||
/>
|
||||
</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>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="view_account_payment_tree_payment_order" model="ir.ui.view">
|
||||
<field name="name">account.payment.tree</field>
|
||||
<field name="model">account.payment</field>
|
||||
<field name="mode">primary</field>
|
||||
<field name="inherit_id" ref="account.view_account_payment_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="payment_reference" />
|
||||
<field name="order_state" invisible="1" />
|
||||
<button
|
||||
name="update_payment_reference"
|
||||
attrs="{'invisible': [('order_state', '!=', 'open')]}"
|
||||
title="Update Payment Reference"
|
||||
type="object"
|
||||
icon="fa-undo"
|
||||
/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_account_payment_form" model="ir.ui.view">
|
||||
<field name="name">account.payment.form</field>
|
||||
<field name="model">account.payment</field>
|
||||
<field name="inherit_id" ref="account.view_account_payment_form" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="ref" position="after">
|
||||
<field
|
||||
name="payment_reference"
|
||||
attrs="{'invisible': [('payment_reference', '=', False)]}"
|
||||
/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?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"
|
||||
readonly="1"
|
||||
/>
|
||||
<label for="create_uid" string="Created by" />
|
||||
<div name="creation_div">
|
||||
<field name="create_uid" readonly="1" class="oe_inline" />
|
||||
on
|
||||
<field name="create_date" readonly="1" class="oe_inline" />
|
||||
</div>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
from . import account_payment_line_create
|
||||
from . import account_invoice_payment_line_multi
|
||||
from . import account_payment_update
|
||||
from . import res_config_settings
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
<?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"
|
||||
colspan="2"
|
||||
/>
|
||||
</group>
|
||||
<group
|
||||
name="move_lines"
|
||||
string="Selected Move Lines to Create Transactions"
|
||||
>
|
||||
<field
|
||||
name="move_line_ids"
|
||||
nolabel="1"
|
||||
force_save="1"
|
||||
context="{'tree_view_ref': 'account_payment_order.view_move_line_tree', 'form_view_ref':'account_payment_order.view_move_line_form_no_edit'}"
|
||||
colspan="2"
|
||||
>
|
||||
<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>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# Copyright 2009-2023 Noviat
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class AccountPaymentUpdate(models.TransientModel):
|
||||
_name = "account.payment.update"
|
||||
_description = "Update Payment Reference"
|
||||
|
||||
payment_reference = fields.Char(required=True)
|
||||
|
||||
def update_payment_reference(self):
|
||||
payment = self.env["account.payment"].browse(self.env.context.get("active_id"))
|
||||
payment.payment_reference = self.payment_reference
|
||||
payment.ref = self.payment_reference
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<odoo>
|
||||
|
||||
<record id="account_payment_update_view_form" model="ir.ui.view">
|
||||
<field name="name">Update Payment Communication</field>
|
||||
<field name="model">account.payment.update</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="payment_reference" />
|
||||
</group>
|
||||
<footer>
|
||||
<button
|
||||
string="Update Payment Reference"
|
||||
name="update_payment_reference"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
data-hotkey="q"
|
||||
/>
|
||||
<button
|
||||
string="Cancel"
|
||||
class="btn-secondary"
|
||||
special="cancel"
|
||||
data-hotkey="z"
|
||||
/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# Copyright 2023 Noviat
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
transfer_journal_id = fields.Many2one(
|
||||
related="company_id.transfer_journal_id", readonly=False
|
||||
)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.payment</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="account.res_config_settings_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@id='analytic']" position="after">
|
||||
<h2>Payment Orders</h2>
|
||||
<div class="row mt16 o_settings_container" id="transfer_journal">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_right_pane">
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label
|
||||
for="transfer_journal_id"
|
||||
class="col-md-6 o_light_label"
|
||||
/>
|
||||
<field name="transfer_journal_id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Loading…
Add table
Add a link
Reference in a new issue