From 6eb570456fa2eded2bb8a810296da2544f4a6dab Mon Sep 17 00:00:00 2001 From: zhou <123@> Date: Thu, 24 Jul 2025 20:37:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_property/models/yuthon_property.py | 2 +- yuthon_salary/__manifest__.py | 1 + yuthon_salary/data/rank_log_time.xml | 18 ++++ .../models/inherit_hr_payroll_structure.py | 85 +++++++++++++++++++ yuthon_salary/security/ir.model.access.csv | 4 +- .../inherit_hr_payroll_structure_views.xml | 36 +++++++- yuthon_salary/wizard/__init__.py | 3 +- yuthon_salary/wizard/rank_log_wizard.py | 44 ++++++++++ .../wizard/rank_log_wizard_views.xml | 34 ++++++++ 9 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 yuthon_salary/data/rank_log_time.xml create mode 100644 yuthon_salary/wizard/rank_log_wizard.py create mode 100644 yuthon_salary/wizard/rank_log_wizard_views.xml diff --git a/yuthon_property/models/yuthon_property.py b/yuthon_property/models/yuthon_property.py index dcfd211a2..1d4d2593b 100644 --- a/yuthon_property/models/yuthon_property.py +++ b/yuthon_property/models/yuthon_property.py @@ -157,7 +157,7 @@ class YuthonProperty(models.Model): 'admin_id': latest_info.admin_id, 'company_id': latest_info.company_id, }) - contract_line_ids = self.env['property.lease.contract.line'].search([('address_id', '=', rec.id)]) + contract_line_ids = self.env['property.lease.contract.line'].search([('address_id', F'=', rec.id)]) for contract_line_id in contract_line_ids: contract_line_id.admin_line_id = latest_info.admin_id diff --git a/yuthon_salary/__manifest__.py b/yuthon_salary/__manifest__.py index bc3f09971..2355f759f 100644 --- a/yuthon_salary/__manifest__.py +++ b/yuthon_salary/__manifest__.py @@ -29,6 +29,7 @@ 'views/yuthon_welfare_expenses_views.xml', 'views/yuthon_payroll_retired_views.xml', 'views/yuthon_salary_year_views.xml', + 'wizard/rank_log_wizard_views.xml', 'wizard/yuthon_salary_wizard_views.xml', 'wizard/temporary_payslip_wizard_views.xml', 'wizard/yuthon_payroll_wizard_views.xml', diff --git a/yuthon_salary/data/rank_log_time.xml b/yuthon_salary/data/rank_log_time.xml new file mode 100644 index 000000000..1b768745e --- /dev/null +++ b/yuthon_salary/data/rank_log_time.xml @@ -0,0 +1,18 @@ + + + + + 定时任务:更新职级日志 + + code + model.rank_log_time() + + + 1 + months + + -1 + + + + \ No newline at end of file diff --git a/yuthon_salary/models/inherit_hr_payroll_structure.py b/yuthon_salary/models/inherit_hr_payroll_structure.py index e666493c2..0b91bb85c 100644 --- a/yuthon_salary/models/inherit_hr_payroll_structure.py +++ b/yuthon_salary/models/inherit_hr_payroll_structure.py @@ -1,6 +1,8 @@ from odoo import api, fields, models, _ import re from pypinyin import pinyin, Style +from datetime import date + class HrPayrollStructure(models.Model): @@ -25,6 +27,7 @@ class HrPayrollStructure(models.Model): position_subsidy = fields.Monetary(string="岗位补贴", currency_field="currency_id") integrated_subsidy = fields.Monetary(string="综合补贴", currency_field="currency_id") advance_performance_bonuses = fields.Monetary(string="预发绩效奖金", currency_field="currency_id") + renk_log_ids = fields.One2many("rank.log.line", "structure_id", string="职级日志明细行") @api.model def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True): @@ -105,6 +108,19 @@ class HrPayrollStructure(models.Model): "view_mode": "form", } + def write(self, vals): + result = super(HrPayrollStructure, self).write(vals) + field_mapping = { + 'base_salary': '基本工资', + 'rank_salary': '职级工资', + 'communication_subsidy': '通讯补贴', + 'position_salary': '岗位工资' + } + for field, name in field_mapping.items(): + if field in vals: + self._handle_salary_rule(name, self[field]) + return result + def action_yuthon_payroll_wizard(self): return { "type": "ir.actions.act_window", @@ -184,3 +200,72 @@ class HrPayrollStructure(models.Model): for rule in payroll.rule_ids: if rule.name == '工会会费': rule.write({'category_id': category_id.id}) + + def rank_log_wizard(self): + return { + "type": "ir.actions.act_window", + "name": "职级日志", + "res_model": "rank.log.wizard", + "view_mode": "form", + "target": "new", + } + + def update_rank(self): + payroll_ids = self.env["hr.payroll.structure"].browse( + self._context.get('active_ids', self._context.get('active_id'))) + for payroll in payroll_ids: + log_data = { + 'structure_id': payroll.id, + 'base_salary': payroll.base_salary, + 'rank_salary': payroll.rank_salary, + 'communication_subsidy': payroll.communication_subsidy, + 'position_salary': payroll.position_salary, + 'advance_performance_bonuses': payroll.advance_performance_bonuses, + 'effective_date': payroll.create_time, + 'lapse_date': payroll.end_time, + 'is_enable': payroll.is_enable + } + self.env['rank.log.line'].create(log_data) + + def empty_the_log(self): + payroll_ids = self.env["hr.payroll.structure"].browse( + self._context.get('active_ids', self._context.get('active_id'))) + for payroll in payroll_ids: + payroll.renk_log_ids.unlink() + + def rank_log_time(self): + """定时任务:根据日志记录的生效时间更新主模型字段值""" + today = date.today() + for structure in self.search([]): + # 获取该职级下所有有效的日志记录 + valid_logs = structure.renk_log_ids.filtered( + lambda log: log.is_enable and + log.effective_date and + log.effective_date <= today and + (not log.lapse_date or log.lapse_date >= today) + ).sorted(key=lambda l: l.effective_date, reverse=True) + if valid_logs: + latest_log = valid_logs[0] + structure.write({ + 'base_salary': latest_log.base_salary, + 'rank_salary': latest_log.rank_salary, + 'communication_subsidy': latest_log.communication_subsidy, + 'position_salary': latest_log.position_salary, + 'advance_performance_bonuses': latest_log.advance_performance_bonuses, + }) + +class RankLogLine(models.Model): + _name = 'rank.log.line' + _description = '职级日志' + + structure_id = fields.Many2one('hr.payroll.structure', string='关联职级') + base_salary = fields.Float(string="基本工资") + rank_salary = fields.Float(string="职级工资") + communication_subsidy = fields.Float(string="通讯补贴") + position_salary = fields.Float(string="岗位工资") + advance_performance_bonuses = fields.Float(string="预发绩效奖金") + effective_date = fields.Date(string="生效日期") + lapse_date = fields.Date(string="失效日期") + is_enable = fields.Boolean(string="启用") + + diff --git a/yuthon_salary/security/ir.model.access.csv b/yuthon_salary/security/ir.model.access.csv index 0850fb46f..c0626e8c9 100644 --- a/yuthon_salary/security/ir.model.access.csv +++ b/yuthon_salary/security/ir.model.access.csv @@ -31,4 +31,6 @@ access_yuthon_salary_project, access_yuthon_salary_project,model_yuthon_salary_p access_yuthon_salary_asset, access_yuthon_salary_asset,model_yuthon_salary_asset,base.group_user,1,1,1,1 access_salary_payroll_distribution,access_salary_payroll_distribution,model_salary_payroll_distribution,base.group_user,1,1,1,1 access_yuthon_welfare_expenses,access_yuthon_welfare_expenses,model_yuthon_welfare_expenses,base.group_user,1,1,1,1 -access_initialize_payroll_wizard,access_initialize_payroll_wizard,model_initialize_payroll_wizard,base.group_user,1,1,1,1 \ No newline at end of file +access_initialize_payroll_wizard,access_initialize_payroll_wizard,model_initialize_payroll_wizard,base.group_user,1,1,1,1 +access_rank_log_line,access_rank_log_line,model_rank_log_line,base.group_user,1,1,1,1 +access_rank_log_wizard,access_rank_log_wizard,model_rank_log_wizard,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/yuthon_salary/views/inherit_hr_payroll_structure_views.xml b/yuthon_salary/views/inherit_hr_payroll_structure_views.xml index ee0699d28..2a420f82a 100644 --- a/yuthon_salary/views/inherit_hr_payroll_structure_views.xml +++ b/yuthon_salary/views/inherit_hr_payroll_structure_views.xml @@ -76,6 +76,22 @@ + + + + + + + + + + + + + + + + @@ -125,6 +141,13 @@ code action = model.action_select_delete_wizard() + + + 职级日志导入 + + + code + action = model.rank_log_wizard() 删除工资项 @@ -133,11 +156,18 @@ code action = model.delete_structure() - - 一次性 + + 更新日志 code - action = model.update_payroll_items() + action = model.update_rank() + + + 清空职级日志 + + + code + action = model.empty_the_log() \ No newline at end of file diff --git a/yuthon_salary/wizard/__init__.py b/yuthon_salary/wizard/__init__.py index 4a56efcf8..db9888a04 100644 --- a/yuthon_salary/wizard/__init__.py +++ b/yuthon_salary/wizard/__init__.py @@ -10,4 +10,5 @@ from . import payroll_retired_wizard from . import salary_print_wizard from . import salary_year_wizard from . import payslip_send_wizard -from . import initialize_payroll_wizard \ No newline at end of file +from . import initialize_payroll_wizard +from . import rank_log_wizard \ No newline at end of file diff --git a/yuthon_salary/wizard/rank_log_wizard.py b/yuthon_salary/wizard/rank_log_wizard.py new file mode 100644 index 000000000..004a4c8fa --- /dev/null +++ b/yuthon_salary/wizard/rank_log_wizard.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +from odoo import models, fields, api, _, SUPERUSER_ID +from odoo.exceptions import ValidationError, UserError +from pypinyin import pinyin, Style +import xlrd +import base64 +import datetime +from datetime import datetime + + +class RankLogWizard(models.Model): + _name = 'rank.log.wizard' + _description = '职级日志导入弹窗' + + excel_file = fields.Binary(string="上传Excel表") + excel_name = fields.Char(string="文件名") + + def confirm(self): + if not self.excel_file: + raise ValidationError('请上传文件。') + book = xlrd.open_workbook(file_contents=base64.decodebytes(self.excel_file)) + sh = book.sheet_by_index(0) + for rx in range(1, sh.nrows): + row = sh.row(rx) + self.payroll_rank_log(row) + + def payroll_rank_log(self, row): + structure_id = self.env['hr.payroll.structure'].search([('name', '=', row[1].value)]) + base_salary = row[2].value + rank_salary = row[3].value + advance_performance_bonuses = row[4].value + communication_subsidy = row[5].value + position_salary = row[6].value + log_data = { + 'structure_id': structure_id.id, + 'base_salary': float(base_salary) if base_salary else 0.0, + 'rank_salary': float(rank_salary) if rank_salary else 0.0, + 'advance_performance_bonuses': float(advance_performance_bonuses) if advance_performance_bonuses else 0.0, + 'communication_subsidy': float(communication_subsidy) if communication_subsidy else 0.0, + 'position_salary': float(position_salary) if position_salary else 0.0, + 'effective_date': fields.Date.today(), + 'is_enable': True + } + self.env['rank.log.line'].create(log_data) diff --git a/yuthon_salary/wizard/rank_log_wizard_views.xml b/yuthon_salary/wizard/rank_log_wizard_views.xml new file mode 100644 index 000000000..e4ed074f8 --- /dev/null +++ b/yuthon_salary/wizard/rank_log_wizard_views.xml @@ -0,0 +1,34 @@ + + + + 职级日志模板 + self + . + + + 审批意见弹窗 + rank.log.wizard + +
+ +
+
+ + + + + + + + +
+ +
+
+
+
\ No newline at end of file