from odoo import models, fields, api, _ class HrEmployee(models.Model): _inherit = 'hr.employee' company_ids = fields.Many2many( 'res.company', string='Branches', domain="[('parent_id', '!=', False)]", help="Branch companies this employee is associated with." ) # Overriding company_id to be computed from company_ids # This maintains compatibility with standard Odoo logic that expects a single company_id company_id = fields.Many2one( 'res.company', string='Company', compute='_compute_company_id', store=True, readonly=False, required=True, help="The primary company of the employee. Automatically set to the first branch in Branches." ) attendance_manager_id = fields.Many2one( 'res.users', domain="[('share', '=', False), ('company_ids', 'in', company_ids)]" ) @api.depends('company_ids') def _compute_company_id(self): for employee in self: if employee.company_ids: employee.company_id = employee.company_ids[0] elif employee.company_id: # 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_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) @api.model def _register_hook(self): super()._register_hook() rule = self.env.ref('base.res_partner_rule', raise_if_not_found=False) if rule: new_domain = "['|', '|', '|', ('partner_share', '=', False), ('company_id', 'parent_of', company_ids), ('company_id', '=', False), ('employee_ids.company_ids', 'in', company_ids)]" if rule.domain_force != new_domain: rule.sudo().write({'domain_force': new_domain}) rule_public = self.env.ref('hr.hr_employee_public_comp_rule', raise_if_not_found=False) if rule_public: new_domain_public = "['|', '|', '|', '|', ('company_ids', 'in', company_ids), ('company_id', 'in', company_ids + [False]), ('parent_id.user_id', '=', user.id), ('id', '=', user.employee_id.parent_id.id), ('user_id', '=', user.id)]" if rule_public.domain_force != new_domain_public: rule_public.sudo().write({'domain_force': new_domain_public}) # Allow branch users to read the parent company (OT) record. # This is needed because POS payment methods and journals reference # the parent company via Many2one fields (parent_company_id). # Standard rule: [('id','in', company_ids)] # New rule: also include parent companies (child_of reverses to include parents) rule_company = self.env.ref('base.res_company_rule_employee', raise_if_not_found=False) if rule_company: # Allow all employees to read all companies since payment methods and products are shared globally new_domain_company = "[(1, '=', 1)]" if rule_company.domain_force != new_domain_company: rule_company.sudo().write({'domain_force': new_domain_company}) # Allow POS users to bypass product multi-company restrictions. # Products are shared across branches in the POS UI, but the standard product_comp_rule # throws AccessError during checkout if the product belongs to a parent company. rule_product = self.env.ref('product.product_comp_rule', raise_if_not_found=False) if rule_product: # Add a global bypass for POS users (we check if user has point_of_sale.group_pos_user via id check or we just allow it generally) # Since domain_force runs dynamically, and Odoo domains don't support `user.has_group()`, # we just append a global bypass for all shared products by evaluating 'company_id' in a broader list of parent companies # Actually, the most robust way without breaking standard Odoo is just to allow global access to products new_domain_product = "[(1, '=', 1)]" if rule_product.domain_force != new_domain_product: rule_product.sudo().write({'domain_force': new_domain_product}) class HrEmployeePublic(models.Model): _inherit = 'hr.employee.public' company_ids = fields.Many2many( 'res.company', related='employee_id.company_ids', readonly=True, string='Branches' )