# -*- 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) # Fields used to group bookings in gantt view 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) # pengyuthon 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="会议参加人员",store=True) other_company_id = fields.Many2one('res.partner', 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="关联流程类型") #打开选择范围弹窗 def open_select_range_wizard(self): return { 'name': '选择范围', 'type': 'ir.actions.act_window', 'view_mode': 'form', 'res_model': 'room.select.range.wizard', 'target': 'new', 'context': {'default_room_booking_id': self.id}, } @api.model_create_multi def create(self, vals_list): bookings = super(RoomBooking, self).create(vals_list) # Notify frontend views of new bookings 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): """ """ if self.room_users_ids: # 便利内部人员和其他人员 html_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.depends('department_ids', 'employee_ids', 'users_role_ids') # def compute_room_users_ids(self): # department_users_list = users_role_list = [] # for users in self: # if users.department_ids: # # 查看是否下级部门 # for depart in users.department_ids: # department_users_data = users.env['hr.employee'].search([('department_id', '=', depart.id)]) # department_users_list = [de.user_id.id for de in department_users_data] # if depart.child_ids: # for child in depart.child_ids: # department_child_users_data = users.env['hr.employee'].search( # [('department_id', '=', child.id)]) # department_users_list += [de.user_id.id for de in department_child_users_data] # employee_users_list = users.employee_ids.user_id.ids # if users.users_role_ids: # for user_id in users.users_role_ids: # user_role_data = users.env['hr.employee'].search(['|', ('user_role_id', '=', user_id.id), ('user_role_ids', '=', user_id.id)]) # users_role_list = [ro.user_id.id for ro in user_role_data] # publish_user_list = department_users_list + employee_users_list + users_role_list # false_none_list = [x for x in publish_user_list if x != False] # users.room_users_ids = [(6, 0, list(set(false_none_list)))] @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, # 'sign_user_name': external.name, # 单位名称里面的联系人 # 'job': external.function, # 'mobile': external.mobile, '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, }) # 企业用户发送通知 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') # odoo发通知 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