Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions account_billing_portal/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
======================
Account Billing Portal
======================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:89babb2f094b4575c62722a24872eaccae37cdc5edc018aaaf18c671e7d243a1
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Faccount--invoicing-lightgray.png?logo=github
:target: https://github.com/OCA/account-invoicing/tree/18.0/account_billing_portal
:alt: OCA/account-invoicing
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/account-invoicing-18-0/account-invoicing-18-0-account_billing_portal
: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/account-invoicing&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module adds a portal view for account billings. It allows users to
change the portal billing report template via a configurable setting. It
also adds the ability to send emails to partners directly from billing
records with the billing report attached.

.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_

**Table of contents**

.. contents::
:local:

Configuration
=============

To choose the billing portal template:

- Go to *Invoicing → Configuration → Settings*.
- Set a value in **Choose Billing Portal Report**. If set, this report
will be used for both the billing portal and the billing email
attachment sent to the partner. If left empty, the standard report
from the Account Billing module will be used instead.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/account-invoicing/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/account-invoicing/issues/new?body=module:%20account_billing_portal%0Aversion:%2018.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
-------

* Quartile

Contributors
------------

- `Quartile <https://www.quartile.co>`__:

- Aung Ko Ko Lin

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.

.. |maintainer-yostashiro| image:: https://github.com/yostashiro.png?size=40px
:target: https://github.com/yostashiro
:alt: yostashiro
.. |maintainer-aungkokolin1997| image:: https://github.com/aungkokolin1997.png?size=40px
:target: https://github.com/aungkokolin1997
:alt: aungkokolin1997

Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-yostashiro| |maintainer-aungkokolin1997|

This module is part of the `OCA/account-invoicing <https://github.com/OCA/account-invoicing/tree/18.0/account_billing_portal>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
2 changes: 2 additions & 0 deletions account_billing_portal/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import controllers
from . import models
22 changes: 22 additions & 0 deletions account_billing_portal/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2025 Quartile (https://www.quartile.co)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Account Billing Portal",
"version": "18.0.1.0.0",
"author": "Quartile, Odoo Community Association (OCA)",
"category": "Accounting",
"website": "https://github.com/OCA/account-invoicing",
"license": "AGPL-3",
"depends": ["account_billing"],
"data": [
"security/account_billing_portal_security.xml",
"security/ir.model.access.csv",
"data/mail_template_data.xml",
"views/account_billing_portal_templates.xml",
"views/account_billing_views.xml",
"views/res_config_settings_views.xml",
],
"maintainers": ["yostashiro", "aungkokolin1997"],
"development_status": "Alpha",
"installable": True,
}
1 change: 1 addition & 0 deletions account_billing_portal/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import portal
175 changes: 175 additions & 0 deletions account_billing_portal/controllers/portal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Copyright 2025 Quartile (https://www.quartile.co)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from collections import OrderedDict

from odoo import _, http
from odoo.exceptions import AccessError, MissingError
from odoo.http import request

from odoo.addons.portal.controllers.portal import CustomerPortal
from odoo.addons.portal.controllers.portal import pager as portal_pager


class CustomerPortalBilling(CustomerPortal):
def _show_report(self, model, report_type, report_ref, download=False):
if not (model._name == "account.billing"):
return super()._show_report(model, report_type, report_ref, download)
billing_report = request.env.user.company_id.billing_portal_report
if billing_report:
external_id = billing_report.get_external_id()
report_ref = external_id.get(billing_report.id)
return super()._show_report(model, report_type, report_ref, download)

def _get_billing_domain(self, bill_type=None):
domain = [("state", "=", "billed")]
if bill_type:
domain.append(("bill_type", "=", bill_type))
return domain

def _prepare_home_portal_values(self, counters):
values = super()._prepare_home_portal_values(counters)
Billing = request.env["account.billing"]
if "customer_bill_count" in counters:
values["customer_bill_count"] = (
Billing.search_count(self._get_billing_domain("out_invoice"))
if Billing.has_access("read")
else 0
)
if "vendor_bill_count" in counters:
values["vendor_bill_count"] = (
Billing.search_count(self._get_billing_domain("in_invoice"))
if Billing.has_access("read")
else 0
)
return values

def _get_billing_searchbar_sortings(self):
return {
"date": {"label": _("Newest"), "order": "create_date desc, id desc"},
"billing_date": {"label": _("Billing Date"), "order": "date desc, id desc"},
"name": {"label": _("Name"), "order": "name asc, id asc"},
}

def _render_billing_portal(
self,
page,
date_begin,
date_end,
sortby,
filterby,
searchbar_filters,
default_filter,
):
values = self._prepare_portal_layout_values()
Billing = request.env["account.billing"]
domain = self._get_billing_domain()
if date_begin and date_end:
domain += [
("create_date", ">", date_begin),
("create_date", "<=", date_end),
]
searchbar_sortings = self._get_billing_searchbar_sortings()
if not sortby:
sortby = "date"
order = searchbar_sortings[sortby]["order"]
if searchbar_filters:
if not filterby:
filterby = default_filter
domain += searchbar_filters[filterby]["domain"]
count = Billing.search_count(domain)
pager = portal_pager(
url="/my/billings",
url_args={
"date_begin": date_begin,
"date_end": date_end,
"sortby": sortby,
"filterby": filterby,
},
total=count,
page=page,
step=self._items_per_page,
)
billings = Billing.search(
domain, order=order, limit=self._items_per_page, offset=pager["offset"]
)
request.session["my_billing_history"] = billings.ids[:100]
values.update(
{
"date": date_begin,
"billings": billings,
"page_name": "billing",
"pager": pager,
"searchbar_sortings": searchbar_sortings,
"sortby": sortby,
"searchbar_filters": OrderedDict(sorted(searchbar_filters.items())),
"filterby": filterby,
"default_url": "/my/billings",
}
)
return request.render("account_billing_portal.portal_my_billings", values)

@http.route(
["/my/billings", "/my/billings/page/<int:page>"],
type="http",
auth="user",
website=True,
)
def portal_my_billings(
self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, **kw
):
return self._render_billing_portal(
page,
date_begin,
date_end,
sortby,
filterby,
{
"all": {
"label": _("All"),
"domain": [("state", "in", ["draft", "billed", "cancel"])],
},
"out_invoice": {
"label": _("Customer Bills"),
"domain": [("bill_type", "=", "out_invoice")],
},
"in_invoice": {
"label": _("Vendor Bills"),
"domain": [("bill_type", "=", "in_invoice")],
},
},
"all",
)

def _billing_get_page_view_values(self, billing, access_token, **kwargs):
values = {
"billing": billing,
"page_name": "billing",
"report_type": "html",
}
return self._get_page_view_values(
billing, access_token, values, "my_billing_history", False, **kwargs
)

@http.route(
["/my/billings/<int:billing_id>"], type="http", auth="public", website=True
)
def portal_my_billing(
self, billing_id, access_token=None, report_type=None, download=False, **kw
):
try:
billing_sudo = self._document_check_access(
"account.billing", billing_id, access_token
)
except (AccessError, MissingError):
return request.redirect("/my")
if report_type in ("html", "pdf", "text"):
pdf_report_name = "account_billing.report_account_billing"
return self._show_report(
model=billing_sudo,
report_type=report_type,
report_ref=pdf_report_name,
download=download,
)
values = self._billing_get_page_view_values(billing_sudo, access_token, **kw)
return request.render("account_billing_portal.portal_my_billing", values)
37 changes: 37 additions & 0 deletions account_billing_portal/data/mail_template_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="email_template_billing" model="mail.template">
<field name="name">Billing</field>
<field name="model_id" ref="account_billing.model_account_billing" />
<field
name="subject"
>{{ object.company_id.name }} Billing (Ref {{ object.name or 'n/a' }})</field>
<field name="partner_to">{{ object.partner_id.id }}</field>
<field name="description">Sent billing manually to customer/vendor</field>
<field name="body_html" type="html">
<div style="margin: 0px; padding: 0px;">
<p style="margin: 0px; padding: 0px; font-size: 13px;">
Dear <t t-out="object.partner_id.name or ''">Brandon Freeman</t>
<t t-if="object.partner_id.parent_id">
(<t t-out="object.partner_id.parent_id.name or ''">Azure Interior</t>)
</t>
<br /><br />
Please find attached the billing from <t
t-out="object.company_id.name or ''"
>YourCompany</t>.
<br /><br />
If you have any questions, please do not hesitate to contact us.
<br /><br />
Best regards,
<t t-if="not is_html_empty(env.user.signature)">
<br />
<br />
<t t-out="env.user.signature or ''" />
</t>
</p>
</div>
</field>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True" />
</record>
</odoo>
3 changes: 3 additions & 0 deletions account_billing_portal/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import account_billing
from . import res_company
from . import res_config_settings
Loading