fix(portal): dedicated /my/jobs/<id>/so_confirmation route with sudo render
The FP sale report template (report_fp_sale_portrait) walks into fp.part.catalog records, which portal users don't have ACL on - they'd hit 'You are not allowed to access Fusion Plating - Part Catalog' when rendering. Standard /report/pdf/ route runs as the authed user, so the template traversal fails. Mirror the portal_download_coc pattern: gate on _document_check_access for the portal job (customer can only ever reach their own data), then render the report via ir.actions.report.sudo()._render_qweb_pdf so the QWeb template traversal bypasses ACL. Return the PDF as an attachment with a friendly filename. Updates _fp_group_documents to point the From-You SO Confirmation link at this new route instead of /report/pdf/ directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -197,17 +197,18 @@ class FpCustomerPortal(CustomerPortal):
|
||||
if backend_job and 'sale_order_id' in backend_job._fields:
|
||||
so = backend_job.sale_order_id
|
||||
if so:
|
||||
# IMPORTANT: use the FP custom sale report, NOT sale.report_saleorder.
|
||||
# Per CLAUDE.md, sale_pdf_quote_builder gates on report_name being
|
||||
# 'sale.report_saleorder' EXACTLY and produces broken output when
|
||||
# the standard template is hit on FP-customised sale orders.
|
||||
# report_fp_sale_portrait is the customer-facing template.
|
||||
# IMPORTANT: route through /my/jobs/<id>/so_confirmation, NOT
|
||||
# /report/pdf/ directly. The FP sale report template walks into
|
||||
# fp.part.catalog which portal users don't have ACL on; our
|
||||
# controller renders with sudo() to bypass that. Also avoids
|
||||
# the sale_pdf_quote_builder gate that broke the standard
|
||||
# sale.report_saleorder (CLAUDE.md MEMORY.md gotcha).
|
||||
groups[0]['docs'].append({
|
||||
'label': 'Sales Order Confirmation · %s' % so.name,
|
||||
'sub': 'EN Plating · %s' % (
|
||||
so.date_order and so.date_order.strftime('%b %d, %Y') or ''
|
||||
),
|
||||
'url': '/report/pdf/fusion_plating_reports.report_fp_sale_portrait/%s?download=true' % so.id,
|
||||
'url': '/my/jobs/%s/so_confirmation' % job.id,
|
||||
'icon_class': 'o_fp_doc_icon_input',
|
||||
'icon': '📄',
|
||||
})
|
||||
@@ -800,6 +801,57 @@ class FpCustomerPortal(CustomerPortal):
|
||||
attachment, 'raw'
|
||||
).get_response(as_attachment=True)
|
||||
|
||||
# ==========================================================================
|
||||
# JOBS -- download Sales Order Confirmation PDF
|
||||
# ==========================================================================
|
||||
# Renders the FP custom sale report with sudo so the QWeb template can
|
||||
# walk into restricted models (fp.part.catalog etc.) that portal users
|
||||
# don't have direct ACL on. We still gate on _document_check_access for
|
||||
# the portal job, so the customer only ever sees their own data.
|
||||
@http.route(
|
||||
['/my/jobs/<int:job_id>/so_confirmation'],
|
||||
type='http',
|
||||
auth='user',
|
||||
website=True,
|
||||
)
|
||||
def portal_download_so_confirmation(self, job_id, access_token=None, **kw):
|
||||
try:
|
||||
job_sudo = self._document_check_access(
|
||||
'fusion.plating.portal.job',
|
||||
job_id,
|
||||
access_token,
|
||||
)
|
||||
except (AccessError, MissingError):
|
||||
return request.redirect('/my')
|
||||
|
||||
# Resolve SO via the backend fp.job link.
|
||||
backend_job = (
|
||||
job_sudo.x_fc_job_id
|
||||
if 'x_fc_job_id' in job_sudo._fields
|
||||
else False
|
||||
)
|
||||
so = (
|
||||
backend_job.sale_order_id
|
||||
if backend_job and 'sale_order_id' in backend_job._fields
|
||||
else False
|
||||
)
|
||||
if not so:
|
||||
return request.redirect('/my/jobs/%s' % job_id)
|
||||
|
||||
pdf, _content_type = request.env['ir.actions.report'].sudo()._render_qweb_pdf(
|
||||
'fusion_plating_reports.report_fp_sale_portrait',
|
||||
res_ids=[so.id],
|
||||
)
|
||||
filename = 'Sales-Order-%s.pdf' % so.name.replace('/', '-')
|
||||
return request.make_response(
|
||||
pdf,
|
||||
headers=[
|
||||
('Content-Type', 'application/pdf'),
|
||||
('Content-Length', str(len(pdf))),
|
||||
('Content-Disposition', 'attachment; filename="%s"' % filename),
|
||||
],
|
||||
)
|
||||
|
||||
# ==========================================================================
|
||||
# PURCHASE ORDERS -- list
|
||||
# ==========================================================================
|
||||
|
||||
Reference in New Issue
Block a user