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