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>
154 lines
8.6 KiB
XML
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>
|