feat(fusion_repairs): Phase 1 MVP - backend intake wizard + core models

Scaffolds the fusion_repairs module that extends Odoo 19 repair.order with
a guided medical-equipment intake workflow.

Models
- fusion.repair.product.category (8 medical equipment categories seeded)
- fusion.repair.intake.template / .question / .answer (7 templates,
  32 questions seeded across hospital bed, stairlift, porch lift,
  wheelchair, walker/rollator, mattress)
- fusion.repair.intake.service (AbstractModel) - single entry point used
  by backend wizard, sales rep portal, and public client portal so all
  three surfaces produce identical outcomes
- repair.order extensions (x_fc_intake_*, x_fc_third_party_equipment,
  x_fc_photo_ids, x_fc_urgency, x_fc_estimated/actual_cost, AI summary)
- fusion.technician.task back-link (x_fc_repair_order_id)
- res.partner service preferences (preferred tech, time window, access notes)
- res.users repair extensions (skills, cost rate, on-call rotation fields)
- res.config.settings for variance thresholds, portal URL, rate limit

UI
- Backend intake wizard with multi-equipment loop, third-party flag, photos
- repair.order form: Intake tab, Photos, Pricing tab, AI tab, smart buttons
  (technician tasks, intake answers, original SO)
- Kanban + list view urgency badges
- Fusion Repairs app menu (New Service Call, Repair Orders, Config)

Activities & Email
- 4 follow-up activity types (CS callback, tech dispatch, visit follow-up,
  manager review) with urgency-tiered deadlines
- 2 mail templates (client confirmation + office notification) with the
  same dark/light-safe styling as fusion_claims ADP templates

Security
- New res.groups.privilege + 3 groups (User, Dispatcher, Manager)
- Reuses fusion_tasks.group_field_technician (do NOT recreate)
- Reuses fusion_authorizer_portal.group_sales_rep_portal
- Multi-company global rule + technician scoping rule on repair.order

Verified end-to-end on local westin-v19 dev DB via odoo-shell - creates
multiple repairs in one session, auto-creates dispatch task for urgent,
attaches 4 activity types correctly per urgency tier and third-party flag.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
gsinghpal
2026-05-20 21:35:52 -04:00
parent 79fbfec61f
commit 429084e0bf
32 changed files with 2823 additions and 0 deletions

View File

@@ -0,0 +1,378 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Seed intake templates - one per major medical equipment category.
Question banks based on the design spec Section "Configurable intake".
All templates noupdate=1 so customers can customise without losing data on upgrade.
-->
<odoo>
<data noupdate="1">
<!-- ============================================================== -->
<!-- DEFAULT (fallback) - applies to any category without override -->
<!-- ============================================================== -->
<record id="intake_template_default" model="fusion.repair.intake.template">
<field name="name">Default - General Intake</field>
<field name="code">default</field>
<field name="sequence">1</field>
<field name="is_default" eval="True"/>
<field name="description"><![CDATA[<p>Generic question set used when no equipment-specific template is configured.</p>]]></field>
</record>
<record id="q_default_caller_relationship" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_default"/>
<field name="sequence">10</field>
<field name="name">Who is calling? (self / family / caregiver / other)</field>
<field name="code">caller_relationship</field>
<field name="question_type">char</field>
<field name="required" eval="True"/>
</record>
<record id="q_default_address_match" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_default"/>
<field name="sequence">20</field>
<field name="name">Is the service address the same as the contact address on file?</field>
<field name="code">address_match</field>
<field name="question_type">boolean</field>
<field name="required" eval="True"/>
</record>
<record id="q_default_purchased_from_us" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_default"/>
<field name="sequence">30</field>
<field name="name">Was this equipment purchased from us?</field>
<field name="code">purchased_from_us</field>
<field name="question_type">boolean</field>
</record>
<record id="q_default_purchase_date" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_default"/>
<field name="sequence">40</field>
<field name="name">Approximate purchase date (if known)</field>
<field name="code">purchase_date</field>
<field name="question_type">date</field>
</record>
<record id="q_default_issue_summary" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_default"/>
<field name="sequence">50</field>
<field name="name">Describe the issue in your own words</field>
<field name="code">issue_summary</field>
<field name="question_type">text</field>
<field name="required" eval="True"/>
</record>
<record id="q_default_safety_concern" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_default"/>
<field name="sequence">60</field>
<field name="name">Does this issue affect anyone's safety right now?</field>
<field name="code">safety_concern</field>
<field name="question_type">boolean</field>
<field name="required" eval="True"/>
</record>
<record id="q_default_access_notes" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_default"/>
<field name="sequence">70</field>
<field name="name">Anything the technician should know about access? (stairs, parking, gate code, pet)</field>
<field name="code">access_notes</field>
<field name="question_type">text</field>
<field name="help_text">e.g. "dog in front yard, use side gate"</field>
</record>
<!-- ============================================================== -->
<!-- HOSPITAL BED -->
<!-- ============================================================== -->
<record id="intake_template_hospital_bed" model="fusion.repair.intake.template">
<field name="name">Hospital Bed - Intake</field>
<field name="code">hospital_bed</field>
<field name="sequence">10</field>
<field name="product_category_ids" eval="[(6, 0, [ref('category_hospital_bed')])]"/>
</record>
<record id="q_bed_powered" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_hospital_bed"/>
<field name="sequence">10</field>
<field name="name">Is the bed plugged in and does it power on?</field>
<field name="code">powered</field>
<field name="question_type">selection</field>
<field name="selection_options">Yes - powers on normally
No - no lights/sound at all
Powers on but won't move</field>
<field name="required" eval="True"/>
</record>
<record id="q_bed_remote_works" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_hospital_bed"/>
<field name="sequence">20</field>
<field name="name">Does the remote control respond when buttons are pressed?</field>
<field name="code">remote_works</field>
<field name="question_type">boolean</field>
</record>
<record id="q_bed_motor_side" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_hospital_bed"/>
<field name="sequence">30</field>
<field name="name">Which motor seems affected? (head, foot, height, all)</field>
<field name="code">motor_side</field>
<field name="question_type">char</field>
<field name="symptom_keywords">motor</field>
</record>
<record id="q_bed_rails" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_hospital_bed"/>
<field name="sequence">40</field>
<field name="name">Are the side rails functioning normally?</field>
<field name="code">rails_ok</field>
<field name="question_type">boolean</field>
<field name="symptom_keywords">rail,side</field>
</record>
<record id="q_bed_mattress" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_hospital_bed"/>
<field name="sequence">50</field>
<field name="name">Is the mattress included in this issue?</field>
<field name="code">mattress_involved</field>
<field name="question_type">boolean</field>
</record>
<!-- ============================================================== -->
<!-- STAIRLIFT -->
<!-- ============================================================== -->
<record id="intake_template_stairlift" model="fusion.repair.intake.template">
<field name="name">Stairlift - Intake</field>
<field name="code">stairlift</field>
<field name="sequence">20</field>
<field name="product_category_ids" eval="[(6, 0, [ref('category_stairlift')])]"/>
</record>
<record id="q_stairlift_powered" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_stairlift"/>
<field name="sequence">10</field>
<field name="name">Does the stairlift power on? (any lights, beeps)</field>
<field name="code">powered</field>
<field name="question_type">boolean</field>
<field name="required" eval="True"/>
</record>
<record id="q_stairlift_error_code" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_stairlift"/>
<field name="sequence">20</field>
<field name="name">Is there an error code displayed? (note the number/letter shown)</field>
<field name="code">error_code</field>
<field name="question_type">char</field>
<field name="symptom_keywords">error code</field>
</record>
<record id="q_stairlift_stuck_position" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_stairlift"/>
<field name="sequence">30</field>
<field name="name">Is anyone currently stuck on the stairlift?</field>
<field name="code">person_stuck</field>
<field name="question_type">boolean</field>
<field name="required" eval="True"/>
<field name="help_text">If yes, this is a safety issue - escalate immediately.</field>
</record>
<record id="q_stairlift_stops_midway" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_stairlift"/>
<field name="sequence">40</field>
<field name="name">Does it stop partway up or down the track?</field>
<field name="code">stops_midway</field>
<field name="question_type">boolean</field>
<field name="symptom_keywords">stops midway</field>
</record>
<record id="q_stairlift_burning" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_stairlift"/>
<field name="sequence">50</field>
<field name="name">Any burning smell, smoke, or unusual noise?</field>
<field name="code">burning_smell</field>
<field name="question_type">boolean</field>
<field name="required" eval="True"/>
<field name="symptom_keywords">burning smell,smoke</field>
</record>
<!-- ============================================================== -->
<!-- PORCH LIFT -->
<!-- ============================================================== -->
<record id="intake_template_porch_lift" model="fusion.repair.intake.template">
<field name="name">Porch Lift - Intake</field>
<field name="code">porch_lift</field>
<field name="sequence">30</field>
<field name="product_category_ids" eval="[(6, 0, [ref('category_porch_lift')])]"/>
</record>
<record id="q_porch_powered" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_porch_lift"/>
<field name="sequence">10</field>
<field name="name">Does the lift respond when you press the call/send button?</field>
<field name="code">powered</field>
<field name="question_type">boolean</field>
<field name="required" eval="True"/>
</record>
<record id="q_porch_gate_switches" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_porch_lift"/>
<field name="sequence">20</field>
<field name="name">Are all gate and door safety switches fully closed?</field>
<field name="code">gate_switches</field>
<field name="question_type">boolean</field>
</record>
<record id="q_porch_stuck" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_porch_lift"/>
<field name="sequence">30</field>
<field name="name">Is anyone currently stuck on the lift?</field>
<field name="code">person_stuck</field>
<field name="question_type">boolean</field>
<field name="required" eval="True"/>
</record>
<record id="q_porch_outdoor" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_porch_lift"/>
<field name="sequence">40</field>
<field name="name">Is the lift outdoors exposed to weather?</field>
<field name="code">outdoor</field>
<field name="question_type">boolean</field>
</record>
<!-- ============================================================== -->
<!-- WHEELCHAIR (MANUAL + POWER - shared template) -->
<!-- ============================================================== -->
<record id="intake_template_wheelchair" model="fusion.repair.intake.template">
<field name="name">Wheelchair - Intake</field>
<field name="code">wheelchair</field>
<field name="sequence">40</field>
<field name="product_category_ids" eval="[(6, 0, [ref('category_wheelchair_manual'), ref('category_wheelchair_power')])]"/>
</record>
<record id="q_wc_brakes" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_wheelchair"/>
<field name="sequence">10</field>
<field name="name">Do the brakes engage and hold the wheelchair?</field>
<field name="code">brakes_ok</field>
<field name="question_type">boolean</field>
<field name="required" eval="True"/>
<field name="symptom_keywords">brake</field>
</record>
<record id="q_wc_tires" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_wheelchair"/>
<field name="sequence">20</field>
<field name="name">Are both tires inflated and undamaged?</field>
<field name="code">tires_ok</field>
<field name="question_type">boolean</field>
</record>
<record id="q_wc_frame" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_wheelchair"/>
<field name="sequence">30</field>
<field name="name">Is there any visible damage to the frame or footrests?</field>
<field name="code">frame_damage</field>
<field name="question_type">boolean</field>
</record>
<record id="q_wc_battery" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_wheelchair"/>
<field name="sequence">40</field>
<field name="name">For power chairs: does the battery hold a charge?</field>
<field name="code">battery_holds_charge</field>
<field name="question_type">boolean</field>
<field name="symptom_keywords">battery,charge</field>
</record>
<record id="q_wc_joystick" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_wheelchair"/>
<field name="sequence">50</field>
<field name="name">For power chairs: any error code shown on the joystick display?</field>
<field name="code">joystick_error</field>
<field name="question_type">char</field>
</record>
<!-- ============================================================== -->
<!-- WALKER / ROLLATOR (shared) -->
<!-- ============================================================== -->
<record id="intake_template_walker_rollator" model="fusion.repair.intake.template">
<field name="name">Walker / Rollator - Intake</field>
<field name="code">walker_rollator</field>
<field name="sequence">50</field>
<field name="product_category_ids" eval="[(6, 0, [ref('category_walker'), ref('category_rollator')])]"/>
</record>
<record id="q_walker_wheels" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_walker_rollator"/>
<field name="sequence">10</field>
<field name="name">Do all wheels roll freely?</field>
<field name="code">wheels_roll</field>
<field name="question_type">boolean</field>
</record>
<record id="q_walker_brakes" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_walker_rollator"/>
<field name="sequence">20</field>
<field name="name">Do the brakes lock when engaged? (rollator only)</field>
<field name="code">brakes_lock</field>
<field name="question_type">boolean</field>
</record>
<record id="q_walker_frame" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_walker_rollator"/>
<field name="sequence">30</field>
<field name="name">Is the frame stable, with no wobble or loose parts?</field>
<field name="code">frame_stable</field>
<field name="question_type">boolean</field>
</record>
<!-- ============================================================== -->
<!-- MEDICAL MATTRESS -->
<!-- ============================================================== -->
<record id="intake_template_mattress" model="fusion.repair.intake.template">
<field name="name">Medical Mattress - Intake</field>
<field name="code">mattress</field>
<field name="sequence">60</field>
<field name="product_category_ids" eval="[(6, 0, [ref('category_mattress')])]"/>
</record>
<record id="q_mattress_pump_powered" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_mattress"/>
<field name="sequence">10</field>
<field name="name">Is the pump plugged in and showing any indicator lights?</field>
<field name="code">pump_powered</field>
<field name="question_type">boolean</field>
<field name="required" eval="True"/>
</record>
<record id="q_mattress_leak" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_mattress"/>
<field name="sequence">20</field>
<field name="name">Is the mattress leaking or losing air?</field>
<field name="code">leak</field>
<field name="question_type">boolean</field>
<field name="symptom_keywords">leak,deflate</field>
</record>
<record id="q_mattress_alarm" model="fusion.repair.intake.question">
<field name="template_id" ref="intake_template_mattress"/>
<field name="sequence">30</field>
<field name="name">Is the pump showing an error code or alarm?</field>
<field name="code">alarm</field>
<field name="question_type">char</field>
</record>
<!-- ============================================================== -->
<!-- Backfill category defaults -->
<!-- ============================================================== -->
<function model="fusion.repair.product.category" name="write">
<value model="fusion.repair.product.category" eval="[ref('category_hospital_bed')]"/>
<value eval="{'intake_template_id': ref('intake_template_hospital_bed')}"/>
</function>
<function model="fusion.repair.product.category" name="write">
<value model="fusion.repair.product.category" eval="[ref('category_stairlift')]"/>
<value eval="{'intake_template_id': ref('intake_template_stairlift')}"/>
</function>
<function model="fusion.repair.product.category" name="write">
<value model="fusion.repair.product.category" eval="[ref('category_porch_lift')]"/>
<value eval="{'intake_template_id': ref('intake_template_porch_lift')}"/>
</function>
<function model="fusion.repair.product.category" name="write">
<value model="fusion.repair.product.category" eval="[ref('category_wheelchair_manual')]"/>
<value eval="{'intake_template_id': ref('intake_template_wheelchair')}"/>
</function>
<function model="fusion.repair.product.category" name="write">
<value model="fusion.repair.product.category" eval="[ref('category_wheelchair_power')]"/>
<value eval="{'intake_template_id': ref('intake_template_wheelchair')}"/>
</function>
<function model="fusion.repair.product.category" name="write">
<value model="fusion.repair.product.category" eval="[ref('category_walker')]"/>
<value eval="{'intake_template_id': ref('intake_template_walker_rollator')}"/>
</function>
<function model="fusion.repair.product.category" name="write">
<value model="fusion.repair.product.category" eval="[ref('category_rollator')]"/>
<value eval="{'intake_template_id': ref('intake_template_walker_rollator')}"/>
</function>
<function model="fusion.repair.product.category" name="write">
<value model="fusion.repair.product.category" eval="[ref('category_mattress')]"/>
<value eval="{'intake_template_id': ref('intake_template_default')}"/>
</function>
<function model="fusion.repair.product.category" name="write">
<value model="fusion.repair.product.category" eval="[ref('category_other')]"/>
<value eval="{'intake_template_id': ref('intake_template_default')}"/>
</function>
</data>
</odoo>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Feature toggles -->
<record id="param_enable_email_notifications" model="ir.config_parameter">
<field name="key">fusion_repairs.enable_email_notifications</field>
<field name="value">True</field>
</record>
<!-- Outstanding balance warning threshold (CAD) - C5 -->
<record id="param_outstanding_balance_threshold" model="ir.config_parameter">
<field name="key">fusion_repairs.outstanding_balance_threshold</field>
<field name="value">100.00</field>
</record>
<!-- Duplicate-call detection window (days) - C1 -->
<record id="param_duplicate_call_window_days" model="ir.config_parameter">
<field name="key">fusion_repairs.duplicate_call_window_days</field>
<field name="value">14</field>
</record>
<!-- Pricing variance reconciliation - Phase 2 -->
<record id="param_variance_threshold_pct" model="ir.config_parameter">
<field name="key">fusion_repairs.variance_threshold_pct</field>
<field name="value">20</field>
</record>
<record id="param_variance_threshold_amount" model="ir.config_parameter">
<field name="key">fusion_repairs.variance_threshold_amount</field>
<field name="value">100.00</field>
</record>
<!-- Office follow-up cron toggles - Phase 3 -->
<record id="param_followup_maintenance_enabled" model="ir.config_parameter">
<field name="key">fusion_repairs.followup_maintenance_enabled</field>
<field name="value">True</field>
</record>
<record id="param_followup_repair_no_tech_enabled" model="ir.config_parameter">
<field name="key">fusion_repairs.followup_repair_no_tech_enabled</field>
<field name="value">True</field>
</record>
<record id="param_followup_overdue_visit_enabled" model="ir.config_parameter">
<field name="key">fusion_repairs.followup_overdue_visit_enabled</field>
<field name="value">True</field>
</record>
<record id="param_followup_unpaid_invoice_enabled" model="ir.config_parameter">
<field name="key">fusion_repairs.followup_unpaid_invoice_enabled</field>
<field name="value">True</field>
</record>
<!-- Public client portal - Phase 1+ -->
<record id="param_client_portal_url" model="ir.config_parameter">
<field name="key">fusion_repairs.client_portal_url</field>
<field name="value">/repair</field>
</record>
<record id="param_client_portal_rate_limit_per_hour" model="ir.config_parameter">
<field name="key">fusion_repairs.client_portal_rate_limit_per_hour</field>
<field name="value">10</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Intake session reference. -->
<!-- Groups multiple repair.order records created from the same call. -->
<record id="seq_repair_intake_session" model="ir.sequence">
<field name="name">Repair Intake Session</field>
<field name="code">fusion.repair.intake.session</field>
<field name="prefix">RIS</field>
<field name="padding">6</field>
<field name="number_next">1</field>
<field name="number_increment">1</field>
<field name="company_id" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- CS callback after intake - confirms call back if anything was missing -->
<record id="mail_activity_type_cs_callback" model="mail.activity.type">
<field name="name">Repair: CS Callback</field>
<field name="summary">Call client back if any intake info was missing</field>
<field name="delay_count">1</field>
<field name="delay_unit">days</field>
<field name="delay_from">previous_activity</field>
<field name="res_model">repair.order</field>
<field name="icon">fa-phone</field>
<field name="sequence">10</field>
</record>
<!-- Tech dispatch needed - office must assign a technician -->
<record id="mail_activity_type_tech_dispatch" model="mail.activity.type">
<field name="name">Repair: Assign Technician</field>
<field name="summary">Assign a technician to this repair</field>
<field name="delay_count">2</field>
<field name="delay_unit">days</field>
<field name="delay_from">previous_activity</field>
<field name="res_model">repair.order</field>
<field name="icon">fa-wrench</field>
<field name="sequence">20</field>
</record>
<!-- Visit follow-up - tech must report visit outcome -->
<record id="mail_activity_type_visit_followup" model="mail.activity.type">
<field name="name">Repair: Visit Follow-Up</field>
<field name="summary">Confirm visit outcome and complete repair</field>
<field name="delay_count">1</field>
<field name="delay_unit">days</field>
<field name="delay_from">previous_activity</field>
<field name="res_model">repair.order</field>
<field name="icon">fa-check-square-o</field>
<field name="sequence">30</field>
</record>
<!-- Manager review - third-party equipment -->
<record id="mail_activity_type_manager_review" model="mail.activity.type">
<field name="name">Repair: Manager Review</field>
<field name="summary">Third-party equipment - manager awareness</field>
<field name="delay_count">1</field>
<field name="delay_unit">days</field>
<field name="delay_from">previous_activity</field>
<field name="res_model">repair.order</field>
<field name="icon">fa-flag</field>
<field name="sequence">40</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Fusion Repairs Mail Templates.
Styling: 4px accent bar, 600px max-width, dark/light safe.
Mirrors fusion_claims/data/mail_template_data.xml ADP templates for consistency.
-->
<odoo>
<data noupdate="1">
<!-- ============================================================== -->
<!-- Repair Intake Received - Client Confirmation -->
<!-- ============================================================== -->
<record id="email_template_intake_received_client" model="mail.template">
<field name="name">Repair: Intake Received (Client)</field>
<field name="model_id" ref="repair.model_repair_order"/>
<field name="subject">{{ object.company_id.name }} - Service Call {{ object.name or 'received' }}</field>
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
<field name="partner_to">{{ object.partner_id.id }}</field>
<field name="body_html" type="html">
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;">
<div style="height:4px;background-color:#2B6CB0;"></div>
<div style="padding:32px 28px;">
<p style="color:#2B6CB0;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
<t t-out="object.company_id.name"/>
</p>
<h2 style="font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">We received your service request</h2>
<p style="opacity:0.65;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
Hello <t t-out="object.partner_id.name or 'there'"/>, thank you for letting us know about your equipment.
Your service call reference is <strong><t t-out="object.name"/></strong>.
</p>
<table style="width:100%;border-collapse:collapse;margin:0 0 24px 0;">
<tr><td colspan="2" style="padding:10px 14px;font-size:12px;font-weight:600;opacity:0.55;text-transform:uppercase;letter-spacing:0.5px;border-bottom:2px solid rgba(128,128,128,0.25);">Service Call Details</td></tr>
<tr><td style="padding:10px 14px;opacity:0.6;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);width:35%;">Reference</td><td style="padding:10px 14px;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);"><t t-out="object.name"/></td></tr>
<t t-if="object.product_id">
<tr><td style="padding:10px 14px;opacity:0.6;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);">Equipment</td><td style="padding:10px 14px;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);"><t t-out="object.product_id.display_name"/></td></tr>
</t>
<t t-if="object.schedule_date">
<tr><td style="padding:10px 14px;opacity:0.6;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);">Scheduled</td><td style="padding:10px 14px;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);"><t t-out="object.schedule_date" t-options="{'widget': 'datetime'}"/></td></tr>
</t>
<tr><td style="padding:10px 14px;opacity:0.6;font-size:14px;">Status</td><td style="padding:10px 14px;font-size:14px;"><t t-out="dict(object._fields['state'].selection).get(object.state)"/></td></tr>
</table>
<div style="border-left:3px solid #2B6CB0;padding:12px 16px;margin:0 0 24px 0;">
<p style="margin:0;font-size:14px;line-height:1.5;">
A team member will be in touch shortly to confirm the next steps.
If you need to reach us before then, please contact our office directly.
</p>
</div>
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
</t>
</div>
</div>
</field>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True"/>
</record>
<!-- ============================================================== -->
<!-- Repair Intake Received - Office Notification -->
<!-- ============================================================== -->
<record id="email_template_intake_received_office" model="mail.template">
<field name="name">Repair: Intake Received (Office)</field>
<field name="model_id" ref="repair.model_repair_order"/>
<field name="subject">[New Service Call] {{ object.partner_id.name or 'Walk-in' }} - {{ object.name or 'n/a' }}</field>
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
<field name="body_html" type="html">
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;">
<div style="height:4px;background-color:#d69e2e;"></div>
<div style="padding:32px 28px;">
<p style="color:#d69e2e;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
Internal: New Service Call
</p>
<h2 style="font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">A new repair has been submitted</h2>
<p style="opacity:0.65;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
Submitted by <strong><t t-out="object.x_fc_intake_user_id.name or object.user_id.name or 'system'"/></strong>
via the <strong><t t-out="dict(object._fields['x_fc_intake_source'].selection).get(object.x_fc_intake_source) or 'intake'"/></strong>.
</p>
<table style="width:100%;border-collapse:collapse;margin:0 0 24px 0;">
<tr><td colspan="2" style="padding:10px 14px;font-size:12px;font-weight:600;opacity:0.55;text-transform:uppercase;letter-spacing:0.5px;border-bottom:2px solid rgba(128,128,128,0.25);">Details</td></tr>
<tr><td style="padding:10px 14px;opacity:0.6;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);width:35%;">Reference</td><td style="padding:10px 14px;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);"><t t-out="object.name"/></td></tr>
<tr><td style="padding:10px 14px;opacity:0.6;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);">Client</td><td style="padding:10px 14px;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);"><t t-out="object.partner_id.name or 'Walk-in / unknown'"/></td></tr>
<t t-if="object.partner_id.phone">
<tr><td style="padding:10px 14px;opacity:0.6;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);">Phone</td><td style="padding:10px 14px;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);"><t t-out="object.partner_id.phone"/></td></tr>
</t>
<t t-if="object.product_id">
<tr><td style="padding:10px 14px;opacity:0.6;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);">Equipment</td><td style="padding:10px 14px;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);"><t t-out="object.product_id.display_name"/></td></tr>
</t>
<tr><td style="padding:10px 14px;opacity:0.6;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);">Urgency</td><td style="padding:10px 14px;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);"><t t-out="dict(object._fields['x_fc_urgency'].selection).get(object.x_fc_urgency) or 'normal'"/></td></tr>
<t t-if="object.x_fc_third_party_equipment">
<tr><td style="padding:10px 14px;opacity:0.6;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);">Third-party</td><td style="padding:10px 14px;color:#d69e2e;font-size:14px;font-weight:600;border-bottom:1px solid rgba(128,128,128,0.15);">Yes - equipment not sold by us</td></tr>
</t>
<t t-if="object.under_warranty">
<tr><td style="padding:10px 14px;opacity:0.6;font-size:14px;border-bottom:1px solid rgba(128,128,128,0.15);">Warranty</td><td style="padding:10px 14px;color:#38a169;font-size:14px;font-weight:600;border-bottom:1px solid rgba(128,128,128,0.15);">Under warranty</td></tr>
</t>
</table>
</div>
</div>
</field>
<field name="auto_delete" eval="True"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Medical equipment categories used for repair intake routing and skills matching. -->
<record id="category_hospital_bed" model="fusion.repair.product.category">
<field name="name">Hospital Bed</field>
<field name="code">hospital_bed</field>
<field name="sequence">10</field>
<field name="icon">fa-bed</field>
<field name="description">Electric and manual hospital beds, semi-electric beds, low beds.</field>
</record>
<record id="category_wheelchair_manual" model="fusion.repair.product.category">
<field name="name">Wheelchair (Manual)</field>
<field name="code">wheelchair_manual</field>
<field name="sequence">20</field>
<field name="icon">fa-wheelchair</field>
<field name="description">Standard, transport, and tilt-in-space manual wheelchairs.</field>
</record>
<record id="category_wheelchair_power" model="fusion.repair.product.category">
<field name="name">Wheelchair (Power)</field>
<field name="code">wheelchair_power</field>
<field name="sequence">30</field>
<field name="icon">fa-wheelchair</field>
<field name="description">Power wheelchairs, scooters, and powered mobility devices.</field>
</record>
<record id="category_stairlift" model="fusion.repair.product.category">
<field name="name">Stairlift</field>
<field name="code">stairlift</field>
<field name="sequence">40</field>
<field name="icon">fa-arrows-v</field>
<field name="description">Straight and curved indoor stairlifts. Annual safety inspection required in many jurisdictions.</field>
<field name="safety_critical" eval="True"/>
</record>
<record id="category_porch_lift" model="fusion.repair.product.category">
<field name="name">Porch Lift</field>
<field name="code">porch_lift</field>
<field name="sequence">50</field>
<field name="icon">fa-arrow-up</field>
<field name="description">Vertical platform lifts for porches, decks, and accessible building entrances.</field>
<field name="safety_critical" eval="True"/>
</record>
<record id="category_walker" model="fusion.repair.product.category">
<field name="name">Walker</field>
<field name="code">walker</field>
<field name="sequence">60</field>
<field name="icon">fa-male</field>
<field name="description">Standard walkers, hemi-walkers, and folding walkers.</field>
</record>
<record id="category_rollator" model="fusion.repair.product.category">
<field name="name">Rollator</field>
<field name="code">rollator</field>
<field name="sequence">70</field>
<field name="icon">fa-male</field>
<field name="description">Wheeled walkers with seats and brakes.</field>
</record>
<record id="category_mattress" model="fusion.repair.product.category">
<field name="name">Medical Mattress</field>
<field name="code">mattress</field>
<field name="sequence">80</field>
<field name="icon">fa-bed</field>
<field name="description">Air mattresses, alternating pressure, low air loss, and pressure relief mattresses.</field>
</record>
<record id="category_other" model="fusion.repair.product.category">
<field name="name">Other Equipment</field>
<field name="code">other</field>
<field name="sequence">100</field>
<field name="icon">fa-question-circle</field>
<field name="description">Any other medical equipment not in the standard categories.</field>
</record>
</data>
</odoo>