7f8a80fecbc575e1b4738ca0bd423f6756dd4f1e
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
38a79a4b04 |
feat(fusion_repairs): OWL dashboard - quick actions, KPIs, portal share
A real landing dashboard for the Fusion Repairs app so users see at a glance what is open, what is urgent, and where to click. Built as an OWL client action, theme-aware (light AND dark) at SCSS compile time, zero hardcoded user-facing colours. What's on it - Hero banner with gradient accent - 4 quick-action tiles (New Service Call, Service Calls, Maintenance Contracts, Repair Warranties) - 6 KPI stat tiles (Open / Urgent+Safety / Awaiting Dispatch / Needs Re-Quote / New This Month / Maintenance Due 30d) - each is clickable and lands in the right filtered list - Self-service portal cards with copy-to-clipboard for the public client portal URL and the sales rep portal URL (so office can share them on voicemail / printed materials / training) - Recent Service Calls list (last 5) - click jumps to repair form - Upcoming Maintenance list (next 5 due) - red pill when <=7 days out - Configuration tiles (Equipment Categories / Intake Templates / Service Catalogue) - Refresh button Architecture - fusion.repair.dashboard AbstractModel exposes get_dashboard_data(): returns stats + urgency_breakdown + source_breakdown + recent[5] + upcoming[5] + portals (URLs resolved via web.base.url + fusion_repairs.client_portal_url) - FusionRepairsDashboard OWL component (registry actions 'fusion_repairs.dashboard') uses standalone rpc() per project rule #3, useService('action') for navigation, useService('notification') for copy feedback. static props = ['*'] to accept the client-action props envelope. - _fr_tokens.scss registered FIRST in web.assets_backend so its variables are in scope when dashboard.scss compiles. NO @import (per project rule). Branches on $o-webclient-color-scheme at compile time so the dark bundle (web.assets_web_dark) gets dark hex values automatically - per project CLAUDE.md rule on dark mode. - All visible colours come from CSS-variable-wrapped SCSS tokens (--fr-page-bg, --fr-card-bg, --fr-border, --fr-accent, ...) which fall back to the SCSS hex value. Three-layer contrast: page (grayest) -> card (mid) -> elevated (brightest). - New ir.actions.client action_fusion_repairs_home_dashboard with tag='fusion_repairs.dashboard'. - Top-level menu now lands on this dashboard. 'Dashboard' added as the first sub-menu; 'Service Calls' (the kanban) is still right below it. Verified on local westin-v19: STATS: open=15, urgent=4, new_this_month=13, awaiting_dispatch=9, requires_requote=1, maintenance_due_30d=1, active_total=2 PORTALS: client=http://192.168.139.165:8069/repair sales_rep=http://192.168.139.165:8069/my/repair/new RECENT count: 5 UPCOMING count: 2 SOURCE breakdown: backend_wizard 9, client_portal 3, manual 2, sales_rep_portal 1 Web /web/login: 200, no SCSS compile errors in logs. Bumped to 19.0.1.0.5 so the asset bundle hash refreshes. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
5a5e310a83 |
feat(fusion_repairs): repair.order reference format -> RO-YYYYMM-NN
Replaced the picking-type default reference (BR-WA/RO/00010) with a date-based monthly-resetting sequence: RO-202605-01, RO-202605-02, ... where YYYY is the year and MM is the zero-padded month. The counter resets to 01 every time the month rolls over. Implementation: - New ir.sequence 'fusion.repair.order.monthly' with prefix 'RO-%(year)s%(month)s-', padding=2, use_date_range=True (Odoo creates one ir.sequence.date_range per month, each with its own number_next) - repair.order.create() override pre-fills vals['name'] with the new sequence BEFORE super(), so Odoo's native picking-type sequence assignment (which only fires when name is empty / 'New') is bypassed Verified on local westin-v19: three back-to-back creates produced RO-202605-01 / -02 / -03. Existing records (pre-upgrade) keep their old BR-WA/RO/##### references - this only affects repairs created from this version onward. Bumped to 19.0.1.0.4. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
cb56a38680 |
fix(fusion_repairs): chatter posts render HTML correctly via Markup
Reports of literal '<b>Client Self-Service</b>' showing in the chatter
instead of bold formatting. Cause: message_post(body=str) HTML-escapes
the string. The Odoo idiom for HTML chatter bodies is markupsafe.Markup,
with the % operator auto-escaping substitution values for XSS safety.
Fixed every message_post call:
models/intake_service.py
- 'Service call submitted via <b>...</b>' (the reported one)
- 'This repair MAY be covered by our active warranty <b>...</b>'
models/maintenance_contract.py
- 'Sent N-day maintenance reminder to <email>'
- 'Maintenance visit <b>...</b> booked from reminder link'
models/technician_task.py
- 'Rolled forward after maintenance task <b>...</b> completed'
wizard/repair_visit_report_wizard.py
- 'Spawned follow-up repair <b>...</b> for "found another issue"'
Pattern used: Markup(_('... <b>%(x)s</b> ...')) % {'x': escaped_value}.
Verified on local westin-v19 (BR-WA/RO/00026): DB row now reads
'<p>Service call submitted via <b>Client Self-Service</b> by Gurpreet
Singh. Session reference: RIS000015.</p>' which renders correctly in
the chatter UI.
Bumped to 19.0.1.0.3.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|
|
c86f1bbbe5 |
fix(fusion_repairs): code-review batch - 4 critical + 8 high + 8 medium/low
Critical - C1: _sql_constraints -> models.Constraint (Odoo 19 deprecation rule violation) - C2: variance threshold no longer uses abs() - under-cost is good news, must not block invoicing. Now only OVER-cost triggers requires_requote. - C3: roll_next_due_date() was dead code - now wired from fusion.technician.task.write() when a maintenance task transitions to 'completed', so the whole maintenance lifecycle actually advances. - C4: warranty.is_active was store=True but time-dependent (became stale). Dropped store=True; find_active_for() now filters by expiry_date directly. High - H1: added x_fc_maintenance_contract_id back-link on repair.order and populated it from create_repair_from_booking(). - H2: find_active_for() returns empty when neither lot nor product is supplied - prevents cross-product false warranty matches. - H3: visit-report wizard now creates stock.move records of repair_line_type 'add' for each part line, so Odoo's native action_create_sale_order() chain has lines to invoice and stock gets consumed properly. - H4: office intake email template now carries a fallback email_to header computed from res.company.x_fc_office_notification_ids (or company email), so it does not silently send with no recipient. - H5: maintenance reminder cron nextcall now always rolls to tomorrow at 07:00 local time, so installing/upgrading after 07:00 does not immediately fire all the day's reminders. - H6: public portal no longer hardcodes UID 1 as the intake user fallback (which in Odoo 19 is OdooBot). Prefers base.user_admin, else the lowest-id non-share user, else SUPERUSER_ID. - H7: public portal validates client_email via tools.email_normalize before partner creation; malformed addresses redirect with error=email. - H8: find_best_match() returns empty when no symptom keywords match (no silent first-catalog guess) and uses word-boundary regex to avoid matching 'battery' inside 'no battery problem'. Medium - M1: _inherit moved next to _name on maintenance_contract (cosmetic but brittle if Odoo refactors model class detection) - M2: relativedelta(months=N) instead of timedelta(days=N*30) for warranty and maintenance intervals (correct month boundaries) - M3: unique constraint on fusion.repair.maintenance.contract.booking_token - M6: dispatch task fallback now searches for an actual x_fc_is_field_staff user; gracefully skips and logs if no field staff exists (instead of silently failing the constraint check) - M7: maintenance contract list view date decoration uses context_today() (date) instead of strftime(string) - the str comparison would TypeError - M9: Visit Report button hidden on draft repairs and when no technician task is linked yet Low - L2: portal-created partners get default lang + company_id so mail templates render in the right language - L3: dropped unused exception variable in sales rep portal controller - L4: visit-report wizard 'found another issue' now redirects to the spawned stub repair so the tech can fill it in immediately - L5: dropped unrecognized data-string from <app> in settings view Public portal also: rate-limit check moved BEFORE the counter increment so blocked attempts do not keep inflating the bucket. All fixes verified end-to-end on local westin-v19: - variance one-sided: 0.5h labour vs $500 est -> requires_requote=False; 2h x $250 + $200 parts vs $100 est -> requires_requote=True - maintenance roll-forward: created MC/00006 due 2026-05-31, completed linked maintenance task -> contract rolled to 2026-11-21 with last_reminder_band reset - warranty find_active_for(partner only) -> empty recordset - service catalog find_best_match with unrelated text -> empty recordset - pg_constraint shows fusion_repair_maintenance_contract_booking_token_unique - /repair landing still 200 after restart Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
afe19f2105 |
feat(fusion_repairs): sale.order smart buttons - repairs + maintenance
On the original purchase sale.order: - Repairs button (fa-wrench) lists all repair.order records where x_fc_original_sale_order_id = this SO - Maintenance button (fa-calendar-check-o) lists all fusion.repair.maintenance.contract records spawned from this SO - Both auto-hide when count is zero - Both gated by fusion_repairs.group_fusion_repairs_user Follows the count + action_view_* + oe_stat_button / statinfo pattern from fusion_claims/views/sale_order_views.xml line ~1176. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
73ee48e7c9 |
feat(fusion_repairs): Phase 3 - maintenance contracts + client self-booking
Maintenance contracts - New fusion.repair.maintenance.contract model: one per partner + product + lot. Fields: interval_months, last_service_date, next_due_date, state, booking_token (secrets.token_urlsafe), last_reminder_band (30 / 7 / 1), booking_repair_id - roll_next_due_date() advances the cycle by interval_months and resets the band / booked-repair so the next cycle starts fresh - sale.order._spawn_maintenance_contracts() creates contracts for delivered SOs whose product has x_fc_maintenance_interval_months > 0 (called from Phase 3 hooks; ready for cron / on-state change wiring) Reminder cron - Daily ir.cron at 07:00 -> cron_send_due_reminders() - Sends email at 30 / 7 / 1 day bands before next_due_date; tracks last_reminder_band so we never re-send the same band in one cycle - Master toggle via ir.config_parameter fusion_repairs.enable_email_notifications Public client booking portal - /repairs/maintenance/book/<token> GET landing page with a date input - /repairs/maintenance/book/<token>/confirm POST creates a repair.order via contract.create_repair_from_booking() (source='client_portal') - Idempotent: existing booking shows "already booked" instead of spawning a duplicate - Invalid / expired tokens render a friendly "link not valid" page Mail template - email_template_maintenance_due_reminder with 4px green accent bar, 600px max-width, dark/light safe; renders the tokenized booking CTA button directly to /repairs/maintenance/book/<token> Backend - Maintenance Contracts list / form with statusbar + chatter - Menu under Operations -> Maintenance Contracts - Sequence MC/##### for contract reference - Access rules: User read, Dispatcher write, Manager full Verified end-to-end on local westin-v19: - Contract MC/00003 created due in 7 days - cron_send_due_reminders() fires the 7-day band; second invocation skips (idempotent) - create_repair_from_booking() spawns BR-WA/RO/00014 with x_fc_intake_source='client_portal' and links it back to the contract - HTTP GET /repairs/maintenance/book/<token> -> 200 with the date input and contract reference visible in the page Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
7727745b73 |
feat(fusion_repairs): Phase 2 - service catalogue, visit report, warranty, Poynt
Service catalogue - New fusion.repair.service.catalog model: named service entries per equipment category with symptom keywords, estimated hours / cost, default parts, auto_schedule flag, optional pricelist override - find_best_match() scores candidates by symptom-keyword overlap against intake text hints (issue summary + category + notes) - Intake service wires it in: on submit, the matcher sets x_fc_service_catalog_id + x_fc_estimated_duration + x_fc_estimated_cost and (when auto_schedule=True) creates a draft dispatch task - Double-task guard: if catalogue match already created a task, the urgency-based dispatch skips so we never duplicate Visit report wizard - fusion.repair.visit.report.wizard with labour hours + parts lines + technician notes + 'found another issue' branch - Computes actual cost = (labour x service_product.list_price) + parts - Compares against estimate -> sets requires_requote when variance exceeds configured threshold (% or $); shows warning banner inline - On confirm: writes actuals back to repair, posts notes to chatter, optionally spawns a follow-up repair (T5 'found another issue') Repair warranty - New fusion.repair.warranty.coverage model (start/expiry, partner, product, lot, active flag) - find_active_for(partner, product, lot) returns the most-recent active coverage - Intake service auto-checks: when a new repair lands on an equipment that has active warranty coverage, posts a chatter banner so the office knows the work may be free under our 30/90-day re-do policy (manager review still required; never auto-zeros pricing) Repair form - Header: Visit Report + Collect Payment buttons (gated by group) - action_collect_payment looks up the linked posted unpaid invoice on the repair SO and opens the Poynt wizard (action_open_poynt_payment_wizard) AI intake summary - _generate_ai_summary calls self.env['fusion.api.service'].call_openai with consumer='fusion_repairs', feature='intake_triage' - Strict system prompt: no medical advice, no diagnoses, no recommending stop equipment use; ~80 words; plain English - Try/fallback per fusion-api-integration.mdc: if fusion_api not installed or call fails -> silently skip; intake never blocked Verified end-to-end on local westin-v19: - Stairlift motor intake -> catalogue match -> estimated $500/2h -> auto dispatch task (count=1, not duplicated) - Visit report: 2.5h x $250 + $100 parts = $725 actual vs $500 estimated = 45% variance -> requires_requote=True - Warranty: 30-day coverage on the completed repair; second repair on same partner triggers warranty banner in chatter Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
429084e0bf |
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> |