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:
@@ -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
|
||||
# ==========================================================================
|
||||
|
||||
Reference in New Issue
Block a user