From acdc79a37b824bb7bc20cfa27d29c501fec3ebe6 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: Sun, 22 Feb 2026 17:29:14 +0800 Subject: [PATCH 1/6] =?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/soong_workflow_record.py | 5 +- .../views/soong_workflow_record.xml | 1 - .../wizard/soong_workflow_comment_wizard.py | 1 - yuthon_holiday/models/inherit_hr_leave.py | 26 ++++-- .../models/yuthon_vacation_reports.py | 1 + yuthon_issue/__manifest__.py | 1 + yuthon_issue/data/notice_room.xml | 18 +++++ yuthon_issue/models/inherit_room_booking.py | 81 +++++++++++++++++++ yuthon_issue/models/issue_library.py | 17 +++- yuthon_notice/models/yuthon_notice.py | 2 +- yuthon_official/models/yuthon_official.py | 33 ++++++++ .../models/yuthon_official_incoming_normal.py | 18 ++++- .../yuthon_official_incoming_normal_views.xml | 8 +- .../views/yuthon_official_views.xml | 8 +- .../models/property_meter_archives.py | 2 +- .../models/property_meter_water.py | 2 +- .../models/property_lease_contract.py | 28 ++++--- 17 files changed, 218 insertions(+), 34 deletions(-) create mode 100644 yuthon_issue/data/notice_room.xml diff --git a/soong_workflow/models/soong_workflow_record.py b/soong_workflow/models/soong_workflow_record.py index e830c5f12..6a6cba87c 100644 --- a/soong_workflow/models/soong_workflow_record.py +++ b/soong_workflow/models/soong_workflow_record.py @@ -297,7 +297,10 @@ class SoongWorkflowRecord(models.Model): valid_employee_ids = self.env['hr.employee'].search([ ('department_id', 'in', department_ids) ]).ids - domain = [('employee_ids', 'in', valid_employee_ids)] + valid_user_ids = self.env['res.users'].search([ + ('employee_id', 'in', valid_employee_ids) + ]).ids + domain = [('users_ids', 'in', valid_user_ids)] if (operator == '=' and not value) or (operator == '!=' and value): domain = ['!'] + domain return domain diff --git a/soong_workflow/views/soong_workflow_record.xml b/soong_workflow/views/soong_workflow_record.xml index 55d699de8..626c77f59 100644 --- a/soong_workflow/views/soong_workflow_record.xml +++ b/soong_workflow/views/soong_workflow_record.xml @@ -490,7 +490,6 @@ soong.workflow.record tree,form,search {'default_order': 'display_end_time desc,id desc'} - [('reclaim_state', '=', False), ('is_company', '=', True)] diff --git a/soong_workflow/wizard/soong_workflow_comment_wizard.py b/soong_workflow/wizard/soong_workflow_comment_wizard.py index 9d3528a87..2d49f17cd 100644 --- a/soong_workflow/wizard/soong_workflow_comment_wizard.py +++ b/soong_workflow/wizard/soong_workflow_comment_wizard.py @@ -582,7 +582,6 @@ class SoongWorkflowCommentWizard(models.TransientModel): user_ids = [employee.user_id.id for employee in self.employee_ids if employee.user_id] if hasattr(related_record, 'self_users_ids'): related_record.self_users_ids = [(6, 0, user_ids)] if user_ids else [(5,)] - workflow_record_id.users_ids = [(6, 0, user_ids)] if self.node_btn_name in ['结束', '确认']: is_work_remind = False current_user = self.env.user diff --git a/yuthon_holiday/models/inherit_hr_leave.py b/yuthon_holiday/models/inherit_hr_leave.py index 9453b0ae6..22f93a087 100644 --- a/yuthon_holiday/models/inherit_hr_leave.py +++ b/yuthon_holiday/models/inherit_hr_leave.py @@ -600,14 +600,24 @@ class HolidaysRequestBatch(models.Model): }) actual_vacation_report.update_line() actual_vacation_report._compute_working_overtime() - domain = [ - ('employee_id', '=', self.users_id.employee_ids.id), - ('is_visible', '=', False), - ('is_void', '=', False), - ('text', '!=', '上月额度继承'), - ] - if self.users_id.employee_ids.company_id.name == '集团本部': - domain.append(('start_time', '>=', datetime(2025, 12, 1)),) + if self.env.user.company_id.name == '集团本部': + domain = [ + ('employee_id.name', '=', '吴欣欣'), + '|', + ('is_initial_number', '=', True), + '&', ('is_initial_number', '=', False), + '&', ('update_type', '=', 'add'), + '&', ('workflow_record_id', '!=', False), + '&', ('start_time', '>=', datetime(2025, 12, 31, 23, 59, 59)), + '&', ('vacation_id', '!=', False), + ('employee_id.is_annual_salary', '!=', True) + ] + else: + domain = [ + ('workflow_record_id', '!=', False), + ('update_type', '=', 'add'), + ('remaining_quantity', '!=', 0.0), + ] overtime_records = self.env['vacation.over.line'].search(domain, order='start_time ASC') remaining_deduct = hours for record in overtime_records: diff --git a/yuthon_hr_attendance/models/yuthon_vacation_reports.py b/yuthon_hr_attendance/models/yuthon_vacation_reports.py index 897dc7d2c..5180475af 100644 --- a/yuthon_hr_attendance/models/yuthon_vacation_reports.py +++ b/yuthon_hr_attendance/models/yuthon_vacation_reports.py @@ -1614,6 +1614,7 @@ class YuthonVacationReports(models.Model): # 原有domain(先筛选出基础数据) if self.env.user.company_id.name == '集团本部': domain = [ + ('employee_id.name', '=', '吴欣欣'), '|', ('is_initial_number', '=', True), '&', ('is_initial_number', '=', False), diff --git a/yuthon_issue/__manifest__.py b/yuthon_issue/__manifest__.py index ce88d93b8..b37a66d52 100644 --- a/yuthon_issue/__manifest__.py +++ b/yuthon_issue/__manifest__.py @@ -15,6 +15,7 @@ 'security/issue_security_group.xml', 'security/ir.model.access.csv', 'data/issue_library_code_data.xml', + 'data/notice_room.xml', 'views/issue_library_views.xml', 'views/issue_type_views.xml', 'views/issue_approval_views.xml', diff --git a/yuthon_issue/data/notice_room.xml b/yuthon_issue/data/notice_room.xml new file mode 100644 index 000000000..348328a5d --- /dev/null +++ b/yuthon_issue/data/notice_room.xml @@ -0,0 +1,18 @@ + + + + + 定时任务:会议通知 + + code + model.notice_room() + + + 1 + minutes + + -1 + + + + diff --git a/yuthon_issue/models/inherit_room_booking.py b/yuthon_issue/models/inherit_room_booking.py index 9fdee03fb..b50ac52be 100644 --- a/yuthon_issue/models/inherit_room_booking.py +++ b/yuthon_issue/models/inherit_room_booking.py @@ -1,6 +1,7 @@ from odoo import api, fields, models, _ from odoo.exceptions import UserError from datetime import timedelta +from datetime import datetime, timedelta, date class RoomBooking(models.Model): @@ -52,6 +53,86 @@ class RoomBooking(models.Model): else: return [('company_id', 'not in', company_ids)] + def notice_room(self): + booking_records = self.search([ + ('work_end', '=', True), + ('task_time_ids', '!=', False), + ('start_datetime', '!=', False) + ]) + if not booking_records: + return + + now = fields.Datetime.now() + for booking in booking_records: + remind_time_thresholds = [] + for task_time in booking.task_time_ids: + if not task_time.number or task_time.number <= 0: + continue + if task_time.task_unit == 'min': + threshold = timedelta(minutes=task_time.number) + elif task_time.task_unit == 'hour': + threshold = timedelta(hours=task_time.number) + elif task_time.task_unit == 'day': + threshold = timedelta(days=task_time.number) + else: + continue + remind_time_thresholds.append(threshold) + + if not remind_time_thresholds: + continue + + meeting_start = fields.Datetime.to_datetime(booking.start_datetime) + timedelta(hours=8) + actual_diff = meeting_start - now + if actual_diff.total_seconds() < 0: + continue + + need_remind = False + for threshold in remind_time_thresholds: + diff_seconds = abs(actual_diff.total_seconds() - threshold.total_seconds()) + if diff_seconds <= 60: # 误差≤60秒即判定为匹配 + need_remind = True + break + + if need_remind: + remind_users = self._get_remind_users(booking) + employee_ids = self.env['hr.employee'].search([('user_id', 'in', remind_users.ids)]) + url = f'https://oa.thtzjt.com/web#id={self.id}&menu_id=9539&action=811&model=room.booking&view_type=form' + data = self.name + '\n' + time_str = fields.Datetime.to_datetime(self.start_datetime + timedelta(hours=8)).strftime('%H:%M:%S') + data += f'会议时间:{time_str}' + for employee_id in employee_ids: + self.sudo().env["wecom.apps"].sync_send_message_textcard(category='task', + user_id=employee_id.wecom_userid, + title="会议通知", description=data, + url=url, btntxt="详细信息") + + def _get_remind_users(self, booking): + remind_user_ids = set() + # ① 组织者 + if booking.organizer_id: + remind_user_ids.add(booking.organizer_id.id) + # ② 记录人 + if booking.user_id: + remind_user_ids.add(booking.user_id.id) + # ③ 出席人员 + if booking.room_users_ids: + remind_user_ids.update(booking.room_users_ids.ids) + # ④ 传达人 + for line in booking.issue_meeting_line_ids: + if line.employee_id and line.employee_id.user_id: + remind_user_ids.add(line.employee_id.user_id.id) + # ⑤ 列席人员 + for line in booking.issue_meeting_line_ids: + if line.attendees_ids: + for employee in line.attendees_ids: + remind_user_ids.add(employee.user_id.id) + + # ⑥ 主持人 + if booking.host and booking.host.user_id: + remind_user_ids.add(booking.host.user_id.id) + return self.env['res.users'].browse(list(remind_user_ids)).filtered(lambda u: u.active) + + @api.depends('number_of_meetings', 'meeting_type_id', 'start_datetime') def _compute_name(self): if self.meeting_type_id and self.start_datetime: diff --git a/yuthon_issue/models/issue_library.py b/yuthon_issue/models/issue_library.py index be662b1a5..6210c767e 100644 --- a/yuthon_issue/models/issue_library.py +++ b/yuthon_issue/models/issue_library.py @@ -576,15 +576,30 @@ class IssueLibrary(models.Model): ('department_id', 'in', all_dept_ids), ('active', '=', True) ]) + target_dept_names = ['办公室', '财务部', '风控法务部', '纪检审计部', '综合办公室'] + target_managers = self.env['hr.employee'].search([ + ('company_id', '=', current_user.company_id.id), + ('department_id.name', 'in', target_dept_names), + ('user_role_id.name', '=', '部门经理'), + ('active', '=', True) + ]) + manager_employee = self.env['hr.employee'].search([ - ('id', 'in', target_employees.ids), + ('id', 'in', target_managers.ids), ('user_role_id.name', '=', '部门经理') ], limit=1) + + attendees_ids = [] + if target_managers and manager_employee: + attendees_ids = target_managers.filtered(lambda emp: emp.id != manager_employee.id).ids + elif target_managers: + attendees_ids = target_managers.id for issue in self: issue_details.append((0, 0, { 'issue_library_id': issue.id, 'employee_id': manager_employee.id, 'designated_person_ids': [(6, 0, target_employees.ids)], + 'attendees_ids': [(6, 0, attendees_ids)], })) if target_meeting_type_id: continue diff --git a/yuthon_notice/models/yuthon_notice.py b/yuthon_notice/models/yuthon_notice.py index 16f830e25..d13927e3b 100644 --- a/yuthon_notice/models/yuthon_notice.py +++ b/yuthon_notice/models/yuthon_notice.py @@ -221,7 +221,7 @@ class YuthonNotice(models.Model): msg_id = False touser = "|".join(we_id_list) if we_id_list else "" if touser: - url = f'http://oa.thtzjt.com/webh5#/notice?id={self.id}' + url = f'https://phone.thtzjt.com/phone/Notice/110?employee_id=' result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard( category='notice', touser=touser, diff --git a/yuthon_official/models/yuthon_official.py b/yuthon_official/models/yuthon_official.py index 5c9012bf8..cee50ac44 100644 --- a/yuthon_official/models/yuthon_official.py +++ b/yuthon_official/models/yuthon_official.py @@ -66,6 +66,8 @@ class YuthonOfficial(models.Model): deadline = fields.Datetime(string="办理期限") done_date = fields.Date(string="完成时间") workflow_inspector_ids = fields.One2many('soong.workflow.inspector', 'yuthon_official_id', string="督办事项") + approve_state = fields.Selection([("start", "草稿"), ("pending", "审批中"), ("end", "结束")], string="审批状态", + compute='_compute_workflow', index=True) room_booking_ids = fields.One2many('room.booking', 'yuthon_official_id', string="会议关联") issue_library_ids = fields.One2many('issue.library', 'yuthon_official_id', string="议题关联") work_end = fields.Boolean(string="结束") @@ -77,6 +79,37 @@ class YuthonOfficial(models.Model): name_title = fields.Char(default=_get_default_name_title) self_users_ids = fields.Many2many('res.users', string="当前经办人", default=lambda self: [(6, 0, [self.env.user.id])]) + is_company = fields.Boolean(string="本公司", default=True, compute='_compute_is_company', + search='_search_part_of_company') + + @api.depends('code') + def _compute_workflow(self): + for record in self: + record.approve_state = False + workflow = self.env['soong.workflow.record'].sudo().search( + [('code', '=', record.code)], + limit=1 + ) + if workflow: + record.approve_state = workflow.approve_state + + @api.depends_context('uid', 'company') + @api.depends('company_id') + def _compute_is_company(self): + active_company_ids = self.env.companies + for employee in self: + employee.is_company = employee.company_id in active_company_ids + + def _search_part_of_company(self, operator, value): + company_ids = self.env.companies.ids # 获取公司ID列表 + # 处理反向操作符 + if not value: + operator = '!=' if operator == '=' else '=' + # 使用company_id in company_ids的结构 + if operator == '=': + return [('company_id', 'in', company_ids)] + else: # operator == '!=' + return [('company_id', 'not in', company_ids)] @api.depends('name') def _compute_text(self): diff --git a/yuthon_official/models/yuthon_official_incoming_normal.py b/yuthon_official/models/yuthon_official_incoming_normal.py index 2ec3c83fe..1055e4ebb 100644 --- a/yuthon_official/models/yuthon_official_incoming_normal.py +++ b/yuthon_official/models/yuthon_official_incoming_normal.py @@ -20,6 +20,7 @@ class YuthonOfficialIncomingNormal(models.Model): main_text = fields.Text(string='正文') come_file_id = fields.Char(string="来文编号") incoming_file_id = fields.Char(string="收文编号") + approve_state = fields.Selection([("start", "草稿"), ("pending", "审批中"), ("end", "结束")], string="审批状态", compute='_compute_workflow', index=True) urgency_level = fields.Selection([('normal', '一般'), ('urgent', '紧急'), ('extra_urgent', '特急')], string="紧急程度", default='normal') secret_level = fields.Selection([('normal', '一般'), ('urgent', '秘密'), ('secret', '机密'), ('so_secret', '绝密')], string="密级") time_date = fields.Datetime(string="日期", default=fields.Datetime.now) @@ -45,7 +46,8 @@ class YuthonOfficialIncomingNormal(models.Model): room_booking_ids = fields.One2many('room.booking', 'incoming_normal_id', string="会议关联") issue_library_ids = fields.One2many('issue.library', 'incoming_normal_id', string="议题关联") related_issues_id = fields.Many2one('issue.library', string="关联议题") - + is_void = fields.Boolean(string="作废", compute='_compute_workflow', store=True) + # 显示用的办结时间(如果有审批日志,取最后一条日志的时间;否则使用write_date) display_end_time = fields.Datetime( string="办结时间", @@ -103,6 +105,20 @@ class YuthonOfficialIncomingNormal(models.Model): else: return [('company_id', 'not in', company_ids)] + @api.depends('code') + def _compute_workflow(self): + for record in self: + record.approve_state = False + record.is_void = False + workflow = self.env['soong.workflow.record'].sudo().search( + [('code', '=', record.code)], + limit=1 + ) + if workflow: + record.approve_state = workflow.approve_state + if workflow.reclaim_state == True: + record.is_void = True + @api.onchange('is_issues') def _onchange_is_issues(self): if self.is_issues: diff --git a/yuthon_official/views/yuthon_official_incoming_normal_views.xml b/yuthon_official/views/yuthon_official_incoming_normal_views.xml index 5e27a83df..4fcbcac10 100644 --- a/yuthon_official/views/yuthon_official_incoming_normal_views.xml +++ b/yuthon_official/views/yuthon_official_incoming_normal_views.xml @@ -9,10 +9,12 @@ - - + + + @@ -104,7 +106,7 @@ 行政收文 yuthon.official.incoming.normal - [('is_company','=',True)] + [('is_company','=',True), ('is_void','=',False)] tree,form \ No newline at end of file diff --git a/yuthon_official/views/yuthon_official_views.xml b/yuthon_official/views/yuthon_official_views.xml index b0f8bd899..ba08395dd 100644 --- a/yuthon_official/views/yuthon_official_views.xml +++ b/yuthon_official/views/yuthon_official_views.xml @@ -9,11 +9,13 @@ - + - + +