feat: implement multi-branch employee visibility through updated security rules, search overrides, and company sync logic

This commit is contained in:
Suherdy Yacob 2026-05-15 13:46:07 +07:00
parent 6a13fef24d
commit b45364a528
7 changed files with 80 additions and 6 deletions

22
README.md Normal file
View File

@ -0,0 +1,22 @@
# Multi-Company Employee Assignment (Odoo 19)
This module extends the standard Odoo HR functionality to allow a single employee record to be associated with multiple branch companies.
## Features
- **Multi-Branch Assignment**: Add a "Branches" (Many2many) field to the employee form.
- **Cross-Branch Visibility**: Employees appear in the "Employees" menu and search panel for all assigned branches.
- **POS Integration**: Cashiers and employees appear in the POS login interface for all branches they are assigned to.
- **Security Logic**: Access control rules are updated to allow visibility across all selected branches.
- **Fallback Mode**: Existing employees with no branches assigned remain visible based on their original primary company.
## Usage
1. Open an **Employee** form.
2. In the header/top section, find the **Branches** field.
3. Select all the branch companies this employee should belong to.
4. The first branch in the list will automatically be set as the "Primary Company" for standard Odoo processes.
## Technical Details
- Overrides `hr.employee`'s `company_id` to be a computed field based on `company_ids`.
- Overrides `_search` and `ir.rule` to support Many2many company filtering.
- Overrides `pos.config`'s `_employee_domain` for POS login visibility.
- Includes transition logic for existing data.

Binary file not shown.

View File

@ -32,6 +32,37 @@ class HrEmployee(models.Model):
for employee in self: for employee in self:
if employee.company_ids: if employee.company_ids:
employee.company_id = employee.company_ids[0] employee.company_id = employee.company_ids[0]
elif not employee.company_id: elif employee.company_id:
# Fallback to current company if none specified # Sync company_ids from company_id for existing records
employee.company_ids = [(6, 0, [employee.company_id.id])]
else:
employee.company_id = self.env.company employee.company_id = self.env.company
employee.company_ids = [(6, 0, [self.env.company.id])]
@api.model
def _search(self, domain, offset=0, limit=None, order=None, **kwargs):
""" Override search to include company_ids and fallback to company_id if empty """
def replace_company_leaf(dom):
if not dom:
return dom
new_dom = []
for leaf in dom:
if isinstance(leaf, (list, tuple)):
if leaf[0] == 'company_id':
# Match if Branch is in search OR (Branch is empty AND Company is in search)
new_dom.append('|')
new_dom.append(('company_ids', leaf[1], leaf[2]))
new_dom.append('&')
new_dom.append(('company_ids', '=', False))
new_dom.append(('company_id', leaf[1], leaf[2]))
else:
new_dom.append(leaf)
elif isinstance(leaf, list):
new_dom.append(replace_company_leaf(leaf))
else:
new_dom.append(leaf)
return new_dom
if domain:
domain = replace_company_leaf(domain)
return super()._search(domain, offset=offset, limit=limit, order=order, **kwargs)

View File

@ -5,14 +5,14 @@
<field name="name">Attendance multi branch rule</field> <field name="name">Attendance multi branch rule</field>
<field name="model_id" ref="hr_attendance.model_hr_attendance"/> <field name="model_id" ref="hr_attendance.model_hr_attendance"/>
<field name="global" eval="True"/> <field name="global" eval="True"/>
<field name="domain_force">['|', ('employee_id.company_ids', '=', False), ('employee_id.company_ids', 'in', company_ids)]</field> <field name="domain_force">['|', ('employee_id.company_ids', 'in', company_ids), '&amp;', ('employee_id.company_ids', '=', False), ('employee_id.company_id', 'in', company_ids)]</field>
</record> </record>
<record id="hr_attendance.hr_attendance_overtime_line_rule_employee_company" model="ir.rule"> <record id="hr_attendance.hr_attendance_overtime_line_rule_employee_company" model="ir.rule">
<field name="name">Overtime Line multi branch rule</field> <field name="name">Overtime Line multi branch rule</field>
<field name="model_id" ref="hr_attendance.model_hr_attendance_overtime_line"/> <field name="model_id" ref="hr_attendance.model_hr_attendance_overtime_line"/>
<field name="global" eval="True"/> <field name="global" eval="True"/>
<field name="domain_force">['|', ('employee_id.company_ids', '=', False), ('employee_id.company_ids', 'in', company_ids)]</field> <field name="domain_force">['|', ('employee_id.company_ids', 'in', company_ids), '&amp;', ('employee_id.company_ids', '=', False), ('employee_id.company_id', 'in', company_ids)]</field>
</record> </record>
</data> </data>
</odoo> </odoo>

View File

@ -5,7 +5,7 @@
<field name="name">Employee multi branch rule</field> <field name="name">Employee multi branch rule</field>
<field name="model_id" ref="hr.model_hr_employee"/> <field name="model_id" ref="hr.model_hr_employee"/>
<field name="global" eval="True"/> <field name="global" eval="True"/>
<field name="domain_force">['|', ('company_ids', '=', False), ('company_ids', 'in', company_ids)]</field> <field name="domain_force">['|', ('company_ids', 'in', company_ids), '&amp;', ('company_ids', '=', False), ('company_id', 'in', company_ids)]</field>
</record> </record>
</data> </data>
</odoo> </odoo>

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<!-- Form View Extension -->
<record id="view_employee_form_inherit_multi_company" model="ir.ui.view"> <record id="view_employee_form_inherit_multi_company" model="ir.ui.view">
<field name="name">hr.employee.form.inherit.multi.company</field> <field name="name">hr.employee.form.inherit.multi.company</field>
<field name="model">hr.employee</field> <field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/> <field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<!-- Replace company_id with company_ids -->
<xpath expr="//field[@name='company_id']" position="after"> <xpath expr="//field[@name='company_id']" position="after">
<field name="company_ids" widget="many2many_tags" options="{'no_create': True}"/> <field name="company_ids" widget="many2many_tags" options="{'no_create': True}"/>
</xpath> </xpath>
@ -14,4 +14,25 @@
</xpath> </xpath>
</field> </field>
</record> </record>
<!-- Search View Extension -->
<record id="view_employee_filter_inherit_multi_company" model="ir.ui.view">
<field name="name">hr.employee.search.inherit.multi.company</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_filter"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='group_job']" position="after">
<filter name="group_branches" string="Branches" domain="[]" context="{'group_by': 'company_ids'}"/>
</xpath>
</field>
</record>
<!-- Action Overrides to support Multi-Branch visibility in views -->
<record id="hr.open_view_employee_list_my" model="ir.actions.act_window">
<field name="domain">[('company_id', 'in', allowed_company_ids)]</field>
</record>
<record id="hr.open_view_employee_list" model="ir.actions.act_window">
<field name="domain">[('company_id', 'in', allowed_company_ids)]</field>
</record>
</odoo> </odoo>