336 lines
16 KiB
Python
336 lines
16 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
|
|
|
|
|
|
"""富文本字段保存无效"""
|
|
class RoomBooking(models.Model):
|
|
_name = "room.booking"
|
|
_inherit = ["mail.thread"]
|
|
_description = "Room Booking"
|
|
_order = "start_datetime desc, id"
|
|
|
|
name = fields.Char(string="标题", required=True, tracking=1)
|
|
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)
|
|
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="room_id.company_id", string="Company", readonly=True, store=True)
|
|
|
|
department_id = fields.Many2one('hr.department', related='organizer_id.employee_id.department_id', string="申请人部门")
|
|
is_notice = fields.Boolean(string="是否发布通知", default=True)
|
|
is_hour = fields.Boolean(string="提前一小时", default=True)
|
|
is_day = fields.Boolean(string="提前一天", default=True)
|
|
meeting_type = fields.Selection([('up', '线上'), ('down', '线下'), ('exterior', '外部')], default='down', string="会议形式",
|
|
required=True)
|
|
meeting_type_id = fields.Many2one('room.meeting.type', string="会议类型")
|
|
room_users_ids = fields.Many2many('res.users', string="出席人员")
|
|
room_users_names = fields.Char(string="出席人员char", compute="_compute_room_users", store=True)
|
|
other_company_id = fields.Many2one('res.partner', string="外部公司")
|
|
host = fields.Many2one('res.users', string="主持人")
|
|
stationed_group = fields.Boolean(string="驻组参会")
|
|
other_company_user_ids = fields.One2many(related="other_company_id.child_ids", 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")
|
|
|
|
should_user = fields.Integer(string="应到")
|
|
right_user = fields.Integer(string="实到")
|
|
driver = fields.Many2one('hr.employee', string="司机")
|
|
partner_ids = fields.Many2many('res.partner', string="日程安排人员")
|
|
|
|
# 关联流程
|
|
relevancy_type = fields.Selection([('none', '无')], string="关联流程类型")
|
|
|
|
@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.onchange('meeting_type_id')
|
|
def _onchange_meeting_type_id(self):
|
|
if self.meeting_type_id:
|
|
self.room_users_ids = self.meeting_type_id.users_ids
|
|
self.host = self.meeting_type_id.host_user_id
|
|
else:
|
|
self.room_users_ids = [(5, 0, 0)]
|
|
self.host = False
|
|
|
|
@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]
|
|
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',
|
|
})
|
|
# 缺少逻辑
|
|
# for external in self.other_user_ids:
|
|
# self.env['room.meeting.sign'].create({
|
|
# 'room_booking_id': self.id,
|
|
# 'partner_name': external.name,
|
|
# 'user_type': 'outsiders',
|
|
# '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': '面试',
|
|
})
|
|
self.env['yuthon.will.task'].create({
|
|
'start_time': self.start_datetime,
|
|
'task_type':task_type.id,
|
|
'employee_ids': [(6, 0, employee_records.ids)],
|
|
'task_state':'will',
|
|
'task_content': '面试安排'
|
|
})
|
|
# 企业用户发送通知
|
|
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': {
|
|
'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
|
|
))
|
|
|
|
# ------------------------------------------------------
|
|
# CRUD / ORM
|
|
# ------------------------------------------------------
|
|
|
|
def sync_wechat_room(self):
|
|
for record in self:
|
|
admin_userid = record.organizer_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
|