- Move the call-type select handler into onCallType() — OWL cannot compile a multi-statement inline t-on body (was a render-breaking crash on mount). - Replace color-mix() inside border shorthands with var(--sb-border) (Odoo-19 SCSS drops color-mix in a border shorthand). - Technician placeholder option value '' (not 'false') so the required-tech guard isn't bypassed. - Remove dead setTiming(); null-coalesce the refdata onWillStart load. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
209 lines
16 KiB
XML
209 lines
16 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
||
<templates xml:space="preserve">
|
||
|
||
<t t-name="fusion_claims.ServiceBookingWizard" owl="1">
|
||
<div class="o_service_booking">
|
||
<div class="wrap">
|
||
<div class="dialog">
|
||
<div class="topbar">
|
||
<div>
|
||
<h1>Book a Service</h1>
|
||
<div class="sub">Repair · delivery · pickup — captures the job and creates the priced repair order</div>
|
||
</div>
|
||
</div>
|
||
<div class="stepper">
|
||
<span class="step active">Scheduled</span>
|
||
<span class="step">En Route</span>
|
||
<span class="step">In Progress</span>
|
||
<span class="step">Completed</span>
|
||
<span class="step draft">● Draft repair SO will be created</span>
|
||
</div>
|
||
|
||
<div class="body">
|
||
<div class="grid">
|
||
<!-- CUSTOMER -->
|
||
<div class="card">
|
||
<h3><span class="dot"></span>Customer</h3>
|
||
<div class="row">
|
||
<div class="seg full">
|
||
<button t-att-class="{ on: state.custMode === 'existing' }"
|
||
t-on-click="() => this.setCust('existing')">Existing customer</button>
|
||
<button t-att-class="{ on: state.custMode === 'new' }"
|
||
t-on-click="() => this.setCust('new')">New client</button>
|
||
</div>
|
||
</div>
|
||
<div t-if="state.custMode === 'existing'">
|
||
<div class="row">
|
||
<label class="fl">Search by phone, name or SO</label>
|
||
<input class="f" t-model="state.soSearch" placeholder="e.g. (416) 555-0142 …"/>
|
||
<div class="hint">Inbound call? Type the phone number — we match the contact & their history.</div>
|
||
</div>
|
||
</div>
|
||
<div t-if="state.custMode === 'new'">
|
||
<div class="row two">
|
||
<div><label class="fl">Client name *</label><input class="f" t-model="state.customer.name" placeholder="Full name"/></div>
|
||
<div><label class="fl">Phone *</label><input class="f" t-model="state.customer.phone" placeholder="(416) 555-…"/></div>
|
||
</div>
|
||
<div class="row"><label class="fl">Email</label><input class="f" type="email" t-model="state.customer.email" placeholder="client@email.com"/></div>
|
||
<div class="row"><label class="fl">Address</label>
|
||
<div class="with-icon"><input class="f" t-model="state.customer.street" placeholder="Start typing an address…"/><span class="pin">📍</span></div>
|
||
</div>
|
||
<div class="row three">
|
||
<div><label class="fl">Unit</label><input class="f" t-model="state.customer.unit" placeholder="#"/></div>
|
||
<div><label class="fl">Buzz</label><input class="f" t-model="state.customer.buzz" placeholder="—"/></div>
|
||
<div><label class="fl">City</label><input class="f" t-model="state.customer.city" placeholder="City"/></div>
|
||
</div>
|
||
<div class="hint">Contact is created & linked on save — all from this page.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SERVICE & PRICING -->
|
||
<div class="card">
|
||
<h3><span class="dot"></span>Service & Pricing<span class="tag">$ REVENUE</span></h3>
|
||
<div class="row two">
|
||
<div>
|
||
<label class="fl">Device being serviced</label>
|
||
<select class="f" t-on-change="onDevice">
|
||
<option value="standard">Mobility Scooter</option>
|
||
<option value="standard">Powerchair</option>
|
||
<option value="standard">Wheelchair</option>
|
||
<option value="lift">Stairlift</option>
|
||
<option value="lift">Patient / Ceiling Lift</option>
|
||
<option value="standard">Lift Chair</option>
|
||
<option value="standard">Hospital Bed</option>
|
||
<option value="standard">Other</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="fl">Issue / symptom</label>
|
||
<input class="f" t-model="state.issue" placeholder="e.g. won't power on"/>
|
||
</div>
|
||
</div>
|
||
<div class="row" t-if="!state.inShop">
|
||
<label class="fl">Service call type</label>
|
||
<select class="f"
|
||
t-on-change="onCallType">
|
||
<t t-foreach="state.calloutRates" t-as="r" t-key="r.code">
|
||
<option t-att-value="r.code"
|
||
t-att-selected="state.category === r.category and state.timing === r.timing">
|
||
<t t-esc="r.name"/> — $<t t-esc="fmt(r.price)"/><t t-if="r.adds_per_km"> + $<t t-esc="fmt(state.perKm)"/>/km ×2-way</t>
|
||
</option>
|
||
</t>
|
||
</select>
|
||
<div class="hint">Auto-suggested from the device — change if needed.</div>
|
||
</div>
|
||
<div class="feeline" t-if="!state.inShop and callout">
|
||
<div class="lbl">Call-out fee<small><t t-esc="callout.name"/><t t-if="callout.adds_per_km"> · + travel</t><t t-else=""> · includes 30 min labour</t></small></div>
|
||
<div class="amt">$<t t-esc="fmt(callout.price)"/></div>
|
||
</div>
|
||
<div class="hint" t-if="state.inShop">In-shop job — no call-out fee; labour billed at $<t t-esc="fmt(state.labour.inshop)"/>/hr.</div>
|
||
</div>
|
||
|
||
<!-- SCHEDULE -->
|
||
<div class="card">
|
||
<h3><span class="dot"></span>Schedule</h3>
|
||
<div class="row two">
|
||
<div><label class="fl">Date</label><input class="f" type="date" t-model="state.date"/></div>
|
||
<div><label class="fl">Duration</label>
|
||
<select class="f" t-model.number="state.durationHr">
|
||
<option value="0.5">30 min</option>
|
||
<option value="1">1 hour</option>
|
||
<option value="1.5">1.5 hours</option>
|
||
<option value="2">2 hours</option>
|
||
<option value="3">3 hours</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<label class="fl">Start time</label>
|
||
<div class="timepick">
|
||
<select class="f" t-model.number="state.hour">
|
||
<option value="9">9</option>
|
||
<option value="10">10</option>
|
||
<option value="11">11</option>
|
||
<option value="12">12</option>
|
||
<option value="1">1</option>
|
||
<option value="2">2</option>
|
||
<option value="3">3</option>
|
||
<option value="4">4</option>
|
||
</select>
|
||
<select class="f" t-model.number="state.minute">
|
||
<option value="0">:00</option>
|
||
<option value="15">:15</option>
|
||
<option value="30">:30</option>
|
||
<option value="45">:45</option>
|
||
</select>
|
||
<div class="ampm">
|
||
<button t-att-class="{ on: state.ampm === 'AM' }" t-on-click="() => this.setAmpm('AM')">AM</button>
|
||
<button t-att-class="{ on: state.ampm === 'PM' }" t-on-click="() => this.setAmpm('PM')">PM</button>
|
||
</div>
|
||
</div>
|
||
<div class="endtime">Ends at <b><t t-esc="endLabel"/></b> · your local time</div>
|
||
</div>
|
||
<div class="row">
|
||
<label class="fl">Technician</label>
|
||
<select class="f" t-model.number="state.technicianId">
|
||
<option value="">— Choose —</option>
|
||
<t t-foreach="state.technicians" t-as="t" t-key="t.id">
|
||
<option t-att-value="t.id"><t t-esc="t.name"/></option>
|
||
</t>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- LOCATION -->
|
||
<div class="card">
|
||
<h3><span class="dot"></span>Location</h3>
|
||
<div class="opt" style="border:none; padding-top:0;">
|
||
<div class="lab">In-shop job<small>At the store — no call-out, labour @ $<t t-esc="fmt(state.labour.inshop)"/>/hr</small></div>
|
||
<div class="sw" t-att-class="{ on: state.inShop }" t-on-click="toggleInShop"></div>
|
||
</div>
|
||
<div t-if="!state.inShop">
|
||
<div class="row"><label class="fl">Job address</label>
|
||
<div class="with-icon"><input class="f" t-model="state.customer.street" placeholder="Auto-fills from customer…"/><span class="pin">📍</span></div>
|
||
</div>
|
||
<div class="row two">
|
||
<div><label class="fl">Unit / Suite</label><input class="f" t-model="state.customer.unit" placeholder="#"/></div>
|
||
<div><label class="fl">Buzz code</label><input class="f" t-model="state.customer.buzz" placeholder="—"/></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- JOB DETAILS -->
|
||
<div class="card span2">
|
||
<h3><span class="dot"></span>Job details</h3>
|
||
<div class="two">
|
||
<div class="row"><label class="fl">Work description</label><textarea class="f" t-model="state.description" placeholder="Symptom, what to check, history…"></textarea></div>
|
||
<div class="row"><label class="fl">Parts / materials to bring</label><textarea class="f" t-model="state.materials" placeholder="Batteries, controller, casters…"></textarea></div>
|
||
</div>
|
||
<div class="opt"><div class="lab">Under manufacturer warranty<small>Parts not billed when covered</small></div><div class="sw" t-att-class="{ on: state.warranty }" t-on-click="() => state.warranty = !state.warranty"></div></div>
|
||
<div class="opt"><div class="lab">POD required<small>Capture proof of delivery on completion</small></div><div class="sw" t-att-class="{ on: state.pod }" t-on-click="() => state.pod = !state.pod"></div></div>
|
||
<div class="opt"><div class="lab">Send client confirmation (email/SMS)<small>Booked · en-route · completed</small></div><div class="sw" t-att-class="{ on: state.emailConfirm }" t-on-click="() => state.emailConfirm = !state.emailConfirm"></div></div>
|
||
<div class="opt"><div class="lab">Request Google review after completion</div><div class="sw" t-att-class="{ on: state.googleReview }" t-on-click="() => state.googleReview = !state.googleReview"></div></div>
|
||
</div>
|
||
|
||
<!-- ESTIMATE -->
|
||
<div class="estimate">
|
||
<div class="breakdown">
|
||
<div class="bk"><div class="k">Call-out</div><div class="v"><t t-if="state.inShop">—</t><t t-else="">$<t t-esc="fmt(estimate.callout)"/></t></div></div>
|
||
<div class="bk"><div class="k">Est. labour</div><div class="v">$<t t-esc="fmt(estimate.labour)"/> · <t t-esc="estimate.billHr"/>h @ $<t t-esc="fmt(labourRate)"/></div></div>
|
||
<div class="bk" t-if="estimate.addsKm"><div class="k">Travel ($<t t-esc="fmt(state.perKm)"/>/km ×2)</div><div class="v">$<t t-esc="fmt(estimate.km)"/></div></div>
|
||
</div>
|
||
<div class="total"><div class="k">Estimated total</div><div class="v">$<t t-esc="fmt(estimate.total)"/></div>
|
||
<div class="note">+ parts as used · pre-tax · a draft SO is created</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="foot">
|
||
<span class="spacer">Local time · America/Toronto · <t t-esc="state.distanceKm"/> km away</span>
|
||
<button class="btn ghost" t-on-click="() => this.action.doAction({ type: 'ir.actions.act_window_close' })">Cancel</button>
|
||
<button class="btn primary" t-on-click="submit" t-att-disabled="state.saving">Book & Create SO</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</t>
|
||
|
||
</templates>
|