diff options
| author | Yannick Tivisse <yti@odoo.com> | 2018-11-14 13:55:35 +0100 |
|---|---|---|
| committer | Yannick Tivisse <yti@odoo.com> | 2019-01-21 12:33:03 +0100 |
| commit | 1684ec555dcccfbd4fca9123902065e01a67c057 (patch) | |
| tree | 00bb60d1b262f17289abac57cd7395029f8018c9 | |
| parent | 4764a70af7d9e32468a3608efceb4553b01a242a (diff) | |
[IMP] hr_payroll: Manage multi company on payslips/contracts
Purpose
=======
Currently the payroll doens't manage the multi company
Specification
=============
1/ Add ir.rules on hr.contract and hr.payslip to prevent users
to access a record in another company.
2/ Add ir.rules on hr.payroll.structure to prevent users to access
a record for another country.
3/ Remove company_id fields on models that doesn't require it, as
hr.salary.rule.category.
4/ Make the accounting fields on the salary rules company dependent.
5/ Specify the country on the salary structures on the
l10n_**_hr_payroll modules.
6/ Add demo data and modify the salary package tour.
7/ Make the car_atn and company_car_total_depreciated_costs fields
compute_sudo=True, as one car can be assigned on several contract,
for different companies
Closes #26866
Closes #25231
Closes #24683
Closes #23814
17 files changed, 59 insertions, 36 deletions
diff --git a/addons/hr_contract/security/security.xml b/addons/hr_contract/security/security.xml index 6a922ba15b2..1e4cd3a829f 100644 --- a/addons/hr_contract/security/security.xml +++ b/addons/hr_contract/security/security.xml @@ -10,5 +10,13 @@ <record id="base.default_user" model="res.users"> <field name="groups_id" eval="[(4,ref('hr_contract.group_hr_contract_manager'))]"/> </record> + + <record id="ir_rule_hr_contract_multi_company" model="ir.rule"> + <field name="name">HR Contract: Multi Company</field> + <field name="model_id" ref="model_hr_contract"/> + <field name="global" eval="True"/> + <field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field> + </record> + </data> </odoo> diff --git a/addons/hr_payroll/data/hr_payroll_data.xml b/addons/hr_payroll/data/hr_payroll_data.xml index 72c2b198699..0775b47795e 100644 --- a/addons/hr_payroll/data/hr_payroll_data.xml +++ b/addons/hr_payroll/data/hr_payroll_data.xml @@ -87,7 +87,6 @@ <field name="code">BASE</field> <field name="name">Base for new structures</field> <field eval="[(6, 0, [ref('hr_rule_basic'), ref('hr_rule_taxable'),ref('hr_rule_net')])]" name="rule_ids"/> - <field name="company_id" ref="base.main_company"/> </record> <!-- Decimal Precision --> diff --git a/addons/hr_payroll/data/hr_payroll_demo.xml b/addons/hr_payroll/data/hr_payroll_demo.xml index b8914e79d85..d099924d4ee 100644 --- a/addons/hr_payroll/data/hr_payroll_demo.xml +++ b/addons/hr_payroll/data/hr_payroll_demo.xml @@ -88,7 +88,6 @@ <field name="code">ME</field> <field name="name">Marketing Executive</field> <field eval="[(6, 0, [ref('hr_salary_rule_houserentallowance1'), ref('hr_salary_rule_convanceallowance1'),ref('hr_salary_rule_professionaltax1'),ref('hr_salary_rule_providentfund1')])]" name="rule_ids"/> - <field name="company_id" ref="base.main_company"/> <field name="parent_id" ref="structure_base"/> </record> @@ -96,7 +95,6 @@ <field name="code">MEGG</field> <field name="name">Marketing Executive for Gilles Gravie</field> <field eval="[(6, 0, [ref('hr_salary_rule_ca_gravie'), ref('hr_salary_rule_meal_voucher')])]" name="rule_ids"/> - <field name="company_id" ref="base.main_company"/> <field name="parent_id" ref="structure_001"/> </record> diff --git a/addons/hr_payroll/models/hr_payslip.py b/addons/hr_payroll/models/hr_payslip.py index dcfa2d133ad..0d15f51269f 100644 --- a/addons/hr_payroll/models/hr_payslip.py +++ b/addons/hr_payroll/models/hr_payslip.py @@ -47,7 +47,7 @@ class HrPayslip(models.Model): \n* When user cancel payslip the status is \'Rejected\'.""") line_ids = fields.One2many('hr.payslip.line', 'slip_id', string='Payslip Lines', readonly=True, states={'draft': [('readonly', False)], 'verify': [('readonly', False)]}) - company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, copy=False, + company_id = fields.Many2one('res.company', string='Company', readonly=True, copy=False, required=True, default=lambda self: self.env['res.company']._company_default_get(), states={'draft': [('readonly', False)], 'verify': [('readonly', False)]}) worked_days_line_ids = fields.One2many('hr.payslip.worked_days', 'payslip_id', diff --git a/addons/hr_payroll/models/hr_salary_rule.py b/addons/hr_payroll/models/hr_salary_rule.py index 0fe344897db..2031663ca40 100644 --- a/addons/hr_payroll/models/hr_salary_rule.py +++ b/addons/hr_payroll/models/hr_salary_rule.py @@ -27,8 +27,7 @@ class HrPayrollStructure(models.Model): name = fields.Char(required=True) code = fields.Char(string='Reference', required=True) - company_id = fields.Many2one('res.company', string='Company', required=True, - copy=False, default=lambda self: self.env['res.company']._company_default_get()) + country_id = fields.Many2one('res.country', string='Country') note = fields.Text(string='Description') parent_id = fields.Many2one('hr.payroll.structure', string='Parent', default=_get_parent) children_ids = fields.One2many('hr.payroll.structure', 'parent_id', string='Children', copy=True) @@ -70,8 +69,6 @@ class HrContributionRegister(models.Model): _name = 'hr.contribution.register' _description = 'Contribution Register' - company_id = fields.Many2one('res.company', string='Company', - default=lambda self: self.env['res.company']._company_default_get()) partner_id = fields.Many2one('res.partner', string='Partner') name = fields.Char(required=True) register_line_ids = fields.One2many('hr.payslip.line', 'register_id', @@ -89,8 +86,6 @@ class HrSalaryRuleCategory(models.Model): help="Linking a salary category to its parent is used only for the reporting purpose.") children_ids = fields.One2many('hr.salary.rule.category', 'parent_id', string='Children') note = fields.Text(string='Description') - company_id = fields.Many2one('res.company', string='Company', - default=lambda self: self.env['res.company']._company_default_get()) @api.constrains('parent_id') def _check_parent_id(self): @@ -120,8 +115,6 @@ class HrSalaryRule(models.Model): appears_on_payslip = fields.Boolean(string='Appears on Payslip', default=True, help="Used to display the salary rule on payslip.") parent_rule_id = fields.Many2one('hr.salary.rule', string='Parent Salary Rule', index=True) - company_id = fields.Many2one('res.company', string='Company', - default=lambda self: self.env['res.company']._company_default_get()) condition_select = fields.Selection([ ('none', 'Always True'), ('range', 'Range'), diff --git a/addons/hr_payroll/security/hr_payroll_security.xml b/addons/hr_payroll/security/hr_payroll_security.xml index ba26e5966a6..e666a36cb80 100644 --- a/addons/hr_payroll/security/hr_payroll_security.xml +++ b/addons/hr_payroll/security/hr_payroll_security.xml @@ -55,5 +55,19 @@ <field name="perm_read" eval="0"/> </record> + <record id="ir_rule_hr_payroll_structure_multi_company" model="ir.rule"> + <field name="name">HR Payroll Structure: Multi Company</field> + <field name="model_id" ref="model_hr_payroll_structure"/> + <field name="global" eval="True"/> + <field name="domain_force">['|', ('country_id', '=', False), ('country_id', '=', user.company_id.country_id.id)]</field> + </record> + + <record id="ir_rule_hr_payslip_multi_company" model="ir.rule"> + <field name="name">HR Payslip: Multi Company</field> + <field name="model_id" ref="model_hr_payslip"/> + <field name="global" eval="True"/> + <field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id])]</field> + </record> + </data> </odoo> diff --git a/addons/hr_payroll/tests/common.py b/addons/hr_payroll/tests/common.py index 8d50dec7621..3848011849c 100644 --- a/addons/hr_payroll/tests/common.py +++ b/addons/hr_payroll/tests/common.py @@ -32,7 +32,6 @@ class TestPayslipBase(TransactionCase): self.developer_pay_structure = self.env['hr.payroll.structure'].create({ 'name': 'Salary Structure for Software Developer', 'code': 'SD', - 'company_id': self.ref('base.main_company'), 'rule_ids': [(4, self.hra_rule_id), (4, self.conv_rule_id), (4, self.prof_tax_rule_id), (4, self.pf_rule_id), (4, self.mv_rule_id)], diff --git a/addons/hr_payroll/views/hr_payslip_views.xml b/addons/hr_payroll/views/hr_payslip_views.xml index 60c5ff4ba2b..01c8ff8f138 100644 --- a/addons/hr_payroll/views/hr_payslip_views.xml +++ b/addons/hr_payroll/views/hr_payslip_views.xml @@ -228,7 +228,7 @@ <page string="Accounting Information"> <group> <group string="Miscellaneous"> - <field name="company_id" groups="base.group_multi_company"/> + <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/> <field name="payslip_run_id" domain="[('state','=','draft')]"/> </group> <group name="accounting" string="Accounting"> diff --git a/addons/hr_payroll/views/hr_salary_rule_views.xml b/addons/hr_payroll/views/hr_salary_rule_views.xml index 29190e6893a..4266a0074a8 100644 --- a/addons/hr_payroll/views/hr_salary_rule_views.xml +++ b/addons/hr_payroll/views/hr_salary_rule_views.xml @@ -2,6 +2,7 @@ <odoo> <!-- Salary structure --> + <!-- YTI TODO: One salary structure list view seems to unused --> <record id="view_hr_payroll_structure_list_view" model="ir.ui.view"> <field name="name">hr.payroll.structure.tree</field> <field name="model">hr.payroll.structure</field> @@ -10,7 +11,7 @@ <field name="name"/> <field name="code"/> <field name="rule_ids"/> - <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/> + <field name="country_id" options="{'no_create': True}"/> </tree> </field> </record> @@ -48,7 +49,7 @@ <tree string="Salary Structure"> <field name="name"/> <field name="code"/> - <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/> + <field name="country_id" options="{'no_create': True}"/> </tree> </field> </record> @@ -74,7 +75,7 @@ <field name="code"/> <field name="parent_id"/> <field name="report_id"/> - <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/> + <field name="country_id" options="{'no_create': True}"/> </group> <notebook colspan="4"> <page name="salary_rules" string="Salary Rules"> @@ -161,7 +162,6 @@ <field name="arch" type="xml"> <tree string="Contribution Registers"> <field name="name" /> - <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/> </tree> </field> </record> @@ -190,7 +190,6 @@ <field name="arch" type="xml"> <search string="Contribution Registers"> <field name="name" string="Contribution Registers"/> - <field name="company_id" groups="base.group_multi_company"/> </search> </field> </record> @@ -203,7 +202,6 @@ <group name="general_info"> <field name="name"/> <field name="partner_id"/> - <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}" class="oe_inline"/> </group> <newline/> <group name="description"> @@ -283,7 +281,6 @@ <field name="name"/> <field name="code"/> <field name="category_id" invisible="True"/> - <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/> </tree> </field> </record> @@ -303,7 +300,6 @@ <field name="sequence" /> <field name="active"/> <field name="appears_on_payslip"/> - <field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/> </group> <notebook colspan="6"> <page name="general" string="General"> diff --git a/addons/hr_payroll_account/tests/test_hr_payroll_account.py b/addons/hr_payroll_account/tests/test_hr_payroll_account.py index 5bb4bdcced2..b82f358dd78 100644 --- a/addons/hr_payroll_account/tests/test_hr_payroll_account.py +++ b/addons/hr_payroll_account/tests/test_hr_payroll_account.py @@ -46,7 +46,6 @@ class TestHrPayrollAccount(common.TransactionCase): self.hr_structure_softwaredeveloper = self.env['hr.payroll.structure'].create({ 'name': 'Salary Structure for Software Developer', 'code': 'SD', - 'company_id': self.ref('base.main_company'), 'parent_id': self.ref('hr_payroll.structure_base'), 'rule_ids': [(6, 0, [ self.ref('hr_payroll.hr_salary_rule_houserentallowance1'), diff --git a/addons/l10n_be_hr_payroll/__manifest__.py b/addons/l10n_be_hr_payroll/__manifest__.py index 5a1f5c00865..3dbf4eab5d7 100644 --- a/addons/l10n_be_hr_payroll/__manifest__.py +++ b/addons/l10n_be_hr_payroll/__manifest__.py @@ -25,5 +25,8 @@ Belgian Payroll Rules. 'data/l10n_be_hr_payroll_data.xml', 'views/res_config_settings_views.xml', ], + 'demo':[ + 'data/l10n_be_hr_payroll_demo.xml' + ], 'auto_install': True, } diff --git a/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_data.xml b/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_data.xml index 1a2a4f4079e..c1ddd9cb4b1 100644 --- a/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_data.xml +++ b/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_data.xml @@ -511,7 +511,7 @@ result = -min(ip_amount * tax_rate, 11745) ref('hr_salary_rule_ip_part'), ref('hr_salary_rule_gross_with_ip') ])]" name="rule_ids"/> - <field name="company_id" ref="base.main_company"/> + <field name="country_id" ref="base.be"/> <field name="parent_id" ref="hr_payroll.structure_base"/> </record> <record id="hr_payroll_salary_structure_worker" model="hr.payroll.structure"> @@ -529,7 +529,7 @@ result = -min(ip_amount * tax_rate, 11745) ref('hr_payroll_rules_suppl_net'), ref('hr_payroll_rules_retained_net'), ])]" name="rule_ids"/> - <field name="company_id" ref="base.main_company"/> + <field name="country_id" ref="base.be"/> <field name="parent_id" ref="hr_payroll.structure_base"/> </record> diff --git a/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_demo.xml b/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_demo.xml new file mode 100644 index 00000000000..02044439e56 --- /dev/null +++ b/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_demo.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record id="res_company_be" model="res.company"> + <field name="name">My Belgian Company</field> + <field name="country_id" ref="base.be"/> + <field name="currency_id" ref="base.EUR"/> + </record> + <record id="base.user_admin" model="res.users"> + <field name="company_ids" eval="[(4, ref('l10n_be_hr_payroll.res_company_be'))]"/> + </record> + <record id="base.user_demo" model="res.users"> + <field name="company_ids" eval="[(4, ref('l10n_be_hr_payroll.res_company_be'))]"/> + </record> +</odoo> diff --git a/addons/l10n_be_hr_payroll_fleet/models/hr_contract.py b/addons/l10n_be_hr_payroll_fleet/models/hr_contract.py index 70e04f0706b..a680b2f1cfe 100644 --- a/addons/l10n_be_hr_payroll_fleet/models/hr_contract.py +++ b/addons/l10n_be_hr_payroll_fleet/models/hr_contract.py @@ -11,8 +11,8 @@ class HrContract(models.Model): default=lambda self: self.env['fleet.vehicle'].search([('driver_id', '=', self.employee_id.address_home_id.id)], limit=1), tracking=True, help="Employee's company car.") - car_atn = fields.Float(compute='_compute_car_atn_and_costs', string='ATN Company Car', store=True) - company_car_total_depreciated_cost = fields.Float(compute='_compute_car_atn_and_costs', store=True) + car_atn = fields.Float(compute='_compute_car_atn_and_costs', string='ATN Company Car', store=True, compute_sudo=True) + company_car_total_depreciated_cost = fields.Float(compute='_compute_car_atn_and_costs', store=True, compute_sudo=True) available_cars_amount = fields.Integer(compute='_compute_available_cars_amount', string='Number of available cars') new_car = fields.Boolean('Request a new car') # YTI: Check if could be removed diff --git a/addons/l10n_fr_hr_payroll/data/l10n_fr_hr_payroll_data.xml b/addons/l10n_fr_hr_payroll/data/l10n_fr_hr_payroll_data.xml index 0ec114aff8a..f1aa76c7622 100644 --- a/addons/l10n_fr_hr_payroll/data/l10n_fr_hr_payroll_data.xml +++ b/addons/l10n_fr_hr_payroll/data/l10n_fr_hr_payroll_data.xml @@ -1231,7 +1231,7 @@ <field name="code">Fr-Base</field> <field name="name">Cotisations de base Employe Francais</field> <field eval="[(6, 0, [ref('hr_payroll.hr_rule_basic'), ref('hr_payroll.hr_rule_taxable'),ref('hr_payroll.hr_rule_net'), ref('hr_rule_secu'), ref('hr_rule_tranche_a'), ref('hr_rule_tranche_b'), ref('hr_rule_tranche_c'), ref('hr_rule_tranche_1'), ref('hr_rule_tranche_2'), ref('hr_rule_total'), ref('hr_rule_total_charges_salariales'), ref('hr_rule_total_retenues'), ref('hr_rule_cumul_imposable'), ref('hr_rule_total_charges_patronales'), ref('hr_payroll_rules_C1_employe'), ref('hr_payroll_rules_C2_employe'), ref('hr_payroll_rules_C3_employe'), ref('hr_payroll_rules_C4_employe'), ref('hr_payroll_rules_C5_employe'), ref('hr_payroll_rules_C7_employe'), ref('hr_payroll_rules_C8_employe'), ref('hr_payroll_rules_C9_employe'), ref('hr_payroll_rules_C10_employe'), ref('hr_payroll_rules_C11_employe'), ref('hr_payroll_rules_C22_employe'), ref('hr_payroll_rules_C23_employe'), ref('hr_payroll_rules_C24_employe'), ref('hr_payroll_rules_C25_employe'), ref('hr_payroll_rules_C26_employe'), ref('hr_payroll_rules_C27_employe'), ref('hr_payroll_rules_C28_employe'), ref('hr_payroll_rules_C29_employe'), ref('hr_payroll_rules_C30_employe'), ref('hr_payroll_rules_C40_employe')])]" name="rule_ids"/> - <field name="company_id" ref="base.main_company"/> + <field name="country_id" ref="base.fr"/> <field name="parent_id" ref="hr_payroll.structure_base"/> </record> @@ -1240,7 +1240,7 @@ <field name="code">Fr-NC</field> <field name="name">Employe non-cadre</field> <field eval="[(6, 0, [ref('hr_payroll_rules_C12_employe'), ref('hr_payroll_rules_C13_employe'), ref('hr_payroll_rules_C14_employe'), ref('hr_payroll_rules_C15_employe')])]" name="rule_ids"/> - <field name="company_id" ref="base.main_company"/> + <field name="country_id" ref="base.fr"/> <field name="parent_id" ref="hr_payroll_salary_structure_base"/> </record> @@ -1249,7 +1249,7 @@ <field name="code">Fr-C</field> <field name="name">Employe cadre</field> <field eval="[(6, 0, [ref('hr_payroll_rules_C16_employe'), ref('hr_payroll_rules_C17_employe'), ref('hr_payroll_rules_C18_employe'), ref('hr_payroll_rules_C19_employe'), ref('hr_payroll_rules_C20_employe'), ref('hr_payroll_rules_C21_employe'), ref('hr_payroll_rules_C38_employe'), ref('hr_payroll_rules_C39_employe')])]" name="rule_ids"/> - <field name="company_id" ref="base.main_company"/> + <field name="country_id" ref="base.fr"/> <field name="parent_id" ref="hr_payroll_salary_structure_base"/> </record> </odoo> diff --git a/addons/l10n_in_hr_payroll/demo/l10n_in_hr_payroll_demo.xml b/addons/l10n_in_hr_payroll/demo/l10n_in_hr_payroll_demo.xml index 541204b1ad0..98795542586 100644 --- a/addons/l10n_in_hr_payroll/demo/l10n_in_hr_payroll_demo.xml +++ b/addons/l10n_in_hr_payroll/demo/l10n_in_hr_payroll_demo.xml @@ -6,7 +6,7 @@ <field name="code">NE</field> <field name="name">Non-Executive Employee</field> <field eval="[(6, 0, [ref('hr_salary_rule_medical'), ref('hr_salary_rule_da'), ref('hr_salary_rule_lta'), ref('hr_salary_rule_telephone'), ref('hr_salary_rule_internet'), ref('hr_payroll_rule_child1')])]" name="rule_ids"/> - <field name="company_id" ref="base.main_company"/> + <field name="country_id" ref="base.in"/> <field name="parent_id" ref="hr_payroll.structure_base"/> </record> </odoo> diff --git a/odoo/addons/test_new_api/static/tests/x2many.js b/odoo/addons/test_new_api/static/tests/x2many.js index c12669d17a2..06a804fca9e 100644 --- a/odoo/addons/test_new_api/static/tests/x2many.js +++ b/odoo/addons/test_new_api/static/tests/x2many.js @@ -59,7 +59,7 @@ odoo.define('web.test.x2many', function (require) { trigger: '[data-toggle="tab"]:contains(Participants)' }, { content: "click to add participants", - trigger: '.tab-pane:eq(2).active .o_field_x2many_list_row_add a' + trigger: '.o_form_sheet .tab-pane:eq(2).active .o_field_x2many_list_row_add a' }, { content: "select Admin", trigger: 'tr:has(td:containsExact(Mitchell Admin)) .o_list_record_selector input[type="checkbox"]' @@ -70,7 +70,7 @@ odoo.define('web.test.x2many', function (require) { }, { // save content: "save discussion", trigger: 'button.o_form_button_save', - extra_trigger: '.tab-pane:eq(2) .o_field_widget tbody tr:has(td:containsExact(Mitchell Admin))', + extra_trigger: '.o_form_sheet .tab-pane:eq(2) .o_field_widget tbody tr:has(td:containsExact(Mitchell Admin))', }, { // edit content: "edit discussion", trigger: 'button.o_form_button_edit', @@ -79,7 +79,7 @@ odoo.define('web.test.x2many', function (require) { trigger: 'a[role=tab]:first', }, { content: "create new message a", - trigger: '.tab-pane:eq(0) .o_field_x2many_list_row_add a' + trigger: '.o_form_sheet .tab-pane:eq(0) .o_field_x2many_list_row_add a' }, { content: "insert body a", trigger: '.modal-body textarea:first', @@ -330,7 +330,7 @@ odoo.define('web.test.x2many', function (require) { { content: "switch to the second form view to test one2many with editable list (toggle menu dropdown)", - trigger: 'nav .o_menu_sections .dropdown-toggle:containsExact(Discussions)', + trigger: 'nav .o_menu_sections .dropdown-toggle:containsExact(Discussions), li.o_extra_menu_items a i.fa-plus', extra_trigger: '.tab-pane:eq(0) .o_field_widget tbody .o_data_row:eq(2)', }, { content: "switch to the second form view to test one2many with editable list (open submenu)", @@ -447,7 +447,7 @@ odoo.define('web.test.x2many', function (require) { { content: "switch to the third form view to test onchange changing one2many (toggle menu dropdown)", - trigger: 'nav .o_menu_sections .dropdown-toggle:containsExact(Discussions)', + trigger: 'nav .o_menu_sections .dropdown-toggle:containsExact(Discussions), li.o_extra_menu_items a i.fa-plus', extra_trigger: '.tab-pane:eq(0) .o_field_widget tbody .o_data_row:eq(2)', }, { content: "switch to the thied form view to test onchange changing one2many (open submenu)", |
