From 904b455ee8c20f144f329c1d6c9a0906e29e4aa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Wed, 22 Apr 2026 09:48:27 +0800 Subject: [PATCH 01/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_purchase/models/inherit_purchase_order.py | 1 + 1 file changed, 1 insertion(+) diff --git a/yuthon_purchase/models/inherit_purchase_order.py b/yuthon_purchase/models/inherit_purchase_order.py index 9d9ebcd6e..5253fe3d9 100644 --- a/yuthon_purchase/models/inherit_purchase_order.py +++ b/yuthon_purchase/models/inherit_purchase_order.py @@ -20,6 +20,7 @@ class InheritPurchaseOrder(models.Model): code = fields.Char(string="流水号", default=lambda self: self.env['ir.sequence'].next_by_code('purchase_workflow_approval_code')) state = fields.Selection(selection_add=[('申请人', '申请人'), ('部门经理', '部门经理'), ('工会主席', '工会主席'), ('协办', '协办'), + ('财务', '财务'), ('复核', '复核'), ('分管领导', '分管领导'), ('总经理', '总经理'), ('归档', '归档')], default='申请人') From 0f5b693cf333ac2649695f03ac21db7d31538d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Wed, 22 Apr 2026 10:07:18 +0800 Subject: [PATCH 02/64] =?UTF-8?q?=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_property/wizard/batch_invoice_export_wizard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yuthon_property/wizard/batch_invoice_export_wizard.py b/yuthon_property/wizard/batch_invoice_export_wizard.py index 5dae658fa..a11045647 100644 --- a/yuthon_property/wizard/batch_invoice_export_wizard.py +++ b/yuthon_property/wizard/batch_invoice_export_wizard.py @@ -123,8 +123,8 @@ class BatchInvoiceExportWizard(models.TransientModel): date_range = record.due_date.strftime('%Y-%m-%d') sheet.cell(row=row, column=2, value=date_range) - # C列:金额(当期金额) - sheet.cell(row=row, column=3, value=record.original_amount or 0) + # C列:金额(当期金额 - 减免金额) + sheet.cell(row=row, column=3, value=(record.original_amount or 0) - (record.discount_amount or 0)) # D列:物业名称 sheet.cell(row=row, column=4, value=record.property_name or '') From f03d9e7ee55e4554de2da9c1796456b0ebee5e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Wed, 22 Apr 2026 10:18:35 +0800 Subject: [PATCH 03/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../models/message_reminder_tasks.py | 133 +++++++++++------- 1 file changed, 82 insertions(+), 51 deletions(-) diff --git a/yuthon_hr_attendance/models/message_reminder_tasks.py b/yuthon_hr_attendance/models/message_reminder_tasks.py index c3e8715b2..4da78d6ae 100644 --- a/yuthon_hr_attendance/models/message_reminder_tasks.py +++ b/yuthon_hr_attendance/models/message_reminder_tasks.py @@ -22,13 +22,29 @@ class MessageReminderTasks(models.Model): holiday = fields.Boolean(string="假期") def get_reminder_tasks(self): + """每天自动生成当日提醒任务""" current_date = (fields.Date.today() + relativedelta(hours=8)).date() + self._generate_reminder_tasks_by_date(current_date) + + def generate_weekly_reminder_tasks(self): + """批量生成 2026-04-15 至 2026-04-21 提醒任务""" + start_date = fields.Date.from_string('2026-04-15') + end_date = fields.Date.from_string('2026-04-21') + + current_date = start_date + while current_date <= end_date: + self._generate_reminder_tasks_by_date(current_date) + current_date += timedelta(days=1) + + def _generate_reminder_tasks_by_date(self, current_date): + """按指定日期生成提醒任务(每日/批量共用逻辑)""" target_company_names = ['天河物管', '天河粮储', '集团本部'] target_companies = self.env['res.company'].search([('name', 'in', target_company_names)]) target_calendars = self.env['resource.calendar'].search([ ('company_id', 'in', target_companies.ids) ]) - # 获取日历关联的白名单员工 + + # 日历白名单员工 whitelist_employees = target_calendars.mapped('resource_user_ids').ids condition1_employees = self.env['hr.employee'].search([ ('id', 'not in', whitelist_employees), @@ -36,42 +52,50 @@ class MessageReminderTasks(models.Model): ('user_state', '=', 'in'), ('active', '=', True) ]) + + # 角色:保安、工程、联安粮库 target_role_names = ['保安部', '工程部', '联安粮库'] - target_roles = self.env['planning.role'].search([ - ('name', 'in', target_role_names) - ]) - default_reminder_types = ['work', 'offWork'] - default_reminder_times = [ - datetime.combine(current_date, datetime.strptime('00:55', '%H:%M').time()), # 时差 - datetime.combine(current_date, datetime.strptime('10:00', '%H:%M').time()) - ] + target_roles = self.env['planning.role'].search([('name', 'in', target_role_names)]) + condition2_employee_ids = [] if target_roles: for role in target_roles: resource_employees = role.resource_ids.mapped('employee_id') condition2_employee_ids.extend(resource_employees) condition2_employee_ids = list(set(condition2_employee_ids)) + # 哺乳人员 breastfeeding_employees = target_calendars.mapped('breastfeeding_holiday_ids.employee_id') - breastfeeding_employee_ids = breastfeeding_employees.ids if breastfeeding_employees else [] - # 获取不在白名单且是行政班次的人员 + breastfeeding_employee_ids = breastfeeding_employees.ids or [] + + # 行政班人员(不在白名单、不在角色排班) final_employees = condition1_employees.filtered(lambda emp: emp not in condition2_employee_ids) + task_vals_list = [] is_workday = self._process_shift_days(current_date) - template = self.env['planning.slot.template'].search( - [('role_id.name', '=', '集团行政')], - limit=1 - ) + + # 集团行政模板 + template = self.env['planning.slot.template'].search([('role_id.name', '=', '集团行政')], limit=1) sch_check_in_time, sch_check_out_time = self.normal_clock_in_time(template, current_date) + + # 默认提醒时间 + default_reminder_times = [ + datetime.combine(current_date, datetime.strptime('00:55', '%H:%M').time()), + datetime.combine(current_date, datetime.strptime('10:00', '%H:%M').time()) + ] + default_reminder_types = ['work', 'offWork'] + + # 1. 行政班次 if is_workday: for employee in final_employees: emp_name = employee.name emp_department = employee.department_id.name emp_company = employee.company_id.name is_lactating = employee.id in breastfeeding_employee_ids + if is_lactating: - # 哺乳人员 - reminder_times = self.get_reminder_lactating_personnel(employee, current_date, sch_check_in_time, sch_check_out_time) + reminder_times = self.get_reminder_lactating_personnel(employee, current_date, sch_check_in_time, + sch_check_out_time) reminder_types = ['work', 'offWork'] shift_name = '行政班次(哺乳)' else: @@ -79,59 +103,66 @@ class MessageReminderTasks(models.Model): reminder_types = default_reminder_types shift_name = '行政班次' - for reminder_time, reminder_type in zip(reminder_times, reminder_types): + for rt, rtype in zip(reminder_times, reminder_types): task_vals = { 'name': emp_name, - 'current_data': reminder_time.date(), + 'current_data': rt.date(), 'department': emp_department, 'company': emp_company, 'shift_name': shift_name, - 'type': reminder_type, - 'reminder_time': fields.Datetime.to_string(reminder_time), + 'type': rtype, + 'reminder_time': fields.Datetime.to_string(rt), } task_vals_list.append(task_vals) - # 计划行政 + # 2. 计划排班班次 slot_start = datetime.combine(current_date, datetime.min.time()) - timedelta(hours=8) slot_end = datetime.combine(current_date + timedelta(days=1), datetime.min.time()) - timedelta(hours=8) - # 查找员工对应的班次 + for employee in condition2_employee_ids: - reminder_plan_times = [] - reminder_plan_types = [] planning_slot = self.env['planning.slot'].search([ ('resource_id.name', '=', employee.name), ('start_datetime', '>=', slot_start), ('start_datetime', '<', slot_end), ], limit=1) - template = self.env['planning.slot.template'].search([('planning_name', '=', planning_slot.planning_name)], limit=1) - if template: - sch_check_in_time, sch_check_out_time = self.normal_clock_in_time(template, current_date) - is_lactating = employee.id in breastfeeding_employee_ids - if is_lactating: - reminder_plan_times = self.get_reminder_lactating_personnel(employee.id, current_date, sch_check_in_time, sch_check_out_time) - reminder_plan_types = ['work', 'offWork'] - shift_name = f"{template.planning_name}(哺乳)" - else: - reminder_plan_times.append(sch_check_in_time - timedelta(minutes=5)) - reminder_plan_types.append('work') - reminder_plan_times.append(sch_check_out_time) - reminder_plan_types.append('offWork') - shift_name = template.planning_name + if not planning_slot: + continue - for reminder_time, reminder_type in zip(reminder_plan_times, reminder_plan_types): - task_plan_vals = { - 'name': employee.name, - 'department': employee.department_id.name, - 'company': employee.company_id.name, - 'shift_name': shift_name, - 'current_data': (reminder_time + relativedelta(hours=8)).date(), - 'type': reminder_type, - 'reminder_time': fields.Datetime.to_string(reminder_time), - } - task_vals_list.append(task_plan_vals) + template = self.env['planning.slot.template'].search([ + ('planning_name', '=', planning_slot.planning_name) + ], limit=1) - self.env['message.reminder.tasks'].create(task_vals_list) + if not template: + continue + + c_in, c_out = self.normal_clock_in_time(template, current_date) + is_lactating = employee.id in breastfeeding_employee_ids + + if is_lactating: + r_times = self.get_reminder_lactating_personnel(employee, current_date, c_in, c_out) + r_types = ['work', 'offWork'] + shift_name = f"{template.planning_name}(哺乳)" + else: + r_times = [c_in - timedelta(minutes=5), c_out] + r_types = ['work', 'offWork'] + shift_name = template.planning_name + + for rt, rtype in zip(r_times, r_types): + task_plan_vals = { + 'name': employee.name, + 'department': employee.department_id.name, + 'company': employee.company_id.name, + 'shift_name': shift_name, + 'current_data': (rt + timedelta(hours=8)).date(), + 'type': rtype, + 'reminder_time': fields.Datetime.to_string(rt), + } + task_vals_list.append(task_plan_vals) + + # 创建数据 + if task_vals_list: + self.env['message.reminder.tasks'].create(task_vals_list) def get_reminder_lactating_personnel(self, employee, current_data, sch_check_in_time, sch_check_out_time): From c359a80469106afb9e395dd2607f7ad79db04b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Wed, 22 Apr 2026 10:31:16 +0800 Subject: [PATCH 04/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../models/message_reminder_tasks.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/yuthon_hr_attendance/models/message_reminder_tasks.py b/yuthon_hr_attendance/models/message_reminder_tasks.py index 4da78d6ae..c8c066ccf 100644 --- a/yuthon_hr_attendance/models/message_reminder_tasks.py +++ b/yuthon_hr_attendance/models/message_reminder_tasks.py @@ -21,6 +21,26 @@ class MessageReminderTasks(models.Model): remake = fields.Char(string="备注") holiday = fields.Boolean(string="假期") + def remove_duplicate_reminder_tasks(self): + tasks = self.env['message.reminder.tasks'].search([]) + from collections import defaultdict + task_groups = defaultdict(list) + for task in tasks: + key = (task.name, task.current_data, task.type) + task_groups[key].append(task) + for key, task_list in task_groups.items(): + if len(task_list) <= 1: + continue # 无重复,跳过 + done_tasks = [t for t in task_list if t.state == 'done'] + keep_task = None + if done_tasks: + keep_task = done_tasks[0] + else: + keep_task = task_list[0] + for task in task_list: + if task != keep_task: + task.unlink() + def get_reminder_tasks(self): """每天自动生成当日提醒任务""" current_date = (fields.Date.today() + relativedelta(hours=8)).date() From 2d94f1abab42999b975f53a163ce3a76dea5a7ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Wed, 22 Apr 2026 10:38:44 +0800 Subject: [PATCH 05/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_hr_attendance/models/hr_attendance.py | 30 ++++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/yuthon_hr_attendance/models/hr_attendance.py b/yuthon_hr_attendance/models/hr_attendance.py index f9c6e0508..708c15648 100644 --- a/yuthon_hr_attendance/models/hr_attendance.py +++ b/yuthon_hr_attendance/models/hr_attendance.py @@ -83,15 +83,27 @@ class HrAttendance(models.Model): @api.constrains("check_out") def _check_out_in(self): - if self.shift_name in ['夜班', '旧晚(旧区府晚班)']: - current_data = self.current_data + timedelta(days=1) - else: - current_data = self.current_data - reminder_id = self.env['message.reminder.tasks'].search([('name', '=', self.employee_id.name), ('state', '=', 'todo'), - ('type', '=', 'offWork'), - ('current_data', '=', current_data)], limit=1) - if reminder_id: - reminder_id.state = 'done' + @api.constrains("check_out") + def _check_out_in(self): + for record in self: + # 只打印这两个值,出错就打印异常 + try: + print("员工姓名:", record.employee_id.name) + print("当前日期:", record.current_data) + except Exception as e: + print("获取值失败:", e) + if record.shift_name in ['夜班', '旧晚(旧区府晚班)']: + current_data = record.current_data + timedelta(days=1) + else: + current_data = record.current_data + reminder_id = self.env['message.reminder.tasks'].search([ + ('name', '=', record.employee_id.name), + ('state', '=', 'todo'), + ('type', '=', 'offWork'), + ('current_data', '=', current_data) + ], limit=1) + if reminder_id: + reminder_id.state = 'done' def one_time(self): for att in self.env["hr.attendance"].browse(self._context.get('active_ids', self._context.get('active_id'))): From 1c6af352b7ee99343cbdc75013a5dcfadd8edf03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Wed, 22 Apr 2026 10:41:49 +0800 Subject: [PATCH 06/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_hr_attendance/models/hr_attendance.py | 40 ++++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/yuthon_hr_attendance/models/hr_attendance.py b/yuthon_hr_attendance/models/hr_attendance.py index 708c15648..715d1fe9c 100644 --- a/yuthon_hr_attendance/models/hr_attendance.py +++ b/yuthon_hr_attendance/models/hr_attendance.py @@ -83,27 +83,25 @@ class HrAttendance(models.Model): @api.constrains("check_out") def _check_out_in(self): - @api.constrains("check_out") - def _check_out_in(self): - for record in self: - # 只打印这两个值,出错就打印异常 - try: - print("员工姓名:", record.employee_id.name) - print("当前日期:", record.current_data) - except Exception as e: - print("获取值失败:", e) - if record.shift_name in ['夜班', '旧晚(旧区府晚班)']: - current_data = record.current_data + timedelta(days=1) - else: - current_data = record.current_data - reminder_id = self.env['message.reminder.tasks'].search([ - ('name', '=', record.employee_id.name), - ('state', '=', 'todo'), - ('type', '=', 'offWork'), - ('current_data', '=', current_data) - ], limit=1) - if reminder_id: - reminder_id.state = 'done' + for record in self: + # 只打印这两个值,出错就打印异常 + try: + print("员工姓名:", record.employee_id.name) + print("当前日期:", record.current_data) + except Exception as e: + print("获取值失败:", e) + if record.shift_name in ['夜班', '旧晚(旧区府晚班)']: + current_data = record.current_data + timedelta(days=1) + else: + current_data = record.current_data + reminder_id = self.env['message.reminder.tasks'].search([ + ('name', '=', record.employee_id.name), + ('state', '=', 'todo'), + ('type', '=', 'offWork'), + ('current_data', '=', current_data) + ], limit=1) + if reminder_id: + reminder_id.state = 'done' def one_time(self): for att in self.env["hr.attendance"].browse(self._context.get('active_ids', self._context.get('active_id'))): From 2829b417214458e5f3283b73c9f099bed0cb98a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Wed, 22 Apr 2026 10:43:20 +0800 Subject: [PATCH 07/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_hr_attendance/models/hr_attendance.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/yuthon_hr_attendance/models/hr_attendance.py b/yuthon_hr_attendance/models/hr_attendance.py index 715d1fe9c..74f8bf5e1 100644 --- a/yuthon_hr_attendance/models/hr_attendance.py +++ b/yuthon_hr_attendance/models/hr_attendance.py @@ -84,12 +84,8 @@ class HrAttendance(models.Model): @api.constrains("check_out") def _check_out_in(self): for record in self: - # 只打印这两个值,出错就打印异常 - try: - print("员工姓名:", record.employee_id.name) - print("当前日期:", record.current_data) - except Exception as e: - print("获取值失败:", e) + _logger.info('----------打卡取值验证--------') + _logger.info(f"当前员工姓名为{record.employee_id.name}, 当前日期为{record.current_data}") if record.shift_name in ['夜班', '旧晚(旧区府晚班)']: current_data = record.current_data + timedelta(days=1) else: From cab9f6c7898e4515fcc2748158932b0c350cb84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Wed, 22 Apr 2026 11:04:12 +0800 Subject: [PATCH 08/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_hr_attendance/models/hr_attendance.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/yuthon_hr_attendance/models/hr_attendance.py b/yuthon_hr_attendance/models/hr_attendance.py index 74f8bf5e1..3748dfb84 100644 --- a/yuthon_hr_attendance/models/hr_attendance.py +++ b/yuthon_hr_attendance/models/hr_attendance.py @@ -75,11 +75,14 @@ class HrAttendance(models.Model): @api.constrains("check_in") def _check_in_out(self): - reminder_id = self.env['message.reminder.tasks'].search([('name', '=', self.employee_id.name), ('state', '=', 'todo'), - ('type', '=', 'work'), - ('current_data', '=', self.current_data)], limit=1) - if reminder_id: - reminder_id.state = 'done' + for record in self: + _logger.info('----------打卡取值验证--------') + _logger.info(f"当前员工姓名为{record.employee_id.name}, 当前日期为{record.current_data}") + reminder_id = self.env['message.reminder.tasks'].search([('name', '=', record.employee_id.name), ('state', '=', 'todo'), + ('type', '=', 'work'), + ('current_data', '=', record.current_data)], limit=1) + if reminder_id: + reminder_id.state = 'done' @api.constrains("check_out") def _check_out_in(self): From 86a0c00ea3c784a58643942e201c706b3ee87592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Wed, 22 Apr 2026 11:10:20 +0800 Subject: [PATCH 09/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_hr_attendance/models/hr_attendance.py | 4 --- .../models/message_reminder_tasks.py | 28 +++++++++++++++++-- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/yuthon_hr_attendance/models/hr_attendance.py b/yuthon_hr_attendance/models/hr_attendance.py index 3748dfb84..bdcdc56bb 100644 --- a/yuthon_hr_attendance/models/hr_attendance.py +++ b/yuthon_hr_attendance/models/hr_attendance.py @@ -76,8 +76,6 @@ class HrAttendance(models.Model): @api.constrains("check_in") def _check_in_out(self): for record in self: - _logger.info('----------打卡取值验证--------') - _logger.info(f"当前员工姓名为{record.employee_id.name}, 当前日期为{record.current_data}") reminder_id = self.env['message.reminder.tasks'].search([('name', '=', record.employee_id.name), ('state', '=', 'todo'), ('type', '=', 'work'), ('current_data', '=', record.current_data)], limit=1) @@ -87,8 +85,6 @@ class HrAttendance(models.Model): @api.constrains("check_out") def _check_out_in(self): for record in self: - _logger.info('----------打卡取值验证--------') - _logger.info(f"当前员工姓名为{record.employee_id.name}, 当前日期为{record.current_data}") if record.shift_name in ['夜班', '旧晚(旧区府晚班)']: current_data = record.current_data + timedelta(days=1) else: diff --git a/yuthon_hr_attendance/models/message_reminder_tasks.py b/yuthon_hr_attendance/models/message_reminder_tasks.py index c8c066ccf..bc2b14d31 100644 --- a/yuthon_hr_attendance/models/message_reminder_tasks.py +++ b/yuthon_hr_attendance/models/message_reminder_tasks.py @@ -124,9 +124,21 @@ class MessageReminderTasks(models.Model): shift_name = '行政班次' for rt, rtype in zip(reminder_times, reminder_types): + current_data_val = rt.date() + + # ================== 加了这里:判断是否已存在 ================== + exists = self.env['message.reminder.tasks'].search([ + ('name', '=', emp_name), + ('current_data', '=', current_data_val), + ('type', '=', rtype), + ], limit=1) + if exists: + continue # 已存在,跳过不创建 + # ============================================================== + task_vals = { 'name': emp_name, - 'current_data': rt.date(), + 'current_data': current_data_val, 'department': emp_department, 'company': emp_company, 'shift_name': shift_name, @@ -169,12 +181,24 @@ class MessageReminderTasks(models.Model): shift_name = template.planning_name for rt, rtype in zip(r_times, r_types): + current_data_val = (rt + timedelta(hours=8)).date() + + # ================== 加了这里:判断是否已存在 ================== + exists = self.env['message.reminder.tasks'].search([ + ('name', '=', employee.name), + ('current_data', '=', current_data_val), + ('type', '=', rtype), + ], limit=1) + if exists: + continue # 已存在,跳过不创建 + # ============================================================== + task_plan_vals = { 'name': employee.name, 'department': employee.department_id.name, 'company': employee.company_id.name, 'shift_name': shift_name, - 'current_data': (rt + timedelta(hours=8)).date(), + 'current_data': current_data_val, 'type': rtype, 'reminder_time': fields.Datetime.to_string(rt), } From 5f95385933c679adc00518ceb4df29e3cd1dbe64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Wed, 22 Apr 2026 11:13:14 +0800 Subject: [PATCH 10/64] =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_catering/models/yuthon_catering.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/yuthon_catering/models/yuthon_catering.py b/yuthon_catering/models/yuthon_catering.py index a4860cdf2..61bda71cb 100644 --- a/yuthon_catering/models/yuthon_catering.py +++ b/yuthon_catering/models/yuthon_catering.py @@ -14,9 +14,17 @@ class YuthonCatering(models.Model): entry_date = fields.Date(related='employee_id.entry_date', string="入职时间", store=True) breakfast = fields.Boolean(string='早餐', default=False, group_operator='count_true') lunch = fields.Boolean(string='午餐', default=False, group_operator='count_true') + breakfast_count = fields.Integer(string='早餐人数', compute='_compute_meal_count', store=True) + lunch_count = fields.Integer(string='午餐人数', compute='_compute_meal_count', store=True) menu_day_id = fields.Many2one('yuthon.menu.setting', string='菜单') amount = fields.Float(string='结算费用', compute='_compute_amount', store=True) + @api.depends('breakfast', 'lunch') + def _compute_meal_count(self): + for rec in self: + rec.breakfast_count = 1 if rec.breakfast else 0 + rec.lunch_count = 1 if rec.lunch else 0 + @api.depends('date') def _compute_week_day(self): week_map = {'0': '周一', '1': '周二', '2': '周三', '3': '周四', '4': '周五', '5': '周六', '6': '周日'} From 1cc4fbfcbee520205cfc80c540b0d892dc0dc1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Wed, 22 Apr 2026 14:51:46 +0800 Subject: [PATCH 11/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_hr_attendance/models/message_reminder_tasks.py | 2 -- yuthon_will_task/models/yuthon_will_task.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/yuthon_hr_attendance/models/message_reminder_tasks.py b/yuthon_hr_attendance/models/message_reminder_tasks.py index bc2b14d31..4da0b447c 100644 --- a/yuthon_hr_attendance/models/message_reminder_tasks.py +++ b/yuthon_hr_attendance/models/message_reminder_tasks.py @@ -183,7 +183,6 @@ class MessageReminderTasks(models.Model): for rt, rtype in zip(r_times, r_types): current_data_val = (rt + timedelta(hours=8)).date() - # ================== 加了这里:判断是否已存在 ================== exists = self.env['message.reminder.tasks'].search([ ('name', '=', employee.name), ('current_data', '=', current_data_val), @@ -191,7 +190,6 @@ class MessageReminderTasks(models.Model): ], limit=1) if exists: continue # 已存在,跳过不创建 - # ============================================================== task_plan_vals = { 'name': employee.name, diff --git a/yuthon_will_task/models/yuthon_will_task.py b/yuthon_will_task/models/yuthon_will_task.py index d3a2c49d6..a38152fbb 100644 --- a/yuthon_will_task/models/yuthon_will_task.py +++ b/yuthon_will_task/models/yuthon_will_task.py @@ -54,7 +54,7 @@ class YuthonWillTask(models.Model): priority = fields.Selection([('important', '重要不紧急'), ('urgent', '紧急不重要'), ('very_urgent', '重要且紧急')], string='优先级') code = fields.Char(string="任务编号", default=lambda self: self.env['ir.sequence'].next_by_code('task_code')) user_id = fields.Many2one('res.users', string="创建人", default=lambda self: self.env.user) - company_id = fields.Many2one(related="user_id.employee_id.company_id", string="公司", store=True) + company_id = fields.Many2one(related="employee_id.company_id", string="公司", store=True) name = fields.Text(string='任务内容') remarks = fields.Text(string='备注') employee_ids_short = fields.Char(string='参与人简略', compute='_compute_employee_ids_short') From dab39e5d3f4fd4a7ed2ffd9cd7017935a76b41d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Wed, 22 Apr 2026 15:03:41 +0800 Subject: [PATCH 12/64] =?UTF-8?q?=E5=AD=97=E6=AE=B5=E6=8D=A2=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_issue/models/inherit_room_booking.py | 6 ++++++ yuthon_issue/views/inherit_room_booking.xml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/yuthon_issue/models/inherit_room_booking.py b/yuthon_issue/models/inherit_room_booking.py index 2df2955a0..c24a1157d 100644 --- a/yuthon_issue/models/inherit_room_booking.py +++ b/yuthon_issue/models/inherit_room_booking.py @@ -279,6 +279,12 @@ class IssueMeetingLine(models.Model): sequence = fields.Char(string='序号', store=True) issue_library_id = fields.Many2one('issue.library', string='议题') issue_library_name = fields.Char(related='issue_library_id.name', string='议题') + issue_library_text = fields.Text(string='议题内容', compute='_compute_issue_library_text', store=False) + + @api.depends('issue_library_id') + def _compute_issue_library_text(self): + for rec in self: + rec.issue_library_text = rec.issue_library_id.name or '' employee_id = fields.Many2one('hr.employee', string='传达人') designated_person_ids = fields.Many2many( 'hr.employee', diff --git a/yuthon_issue/views/inherit_room_booking.xml b/yuthon_issue/views/inherit_room_booking.xml index 9a3462d90..225d5b290 100644 --- a/yuthon_issue/views/inherit_room_booking.xml +++ b/yuthon_issue/views/inherit_room_booking.xml @@ -29,7 +29,7 @@ + + + \ No newline at end of file diff --git a/yuthon_hr_attendance/views/all_attendance_menu.xml b/yuthon_hr_attendance/views/all_attendance_menu.xml index 620d0b59c..285b45122 100644 --- a/yuthon_hr_attendance/views/all_attendance_menu.xml +++ b/yuthon_hr_attendance/views/all_attendance_menu.xml @@ -158,11 +158,6 @@ - - - - - + yuthon.customer.planning.tree yuthon.customer.planning - - - - - + + + - - yuthon.customer.planning.form - yuthon.customer.planning - -
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- 排版规则 + 客服值班 yuthon.customer.planning tree,form diff --git a/yuthon_hr_attendance/wizard/__init__.py b/yuthon_hr_attendance/wizard/__init__.py index 15a002755..3091dcb00 100644 --- a/yuthon_hr_attendance/wizard/__init__.py +++ b/yuthon_hr_attendance/wizard/__init__.py @@ -14,4 +14,5 @@ from . import export_date_wizard from . import yuthon_vacation_import_wizard from . import overtime_change_wizard from . import vacation_meal_active_wizard -from . import vacation_overtime_active_wizard \ No newline at end of file +from . import vacation_overtime_active_wizard +from . import customer_create_wizard \ No newline at end of file diff --git a/yuthon_hr_attendance/wizard/customer_create_wizard.py b/yuthon_hr_attendance/wizard/customer_create_wizard.py new file mode 100644 index 000000000..8e6260df5 --- /dev/null +++ b/yuthon_hr_attendance/wizard/customer_create_wizard.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +import calendar +from odoo import models, api, fields, _ +from odoo.tools.float_utils import float_round +from odoo.exceptions import ValidationError, UserError +from datetime import date +from datetime import timedelta +from datetime import datetime, time + + +class CustomerCreateWizard(models.TransientModel): + _name = 'customer.create.wizard' + _description = '客服排班生成弹窗' + + 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(2026, 2060)], string='年份', + default=lambda self: str(datetime.now().year)) + + def create_customer(self): + year = int(self.select_year) + month = int(self.select_month) + saturdays = [] + current_date = datetime(year, month, 1) + if month == 12: + next_month = datetime(year + 1, 1, 1) + else: + next_month = datetime(year, month + 1, 1) + last_day = next_month - timedelta(days=1) + + while current_date <= last_day: + if current_date.weekday() == 5: + saturdays.append(current_date.strftime('%Y-%m-%d')) + current_date += timedelta(days=1) + + # 3. 批量创建值班计划 + planning_vals_list = [] + for date in saturdays: + planning_vals_list.append({ + 'date': date, + }) + + if planning_vals_list: + self.env['yuthon.customer.planning'].create(planning_vals_list) + diff --git a/yuthon_hr_attendance/wizard/customer_create_wizard_views.xml b/yuthon_hr_attendance/wizard/customer_create_wizard_views.xml new file mode 100644 index 000000000..e68827272 --- /dev/null +++ b/yuthon_hr_attendance/wizard/customer_create_wizard_views.xml @@ -0,0 +1,21 @@ + + + + 生成弹窗 + customer.create.wizard + +
+ + + + +
+
+
+
+
+
+ + From f3cbf7b91fdd16313d8c81e67522ed29e8272278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Thu, 23 Apr 2026 11:44:16 +0800 Subject: [PATCH 31/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_hr_attendance/views/all_attendance_menu.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yuthon_hr_attendance/views/all_attendance_menu.xml b/yuthon_hr_attendance/views/all_attendance_menu.xml index 285b45122..5b8dae47b 100644 --- a/yuthon_hr_attendance/views/all_attendance_menu.xml +++ b/yuthon_hr_attendance/views/all_attendance_menu.xml @@ -181,7 +181,7 @@ + sequence="20"/> Date: Thu, 23 Apr 2026 14:43:04 +0800 Subject: [PATCH 32/64] =?UTF-8?q?=E9=87=87=E8=B4=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_purchase/models/inherit_purchase_order.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/yuthon_purchase/models/inherit_purchase_order.py b/yuthon_purchase/models/inherit_purchase_order.py index 5253fe3d9..ee1188901 100644 --- a/yuthon_purchase/models/inherit_purchase_order.py +++ b/yuthon_purchase/models/inherit_purchase_order.py @@ -235,10 +235,9 @@ class InheritPurchaseOrder(models.Model): def action_print_purchase_order(self): - """打印采购审批单""" + """打印采购审批单"" self.ensure_one() report_ref = self.env.ref('yuthon_purchase.action_purchase_order_print_report') - # 使用 sudo 以便打印时绕过联系人等模型的访问限制 - return report_ref.report_action(self.sudo()) + return report_ref.sudo().report_action(self) class InheritPurchaseOrderLine(models.Model): _inherit = 'purchase.order.line' From a7729167112182ad29a35a003ff2262d66e100c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Thu, 23 Apr 2026 14:46:16 +0800 Subject: [PATCH 33/64] =?UTF-8?q?=E9=87=87=E8=B4=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_purchase/models/inherit_purchase_order.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yuthon_purchase/models/inherit_purchase_order.py b/yuthon_purchase/models/inherit_purchase_order.py index ee1188901..c9e524012 100644 --- a/yuthon_purchase/models/inherit_purchase_order.py +++ b/yuthon_purchase/models/inherit_purchase_order.py @@ -235,7 +235,8 @@ class InheritPurchaseOrder(models.Model): def action_print_purchase_order(self): - """打印采购审批单"" self.ensure_one() + """打印采购审批单""" + self.ensure_one() report_ref = self.env.ref('yuthon_purchase.action_purchase_order_print_report') return report_ref.sudo().report_action(self) From e86db760e73691bf5c9ed1a7a4b878dbcc6a96da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Thu, 23 Apr 2026 15:12:50 +0800 Subject: [PATCH 34/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_hr_employee_approval/models/yuthon_onboard_approval.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yuthon_hr_employee_approval/models/yuthon_onboard_approval.py b/yuthon_hr_employee_approval/models/yuthon_onboard_approval.py index 1ad9fcdde..9c4a43784 100644 --- a/yuthon_hr_employee_approval/models/yuthon_onboard_approval.py +++ b/yuthon_hr_employee_approval/models/yuthon_onboard_approval.py @@ -265,8 +265,8 @@ class YuthonOnboardApproval(models.Model): }) vacation_date = self.duration_employment.replace(day=1) - self.env['yuthon.vacation.reports'].search([('employee_id', '=', employee_id.id), ('vacation_date', '=', vacation_date)]) - if not vacation_date: + report_id = self.env['yuthon.vacation.reports'].search([('employee_id', '=', employee_id.id), ('vacation_date', '=', vacation_date)]) + if not report_id: self.env['yuthon.vacation.reports'].sudo().create({ 'employee_id': employee_id.id, 'vacation_date': vacation_date, From 1d2ec4e24757b55290628da22c34efd22b1d83c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Thu, 23 Apr 2026 15:51:28 +0800 Subject: [PATCH 35/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_notice/models/yuthon_notice.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/yuthon_notice/models/yuthon_notice.py b/yuthon_notice/models/yuthon_notice.py index e77ceb0c8..c4144f0e6 100644 --- a/yuthon_notice/models/yuthon_notice.py +++ b/yuthon_notice/models/yuthon_notice.py @@ -210,6 +210,8 @@ class YuthonNotice(models.Model): if not employee: continue url = f'https://phone.thtzjt.com/phone/Notice/{self.id}?employee_id={employee.id}' + + # 发送消息(和上面保持完全一致) result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard( category='notice', touser=user_id.employee_id.wecom_userid, @@ -218,11 +220,10 @@ class YuthonNotice(models.Model): url=url, btntxt="详细信息" ) + msg_id = None - if result_data and isinstance(result_data, tuple) and len(result_data) >= 2: - res_dict = result_data[1] - if res_dict.get('errcode') == 0 and res_dict.get('errmsg') == 'ok' and 'msgid' in res_dict: - msg_id = res_dict['msgid'] + if result_data and 'msgid' in result_data[-1]: + msg_id = result_data[-1]['msgid'] user_wecom_mapping.append({ 'emp_id': employee.id, @@ -249,11 +250,6 @@ class YuthonNotice(models.Model): }) else: is_exist.write({ - 'notice_id': self.id, - 'employee_id': emp_id, - 'notice_code': self.code, - 'notice_name': self.name, - 'release_date': self.create_date1, 'state': 'no', 'msg_id': msg_id, }) From d1b66cff86cd3123029ac89c709e5f1407791a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Thu, 23 Apr 2026 16:09:47 +0800 Subject: [PATCH 36/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_notice/models/yuthon_notice.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yuthon_notice/models/yuthon_notice.py b/yuthon_notice/models/yuthon_notice.py index c4144f0e6..fa77fa18b 100644 --- a/yuthon_notice/models/yuthon_notice.py +++ b/yuthon_notice/models/yuthon_notice.py @@ -211,7 +211,6 @@ class YuthonNotice(models.Model): continue url = f'https://phone.thtzjt.com/phone/Notice/{self.id}?employee_id={employee.id}' - # 发送消息(和上面保持完全一致) result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard( category='notice', touser=user_id.employee_id.wecom_userid, @@ -222,8 +221,9 @@ class YuthonNotice(models.Model): ) msg_id = None - if result_data and 'msgid' in result_data[-1]: - msg_id = result_data[-1]['msgid'] + if result_data: + res_dict = result_data[1] + msg_id = res_dict.get('msgid') user_wecom_mapping.append({ 'emp_id': employee.id, From 20d8f78387883dab8a55b182649eb4c50bd6366d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Thu, 23 Apr 2026 16:15:24 +0800 Subject: [PATCH 37/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_notice/models/yuthon_notice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yuthon_notice/models/yuthon_notice.py b/yuthon_notice/models/yuthon_notice.py index fa77fa18b..c95a282d7 100644 --- a/yuthon_notice/models/yuthon_notice.py +++ b/yuthon_notice/models/yuthon_notice.py @@ -213,7 +213,7 @@ class YuthonNotice(models.Model): result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard( category='notice', - touser=user_id.employee_id.wecom_userid, + touser=user_id.employee_ids.wecom_userid, title="通知公告", description=f"{self.name} 发布人: {self.users_id.name}", url=url, From dad58d0c01486389f660b4fc8065f98846b322ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Thu, 23 Apr 2026 16:25:10 +0800 Subject: [PATCH 38/64] =?UTF-8?q?=E6=8E=92=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_notice/views/yuthon_notice_views.xml | 31 +++++++++++---------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/yuthon_notice/views/yuthon_notice_views.xml b/yuthon_notice/views/yuthon_notice_views.xml index a931a9c7d..091ac05b8 100644 --- a/yuthon_notice/views/yuthon_notice_views.xml +++ b/yuthon_notice/views/yuthon_notice_views.xml @@ -84,20 +84,23 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + From 45adea0f0303b048ed616bff2c4d775c8ad8e0ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Thu, 23 Apr 2026 16:29:16 +0800 Subject: [PATCH 39/64] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/soong_workflow_templates.xml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/soong_workflow/templates/soong_workflow_templates.xml b/soong_workflow/templates/soong_workflow_templates.xml index cf2f0ed07..ed11c759d 100644 --- a/soong_workflow/templates/soong_workflow_templates.xml +++ b/soong_workflow/templates/soong_workflow_templates.xml @@ -34,8 +34,13 @@