添加功能

This commit is contained in:
李鹏宇
2026-04-14 15:02:53 +08:00
parent b7a708e2ef
commit 5ca4eef131
10 changed files with 422 additions and 34 deletions
+3
View File
@@ -27,6 +27,7 @@
'views/hr_attendance_views.xml',
'views/resource_calendar_leaves_views.xml',
'views/inherit_hr_employee.xml',
'views/yuthon_meal_line_views.xml',
'views/inherit_planning_role_views.xml',
'views/inherit_planning_slot_views.xml',
'views/attendance_message_log.xml',
@@ -115,6 +116,8 @@
'yuthon_hr_attendance/static/src/xml/button_statement_confirm.xml',
'yuthon_hr_attendance/static/src/js/button_overtime_export.js',
'yuthon_hr_attendance/static/src/xml/button_overtime_export.xml',
'yuthon_hr_attendance/static/src/js/button_vacation_meal_confirm.js',
'yuthon_hr_attendance/static/src/xml/button_vacation_meal_confirm.xml',
'yuthon_hr_attendance/static/src/js/button_overtime_active_export.js',
'yuthon_hr_attendance/static/src/xml/button_overtime_active_export.xml',
'yuthon_hr_attendance/static/src/js/button_meal_active_export.js',
+1
View File
@@ -41,6 +41,7 @@ from . import overtime_active_line
from . import vacation_meal_active
from . import duty_adjustment_schedule
from . import overtime_carryover
from . import yuthon_meal_line
# from odoo.tools import SQL
# import odoo
# odoo.models.READ_GROUP_AGGREGATE.update({
@@ -8,12 +8,20 @@ from datetime import date
from dateutil.relativedelta import relativedelta
from odoo.exceptions import UserError, ValidationError
from odoo.odoo.fields import first
class VacationMealActive(models.Model):
_name = 'vacation.meal.active'
_description = '误餐费归档'
meal_name = fields.Char(string='标题')
select_month = fields.Selection([('1', '一月'), ('2', '二月'), ('3', '三月'), ('4', '四月'),
('5', '五月'), ('6', '六月'), ('7', '七月'), ('8', '八月'),
('9', '九月'), ('10', '十月'), ('11', '十一月'),
('12', '十二月')], string='月份',
default=lambda self: str(datetime.now().month))
select_year = fields.Selection(selection=[(str(i), str(i)) for i in range(2024, 2060)], string='年份',)
line_id = fields.Many2one('vacation.meal.line', string='关联误餐明细')
employee_id = fields.Many2one('hr.employee', string='员工')
workflow_id = fields.Many2one('soong.workflow', string='审批类型')
@@ -40,15 +48,28 @@ class VacationMealActive(models.Model):
def revoke(self):
self.ensure_one()
rec_year = self.select_year
rec_month = self.select_month
self.select_revoke = 'done'
self.line_id.is_filing = True
report_id = self.env['yuthon.vacation.reports'].search([('employee_id', '=', self.employee_id.id),
('vacation_date', '=', (self.start_time.date()).replace(day=1)),
])
self.line_id.is_filing = False
if not rec_month and not rec_year:
first_day = (self.start_time.date()).replace(day=1)
else:
first_day = datetime(rec_year, rec_month, 1).date()
report_id = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', self.employee_id.id),
('vacation_date', '=', first_day),
])
meal_id = self.env['vacation.meal.line'].search([('vacation_id', '=', report_id.id),
('update_type', '=', 'reduce')
])
if meal_id:
future_reps = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', self.employee_id),
('vacation_date', '>=', first_day)
], order='vacation_date asc')
for rep in future_reps:
rep.update_line()
meal_id.unlink()
def all_revoke(self):
@@ -0,0 +1,113 @@
from odoo import api, fields, models, tools, _
from datetime import datetime, time, timedelta
from odoo.exceptions import UserError
from datetime import date, timedelta
from dateutil.relativedelta import relativedelta
class YuthonMealLine(models.Model):
_name = 'yuthon.meal.line'
_description = '误餐明细'
employee_id = fields.Many2one('hr.employee', string="员工")
meal_name = fields.Char(string='标题')
meal_id = fields.Many2one('vacation.meal.line', string="关联误餐额度记录")
entry_date = fields.Date(related='employee_id.entry_date', string="入职时间", store=True)
select_month = fields.Selection([('1', '一月'), ('2', '二月'), ('3', '三月'), ('4', '四月'),
('5', '五月'), ('6', '六月'), ('7', '七月'), ('8', '八月'),
('9', '九月'), ('10', '十月'), ('11', '十一月'),
('12', '十二月')], string='月份',
default=lambda self: str(datetime.now().month))
select_year = fields.Selection(selection=[(str(i), str(i)) for i in range(2024, 2060)], string='年份',
default=lambda self: str(datetime.now().year))
workflow_record_id = fields.Many2one('soong.workflow.record', string='流程')
workflow_id = fields.Many2one(related="workflow_record_id.workflow_id", string="审批类型")
start_time = fields.Datetime(string="开始时间")
end_time = fields.Datetime(string="结束时间")
duration = fields.Float(string="时长")
update_lines = fields.Float(string='更新额度')
reasons = fields.Text(string="申请事由")
meal = fields.Selection([('yes', '是'), ('no', '否')])
is_filing = fields.Boolean(string="归档")
def confirm(self):
valid_leaves = self.env['yuthon.meal.line'].browse(
self._context.get('active_ids', self._context.get('active_id')))
emp_month_summary = {}
for rec in valid_leaves:
rec_year = rec.select_year
rec_month = rec.select_month
rec.meal_id.write({'is_filing': True})
emp_id = rec.employee_id.id
active_exists = self.env['vacation.meal.active'].search([
('line_id', '=', rec.meal_id.id),
('employee_id', '=', emp_id),
('carry_forward_state', '=', 'completed')
], limit=1)
if not active_exists:
meal_active_vals = {
'line_id': rec.meal_id.id,
'meal_name': rec.meal_name,
'employee_id': emp_id,
'workflow_id': rec.workflow_id.id if rec.workflow_id else False,
'start_time': rec.start_time,
'end_time': rec.end_time,
'duration': rec.duration,
'update_lines': rec.update_lines,
'reasons': rec.reasons,
'meal': rec.meal,
'carry_forward_date': fields.Date.today(),
'carry_forward_state': 'completed',
'select_revoke': 'to_do'
}
self.env['vacation.meal.active'].create(meal_active_vals)
summary_key = (emp_id, rec_year, rec_month)
if summary_key not in emp_month_summary:
emp_month_summary[summary_key] = 0.0
emp_month_summary[summary_key] += rec.update_lines or 0.0
# 4. 第二步:遍历汇总结果,为每个【员工+年月】创建一条vacation.over.line
for (emp_id, rec_year, rec_month), total_amount in emp_month_summary.items():
if total_amount <= 1e-6:
continue
report_id = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', emp_id),
('vacation_date', '=', fields.Date.from_string(f'{rec_year}-{rec_month}-01'))
], limit=1)
if report_id:
future_reps = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', emp_id),
('vacation_date', '>=', fields.Date.from_string(f'{rec_year}-{rec_month}-01'))
], order='vacation_date asc')
for rep in future_reps:
rep.update_line()
over_exists = self.env['vacation.over.line'].search([
('vacation_id', '=', report_id.id),
('employee_id', '=', emp_id),
('update_type', '=', 'reduce'),
('text', '=', f'{rec_month}月份误餐结转'),
], limit=1)
if not over_exists:
self.env['vacation.meal.line'].create([{
'vacation_id': report_id.id,
'employee_id': emp_id,
'update_type': 'reduce',
'update_lines': total_amount,
'update_date': fields.Date.today(),
'is_void': False,
'text': f'{rec_month}月份误餐结转',
}])
def open_record(self):
if self.workflow_record_id:
return {
"name": "流程详情",
'type': 'ir.actions.act_window',
'res_model': f'{self.workflow_record_id.res_model}',
'res_id': self.workflow_record_id.res_id,
'view_mode': 'form',
}
@@ -282,34 +282,6 @@ class YuthonVacationReports(models.Model):
'target': 'new',
}
@api.model
def meal_line(self):
today = date.today()
next_month = today.month if today.month < 12 else 1
next_year = today.year if today.month < 12 else today.year + 1
end_date = date(next_year, next_month, 1) - timedelta(days=1)
active_records = self.env['vacation.meal.active'].search([('select_revoke', '=', 'to_do')])
line_ids = [rec.line_id.id for rec in active_records if rec.line_id]
return {
'type': 'ir.actions.act_window',
'name': '误餐情况',
"views": [(False, "tree")],
'res_model': 'vacation.meal.line',
'domain': [
('start_time', '>=', datetime(2026, 1, 1)),
('meal', '=', 'yes'),
('vacation_id', '!=', False),
('workflow_id', '!=', False),
('id', 'not in', line_ids),
('update_type', '>=', 'add'),
('vacation_date', '<=', end_date),
('company_id', 'in', self.env.companies.ids),
('text', '!=', '上月额度继承')
],
'target': 'current',
'context': {'tree_view_ref': "yuthon_hr_attendance.view_vacation_meal_line_tree"}
}
def update_vacation_task(self):
today = date.today()
current_month_first = today.replace(day=1)
@@ -2089,6 +2061,202 @@ class YuthonVacationReports(models.Model):
}
# 误餐上月
@api.model
def last_meal_month(self):
yuthon_meal_env = self.env['yuthon.meal.line']
last_record = yuthon_meal_env.search([], limit=1, order='id desc')
if last_record:
current_year = int(last_record.select_year)
current_month = int(last_record.select_month)
else:
now = datetime.now()
current_year = now.year
current_month = now.month
if current_month == 1:
last_year = current_year - 1
last_month = 12
else:
last_year = current_year
last_month = current_month - 1
end_date = date(last_year, last_month, 1) + relativedelta(months=1) - relativedelta(days=1)
domain = [
('start_time', '>=', datetime(2026, 1, 1)),
('meal', '=', 'yes'),
('vacation_id', '!=', False),
('workflow_id', '!=', False),
('update_type', '>=', 'add'),
('vacation_date', '<=', end_date),
('company_id', 'in', self.env.companies.ids),
('text', '!=', '上月额度继承')
]
meal_lines = self.env['vacation.meal.line'].search(domain)
yuthon_meal_env.search([]).unlink()
# 批量创建
create_vals_list = []
for line in meal_lines:
create_vals = {
'meal_name': line.meal_name,
'meal_id': line.id,
'employee_id': line.employee_id.id,
'entry_date': line.employee_id.entry_date,
'select_year': str(last_year),
'select_month': str(last_month),
'workflow_record_id': line.workflow_record_id.id,
'start_time': line.start_time,
'end_time': line.end_time,
'duration': line.duration,
'meal': line.meal,
'is_filing':line.is_filing,
'update_lines': line.update_lines,
'reasons': line.reasons,
}
create_vals_list.append(create_vals)
if create_vals_list:
yuthon_meal_env.create(create_vals_list)
return {
'type': 'ir.actions.act_window',
'name': f'误餐情况',
"views": [(False, "tree")],
'res_model': 'yuthon.meal.line',
'domain': [],
'target': 'current',
'context': {
'group_by': ['is_filing', 'employee_id'],
}
}
# 误餐本月
@api.model
def meal_line(self):
today = date.today()
end_date = date(today.year, today.month, 1) + relativedelta(months=1) - relativedelta(days=1)
domain = [
('start_time', '>=', datetime(2026, 1, 1)),
('meal', '=', 'yes'),
('vacation_id', '!=', False),
('workflow_id', '!=', False),
('update_type', '>=', 'add'),
('vacation_date', '<=', end_date),
('company_id', 'in', self.env.companies.ids),
('text', '!=', '上月额度继承')
]
meal_lines = self.env['vacation.meal.line'].search(domain)
yuthon_meal_env = self.env['yuthon.meal.line']
yuthon_meal_env.search([]).unlink()
create_vals_list = []
for line in meal_lines:
create_vals = {
'meal_id': line.id,
'meal_name': line.meal_name,
'employee_id': line.employee_id.id,
'entry_date': line.employee_id.entry_date,
'select_month': str(today.month),
'select_year': str(today.year),
'workflow_record_id': line.workflow_record_id.id,
'start_time': line.start_time,
'end_time': line.end_time,
'duration': line.duration,
'update_lines': line.update_lines,
'is_filing': line.is_filing,
'meal': line.meal,
'reasons': line.reasons,
}
create_vals_list.append(create_vals)
if create_vals_list:
yuthon_meal_env.create(create_vals_list)
return {
'type': 'ir.actions.act_window',
'name': f'误餐情况',
"views": [(False, "tree")],
'res_model': 'yuthon.meal.line',
'domain': [],
'target': 'current',
'context': {
'group_by': ['is_filing', 'employee_id'],
}
}
@api.model
def next_meal_month(self):
# 核心:从已生成数据取年月
yuthon_meal_env = self.env['yuthon.meal.line']
last_record = yuthon_meal_env.search([], limit=1, order='id desc')
if last_record:
current_year = int(last_record.select_year)
current_month = int(last_record.select_month)
else:
now = datetime.now()
current_year = now.year
current_month = now.month
# 计算:下月
if current_month == 12:
next_year = current_year + 1
next_month = 1
else:
next_year = current_year
next_month = current_month + 1
# 下月最后一天
end_date = date(next_year, next_month, 1) + relativedelta(months=1) - relativedelta(days=1)
active_records = self.env['vacation.meal.active'].search([('select_revoke', '=', 'to_do')])
domain = [
('start_time', '>=', datetime(2026, 1, 1)),
('meal', '=', 'yes'),
('vacation_id', '!=', False),
('workflow_id', '!=', False),
('update_type', '>=', 'add'),
('vacation_date', '<=', end_date),
('company_id', 'in', self.env.companies.ids),
('text', '!=', '上月额度继承')
]
meal_lines = self.env['vacation.meal.line'].search(domain)
# 清空
yuthon_meal_env.search([]).unlink()
# 批量创建
create_vals_list = []
for line in meal_lines:
create_vals = {
'meal_id': line.id,
'meal_name': line.meal_name,
'employee_id': line.employee_id.id,
'entry_date': line.employee_id.entry_date,
'select_year': str(next_year), # 用下一年月
'select_month': str(next_month),
'workflow_record_id': line.workflow_record_id.id,
'start_time': line.start_time,
'end_time': line.end_time,
'duration': line.duration,
'update_lines': line.update_lines,
'is_filing': line.is_filing,
'meal': line.meal,
'reasons': line.reasons,
}
create_vals_list.append(create_vals)
if create_vals_list:
yuthon_meal_env.create(create_vals_list)
return {
'type': 'ir.actions.act_window',
'name': f'误餐情况',
"views": [(False, "tree")],
'res_model': 'yuthon.meal.line',
'domain': [],
'target': 'current',
'context': {
'group_by': ['is_filing', 'employee_id'],
}
}
class VacationLeaveLine(models.Model):
_name = 'vacation.leave.line'
_description = '年假明细'
@@ -2521,6 +2689,12 @@ class VacationMealLine(models.Model):
('vacation_date', '=', fields.Date.from_string(f'{rec_year}-{rec_month}-01'))
], limit=1)
if report_id:
future_reps = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', emp_id),
('vacation_date', '>=', fields.Date.from_string(f'{rec_year}-{rec_month}-01'))
], order='vacation_date asc')
for rep in future_reps:
rep.update_line()
over_exists = self.env['vacation.over.line'].search([
('vacation_id', '=', report_id.id),
('employee_id', '=', emp_id),
@@ -98,4 +98,6 @@ access_yuthon_vacation_import_wizard,yuthon.vacation.import.wizard,model_yuthon_
access_overtime_change_wizard,overtime.change.wizard,model_overtime_change_wizard,base.group_user,1,1,1,1
access_duty_adjustment_schedule,duty.adjustment.schedule,model_duty_adjustment_schedule,base.group_user,1,1,1,1
access_overtime_carryover,overtime.carryover,model_overtime_carryover,base.group_user,1,1,1,1
access_overtime_carryover_line,overtime.carryover.line,model_overtime_carryover_line,base.group_user,1,1,1,1
access_overtime_carryover_line,overtime.carryover.line,model_overtime_carryover_line,base.group_user,1,1,1,1
access_yuthon_meal_line,yuthon.meal.line,model_yuthon_meal_line,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
98
99
100
101
102
103
@@ -0,0 +1,24 @@
/** @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 CustomAttendanceVacationMealConfirmController extends ListController {
setup() {
super.setup();
}
async getNextAction(lock_type) {
await this.orm.call('yuthon.vacation.reports', 'next_meal_month', []);
this.model.root.load()
}
async getLastAction(lock_type) {
await this.orm.call('yuthon.vacation.reports', 'last_meal_month', []);
this.model.root.load()
}
}
registry.category("views").add("custom_vacation_confirm_meal_button", {
...listView,
Controller: CustomAttendanceVacationMealConfirmController,
buttonTemplate: "vacation_meal_confirm_module.ListView.Buttons",
});
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<t t-name="vacation_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="getLastAction">
上月
</button>
<button type="button" class="btn btn-primary" style="margin-left: 10px;" t-on-click="getNextAction">
下月
</button>
</xpath>
</t>
</templates>
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_yuthon_meal_line_tree" model="ir.ui.view">
<field name="name">yuthon.meal.line.tree</field>
<field name="model">yuthon.meal.line</field>
<field name="arch" type="xml">
<tree string="误餐情况" create="false" default_order="entry_date asc" js_class="custom_vacation_confirm_meal_button">
<header>
<button name="confirm" type="object" string="归档" class="oe_highlight"/>
</header>
<field name="select_year"/>
<field name="select_month"/>
<field name="employee_id" string="申请人"/>
<field name="workflow_id"/>
<field name="meal_name" column_invisible="1"/>
<field name="meal_id" column_invisible="1"/>
<field name="workflow_record_id" column_invisible="1"/>
<field name="start_time"/>
<field name="end_time"/>
<field name="duration"/>
<field name="update_lines"/>
<field name="reasons"/>
<field name="meal"/>
<button name="open_record" string="详情" type="object" class="btn-primary"/>
</tree>
</field>
</record>
<record id="view_yuthon_vacation_meal_search" model="ir.ui.view">
<field name="name">yuthon.meal.line.search</field>
<field name="model">yuthon.meal.line</field>
<field name="arch" type="xml">
<search string="误餐情况">
<field name="employee_id"/>
</search>
</field>
</record>
</odoo>
@@ -34,7 +34,6 @@
<filter name="now_month" string="当月" domain="[('vacation_date', '&gt;=', (context_today() + relativedelta(day=1)).strftime('%Y-%m-%d')),
('vacation_date', '&lt;', (context_today() + relativedelta(months=+1, day=1)).strftime('%Y-%m-%d'))]"/>
<header_search class="header-search-inline">
<field name="vacation_date"/>
<field name="employee_id"/>
<field name="department_id"/>
</header_search>