402 lines
19 KiB
Python
402 lines
19 KiB
Python
# -*- coding: utf-8 -*-
|
||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||
import qrcode
|
||
import base64
|
||
from io import BytesIO
|
||
from odoo import api, fields, models, _
|
||
from odoo.exceptions import ValidationError, UserError
|
||
import os
|
||
from datetime import timedelta
|
||
from datetime import datetime
|
||
|
||
"""富文本字段保存无效"""
|
||
class RoomBooking(models.Model):
|
||
_name = "room.booking"
|
||
_inherit = ['mail.thread.main.attachment', 'mail.activity.mixin', 'soong.workflow.mixin']
|
||
_description = "Room Booking"
|
||
_order = "start_datetime desc, id"
|
||
|
||
name = fields.Char(string="标题", compute="_compute_name", store=True)
|
||
code = fields.Char(string="流水号", default=lambda self: self.env['ir.sequence'].next_by_code('room_booking_approval_code'))
|
||
room_id = fields.Many2one("room.room", string="Room", required=True, ondelete="cascade",
|
||
group_expand="_read_group_room_id", tracking=4)
|
||
start_datetime = fields.Datetime(string="Start Datetime", required=True, tracking=2)
|
||
start_datetime_local = fields.Datetime(string="本地开始时间", compute="_compute_start_datetime_local", store=False)
|
||
stop_datetime = fields.Datetime(string="End Datetime", required=True, tracking=3)
|
||
organizer_id = fields.Many2one("res.users", string="Organizer",
|
||
default=lambda self: self.env.user.id if not self.env.user._is_public() else False,
|
||
tracking=5)
|
||
|
||
office_id = fields.Many2one(related="room_id.office_id", string="Office", readonly=True, store=True)
|
||
company_id = fields.Many2one(related="user_id.employee_id.company_id", string="Company", readonly=True, store=True)
|
||
department_id = fields.Many2one('hr.department', related='user_id.employee_id.department_id', string="部门")
|
||
is_notice = fields.Boolean(string="是否发布通知", default=True)
|
||
user_id = fields.Many2one('res.users', string='申请人', default=lambda self: self.env.user.id)
|
||
task_time_ids = fields.Many2many('yuthon.task.time', string='提醒')
|
||
meeting_type = fields.Selection([('up', '线上'), ('down', '线下'), ('exterior', '外部')], default='down', string="会议形式",
|
||
required=True)
|
||
urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度',
|
||
default='normal')
|
||
meeting_type_id = fields.Many2one('room.meeting.type', string="会议类型")
|
||
self_users_ids = fields.Many2many(
|
||
'res.users',
|
||
string="当前经办人",
|
||
relation='issue_room_approval_self_users_res_users_rel',
|
||
column1='approval_id',
|
||
column2='self_user_id',
|
||
default=lambda self: [(6, 0, [self.env.user.id])]
|
||
)
|
||
room_users_ids = fields.Many2many(
|
||
'res.users',
|
||
string="参会人员",
|
||
relation='issue_room_approval_room_users_res_users_rel',
|
||
column1='approval_id',
|
||
column2='room_user_id'
|
||
)
|
||
room_users_names = fields.Char(string="参会人员char", compute="_compute_room_users", store=True)
|
||
other_company_id = fields.Many2one('res.partner', string="外部公司")
|
||
host = fields.Many2one('hr.employee', string="主持人")
|
||
stationed_group = fields.Boolean(string="派驻组")
|
||
recorder_ids = fields.Many2many('hr.employee', string='记录员')
|
||
other_company_user_ids = fields.Many2many('other.meet.employee', string="其他人员")
|
||
fleet_id = fields.Many2one('fleet.vehicle', string="车辆")
|
||
meeting_place = fields.Char(string="会议地点")
|
||
meeting_sing_ids = fields.One2many('room.meeting.sign', 'room_booking_id', string="签到详情")
|
||
meeting_data = fields.Html(string="会议说明", sanitize=False)
|
||
wechat_meetingid = fields.Char("企业微信会议室ID")
|
||
documents_ids = fields.Many2many('ir.attachment', string="会议材料附件")
|
||
is_meeting = fields.Boolean(compute="_compute_is_meeting", store=True)
|
||
should_user = fields.Integer(string="应到")
|
||
right_user = fields.Integer(string="实到")
|
||
driver = fields.Many2one('hr.employee', string="司机")
|
||
partner_ids = fields.Many2many('res.partner', string="日程安排人员")
|
||
is_this_company = fields.Boolean(string='本公司', default=True)
|
||
work_end = fields.Boolean(string="结束")
|
||
name_title = fields.Char(string="会议通知")
|
||
# 关联流程
|
||
relevancy_type = fields.Selection([('none', '无')], string="关联流程类型")
|
||
|
||
@api.depends('meeting_type_id')
|
||
def _compute_is_meeting(self):
|
||
for record in self:
|
||
if record.meeting_type_id:
|
||
record.is_meeting = record.meeting_type_id.name in ['党委会', '班子会', '董事会']
|
||
else:
|
||
record.is_meeting = False
|
||
|
||
@api.depends('room_users_ids')
|
||
def _compute_room_users(self):
|
||
for i in self:
|
||
employee_list = [rec.name for rec in i.room_users_ids]
|
||
i.room_users_names = ','.join(employee_list)
|
||
|
||
# @api.model_create_multi
|
||
# def create(self, vals_list):
|
||
# bookings = super(RoomBooking, self).create(vals_list)
|
||
# for room, bookings in bookings.grouped("room_id").items():
|
||
# room._notify_booking_view("create", bookings)
|
||
# data = vals_list[0]
|
||
# if data.get('partner_ids'):
|
||
# self.env['calendar.event'].create({
|
||
# 'name': data['name'],
|
||
# 'start': data['start_datetime'],
|
||
# 'stop': data['stop_datetime'],
|
||
# 'partner_ids': data['partner_ids'],
|
||
# 'sth_type': 'meet',
|
||
# 'room_booking_id': bookings.id,
|
||
# })
|
||
# return bookings
|
||
|
||
@api.onchange('room_id')
|
||
def onchange_meeting_data(self):
|
||
self.meeting_data = self.room_id.description
|
||
|
||
def button_meeting_data(self):
|
||
"""
|
||
<select>
|
||
<option value="option1">Option 1</option>
|
||
<option value="option2">Option 2</option>
|
||
<option value="option3">Option 3</option>
|
||
</select>
|
||
"""
|
||
if self.room_users_ids:
|
||
# 便利内部人员和其他人员
|
||
html_select = '<select>\n<option value="name0"> </option>\n'
|
||
for index, user in enumerate(self.room_users_ids, start=1):
|
||
value = f'name{index}'
|
||
option_text = user.name
|
||
html_select += f' <option value="{value}">{option_text}</option>\n'
|
||
for index, partner in enumerate(self.other_company_user_ids, start=99):
|
||
value_partner = f'name{index}'
|
||
option_partner_name = partner.name
|
||
html_select += f' <option value="{value_partner}">{option_partner_name}</option>\n'
|
||
html_select += '</select>'
|
||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||
parent_dir = os.path.dirname(current_dir)
|
||
file_path_template = os.path.join(parent_dir, 'static', 'src', 'html', 'template1.html')
|
||
with open(file_path_template, 'r', encoding='utf-8') as file:
|
||
html_content = file.read().replace('姓名', html_select)
|
||
self.write({'meeting_data': html_content})
|
||
|
||
# @api.onchange('department_ids', 'employee_ids', 'users_role_ids', 'room_users_ids')
|
||
# def onchange_partner_ids(self):
|
||
# for par in self:
|
||
# if par.room_users_ids:
|
||
# res_users_list = [users.partner_id.id.origin for users in par.room_users_ids]
|
||
# par.partner_ids = [(6, 0, res_users_list)]
|
||
# else:
|
||
# par.partner_ids = [(5, 0, 0)]
|
||
|
||
def meet_notice(self):
|
||
if self.is_notice:
|
||
for internal in self.room_users_ids:
|
||
self.env['room.meeting.sign'].create({
|
||
'room_booking_id': self.id,
|
||
'sign_user_name': internal.name,
|
||
'partner_name': internal.employee_id.department_id.name,
|
||
'job': internal.employee_id.job_id.name,
|
||
'mobile': internal.employee_id.mobile_phone,
|
||
'user_type': 'insiders',
|
||
'sign_type': 'no',
|
||
})
|
||
event = self.env['calendar.event'].create({
|
||
'name': self.name,
|
||
'start': self.start_datetime,
|
||
'stop': self.stop_datetime,
|
||
})
|
||
for partner in self.room_users_ids.employee_ids:
|
||
self.env['calendar.attendee'].create({
|
||
'event_id': event.id,
|
||
'partner_id': partner.address_id.id,
|
||
})
|
||
task_type = self.env['task.type.setting'].search([('name', '=', '面试')])
|
||
employee_records = self.env['hr.employee'].search([('user_id', 'in', self.room_users_ids.ids)])
|
||
if not task_type:
|
||
task_type = self.env['task.type.setting'].create({
|
||
'name': '面试',
|
||
})
|
||
date_part = self.start_datetime.date()
|
||
self.env['yuthon.will.task'].create({
|
||
'start_date': date_part,
|
||
'task_type':task_type.id,
|
||
'employee_ids': [(6, 0, employee_records.ids)],
|
||
'task_state':'will',
|
||
'name': '面试安排'
|
||
})
|
||
# 企业用户发送通知
|
||
partner_ids = self.sudo().env['res.users'].search([('id', 'in', self.room_users_ids.ids)]).mapped(
|
||
'partner_id').ids
|
||
self.sudo().with_context(lang=self.env.lang)._message_auto_subscribe_notify(partner_ids, 'room.message_yuthon_room_users_data')
|
||
|
||
def meeting_sign_in(self):
|
||
# 页面填写信息之后返回给地址
|
||
code_url = self.get_base_url() + "/web/room?room_id=" + str(self.id)
|
||
# 制作填写表单的地址
|
||
qrcode_url = qrcode.make(code_url)
|
||
buffer = BytesIO()
|
||
qrcode_url.save(buffer, format='PNG')
|
||
qrcode_image = base64.b64encode(buffer.getvalue()).decode('utf-8')
|
||
# 返回给向导试图的字段
|
||
action = {
|
||
'type': 'ir.actions.act_window',
|
||
'name': _("签到二维码"),
|
||
'res_model': 'room.booking.sign.wizard',
|
||
'views': [(False, 'form')],
|
||
'target': 'new',
|
||
'context': {
|
||
'default_qrcode_image': qrcode_image,
|
||
}
|
||
}
|
||
return action
|
||
|
||
def meeting_partner_create(self):
|
||
# 页面填写信息之后返回给地址
|
||
code_url = self.get_base_url() + "/web/room/partner/create?room_id=" + str(self.id)
|
||
# 制作填写表单的地址
|
||
qrcode_url = qrcode.make(code_url)
|
||
buffer = BytesIO()
|
||
qrcode_url.save(buffer, format='PNG')
|
||
qrcode_image = base64.b64encode(buffer.getvalue()).decode('utf-8')
|
||
# 返回给向导试图的字段
|
||
action = {
|
||
'type': 'ir.actions.act_window',
|
||
'name': _("会议人员录入"),
|
||
'res_model': 'room.booking.sign.wizard',
|
||
'views': [(False, 'form')],
|
||
'target': 'new',
|
||
'context': {
|
||
'efault_default_qrcode_image': qrcode_image,
|
||
}
|
||
}
|
||
return action
|
||
|
||
def sync_room_get_attendee_list(self):
|
||
for record in self:
|
||
if record.wechat_meetingid and record.meeting_type == "up" and record.start_datetime < fields.Datetime.now() and record.stop_datetime > fields.Datetime.now():
|
||
sync_result, sync_data = self.sudo().env["wecom.apps"].sync_get_attendee_list(record.wechat_meetingid)
|
||
if sync_result:
|
||
attendees = sync_data.get("attendees", [])
|
||
for attendee in attendees:
|
||
if attendee.get("userid", ""):
|
||
user_obj = self.sudo().env["res_users"].search(
|
||
[("oauth_uid", '=', attendee.get("userid", ""))])
|
||
for obj in user_obj:
|
||
sign_obj = self.sudo().env["room.meeting.sign"].search(
|
||
[("sign_user_id", '=', obj.id), ("room_booking_id", "=", record.id)])
|
||
sign_obj.write({"sign_type": 'yes'})
|
||
|
||
@api.constrains("start_datetime", "stop_datetime")
|
||
def _check_date_boundaries(self):
|
||
for booking in self:
|
||
if booking.start_datetime >= booking.stop_datetime:
|
||
raise ValidationError(_(
|
||
"The start date of %(booking_name)s must be earlier than the end date.",
|
||
booking_name=booking.name
|
||
))
|
||
|
||
@api.constrains("start_datetime", "stop_datetime")
|
||
def _check_unique_slot(self):
|
||
min_start = min(self.mapped("start_datetime"))
|
||
max_stop = max(self.mapped("stop_datetime"))
|
||
bookings_by_room = self.search([("room_id", "in", self.room_id.ids), ("start_datetime", "<", max_stop),
|
||
("stop_datetime", ">", min_start)]).grouped("room_id")
|
||
for booking in self:
|
||
if bookings_by_room.get(booking.room_id) and bookings_by_room[booking.room_id].filtered(
|
||
lambda b: b.id != booking.id and b.start_datetime < booking.stop_datetime and b.stop_datetime > booking.start_datetime
|
||
):
|
||
raise ValidationError(_(
|
||
"Room %(room_name)s is already booked during the selected time slot.",
|
||
room_name=booking.room_id.name
|
||
))
|
||
|
||
# @api.model
|
||
# def create(self, vals):
|
||
#
|
||
# booking =
|
||
# employee_records = self.env['hr.employee'].search([('user_id', 'in', booking.room_users_ids.ids)]) if booking.room_users_ids else self.env['hr.employee']
|
||
#
|
||
# task_name = booking.name or f'会议_{booking.start_datetime.date()}'
|
||
#
|
||
# address = booking.room_id.name if booking.room_id else ''
|
||
#
|
||
# remarks = booking.meeting_data or ''
|
||
# # 确保room_users_ids有值
|
||
# room_users_ids = booking.room_users_ids.ids if booking.room_users_ids else []
|
||
#
|
||
# self_users_ids = booking.self_users_ids.ids if booking.self_users_ids else []
|
||
# # 创建日程记录
|
||
# task = self.env['yuthon.will.task'].create({
|
||
# 'name': task_name,
|
||
# 'start_date': booking.start_datetime.date(),
|
||
# 'task_type': 'meet',
|
||
# 'room_booking_id': booking.id,
|
||
# 'employee_ids': [(6, 0, employee_records.ids)] if employee_records else [(5, 0, 0)],
|
||
# 'task_state': 'will',
|
||
# 'address': address,
|
||
# 'remarks': remarks,
|
||
# 'users_ids': [(6, 0, room_users_ids)] if room_users_ids else [(5, 0, 0)],
|
||
# 'self_users_ids': [(6, 0, self_users_ids)] if self_users_ids else [(5, 0, 0)],
|
||
# })
|
||
# return super(RoomBooking, self).create(vals)
|
||
|
||
# ------------------------------------------------------
|
||
# CRUD / ORM
|
||
# ------------------------------------------------------
|
||
|
||
def sync_wechat_room(self):
|
||
for record in self:
|
||
admin_userid = record.user_id.oauth_uid
|
||
title = record.name
|
||
meeting_start = record.start_datetime.timestamp()
|
||
meeting_duration = record.stop_datetime.timestamp() - record.start_datetime.timestamp()
|
||
if not record.wechat_meetingid:
|
||
create_result, create_data = self.sudo().env["wecom.apps"].sync_meeting_create(
|
||
admin_userid=admin_userid,
|
||
title=title,
|
||
meeting_start=meeting_start,
|
||
meeting_duration=meeting_duration,
|
||
kwargs={
|
||
"location": record.room_id.name,
|
||
"invitees": {
|
||
"userid": [r.oauth_uid for r in record.room_users_ids]
|
||
},
|
||
}
|
||
)
|
||
if create_result:
|
||
record.wechat_meetingid = create_data.get("meetingid", "")
|
||
if not create_result:
|
||
raise UserError("同步企业微信会议失败:{0}".format(str(create_data)))
|
||
if record.wechat_meetingid:
|
||
update_result, update_data = self.sudo().env["wecom.apps"].sync_meeting_update(
|
||
meetingid=record.wechat_meetingid,
|
||
kwargs={
|
||
"title": title,
|
||
"meeting_start": meeting_start,
|
||
"meeting_duration": meeting_duration,
|
||
"location": record.room_id.name,
|
||
"invitees": {
|
||
"userid": [r.oauth_uid for r in record.room_users_ids]
|
||
},
|
||
}
|
||
)
|
||
if not update_result:
|
||
raise UserError("更新企业微信会议失败:{0}".format(str(update_data)))
|
||
|
||
def unlink(self):
|
||
# Notify frontend of deleted bookings
|
||
bookings_by_room = self.grouped("room_id")
|
||
for room, bookings in bookings_by_room.items():
|
||
room._notify_booking_view("delete", bookings)
|
||
if self.wechat_meetingid:
|
||
self.sudo().env["wecom.apps"].sync_meeting_cancel(meetingid=self.wechat_meetingid)
|
||
return super(RoomBooking, self).unlink()
|
||
|
||
def write(self, vals):
|
||
bookings_by_room = self.grouped("room_id")
|
||
res = super(RoomBooking, self).write(vals)
|
||
# Notify frontend of updated bookings
|
||
if new_room_id := vals.get("room_id"):
|
||
new_room = self.env["room.room"].browse(new_room_id)
|
||
for room, bookings in bookings_by_room.items():
|
||
room._notify_booking_view("delete", bookings)
|
||
new_room._notify_booking_view("create", bookings)
|
||
elif {"name", "start_datetime", "stop_datetime"} & vals.keys():
|
||
for room, bookings in bookings_by_room.items():
|
||
room._notify_booking_view("update", bookings)
|
||
# self.sync_wechat_room()
|
||
return res
|
||
|
||
@api.model
|
||
def _read_group_room_id(self, rooms, domain, order):
|
||
# Display all the rooms in the gantt view even if they have no booking,
|
||
# and order them by office first, then by usual order (because the
|
||
# office name is shown in the display name)
|
||
if self.env.context.get("room_booking_gantt_show_all_rooms"):
|
||
room_ids = rooms._search([], order="office_id," + order)
|
||
return rooms.browse(room_ids)
|
||
return rooms
|
||
|
||
def action_print_room_booking_agenda(self):
|
||
"""打印会议议程安排表"""
|
||
self.ensure_one()
|
||
report = self.env.ref('room.action_room_booking_agenda_report')
|
||
return report.report_action(self)
|
||
|
||
@api.depends('start_datetime')
|
||
def _compute_start_datetime_local(self):
|
||
"""计算本地时区时间(UTC+8)"""
|
||
for record in self:
|
||
if record.start_datetime:
|
||
record.start_datetime_local = record.start_datetime + timedelta(hours=8)
|
||
else:
|
||
record.start_datetime_local = False
|
||
|
||
def get_edit_button_state(self):
|
||
"""会议室预约不做流程控制,编辑按钮常显示"""
|
||
self.ensure_one()
|
||
return {
|
||
'show_edit': True,
|
||
'self_users_ids': self.self_users_ids.ids,
|
||
'work_end': self.work_end,
|
||
}
|