Replace the ``tax_receipt_in_portal.report_uuid`` system parameter by a per-company setting, in Settings > Invoicing > Tax Receipts (``res.company.tax_receipt_report_id``). Each receipt is rendered with the report of its issuing company, not the current one: in the portal, the current company is the website's. The selector only offers PDF Form reports on ``l10n.fr.tax.receipt``; type and model are still checked at rendering time since the report can be edited after selection. The download buttons of the list and detail pages are only shown when the PDF can actually be rendered: a valid report is configured for the issuing company and no mapped field resolves to an empty value. The checks are those of the rendering itself, exposed as a filter (``_get_pdf_available_receipts``) and run in sudo to avoid false negatives on field paths the donor cannot read. Otherwise a discreet "PDF available soon" mention replaces the button, and the download URL redirects to the receipt page. The detail page download link now also carries the share token, so a visitor reaching the page by token can download the PDF.
210 lines
7.9 KiB
ReStructuredText
210 lines
7.9 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
|
|
=============
|
|
|
|
Select the report rendering tax receipts in
|
|
:menuselection:`Settings --> Invoicing --> Tax Receipts --> Tax Receipt
|
|
Report`.
|
|
|
|
The setting is **per company**: each receipt is rendered with the report of
|
|
its *issuing* company (``company_id`` of the receipt), not the current one —
|
|
in the portal, the current company is the website's.
|
|
|
|
The selector only offers reports that:
|
|
|
|
* are of type ``PDF Form`` (``report_type = pdf_form``);
|
|
* target the model ``l10n.fr.tax.receipt``;
|
|
* belong to the company, or are shared (no company).
|
|
|
|
These conditions are checked again at rendering time, since a report can be
|
|
edited after being selected. Deleting the selected report empties the
|
|
setting.
|
|
|
|
Without a valid report, donors still see their receipts, but no download
|
|
button: a discreet "PDF available soon" mention replaces it.
|
|
|
|
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.
|
|
|
|
A download button is shown only when the PDF can actually be rendered: a
|
|
report is configured for the issuing company and no mapped field resolves
|
|
to an empty value. Otherwise the button is replaced by a "PDF available
|
|
soon" mention, and a direct call to the download URL redirects to the
|
|
receipt page.
|
|
|
|
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.
|
|
* **Availability is evaluated at each display.** Deciding whether to show
|
|
the download button resolves every mapped PDF field of each displayed
|
|
receipt — at most one page of the list. A report with costly Python
|
|
expressions slows the list down accordingly.
|
|
* **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.
|