Files
Odoo-Modules/fusion_repairs/security/security.xml
gsinghpal d15d9e4303 fix(fusion_repairs): admin + office users get full read/schedule access
When admin (gsingh, uid=2) opened a repair on the dashboard:
  "Sorry, Gurpreet Singh (id=2) doesn't have 'read' access to:
   - Repair Order, RO-202605-04 (repair.order: 34)
   Blame the following rules:
   - Repair Order: Technician sees own repairs"

Root cause: per-group record rules in Odoo are OR'd within the same
model. Admin had been added directly to fusion_tasks.group_field_technician
in this database (verified via res_groups_users_rel - direct=1), so the
technician's restrictive rule ('only repairs you are assigned to') kicked
in. Until now there was no per-group rule for the Repairs Office groups
to OR against, so the restrictive rule won by default.

Fix - added two pairs of permissive rules:

  rule_repair_order_repairs_user_full        - User can read/write/create
  rule_repair_order_repairs_manager_unlink   - Manager also can delete
  rule_technician_task_repairs_office        - User can read/write/create tasks
  rule_technician_task_repairs_manager_unlink - Manager also can delete tasks

Both have domain_force=[(1,'=',1)] so they grant unrestricted access for
the Repairs groups. OR'd with the field_technician rule, admin and other
office users now see everything. Field technicians who do NOT have any
Repairs group still see only their assigned repairs (rule unchanged).

Also added the matching ir.model.access.csv entries - record rules don't
fire if the user has no model-level ACL. This is the second fix
('office users can schedule') from the same complaint - Repairs User now
has read/write/create on fusion.technician.task; Repairs Manager also
gets unlink.

Verified end-to-end on westin-v19:
  Admin can see 17 repairs (was 0 before fix)
  Admin can read RO-202605-04 -> 'Gurpreet Singh' (the exact failing record)
  Admin can create fusion.technician.task -> permission check passes
  (model's own time-overlap business validation correctly rejects an
  overlap, but that is a value error not a permission error)

Bumped to 19.0.1.0.7.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 23:11:37 -04:00

154 lines
8.6 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ==================================================================== -->
<!-- MODULE CATEGORY -->
<!-- ==================================================================== -->
<record id="module_category_fusion_repairs" model="ir.module.category">
<field name="name">Fusion Repairs</field>
<field name="sequence">47</field>
</record>
<!-- ==================================================================== -->
<!-- FUSION REPAIRS PRIVILEGE (Odoo 19 res.groups.privilege pattern) -->
<!-- ==================================================================== -->
<record id="res_groups_privilege_fusion_repairs" model="res.groups.privilege">
<field name="name">Fusion Repairs</field>
<field name="sequence">47</field>
<field name="category_id" ref="module_category_fusion_repairs"/>
</record>
<!-- ==================================================================== -->
<!-- GROUPS -->
<!-- ==================================================================== -->
<record id="group_fusion_repairs_user" model="res.groups">
<field name="name">Repairs: User (CS Intake)</field>
<field name="privilege_id" ref="res_groups_privilege_fusion_repairs"/>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
<field name="comment">CS / front-office staff who take repair intake calls and view repairs.</field>
</record>
<record id="group_fusion_repairs_dispatcher" model="res.groups">
<field name="name">Repairs: Dispatcher</field>
<field name="privilege_id" ref="res_groups_privilege_fusion_repairs"/>
<field name="implied_ids" eval="[(4, ref('group_fusion_repairs_user'))]"/>
<field name="comment">Assigns technicians to repairs, reschedules visits, manages parts pre-pull picklists.</field>
</record>
<record id="group_fusion_repairs_manager" model="res.groups">
<field name="name">Repairs: Manager</field>
<field name="privilege_id" ref="res_groups_privilege_fusion_repairs"/>
<field name="implied_ids" eval="[(4, ref('group_fusion_repairs_dispatcher'))]"/>
<field name="comment">Configures intake templates, pricing, maintenance contracts, on-call rotation, variance overrides.</field>
</record>
<!-- =====================================================================
Admin auto-membership: anyone with base.group_system (Settings /
Administration) automatically gets Repairs Manager, which implies
Dispatcher and User. So admin users see the Fusion Repairs app
and have full access without needing to be added manually.
===================================================================== -->
<record id="base.group_system" model="res.groups">
<field name="implied_ids" eval="[(4, ref('fusion_repairs.group_fusion_repairs_manager'))]"/>
</record>
<!-- ==================================================================== -->
<!-- RECORD RULES -->
<!-- ==================================================================== -->
<!-- Multi-company isolation on repair.order -->
<record id="rule_repair_order_company" model="ir.rule">
<field name="name">Repair Order: Multi-Company</field>
<field name="model_id" ref="repair.model_repair_order"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
<field name="global" eval="True"/>
</record>
<!-- Field technicians (from fusion_tasks) see only repairs they're assigned to as technician on a linked task.
Uses STORED fields (technician_id + additional_technician_ids) - not the computed all_technician_ids.
NOTE: per-group rules in Odoo are OR'd. A user who is BOTH a field
technician AND a Repairs User/Dispatcher/Manager will see all repairs
because the permissive Repairs rules below grant access via the OR. -->
<record id="rule_repair_order_technician_own" model="ir.rule">
<field name="name">Repair Order: Technician sees own repairs</field>
<field name="model_id" ref="repair.model_repair_order"/>
<field name="domain_force">['|', ('x_fc_technician_task_ids.technician_id', '=', user.id), ('x_fc_technician_task_ids.additional_technician_ids', 'in', [user.id])]</field>
<field name="groups" eval="[(4, ref('fusion_tasks.group_field_technician'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<!-- Repairs office users (User / Dispatcher / Manager) see all repairs
across companies they have access to. OR'd with the technician rule
above so admin / dispatchers who happen to also be in field_technician
still see everything. -->
<record id="rule_repair_order_repairs_user_full" model="ir.rule">
<field name="name">Repair Order: Repairs Office Full Access</field>
<field name="model_id" ref="repair.model_repair_order"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('group_fusion_repairs_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="rule_repair_order_repairs_manager_unlink" model="ir.rule">
<field name="name">Repair Order: Repairs Manager Can Delete</field>
<field name="model_id" ref="repair.model_repair_order"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('group_fusion_repairs_manager'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
<!-- Repairs office users can read AND schedule technician tasks. This is
what makes "office can dispatch / reschedule" work without requiring
them to also be in the sales_team groups that fusion_tasks normally
keys off of. -->
<record id="rule_technician_task_repairs_office" model="ir.rule">
<field name="name">Technician Task: Repairs Office Access</field>
<field name="model_id" ref="fusion_tasks.model_fusion_technician_task"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('group_fusion_repairs_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="rule_technician_task_repairs_manager_unlink" model="ir.rule">
<field name="name">Technician Task: Repairs Manager Can Delete</field>
<field name="model_id" ref="fusion_tasks.model_fusion_technician_task"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('group_fusion_repairs_manager'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
<!-- Intake answer access scoped to repair access -->
<record id="rule_repair_intake_answer_company" model="ir.rule">
<field name="name">Repair Intake Answer: Multi-Company</field>
<field name="model_id" ref="model_fusion_repair_intake_answer"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
<field name="global" eval="True"/>
</record>
<!-- Sales Rep Portal: sees only repair orders they submitted -->
<record id="rule_repair_order_sales_rep_portal" model="ir.rule">
<field name="name">Repair Order: Sales Rep Portal - Own Repairs</field>
<field name="model_id" ref="repair.model_repair_order"/>
<field name="domain_force">[('x_fc_intake_user_id', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
</odoo>