feat(portal): real-time search + filter pills on 4 FP list pages
Replaces the tab nav / portal.portal_searchbar on the 4 FP list
pages with the new fp_portal_list_controls macro (filter pills +
search input + sort dropdown) and drops portal_pager in favour of
client-side filtering of up to 500 records:
- Quote Requests (/my/quote_requests):
filters: All / Active / Converted / Declined
sorts: Newest / Reference / Status
extra search fields: contact_name, contact_email, line.part_number,
line.description, line.product_id.default_code
- Work Orders (/my/jobs, cards layout):
filters: All / Active / Ready to Ship / Complete
sorts: Newest / Reference / Status
extra search fields per card: part_catalog.part_number, part_catalog.name,
sale_order.name, sale_order.client_order_ref,
job.notes
- Certifications (/my/certifications):
no filters (all rows are terminal CoC jobs)
sorts: Newest / Reference
extra search fields: part name, processes (already in card text)
- Packing Slips / Deliveries (/my/deliveries):
no filters (all rows are state=done)
sorts: Newest / Reference
adds a visible Origin column (sale order ref) so customers can
locate a slip by the SO it came from
Each route accepts ?filter_state=... and ?sortby=... query params,
returns up to 500 records, and passes result_total + clipped to the
template so the macro can render a "showing latest 500 of N" notice
when the cap is hit.
Hidden <td class="d-none"> cells inside each row carry extra terms
that aren't displayed but are matched by the JS textContent scan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -697,72 +697,70 @@ class FpCustomerPortal(CustomerPortal):
|
||||
)
|
||||
|
||||
# ==========================================================================
|
||||
# QUOTE REQUESTS -- list with tabs
|
||||
# QUOTE REQUESTS -- list with filter pills + real-time search
|
||||
# ==========================================================================
|
||||
@http.route(
|
||||
['/my/quote_requests', '/my/quote_requests/page/<int:page>'],
|
||||
['/my/quote_requests'],
|
||||
type='http',
|
||||
auth='user',
|
||||
website=True,
|
||||
)
|
||||
def portal_my_quote_requests(self, page=1, sortby=None, filterby=None, **kw):
|
||||
def portal_my_quote_requests(self, sortby=None, filter_state=None, **kw):
|
||||
partner = request.env.user.partner_id
|
||||
commercial = partner.commercial_partner_id
|
||||
Quote = request.env['fusion.plating.quote.request']
|
||||
domain = [('partner_id', 'child_of', commercial.id)]
|
||||
|
||||
searchbar_sortings = {
|
||||
'date': {'label': _('Newest'), 'order': 'create_date desc'},
|
||||
'name': {'label': _('Reference'), 'order': 'name desc'},
|
||||
'state': {'label': _('Status'), 'order': 'state'},
|
||||
_sort_map = {
|
||||
'date': 'create_date desc',
|
||||
'name': 'name asc',
|
||||
'state': 'state asc',
|
||||
}
|
||||
if not sortby:
|
||||
if sortby not in _sort_map:
|
||||
sortby = 'date'
|
||||
order = searchbar_sortings[sortby]['order']
|
||||
order = _sort_map[sortby]
|
||||
|
||||
# Tab filters
|
||||
searchbar_filters = {
|
||||
'all': {'label': _('All'), 'domain': []},
|
||||
'active': {'label': _('Active'), 'domain': [
|
||||
('state', 'in', ['new', 'under_review', 'quoted']),
|
||||
]},
|
||||
'converted': {'label': _('Converted'), 'domain': [
|
||||
('state', '=', 'accepted'),
|
||||
]},
|
||||
'declined': {'label': _('Declined'), 'domain': [
|
||||
('state', 'in', ['declined', 'expired']),
|
||||
]},
|
||||
# Filter pills
|
||||
_filter_map = {
|
||||
'all': [],
|
||||
'active': [('state', 'in', ['new', 'under_review', 'quoted'])],
|
||||
'converted': [('state', '=', 'accepted')],
|
||||
'declined': [('state', 'in', ['declined', 'expired'])],
|
||||
}
|
||||
if not filterby or filterby not in searchbar_filters:
|
||||
filterby = 'all'
|
||||
domain += searchbar_filters[filterby]['domain']
|
||||
if filter_state not in _filter_map:
|
||||
filter_state = 'all'
|
||||
domain += _filter_map[filter_state]
|
||||
|
||||
total = Quote.search_count(domain)
|
||||
pager = portal_pager(
|
||||
url='/my/quote_requests',
|
||||
url_args={'sortby': sortby, 'filterby': filterby},
|
||||
total=total,
|
||||
page=page,
|
||||
step=self._items_per_page,
|
||||
)
|
||||
quote_requests = Quote.search(
|
||||
domain,
|
||||
order=order,
|
||||
limit=self._items_per_page,
|
||||
offset=pager['offset'],
|
||||
)
|
||||
total_count = Quote.search_count(domain)
|
||||
cap = 500
|
||||
clipped = total_count > cap
|
||||
quote_requests = Quote.search(domain, order=order, limit=cap)
|
||||
request.session['my_fp_quote_requests_history'] = quote_requests.ids[:100]
|
||||
|
||||
values = self._prepare_portal_layout_values()
|
||||
values.update({
|
||||
'quote_requests': quote_requests,
|
||||
'page_name': 'fp_quote_requests',
|
||||
'pager': pager,
|
||||
'default_url': '/my/quote_requests',
|
||||
'searchbar_sortings': searchbar_sortings,
|
||||
'sortby': sortby,
|
||||
'searchbar_filters': searchbar_filters,
|
||||
'filterby': filterby,
|
||||
'filter_state': filter_state,
|
||||
'filters': [
|
||||
('all', 'All'),
|
||||
('active', 'Active'),
|
||||
('converted', 'Converted'),
|
||||
('declined', 'Declined'),
|
||||
],
|
||||
'sorts': [
|
||||
('date', 'Newest'),
|
||||
('name', 'Reference'),
|
||||
('state', 'Status'),
|
||||
],
|
||||
'result_total': total_count,
|
||||
'clipped': clipped,
|
||||
'search': '',
|
||||
'url': '/my/quote_requests',
|
||||
'extra_qs': '',
|
||||
'target': 'tbody.o_fp_qr_filterable',
|
||||
})
|
||||
return request.render(
|
||||
'fusion_plating_portal.portal_my_quote_requests',
|
||||
@@ -956,15 +954,15 @@ class FpCustomerPortal(CustomerPortal):
|
||||
return request.redirect('/my/quote_requests/%s' % quote.id)
|
||||
|
||||
# ==========================================================================
|
||||
# JOBS -- list
|
||||
# JOBS -- list with filter pills + real-time search (cards layout)
|
||||
# ==========================================================================
|
||||
@http.route(
|
||||
['/my/jobs', '/my/jobs/page/<int:page>'],
|
||||
['/my/jobs'],
|
||||
type='http',
|
||||
auth='user',
|
||||
website=True,
|
||||
)
|
||||
def portal_my_jobs(self, page=1, sortby=None, **kw):
|
||||
def portal_my_jobs(self, sortby=None, filter_state=None, **kw):
|
||||
partner = request.env.user.partner_id
|
||||
commercial = partner.commercial_partner_id
|
||||
# sudo() so the rendered cards can traverse job.x_fc_job_id -> fp.job
|
||||
@@ -974,39 +972,56 @@ class FpCustomerPortal(CustomerPortal):
|
||||
Job = request.env['fusion.plating.portal.job'].sudo()
|
||||
domain = [('partner_id', 'child_of', commercial.id)]
|
||||
|
||||
searchbar_sortings = {
|
||||
'date': {'label': _('Newest'), 'order': 'received_date desc, id desc'},
|
||||
'name': {'label': _('Reference'), 'order': 'name desc'},
|
||||
'state': {'label': _('Status'), 'order': 'state'},
|
||||
_sort_map = {
|
||||
'date': 'received_date desc, id desc',
|
||||
'name': 'name asc',
|
||||
'state': 'state asc',
|
||||
}
|
||||
if not sortby:
|
||||
if sortby not in _sort_map:
|
||||
sortby = 'date'
|
||||
order = searchbar_sortings[sortby]['order']
|
||||
order = _sort_map[sortby]
|
||||
|
||||
total = Job.search_count(domain)
|
||||
pager = portal_pager(
|
||||
url='/my/jobs',
|
||||
url_args={'sortby': sortby},
|
||||
total=total,
|
||||
page=page,
|
||||
step=self._items_per_page,
|
||||
)
|
||||
jobs = Job.search(
|
||||
domain,
|
||||
order=order,
|
||||
limit=self._items_per_page,
|
||||
offset=pager['offset'],
|
||||
)
|
||||
# Filter pills
|
||||
_filter_map = {
|
||||
'all': [],
|
||||
'active': [('state', 'in', ['received', 'in_progress', 'quality_check'])],
|
||||
'ready': [('state', '=', 'ready_to_ship')],
|
||||
'complete': [('state', 'in', ['shipped', 'complete'])],
|
||||
}
|
||||
if filter_state not in _filter_map:
|
||||
filter_state = 'all'
|
||||
domain += _filter_map[filter_state]
|
||||
|
||||
total_count = Job.search_count(domain)
|
||||
cap = 500
|
||||
clipped = total_count > cap
|
||||
jobs = Job.search(domain, order=order, limit=cap)
|
||||
request.session['my_fp_jobs_history'] = jobs.ids[:100]
|
||||
|
||||
values = self._prepare_portal_layout_values()
|
||||
values.update({
|
||||
'jobs': jobs,
|
||||
'page_name': 'fp_jobs',
|
||||
'pager': pager,
|
||||
'default_url': '/my/jobs',
|
||||
'searchbar_sortings': searchbar_sortings,
|
||||
'sortby': sortby,
|
||||
'filter_state': filter_state,
|
||||
'filters': [
|
||||
('all', 'All'),
|
||||
('active', 'Active'),
|
||||
('ready', 'Ready to Ship'),
|
||||
('complete', 'Complete'),
|
||||
],
|
||||
'sorts': [
|
||||
('date', 'Newest'),
|
||||
('name', 'Reference'),
|
||||
('state', 'Status'),
|
||||
],
|
||||
'result_total': total_count,
|
||||
'clipped': clipped,
|
||||
'search': '',
|
||||
'url': '/my/jobs',
|
||||
'extra_qs': '',
|
||||
'target': '#fp_jobs_list',
|
||||
})
|
||||
return request.render(
|
||||
'fusion_plating_portal.portal_my_jobs',
|
||||
@@ -1239,15 +1254,15 @@ class FpCustomerPortal(CustomerPortal):
|
||||
return request.redirect('/my/account_summary')
|
||||
|
||||
# ==========================================================================
|
||||
# SHIPPING / DELIVERIES -- list
|
||||
# SHIPPING / DELIVERIES -- list with search + sort
|
||||
# ==========================================================================
|
||||
@http.route(
|
||||
['/my/deliveries', '/my/deliveries/page/<int:page>'],
|
||||
['/my/deliveries'],
|
||||
type='http',
|
||||
auth='user',
|
||||
website=True,
|
||||
)
|
||||
def portal_my_deliveries(self, page=1, sortby=None, **kw):
|
||||
def portal_my_deliveries(self, sortby=None, **kw):
|
||||
partner = request.env.user.partner_id
|
||||
commercial = partner.commercial_partner_id
|
||||
Picking = request.env['stock.picking'].sudo()
|
||||
@@ -1257,37 +1272,37 @@ class FpCustomerPortal(CustomerPortal):
|
||||
('state', '=', 'done'),
|
||||
]
|
||||
|
||||
searchbar_sortings = {
|
||||
'date': {'label': _('Newest'), 'order': 'date_done desc'},
|
||||
'name': {'label': _('Reference'), 'order': 'name desc'},
|
||||
_sort_map = {
|
||||
'date': 'date_done desc',
|
||||
'name': 'name asc',
|
||||
}
|
||||
if not sortby:
|
||||
if sortby not in _sort_map:
|
||||
sortby = 'date'
|
||||
order = searchbar_sortings[sortby]['order']
|
||||
order = _sort_map[sortby]
|
||||
|
||||
total = Picking.search_count(domain)
|
||||
pager = portal_pager(
|
||||
url='/my/deliveries',
|
||||
url_args={'sortby': sortby},
|
||||
total=total,
|
||||
page=page,
|
||||
step=self._items_per_page,
|
||||
)
|
||||
deliveries = Picking.search(
|
||||
domain,
|
||||
order=order,
|
||||
limit=self._items_per_page,
|
||||
offset=pager['offset'],
|
||||
)
|
||||
total_count = Picking.search_count(domain)
|
||||
cap = 500
|
||||
clipped = total_count > cap
|
||||
deliveries = Picking.search(domain, order=order, limit=cap)
|
||||
|
||||
values = self._prepare_portal_layout_values()
|
||||
values.update({
|
||||
'deliveries': deliveries,
|
||||
'page_name': 'fp_deliveries',
|
||||
'pager': pager,
|
||||
'default_url': '/my/deliveries',
|
||||
'searchbar_sortings': searchbar_sortings,
|
||||
'sortby': sortby,
|
||||
'filter_state': 'all',
|
||||
'filters': False,
|
||||
'sorts': [
|
||||
('date', 'Newest'),
|
||||
('name', 'Reference'),
|
||||
],
|
||||
'result_total': total_count,
|
||||
'clipped': clipped,
|
||||
'search': '',
|
||||
'url': '/my/deliveries',
|
||||
'extra_qs': '',
|
||||
'target': 'tbody.o_fp_deliveries_filterable',
|
||||
})
|
||||
return request.render(
|
||||
'fusion_plating_portal.portal_my_deliveries',
|
||||
@@ -1295,15 +1310,15 @@ class FpCustomerPortal(CustomerPortal):
|
||||
)
|
||||
|
||||
# ==========================================================================
|
||||
# CERTIFICATIONS -- list
|
||||
# CERTIFICATIONS -- list with search + sort
|
||||
# ==========================================================================
|
||||
@http.route(
|
||||
['/my/certifications', '/my/certifications/page/<int:page>'],
|
||||
['/my/certifications'],
|
||||
type='http',
|
||||
auth='user',
|
||||
website=True,
|
||||
)
|
||||
def portal_my_certifications(self, page=1, sortby=None, **kw):
|
||||
def portal_my_certifications(self, sortby=None, **kw):
|
||||
partner = request.env.user.partner_id
|
||||
commercial = partner.commercial_partner_id
|
||||
# sudo() so the rendered cards can traverse job.x_fc_job_id -> fp.job
|
||||
@@ -1316,37 +1331,37 @@ class FpCustomerPortal(CustomerPortal):
|
||||
('coc_attachment_id', '!=', False),
|
||||
]
|
||||
|
||||
searchbar_sortings = {
|
||||
'date': {'label': _('Newest'), 'order': 'actual_ship_date desc, id desc'},
|
||||
'name': {'label': _('Job Reference'), 'order': 'name desc'},
|
||||
_sort_map = {
|
||||
'date': 'actual_ship_date desc, id desc',
|
||||
'name': 'name asc',
|
||||
}
|
||||
if not sortby:
|
||||
if sortby not in _sort_map:
|
||||
sortby = 'date'
|
||||
order = searchbar_sortings[sortby]['order']
|
||||
order = _sort_map[sortby]
|
||||
|
||||
total = Job.search_count(domain)
|
||||
pager = portal_pager(
|
||||
url='/my/certifications',
|
||||
url_args={'sortby': sortby},
|
||||
total=total,
|
||||
page=page,
|
||||
step=self._items_per_page,
|
||||
)
|
||||
cert_jobs = Job.search(
|
||||
domain,
|
||||
order=order,
|
||||
limit=self._items_per_page,
|
||||
offset=pager['offset'],
|
||||
)
|
||||
total_count = Job.search_count(domain)
|
||||
cap = 500
|
||||
clipped = total_count > cap
|
||||
cert_jobs = Job.search(domain, order=order, limit=cap)
|
||||
|
||||
values = self._prepare_portal_layout_values()
|
||||
values.update({
|
||||
'cert_jobs': cert_jobs,
|
||||
'page_name': 'fp_certifications',
|
||||
'pager': pager,
|
||||
'default_url': '/my/certifications',
|
||||
'searchbar_sortings': searchbar_sortings,
|
||||
'sortby': sortby,
|
||||
'filter_state': 'all',
|
||||
'filters': False,
|
||||
'sorts': [
|
||||
('date', 'Newest'),
|
||||
('name', 'Reference'),
|
||||
],
|
||||
'result_total': total_count,
|
||||
'clipped': clipped,
|
||||
'search': '',
|
||||
'url': '/my/certifications',
|
||||
'extra_qs': '',
|
||||
'target': 'tbody.o_fp_certs_filterable',
|
||||
})
|
||||
return request.render(
|
||||
'fusion_plating_portal.portal_my_certifications',
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<odoo>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- QUOTE REQUESTS — list with tabs (Active / Converted / Declined) -->
|
||||
<!-- QUOTE REQUESTS — list with filter pills + real-time search -->
|
||||
<!-- ================================================================== -->
|
||||
<template id="portal_my_quote_requests" name="My Quote Requests">
|
||||
<t t-call="portal.portal_layout">
|
||||
@@ -15,15 +15,19 @@
|
||||
<t t-set="title">Quote Requests</t>
|
||||
</t>
|
||||
|
||||
<!-- Tab navigation -->
|
||||
<ul class="nav nav-tabs mb-3">
|
||||
<li class="nav-item" t-foreach="searchbar_filters" t-as="f">
|
||||
<a t-attf-class="nav-link #{'active' if filterby == f else ''}"
|
||||
t-attf-href="/my/quote_requests?filterby=#{f}&sortby=#{sortby}">
|
||||
<t t-out="searchbar_filters[f]['label']"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Filter pills + search + sort strip -->
|
||||
<t t-call="fusion_plating_portal.fp_portal_list_controls">
|
||||
<t t-set="filters" t-value="filters"/>
|
||||
<t t-set="active_filter" t-value="filter_state"/>
|
||||
<t t-set="sorts" t-value="sorts"/>
|
||||
<t t-set="active_sort" t-value="sortby"/>
|
||||
<t t-set="search" t-value="search"/>
|
||||
<t t-set="url" t-value="url"/>
|
||||
<t t-set="extra_qs" t-value="extra_qs"/>
|
||||
<t t-set="target" t-value="target"/>
|
||||
<t t-set="result_total" t-value="result_total"/>
|
||||
<t t-set="clipped" t-value="clipped"/>
|
||||
</t>
|
||||
|
||||
<div class="d-flex justify-content-end mb-3">
|
||||
<a href="/my/quote_requests/new" class="o_fp_btn_primary">
|
||||
@@ -49,7 +53,7 @@
|
||||
<th class="text-end">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="o_fp_qr_filterable">
|
||||
<tr t-foreach="quote_requests" t-as="qr">
|
||||
<td>
|
||||
<a t-att-href="'/my/quote_requests/%s' % qr.id"
|
||||
@@ -73,6 +77,16 @@
|
||||
<t t-set="label" t-value="dict(qr._fields['state']._description_selection(qr.env)).get(qr.state)"/>
|
||||
</t>
|
||||
</td>
|
||||
<!-- Hidden search fields: contact, part numbers, descriptions -->
|
||||
<td class="d-none" aria-hidden="true">
|
||||
<span t-out="qr.contact_name or ''"/>
|
||||
<span t-out="qr.contact_email or ''"/>
|
||||
<t t-foreach="qr.line_ids" t-as="ln">
|
||||
<span t-out="ln.part_number or ''"/>
|
||||
<span t-out="ln.description or ''"/>
|
||||
<span t-if="ln.product_id" t-out="ln.product_id.default_code or ''"/>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</t>
|
||||
@@ -417,7 +431,7 @@
|
||||
</template>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- JOBS — list with segmented progress bars -->
|
||||
<!-- JOBS — list with filter pills + real-time search (cards layout) -->
|
||||
<!-- ================================================================== -->
|
||||
<template id="portal_my_jobs" name="My Work Orders">
|
||||
<t t-call="portal.portal_layout">
|
||||
@@ -425,6 +439,20 @@
|
||||
<t t-set="title">Work Orders</t>
|
||||
</t>
|
||||
|
||||
<!-- Filter pills + search + sort strip -->
|
||||
<t t-call="fusion_plating_portal.fp_portal_list_controls">
|
||||
<t t-set="filters" t-value="filters"/>
|
||||
<t t-set="active_filter" t-value="filter_state"/>
|
||||
<t t-set="sorts" t-value="sorts"/>
|
||||
<t t-set="active_sort" t-value="sortby"/>
|
||||
<t t-set="search" t-value="search"/>
|
||||
<t t-set="url" t-value="url"/>
|
||||
<t t-set="extra_qs" t-value="extra_qs"/>
|
||||
<t t-set="target" t-value="target"/>
|
||||
<t t-set="result_total" t-value="result_total"/>
|
||||
<t t-set="clipped" t-value="clipped"/>
|
||||
</t>
|
||||
|
||||
<t t-if="not jobs">
|
||||
<div class="o_fp_card text-center text-muted">
|
||||
<p class="mb-2">You have no plating jobs yet.</p>
|
||||
@@ -432,11 +460,32 @@
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="jobs">
|
||||
<div class="o_fp_dashboard">
|
||||
<!-- id="fp_jobs_list" is the data-fp-target for the search JS -->
|
||||
<div class="o_fp_dashboard" id="fp_jobs_list">
|
||||
<t t-foreach="jobs" t-as="job">
|
||||
<t t-call="fusion_plating_portal.fp_portal_job_card">
|
||||
<t t-set="job" t-value="job"/>
|
||||
</t>
|
||||
<!-- Wrapper div is the filterable row unit.
|
||||
Hidden span carries extra search terms that
|
||||
are not visible in the card UI. -->
|
||||
<div class="o_fp_job_card_wrap">
|
||||
<t t-call="fusion_plating_portal.fp_portal_job_card">
|
||||
<t t-set="job" t-value="job"/>
|
||||
</t>
|
||||
<!-- Extra hidden search terms for this card -->
|
||||
<t t-set="_backend_job" t-value="job.x_fc_job_id if 'x_fc_job_id' in job._fields else False"/>
|
||||
<t t-set="_so" t-value="_backend_job.sale_order_id if _backend_job and 'sale_order_id' in _backend_job._fields else False"/>
|
||||
<t t-set="_part" t-value="_backend_job.part_catalog_id if _backend_job and 'part_catalog_id' in _backend_job._fields else False"/>
|
||||
<span class="d-none" aria-hidden="true">
|
||||
<t t-if="_part">
|
||||
<t t-out="_part.part_number or ''"/>
|
||||
<t t-out="_part.name or ''"/>
|
||||
</t>
|
||||
<t t-if="_so">
|
||||
<t t-out="_so.name or ''"/>
|
||||
<t t-out="_so.client_order_ref or ''"/>
|
||||
</t>
|
||||
<t t-out="job.notes or ''"/>
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
@@ -574,7 +623,7 @@
|
||||
</template>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- DELIVERIES / PACKING SLIPS — list -->
|
||||
<!-- DELIVERIES / PACKING SLIPS — list with search + sort -->
|
||||
<!-- ================================================================== -->
|
||||
<template id="portal_my_deliveries" name="My Deliveries">
|
||||
<t t-call="portal.portal_layout">
|
||||
@@ -582,6 +631,20 @@
|
||||
<t t-set="title">Packing Slips / Deliveries</t>
|
||||
</t>
|
||||
|
||||
<!-- Search + sort strip (no filter pills — all rows are delivered) -->
|
||||
<t t-call="fusion_plating_portal.fp_portal_list_controls">
|
||||
<t t-set="filters" t-value="filters"/>
|
||||
<t t-set="active_filter" t-value="filter_state"/>
|
||||
<t t-set="sorts" t-value="sorts"/>
|
||||
<t t-set="active_sort" t-value="sortby"/>
|
||||
<t t-set="search" t-value="search"/>
|
||||
<t t-set="url" t-value="url"/>
|
||||
<t t-set="extra_qs" t-value="extra_qs"/>
|
||||
<t t-set="target" t-value="target"/>
|
||||
<t t-set="result_total" t-value="result_total"/>
|
||||
<t t-set="clipped" t-value="clipped"/>
|
||||
</t>
|
||||
|
||||
<t t-if="not deliveries">
|
||||
<div class="o_fp_portal_card card bg-body-tertiary border-0 p-4 text-center">
|
||||
<p class="text-muted mb-0">No deliveries found.</p>
|
||||
@@ -591,13 +654,17 @@
|
||||
<thead>
|
||||
<tr class="active">
|
||||
<th>Reference</th>
|
||||
<th>Origin</th>
|
||||
<th>Date</th>
|
||||
<th class="text-end">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="o_fp_deliveries_filterable">
|
||||
<tr t-foreach="deliveries" t-as="dlv">
|
||||
<td t-out="dlv.name"/>
|
||||
<td>
|
||||
<span t-out="dlv.origin or ''"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-if="dlv.date_done"
|
||||
t-field="dlv.date_done"
|
||||
@@ -608,6 +675,13 @@
|
||||
<span class="o_fp_badge_dot"/>Delivered
|
||||
</span>
|
||||
</td>
|
||||
<!-- Hidden: partner ref / customer PO on the origin SO -->
|
||||
<td class="d-none" aria-hidden="true">
|
||||
<t t-if="dlv.sale_id">
|
||||
<span t-out="dlv.sale_id.name or ''"/>
|
||||
<span t-out="dlv.sale_id.client_order_ref or ''"/>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</t>
|
||||
@@ -615,7 +689,7 @@
|
||||
</template>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- CERTIFICATIONS — list -->
|
||||
<!-- CERTIFICATIONS — list with search + sort -->
|
||||
<!-- ================================================================== -->
|
||||
<template id="portal_my_certifications" name="My Certifications">
|
||||
<t t-call="portal.portal_layout">
|
||||
@@ -623,6 +697,20 @@
|
||||
<t t-set="title">Certifications & Quality</t>
|
||||
</t>
|
||||
|
||||
<!-- Search + sort strip (no filter pills — all certs are terminal) -->
|
||||
<t t-call="fusion_plating_portal.fp_portal_list_controls">
|
||||
<t t-set="filters" t-value="filters"/>
|
||||
<t t-set="active_filter" t-value="filter_state"/>
|
||||
<t t-set="sorts" t-value="sorts"/>
|
||||
<t t-set="active_sort" t-value="sortby"/>
|
||||
<t t-set="search" t-value="search"/>
|
||||
<t t-set="url" t-value="url"/>
|
||||
<t t-set="extra_qs" t-value="extra_qs"/>
|
||||
<t t-set="target" t-value="target"/>
|
||||
<t t-set="result_total" t-value="result_total"/>
|
||||
<t t-set="clipped" t-value="clipped"/>
|
||||
</t>
|
||||
|
||||
<t t-if="not cert_jobs">
|
||||
<div class="o_fp_portal_card card bg-body-tertiary border-0 p-4 text-center">
|
||||
<p class="text-muted mb-0">No certificates available yet.</p>
|
||||
@@ -637,7 +725,7 @@
|
||||
<th class="text-end">Download</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="o_fp_certs_filterable">
|
||||
<tr t-foreach="cert_jobs" t-as="cj">
|
||||
<td>
|
||||
<a t-att-href="'/my/jobs/%s' % cj.id" t-out="cj.name"/>
|
||||
@@ -658,6 +746,19 @@
|
||||
<i class="fa fa-download"/> CoC
|
||||
</a>
|
||||
</td>
|
||||
<!-- Hidden: part name, customer PO from the backend job -->
|
||||
<td class="d-none" aria-hidden="true">
|
||||
<t t-set="_bj" t-value="cj.x_fc_job_id if 'x_fc_job_id' in cj._fields else False"/>
|
||||
<t t-set="_so" t-value="_bj.sale_order_id if _bj and 'sale_order_id' in _bj._fields else False"/>
|
||||
<t t-set="_part" t-value="_bj.part_catalog_id if _bj and 'part_catalog_id' in _bj._fields else False"/>
|
||||
<t t-if="_part">
|
||||
<span t-out="_part.part_number or ''"/>
|
||||
<span t-out="_part.name or ''"/>
|
||||
</t>
|
||||
<t t-if="_so">
|
||||
<span t-out="_so.client_order_ref or ''"/>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</t>
|
||||
|
||||
Reference in New Issue
Block a user