添加功能
This commit is contained in:
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
|
||||
|
||||
Reference in New Issue
Block a user