account_payment_portal
Some checks failed
pre-commit / pre-commit (push) Failing after 2m39s
tests / Detect unreleased dependencies (push) Successful in 5s
tests / test with OCB (push) Successful in 3m32s
tests / test with Odoo (push) Successful in 2m34s

* [WIP] account_payment_portal: first draft

* [ADD] account_payment_portal

* [IMP] account_payment_portal: Fix user rights and add test

---------

Co-authored-by: Niels Göttsch <ng@ife.de>
This commit is contained in:
madmooose 2025-07-30 12:26:09 +02:00
parent 396894a948
commit e2492df90e
19 changed files with 1010 additions and 0 deletions

View file

@ -0,0 +1,74 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association
======================
Account Payment Portal
======================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:d961a48b7e2fd11928567081091441bb33770a0f2b9b3b1f4f853a95d5589cec
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-madmooose%2Fodooapps-lightgray.png?logo=github
:target: https://github.com/madmooose/odooapps/tree/16.0/account_payment_portal
:alt: madmooose/odooapps
|badge1| |badge2| |badge3|
Add a Portal View for Customer Payments
**Table of contents**
.. contents::
:local:
Changelog
=========
- 16.0.1.0.0: Initial module
- 16.0.1.0.1: Fix security rules
- 16.0.1.0.2: Add tests
- 16.0.1.0.3: Add and use default image field
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/madmooose/odooapps/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/madmooose/odooapps/issues/new?body=module:%20account_payment_portal%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
~~~~~~~
* BAKEUP
* Niels Göttsch
Contributors
~~~~~~~~~~~~
* Niels Göttsch <niels@ziemlichoptimal.de>
* Matthias Brück <hi@brueck.io>
Maintainers
~~~~~~~~~~~
This module is part of the `madmooose/odooapps <https://github.com/madmooose/odooapps/tree/16.0/account_payment_portal>`_ project on GitHub.
You are welcome to contribute.

View file

@ -0,0 +1,2 @@
from . import controllers
from . import models

View file

@ -0,0 +1,15 @@
{
"name": "Account Payment Portal",
"summary": "Add a Portal View for Customer Payments",
"author": "BAKEUP,Niels Göttsch",
"website": "https://ziemlichoptimal.de",
"category": "Accounting",
"version": "16.0.1.0.2",
"depends": ["account", "portal"],
"data": [
"security/ir.model.access.csv",
"security/account_security.xml",
"views/portal_templates.xml",
],
"license": "LGPL-3",
}

View file

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

View file

@ -0,0 +1,153 @@
from collections import OrderedDict
from odoo import _, http
from odoo.http import request
from odoo.osv import expression
from odoo.addons.portal.controllers.portal import CustomerPortal
from odoo.addons.portal.controllers.portal import pager as portal_pager
class AccountPaymentPortal(CustomerPortal):
def _prepare_home_portal_values(self, counters):
values = super()._prepare_home_portal_values(counters)
if "payment_count" in counters:
payment_count = (
request.env["account.payment"].search_count(self._get_payment_domain())
if request.env["account.payment"].check_access_rights(
"read", raise_exception=False
)
else 0
)
values["payment_count"] = payment_count
return values
# ------------------------------------------------------------
# My Payments
# ------------------------------------------------------------
def _payment_get_page_view_values(self, payment, access_token, **kwargs):
values = {"page_name": "payment", "payment": payment}
return self._get_page_view_values(
payment, access_token, values, "my_invoices_history", False, **kwargs
)
def _get_payment_domain(self):
return [("is_internal_transfer", "=", False)]
def _get_account_searchbar_sortings(self):
return {
"date": {"label": _("Date"), "order": "date desc"},
"name": {"label": _("Reference"), "order": "name desc"},
"state": {"label": _("Status"), "order": "state"},
}
def _get_account_searchbar_filters(self):
# Add filter for payments and credit notes?
return {
"all": {"label": _("All"), "domain": []},
"sent": {
"label": _("Sent"),
"domain": [("payment_type", "=", "inbound")],
},
"received": {
"label": _("Received"),
"domain": [("payment_type", "=", "outbound")],
},
}
@http.route(
["/my/payments", "/my/payments/page/<int:page>"],
type="http",
auth="user",
website=True,
)
def portal_my_payments(
self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, **kw
):
values = self._prepare_my_payments_values(
page, date_begin, date_end, sortby, filterby
)
# pager
pager = portal_pager(**values["pager"])
# content according to pager and archive selected
payments = values["payments"](pager["offset"])
request.session["my_payments_history"] = payments.ids[:100]
values.update({"payments": payments, "pager": pager})
return request.render("account_payment_portal.portal_my_payments", values)
def _prepare_my_payments_values(
self,
page,
date_begin,
date_end,
sortby,
filterby,
domain=None,
url="/my/payments",
):
values = self._prepare_portal_layout_values()
AccountPayment = request.env["account.payment"]
domain = expression.AND([domain or [], self._get_payment_domain()])
searchbar_sortings = self._get_account_searchbar_sortings()
# default sort by order
if not sortby:
sortby = "date"
order = searchbar_sortings[sortby]["order"]
searchbar_filters = self._get_account_searchbar_filters()
# default filter by value
if not filterby:
filterby = "all"
domain += searchbar_filters[filterby]["domain"]
if date_begin and date_end:
domain += [
("create_date", ">", date_begin),
("create_date", "<=", date_end),
]
values.update(
{
"date": date_begin,
# content according to pager and archive selected lambda function to
# get the invoices recordset when the pager will be defined in the main
# method of a route
"payments": lambda pager_offset: (
AccountPayment.search(
domain,
order=order,
limit=self._items_per_page,
offset=pager_offset,
)
if AccountPayment.check_access_rights("read", raise_exception=False)
else AccountPayment
),
"page_name": "payment",
"pager": { # vals to define the pager.
"url": url,
"url_args": {
"date_begin": date_begin,
"date_end": date_end,
"sortby": sortby,
},
"total": AccountPayment.search_count(domain)
if AccountPayment.check_access_rights("read", raise_exception=False)
else 0,
"page": page,
"step": self._items_per_page,
},
"default_url": url,
"searchbar_sortings": searchbar_sortings,
"sortby": sortby,
"searchbar_filters": OrderedDict(sorted(searchbar_filters.items())),
"filterby": filterby,
}
)
return values

View file

@ -0,0 +1,125 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_payment_portal
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-07-29 10:44+0000\n"
"PO-Revision-Date: 2025-07-29 10:44+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_payment_portal
#: model_terms:ir.ui.view,arch_db:account_payment_portal.portal_my_payments
msgid ""
"<span class=\"badge rounded-pill text-bg-info\"><i class=\"fa fa-fw fa-"
"check\" aria-label=\"Draft\" title=\"Draft\" role=\"img\"/><span "
"class=\"d-none d-md-inline\"> Draft</span></span>"
msgstr ""
"<span class=\"badge rounded-pill text-bg-info\"><i class=\"fa fa-fw fa-"
"check\" aria-label=\"Draft\" title=\"Draft\" role=\"img\"/><span "
"class=\"d-none d-md-inline\"> Entwurf</span></span>"
#. module: account_payment_portal
#: model_terms:ir.ui.view,arch_db:account_payment_portal.portal_my_payments
msgid ""
"<span class=\"badge rounded-pill text-bg-success\"><i class=\"fa fa-fw fa-"
"check\" aria-label=\"Posted\" title=\"Posted\" role=\"img\"/><span "
"class=\"d-none d-md-inline\"> Posted</span></span>"
msgstr ""
"<span class=\"badge rounded-pill text-bg-success\"><i class=\"fa fa-fw fa-"
"check\" aria-label=\"Posted\" title=\"Posted\" role=\"img\"/><span "
"class=\"d-none d-md-inline\"> Registriert</span></span>"
#. module: account_payment_portal
#: model_terms:ir.ui.view,arch_db:account_payment_portal.portal_my_payments
msgid ""
"<span class=\"badge rounded-pill text-bg-warning\"><i class=\"fa fa-fw fa-"
"remove\" aria-label=\"Cancelled\" title=\"Cancelled\" role=\"img\"/><span "
"class=\"d-none d-md-inline\"> Cancelled</span></span>"
msgstr ""
"<span class=\"badge rounded-pill text-bg-warning\"><i class=\"fa fa-fw fa-"
"remove\" aria-label=\"Cancelled\" title=\"Cancelled\" role=\"img\"/><span "
"class=\"d-none d-md-inline\"> Abgebrochen</span></span>"
#. module: account_payment_portal
#. odoo-python
#: code:addons/account_payment_portal/controllers/portal.py:0
#, python-format
msgid "All"
msgstr "Alle"
#. module: account_payment_portal
#: model_terms:ir.ui.view,arch_db:account_payment_portal.portal_my_payments
msgid "Amount"
msgstr "Betrag"
#. module: account_payment_portal
#. odoo-python
#: code:addons/account_payment_portal/controllers/portal.py:0
#, python-format
msgid "Date"
msgstr "Datum"
#. module: account_payment_portal
#: model_terms:ir.ui.view,arch_db:account_payment_portal.portal_my_payments
msgid "Payment #"
msgstr "Zahlung #"
#. module: account_payment_portal
#: model:ir.model.fields,field_description:account_payment_portal.field_account_payment__payment_count
msgid "Payment Count"
msgstr ""
#. module: account_payment_portal
#: model_terms:ir.ui.view,arch_db:account_payment_portal.portal_my_payments
msgid "Payment Date"
msgstr "Zahldatum"
#. module: account_payment_portal
#: model:ir.model,name:account_payment_portal.model_account_payment
#: model_terms:ir.ui.view,arch_db:account_payment_portal.portal_my_home_menu_payment
#: model_terms:ir.ui.view,arch_db:account_payment_portal.portal_my_home_payment
#: model_terms:ir.ui.view,arch_db:account_payment_portal.portal_my_payments
msgid "Payments"
msgstr "Zahlungen"
#. module: account_payment_portal
#. odoo-python
#: code:addons/account_payment_portal/controllers/portal.py:0
#, python-format
msgid "Received"
msgstr "Empfangen"
#. module: account_payment_portal
#. odoo-python
#: code:addons/account_payment_portal/controllers/portal.py:0
#, python-format
msgid "Reference"
msgstr "Referenz"
#. module: account_payment_portal
#. odoo-python
#: code:addons/account_payment_portal/controllers/portal.py:0
#, python-format
msgid "Sent"
msgstr "Gesendet"
#. module: account_payment_portal
#. odoo-python
#: code:addons/account_payment_portal/controllers/portal.py:0
#: model_terms:ir.ui.view,arch_db:account_payment_portal.portal_my_payments
#, python-format
msgid "Status"
msgstr ""
#. module: account_payment_portal
#: model_terms:ir.ui.view,arch_db:account_payment_portal.portal_my_payments
msgid "There are currently no payments for your account."
msgstr "Es gibt keine Zahlungen für diesen Account."

View file

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

View file

@ -0,0 +1,14 @@
from odoo import api, fields, models
class AccountPayment(models.Model):
_inherit = "account.payment"
payment_count = fields.Integer(compute="_compute_count_payment")
@api.depends("partner_id")
def _compute_count_payment(self):
for payment in self:
payment.payment_count = self.env["account.payment"].search_count(
[("partner_id", "=", payment.partner_id.id)]
)

View file

@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View file

@ -0,0 +1,2 @@
* Niels Göttsch <niels@ziemlichoptimal.de>
* Matthias Brück <hi@brueck.io>

View file

@ -0,0 +1 @@
Add a Portal View for Customer Payments

View file

@ -0,0 +1,4 @@
- 16.0.1.0.0: Initial module
- 16.0.1.0.1: Fix security rules
- 16.0.1.0.2: Add tests
- 16.0.1.0.3: Add and use default image field

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<!-- Portal for payment -->
<record id="account_move_payment_rule_portal" model="ir.rule">
<field name="name">Portal Personal Account Payments</field>
<field name="model_id" ref="account.model_account_move" />
<field
name="domain_force"
>[('partner_id','=', user.partner_id.id),('move_type','=','entry')]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]" />
<field name="perm_unlink" eval="False" />
<field name="perm_write" eval="False" />
<field name="perm_read" eval="True" />
<field name="perm_create" eval="False" />
</record>
</odoo>

View file

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_payment_portal,account.payment.portal,account.model_account_payment,base.group_portal,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_payment_portal account.payment.portal account.model_account_payment base.group_portal 1 0 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,434 @@
<!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-portal">
<h1>Account Payment Portal</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:d961a48b7e2fd11928567081091441bb33770a0f2b9b3b1f4f853a95d5589cec
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/license-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/madmooose/odooapps/tree/16.0/account_payment_portal"><img alt="madmooose/odooapps" src="https://img.shields.io/badge/github-madmooose%2Fodooapps-lightgray.png?logo=github" /></a></p>
<p>Add a Portal View for Customer Payments</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#changelog" id="toc-entry-1">Changelog</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-4">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-5">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-6">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="changelog">
<h2><a class="toc-backref" href="#toc-entry-1">Changelog</a></h2>
<ul class="simple">
<li>16.0.1.0.0: Initial module</li>
<li>16.0.1.0.1: Fix security rules</li>
<li>16.0.1.0.2: Add tests</li>
<li>16.0.1.0.3: Add and use default image field</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/madmooose/odooapps/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/madmooose/odooapps/issues/new?body=module:%20account_payment_portal%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-3">Credits</a></h2>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-4">Authors</a></h3>
<ul class="simple">
<li>BAKEUP</li>
<li>Niels Göttsch</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-5">Contributors</a></h3>
<ul class="simple">
<li>Niels Göttsch &lt;<a class="reference external" href="mailto:niels&#64;ziemlichoptimal.de">niels&#64;ziemlichoptimal.de</a>&gt;</li>
<li>Matthias Brück &lt;<a class="reference external" href="mailto:hi&#64;brueck.io">hi&#64;brueck.io</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h3>
<p>This module is part of the <a class="reference external" href="https://github.com/madmooose/odooapps/tree/16.0/account_payment_portal">madmooose/odooapps</a> project on GitHub.</p>
<p>You are welcome to contribute.</p>
</div>
</div>
</div>
</div>
</body>
</html>

View file

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

View file

@ -0,0 +1,58 @@
from odoo.tests import tagged
from odoo.tests.common import TransactionCase
@tagged("post_install", "-at_install")
class TestAccountPaymentPortal(TransactionCase):
def setUp(self):
super().setUp()
# Create two partners
portal_partner = self.env["res.partner"].create({"name": "Portal User"})
other_partner = self.env["res.partner"].create({"name": "Other User"})
# Create two users, one portal
portal_group = self.env.ref("base.group_portal")
portal_user = self.env["res.users"].create(
{
"name": "Portal User",
"login": "portaluser@example.com",
"partner_id": portal_partner.id,
"groups_id": [(6, 0, [portal_group.id])],
}
)
other_user = self.env["res.users"].create(
{
"name": "Other User",
"login": "otheruser@example.com",
"partner_id": other_partner.id,
}
)
# Create two payments
portal_partner_payment = self.env["account.payment"].create(
{
"partner_id": portal_partner.id,
"amount": 100,
"payment_type": "inbound",
"state": "draft",
}
)
other_partner_payment = self.env["account.payment"].create(
{
"partner_id": other_partner.id,
"amount": 200,
"payment_type": "inbound",
"state": "draft",
}
)
self.portal_user = portal_user
self.other_user = other_user
self.portal_partner_payment = portal_partner_payment
self.other_partner_payment = other_partner_payment
def test_portal_user_sees_only_own_payment(self):
# Switch to portal user context
payments = self.env["account.payment"].with_user(self.portal_user.id).search([])
self.assertIn(self.portal_partner_payment, payments)
self.assertNotIn(self.other_partner_payment, payments)

View file

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<template
id="portal_my_home_menu_payment"
name="Portal layout : payment menu entries"
inherit_id="portal.portal_breadcrumbs"
priority="35"
>
<xpath expr="//ol[hasclass('o_portal_submenu')]" position="inside">
<li
t-if="page_name == 'payment'"
t-attf-class="breadcrumb-item #{'active ' if not payment else ''}"
>
Payments
</li>
</xpath>
</template>
<template
id="portal_my_home_payment"
name="Show Payments"
inherit_id="portal.portal_my_home"
customize_show="True"
priority="35"
>
<xpath expr="//div[hasclass('o_portal_docs')]" position="inside">
<t t-call="portal.portal_docs_entry">
<t t-set="title">Payments</t>
<t t-set="url" t-value="'/my/payments'" />
<t t-set="placeholder_count" t-value="'payment_count'" />
</t>
</xpath>
</template>
<template id="portal_my_payments" name="Portal My Payments">
<t t-call="portal.portal_layout">
<t t-set="breadcrumbs_searchbar" t-value="True" />
<t t-call="portal.portal_searchbar">
<t t-set="title">Payments</t>
</t>
<t t-if="not payments">
<p>There are currently no payments for your account.</p>
</t>
<t t-if="payments" t-call="portal.portal_table">
<thead>
<tr class="active">
<th>Payment #</th>
<th>Payment Date</th>
<th class="text-center">Status</th>
<th class="text-end">Amount</th>
</tr>
</thead>
<tbody>
<t t-foreach="payments" t-as="payment">
<tr>
<td>
<t t-esc="payment.name" />
</td>
<td><span t-field="payment.date" /></td>
<td class="tx_status text-center">
<t t-if="payment.state == 'posted'">
<span class="badge rounded-pill text-bg-success"><i
class="fa fa-fw fa-check"
aria-label="Posted"
title="Posted"
role="img"
/><span
class="d-none d-md-inline"
> Posted</span></span>
</t>
<t t-if="payment.state == 'draft'">
<span class="badge rounded-pill text-bg-info"><i
class="fa fa-fw fa-check"
aria-label="Draft"
title="Draft"
role="img"
/><span
class="d-none d-md-inline"
> Draft</span></span>
</t>
<t t-if="payment.state == 'cancel'">
<span class="badge rounded-pill text-bg-warning"><i
class="fa fa-fw fa-remove"
aria-label="Cancelled"
title="Cancelled"
role="img"
/><span
class="d-none d-md-inline"
> Cancelled</span></span>
</t>
</td>
<td class="text-end">
<span
t-out="payment.amount_signed"
t-options='{"widget": "monetary", "display_currency": payment.currency_id}'
/>
</td>
</tr>
</t>
</tbody>
</t>
</t>
</template>
</odoo>