feat(fusion_claims): OWL service-booking wizard + dark/light SCSS
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
102
fusion_claims/static/src/js/service_booking/service_booking.js
Normal file
102
fusion_claims/static/src/js/service_booking/service_booking.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/** @odoo-module **/
|
||||
import { Component, useState, onWillStart } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
export class ServiceBookingWizard extends Component {
|
||||
static template = "fusion_claims.ServiceBookingWizard";
|
||||
static props = ["*"];
|
||||
|
||||
setup() {
|
||||
this.action = useService("action");
|
||||
this.notification = useService("notification");
|
||||
this.state = useState({
|
||||
custMode: "existing",
|
||||
customer: { name: "", phone: "", email: "", street: "", unit: "", buzz: "", city: "" },
|
||||
partnerId: false, soSearch: "",
|
||||
device: "standard", category: "standard", timing: "normal", inShop: false, issue: "",
|
||||
date: "", hour: 9, minute: 0, ampm: "AM", durationHr: 1.0, technicianId: false,
|
||||
warranty: false, pod: false, emailConfirm: true, googleReview: true,
|
||||
description: "", materials: "",
|
||||
technicians: [], calloutRates: [], perKm: 0.70,
|
||||
labour: { onsite: 85, inshop: 75, lift: 110 }, distanceKm: 13, saving: false,
|
||||
});
|
||||
onWillStart(async () => {
|
||||
const r = await rpc("/fusion_claims/service_booking/refdata", {});
|
||||
Object.assign(this.state, {
|
||||
technicians: r.technicians, calloutRates: r.callout_rates,
|
||||
perKm: r.per_km, labour: r.labour,
|
||||
});
|
||||
});
|
||||
}
|
||||
get callout() {
|
||||
if (this.state.inShop) return null;
|
||||
return this.state.calloutRates.find(
|
||||
r => r.category === this.state.category && r.timing === this.state.timing) || null;
|
||||
}
|
||||
get labourRate() {
|
||||
if (this.state.inShop) return this.state.labour.inshop;
|
||||
return this.state.category === "lift" ? this.state.labour.lift : this.state.labour.onsite;
|
||||
}
|
||||
get estimate() {
|
||||
const c = this.callout;
|
||||
const callout = c ? c.price : 0;
|
||||
const incl = (c && !c.adds_per_km) ? 0.5 : 0;
|
||||
const billHr = Math.max(0, this.state.durationHr - incl);
|
||||
const labour = billHr * this.labourRate;
|
||||
const km = (c && c.adds_per_km) ? this.state.distanceKm * 2 * this.state.perKm : 0;
|
||||
return { callout, labour, billHr, km, total: callout + labour + km, addsKm: !!(c && c.adds_per_km) };
|
||||
}
|
||||
get endLabel() {
|
||||
let h = (this.state.hour % 12) + (this.state.ampm === "PM" ? 12 : 0);
|
||||
let m = h * 60 + this.state.minute + this.state.durationHr * 60;
|
||||
let eh = Math.floor(m / 60) % 24, em = m % 60, ap = eh >= 12 ? "PM" : "AM";
|
||||
return `${eh % 12 || 12}:${String(em).padStart(2, "0")} ${ap}`;
|
||||
}
|
||||
fmt(n) { return (n || 0).toFixed(2); }
|
||||
onDevice(ev) {
|
||||
this.state.device = ev.target.value;
|
||||
this.state.category = ev.target.value === "lift" ? "lift" : "standard";
|
||||
}
|
||||
setCust(m) { this.state.custMode = m; }
|
||||
setTiming(t) { this.state.timing = t; }
|
||||
setAmpm(v) { this.state.ampm = v; }
|
||||
toggleInShop() { this.state.inShop = !this.state.inShop; }
|
||||
_timeStartFloat() { return (this.state.hour % 12) + (this.state.ampm === "PM" ? 12 : 0) + this.state.minute / 60; }
|
||||
|
||||
async submit() {
|
||||
if (this.state.saving) return;
|
||||
const s = this.state;
|
||||
if (s.custMode === "new" && (!s.customer.name || !s.customer.phone)) {
|
||||
this.notification.add("Client name and phone are required.", { type: "danger" });
|
||||
return;
|
||||
}
|
||||
if (!s.technicianId) {
|
||||
this.notification.add("Please choose a technician.", { type: "danger" });
|
||||
return;
|
||||
}
|
||||
s.saving = true;
|
||||
const payload = {
|
||||
cust_mode: s.custMode, customer: s.customer, partner_id: s.partnerId, so_search: s.soSearch,
|
||||
category: s.category, timing: s.timing, in_shop: s.inShop, device: s.device, issue: s.issue,
|
||||
date: s.date, time_start: this._timeStartFloat(), duration_hr: s.durationHr,
|
||||
technician_id: s.technicianId, warranty: s.warranty, pod: s.pod,
|
||||
email_confirm: s.emailConfirm, google_review: s.googleReview,
|
||||
description: s.description, materials: s.materials,
|
||||
};
|
||||
try {
|
||||
const res = await rpc("/fusion_claims/service_booking/submit", { payload });
|
||||
if (res.error) { this.notification.add(res.error, { type: "danger" }); s.saving = false; return; }
|
||||
this.notification.add("Service booked — draft repair SO created.", { type: "success" });
|
||||
this.action.doAction({
|
||||
type: "ir.actions.act_window", res_model: "fusion.technician.task",
|
||||
res_id: res.task_id, views: [[false, "form"]], target: "current",
|
||||
});
|
||||
} catch (e) {
|
||||
this.notification.add("Booking failed: " + (e.message || e), { type: "danger" });
|
||||
s.saving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
registry.category("actions").add("fusion_claims.service_booking", ServiceBookingWizard);
|
||||
Reference in New Issue
Block a user