添加功能

This commit is contained in:
zhou
2025-07-13 20:38:48 +08:00
parent e420fe6e1e
commit da517f81bb
8 changed files with 206 additions and 41 deletions
@@ -38,7 +38,6 @@
</xpath>
<xpath expr="//field[@name='contract_type_id']" position="after">
<field name="termination_date"/>
<field name="is_probation"/>
<field name="salary_day" invisible="not is_internship"/>
<field name="salary_group_id" options="{'no_create': True}"/>
<field name="is_internship" invisible="1"/>
+2 -1
View File
@@ -12,4 +12,5 @@ from . import yuthon_salary_project
from . import yuthon_salary_asset
from . import salary_payroll_distribution
from . import yuthon_welfare_expenses
from . import inherit_hr_payslip_run
from . import inherit_hr_payslip_run
from . import inherit_hr_payslip_line
@@ -2,6 +2,9 @@ from odoo import api, fields, models, _
from odoo.exceptions import ValidationError, UserError
import json
from odoo.tools import date_utils
from dateutil.relativedelta import relativedelta
import re
from datetime import datetime
import logging
_logger = logging.getLogger(__name__)
@@ -144,6 +147,90 @@ class InheritHrPayslip(models.Model):
base_salary_deduction = fields.Float(string='扣发基本工资', compute='_compute_deduction', store=True)
specialized_deduction = fields.Float(string='累计专项扣除')
@api.onchange('payslip_run_id')
def _onchange_payslip_run_id(self):
if not self.payslip_run_id:
self.date_from = False
self.date_to = False
return
batch_name = self.payslip_run_id.name
pattern = r'(\d{4})年-([一二三四五六七八九十]{1,2})月'
match = re.search(pattern, batch_name)
if match:
year = int(match.group(1))
month_name = match.group(2)
month_mapping = {
'一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6,
'七': 7, '八': 8, '九': 9, '十': 10, '十一': 11, '十二': 12
}
month = month_mapping.get(month_name)
if month:
self.date_from = datetime(year, month, 1).date()
self.date_to = (datetime(year, month, 1) + relativedelta(months=1, days=-1)).date()
@api.model_create_multi
def create(self, vals_list):
# 存储每个工资单的检查结果
existing_payslips = {}
for vals in vals_list:
employee_id = vals.get('employee_id')
payslip_run_id = vals.get('payslip_run_id')
if not employee_id or not payslip_run_id:
continue
# 检查工资单是否存在于各个状态
conditions = [
(_('薪资审定'), [
('employee_id', '=', employee_id),
('payslip_run_id', '=', payslip_run_id),
('is_salary_push', '=', True),
('is_salary_release', '=', False)
]),
(_('薪资归档'), [
('employee_id', '=', employee_id),
('payslip_run_id', '=', payslip_run_id),
('is_salary_push', '=', True),
('is_salary_release', '=', True)
]),
(_('薪资计算'), [
('employee_id', '=', employee_id),
('payslip_run_id', '=', payslip_run_id),
('is_push', '=', False)
]),
(_('个税计算'), [
('employee_id', '=', employee_id),
('payslip_run_id', '=', payslip_run_id),
('is_push', '=', True),
('is_salary_push', '=', False)
])
]
for state_name, domain in conditions:
existing = self.search(domain, limit=1)
if existing:
# 获取员工和批次名称用于错误提示
employee = self.env['hr.employee'].browse(employee_id)
payslip_run = self.env['hr.payslip.run'].browse(payslip_run_id)
existing_payslips[(employee_id, payslip_run_id)] = {
'employee_name': employee.name,
'batch_name': payslip_run.name,
'state': state_name
}
break
if existing_payslips:
error_messages = []
for key, info in existing_payslips.items():
msg = _("员工 %(employee)s 在批次 '%(batch)s' 的工资单已存在于'%(state)s'中!") % {
'employee': info['employee_name'],
'batch': info['batch_name'],
'state': info['state']
}
error_messages.append(msg)
raise UserError('\n'.join(error_messages))
res = super(InheritHrPayslip, self).create(vals_list)
return res
@api.ondelete(at_uninstall=False)
def _unlink_if_draft_or_cancel(self):
if any(payslip.state not in ('draft', 'cancel') for payslip in self):
@@ -0,0 +1,20 @@
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError, UserError
import json
from odoo.tools import date_utils
import logging
class InheritHrPayslipLine(models.Model):
_inherit = 'hr.payslip.line'
is_amount = fields.Boolean(string='金额是否一至', default=False)
@api.onchange('total')
def _onchange_total(self):
if self.total and self.amount:
if self.total != self.amount:
self.is_amount = True
else:
self.is_amount = False
@@ -96,7 +96,7 @@ class YuthonPayrollCommon(models.Model):
if ins.name == '环境作业津贴':
formula_salary = "result = 0"
if ins.name == '春节福利' and ins.company_id.name == '天河物管':
formula_salary = "result = 680 if employee.department_id.name != '领导班子' else 0"
formula_salary = "result = 680 if employee.salary_month == '1' and employee.department_id.name != '领导班子' else 0"
if ins.name == '质检员津贴':
formula_salary = "result = employee.quality_inspector_amount"
if ins.name == '工会会费' and ins.company_id.name in ['集团本部', '天河物管']:
@@ -128,10 +128,24 @@
</xpath>
<xpath expr="//page[@name='account_info']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//page[@name='salary_computation']/field[@name='line_ids']/tree" position="inside">
<field name="is_amount" column_invisible="1"/>
</xpath>
<xpath expr="//page[@name='salary_computation']/field[@name='line_ids']/tree" position="attributes">
<attribute name="decoration-danger">is_amount</attribute>
<attribute name="detault_order">category_id</attribute>
</xpath>
<xpath expr="//field[@name='total']" position="attributes">
<attribute name="readonly">0</attribute>
</xpath>
<xpath expr="//field[@name='payslip_run_id']" position="attributes">
<attribute name="string">工资批次</attribute>
</xpath>
<xpath expr="//field[@name='payslip_run_id']" position="replace"/>
<xpath expr="//label[@for='date_from']" position="before">
<field name="payslip_run_id" string="工资批次"/>
</xpath>
<xpath expr="//field[@name='quantity']" position="attributes">
<attribute name="column_invisible">1</attribute>
</xpath>
@@ -9,9 +9,9 @@
<group>
<field name="payslip_ids" widget="many2many_tags" invisible="1"/>
<field name="company_id" invisible="1"/>
<field name="payroll_common_ids" widget="many2many_tags" domain="[('company_id', '=', company_id)]"/>
<field name="payroll_temporary_ids" widget="many2many_tags" domain="[('company_id', '=', company_id)]"/>
<field name="payroll_insurances_ids" widget="many2many_tags" domain="[('company_id', '=', company_id)]"/>
<field name="payroll_common_ids" widget="many2many_tags" domain="[('company_id', '=', company_id)]" options="{'no_open': True, 'no_create': True}"/>
<field name="payroll_temporary_ids" widget="many2many_tags" domain="[('company_id', '=', company_id)]" options="{'no_open': True, 'no_create': True}"/>
<field name="payroll_insurances_ids" widget="many2many_tags" domain="[('company_id', '=', company_id)]" options="{'no_open': True, 'no_create': True}"/>
</group>
</sheet>
<footer>
+79 -35
View File
@@ -96,17 +96,9 @@ class SalaryComputeWizard(models.Model):
'state': 'draft',
'payslip_count': 0
})
# existing_payslip = self.env['hr.payslip'].search([
# ('is_push', '=', False),
# ('employee_id', '=', employee.id),
# ('date_from', '<=', end_date),
# ('date_to', '>=', start_date)
# ])
employee.salary_date = start_date
employee.salary_month = self.select_time
employee.salary_year_jin_ids = [(6, 0, self.employee_ids.ids)]
# if existing_payslip:
# raise UserError(f"员工 {employee.name} 在 {start_date} 到 {end_date} 日期范围内已有工资单记录,无法创建新的工资单。")
# 查找有效合同:date_start <= start_date <= date_end(或date_end为空)
contract_id = self.env['hr.contract'].search([
@@ -119,23 +111,9 @@ class SalaryComputeWizard(models.Model):
existing_payslip = self.env['hr.payslip'].search([
('employee_id', '=', employee.id),
('payslip_run_id', '=', payslip_run_id.id),
('is_push', '=', False)
], limit=1)
if existing_payslip:
# 如果找到记录,则更新现有记录
existing_payslip.write({
'is_bold': is_bold,
'name': employee.name,
'contract_id': contract_id.id or employee.contract_id.id,
'date_from': start_date,
'date_to': end_date,
'salary_group_id': contract_id.salary_group_id.id if contract_id else employee.contract_id.salary_group_id.id,
'state': 'draft',
})
existing_payslip.compute_sheet()
else:
# 如果未找到记录,则创建新工资单
if not existing_payslip:
self.env['hr.payslip'].create({
'is_bold': is_bold,
'name': employee.name,
@@ -147,19 +125,64 @@ class SalaryComputeWizard(models.Model):
'payslip_run_id': payslip_run_id.id,
'state': 'draft',
}).compute_sheet()
internship_ids = self.env['hr.payslip'].search([
('employee_id.contract_id.is_probation', '=', True),
('is_push', '=', False),
('company_id', '=', self.company_id.id),
('date_from', '<=', end_date),
('date_to', '>=', start_date),
])
if internship_ids:
for internship_id in internship_ids:
])
for internship_id in internship_ids:
employee = internship_id.employee_id
contract = employee.contract_id
# 获取离职日期
turnover_date = employee.contract_id.date_end
start_date = fields.Date.from_string(start_date)
end_date = fields.Date.from_string(end_date)
# 判断员工是否在所选月份已离职
is_turnover = False
if turnover_date:
turnover_year = turnover_date.year
turnover_month = turnover_date.month
# 如果离职年月早于或等于所选年月,则视为已离职
is_turnover = (turnover_year < year) or (turnover_year == year and turnover_month <= month)
# 判断员工是否在试用期
is_probation = False
if contract.start_date and contract.end_date:
# 检查所选月份是否在试用期内
selected_date = fields.Date.to_date(f"{year}-{month:02d}-01")
is_probation = contract.start_date <= selected_date <= contract.end_date
original_start_date = internship_id.date_from
original_end_date = internship_id.date_to
if is_probation:
contract_start_date = contract.start_date
contract_end_date = contract.end_date
elif is_turnover:
contract_start_date = internship_id.date_from
contract_end_date = is_turnover
else:
continue
if is_turnover or is_probation:
days = self._process_shift_days(original_start_date, original_end_date) # 总工作日
if is_turnover:
# 离职日期前的工作日
pre_retirement_days = self._process_shift_days(
start_date,
min(end_date, turnover_date)
)
work_days = pre_retirement_days # 离职前的工作日
elif is_probation:
original_start_date = internship_id.date_from
original_end_date = internship_id.date_to
contract_start_date = internship_id.employee_id.contract_id.start_date
contract_end_date = internship_id.employee_id.contract_id.end_date
days = self._process_shift_days(original_start_date, original_end_date) # 总工作日
# 复制原始日期用于修改
start_date = original_start_date
end_date = original_end_date
@@ -173,12 +196,14 @@ class SalaryComputeWizard(models.Model):
# 检查 end_date 是否晚于 contract_end_date
if end_date > contract_end_date:
end_date = contract_end_date
work_days = self._process_shift_days(start_date, end_date) # 转正工作日
if internship_id.line_ids:
work_days = self._process_shift_days(start_date, end_date) # 转正工作日
if internship_id.line_ids:
if is_probation:
target_names = {
'基本工资': 'base_salary',
'职级工资': 'rank_salary',
'岗位工资': 'post_salary',
'岗位工资': 'position_salary',
'预发绩效奖金': 'performance_bonuses',
'通讯补贴': 'commuting_allowance',
'工龄工资': 'seniority_salary',
@@ -236,11 +261,30 @@ class SalaryComputeWizard(models.Model):
setattr(internship_id, field_name, result)
line.write({'total': result})
for rec in self.env['hr.payslip'].search([('is_push', '=', False), ('employee_id.name', '!=', '王鸿怡')]):
rec.compute_sheet()
for rec2 in self.env['hr.payslip'].search([('employee_id.name', 'in', ['黄昱皓', '谭铭浩', '朱文浩'])]):
rec2.compute_sheet()
if is_turnover:
target_names = {
'基本工资': 'base_salary',
'岗位工资': 'position_salary',
'通讯补贴': 'commuting_allowance',
'综合补贴': 'comprehensive_subsidy',
'全勤奖': 'full_attendance_award',
}
for line in internship_id.line_ids:
if line.name in target_names:
field_name = target_names[line.name]
# 统一按公式计算:金额/21.75 * 离职前工作日数量,并保留两位小数
result = round((line.total / 21.75) * work_days, 2)
setattr(internship_id, field_name, result)
line.write({'total': result})
# for rec in self.env['hr.payslip'].search([('is_push', '=', False), ('employee_id.name', '!=', '王鸿怡')]):
# rec.compute_sheet()
#
# for rec2 in self.env['hr.payslip'].search([('employee_id.name', 'in', ['黄昱皓', '谭铭浩', '朱文浩'])]):
# rec2.compute_sheet()
def _get_chinese_month(self, month):
chinese_months = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']