Skip to content

Commit cbb8bd0

Browse files
[ADD] account_billing_portal
1 parent 5a307d0 commit cbb8bd0

24 files changed

Lines changed: 1574 additions & 0 deletions

account_billing_portal/README.rst

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
.. image:: https://odoo-community.org/readme-banner-image
2+
:target: https://odoo-community.org/get-involved?utm_source=readme
3+
:alt: Odoo Community Association
4+
5+
======================
6+
Account Billing Portal
7+
======================
8+
9+
..
10+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
11+
!! This file is generated by oca-gen-addon-readme !!
12+
!! changes will be overwritten. !!
13+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
14+
!! source digest: sha256:ce1072bdc43f0dc3c215ff3f1918bad8baa4bf3a2a4dfc3e8754abdc22eac545
15+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
16+
17+
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
18+
:target: https://odoo-community.org/page/development-status
19+
:alt: Alpha
20+
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
21+
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
22+
:alt: License: AGPL-3
23+
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Faccount--invoicing-lightgray.png?logo=github
24+
:target: https://github.com/OCA/account-invoicing/tree/16.0/account_billing_portal
25+
:alt: OCA/account-invoicing
26+
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
27+
:target: https://translation.odoo-community.org/projects/account-invoicing-16-0/account-invoicing-16-0-account_billing_portal
28+
:alt: Translate me on Weblate
29+
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
30+
:target: https://runboat.odoo-community.org/builds?repo=OCA/account-invoicing&target_branch=16.0
31+
:alt: Try me on Runboat
32+
33+
|badge1| |badge2| |badge3| |badge4| |badge5|
34+
35+
This module adds a portal view for account billings. It allows users to change the
36+
portal billing report template via a configurable setting. It also adds the ability
37+
to send emails to partners directly from billing records with the billing report
38+
attached.
39+
40+
.. IMPORTANT::
41+
This is an alpha version, the data model and design can change at any time without warning.
42+
Only for development or testing purpose, do not use in production.
43+
`More details on development status <https://odoo-community.org/page/development-status>`_
44+
45+
**Table of contents**
46+
47+
.. contents::
48+
:local:
49+
50+
Configuration
51+
=============
52+
53+
To choose the billing portal template:
54+
55+
* Go to *Invoicing → Configuration → Settings*.
56+
* Set a value in **Choose Billing Email Template**.
57+
If set, this template will be used when sending billing emails to
58+
customers/vendors.
59+
If left empty, the default template from the *Account Billing Portal*
60+
module will be used instead.
61+
62+
To use a custom report for the billing portal and billing email
63+
attachment, set the desired report in the *Optional report to print and
64+
attach* field on the email template configured above. If no report is
65+
set on the template, the standard report from the *Account Billing*
66+
module will be used.
67+
68+
Bug Tracker
69+
===========
70+
71+
Bugs are tracked on `GitHub Issues <https://github.com/OCA/account-invoicing/issues>`_.
72+
In case of trouble, please check there if your issue has already been reported.
73+
If you spotted it first, help us to smash it by providing a detailed and welcomed
74+
`feedback <https://github.com/OCA/account-invoicing/issues/new?body=module:%20account_billing_portal%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
75+
76+
Do not contact contributors directly about support or help with technical issues.
77+
78+
Credits
79+
=======
80+
81+
Authors
82+
~~~~~~~
83+
84+
* Quartile
85+
86+
Contributors
87+
~~~~~~~~~~~~
88+
89+
* `Quartile <https://www.quartile.co>`__:
90+
91+
* Aung Ko Ko Lin
92+
93+
Maintainers
94+
~~~~~~~~~~~
95+
96+
This module is maintained by the OCA.
97+
98+
.. image:: https://odoo-community.org/logo.png
99+
:alt: Odoo Community Association
100+
:target: https://odoo-community.org
101+
102+
OCA, or the Odoo Community Association, is a nonprofit organization whose
103+
mission is to support the collaborative development of Odoo features and
104+
promote its widespread use.
105+
106+
.. |maintainer-yostashiro| image:: https://github.com/yostashiro.png?size=40px
107+
:target: https://github.com/yostashiro
108+
:alt: yostashiro
109+
.. |maintainer-aungkokolin1997| image:: https://github.com/aungkokolin1997.png?size=40px
110+
:target: https://github.com/aungkokolin1997
111+
:alt: aungkokolin1997
112+
113+
Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:
114+
115+
|maintainer-yostashiro| |maintainer-aungkokolin1997|
116+
117+
This module is part of the `OCA/account-invoicing <https://github.com/OCA/account-invoicing/tree/16.0/account_billing_portal>`_ project on GitHub.
118+
119+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

account_billing_portal/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import controllers
2+
from . import models
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright 2025 Quartile (https://www.quartile.co)
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
{
4+
"name": "Account Billing Portal",
5+
"summary": "Portal view for account billings",
6+
"version": "16.0.1.0.0",
7+
"author": "Quartile, Odoo Community Association (OCA)",
8+
"category": "Accounting",
9+
"website": "https://github.com/OCA/account-invoicing",
10+
"license": "AGPL-3",
11+
"depends": ["account_billing"],
12+
"data": [
13+
"security/account_billing_portal_security.xml",
14+
"security/ir.model.access.csv",
15+
"data/mail_template_data.xml",
16+
"views/account_billing_portal_templates.xml",
17+
"views/account_billing_views.xml",
18+
"views/res_config_settings_views.xml",
19+
],
20+
"maintainers": ["yostashiro", "aungkokolin1997"],
21+
"development_status": "Alpha",
22+
"installable": True,
23+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import portal
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Copyright 2025 Quartile (https://www.quartile.co)
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
4+
from collections import OrderedDict
5+
6+
from odoo import _, http
7+
from odoo.exceptions import AccessError, MissingError
8+
from odoo.http import request
9+
10+
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
11+
12+
13+
class CustomerPortalBilling(CustomerPortal):
14+
def _show_report(self, model, report_type, report_ref, download=False):
15+
if model._name != "account.billing":
16+
return super()._show_report(model, report_type, report_ref, download)
17+
template = model.company_id.billing_email_template_id or request.env.ref(
18+
"account_billing_portal.email_template_billing", raise_if_not_found=False
19+
)
20+
report = template.sudo().report_template if template else None
21+
if report:
22+
report_ref = report.id
23+
return super()._show_report(model, report_type, report_ref, download)
24+
25+
def _get_billing_domain(self, bill_type=None):
26+
domain = [("state", "=", "billed")]
27+
if bill_type:
28+
domain.append(("bill_type", "=", bill_type))
29+
return domain
30+
31+
def _prepare_home_portal_values(self, counters):
32+
values = super()._prepare_home_portal_values(counters)
33+
Billing = request.env["account.billing"]
34+
if "customer_bill_count" in counters:
35+
values["customer_bill_count"] = (
36+
Billing.search_count(self._get_billing_domain("out_invoice"))
37+
if Billing.check_access_rights("read", raise_exception=False)
38+
else 0
39+
)
40+
if "vendor_bill_count" in counters:
41+
values["vendor_bill_count"] = (
42+
Billing.search_count(self._get_billing_domain("in_invoice"))
43+
if Billing.check_access_rights("read", raise_exception=False)
44+
else 0
45+
)
46+
return values
47+
48+
def _get_billing_searchbar_sortings(self):
49+
return {
50+
"newest": {"label": _("Newest"), "order": "create_date desc, id desc"},
51+
"billing_date": {"label": _("Billing Date"), "order": "date desc, id desc"},
52+
"name": {"label": _("Name"), "order": "name asc, id asc"},
53+
}
54+
55+
def _render_billing_portal(
56+
self,
57+
page,
58+
sortby,
59+
filterby,
60+
searchbar_filters,
61+
default_filter,
62+
):
63+
values = self._prepare_portal_layout_values()
64+
Billing = request.env["account.billing"]
65+
domain = self._get_billing_domain()
66+
searchbar_sortings = self._get_billing_searchbar_sortings()
67+
if not sortby:
68+
sortby = "newest"
69+
order = searchbar_sortings[sortby]["order"]
70+
if searchbar_filters:
71+
if not filterby or filterby not in searchbar_filters:
72+
filterby = default_filter
73+
domain += searchbar_filters[filterby]["domain"]
74+
count = Billing.search_count(domain)
75+
pager = portal_pager(
76+
url="/my/billings",
77+
url_args={"sortby": sortby, "filterby": filterby},
78+
total=count,
79+
page=page,
80+
step=self._items_per_page,
81+
)
82+
billings = Billing.search(
83+
domain, order=order, limit=self._items_per_page, offset=pager["offset"]
84+
)
85+
request.session["my_billing_history"] = billings.ids[:100]
86+
values.update(
87+
{
88+
"billings": billings,
89+
"page_name": "billing",
90+
"pager": pager,
91+
"searchbar_sortings": searchbar_sortings,
92+
"sortby": sortby,
93+
"searchbar_filters": OrderedDict(sorted(searchbar_filters.items())),
94+
"filterby": filterby,
95+
"default_url": "/my/billings",
96+
}
97+
)
98+
return request.render("account_billing_portal.portal_my_billings", values)
99+
100+
@http.route(
101+
["/my/billings", "/my/billings/page/<int:page>"],
102+
type="http",
103+
auth="user",
104+
website=True,
105+
)
106+
def portal_my_billings(self, page=1, sortby=None, filterby=None, **kw):
107+
return self._render_billing_portal(
108+
page,
109+
sortby,
110+
filterby,
111+
{
112+
"all": {
113+
"label": _("All"),
114+
"domain": [],
115+
},
116+
"out_invoice": {
117+
"label": _("Customer Bills"),
118+
"domain": [("bill_type", "=", "out_invoice")],
119+
},
120+
"in_invoice": {
121+
"label": _("Vendor Bills"),
122+
"domain": [("bill_type", "=", "in_invoice")],
123+
},
124+
},
125+
"all",
126+
)
127+
128+
def _billing_get_page_view_values(self, billing, access_token, **kwargs):
129+
values = {
130+
"billing": billing,
131+
"page_name": "billing",
132+
"report_type": "html",
133+
}
134+
return self._get_page_view_values(
135+
billing, access_token, values, "my_billing_history", False, **kwargs
136+
)
137+
138+
@http.route(
139+
["/my/billings/<int:billing_id>"], type="http", auth="public", website=True
140+
)
141+
def portal_my_billing(
142+
self, billing_id, access_token=None, report_type=None, download=False, **kw
143+
):
144+
try:
145+
billing_sudo = self._document_check_access(
146+
"account.billing", billing_id, access_token
147+
)
148+
except (AccessError, MissingError):
149+
return request.redirect("/my")
150+
if report_type in ("html", "pdf", "text"):
151+
pdf_report_name = "account_billing.report_account_billing"
152+
return self._show_report(
153+
model=billing_sudo,
154+
report_type=report_type,
155+
report_ref=pdf_report_name,
156+
download=download,
157+
)
158+
values = self._billing_get_page_view_values(billing_sudo, access_token, **kw)
159+
return request.render("account_billing_portal.portal_my_billing", values)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<odoo noupdate="1">
3+
<record id="email_template_billing" model="mail.template">
4+
<field name="name">Billing</field>
5+
<field name="model_id" ref="account_billing.model_account_billing" />
6+
<field
7+
name="subject"
8+
>{{ object.company_id.name }} Billing (Ref {{ object.name or 'n/a' }})</field>
9+
<field name="partner_to">{{ object.partner_id.id }}</field>
10+
<field name="description">Sent billing to customer/vendor</field>
11+
<field name="body_html" type="html">
12+
<div style="margin: 0px; padding: 0px;">
13+
<p style="margin: 0px; padding: 0px; font-size: 13px;">
14+
Dear <t t-out="object.partner_id.name or ''">Brandon Freeman</t>
15+
<t t-if="object.partner_id.parent_id">
16+
(<t t-out="object.partner_id.parent_id.name or ''">Azure Interior</t>)
17+
</t>
18+
<br /><br />
19+
Please find attached the billing from <t
20+
t-out="object.company_id.name or ''"
21+
>YourCompany</t>.
22+
<br /><br />
23+
If you have any questions, please do not hesitate to contact us.
24+
<br /><br />
25+
Best regards,
26+
<t t-if="not is_html_empty(env.user.signature)">
27+
<br />
28+
<br />
29+
<t t-out="env.user.signature or ''" />
30+
</t>
31+
</p>
32+
</div>
33+
</field>
34+
<field name="report_template" ref="account_billing.report_account_billing" />
35+
<field
36+
name="report_name"
37+
>BILL-{{ object.name }}{{ object.state == 'draft' and '-draft' or '' }}</field>
38+
<field name="lang">{{ object.partner_id.lang }}</field>
39+
<field name="auto_delete" eval="True" />
40+
</record>
41+
</odoo>

0 commit comments

Comments
 (0)