添加功能
This commit is contained in:
@@ -395,7 +395,7 @@
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page string="管理员修改字段" groups="base.group_system">
|
||||
<page string="人事修改字段" groups="hr_payroll.group_hr_payroll_manager">
|
||||
<group col="2">
|
||||
<group>
|
||||
<field name="previous_year"/>
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
<field name="code" invisible="1"/>
|
||||
<field name="work_end" invisible="1"/>
|
||||
<field name="rental_area" invisible="1"/>
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
'views/yuthon_individual_tax_views.xml',
|
||||
'views/yuthon_salary_asset_views.xml',
|
||||
'views/salary_payroll_distribution_viwes.xml',
|
||||
'views/salary_field_config_views.xml',
|
||||
'views/yuthon_salary_project_views.xml',
|
||||
'views/yuthon_payroll_labor_views.xml',
|
||||
'views/yuthon_salary_documents_views.xml',
|
||||
|
||||
@@ -17,4 +17,5 @@ from . import inherit_hr_payslip_line
|
||||
from . import yuthon_salary_party
|
||||
from . import yuthon_individual_tax
|
||||
from . import yuthon_meal_expenses
|
||||
from . import yuthon_salary_active
|
||||
from . import yuthon_salary_active
|
||||
from . import salary_field_config
|
||||
@@ -0,0 +1,131 @@
|
||||
from odoo import models, fields, _
|
||||
|
||||
|
||||
class SalaryFieldConfig(models.TransientModel):
|
||||
_name = 'salary.field.config'
|
||||
_description = '薪资自定义字段配置'
|
||||
|
||||
_dynamic_field_model = 'yuthon.salary.active'
|
||||
_dynamic_field_config = 'salary.field.config'
|
||||
_dynamic_field_prefix = 'x_dynamic_'
|
||||
|
||||
def _get_target_model_fields(self):
|
||||
fields = self.env['ir.model.fields'].search([
|
||||
('model', '=', self._dynamic_field_model),
|
||||
('ttype', 'not in', ['many2many', 'one2many', 'reference'])
|
||||
])
|
||||
return [(f.name, f.field_description) for f in fields]
|
||||
field_type = fields.Selection([
|
||||
('char', '字符串'),
|
||||
('float', '数值(浮点)'),
|
||||
], string='字段类型', default='float', required=True)
|
||||
|
||||
name = fields.Char(string='字段名称', required=True)
|
||||
symbol = fields.Char(string='字段标识', index=True)
|
||||
active = fields.Boolean(string='激活', default=True)
|
||||
|
||||
_sql_constraints = [
|
||||
('symbol_uniq', 'unique(symbol)', '字段标识必须唯一'),
|
||||
]
|
||||
|
||||
def action_sync(self):
|
||||
config_fields = self.env[self._dynamic_field_config].search([('active', 'in', [True, False])])
|
||||
model_id = self.env['ir.model'].search([('model', '=', self._dynamic_field_model)])
|
||||
|
||||
if not config_fields or not model_id:
|
||||
return
|
||||
IrModelFields = self.env['ir.model.fields']
|
||||
dynamic_fields = IrModelFields.search(
|
||||
[('model', '=', self._dynamic_field_model), ('name', 'like', self._dynamic_field_prefix)]
|
||||
)
|
||||
|
||||
def create_dynamic_fields(new_config_fields):
|
||||
new_fields = []
|
||||
for field in new_config_fields:
|
||||
new_fields.append({
|
||||
'name': f'{self._dynamic_field_prefix}{field.id}',
|
||||
'field_description': field.name,
|
||||
'model': self._dynamic_field_model,
|
||||
'model_id': model_id.id,
|
||||
'ttype': field.field_type,
|
||||
})
|
||||
IrModelFields.create(new_fields)
|
||||
for field in new_config_fields:
|
||||
field.symbol = f'{self._dynamic_field_prefix}{field.id}'
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': _('同步字段'),
|
||||
'message': _(f'成功同步 {len(new_fields)} 个薪资自定义字段。'),
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
'next': {'type': 'ir.actions.client', 'tag': 'reload'},
|
||||
},
|
||||
}
|
||||
|
||||
def update_dynamic_fields(updated_config_fields):
|
||||
updated_count = 0
|
||||
for field in updated_config_fields:
|
||||
if field.symbol:
|
||||
ir_field = IrModelFields.search([
|
||||
('model', '=', self._dynamic_field_model),
|
||||
('name', '=', field.symbol)
|
||||
])
|
||||
if ir_field:
|
||||
ir_field.write({
|
||||
'field_description': field.name,
|
||||
'ttype': field.field_type,
|
||||
})
|
||||
updated_count += 1
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': _('同步字段'),
|
||||
'message': _(f'成功更新 {updated_count} 个薪资自定义字段。'),
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
'next': {'type': 'ir.actions.client', 'tag': 'reload'},
|
||||
},
|
||||
}
|
||||
|
||||
exist_dynamic_names = dynamic_fields.mapped('name')
|
||||
exist_ids = [int(e.replace(self._dynamic_field_prefix, '')) for e in exist_dynamic_names if
|
||||
e.replace(self._dynamic_field_prefix, '').isdigit()]
|
||||
|
||||
# 处理新字段
|
||||
new_config_ids = [field.id for field in config_fields if field.id not in exist_ids]
|
||||
if new_config_ids:
|
||||
create_dynamic_fields(self.search([('id', 'in', new_config_ids)]))
|
||||
|
||||
# 处理更新字段
|
||||
updated_config_ids = [field.id for field in config_fields if field.id in exist_ids]
|
||||
if updated_config_ids:
|
||||
update_dynamic_fields(self.search([('id', 'in', updated_config_ids)]))
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': _('同步完成'),
|
||||
'message': _('所有薪资自定义字段已同步完成!'),
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
'next': {'type': 'ir.actions.client', 'tag': 'reload'},
|
||||
},
|
||||
}
|
||||
|
||||
def unlink(self):
|
||||
IrModelFields = self.env['ir.model.fields']
|
||||
for record in self:
|
||||
if record.symbol:
|
||||
ir_field = IrModelFields.search([
|
||||
('model', '=', self._dynamic_field_model),
|
||||
('name', '=', record.symbol)
|
||||
])
|
||||
if ir_field:
|
||||
ir_field.unlink()
|
||||
return super().unlink()
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from lxml import etree
|
||||
from odoo import models, fields, api, _, SUPERUSER_ID
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
from odoo.exceptions import ValidationError
|
||||
@@ -93,6 +94,26 @@ class YuthonSalaryActive(models.Model):
|
||||
enterprise_promotion_bonus_2025 = fields.Float(string='2025年企业宣传奖励')
|
||||
insurance = fields.Float(string='保险', compute='_compute_insurance')
|
||||
|
||||
@property
|
||||
def _dynamic_fields(self):
|
||||
active_fields = (
|
||||
self.env['salary.field.config']
|
||||
.search([('active', '=', True), ('symbol', '!=', False)])
|
||||
.mapped('symbol')
|
||||
)
|
||||
return active_fields
|
||||
|
||||
@api.model
|
||||
def get_views(self, views, options=None):
|
||||
res = super().get_views(views, options)
|
||||
if list_view := res['views'].get('list'):
|
||||
arch = etree.fromstring(list_view['arch'])
|
||||
tree_el = arch.xpath('//tree')[0]
|
||||
for field in self._dynamic_fields:
|
||||
tree_el.append(etree.Element('field', name=field))
|
||||
list_view['arch'] = etree.tostring(arch, encoding='unicode')
|
||||
return res
|
||||
|
||||
@api.depends("basic_endowment_insurance", "basic_medical_insurance", "unemployment_insurance",)
|
||||
def _compute_insurance(self):
|
||||
for rec in self:
|
||||
|
||||
@@ -52,3 +52,4 @@ access_salary_active_wizard,access__salary_active_wizard,model_salary_active_wiz
|
||||
access_salary_active_line,access_salary_active_line,model_salary_active_line,base.group_user,1,1,1,1
|
||||
access_meal_expenses_review_wizard,access_meal_expenses_review_wizard,model_meal_expenses_review_wizard,base.group_user,1,1,1,1
|
||||
access_party_expenses_review_wizard,access_party_expenses_review_wizard,model_party_expenses_review_wizard,base.group_user,1,1,1,1
|
||||
access_salary_field_config,access_salary_field_config,model_salary_field_config,base.group_user,1,1,1,1
|
||||
|
||||
|
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="salary_field_config_view_tree" model="ir.ui.view">
|
||||
<field name="name">salary.field.config.view.tree</field>
|
||||
<field name="model">salary.field.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree editable="bottom">
|
||||
<header>
|
||||
<button name="action_sync" string="同步字段" type="object" class="btn btn-danger" display="always"/>
|
||||
</header>
|
||||
<field name="name" readonly="symbol != False" string="字段名称"/>
|
||||
<field name="field_type" string="字段类型"/>
|
||||
<field name="symbol" readonly="1" force_save="1" string="字段标识" />
|
||||
<field name="active" string="激活" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="salary_field_config_view_search" model="ir.ui.view">
|
||||
<field name="name">salary.field.config.view.search</field>
|
||||
<field name="model">salary.field.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name" string="字段名称" />
|
||||
<field name="field_type" string="字段类型"/>
|
||||
<field name="symbol" string="字段标识" />
|
||||
<field name="active" string="激活" />
|
||||
<filter name="filter_all" string="全部" domain="[('active', 'in', [True, False])]"/>
|
||||
<filter name="filter_symbol" string="有标识" domain="[('symbol', '!=', False)]" />
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="salary_field_config_action" model="ir.actions.act_window">
|
||||
<field name="name">薪资自定义字段配置</field>
|
||||
<field name="res_model">salary.field.config</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="search_view_id" ref="salary_field_config_view_search"/>
|
||||
<field name="context">{'search_default_filter_all': 1}</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -404,6 +404,11 @@
|
||||
parent="human_resources_salary_root_board"
|
||||
action="yuthon_salary.yuthon_salary_active_action"
|
||||
sequence="99"/>
|
||||
<menuitem id="human_salary_field_config"
|
||||
name="动态字段"
|
||||
parent="human_resources_salary_root_board"
|
||||
action="yuthon_salary.salary_field_config_action"
|
||||
sequence="100"/>
|
||||
<menuitem id="human_pay_slips_root_board_jtbb"
|
||||
name="员工薪资"
|
||||
groups="yuthon_salary.group_jtbb_user"
|
||||
|
||||
Reference in New Issue
Block a user