!3675 添加功能

Merge pull request !3675 from 李鹏宇/dev_zhou
This commit is contained in:
李鹏宇
2025-07-05 09:31:23 +00:00
committed by Gitee
23 changed files with 310 additions and 81 deletions
@@ -17,11 +17,6 @@ class SoongWorkflowMixin(models.AbstractModel):
_name = "soong.workflow.mixin"
_description = "Soong Workflow Mixin"
def end_approval(self):
record = self.env['soong.workflow.record'].search([('code', '=', self.code)])
if record:
record.write({'approve_state': 'end'})
def read(self, fields=None, load='_classic_read'):
if self.log_ids:
if self.log_ids[-1].done_by.id == self.env.user.id and self.log_ids[-1].approval_state == '0':
@@ -518,6 +513,7 @@ class SoongWorkflowMixin(models.AbstractModel):
@api.model
def get_view(self, view_id=None, view_type="form", **options):
res = super().get_view(view_id, view_type, **options)
active_id = self._context.get('active_id')
if '<form create="false"' not in res['arch']:
if view_type in ["tree", "form"]:
params = {}
@@ -746,6 +742,8 @@ class SoongWorkflowMixin(models.AbstractModel):
id_el.set("name", "log_ids")
id_el.set("invisible", "1")
invisible += " or not log_ids "
if button.name == "结束" :
invisible += " or True"
# invisible += " or not work_end "
btn_exec_action_el.set("invisible", invisible)
btn_exec_action_el.set("context", "{'btn_key':'%s','active_model':'%s'}" % (button.btn_key, self._name),)
@@ -22,7 +22,7 @@ class HrContract(models.Model):
salary_group_id = fields.Many2one('yuthon.salary.group', string="工资单分组", store=True, index=True)
is_internship = fields.Boolean(string="实习期", compute='_compute_name', store=True)
date_end = fields.Date(string="合同结束时间", compute='_compute_date_end', store=True)
termination_date = fields.Date(string="合同中止时间")
termination_date = fields.Date(string="中止时间")
work_entry_source = fields.Selection([
('calendar', '考勤安排'),
('attendance', 'Attendances'),
@@ -8,10 +8,16 @@
<xpath expr="//field[@name='date_end']" position="after">
<field name="date_of_entry"/>
</xpath>
<xpath expr="//field[@name='date_end']" position="before">
<field name="termination_date"/>
</xpath>
<xpath expr="//field[@name='employee_id']" position="after">
<field name="contract_type_id" string="用工形式"/>
<field name="is_company" string="是否本公司" optional="hide"/>
</xpath>
<xpath expr="//field[@name='date_end']" position="attributes">
<attribute name="string">结束日期</attribute>
</xpath>
<xpath expr="//field[@name='name']" position="attributes">
<attribute name="column_invisible">1</attribute>
</xpath>
@@ -26,18 +32,12 @@
<xpath expr="//field[@name='employee_id']" position="after">
<field name="code"/>
</xpath>
<xpath expr="//field[@name='date_end']" position="attributes">
<attribute name="readonly">0</attribute>
</xpath>
<xpath expr="//field[@name='date_end']" position="after">
<field name="termination_date"/>
</xpath>
<xpath expr="//page[@name='other']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='contract_type_id']" position="after">
<field name="is_probation"/>
<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"/>
@@ -47,13 +47,6 @@ class AppletAttendance(Controller):
current_data = fields.Date.today()
user_id = request.env(user=SUPERUSER_ID)["res.users"].search([('id', '=', int(kw['user_id']))])
employee_id = request.env(user=SUPERUSER_ID)["hr.employee"].search([('name', '=', user_id.name)])
address_id = request.env(user=SUPERUSER_ID)["applet.address"].search([('address_name', '=', employee_id.company_id.name)])
self_address = self.get_address_from_coords(kw['att_latitude'], kw['att_longitude'])
_logger.info(f"打卡经度{kw['att_longitude']}打卡纬度{kw['att_latitude']}__________")
_logger.info(f"人员{employee_id.name}地址{self_address['formatted_address']}__________")
card_rice = address_id.card_rice * 0.001 # 打卡范围
attendance_id = request.env(user=SUPERUSER_ID)["hr.attendance"].search([('employee_id', '=', employee_id.id), ('current_data', '=', current_data)])
check_in = attendance_id.check_in + timedelta(hours=8) if attendance_id.check_in else ''
check_out = attendance_id.check_out + timedelta(hours=8) if attendance_id.check_out else ''
@@ -78,25 +71,33 @@ class AppletAttendance(Controller):
check_out_str = check_out.strftime('%H:%M:%S') if check_out else ' '
check_in_html = f'<p style="color:red">{check_in_str}</p>' if check_in_status in ['迟到', '早退', '缺卡'] else f'<p style="color:green">{check_in_str}</p>'
check_out_html = f'<p style="color:red">{check_out_str}</p>' if check_out_status in ['迟到', '早退', '缺卡'] else f'<p style="color:green">{check_out_str}</p>'
if not self.is_within_radius_geopy(kw['att_latitude'], kw['att_longitude'], address_id.latitude, address_id.longitude, card_rice):
message = "超出范围,不允许打卡"
state = "no"
_logger.info("-------触发超出范围,不允许打卡---------")
else:
state = 'yes'
message = 'ok'
data = {
'location': self_address['formatted_address'],
'user_id': user_id.id,
'class': f"{attendance_id.shift_name}{start_time_str}-{end_time_str}",
'state_result': state_result,
'check_in': check_in_html,
'check_in_status': check_in_status,
'check_out': check_out_html,
'check_out_status': check_out_status,
'state': state,
},
return_data = {'data': data, 'code': '200', 'message': message, }
self_address = self.get_address_from_coords(kw['att_latitude'], kw['att_longitude'])
_logger.info(f"打卡经度{kw['att_longitude'] }打卡纬度{kw['att_latitude']}__________")
_logger.info(f"人员{employee_id.name}地址{self_address['formatted_address']}__________")
address_ids = request.env(user=SUPERUSER_ID)["applet.address"].search([('address_name', '=', employee_id.company_id.name)])
data = {"message": "超出范围,不允许打卡", 'state': 'no', 'state_result': '超出范围'}
for address_id in address_ids:
card_rice = address_id.card_rice * 0.001
if self.is_within_radius_geopy(
kw['att_latitude'],
kw['att_longitude'],
address_id.latitude,
address_id.longitude,
card_rice
):
data = {
'location': '%s' % address_id.address_name,
'user_id': user_id.id,
'state': 'yes',
'class': [f"{attendance_id.shift_name}{start_time_str}-{end_time_str}"],
'state_result': state_result,
'check_in': check_in_html,
'check_in_status': check_in_status,
'check_out': check_out_html,
'check_out_status': check_out_status,
}
break
return_data = {'data': data, 'code': '200', 'message': 'OK'}
return json.dumps(return_data)
def is_within_radius_geopy(self, lat1, lon1, lat2, lon2, radius_km):
@@ -189,7 +190,8 @@ class AppletAttendance(Controller):
}
"""
_logger.info("----------------/yuthon/att/att---------接口收到响应-------------------------------")
user_id = int(kw['user_id'])
user_id = request.env(user=SUPERUSER_ID)["res.users"].search([('id', '=', int(kw['user_id']))])
employee_id = request.env(user=SUPERUSER_ID)["hr.employee"].search([('name', '=', user_id.name)])
month = kw['selected_month'] # 2015-01
year = int(month[0:4])
month = int(month[-2:])
@@ -206,7 +208,7 @@ class AppletAttendance(Controller):
# 本月考勤记录
leave_days = 0
current_attendance = request.env(user=SUPERUSER_ID)['hr.attendance'].sudo().search([
('employee_id.user_id', '=', user_id),
('employee_id', '=', employee_id.id),
('current_data', '>=', current_month_first.date()),
('current_data', '<=', current_month_last.date()),
('week', 'not in', ['seven', 'six'])
@@ -250,7 +252,6 @@ class AppletAttendance(Controller):
records = []
for att in attendances:
# 获取班次信息
shift_name = att.shift_id.name if att.shift_id else ""
check_in_start = att.shift_id.template_id.check_in_start if att.shift_id and att.shift_id.template_id else 0
check_out_end = att.shift_id.template_id.check_out_end if att.shift_id and att.shift_id.template_id else 0
start_hour = int(check_in_start)
@@ -277,7 +278,7 @@ class AppletAttendance(Controller):
# 添加到记录列表
records.append({
'date': att.current_data.strftime('%Y-%m-%d'),
'class': [f"{shift_name}{standard_check_in_str}-{standard_check_out_str}"],
'class': [f"{att.shift_name}{standard_check_in_str}-{standard_check_out_str}"],
'user_id': user_id,
'check_in': check_in_str,
'check_in_state': check_in_html,
@@ -167,6 +167,10 @@ class YuthonHrEmployee(models.Model):
# 工资计算分组
salary_group_id = fields.Many2one('yuthon.salary.group', string="工资单分组", store=True)
salary_date = fields.Date(string="薪资计算时间", store=True)
salary_month = fields.Selection([('1', '一月'), ('2', '二月'), ('3', '三月'), ('4', '四月'),
('5', '五月'), ('6', '六月'), ('7', '七月'), ('8', '八月'),
('9', '九月'), ('10', '十月'), ('11', '十一月'),
('12', '十二月')], string='薪资计算月份')
salary_year_jin_ids = fields.Many2many(
'hr.employee', # 自关联到hr.employee
relation='hr_employee_salary_year_jin_rel', # 显式指定关联表名
@@ -188,6 +192,12 @@ class YuthonHrEmployee(models.Model):
last_month_base_salary_deduction = fields.Float(string="上月基本薪资扣款")
last_month_performance_deduction = fields.Float(string="上月绩效扣款")
last_month_commuting_deduction = fields.Float(string='上月通信扣款')
# 薪资计算 基数
salary_medical_insurance = fields.Float(string='薪资医疗费基数', compute='_compute_insurance_salary')
salary_insurance = fields.Float(string='薪资养老费基数', compute='_compute_insurance_salary')
salary_work_injury_insurance = fields.Float(string='薪资保险金基数', compute='_compute_insurance_salary')
salary_housing_fund = fields.Float(string='薪资公积金基数', compute='_compute_insurance_salary')
# 显示基数
medical_insurance_base = fields.Float(string='基本医疗费基数', compute='_compute_insurance_bases')
pension_insurance_base = fields.Float(string='基本养老费基数', compute='_compute_insurance_bases')
work_injury_insurance_base = fields.Float(string='失业保险金基数', compute='_compute_insurance_bases')
@@ -200,6 +210,7 @@ class YuthonHrEmployee(models.Model):
subsidy_amount = fields.Float(string="岗位补贴金额", compute='_compute_subsidy_amount', store=True)
security_amount = fields.Float(string="安全委员会金额", compute='_compute_subsidy_amount', store=True)
quality_inspector_amount = fields.Float(string="质检员金额", compute='_compute_subsidy_amount', store=True)
@api.depends('quota_issued', 'user_year')
def _compute_remainder_year(self):
@@ -213,6 +224,7 @@ class YuthonHrEmployee(models.Model):
def _compute_subsidy_amount(self):
for record in self:
amount = 0
quality_amount = 0
# 获取主角色名称
primary_role_name = record.user_role_id.name if record.user_role_id else ''
# 获取所有辅助角色名称列表
@@ -244,7 +256,7 @@ class YuthonHrEmployee(models.Model):
for cert in record.skills_certificates_ids
)
if quality and department_name == '仓储组':
amount += 100
quality_amount += 100
if department_name == '领导班子':
record.security_amount = 0
@@ -254,7 +266,7 @@ class YuthonHrEmployee(models.Model):
record.security_amount = 0
record.subsidy_amount = amount
record.quality_inspector_amount = quality_amount
@api.depends('converted', 'salary_year_jin_ids')
def _compute_total_coefficients(self):
for record in self:
@@ -291,7 +303,7 @@ class YuthonHrEmployee(models.Model):
def _compute_overtime_this_month(self):
for employee in self:
employee.overtime_this_month = employee.normal_overtime_hours + employee.weekend_overtime_hours + employee.holiday_overtime_hours
# 根据当前月份来选择具体的值
@api.depends('social_base_on', 'social_base_off')
def _compute_insurance_bases(self):
current_month = datetime.now().month
@@ -322,6 +334,39 @@ class YuthonHrEmployee(models.Model):
else:
setattr(record, field_name, base)
# 根据薪资计算选择的月份 来获取确定的值
@api.depends('social_base_on', 'social_base_off', 'salary_month')
def _compute_insurance_salary(self):
insurance_names = {
'薪资医疗保险费': 'salary_medical_insurance',
'薪资养老保险费': 'salary_insurance',
'薪资保险费': 'salary_work_injury_insurance',
'薪资公积金': 'salary_housing_fund'
}
name_mapping = {
'基本医疗保险费': '薪资医疗保险费',
'基本养老保险费': '薪资养老保险费',
'失业保险费': '薪资保险费',
'住房公积金': '薪资公积金'
}
insurance_records = self.env['yuthon.payroll.insurances'].search([('name', 'in',['基本医疗保险费', '基本养老保险费', '失业保险费', '住房公积金'])])
for record in self:
for field_name in insurance_names.values():
setattr(record, field_name, 0)
base = record.social_base_on if 1 <= int(record.salary_month) <= 6 else record.social_base_off
for insurance in insurance_records:
insurance_name_key = name_mapping.get(insurance.name)
field_name = insurance_names.get(insurance_name_key)
if field_name == 'housing_fund_base' and record.name in ['郭爱娣', '黄炎方']:
record.housing_fund_base = 4000
continue
if base > insurance.employee_base_ceiling:
setattr(record, field_name, insurance.employee_base_ceiling)
elif base < insurance.employee_base_floor:
setattr(record, field_name, insurance.employee_base_floor)
else:
setattr(record, field_name, base)
@api.onchange('job_id')
def _onchange_job_id(self):
if self.job_id:
@@ -162,6 +162,11 @@
<field name="pension_insurance_base"/>
<field name="work_injury_insurance_base"/>
<field name="housing_fund_base"/>
<field name="salary_month" invisible="1"/>
<field name="salary_medical_insurance" invisible="1"/>
<field name="salary_insurance" invisible="1"/>
<field name="salary_work_injury_insurance" invisible="1"/>
<field name="salary_housing_fund" invisible="1"/>
</group>
<group>
<field name="converted"/>
@@ -31,7 +31,8 @@ class YuthonOnboardApproval(models.Model):
work_end = fields.Boolean(string="结束")
bank = fields.Char(string='开户行')
use_user_type = fields.Selection(related='users_id.employee_id.use_user_type', string="用工形式")
department_id1 = fields.Many2one('hr.department', string="部门")
job = fields.Char(related='employee_name_id.name', string="岗位")
department_id1 = fields.Many2one(related='employee_name_id.department_id', string="部门")
onboarding_status = fields.Selection([('none', '待入职'), ('onboarding', '已入职')], string='审批后员工状态', default='none')
onboard_time = fields.Date(string='入职时间', default=fields.Datetime.now)
probation_period = fields.Selection([('zero', '0个月'), ('one', '1个月'),
@@ -50,7 +51,6 @@ class YuthonOnboardApproval(models.Model):
is_issues = fields.Boolean(string='是否上会')
issues_result = fields.Html(string="会议结果")
related_issues_id = fields.Many2one('issue.library', string="关联议题")
job_id = fields.Many2one('hr.job', string="岗位")
# 合同
contracted_status = fields.Selection([('first', '首'
'签'), ('renew', '续签')], string='合同类型')
@@ -75,7 +75,7 @@
</div>
</group>
<group>
<field name="job_id"/>
<field name="job"/>
<field name="department_id1"/>
</group>
<group>
@@ -52,7 +52,7 @@ class PropertyLeaseContract(models.Model):
name = fields.Char(string='物业信息')
code = fields.Char(string="流水号", default=lambda self: self.env['ir.sequence'].next_by_code('property_lease_contract_code'))
admin_id = fields.Many2one('res.users', string="管理员")
admin_id = fields.Many2one('res.users', string="管理员", compute='_compute_admin_id', store=True)
tenure_type = fields.Selection([('self', '自有'), ('agency', '代管')], string='权属类型')
contract_code = fields.Char(string="合同编号")
contract_date = fields.Date(string="合同签订日期")
@@ -212,6 +212,20 @@ class PropertyLeaseContract(models.Model):
store=True)
responsibility_book_name = fields.Char(string="安全生产责任书(住宅)名称", compute='_compute_attachment_names',
store=True)
def update_admin_id(self):
contract_ids = self.env["property.lease.contract"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for rec in contract_ids:
if rec.lease_ids:
rec.admin_id = rec.lease_ids[0].admin_line_id.id
@api.depends('lease_ids')
def _compute_admin_id(self):
for rec in self:
if rec.lease_ids:
rec.admin_id = rec.lease_ids[0].admin_line_id.id
@api.depends(
'license_id.name',
'legal_entity_id.name',
@@ -809,6 +809,7 @@
<field name="name">合同管理</field>
<field name="res_model">property.lease.contract</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_group_by_admin_id': True}</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_property_approval.view_property_lease_contract_tree1')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('yuthon_property_approval.view_property_lease_contract_form1')})]"/>
@@ -818,6 +819,7 @@
<field name="res_model">property.lease.contract</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('condition', 'in', ['new', 'approval', 'cancel'])]</field>
<field name="context">{'search_default_group_by_admin_id': True}</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_property_approval.view_property_lease_contract_tree')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('yuthon_property_approval.view_property_lease_contract_form')})]"/>
@@ -857,6 +859,14 @@
<field name="state">code</field>
<field name="code">action = model.all_end_approval()</field>
</record>
<record id="update_admin_id" model="ir.actions.server">
<field name="name">同步更新管理员</field>
<field name="model_id" ref="yuthon_property_approval.model_property_lease_contract"/>
<field name="binding_model_id" ref="yuthon_property_approval.model_property_lease_contract"/>
<field name="state">code</field>
<field name="code">action = model.update_admin_id()</field>
</record>
<!-- <record id="all_data_action_use_admin" model="ir.actions.server">-->
<!-- <field name="name">调换</field>-->
<!-- <field name="model_id" ref="yuthon_property_approval.model_property_lease_contract"/>-->
+1
View File
@@ -34,6 +34,7 @@
'wizard/yuthon_payroll_wizard_views.xml',
'wizard/payroll_labor_wizard_views.xml',
'wizard/payroll_retired_wizard_views.xml',
'wizard/initialize_payroll_wizard_views.xml',
'wizard/payslip_send_wizard_views.xml',
'wizard/salary_year_wizard.xml',
'wizard/salary_compute_wizard_views.xml',
+57 -7
View File
@@ -119,13 +119,15 @@ class InheritHrPayslip(models.Model):
leave_absence = fields.Float(string='考勤补发')
# 粮储常用项
quality_allowance = fields.Float(string='质检员津贴')
quality_allowance = fields.Float(string='质检员津贴', compute="_compute_salary", store=True)
environmental_allowance = fields.Float(string='环境作业津贴')
last_duty = fields.Float(string='上月值班费')
high_subsidy = fields.Float(string='高温补贴')
# 粮储临时项
medical_fee = fields.Float(string='超出体检费标准')
retroactive_insurance = fields.Float(string='补扣保险')
distribution_part = fields.Float(string='扣除环境作业津贴多发放部分')
# 三家公司
add_year_float = fields.Float(string='补年金差额')
@@ -187,6 +189,8 @@ class InheritHrPayslip(models.Model):
rec.full_attendance_award = line.total
if line.name == '加班费':
rec.overtime_pay = line.total
if line.name == '质检员津贴':
rec.quality_allowance = line.total
if line.name == '误餐费':
rec.meal_misunderstanding_fee = line.total
if line.name == '事假':
@@ -197,6 +201,8 @@ class InheritHrPayslip(models.Model):
rec.maternity_leave = line.total
if line.name == '婚假':
rec.marriage_leave = line.total
if line.name == '上月值班费':
rec.last_duty = line.total
if line.name == '计生假':
rec.family_planning_leave = line.total
if line.name == '丧假':
@@ -227,6 +233,31 @@ class InheritHrPayslip(models.Model):
rec.commuting_deduction = line.total
if line.name == '扣发基本工资':
rec.base_salary_deduction = line.total
if line.name == '优秀部门经理/员工奖励':
rec.excellent_employees = line.total
if line.name == '考勤补发':
rec.leave_absence = line.total
if line.name == '环境作业津贴':
rec.environmental_allowance = line.total
if line.name == '高温补贴':
rec.high_subsidy = line.total
if line.name == '超出体检费标准':
rec.medical_fee = line.total
if line.name == '补扣保险':
rec.retroactive_insurance = line.total
if line.name == '扣除环境作业津贴多发放部分':
rec.distribution_part = line.total
# 新增字段(无重复name)
if line.name == '岗位绩效':
rec.job_performance = line.total
if line.name == '请假扣发':
rec.leave_allowance = line.total
if line.name == '值班(含节日加班)':
rec.duty = line.total
if line.name == '月加班':
rec.monthly_overtime = line.total
if line.name == '年金':
rec.annuity = line.total
rec.three_one = rec.housing_provident_fund + rec.work_injury_insurance + rec.basic_endowment_insurance + rec.basic_medical_insurance + rec.unemployment_insurance + rec.social_security
@@ -287,12 +318,14 @@ class InheritHrPayslip(models.Model):
@api.depends('year_compute', 'year_jie', 'year_gong', 'jing_te', 'study_on', 'seniority_allowance', 'position_salary',
'quality_allowance','high_subsidy', 'environmental_allowance', 'last_duty', 'seniority_salary', 'commuting_allowance','base_salary','performance_bonuses', 'comprehensive_subsidy', 'full_attendance_award', 'overtime_pay', 'based_pay',
'january_performance','position_subsidy', 'safety_incentives', 'temporary_performance_bonus', 'performance_deduction','commuting_deduction',
'base_salary_deduction', 'last_year_performance_evaluation', 'safety_subsidy', 'job_performance','leave_allowance', 'medical_fee',
'january_performance','position_subsidy', 'safety_incentives', 'temporary_performance_bonus', 'performance_deduction','commuting_deduction', 'retroactive_insurance',
'base_salary_deduction', 'last_year_performance_evaluation', 'safety_subsidy', 'job_performance','leave_allowance', 'distribution_part',
'duty', 'monthly_overtime', 'annuity', 'excellent_employees', 'leave_absence', 'retention_previous_year')
def _compute_total_before_tax(self):
for record in self:
record.total_before_tax = (
record.retroactive_insurance +
record.distribution_part +
record.retention_previous_year +
record.position_salary +
record.high_subsidy +
@@ -338,7 +371,6 @@ class InheritHrPayslip(models.Model):
record.performance_deduction -
record.commuting_deduction -
record.base_salary_deduction -
record.medical_fee -
record.leave_allowance # 请假扣发
)
@@ -346,10 +378,10 @@ class InheritHrPayslip(models.Model):
# 本期应收 - 三险一金 - 年金 - 应补退税 - 工会会费 - 个税退回
# total_before_tax - three_one - b_year - union_dues - tax_payable - individual_tax_refund
@api.depends('total_before_tax', 'three_one', 'b_year', 'union_dues', 'tax_payable', 'individual_tax_refund')
@api.depends('total_before_tax', 'three_one', 'b_year', 'union_dues', 'tax_payable', 'individual_tax_refund', 'medical_fee')
def _compute_actual_salary(self):
for record in self:
record.actual_salary = (record.total_before_tax - record.three_one - record.b_year - record.union_dues - record.tax_payable + record.individual_tax_refund)
record.actual_salary = (record.total_before_tax - record.three_one - record.b_year - record.union_dues - record.tax_payable + record.individual_tax_refund - record.medical_fee)
if record.employee_id.name == '罗梦烨':
_logger.info("罗梦烨实发{} 本期应:{},31:{},年金:{}应补:{}工会{}个税{}".format(record.actual_salary, record.total_before_tax,
record.three_one, record.b_year, record.union_dues,
@@ -500,4 +532,22 @@ class InheritHrPayslip(models.Model):
unconfirmed_slips = self.search(domain)
if unconfirmed_slips:
unconfirmed_slips.write({'confirm_status': 'confirm'})
return super().get_view(view_id, view_type, **options)
return super().get_view(view_id, view_type, **options)
def initialize_payroll_wizard(self):
payslip_ids = self.env["hr.payslip"].browse(
self._context.get('active_ids', self._context.get('active_id')))
payslip_list = []
for record in payslip_ids:
payslip_list.append([4, record.id])
return {
'name': '初始化工资单',
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'initialize.payroll.wizard',
'target': 'new',
'context': {
'default_payslip_ids': payslip_list
}
}
@@ -96,7 +96,7 @@ class YuthonPayrollCommon(models.Model):
if ins.name == '环境作业津贴':
formula_salary = "result = 0"
if ins.name == '质检员津贴':
formula_salary = "result = 0"
formula_salary = "result = employee.quality_inspector_amount"
if ins.name == '工会会费' and ins.company_id.name in ['集团本部', '天河物管']:
formula_salary = "result = round((payslip.base_salary * 0.005), 2)"
if ins.name == '工会会费' and ins.company_id.name == '天河粮储':
@@ -215,19 +215,19 @@ class YuthonPayrollInsurances (models.Model):
code = 'None'
if ins.name == '基本医疗保险费':
code = 'JBYLIAO'
formula_salary = "result = employee.medical_insurance_base * " + str(number)
formula_salary = "result = employee.salary_medical_insurance * " + str(number)
if ins.name == '基本养老保险费':
code = 'JBYLAO'
formula_salary = "result = employee.pension_insurance_base * " + str(number)
formula_salary = "result = employee.salary_insurance * " + str(number)
if ins.name == '失业保险费':
code = 'SYBX'
formula_salary = "result = employee.work_injury_insurance_base * " + str(number)
formula_salary = "result = employee.salary_work_injury_insurance * " + str(number)
if ins.name == '工伤保险费':
code = 'GSBX'
formula_salary = "result = employee.social_base_on * " + str(number)
if ins.name == '住房公积金':
code = 'ZFGJJ'
formula_salary = "result = (round((employee.housing_fund_base * " + str(number) + "), 0)) if employee.is_accum_salary else 0"
formula_salary = "result = (round((employee.salary_housing_fund * " + str(number) + "), 0)) if employee.is_accum_salary else 0"
if ins.name == '企业(职业)年金':
if ins.company_id.name == '天河物管':
code = 'WGQYNJ'
@@ -31,3 +31,4 @@ 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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
31 access_initialize_payroll_wizard access_initialize_payroll_wizard model_initialize_payroll_wizard base.group_user 1 1 1 1
32
33
34
@@ -180,6 +180,7 @@
<field name="seniority_salary" invisible="1" required="0"/>
<field name="union_dues" invisible="1"/>
<field name="overtime_pay" invisible="1"/>
<field name="b_year" invisible="1"/>
<field name="meal_misunderstanding_fee" invisible="1"/>
<field name="personal_leave" invisible="1"/>
<field name="sick_leave" invisible="1"/>
@@ -236,7 +237,7 @@
<field name="date_of_entry" optional="show"/>
<field name="struct_id" string="职级" optional="show" column_invisible="1"/>
<field name="bank_account_id" optional="show" column_invisible="1"/>
<field name="actual_salary" decoration-danger="1" decoration-bf="1" optional="show" column_invisible="1"/>
<field name="actual_salary" decoration-danger="1" decoration-bf="1" optional="show"/>
<field name="total_before_tax" decoration-danger="1" decoration-bf="1" optional="show"/>
<field name="b_year" decoration-danger="1" decoration-bf="1" optional="show"/>
<field name="tax_payable" decoration-danger="1" decoration-bf="1" readonly="1" optional="show"/>
@@ -296,6 +297,8 @@
<field name="study_on" decoration-danger="1" optional="show" groups="yuthon_salary.group_jtbb_user"/>
<field name="excellent_employees" decoration-danger="1" groups="yuthon_salary.group_wg_user" optional="show"/>
<field name="medical_fee" decoration-danger="1" groups="yuthon_salary.group_lc_user" optional="show"/>
<field name="retroactive_insurance" decoration-danger="1" groups="yuthon_salary.group_lc_user" optional="show"/>
<field name="distribution_part" decoration-danger="1" groups="yuthon_salary.group_lc_user" optional="show"/>
<field name="commuting_deduction" decoration-danger="1" optional="show" groups="yuthon_salary.group_jtbb_user"/>
<field name="performance_deduction" decoration-danger="1" optional="show" groups="yuthon_salary.group_jtbb_user"/>
<field name="retention_previous_year" decoration-danger="1" optional="show" groups="yuthon_salary.group_jtbb_user"/>
@@ -464,6 +467,13 @@
<field name="state">code</field>
<field name="code">action = model.action_return_salary_wizard()</field>
</record>
<record id="all_initialize_payroll_wizard" model="ir.actions.server">
<field name="name">初始化工资项</field>
<field name="model_id" ref="hr_payroll.model_hr_payslip"/>
<field name="binding_model_id" ref="hr_payroll.model_hr_payslip"/>
<field name="state">code</field>
<field name="code">action = model.initialize_payroll_wizard()</field>
</record>
<record id="all_data_return_release" model="ir.actions.server">
<field name="name">批量退回薪资审定</field>
<field name="model_id" ref="hr_payroll.model_hr_payslip"/>
+2 -1
View File
@@ -9,4 +9,5 @@ from . import payroll_labor_wizard
from . import payroll_retired_wizard
from . import salary_print_wizard
from . import salary_year_wizard
from . import payslip_send_wizard
from . import payslip_send_wizard
from . import initialize_payroll_wizard
@@ -0,0 +1,34 @@
# -*- 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 InitializePayrollWizard(models.Model):
_name = 'initialize.payroll.wizard'
_description = '初始化工资单弹窗'
payslip_ids = fields.Many2many('hr.payslip', string='工资单')
company_id = fields.Many2one('res.company', string="公司", index=True, default=lambda self: self.env.company)
payroll_common_ids = fields.Many2many('yuthon.payroll.common', string='常用项')
payroll_temporary_ids = fields.Many2many('yuthon.payroll.temporary', string='临时项')
payroll_insurances_ids = fields.Many2many('yuthon.payroll.insurances', string='五险一金')
def confirm(self):
target_names = (
[common.name for common in self.payroll_common_ids] +
[temp.name for temp in self.payroll_temporary_ids] +
[insurance.name for insurance in self.payroll_insurances_ids]
)
for payslip in self.payslip_ids:
for line in payslip.line_ids:
if line.name in target_names:
line.write({
'total': 0,
'amount': 0
})
payslip.with_context(recompute=True)._compute_salary()
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="initialize_payroll_wizard_form" model="ir.ui.view">
<field name="name">初始化工资单</field>
<field name="model">initialize.payroll.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<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)]"/>
</group>
</sheet>
<footer>
<button type="object" name="confirm" string="确认" class="oe_highlight"/>
<button special="cancel" string="取消" class="btn-secondary"/>
</footer>
</form>
</field>
</record>
</odoo>
@@ -41,7 +41,7 @@ class SalaryComputeWizard(models.Model):
('company_id', '=', self.company_id.id),
('is_salary', '=', True),
('entry_date', '<=', start_date),
('contract_id.state', 'not in', ['draft', 'cancel']),
('contract_id.state', 'not in', ['draft', 'cancel', 'close']),
('contract_id', '!=', False),
])
self.employee_ids = [(6, 0, employees.ids)]
@@ -91,6 +91,7 @@ class SalaryComputeWizard(models.Model):
('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} 日期范围内已有工资单记录,无法创建新的工资单。")
@@ -14,26 +14,58 @@ class TemporaryPayslipWizard(models.Model):
excel_file = fields.Binary(string="上传Excel表")
excel_name = fields.Char(string="文件名")
select_time = fields.Selection([('1', '一月'), ('2', '二月'), ('3', '三月'), ('4', '四月'),
('5', '五月'), ('6', '六月'), ('7', '七月'), ('8', '八月'),
('9', '九月'), ('10', '十月'), ('11', '十一月'),
('12', '十二月')], string='选择月份',
default=lambda self: self._get_default_month())
select_year = fields.Selection(selection=[(str(i), str(i)) for i in range(2024, 2060)], string='选择年份',
default=lambda self: str(datetime.now().year))
def _get_default_month(self):
current_month = datetime.now().month
return str(current_month)
def confirm(self):
if not self.excel_file:
raise ValidationError('请上传文件。')
raise UserError("请上传文件")
book = xlrd.open_workbook(file_contents=base64.decodebytes(self.excel_file))
sh = book.sheet_by_index(0)
for rx in range(1, sh.nrows):
# 获取表头(第一行)
headers = [cell.value.strip() for cell in sh.row(0)]
for rx in range(1, sh.nrows): # 从第二行开始读数据
row = sh.row(rx)
self.yuthon_payroll_data(row)
self.process_payslip_data(headers, row)
def yuthon_payroll_data(self, row):
employee_id = self.env['hr.employee'].search([('name', '=', row[0].value)])
payslip_id = self.env['hr.payslip'].search(
[('employee_id', '=', employee_id.id)]
)
payslip_id.write({
'year_compute': row[1].value,
'year_jie': row[2].value,
'year_gong': row[3].value,
'jing_te': row[5].value,
'study_on': row[4].value,
})
def process_payslip_data(self, headers, row):
month = int(self.select_time)
month_mapping = {
1: '一月', 2: '二月', 3: '三月', 4: '四月',
5: '五月', 6: '六月', 7: '七月', 8: '八月',
9: '九月', 10: '十月', 11: '十一月', 12: '十二月'
}
month_name = month_mapping.get(month, '一月')
employee_name = row[0].value if row[0].value else None
employee = self.env["hr.employee"].search([("name", "=", employee_name)], limit=1)
payslip = self.env["hr.payslip"].search([
('payslip_run_id.name', '=', f'{self.select_year}年-{month_name}'),
("employee_id", "=", employee.id),
('is_push', '=', False),
])
line_vals = {}
for col_idx in range(1, len(headers)):
header = headers[col_idx]
cell_value = row[col_idx].value
if cell_value in (None, "", " "):
continue
for line in payslip.line_ids:
if line.name == header:
line_vals[line.id] = {"total": float(cell_value)}
if line_vals:
for line_id, vals in line_vals.items():
self.env["hr.payslip.line"].browse(line_id).write(vals)
payslip.with_context(recompute=True)._compute_salary()
@@ -3,7 +3,7 @@
<record id="temporary_template_downloads_tian_he_action" model="ir.actions.act_url">
<field name="name">临时工资项导入模板</field>
<field name="target">self</field>
<field name="url">/yuthon_salary/static/others/temporary_payslip_template.xlsx</field>
<field name="url">/yuthon_salary/static/others/工资项导入模板.xlsx</field>
</record>
<record id="temporary_payslip_wizard_form" model="ir.ui.view">
<field name="name">审批意见弹窗</field>
@@ -17,6 +17,8 @@
</header>
<group>
<group>
<field name="select_time"/>
<field name="select_year"/>
<field name="excel_file" filename="excel_name"/>
<field name="excel_name" invisible="1"/>
</group>