feat(portal): pulse animation, repeat-order button, 5-panel dashboard

1. Pulse animation on the active step indicator:
   - New @keyframes fp-pulse-teal / fp-pulse-amber in stepper.scss
   - Applied to .o_fp_step_active / _warn and .o_fp_timeline_active
     .o_fp_timeline_dot so dashboard stepper + detail-page timeline
     breathe in sync. 1.8s ease-in-out, ring grows 4px -> 9px and
     fades 20% -> 6% opacity. Two color variants so QC (warn) keeps
     its amber meaning.
   - prefers-reduced-motion: reduce kills the animation for users
     who opted out.

2. Repeat Order button on /my/jobs/<id> detail page:
   - New POST /my/jobs/<id>/repeat route that creates a draft
     fusion.plating.quote.request seeded with the user's contact +
     the job's quantity, posts a chatter link back to the original
     job, redirects to the new RFQ for review/submit.
   - Button placed in the detail footer next to 'Back to all jobs',
     CSRF-protected via the form's csrf_token hidden field.

3. Dashboard expanded from 3 secondary panels to 5 (Recent Quote
   Requests + Recent Purchase Orders added) so every previously-
   designed customer page is reachable from /my/home.
   - Auto-fit grid: 3+2 / 2+2+1 / single column depending on width.
   - Every panel header gets a 'View all ->' link to its list page
     (Quote Requests / POs / Certs / Deliveries / Invoices).
   - Empty-state for Quote Requests gets an inline 'Get a quote ->'
     CTA so first-time customers know where to start.

Version bump: 19.0.3.4.0 -> 19.0.3.5.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-17 03:56:53 -04:00
parent ba6f39375a
commit 49013c64fb
7 changed files with 151 additions and 7 deletions

View File

@@ -891,6 +891,50 @@ class FpCustomerPortal(CustomerPortal):
],
)
# ==========================================================================
# JOBS -- repeat order (clone into a new draft quote_request)
# ==========================================================================
# Customer-initiated "order again". Creates a draft fusion.plating.quote.
# request pre-filled with the user's contact info + job's quantity, then
# redirects to the new RFQ so the customer can adjust + submit. EN
# Plating then prices it via the normal quote workflow.
#
# POST-only so a stray browser prefetch can't accidentally spawn RFQs.
@http.route(
['/my/jobs/<int:job_id>/repeat'],
type='http',
auth='user',
methods=['POST'],
website=True,
csrf=True,
)
def portal_repeat_order(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')
user = request.env.user
Quote = request.env['fusion.plating.quote.request'].sudo()
new_quote = Quote.create({
'partner_id': user.partner_id.id,
'contact_name': user.name,
'contact_email': user.email or '',
'contact_phone': user.partner_id.phone or '',
'quantity': job_sudo.quantity or 1,
'state': 'new',
# name auto-generated by sequence
})
# Cross-link via chatter so EN Plating's estimator sees the origin.
new_quote.message_post(
body=_('Repeat order requested from portal job %s') % job_sudo.name,
)
return request.redirect('/my/quote_requests/%s' % new_quote.id)
# ==========================================================================
# PURCHASE ORDERS -- list
# ==========================================================================