# Conflicts:
#	soong_workflow/wizard/soong_workflow_comment_wizard_view.xml
This commit is contained in:
李鹏宇
2026-01-24 12:15:03 +08:00
23 changed files with 308 additions and 51 deletions
+1
View File
@@ -48,6 +48,7 @@
'soong_workflow/static/src/css/record_scss.scss',
'soong_workflow/static/src/css/button_css.scss',
'soong_workflow/static/src/js/view_button.js',
'soong_workflow/static/src/js/select_create_dialog.js',
'soong_workflow/static/src/diagram_plus_dialog/*',
],
},
@@ -0,0 +1,33 @@
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { SelectCreateDialog } from "@web/views/view_dialogs/select_create_dialog";
patch(SelectCreateDialog.prototype, {
get viewProps() {
// ################### 使用context <field name="user_id" context="{'select_dialog': 'list'}"/>
const { select_dialog } = this.props.context;
const type = select_dialog && ["kanban", "list"].includes(select_dialog) ? select_dialog : this.env.isSmall ? "kanban" : "list";
// ###############################
// #################### 全局改
// const type = "list";
const props = {
loadIrFilters: true,
...this.baseViewProps,
context: this.props.context,
domain: this.props.domain,
dynamicFilters: this.props.dynamicFilters,
resModel: this.props.resModel,
searchViewId: this.props.searchViewId,
type,
};
if (type === "list") {
props.allowSelectors = this.props.multiSelect;
} else if (type === "kanban") {
props.forceGlobalClick = true;
}
return props;
},
});
@@ -117,9 +117,9 @@ class SoongWorkflowCommentWizard(models.TransientModel):
for ret in self.return_link_id:
if ret.name in ['拟办', '申请人']:
if hasattr(record_id, 'user_id') and record_id.user_id:
employee_ids1 = record_id.user_id.employee_ids
employee_ids1 = self.env['hr.employee'].search([('user_id', '=', record_id.user_id.id)])
elif hasattr(record_id, 'users_id') and record_id.users_id:
employee_ids1 = record_id.users_id.employee_ids
employee_ids1 = self.env['hr.employee'].search([('user_id', '=', record_id.users_id.id)])
self.employee_ids1 = employee_ids1
elif self.node_btn_name == '退回':
next_list = []
+1 -1
View File
@@ -567,7 +567,7 @@ class HolidaysRequestBatch(models.Model):
break
current_remaining = record.remaining_quantity
deduct_amount = min(remaining_deduct, current_remaining)
if remaining_deduct > current_remaining:
if remaining_deduct < current_remaining:
carry_state = 'partial'
else:
carry_state = 'completed'
+4
View File
@@ -95,10 +95,14 @@
'yuthon_hr_attendance/static/src/xml/button_detail_wechat.xml',
'yuthon_hr_attendance/static/src/components/*/*.xml',
'yuthon_hr_attendance/static/src/css/*.css',
'yuthon_hr_attendance/static/src/js/button_meal_confirm.js',
'yuthon_hr_attendance/static/src/xml/button_meal_confirm.xml',
'yuthon_hr_attendance/static/src/js/button_vacation_confirm.js',
'yuthon_hr_attendance/static/src/xml/button_vacation_confirm.xml',
'yuthon_hr_attendance/static/src/js/planning_gantt_renderer.js',
'yuthon_hr_attendance/static/src/xml/planning_gantt_renderer.xml',
'yuthon_hr_attendance/static/src/js/button_statement_confirm.js',
'yuthon_hr_attendance/static/src/xml/button_statement_confirm.xml',
'yuthon_hr_attendance/static/src/js/button_vacation_put.js',
'yuthon_hr_attendance/static/src/xml/button_vacation_put.xml',
'yuthon_hr_attendance/static/src/planning_gantt/planning_gantt_controller.xml',
@@ -15,6 +15,7 @@ class InheritHrLeave(models.Model):
is_filing = fields.Boolean(string="归档")
application_reason = fields.Char(string="申请原因", compute="_compute_application_reason")
@api.model
def confirm(self):
now = datetime.now()
current_month = now.month
@@ -973,7 +973,6 @@ class VacationOverline(models.Model):
valid_date = fields.Date(string='有效期至')
adjust_date = fields.Date(string='调整至')
leave_date = fields.Date(string='请假日期')
total = fields.Float(string='总时长', compute='_compute_total')
overtime_methods = fields.Selection([('leave', '补休'), ('fee', '加班费结算')], string='加班补偿方式', default='leave')
text = fields.Text(string='备注')
is_confirm = fields.Boolean(string="是否确认")
@@ -983,7 +982,7 @@ class VacationOverline(models.Model):
start_time = fields.Datetime(string="开始时间", compute="_compute_overtime", store=True)
end_time = fields.Datetime(string="结束时间", compute="_compute_overtime", store=True)
carry_forward_date = fields.Date(string="结转日期")
duration = fields.Float(string="加班时长", compute="_compute_overtime", store=True)
duration = fields.Float(string="时长", compute="_compute_overtime", store=True)
carry_forward_quantity = fields.Float(string="结转时长")
validity = fields.Char(string="有效期")
carry_result = fields.Selection([('success', '成功'), ('failed', '失败')], string="结转结果")
@@ -994,6 +993,7 @@ class VacationOverline(models.Model):
)
remaining_quantity = fields.Float(string='剩余数', compute="_compute_remaining_quantity", store=True)
@api.model
def confirm(self):
domain = [
'|',
@@ -1013,7 +1013,7 @@ class VacationOverline(models.Model):
if not filtered_records:
return
records_to_update = filtered_records.filtered(
lambda r: fields.float_compare(r.total, r.carry_forward_quantity) == 0
lambda r: fields.float_compare(r.duration, r.carry_forward_quantity) == 0
)
if records_to_update:
records_to_update.write({'is_filing': True})
@@ -1033,22 +1033,10 @@ class VacationOverline(models.Model):
rec._compute_employee_id()
rec._compute_last_balance()
@api.depends('public_holidays', 'workday', 'last_balance', 'valid_date')
def _compute_total(self):
current_date = date.today()
for record in self:
last_balance_contribution = record.last_balance
if record.valid_date and current_date > record.valid_date:
last_balance_contribution = 0.0
if record.is_initial_number:
record.total = record.duration
else:
record.total = record.public_holidays + record.workday + last_balance_contribution
@api.depends('total', 'carry_forward_quantity')
@api.depends('duration', 'carry_forward_quantity')
def _compute_remaining_quantity(self):
for rec in self:
rec.remaining_quantity = rec.total - rec.carry_forward_quantity
rec.remaining_quantity = rec.duration - rec.carry_forward_quantity
@api.depends('vacation_id')
def _compute_last_balance(self):
@@ -1079,6 +1067,11 @@ class VacationOverline(models.Model):
rec.overtime_name = '2025年12月结余初始数'
rec.start_time = datetime(2025, 12, 1, 1, 0, 0)
rec.end_time = datetime(2025, 12, 1, 9, 0, 0)
if rec.vacation_id and rec.vacation_id.valid_date1:
current_date = fields.Date.today()
if current_date > rec.vacation_id.valid_date1:
rec._compute_last_balance()
rec.duration = max(0, rec.duration - rec.last_balance)
elif rec.workflow_record_id and rec.update_type == 'add':
res_model = rec.workflow_record_id.res_model
@@ -1235,6 +1228,7 @@ class VacationMealLine(models.Model):
rec._compute_employee_id()
rec._compute_overtime()
@api.model
def confirm(self):
now = datetime.now()
current_month = now.month
@@ -1247,10 +1241,11 @@ class VacationMealLine(models.Model):
last_month_year = current_year
last_month_first_day = datetime(last_month_year, last_month, 1)
current_month_first_day = datetime(current_year, current_month, 1)
valid_leaves = self.filtered(lambda l:
fields.Datetime.to_datetime(l.start_date) >= last_month_first_day and
fields.Datetime.to_datetime(l.start_date) < current_month_first_day
)
valid_leaves = self.search([
('start_date', '>=', fields.Datetime.to_string(last_month_first_day)),
('start_date', '<', fields.Datetime.to_string(current_month_first_day)),
('id', 'in', self.ids)
])
if valid_leaves:
valid_leaves.write({'is_filing': True})
@@ -0,0 +1,25 @@
/** @odoo-module */
import { ListController } from "@web/views/list/list_controller";
import { registry } from '@web/core/registry';
import { listView } from '@web/views/list/list_view';
export class CustomAttendanceMealConfirmController extends ListController {
setup() {
super.setup();
}
async getAction(lock_type) {
const action = await this.orm.call('vacation.meal.line', 'confirm', [lock_type], {context: this.props.context});
action["context"] = {
...action.context,
...this.model.root.context,
}
return action
}
}
registry.category("views").add("custom_vacation_meal_button", {
...listView,
Controller: CustomAttendanceMealConfirmController,
buttonTemplate: "meal_confirm_module.ListView.Buttons",
});
@@ -0,0 +1,25 @@
/** @odoo-module */
import { ListController } from "@web/views/list/list_controller";
import { registry } from '@web/core/registry';
import { listView } from '@web/views/list/list_view';
export class CustomAttendanceStatementConfirmController extends ListController {
setup() {
super.setup();
}
async getAction(lock_type) {
const action = await this.orm.call('hr.leave', 'confirm', [lock_type], {context: this.props.context});
action["context"] = {
...action.context,
...this.model.root.context,
}
return action
}
}
registry.category("views").add("custom_statement_confirm_button", {
...listView,
Controller: CustomAttendanceStatementConfirmController,
buttonTemplate: "statement_confirm_module.ListView.Buttons",
});
@@ -29,7 +29,7 @@ export class CustomAttendanceVacationPutController extends ListController {
view_mode: 'tree,form',
view_type: 'tree',
views: [[false, 'tree']],
target: 'new', // 弹窗形式打开
target: 'current',
context: {'tree_view_ref': "yuthon_hr_attendance.view_hr_leave_statement_tree"},
domain: [
'&',
@@ -47,7 +47,7 @@ export class CustomAttendanceVacationPutController extends ListController {
view_mode: 'tree',
view_type: 'form',
views: [[false, 'tree']],
target: 'new',
target: 'current',
context: {'tree_view_ref': "yuthon_hr_attendance.view_yuthon_vacation_overtime_briefly_tree"},
domain: [
'|',
@@ -87,7 +87,7 @@ export class CustomAttendanceVacationPutController extends ListController {
view_mode: 'tree',
view_type: 'form',
views: [[false, 'tree']],
target: 'new',
target: 'current',
context: {'tree_view_ref': "yuthon_hr_attendance.view_vacation_meal_line_tree",
'group_by': ['employee_id'],
},
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<t t-name="meal_confirm_module.ListView.Buttons" t-inherit="web.ListView.Buttons">
<xpath expr="//div[hasclass('o_list_buttons')]" position="after">
<button type="button" class="btn btn-primary" style="margin-left: 10px;" t-on-click="getAction">
确认
</button>
</xpath>
</t>
</templates>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<t t-name="statement_confirm_module.ListView.Buttons" t-inherit="web.ListView.Buttons">
<xpath expr="//div[hasclass('o_list_buttons')]" position="after">
<button type="button" class="btn btn-primary" style="margin-left: 10px;" t-on-click="getAction">
确认
</button>
</xpath>
</t>
</templates>
@@ -4,7 +4,7 @@
<field name="name">hr.leave.statement.tree</field>
<field name="model">hr.leave</field>
<field name="arch" type="xml">
<tree string="请假情况" editable="bottom" limit="15" create="false" delete="false">
<tree string="请假情况" editable="bottom" limit="15" create="false" delete="false" js_class="custom_statement_confirm_button">
<field name="caption"/>
<field name="employee_id"/>
<field name="employee_company_id"/>
@@ -4,7 +4,7 @@
<field name="name">vacation.meal.line.tree</field>
<field name="model">vacation.meal.line</field>
<field name="arch" type="xml">
<tree string="误餐情况" create="false" delete="false">
<tree string="误餐情况" create="false" delete="false" js_class="custom_vacation_meal_button">
<field name="employee_id"/>
<field name="meal_name"/>
<field name="start_date" column_invisible="1"/>
@@ -545,7 +545,6 @@
<field name="valid_date" groups="yuthon_salary.group_lc_user,yuthon_salary.group_wg_user"/>
<field name="workday"/>
<field name="public_holidays"/>
<field name="total"/>
<field name="carry_forward_quantity"/>
<field name="carry_forward_date"/>
<field name="remaining_quantity"/>
@@ -638,7 +637,7 @@
<field name="model_id" ref="yuthon_hr_attendance.model_vacation_over_line"/>
<field name="binding_model_id" ref="yuthon_hr_attendance.model_vacation_over_line"/>
<field name="state">code</field>
<field name="code">action = model.is_visible()</field>
<field name="code">action = model.update_is_visible()</field>
</record>
<record id="view_yuthon_vacation_meal_action" model="ir.actions.act_window">
<field name="name">误餐情况</field>
@@ -557,11 +557,7 @@
<field name="arch" type="xml">
<tree string="通讯录" create="false" delete="false" editable="bottom">
<field name="name" readonly="1"/>
<field name="mobile_phone" readonly="1"/>
<field name="work_phone" readonly="1"/>
<field name="department_id" readonly="1"/>
<field name="job_id" readonly="1"/>
<field name="company_id" column_invisible="1"/>
</tree>
</field>
</record>
+1
View File
@@ -29,6 +29,7 @@
'views/yuthon_salary_project_views.xml',
'views/yuthon_payroll_labor_views.xml',
'views/yuthon_salary_documents_views.xml',
'views/yuthon_salary_active_views.xml',
'views/yuthon_welfare_expenses_views.xml',
'views/yuthon_payroll_retired_views.xml',
'views/yuthon_meal_expenses_views.xml',
+2 -1
View File
@@ -16,4 +16,5 @@ from . import inherit_hr_payslip_run
from . import inherit_hr_payslip_line
from . import yuthon_salary_party
from . import yuthon_individual_tax
from . import yuthon_meal_expenses
from . import yuthon_meal_expenses
from . import yuthon_salary_active
+21 -3
View File
@@ -15,14 +15,32 @@ class InheritHrPayslipRun(models.Model):
string="阶段")
def Fileline(self):
domain = [('is_salary_push', '=', True), ('is_salary_release', '=', True), ('payslip_run_id', '=', self.id)]
month_cn_to_num = self._get_month_cn_to_num()
target_name = self.name
year_str = ""
month_str = ""
if target_name and "年-" in target_name:
year_part, month_part = target_name.split("年-", 1)
year_str = year_part.replace("年", "")
month_str = month_cn_to_num.get(month_part.strip(), "")
domain = [('company_id', '=', self.company_id.id)]
if year_str:
domain.append(('select_year', '=', year_str))
if month_str:
domain.append(('select_time', '=', month_str))
return {
"name": "薪资归档明细",
'type': 'ir.actions.act_window',
'res_model': 'hr.payslip',
'res_model': 'yuthon.salary.active',
"view_mode": "tree",
"domain": domain,
"context": "{'tree_view_ref': 'yuthon_salary.my_hr_payslip_view_tree'}"
}
def _get_month_cn_to_num(self):
return {
'一月': '1', '二月': '2', '三月': '3', '四月': '4',
'五月': '5', '六月': '6', '七月': '7', '八月': '8',
'九月': '9', '十月': '10', '十一月': '11', '十二月': '12'
}
def write(self, vals):
@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
from dateutil.relativedelta import relativedelta
from odoo import models, fields, api, _, SUPERUSER_ID
from odoo.exceptions import ValidationError, UserError
from odoo.exceptions import ValidationError
import calendar
from pypinyin import pinyin, Style
import math
import xlrd
import base64
import datetime
from datetime import datetime, timedelta
class YuthonSalaryActive(models.Model):
_name = 'yuthon.salary.active'
_description = '薪资存档表'
employee_id = fields.Many2one('hr.employee', string='员工', required=True)
company_id = fields.Many2one('res.company', string='公司', related='employee_id.company_id', store=True)
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, 2040)], string='年份')
basic_salary = fields.Float(string='基本工资')
rank_salary = fields.Float(string='职级工资')
pre_performance_bonus = fields.Float(string='预发绩效奖金')
meal_allowance = fields.Float(string='误餐费')
post_salary = fields.Float(string='岗位工资')
communication_subsidy = fields.Float(string='通讯补贴')
seniority_wage = fields.Float(string='工龄工资')
overtime_pay = fields.Float(string='加班费')
full_attendance_bonus = fields.Float(string='全勤奖')
basic_year_end_bonus = fields.Float(string='基础年终奖')
liquidation_salary_leader = fields.Float(string='2024年清算工资(领导)')
balance_salary = fields.Float(string='2025年结余工资')
liquidation_salary_staff = fields.Float(string='2024年清算工资(职工)') #
business_special_bonus = fields.Float(string='2025经营特别奖')
deduct_performance_bonus = fields.Float(string='扣发绩效奖金')
deduct_communication_subsidy = fields.Float(string='扣发通讯补贴')
attendance_deduction = fields.Float(string='考勤扣发')
# 合计/社保/税费类字段(float 类型)
total_pre_tax_salary = fields.Float(string='税前工资合计')
basic_endowment_insurance = fields.Float(string='基本养老保险费')
basic_medical_insurance = fields.Float(string='基本医疗保险费')
unemployment_insurance = fields.Float(string='失业保险费')
housing_fund = fields.Float(string='住房公积金')
enterprise_annuity = fields.Float(string='企业(职业)年金')
tax_adjustment = fields.Float(string='应补(退)税额')
trade_union_dues = fields.Float(string='工会会费')
# 实发/福利类字段(float 类型)
actual_paid_salary = fields.Float(string='实发工资')
reimbursed_medical_exam_fee = fields.Float(string='已报销体检费')
remaining_medical_exam_fee = fields.Float(string='剩余体检费')
def payslip_wizard(self):
return {
"type": "ir.actions.act_window",
"name": "工资条发送",
"res_model": "payslip.send.wizard",
"view_mode": "form",
"target": "new",
"context": {
'default_employee_id': self.employee_id.id,
}
}
def _get_default_month(self):
current_month = datetime.now().month
return str(current_month)
+2 -1
View File
@@ -44,4 +44,5 @@ access_party_send_wizard,access_party_send_wizard,model_party_send_wizard,base.g
access_yuthon_meal_expenses,access_yuthon_meal_expenses,model_yuthon_meal_expenses,base.group_user,1,1,1,1
access_party_meal_wizard,access_party_meal_wizard,model_party_meal_wizard,base.group_user,1,1,1,1
access_salary_meal_wizard,access_salary_meal_wizard,model_salary_meal_wizard,base.group_user,1,1,1,1
access_salary_meal_wizard,access_salary_meal_wizard,model_salary_meal_wizard,base.group_user,1,1,1,1
access_yuthon_salary_active,access_yuthon_salary_active,model_yuthon_salary_active,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
44
45
46
47
48
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_yuthon_salary_active_tree" model="ir.ui.view">
<field name="name">yuthon.salary.active.tree</field>
<field name="model">yuthon.salary.active</field>
<field name="arch" type="xml">
<tree string="工资单归档" create="0" edit="0" delete="0">
<!-- 核心关联字段:员工 -->
<button type="object" name="payslip_wizard" string="发放" class="oe_highlight"/>
<field name="employee_id" string="员工"/>
<field name="company_id" column_invisible="1"/>
<!-- 基础薪资字段 -->
<field name="basic_salary" string="基本工资"/>
<field name="rank_salary" string="职级工资"/>
<field name="pre_performance_bonus" string="预发绩效奖金"/>
<field name="meal_allowance" string="误餐费"/>
<field name="post_salary" string="岗位工资"/>
<field name="communication_subsidy" string="通讯补贴"/>
<field name="seniority_wage" string="工龄工资"/>
<field name="overtime_pay" string="加班费"/>
<field name="full_attendance_bonus" string="全勤奖"/>
<field name="basic_year_end_bonus" string="基础年终奖"/>
<field name="liquidation_salary_leader" string="2024年清算工资(领导)"/>
<field name="balance_salary" string="2025年结余工资"/>
<field name="liquidation_salary_staff" string="2024年清算工资(职工)"/>
<field name="business_special_bonus" string="2025经营特别奖"/>
<!-- 扣发类字段 -->
<field name="deduct_performance_bonus" string="扣发绩效奖金"/>
<field name="deduct_communication_subsidy" string="扣发通讯补贴"/>
<field name="attendance_deduction" string="考勤扣发"/>
<!-- 合计/社保/税费类字段 -->
<field name="total_pre_tax_salary" string="税前工资合计"/>
<field name="basic_endowment_insurance" string="基本养老保险费"/>
<field name="basic_medical_insurance" string="基本医疗保险费"/>
<field name="unemployment_insurance" string="失业保险费"/>
<field name="housing_fund" string="住房公积金"/>
<field name="enterprise_annuity" string="企业(职业)年金"/>
<field name="tax_adjustment" string="应补(退)税额"/>
<field name="trade_union_dues" string="工会会费"/>
<field name="actual_paid_salary" string="实发工资"/>
<field name="reimbursed_medical_exam_fee" string="已报销体检费"/>
<field name="remaining_medical_exam_fee" string="剩余体检费"/>
<field name="select_time" column_invisible="1"/>
<field name="select_year" column_invisible="1"/>
</tree>
</field>
</record>
<record id="yuthon_salary_active_action" model="ir.actions.act_window">
<field name="name">薪资归档</field>
<field name="res_model">yuthon.salary.active</field>
<field name="view_mode">tree</field>
</record>
</odoo>
+7 -11
View File
@@ -12,19 +12,15 @@ class PayslipSendWizard(models.Model):
employee_ids = fields.Many2many('hr.employee', string='员工')
payslip_run_id = fields.Many2one('hr.payslip.run', string='工资单')
payslip_ids = fields.Many2many('hr.payslip', string='工资条')
salary_id = fields.Many2one('yuthon.salary.active', string='工资条')
def button_send(self):
for payslip in self.payslip_ids:
# 获取工资单关联的员工
employee = payslip.employee_id
if not employee.wecom_userid:
raise UserError(f"请同步{employee.name}的企业微信")
url = f"https://oa.thtzjt.com/webh5/#/salary?id={payslip.id}"
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='default',
user_id=employee.wecom_userid,
description=self.employee_id.name + '请确认薪资:',
title="薪资确认",
url=url, btntxt="详细信息")
url = f"https://oa.thtzjt.com/webh5/#/salary?id={self.salary_id.id}"
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='default',
user_id=self.employee.wecom_userid,
description=self.employee_id.name + '请确认薪资:',
title="薪资确认",
url=url, btntxt="详细信息")
def button_all_send(self):
for payslip in self.payslip_ids:
+5
View File
@@ -379,6 +379,11 @@
parent="human_resources_salary_root_board"
action="hr_payroll.action_hr_payslip_run_tree"
sequence="7"/>
<menuitem id="human_yuthon_salary_active_board"
name="薪资归档"
parent="human_resources_salary_root_board"
action="yuthon_salary.yuthon_salary_active_action"
sequence="99"/>
<menuitem id="human_pay_slips_root_board_jtbb"
name="员工薪资"
groups="yuthon_salary.group_jtbb_user"