From 01f4720422994f165e436924057791282f5fa44b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?=
<9664676+pengyuthon@user.noreply.gitee.com>
Date: Sun, 28 Dec 2025 21:13:10 +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_salary/__manifest__.py | 1 +
yuthon_salary/models/__init__.py | 3 +-
yuthon_salary/models/yuthon_individual_tax.py | 53 +++++++++++++++++++
yuthon_salary/security/ir.model.access.csv | 4 +-
.../security/salary_security_rule.xml | 5 ++
.../views/yuthon_individual_tax_views.xml | 28 ++++++++++
yuthon_salary/wizard/salary_import_wizard.py | 35 ++++++++++--
yuthon_work_brand/views/hr_menus.xml | 5 ++
8 files changed, 127 insertions(+), 7 deletions(-)
create mode 100644 yuthon_salary/models/yuthon_individual_tax.py
create mode 100644 yuthon_salary/views/yuthon_individual_tax_views.xml
diff --git a/yuthon_salary/__manifest__.py b/yuthon_salary/__manifest__.py
index a2511b537..9979fc76a 100644
--- a/yuthon_salary/__manifest__.py
+++ b/yuthon_salary/__manifest__.py
@@ -23,6 +23,7 @@
'views/yuthon_payroll_project_views.xml',
'views/yuthon_payroll_type_setting_views.xml',
'views/yuthon_salary_party_views.xml',
+ 'views/yuthon_individual_tax_views.xml',
'views/yuthon_salary_asset_views.xml',
'views/salary_payroll_distribution_viwes.xml',
'views/yuthon_salary_project_views.xml',
diff --git a/yuthon_salary/models/__init__.py b/yuthon_salary/models/__init__.py
index 0b050ac71..8864f9ba9 100644
--- a/yuthon_salary/models/__init__.py
+++ b/yuthon_salary/models/__init__.py
@@ -14,4 +14,5 @@ from . import salary_payroll_distribution
from . import yuthon_welfare_expenses
from . import inherit_hr_payslip_run
from . import inherit_hr_payslip_line
-from . import yuthon_salary_party
\ No newline at end of file
+from . import yuthon_salary_party
+from . import yuthon_individual_tax
\ No newline at end of file
diff --git a/yuthon_salary/models/yuthon_individual_tax.py b/yuthon_salary/models/yuthon_individual_tax.py
new file mode 100644
index 000000000..06ebc7c3d
--- /dev/null
+++ b/yuthon_salary/models/yuthon_individual_tax.py
@@ -0,0 +1,53 @@
+from email.policy import default
+
+from odoo import api, fields, models, _
+from pypinyin import pinyin, Style
+
+
+class YuthonIndividualTax(models.Model):
+ _name = 'yuthon.individual.tax'
+ _description = '个税'
+
+ name =fields.Char(string="名称")
+ company_id = fields.Many2one('res.company', string="公司", default=lambda self: self.env.company)
+ start_date = fields.Date(string="生效日期", default=fields.Date.today())
+ end_date = fields.Date(string="失效日期")
+ create_time = fields.Date(string="创建日期", default=fields.Datetime.now)
+
+ def create_salary_temporary(self):
+ """
+ 更新薪资个税
+ :return:
+ """
+ individual_ids = self.env["yuthon.individual.tax"].browse(
+ self._context.get('active_ids', self._context.get('active_id')))
+ category_id = self.env['hr.salary.rule.category'].search([('name', '=', '个税')],limit=1)
+ if not category_id:
+ category_id = self.env['hr.salary.rule.category'].create({'name': '个税',
+ 'code': 'gs'
+ })
+ for ins in individual_ids:
+ for sal in self.env["hr.payroll.structure"].search([('company_id', '=', ins.company_id.id)]):
+ rule_list = []
+ if ins.name not in sal.rule_ids.mapped('name'):
+ rule_list.append([0, 0, {
+ 'name': ins.name,
+ 'category_id': category_id.id,
+ 'code': "".join([p[0][0] for p in pinyin(ins.name, style=Style.FIRST_LETTER)]).upper(),
+ 'amount_select': 'fix',
+ 'amount_fix': 0,
+ 'struct_id': sal.id,
+ }])
+ sal.write({'rule_ids': rule_list})
+
+
+ @api.depends('start_date', 'end_date')
+ def compute_is_enable(self):
+ for rec in self:
+ if rec.start_date and rec.end_date:
+ if rec.start_date <= fields.Date.today() < rec.end_date:
+ rec.is_enable = True
+ else:
+ rec.is_enable = False
+
+
diff --git a/yuthon_salary/security/ir.model.access.csv b/yuthon_salary/security/ir.model.access.csv
index bfa5c3a25..34e5dc719 100644
--- a/yuthon_salary/security/ir.model.access.csv
+++ b/yuthon_salary/security/ir.model.access.csv
@@ -37,4 +37,6 @@ access_rank_log_wizard,access_rank_log_wizard,model_rank_log_wizard,base.group_u
access_basic_pension_wizard,access_basic_pension_wizard,model_basic_pension_wizard,base.group_user,1,1,1,1
access_yuthon_salary_party,access_yuthon_salary_party,model_yuthon_salary_party,base.group_user,1,1,1,1
access_salary_party_wizard,access_salary_party_wizard,model_salary_party_wizard,base.group_user,1,1,1,1
-access_salary_import_wizard,access_salary_import_wizard,model_salary_import_wizard,base.group_user,1,1,1,1
\ No newline at end of file
+access_salary_import_wizard,access_salary_import_wizard,model_salary_import_wizard,base.group_user,1,1,1,1
+
+access_yuthon_individual_tax,access_yuthon_individual_tax,model_yuthon_individual_tax,base.group_user,1,1,1,1
\ No newline at end of file
diff --git a/yuthon_salary/security/salary_security_rule.xml b/yuthon_salary/security/salary_security_rule.xml
index ec308053c..298668358 100644
--- a/yuthon_salary/security/salary_security_rule.xml
+++ b/yuthon_salary/security/salary_security_rule.xml
@@ -5,6 +5,11 @@
[('company_id', 'in', company_ids)]
+
+ 个税多公司
+
+ [('company_id', 'in', company_ids)]
+
薪资方法多公司
diff --git a/yuthon_salary/views/yuthon_individual_tax_views.xml b/yuthon_salary/views/yuthon_individual_tax_views.xml
new file mode 100644
index 000000000..16e4017f1
--- /dev/null
+++ b/yuthon_salary/views/yuthon_individual_tax_views.xml
@@ -0,0 +1,28 @@
+
+
+
+ yuthon.individual.tax.tree
+ yuthon.individual.tax
+
+
+
+
+
+
+
+
+
+
+
+ 个税
+ yuthon.individual.tax
+ tree
+
+
+ 更新薪资常用项
+
+
+ code
+ action = model.create_salary_temporary()
+
+
diff --git a/yuthon_salary/wizard/salary_import_wizard.py b/yuthon_salary/wizard/salary_import_wizard.py
index 3bc17bea4..e892c3f60 100644
--- a/yuthon_salary/wizard/salary_import_wizard.py
+++ b/yuthon_salary/wizard/salary_import_wizard.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from odoo import models, fields, api, _, SUPERUSER_ID
+from odoo import models, fields, api, _, SUPERUSER_ID, exceptions
from odoo.exceptions import ValidationError, UserError
from pypinyin import pinyin, Style
import xlrd
@@ -7,6 +7,7 @@ import base64
import datetime
from datetime import datetime, timedelta
+
class SalaryImportWizard(models.TransientModel):
_name = 'salary.import.wizard'
_description = '薪资导入弹窗'
@@ -22,12 +23,31 @@ class SalaryImportWizard(models.TransientModel):
sh = book.sheet_by_index(0)
header_row = sh.row(0)
excel_headers = [str(cell.value).strip() for cell in header_row if cell.value]
- invalid_headers = [header for header in excel_headers]
+ global_valid_line_names = set()
for rx in range(1, sh.nrows):
- row = sh.row(rx)
- self.salary_party_data(row, excel_headers)
+ row = sh.row(rx)
+ self.salary_party_data(row, excel_headers, global_valid_line_names)
+ invalid_headers = [header for header in excel_headers if header not in global_valid_line_names]
+ if invalid_headers:
+ invalid_headers_str = ', '.join(invalid_headers)
+ message = _(f"薪资导入完成!以下表头未匹配到任何有效薪资项目,未进行处理:\n{invalid_headers_str}")
+ notification_type = 'warning'
+ else:
+ message = _("薪资导入完成!所有表头均匹配有效薪资项目。")
+ notification_type = 'success'
- def salary_party_data(self, row, excel_headers):
+ return {
+ 'type': 'ir.actions.client',
+ 'tag': 'display_notification',
+ 'params': {
+ 'title': _("薪资导入结果提示"),
+ 'message': message,
+ 'type': notification_type,
+ 'sticky': False,
+ }
+ }
+
+ def salary_party_data(self, row, excel_headers, global_valid_line_names):
employee_name = row[0].value
employee = self.env['hr.employee'].search([('name', '=', employee_name)], limit=1)
if employee:
@@ -43,6 +63,7 @@ class SalaryImportWizard(models.TransientModel):
'name': employee.name,
'employee_id': employee.id,
'contract_id': employee.contract_id.id,
+ 'salary_group_id': employee.contract_id.salary_group_id.id,
'payslip_run_id': payslip_run_id.id,
'date_from': datetime(2025, 8, 1).date() ,
'date_to': datetime(2025, 8, 31).date(),
@@ -55,6 +76,10 @@ class SalaryImportWizard(models.TransientModel):
payslip.is_salary_push = True
payslip.is_salary_release = False
+ current_payslip_lines = payslip.line_ids.filtered(lambda l: l.name)
+ current_valid_names = {line.name.strip() for line in current_payslip_lines}
+ global_valid_line_names.update(current_valid_names)
+
for line in payslip.line_ids:
line_name = line.name
if line_name not in excel_headers:
diff --git a/yuthon_work_brand/views/hr_menus.xml b/yuthon_work_brand/views/hr_menus.xml
index 8c3be860f..7ed5061c5 100644
--- a/yuthon_work_brand/views/hr_menus.xml
+++ b/yuthon_work_brand/views/hr_menus.xml
@@ -428,6 +428,11 @@
parent="human_payroll_items_root_board"
action="yuthon_salary.view_yuthon_payroll_temporary_action"
sequence="30"/>
+