Let donors view and download their own tax receipts from ``/my/tax-receipts``: a paginated list, a detail page with the receipt information, its invoices and the native portal chatter, and a PDF download rendered on demand through the configured ``pdf_form`` report. The PDF rendering is delegated to ``pdf_form_report`` and nothing is archived: a confirmed receipt is frozen by ``l10n_fr_tax_receipt``, so rendering it again yields the same document. Two safety nets compensate third-party behaviors: - report resolution by ``report_uuid`` (system parameter ``tax_receipt_in_portal.report_uuid``) with strict validation of report type and target model; - a blank-field guard which resolves every mapped PDF field value before rendering, because ``pdf_form_report`` swallows resolution errors and would otherwise silently serve a receipt with a blank amount. Access is enforced twice: a portal record rule (own commercial partner, confirmed/sent state only) and an explicit controller domain that also covers internal users browsing ``/my``. Tested with 17 tests (portal isolation, draft exposure, chatter data access, blank-field guard, report misconfiguration, private API contract of ``pdf_form_report``), including a mutation check proving the security tests fail when the record rule is opened.
198 lines
7.4 KiB
ReStructuredText
198 lines
7.4 KiB
ReStructuredText
=====================
|
|
tax_receipt_in_portal
|
|
=====================
|
|
|
|
Let donors download their own tax receipts from the Odoo portal.
|
|
|
|
Description
|
|
===========
|
|
|
|
This module publishes the tax receipts managed by ``l10n_fr_tax_receipt``
|
|
to the donor they were issued to:
|
|
|
|
* a **Tax Receipts** card on ``/my/home``, with a counter;
|
|
* a paginated list on ``/my/tax-receipts``;
|
|
* a detail page on ``/my/tax-receipts/<id>`` with the receipt
|
|
information, its invoices and the **portal chatter**, where the donor
|
|
can talk to the association;
|
|
* a per-receipt PDF download on ``/my/tax-receipts/<id>/download``.
|
|
|
|
The invoices section only lists invoices the donor is actually allowed to
|
|
read — Odoo auto-subscribes a partner linked to a user when the invoice is
|
|
created, which satisfies the ``account.move`` portal rule. Unreadable
|
|
invoices are hidden rather than shown as dead links.
|
|
|
|
The PDF is produced by the ``pdf_form`` report configured for the receipt
|
|
model and is **rendered on demand**: nothing is archived. This is sound
|
|
because ``l10n_fr_tax_receipt`` freezes a receipt once confirmed — its
|
|
invoice set can no longer change and its amount is stored — so rendering
|
|
the same receipt twice yields the same document.
|
|
|
|
Responsibility split
|
|
--------------------
|
|
|
|
=========================== ==================================================
|
|
Module Responsibility
|
|
=========================== ==================================================
|
|
``l10n_fr_tax_receipt`` The receipt itself: donor, year, invoice
|
|
selection, stored amount, sequential number,
|
|
draft/confirmed/sent/cancelled workflow,
|
|
cancellation.
|
|
``pdf_form_report`` PDF layout and rendering.
|
|
``tax_receipt_in_portal`` Exposes a confirmed receipt to its donor: list,
|
|
detail page with chatter, PDF download.
|
|
=========================== ==================================================
|
|
|
|
This module computes no amount, assigns no number, selects no donation and
|
|
lays out no PDF.
|
|
|
|
Installation
|
|
============
|
|
|
|
Use the normal Odoo module installation procedure to install
|
|
``tax_receipt_in_portal``. Dependencies (``portal``,
|
|
``l10n_fr_tax_receipt`` and transitively ``pdf_form_report``) are
|
|
installed automatically.
|
|
|
|
Configuration
|
|
=============
|
|
|
|
One system parameter is required
|
|
(:menuselection:`Settings --> Technical --> System Parameters`):
|
|
|
|
==================================== =========================================
|
|
Key Value
|
|
==================================== =========================================
|
|
``tax_receipt_in_portal.report_uuid`` The ``report_uuid`` of the PDF Form
|
|
report that renders tax receipts.
|
|
==================================== =========================================
|
|
|
|
A UUID is used rather than a database id so the configuration survives a
|
|
restore on another database. The report must:
|
|
|
|
* be of type ``PDF Form`` (``report_type = pdf_form``);
|
|
* target the model ``l10n.fr.tax.receipt``.
|
|
|
|
Anything else raises an explicit error at download time instead of serving
|
|
a wrong document.
|
|
|
|
Bind the PDF fields to receipt field paths, for instance:
|
|
|
|
* ``name`` — receipt number;
|
|
* ``year`` — civil year;
|
|
* ``partner_id.name`` — donor;
|
|
* ``amount_total`` — amount;
|
|
* ``amount_total_words`` — amount in words;
|
|
* ``last_payment_date`` — date of the last payment.
|
|
|
|
Prefer field paths over Python expressions: a field path is validated when
|
|
saved, an expression is not.
|
|
|
|
Usage
|
|
=====
|
|
|
|
Donors see only their own receipts, and only once **confirmed**. Confirming
|
|
a receipt in the back office is therefore what publishes it.
|
|
|
|
Security
|
|
========
|
|
|
|
The model inherits ``portal.mixin``, so every receipt carries an
|
|
``access_token`` used for share links and by the portal chatter plumbing.
|
|
|
|
Two independent access layers, both required:
|
|
|
|
* a **record rule** restricting portal users to receipts whose
|
|
``partner_id`` is the connected user's commercial entity (or one of its
|
|
children) and whose state is ``confirmed`` or ``sent``. This is what
|
|
protects the detail and download routes for a logged-in donor.
|
|
* an **explicit domain** in the controller, which also applies to internal
|
|
users browsing ``/my`` — they are not subject to portal record rules.
|
|
|
|
A draft receipt is excluded by the record rule rather than by the
|
|
controller: a receipt without a legal number must not leak through a route
|
|
that someone forgets to filter.
|
|
|
|
A valid ``access_token`` grants access to a single receipt — and to its
|
|
chatter — without login, for instance when the receipt is shared by
|
|
email. Listing always requires a session.
|
|
|
|
The PDF rendering itself runs with elevated rights, after the access
|
|
check, so that report field paths may reach data the donor cannot read
|
|
directly.
|
|
|
|
Portal chatter
|
|
==============
|
|
|
|
The detail page embeds the native portal chatter
|
|
(``portal.message_thread``). A logged-in donor can read the thread and
|
|
post messages (the composer posts as the donor; messages reach the
|
|
back-office chatter of the receipt, so accountants see the
|
|
conversation). Internal notes stay invisible to the donor, as handled by
|
|
the core mail access rules.
|
|
|
|
For a public visitor using a share token, the thread is not readable:
|
|
the core chatter endpoint checks record access, which a public user does
|
|
not have. The section then renders empty; the token remains useful for
|
|
the PDF and the receipt information.
|
|
|
|
Known issues / Roadmap
|
|
======================
|
|
|
|
* **No archiving.** The PDF is rendered at each download and never stored.
|
|
A receipt whose rendering becomes impossible (report deleted or
|
|
reconfigured) can no longer be served, even though it was already sent.
|
|
* **No email notification** and **no portal account invitation**: a donor
|
|
without a portal account cannot reach the page. Both are deliberately
|
|
out of scope.
|
|
* **Coupling to a private API.** The blank-field guard calls
|
|
``_compute_pdf_field_value`` from ``pdf_form_report``, which is private.
|
|
``test_compute_api_contract`` fails loudly if that signature changes.
|
|
The clean fix belongs upstream: ``pdf_form_report`` should expose a
|
|
strict rendering mode that raises instead of logging a warning and
|
|
leaving a PDF field blank.
|
|
* Multi-company is handled through the receipt's own ``company_id``; no
|
|
additional portal-side company filter is applied.
|
|
|
|
Why a blank-field guard
|
|
=======================
|
|
|
|
``pdf_form_report`` deliberately swallows field resolution errors: a broken
|
|
field path or Python expression only emits a log warning and leaves the PDF
|
|
field blank. Serving a donor a tax receipt with a blank amount is worse
|
|
than serving none, so every mapped field value is resolved before rendering
|
|
and an empty one aborts with an explicit error.
|
|
|
|
Validation happens before rendering rather than by re-reading the produced
|
|
PDF, because a report drawing its values as an overlay
|
|
(``is_acroform = False``) exposes no readable form field afterwards.
|
|
|
|
Bug Tracker
|
|
===========
|
|
|
|
Bugs are tracked on `our issues website
|
|
<https://git.elabore.coop/elabore/account-tools/issues>`_. In case of
|
|
trouble, please check there if your issue has already been reported. If you
|
|
spotted it first, help us smashing it by providing a detailed and welcomed
|
|
feedback.
|
|
|
|
Credits
|
|
=======
|
|
|
|
Contributors
|
|
------------
|
|
|
|
* Stéphan Sainléger
|
|
|
|
Funders
|
|
-------
|
|
|
|
The development of this module has been financially supported by:
|
|
|
|
* Elabore (https://elabore.coop)
|
|
|
|
Maintainer
|
|
----------
|
|
|
|
This module is maintained by Elabore.
|