New fusion.repair.inspection.certificate model for the annual safety inspections required on stairlifts, porch lifts, and power wheelchairs in many jurisdictions. Model - mail.thread chatter-tracked; fields: name (CERT-YYYY-NNNN auto-seq), partner_id, product_id (filtered to safety-critical categories), lot_id, repair_order_id back-link, inspector_user_id (must be field staff), jurisdiction (selection: Ontario / BC / Alberta / Quebec / Other), issued_date, valid_for_months (default 12), expiry_date (computed, stored, uses relativedelta - correct month boundaries), status (non-stored compute: valid / expiring / expired / revoked), revoked, notes, last_reminder_band. - Unique constraint on certificate number (models.Constraint, not _sql_constraints, per project rule). - Sequence 'fusion.repair.inspection.certificate' with use_date_range=True so the counter resets each year (CERT-2026-0001 ... CERT-2027-0001). Visit report integration - New issue_inspection_cert checkbox on fusion.repair.visit.report.wizard. - When ticked AND the repair's category is safety_critical, action_confirm() creates the certificate via _create_inspection_certificate() and redirects to the cert form so the tech can print immediately. - Non-safety-critical equipment quietly skips with a chatter note explaining why. PDF report - web.html_container + web.external_layout, model bound so it appears as a Print action on the certificate form. - 'Certificate of Inspection' / 'Safety Inspected' gold-banner layout with client name, equipment, serial, jurisdiction, issued + expiry dates, inspector signature line, and the certificate number. - Print Certificate button in form header. Daily cron - cron_send_expiry_reminders runs at 09:00, sends two band-tracked reminders (30 days + 7 days before expiry) to the client. - New mail.template email_template_inspection_expiry_reminder with 4px amber accent, certificate ref, equipment, expiry date, and a CTA to call to book the re-inspection visit. - last_reminder_band on the cert prevents re-sending the same band. Backend wiring - New menu entry 'Fusion Repairs > Inspection Certificates'. - ACL: User read, Dispatcher write, Manager unlink. Field technicians can create (they need to issue from the field). - List view with red/amber/green status decoration. - Form with statusbar, header buttons (Print, Revoke with confirm), chatter. Verified end-to-end on local westin-v19: Stairlift repair RO-202605-15 -> visit-report with issue_inspection_cert=True -> CERT-2026-0001 issued (status=valid, expires 2027-05-21) Cert CERT-2026-0002 expiring in 30 days -> cron flagged last_reminder_band='30' (would email client). Bumped to 19.0.1.4.0 (minor bump for the new public-facing capability). Co-authored-by: Cursor <cursoragent@cursor.com>
87 lines
4.9 KiB
XML
87 lines
4.9 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<odoo>
|
|
<data noupdate="1">
|
|
|
|
<!-- Daily maintenance reminders at 30/7/1 days before due date. -->
|
|
<record id="cron_maintenance_due_reminders" model="ir.cron">
|
|
<field name="name">Fusion Repairs: Send maintenance due reminders</field>
|
|
<field name="model_id" ref="model_fusion_repair_maintenance_contract"/>
|
|
<field name="state">code</field>
|
|
<field name="code">model.cron_send_due_reminders()</field>
|
|
<field name="user_id" ref="base.user_root"/>
|
|
<field name="interval_number">1</field>
|
|
<field name="interval_type">days</field>
|
|
<field name="nextcall" eval="(DateTime.now().replace(hour=7, minute=0, second=0, microsecond=0) + timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
<field name="active" eval="True"/>
|
|
</record>
|
|
|
|
<!-- CL15: Escalate unacknowledged on-call pages every 5 minutes.
|
|
Pages older than fusion_repairs.on_call_escalate_minutes
|
|
(default 15) get re-paged to the next priority. -->
|
|
<record id="cron_on_call_escalate" model="ir.cron">
|
|
<field name="name">Fusion Repairs: Escalate unacknowledged on-call pages</field>
|
|
<field name="model_id" ref="model_fusion_repair_on_call_service"/>
|
|
<field name="state">code</field>
|
|
<field name="code">model.cron_escalate_unacknowledged()</field>
|
|
<field name="user_id" ref="base.user_root"/>
|
|
<field name="interval_number">5</field>
|
|
<field name="interval_type">minutes</field>
|
|
<field name="nextcall" eval="(DateTime.now() + timedelta(minutes=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
<field name="active" eval="True"/>
|
|
</record>
|
|
|
|
<!-- X2: Day-before visit reminders. Runs every morning at 08:00. -->
|
|
<record id="cron_day_before_reminders" model="ir.cron">
|
|
<field name="name">Fusion Repairs: Day-before visit reminders</field>
|
|
<field name="model_id" ref="repair.model_repair_order"/>
|
|
<field name="state">code</field>
|
|
<field name="code">model.cron_send_day_before_reminders()</field>
|
|
<field name="user_id" ref="base.user_root"/>
|
|
<field name="interval_number">1</field>
|
|
<field name="interval_type">days</field>
|
|
<field name="nextcall" eval="(DateTime.now().replace(hour=8, minute=0, second=0, microsecond=0) + timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
<field name="active" eval="True"/>
|
|
</record>
|
|
|
|
<!-- X4: Post-visit NPS. Runs hourly to send 24h after state=done. -->
|
|
<record id="cron_post_visit_nps" model="ir.cron">
|
|
<field name="name">Fusion Repairs: Send post-visit NPS emails</field>
|
|
<field name="model_id" ref="repair.model_repair_order"/>
|
|
<field name="state">code</field>
|
|
<field name="code">model.cron_send_post_visit_nps()</field>
|
|
<field name="user_id" ref="base.user_root"/>
|
|
<field name="interval_number">1</field>
|
|
<field name="interval_type">hours</field>
|
|
<field name="nextcall" eval="(DateTime.now() + timedelta(hours=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
<field name="active" eval="True"/>
|
|
</record>
|
|
|
|
<!-- M1: Inspection certificate expiry reminders (30 + 7 days). -->
|
|
<record id="cron_inspection_expiry_reminders" model="ir.cron">
|
|
<field name="name">Fusion Repairs: Inspection certificate expiry reminders</field>
|
|
<field name="model_id" ref="model_fusion_repair_inspection_certificate"/>
|
|
<field name="state">code</field>
|
|
<field name="code">model.cron_send_expiry_reminders()</field>
|
|
<field name="user_id" ref="base.user_root"/>
|
|
<field name="interval_number">1</field>
|
|
<field name="interval_type">days</field>
|
|
<field name="nextcall" eval="(DateTime.now().replace(hour=9, minute=0, second=0, microsecond=0) + timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
<field name="active" eval="True"/>
|
|
</record>
|
|
|
|
<!-- M3: Offer loaner activity for long-running repairs. Runs daily. -->
|
|
<record id="cron_offer_loaner" model="ir.cron">
|
|
<field name="name">Fusion Repairs: Offer loaner for long-running repairs</field>
|
|
<field name="model_id" ref="repair.model_repair_order"/>
|
|
<field name="state">code</field>
|
|
<field name="code">model.cron_offer_loaner_for_long_repairs()</field>
|
|
<field name="user_id" ref="base.user_root"/>
|
|
<field name="interval_number">1</field>
|
|
<field name="interval_type">days</field>
|
|
<field name="nextcall" eval="(DateTime.now().replace(hour=8, minute=30, second=0, microsecond=0) + timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
<field name="active" eval="True"/>
|
|
</record>
|
|
|
|
</data>
|
|
</odoo>
|