添加功能

This commit is contained in:
李鹏宇
2026-04-27 17:49:28 +08:00
parent be350aeae4
commit 411a828e3f
9 changed files with 205 additions and 2 deletions
@@ -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"/>
+1
View File
@@ -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',
+2 -1
View File
@@ -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
+131
View File
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
52
53
54
55
@@ -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>
+5
View File
@@ -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"