From 39522a97d593db990bf6a219a314d474e23b9070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Sat, 28 Feb 2026 10:17:34 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E9=A4=90=E9=A5=AE=E6=94=B6=E9=9B=86?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_catering/views/yuthon_catering.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yuthon_catering/views/yuthon_catering.xml b/yuthon_catering/views/yuthon_catering.xml index 1cfe07b3a..55740c633 100644 --- a/yuthon_catering/views/yuthon_catering.xml +++ b/yuthon_catering/views/yuthon_catering.xml @@ -13,7 +13,7 @@ - + @@ -24,6 +24,7 @@ 餐饮收集任务 yuthon.catering tree,form + {'default_order': 'date desc'} From afff868c9e2fa361997b26b774459d9579b6a421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Sat, 28 Feb 2026 11:36:55 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E9=A4=90=E9=A5=AE=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_catering/__manifest__.py | 1 + yuthon_catering/data/yuthon_catering_cron.xml | 18 +++ yuthon_catering/models/yuthon_catering.py | 142 +++++++++++++++++- yuthon_catering/views/yuthon_catering.xml | 3 + 4 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 yuthon_catering/data/yuthon_catering_cron.xml diff --git a/yuthon_catering/__manifest__.py b/yuthon_catering/__manifest__.py index c7c33564c..8cbfb5db6 100644 --- a/yuthon_catering/__manifest__.py +++ b/yuthon_catering/__manifest__.py @@ -16,6 +16,7 @@ 'views/yuthon_catering_setting_views.xml', 'views/yuthon_catering_templates.xml', 'wizard/catering_reinstated_wizard_views.xml', + 'data/yuthon_catering_cron.xml', ], 'assets': { 'web.assets_backend': [ diff --git a/yuthon_catering/data/yuthon_catering_cron.xml b/yuthon_catering/data/yuthon_catering_cron.xml new file mode 100644 index 000000000..0c769482d --- /dev/null +++ b/yuthon_catering/data/yuthon_catering_cron.xml @@ -0,0 +1,18 @@ + + + + + 定时任务:餐饮登记提醒 + + code + model.cron_send_message() + + + 1 + days + + -1 + + + + \ No newline at end of file diff --git a/yuthon_catering/models/yuthon_catering.py b/yuthon_catering/models/yuthon_catering.py index 328175718..fee3f0bc8 100644 --- a/yuthon_catering/models/yuthon_catering.py +++ b/yuthon_catering/models/yuthon_catering.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- + from odoo import api, fields, models from datetime import datetime, timedelta @@ -23,23 +23,32 @@ class YuthonCatering(models.Model): conf_records = self.env['yuthon.catering.conf'].search([]) employee_ids = conf_records.mapped('employee_ids') + # 获取今天的日期 today = fields.Date.today() + # 创建未来7天的日期列表 date_list = [today + timedelta(days=i) for i in range(7)] + # 初始化待创建记录列表 create_vals = [] + # 遍历每个员工 for employee in employee_ids: + # 遍历每个日期 for target_date in date_list: + # 检查目标日期是否为工作日,如果不是则跳过 if not self._process_shift_days(target_date): continue + # 检查是否已存在该员工在该日期的记录 existing = self.search([ ('employee_id', '=', employee.id), ('date', '=', target_date) ], limit=1) + # 查找当天的菜单设置 menu_record = self.env['yuthon.menu.setting'].search([ ('menu_date', '=', target_date) ], limit=1) + # 如果不存在记录,则准备创建新记录的数据 if not existing: create_vals.append({ 'menu_record': menu_record.id if menu_record else False, @@ -47,52 +56,175 @@ class YuthonCatering(models.Model): 'employee_id': employee.id, }) + # 如果有待创建的记录,则批量创建 if create_vals: self.create(create_vals) + # 定义私有方法,用于判断给定日期是否为工作日 def _process_shift_days(self, current_data): """ 判断是否是工作日 调休日 双休 """ + # 获取当前年份 current_year = (fields.Date.today() + timedelta(hours=8)).year + # 设置查询开始时间(当年1月1日) query_start = datetime(current_year, 1, 1, 0, 0, 0) - timedelta(hours=8) + # 设置查询结束时间(当年12月31日) query_end = datetime(current_year, 12, 31, 23, 59, 59) - timedelta(hours=8) + # 查询"行政考勤工作日"日历的假期设置 calendar_leaves = self.env['resource.calendar.leaves'].sudo().search([ ("calendar_id.name", "=", "行政考勤工作日"), ("date_from", ">=", query_start), ("date_from", "<=", query_end) ]) + # 初始化假期列表 holidays_list = [] + # 初始化调休列表 shift_days_list = [] # 提取节假日和调休日 for leave in calendar_leaves: + # 如果有假期设置,添加到假期列表 if leave.holidays: holidays_list.extend(leave.holidays.split(',')) + # 如果有调休日设置,添加到假期列表 if leave.alternate_rest_day: holidays_list.extend(leave.alternate_rest_day.split(',')) + # 如果有换班日设置,添加到调休列表 if leave.shift_replacement_day: shift_days_list.extend(leave.shift_replacement_day.split(',')) + # 移除日期字符串中的前导零 holidays_list = [self.remove_leading_zero(date) for date in holidays_list] + # 移除日期字符串中的前导零 shift_days_list = [self.remove_leading_zero(date) for date in shift_days_list] + # 格式化输入的日期为"月日"格式 result = f"{current_data.month}月{current_data.day}日" + # 如果日期在调休列表中,返回True(是工作日) if result in shift_days_list: return True + # 如果日期在假期列表中或为周末,返回False(不是工作日) elif (result in holidays_list) or (current_data.weekday() in [5, 6]): return False + # 其他情况返回True(是工作日) else: return True + # 移除日期字符串中的前导零 def remove_leading_zero(self, date_str): + # 分割月份和日期部分 month, day = date_str.split('月') + # 移除月份前面的零 month = month.lstrip('0') + # 移除日期末尾的"日"字符 day = day.rstrip('日') + # 移除日期前面的零 day = day.lstrip('0') + # 返回处理后的日期字符串 return f'{month}月{day}日' + # 定义定时任务方法,每天九点半发送消息提醒 def cron_send_message(self): - # 登记页面 - url = f'/catering/registration/' - # 统计页面 - url = f'/catering/statistics/' + """ + 定时任务:每天上午九点半发送消息提醒 + """ + # 获取当前时间(本地时间) + current_utc_time = fields.Datetime.now() + local_time = current_utc_time + timedelta(hours=8) + h = local_time.hour + m = local_time.minute + + # 检查是否是上午9:30左右 + if not (h == 9 and 25 <= m <= 35): # 9:25-9:35之间 + return + # 判断今天是否为工作日 + today = fields.Date.today() + if not self._process_shift_days(today): + # 如果不是工作日,则不发送提醒 + return + # 发送通用提醒消息 + employees = self.env['hr.employee'].search([('user_id', '!=', False)]) + + for employee in employees: + if employee.user_id: # 确保员工有关联的用户 + # 构造跳转URL,格式为 f'https://phone.thtzjt.com/phone/Restaurant/(users_id) + users_id = employee.user_id.id + target_url = f'https://phone.thtzjt.com/phone/Restaurant/{users_id}' + + # 发送消息,使用指定的格式 + if hasattr(self.env["wecom.apps"], 'sync_send_message_textcard'): + description = f"您有1条待办未处理,请及时登OA系统处理,谢谢配合" + title = "餐饮登记提醒" + + self.env["wecom.apps"].sync_send_message_textcard( + category='oa', + user_id=employee.wecom_userid if hasattr(employee, 'wecom_userid') else employee.user_id.login, + title=title, + description=description, + url=target_url, + btntxt="查看详情" + ) + + # 定义批量提醒方法 + def batch_remind_catering(self): + """ + 批量提醒功能:手动触发提醒选定的记录 + """ + # 从环境上下文中获取选中的记录ID + active_ids = self.env.context.get('active_ids') + # 浏览选中的记录,如果没有则使用当前记录 + selected_records = self.browse(active_ids) if active_ids else self + # 如果没有选中任何记录,抛出异常 + if not selected_records: + raise ValueError("没有选择任何记录进行提醒") + # 按员工分组存储待通知的记录 + employees_to_notify = {} + # 遍历选中的记录 + for record in selected_records: + # 只处理员工存在且状态为未确认的记录 + if record.employee_id and record.state == 'unconfirmed': + # 如果员工ID不在字典中,则初始化 + if record.employee_id.id not in employees_to_notify: + employees_to_notify[record.employee_id.id] = { + 'employee': record.employee_id, + 'records': [] + } + # 将记录添加到对应员工的列表中 + employees_to_notify[record.employee_id.id]['records'].append(record) + # 发送提醒给每个员工 + notified_count = 0 + # 遍历员工字典 + for emp_id, data in employees_to_notify.items(): + employee = data['employee'] + records = data['records'] + # 构造跳转URL + users_id = employee.user_id.id if employee.user_id else '' + target_url = f'https://phone.thtzjt.com/phone/Restaurant/{users_id}' + # 发送消息,使用指定的格式 + if hasattr(self.env["wecom.apps"], 'sync_send_message_textcard'): + # 构造消息内容 + description = f"您有{len(data['records'])}条待办未处理,请及时登OA系统处理,谢谢配合" + # 设置消息标题 + title = "餐饮登记提醒" + # 调用企业微信API发送消息卡片 + self.env["wecom.apps"].sync_send_message_textcard( + category='oa', # 消息类别为OA + # 获取员工的企业微信ID或登录名 + user_id=employee.wecom_userid if hasattr(employee, 'wecom_userid') else employee.user_id.login if employee.user_id else '', + title=title, # 消息标题 + description=description, # 消息内容 + url=target_url, # 点击后跳转的URL + btntxt="查看详情" # 按钮文字 + ) + # 增加已通知计数 + notified_count += 1 + # 返回通知结果 + return { + 'type': 'ir.actions.client', # 动作类型为客户端动作 + 'tag': 'display_notification', # 标签为显示通知 + 'params': { + 'title': '批量提醒', # 通知标题 + 'message': f'已向 {notified_count} 名员工发送提醒消息', # 通知内容 + 'sticky': False, # 不是粘性通知 + } + } diff --git a/yuthon_catering/views/yuthon_catering.xml b/yuthon_catering/views/yuthon_catering.xml index 55740c633..00c9041d5 100644 --- a/yuthon_catering/views/yuthon_catering.xml +++ b/yuthon_catering/views/yuthon_catering.xml @@ -7,6 +7,9 @@ yuthon.catering +
+