The wizard's dynamic fields were non-functional: the "Existing customer" box had
no search (no endpoint, no handler — the typed string was only sent on submit),
and address autocomplete never attached (google_address_autocomplete.js patches
FormController, which a client action is not).
Live client search:
- new jsonrpc endpoint /fusion_claims/service_booking/search_customers — searches
res.partner (name/phone/email) and resolves a typed SO number to its partner.
- JS: debounced (250ms) onCustSearch -> .sb-cust-results dropdown; pickCustomer()
sets state.partnerId + fills the contact, which action_book_from_wizard already
consumes for cust_mode='existing'.
- FIELD-SAFE domain: res.partner has NO `mobile` field in Odoo 19 — referencing it
raises ValueError and the swallowed exception made the search silently return
nothing. Build the OR domain only over fields in Partner._fields. Smoke-tested on
prod data ('25450'->1, '1 905-'->8).
Address autocomplete (wizard-local):
- component loads Google Places (key = ICP fusion_claims.google_maps_api_key, which
IS configured on westin), attaches via useRef('root')+onMounted/onPatched to every
input.sb-addr-input, writes street/city/lat/lng into reactive state. Fully guarded
(per-input _sbAc, _addrStarted/_addrNoKey gate, .catch on both hooks) so a missing
key degrades to manual entry and can never break render.
Verified: pyflakes clean, JS node --check, SCSS compiles, XML well-formed, dropdown
UI rendered against Bootstrap+compiled CSS. Documented in fusion_claims/CLAUDE.md §48.
Bump fusion_claims 19.0.9.6.0 -> 19.0.9.7.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
77 lines
3.4 KiB
Python
77 lines
3.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
from odoo import http
|
|
from odoo.http import request
|
|
|
|
|
|
class ServiceBookingController(http.Controller):
|
|
|
|
@http.route('/fusion_claims/service_booking/refdata', type='jsonrpc', auth='user')
|
|
def refdata(self, **kw):
|
|
env = request.env
|
|
Users = env['res.users']
|
|
techs = Users.search([('x_fc_is_field_staff', '=', True)]) \
|
|
if 'x_fc_is_field_staff' in Users._fields else Users.search([])
|
|
Rate = env['fusion.service.rate']
|
|
rates = Rate.search([('rate_kind', '=', 'callout'), ('active', '=', True)])
|
|
per_km = Rate.get_rate('per_km')
|
|
|
|
def labour(code):
|
|
r = Rate.get_rate(code)
|
|
return r.price if r else 0.0
|
|
|
|
return {
|
|
'technicians': [{'id': t.id, 'name': t.name} for t in techs],
|
|
'callout_rates': [{
|
|
'code': r.code, 'category': r.category, 'timing': r.timing,
|
|
'name': r.name, 'price': r.price, 'adds_per_km': r.adds_per_km,
|
|
} for r in rates],
|
|
'per_km': per_km.price if per_km else 0.70,
|
|
'labour': {'onsite': labour('labour_onsite'), 'inshop': labour('labour_inshop'),
|
|
'lift': labour('labour_lift')},
|
|
}
|
|
|
|
@http.route('/fusion_claims/service_booking/search_customers', type='jsonrpc', auth='user')
|
|
def search_customers(self, query=None, **kw):
|
|
"""Live customer lookup for the booking wizard's 'Existing customer' box.
|
|
Matches res.partner by name / phone / mobile / email, and also resolves a
|
|
typed sale-order reference to its customer. Returns up to 8 light dicts."""
|
|
q = (query or '').strip()
|
|
if len(q) < 2:
|
|
return {'results': []}
|
|
env = request.env
|
|
Partner = env['res.partner'].sudo()
|
|
# Build the OR domain only over fields that actually exist on this DB —
|
|
# res.partner.mobile is NOT present in Odoo 19, so referencing it raises
|
|
# ValueError and the whole lookup silently returns nothing.
|
|
has_mobile = 'mobile' in Partner._fields
|
|
search_fields = [f for f in ('name', 'phone', 'email', 'mobile')
|
|
if f in Partner._fields]
|
|
leaves = [(f, 'ilike', q) for f in search_fields]
|
|
domain = leaves[:1]
|
|
for leaf in leaves[1:]:
|
|
domain = ['|'] + domain + [leaf]
|
|
partners = Partner.search(domain, limit=8, order='write_date desc')
|
|
# also resolve an SO number -> its partner (the hint promises "name or SO")
|
|
if len(q) >= 3 and len(partners) < 8 and 'sale.order' in env:
|
|
sos = env['sale.order'].sudo().search([('name', 'ilike', q)], limit=5)
|
|
partners = (partners | sos.mapped('partner_id'))[:8]
|
|
results = []
|
|
for p in partners:
|
|
phone = p.phone or (p.mobile if has_mobile else '') or ''
|
|
results.append({
|
|
'id': p.id,
|
|
'name': p.name or '',
|
|
'phone': phone,
|
|
'email': p.email or '',
|
|
'street': p.street or '',
|
|
'city': p.city or '',
|
|
})
|
|
return {'results': results}
|
|
|
|
@http.route('/fusion_claims/service_booking/submit', type='jsonrpc', auth='user')
|
|
def submit(self, payload=None, **kw):
|
|
try:
|
|
return request.env['fusion.technician.task'].action_book_from_wizard(payload or {})
|
|
except Exception as e:
|
|
return {'error': str(e)}
|