The custom dashboard at fusion_plating_portal was rendering a 6-card view at /my/home, but a method-name mismatch left the parent portal.CustomerPortal.home() route active instead. Rename the override to home() so Python MRO does the override naturally, and add CLAUDE.md Critical Rule 16 documenting the gotcha so future controller-override work doesn't trip on it. Version bump: 19.0.2.2.0 -> 19.0.2.3.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
857 lines
30 KiB
Python
857 lines
30 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2026 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
# Part of the Fusion Plating product family.
|
|
|
|
import base64
|
|
import json
|
|
|
|
from odoo import _, http
|
|
from odoo.exceptions import AccessError, MissingError
|
|
from odoo.http import request
|
|
from odoo.addons.portal.controllers.portal import (
|
|
CustomerPortal,
|
|
pager as portal_pager,
|
|
)
|
|
|
|
|
|
class FpCustomerPortal(CustomerPortal):
|
|
"""Customer portal extension for Fusion Plating.
|
|
|
|
Adds a rich dashboard, Quote Requests with part lines, Jobs with
|
|
segmented progress, Purchase Orders, Invoices, Shipping, and
|
|
Certifications sections.
|
|
"""
|
|
|
|
# ==========================================================================
|
|
# Portal Home -- counters
|
|
# ==========================================================================
|
|
def _prepare_home_portal_values(self, counters):
|
|
values = super()._prepare_home_portal_values(counters)
|
|
partner = request.env.user.partner_id
|
|
commercial = partner.commercial_partner_id
|
|
|
|
if 'fp_quote_request_count' in counters:
|
|
values['fp_quote_request_count'] = request.env[
|
|
'fusion.plating.quote.request'
|
|
].search_count([('partner_id', 'child_of', commercial.id)])
|
|
|
|
if 'fp_portal_job_count' in counters:
|
|
values['fp_portal_job_count'] = request.env[
|
|
'fusion.plating.portal.job'
|
|
].search_count([('partner_id', 'child_of', commercial.id)])
|
|
|
|
if 'fp_purchase_order_count' in counters:
|
|
values['fp_purchase_order_count'] = request.env[
|
|
'sale.order'
|
|
].sudo().search_count([
|
|
('partner_id', 'child_of', commercial.id),
|
|
('state', '=', 'sale'),
|
|
])
|
|
|
|
if 'fp_invoice_count' in counters:
|
|
values['fp_invoice_count'] = request.env[
|
|
'account.move'
|
|
].sudo().search_count([
|
|
('partner_id', 'child_of', commercial.id),
|
|
('move_type', '=', 'out_invoice'),
|
|
('state', '=', 'posted'),
|
|
])
|
|
|
|
if 'fp_delivery_count' in counters:
|
|
values['fp_delivery_count'] = request.env[
|
|
'stock.picking'
|
|
].sudo().search_count([
|
|
('partner_id', 'child_of', commercial.id),
|
|
('picking_type_code', '=', 'outgoing'),
|
|
('state', '=', 'done'),
|
|
])
|
|
|
|
if 'fp_certification_count' in counters:
|
|
values['fp_certification_count'] = request.env[
|
|
'fusion.plating.portal.job'
|
|
].search_count([
|
|
('partner_id', 'child_of', commercial.id),
|
|
('coc_attachment_id', '!=', False),
|
|
])
|
|
|
|
return values
|
|
|
|
# ==========================================================================
|
|
# Helpers
|
|
# ==========================================================================
|
|
def _fp_quote_request_get_page_view_values(self, quote_request, access_token, **kwargs):
|
|
values = {
|
|
'page_name': 'fp_quote_request',
|
|
'quote_request': quote_request,
|
|
}
|
|
return self._get_page_view_values(
|
|
quote_request,
|
|
access_token,
|
|
values,
|
|
'my_fp_quote_requests_history',
|
|
False,
|
|
**kwargs,
|
|
)
|
|
|
|
def _fp_portal_job_get_page_view_values(self, job, access_token, **kwargs):
|
|
values = {
|
|
'page_name': 'fp_portal_job',
|
|
'job': job,
|
|
}
|
|
return self._get_page_view_values(
|
|
job,
|
|
access_token,
|
|
values,
|
|
'my_fp_jobs_history',
|
|
False,
|
|
**kwargs,
|
|
)
|
|
|
|
def _fp_get_partner_domain(self):
|
|
"""Return base domain filtering to commercial partner tree."""
|
|
partner = request.env.user.partner_id
|
|
return partner.commercial_partner_id
|
|
|
|
# ==========================================================================
|
|
# DASHBOARD
|
|
# ==========================================================================
|
|
@http.route(
|
|
['/my/home'],
|
|
type='http',
|
|
auth='user',
|
|
website=True,
|
|
)
|
|
def home(self, **kw):
|
|
partner = request.env.user.partner_id
|
|
commercial = partner.commercial_partner_id
|
|
|
|
# Recent quotes (5)
|
|
Quote = request.env['fusion.plating.quote.request']
|
|
quote_domain = [('partner_id', 'child_of', commercial.id)]
|
|
recent_quotes = Quote.search(
|
|
quote_domain, order='create_date desc', limit=5
|
|
)
|
|
quote_count = Quote.search_count(quote_domain)
|
|
|
|
# Recent POs (5) -- sale.order with state=sale
|
|
SO = request.env['sale.order'].sudo()
|
|
po_domain = [
|
|
('partner_id', 'child_of', commercial.id),
|
|
('state', '=', 'sale'),
|
|
]
|
|
recent_pos = SO.search(po_domain, order='date_order desc', limit=5)
|
|
po_count = SO.search_count(po_domain)
|
|
|
|
# Recent jobs (5)
|
|
Job = request.env['fusion.plating.portal.job']
|
|
job_domain = [('partner_id', 'child_of', commercial.id)]
|
|
recent_jobs = Job.search(
|
|
job_domain, order='received_date desc, id desc', limit=5
|
|
)
|
|
job_count = Job.search_count(job_domain)
|
|
|
|
# Certifications (jobs with CoC)
|
|
cert_domain = [
|
|
('partner_id', 'child_of', commercial.id),
|
|
('coc_attachment_id', '!=', False),
|
|
]
|
|
recent_certs = Job.search(
|
|
cert_domain, order='actual_ship_date desc, id desc', limit=5
|
|
)
|
|
cert_count = Job.search_count(cert_domain)
|
|
|
|
# Deliveries (stock.picking outgoing done)
|
|
Picking = request.env['stock.picking'].sudo()
|
|
delivery_domain = [
|
|
('partner_id', 'child_of', commercial.id),
|
|
('picking_type_code', '=', 'outgoing'),
|
|
('state', '=', 'done'),
|
|
]
|
|
recent_deliveries = Picking.search(
|
|
delivery_domain, order='date_done desc', limit=5
|
|
)
|
|
delivery_count = Picking.search_count(delivery_domain)
|
|
|
|
# Invoices
|
|
Invoice = request.env['account.move'].sudo()
|
|
invoice_domain = [
|
|
('partner_id', 'child_of', commercial.id),
|
|
('move_type', '=', 'out_invoice'),
|
|
('state', '=', 'posted'),
|
|
]
|
|
recent_invoices = Invoice.search(
|
|
invoice_domain, order='invoice_date desc', limit=5
|
|
)
|
|
invoice_count = Invoice.search_count(invoice_domain)
|
|
|
|
values = {
|
|
'page_name': 'fp_dashboard',
|
|
'partner': partner,
|
|
# Quotes
|
|
'recent_quotes': recent_quotes,
|
|
'quote_count': quote_count,
|
|
# Purchase Orders
|
|
'recent_pos': recent_pos,
|
|
'po_count': po_count,
|
|
# Jobs / Parts Portal
|
|
'recent_jobs': recent_jobs,
|
|
'job_count': job_count,
|
|
# Certifications
|
|
'recent_certs': recent_certs,
|
|
'cert_count': cert_count,
|
|
# Deliveries
|
|
'recent_deliveries': recent_deliveries,
|
|
'delivery_count': delivery_count,
|
|
# Invoices
|
|
'recent_invoices': recent_invoices,
|
|
'invoice_count': invoice_count,
|
|
}
|
|
return request.render(
|
|
'fusion_plating_portal.fp_portal_home_dashboard',
|
|
values,
|
|
)
|
|
|
|
# ==========================================================================
|
|
# QUOTE REQUESTS -- list with tabs
|
|
# ==========================================================================
|
|
@http.route(
|
|
['/my/quote_requests', '/my/quote_requests/page/<int:page>'],
|
|
type='http',
|
|
auth='user',
|
|
website=True,
|
|
)
|
|
def portal_my_quote_requests(self, page=1, sortby=None, filterby=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'},
|
|
}
|
|
if not sortby:
|
|
sortby = 'date'
|
|
order = searchbar_sortings[sortby]['order']
|
|
|
|
# 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']),
|
|
]},
|
|
}
|
|
if not filterby or filterby not in searchbar_filters:
|
|
filterby = 'all'
|
|
domain += searchbar_filters[filterby]['domain']
|
|
|
|
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'],
|
|
)
|
|
request.session['my_fp_quote_requests_history'] = quote_requests.ids[:100]
|
|
|
|
values = {
|
|
'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,
|
|
}
|
|
return request.render(
|
|
'fusion_plating_portal.portal_my_quote_requests',
|
|
values,
|
|
)
|
|
|
|
# ==========================================================================
|
|
# QUOTE REQUESTS -- detail
|
|
# ==========================================================================
|
|
@http.route(
|
|
['/my/quote_requests/<int:request_id>'],
|
|
type='http',
|
|
auth='user',
|
|
website=True,
|
|
)
|
|
def portal_my_quote_request(self, request_id, access_token=None, **kw):
|
|
try:
|
|
quote_sudo = self._document_check_access(
|
|
'fusion.plating.quote.request',
|
|
request_id,
|
|
access_token,
|
|
)
|
|
except (AccessError, MissingError):
|
|
return request.redirect('/my')
|
|
|
|
values = self._fp_quote_request_get_page_view_values(
|
|
quote_sudo, access_token, **kw
|
|
)
|
|
return request.render(
|
|
'fusion_plating_portal.portal_my_quote_request',
|
|
values,
|
|
)
|
|
|
|
# ==========================================================================
|
|
# QUOTE REQUESTS -- new form (enhanced)
|
|
# ==========================================================================
|
|
@http.route(
|
|
['/my/quote_requests/new'],
|
|
type='http',
|
|
auth='user',
|
|
website=True,
|
|
)
|
|
def portal_new_quote_request(self, **kw):
|
|
partner = request.env.user.partner_id
|
|
commercial = partner.commercial_partner_id
|
|
|
|
process_types = request.env['fusion.plating.process.type'].sudo().search(
|
|
[('active', '=', True)]
|
|
)
|
|
|
|
# Shipping addresses: child contacts of type 'delivery' or 'other'
|
|
addresses = request.env['res.partner'].sudo().search([
|
|
('parent_id', '=', commercial.id),
|
|
('type', 'in', ['delivery', 'other', 'contact']),
|
|
])
|
|
|
|
# Products available for this customer (all saleable products)
|
|
products = request.env['product.product'].sudo().search([
|
|
('sale_ok', '=', True),
|
|
('active', '=', True),
|
|
], limit=200, order='default_code, name')
|
|
|
|
values = {
|
|
'page_name': 'fp_quote_request_new',
|
|
'process_types': process_types,
|
|
'partner': partner,
|
|
'commercial': commercial,
|
|
'addresses': addresses,
|
|
'products': products,
|
|
'error': kw.get('error'),
|
|
'form_data': kw,
|
|
}
|
|
return request.render(
|
|
'fusion_plating_portal.portal_new_quote_request_form',
|
|
values,
|
|
)
|
|
|
|
# ==========================================================================
|
|
# QUOTE REQUESTS -- submit (enhanced for multi-part)
|
|
# ==========================================================================
|
|
@http.route(
|
|
['/my/quote_requests/submit'],
|
|
type='http',
|
|
auth='user',
|
|
website=True,
|
|
methods=['POST'],
|
|
csrf=True,
|
|
)
|
|
def portal_submit_quote_request(self, **post):
|
|
partner = request.env.user.partner_id
|
|
|
|
# Basic validation: need at least one part description or a general description
|
|
parts_json = post.get('parts_data', '[]')
|
|
try:
|
|
parts_data = json.loads(parts_json)
|
|
except (json.JSONDecodeError, TypeError):
|
|
parts_data = []
|
|
|
|
if not parts_data and not post.get('part_description'):
|
|
return request.redirect(
|
|
'/my/quote_requests/new?error=missing_description'
|
|
)
|
|
|
|
process_type_ids = []
|
|
raw_processes = request.httprequest.form.getlist('process_type_ids')
|
|
for pt in raw_processes:
|
|
try:
|
|
process_type_ids.append(int(pt))
|
|
except (TypeError, ValueError):
|
|
continue
|
|
|
|
try:
|
|
quantity = int(post.get('quantity') or 0)
|
|
except (TypeError, ValueError):
|
|
quantity = 0
|
|
|
|
vals = {
|
|
'partner_id': partner.id,
|
|
'contact_name': post.get('contact_name') or partner.name,
|
|
'contact_email': post.get('contact_email') or partner.email,
|
|
'contact_phone': post.get('contact_phone') or partner.phone,
|
|
'company_name': post.get('company_name')
|
|
or (partner.parent_id.name if partner.parent_id else partner.name),
|
|
'part_description': post.get('part_description'),
|
|
'quantity': quantity,
|
|
'special_instructions': post.get('special_instructions'),
|
|
'state': 'new',
|
|
'billing_same_as_shipping': bool(post.get('billing_same_as_shipping')),
|
|
}
|
|
|
|
# Addresses
|
|
if post.get('shipping_address_id'):
|
|
try:
|
|
vals['shipping_address_id'] = int(post['shipping_address_id'])
|
|
except (TypeError, ValueError):
|
|
pass
|
|
if post.get('billing_address_id') and not post.get('billing_same_as_shipping'):
|
|
try:
|
|
vals['billing_address_id'] = int(post['billing_address_id'])
|
|
except (TypeError, ValueError):
|
|
pass
|
|
|
|
if post.get('target_delivery'):
|
|
vals['target_delivery'] = post.get('target_delivery')
|
|
if process_type_ids:
|
|
vals['process_type_ids'] = [(6, 0, process_type_ids)]
|
|
|
|
quote = request.env['fusion.plating.quote.request'].sudo().create(vals)
|
|
|
|
# Create part lines from JSON data
|
|
if parts_data:
|
|
Line = request.env['fusion.plating.quote.request.line'].sudo()
|
|
for idx, part in enumerate(parts_data):
|
|
line_vals = {
|
|
'request_id': quote.id,
|
|
'sequence': (idx + 1) * 10,
|
|
'part_number': part.get('part_number', ''),
|
|
'quantity': int(part.get('quantity', 1) or 1),
|
|
'count': int(part.get('count', 1) or 1),
|
|
'description': part.get('description', ''),
|
|
'spec_text': part.get('spec_text', ''),
|
|
}
|
|
if part.get('product_id'):
|
|
try:
|
|
line_vals['product_id'] = int(part['product_id'])
|
|
except (TypeError, ValueError):
|
|
pass
|
|
Line.create(line_vals)
|
|
|
|
# Handle file uploads (general attachments on the quote)
|
|
files = request.httprequest.files.getlist('drawing_attachments')
|
|
attachment_ids = []
|
|
for f in files:
|
|
if not f or not f.filename:
|
|
continue
|
|
try:
|
|
content = f.read()
|
|
if not content:
|
|
continue
|
|
attachment = request.env['ir.attachment'].sudo().create({
|
|
'name': f.filename,
|
|
'datas': base64.b64encode(content),
|
|
'res_model': 'fusion.plating.quote.request',
|
|
'res_id': quote.id,
|
|
'type': 'binary',
|
|
})
|
|
attachment_ids.append(attachment.id)
|
|
except Exception:
|
|
continue
|
|
if attachment_ids:
|
|
quote.sudo().write({
|
|
'drawing_attachment_ids': [(6, 0, attachment_ids)],
|
|
})
|
|
|
|
# Handle per-line file uploads (line_file_0, line_file_1, ...)
|
|
for idx in range(len(parts_data)):
|
|
line_files = request.httprequest.files.getlist('line_file_%d' % idx)
|
|
line_att_ids = []
|
|
for f in line_files:
|
|
if not f or not f.filename:
|
|
continue
|
|
try:
|
|
content = f.read()
|
|
if not content:
|
|
continue
|
|
attachment = request.env['ir.attachment'].sudo().create({
|
|
'name': f.filename,
|
|
'datas': base64.b64encode(content),
|
|
'res_model': 'fusion.plating.quote.request.line',
|
|
'res_id': quote.line_ids[idx].id if idx < len(quote.line_ids) else 0,
|
|
'type': 'binary',
|
|
})
|
|
line_att_ids.append(attachment.id)
|
|
except Exception:
|
|
continue
|
|
if line_att_ids and idx < len(quote.line_ids):
|
|
quote.line_ids[idx].sudo().write({
|
|
'attachment_ids': [(6, 0, line_att_ids)],
|
|
})
|
|
|
|
return request.redirect('/my/quote_requests/%s' % quote.id)
|
|
|
|
# ==========================================================================
|
|
# JOBS -- list
|
|
# ==========================================================================
|
|
@http.route(
|
|
['/my/jobs', '/my/jobs/page/<int:page>'],
|
|
type='http',
|
|
auth='user',
|
|
website=True,
|
|
)
|
|
def portal_my_jobs(self, page=1, sortby=None, **kw):
|
|
partner = request.env.user.partner_id
|
|
commercial = partner.commercial_partner_id
|
|
Job = request.env['fusion.plating.portal.job']
|
|
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'},
|
|
}
|
|
if not sortby:
|
|
sortby = 'date'
|
|
order = searchbar_sortings[sortby]['order']
|
|
|
|
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'],
|
|
)
|
|
request.session['my_fp_jobs_history'] = jobs.ids[:100]
|
|
|
|
values = {
|
|
'jobs': jobs,
|
|
'page_name': 'fp_jobs',
|
|
'pager': pager,
|
|
'default_url': '/my/jobs',
|
|
'searchbar_sortings': searchbar_sortings,
|
|
'sortby': sortby,
|
|
}
|
|
return request.render(
|
|
'fusion_plating_portal.portal_my_jobs',
|
|
values,
|
|
)
|
|
|
|
# ==========================================================================
|
|
# JOBS -- detail
|
|
# ==========================================================================
|
|
@http.route(
|
|
['/my/jobs/<int:job_id>'],
|
|
type='http',
|
|
auth='user',
|
|
website=True,
|
|
)
|
|
def portal_my_job(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')
|
|
|
|
values = self._fp_portal_job_get_page_view_values(
|
|
job_sudo, access_token, **kw
|
|
)
|
|
values['progress_percent'] = job_sudo._progress_percent()
|
|
return request.render(
|
|
'fusion_plating_portal.portal_my_job',
|
|
values,
|
|
)
|
|
|
|
# ==========================================================================
|
|
# JOBS -- download CoC
|
|
# ==========================================================================
|
|
@http.route(
|
|
['/my/jobs/<int:job_id>/coc'],
|
|
type='http',
|
|
auth='user',
|
|
website=True,
|
|
)
|
|
def portal_download_coc(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')
|
|
|
|
if not job_sudo.coc_attachment_id:
|
|
return request.redirect('/my/jobs/%s' % job_id)
|
|
|
|
attachment = job_sudo.coc_attachment_id.sudo()
|
|
return request.env['ir.binary']._get_stream_from(
|
|
attachment, 'raw'
|
|
).get_response(as_attachment=True)
|
|
|
|
# ==========================================================================
|
|
# PURCHASE ORDERS -- list
|
|
# ==========================================================================
|
|
@http.route(
|
|
['/my/purchase_orders', '/my/purchase_orders/page/<int:page>'],
|
|
type='http',
|
|
auth='user',
|
|
website=True,
|
|
)
|
|
def portal_my_purchase_orders(self, page=1, sortby=None, **kw):
|
|
partner = request.env.user.partner_id
|
|
commercial = partner.commercial_partner_id
|
|
SO = request.env['sale.order'].sudo()
|
|
domain = [
|
|
('partner_id', 'child_of', commercial.id),
|
|
('state', '=', 'sale'),
|
|
]
|
|
|
|
searchbar_sortings = {
|
|
'date': {'label': _('Newest'), 'order': 'date_order desc'},
|
|
'name': {'label': _('Reference'), 'order': 'name desc'},
|
|
'amount': {'label': _('Amount'), 'order': 'amount_total desc'},
|
|
}
|
|
if not sortby:
|
|
sortby = 'date'
|
|
order = searchbar_sortings[sortby]['order']
|
|
|
|
total = SO.search_count(domain)
|
|
pager = portal_pager(
|
|
url='/my/purchase_orders',
|
|
url_args={'sortby': sortby},
|
|
total=total,
|
|
page=page,
|
|
step=self._items_per_page,
|
|
)
|
|
orders = SO.search(
|
|
domain,
|
|
order=order,
|
|
limit=self._items_per_page,
|
|
offset=pager['offset'],
|
|
)
|
|
|
|
values = {
|
|
'orders': orders,
|
|
'page_name': 'fp_purchase_orders',
|
|
'pager': pager,
|
|
'default_url': '/my/purchase_orders',
|
|
'searchbar_sortings': searchbar_sortings,
|
|
'sortby': sortby,
|
|
}
|
|
return request.render(
|
|
'fusion_plating_portal.portal_my_purchase_orders',
|
|
values,
|
|
)
|
|
|
|
# ==========================================================================
|
|
# INVOICES -- list
|
|
# ==========================================================================
|
|
@http.route(
|
|
['/my/fp_invoices', '/my/fp_invoices/page/<int:page>'],
|
|
type='http',
|
|
auth='user',
|
|
website=True,
|
|
)
|
|
def portal_my_fp_invoices(self, page=1, sortby=None, **kw):
|
|
partner = request.env.user.partner_id
|
|
commercial = partner.commercial_partner_id
|
|
Invoice = request.env['account.move'].sudo()
|
|
domain = [
|
|
('partner_id', 'child_of', commercial.id),
|
|
('move_type', '=', 'out_invoice'),
|
|
('state', '=', 'posted'),
|
|
]
|
|
|
|
searchbar_sortings = {
|
|
'date': {'label': _('Newest'), 'order': 'invoice_date desc'},
|
|
'name': {'label': _('Reference'), 'order': 'name desc'},
|
|
'amount': {'label': _('Amount'), 'order': 'amount_total desc'},
|
|
'due': {'label': _('Due Date'), 'order': 'invoice_date_due asc'},
|
|
}
|
|
if not sortby:
|
|
sortby = 'date'
|
|
order = searchbar_sortings[sortby]['order']
|
|
|
|
total = Invoice.search_count(domain)
|
|
pager = portal_pager(
|
|
url='/my/fp_invoices',
|
|
url_args={'sortby': sortby},
|
|
total=total,
|
|
page=page,
|
|
step=self._items_per_page,
|
|
)
|
|
invoices = Invoice.search(
|
|
domain,
|
|
order=order,
|
|
limit=self._items_per_page,
|
|
offset=pager['offset'],
|
|
)
|
|
|
|
values = {
|
|
'invoices': invoices,
|
|
'page_name': 'fp_invoices',
|
|
'pager': pager,
|
|
'default_url': '/my/fp_invoices',
|
|
'searchbar_sortings': searchbar_sortings,
|
|
'sortby': sortby,
|
|
}
|
|
return request.render(
|
|
'fusion_plating_portal.portal_my_fp_invoices',
|
|
values,
|
|
)
|
|
|
|
# ==========================================================================
|
|
# SHIPPING / DELIVERIES -- list
|
|
# ==========================================================================
|
|
@http.route(
|
|
['/my/deliveries', '/my/deliveries/page/<int:page>'],
|
|
type='http',
|
|
auth='user',
|
|
website=True,
|
|
)
|
|
def portal_my_deliveries(self, page=1, sortby=None, **kw):
|
|
partner = request.env.user.partner_id
|
|
commercial = partner.commercial_partner_id
|
|
Picking = request.env['stock.picking'].sudo()
|
|
domain = [
|
|
('partner_id', 'child_of', commercial.id),
|
|
('picking_type_code', '=', 'outgoing'),
|
|
('state', '=', 'done'),
|
|
]
|
|
|
|
searchbar_sortings = {
|
|
'date': {'label': _('Newest'), 'order': 'date_done desc'},
|
|
'name': {'label': _('Reference'), 'order': 'name desc'},
|
|
}
|
|
if not sortby:
|
|
sortby = 'date'
|
|
order = searchbar_sortings[sortby]['order']
|
|
|
|
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'],
|
|
)
|
|
|
|
values = {
|
|
'deliveries': deliveries,
|
|
'page_name': 'fp_deliveries',
|
|
'pager': pager,
|
|
'default_url': '/my/deliveries',
|
|
'searchbar_sortings': searchbar_sortings,
|
|
'sortby': sortby,
|
|
}
|
|
return request.render(
|
|
'fusion_plating_portal.portal_my_deliveries',
|
|
values,
|
|
)
|
|
|
|
# ==========================================================================
|
|
# CERTIFICATIONS -- list
|
|
# ==========================================================================
|
|
@http.route(
|
|
['/my/certifications', '/my/certifications/page/<int:page>'],
|
|
type='http',
|
|
auth='user',
|
|
website=True,
|
|
)
|
|
def portal_my_certifications(self, page=1, sortby=None, **kw):
|
|
partner = request.env.user.partner_id
|
|
commercial = partner.commercial_partner_id
|
|
Job = request.env['fusion.plating.portal.job']
|
|
domain = [
|
|
('partner_id', 'child_of', commercial.id),
|
|
('coc_attachment_id', '!=', False),
|
|
]
|
|
|
|
searchbar_sortings = {
|
|
'date': {'label': _('Newest'), 'order': 'actual_ship_date desc, id desc'},
|
|
'name': {'label': _('Job Reference'), 'order': 'name desc'},
|
|
}
|
|
if not sortby:
|
|
sortby = 'date'
|
|
order = searchbar_sortings[sortby]['order']
|
|
|
|
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'],
|
|
)
|
|
|
|
values = {
|
|
'cert_jobs': cert_jobs,
|
|
'page_name': 'fp_certifications',
|
|
'pager': pager,
|
|
'default_url': '/my/certifications',
|
|
'searchbar_sortings': searchbar_sortings,
|
|
'sortby': sortby,
|
|
}
|
|
return request.render(
|
|
'fusion_plating_portal.portal_my_certifications',
|
|
values,
|
|
)
|
|
|
|
# ==========================================================================
|
|
# JSONRPC -- product search for RFQ form
|
|
# ==========================================================================
|
|
@http.route(
|
|
['/my/quote_requests/search_products'],
|
|
type='jsonrpc',
|
|
auth='user',
|
|
website=True,
|
|
)
|
|
def portal_search_products(self, query='', **kw):
|
|
"""Search products for the RFQ part number dropdown."""
|
|
products = request.env['product.product'].sudo().search([
|
|
('sale_ok', '=', True),
|
|
('active', '=', True),
|
|
'|', '|',
|
|
('name', 'ilike', query),
|
|
('default_code', 'ilike', query),
|
|
('barcode', 'ilike', query),
|
|
], limit=20, order='default_code, name')
|
|
return [{
|
|
'id': p.id,
|
|
'name': p.name,
|
|
'default_code': p.default_code or '',
|
|
'display': '[%s] %s' % (p.default_code, p.name) if p.default_code else p.name,
|
|
} for p in products]
|