mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
502 lines
22 KiB
Python
502 lines
22 KiB
Python
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
import logging
|
|
from datetime import timedelta
|
|
from odoo import models, fields, api, _
|
|
from odoo.exceptions import ValidationError
|
|
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class HotelReservation(models.Model):
|
|
_inherit = 'hotel.reservation'
|
|
|
|
reserve_color = fields.Char(compute='_compute_color', string='Color',
|
|
store=True)
|
|
reserve_color_text = fields.Char(compute='_compute_color', string='Color',
|
|
store=True)
|
|
|
|
@api.multi
|
|
def _generate_color(self):
|
|
self.ensure_one()
|
|
|
|
reserv_color = '#FFFFFF'
|
|
reserv_color_text = '#000000'
|
|
user = self.env.user
|
|
if self.reservation_type == 'staff':
|
|
reserv_color = user.color_staff
|
|
reserv_color_text = user.color_letter_staff
|
|
elif self.reservation_type == 'out':
|
|
reserv_color = user.color_dontsell
|
|
reserv_color_text = user.color_letter_dontsell
|
|
elif self.to_assign:
|
|
reserv_color = user.color_to_assign
|
|
reserv_color_text = user.color_letter_to_assign
|
|
elif self.state == 'draft':
|
|
reserv_color = user.color_pre_reservation
|
|
reserv_color_text = user.color_letter_pre_reservation
|
|
elif self.state == 'confirm':
|
|
if self.folio_id.pending_amount <= 0:
|
|
reserv_color = user.color_reservation_pay
|
|
reserv_color_text = user.color_letter_reservation_pay
|
|
else:
|
|
reserv_color = user.color_reservation
|
|
reserv_color_text = user.color_letter_reservation
|
|
elif self.state == 'booking':
|
|
if self.folio_id.pending_amount <= 0:
|
|
reserv_color = user.color_stay_pay
|
|
reserv_color_text = user.color_letter_stay_pay
|
|
else:
|
|
reserv_color = user.color_stay
|
|
reserv_color_text = user.color_letter_stay
|
|
else:
|
|
if self.folio_id.pending_amount <= 0:
|
|
reserv_color = user.color_checkout
|
|
reserv_color_text = user.color_letter_checkout
|
|
else:
|
|
reserv_color = user.color_payment_pending
|
|
reserv_color_text = user.color_letter_payment_pending
|
|
return (reserv_color, reserv_color_text)
|
|
|
|
@api.depends('state', 'reservation_type', 'folio_id.pending_amount', 'to_assign')
|
|
def _compute_color(self):
|
|
for record in self:
|
|
colors = record._generate_color()
|
|
record.update({
|
|
'reserve_color': colors[0],
|
|
'reserve_color_text': colors[1],
|
|
})
|
|
|
|
@api.model
|
|
def _hcalendar_reservation_data(self, reservations):
|
|
json_reservations = []
|
|
json_reservation_tooltips = {}
|
|
for reserv in reservations:
|
|
json_reservations.append({
|
|
'room_id': reserv['room_id'],
|
|
'id': reserv['id'],
|
|
'name': reserv['closure_reason'] or _('Out of service')
|
|
if reserv['reservation_type'] == 'out'
|
|
else reserv['partner_name'],
|
|
'adults': reserv['adults'],
|
|
'childrens': reserv['children'],
|
|
'checkin': reserv['checkin'],
|
|
'checkout': reserv['checkout'],
|
|
'folio_id': reserv['folio_id'],
|
|
'bgcolor': reserv['reserve_color'],
|
|
'color': reserv['reserve_color_text'],
|
|
'splitted': reserv['splitted'],
|
|
'parent_reservation': reserv['parent_reservation'] or False,
|
|
'read_only': False, # Read-Only
|
|
'fix_days': reserv['splitted'], # Fix Days
|
|
'fix_room': False, # Fix Rooms
|
|
'overbooking': reserv['overbooking'],
|
|
'state': reserv['state'],
|
|
'price_room_services_set': reserv['price_room_services_set'],
|
|
'amount_total': reserv['amount_total'],
|
|
'real_dates': [reserv['real_checkin'], reserv['real_checkout']]})
|
|
json_reservation_tooltips.update({
|
|
reserv['id']: {
|
|
'folio_name': reserv['folio_name'],
|
|
'name': _('Out of service')
|
|
if reserv['reservation_type'] == 'out'
|
|
else reserv['partner_name'],
|
|
'phone': reserv['mobile'] or reserv['phone']
|
|
or _('Phone not provided'),
|
|
'email': reserv['email'] or _('Email not provided'),
|
|
'room_type_name': reserv['room_type'],
|
|
'adults': reserv['adults'],
|
|
'children': reserv['children'],
|
|
'checkin': reserv['checkin'],
|
|
'checkout': reserv['checkout'],
|
|
'arrival_hour': reserv['arrival_hour'],
|
|
'departure_hour': reserv['departure_hour'],
|
|
'price_room_services_set': reserv['price_room_services_set'],
|
|
'invoices_paid': reserv['invoices_paid'],
|
|
'pending_amount': reserv['pending_amount'],
|
|
'type': reserv['reservation_type'] or 'normal',
|
|
'closure_reason': reserv['closure_reason'],
|
|
'out_service_description': reserv['out_service_description']
|
|
or _('No reason given'),
|
|
'splitted': reserv['splitted'],
|
|
'channel_type': reserv['channel_type'],
|
|
'real_dates': [reserv['real_checkin'], reserv['real_checkout']],
|
|
'board_service_name': reserv['board_service_name'] or _('No board services'),
|
|
'services': reserv['services'],
|
|
}
|
|
})
|
|
|
|
return (json_reservations, json_reservation_tooltips)
|
|
|
|
@api.model
|
|
def _hcalendar_room_data(self, rooms):
|
|
pricelist_id = self.env['ir.default'].sudo().get(
|
|
'res.config.settings', 'default_pricelist_id')
|
|
if pricelist_id:
|
|
pricelist_id = int(pricelist_id)
|
|
json_rooms = [
|
|
{
|
|
'id': room.id,
|
|
'name': room.name,
|
|
'capacity': room.capacity,
|
|
'class_name': room.room_type_id.class_id.name,
|
|
'class_id': room.room_type_id.class_id.id,
|
|
'shared': room.shared_room,
|
|
'price': room.room_type_id
|
|
and ['pricelist', room.room_type_id.id, pricelist_id,
|
|
room.room_type_id.name] or 0,
|
|
'room_type_name': room.room_type_id.name,
|
|
'room_type_id': room.room_type_id.id,
|
|
'floor_id': room.floor_id.id,
|
|
'amentity_ids': room.room_type_id.room_amenity_ids.ids,
|
|
} for room in rooms]
|
|
return json_rooms
|
|
|
|
@api.model
|
|
def _hcalendar_calendar_data(self, calendars):
|
|
return [
|
|
{
|
|
'id': calendar.id,
|
|
'name': calendar.name,
|
|
'segmentation_ids': calendar.segmentation_ids.ids,
|
|
'location_ids': calendar.location_ids.ids,
|
|
'amenity_ids': calendar.amenity_ids.ids,
|
|
'room_type_ids': calendar.room_type_ids.ids,
|
|
} for calendar in calendars]
|
|
|
|
@api.model
|
|
def _hcalendar_event_data(self, events):
|
|
json_events = [
|
|
{
|
|
'id': event.id,
|
|
'name': event.name,
|
|
'date': event.start,
|
|
'location': event.location,
|
|
} for event in events]
|
|
return json_events
|
|
|
|
@api.model
|
|
def get_hcalendar_calendar_data(self):
|
|
calendars = self.env['hotel.calendar'].search([])
|
|
res = self._hcalendar_calendar_data(calendars)
|
|
return res
|
|
|
|
@api.model
|
|
def get_hcalendar_reservations_data(self, dfrom_dt, dto_dt, rooms):
|
|
rdfrom_dt = dfrom_dt + timedelta(days=1) # Ignore checkout
|
|
rdfrom_str = rdfrom_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
|
dto_str = dto_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
|
self.env.cr.execute('''
|
|
SELECT
|
|
hr.id, hr.room_id, hr.adults, hr.children, hr.checkin, hr.checkout, hr.reserve_color, hr.reserve_color_text,
|
|
hr.splitted, hr.parent_reservation, hr.overbooking, hr.state, hr.real_checkin, hr.real_checkout,
|
|
hr.out_service_description, hr.arrival_hour, hr.departure_hour, hr.channel_type,
|
|
hr.price_room_services_set,
|
|
|
|
hf.id as folio_id, hf.name as folio_name, hf.reservation_type, hf.invoices_paid, hf.pending_amount,
|
|
hf.amount_total,
|
|
|
|
rp.mobile, rp.phone, rp.email, rp.name as partner_name,
|
|
|
|
pt.name as room_type,
|
|
|
|
array_agg(pt2.name) FILTER (WHERE pt2.show_in_calendar = TRUE) as services,
|
|
|
|
rcr.name as closure_reason,
|
|
|
|
hbs.name as board_service_name
|
|
FROM hotel_reservation AS hr
|
|
LEFT JOIN hotel_folio AS hf ON hr.folio_id = hf.id
|
|
LEFT JOIN hotel_room_type AS hrt ON hr.room_type_id = hrt.id
|
|
LEFT JOIN product_product AS pp ON hrt.product_id = pp.id
|
|
LEFT JOIN product_template AS pt ON pp.product_tmpl_id = pt.id
|
|
LEFT JOIN res_partner AS rp ON hf.partner_id = rp.id
|
|
LEFT JOIN room_closure_reason as rcr
|
|
ON hf.closure_reason_id = rcr.id
|
|
LEFT JOIN hotel_board_service_room_type_rel AS hbsrt ON hr.board_service_room_id = hbsrt.id
|
|
LEFT JOIN hotel_board_service AS hbs ON hbsrt.hotel_board_service_id = hbs.id
|
|
LEFT JOIN hotel_service AS hs ON hr.id = hs.ser_room_line
|
|
LEFT JOIN product_product AS pp2 ON hs.product_id = pp2.id
|
|
LEFT JOIN product_template AS pt2 ON pp2.product_tmpl_id = pt2.id
|
|
WHERE room_id IN %s AND (
|
|
(checkin <= %s AND checkout >= %s AND checkout <= %s)
|
|
OR (checkin >= %s AND checkout <= %s)
|
|
OR (checkin >= %s AND checkin <= %s AND checkout >= %s)
|
|
OR (checkin <= %s AND checkout >= %s))
|
|
GROUP BY hr.id, hf.id, pt.name, rcr.name, hbs.name, rp.mobile, rp.phone, rp.email, rp.name
|
|
ORDER BY checkin DESC, checkout ASC, adults DESC, children DESC
|
|
''', (tuple(rooms.ids),
|
|
rdfrom_str, rdfrom_str, dto_str,
|
|
rdfrom_str, dto_str,
|
|
rdfrom_str, dto_str, dto_str,
|
|
rdfrom_str, dto_str))
|
|
return self._hcalendar_reservation_data(self.env.cr.dictfetchall())
|
|
|
|
@api.model
|
|
def get_hcalendar_pricelist_data(self, dfrom_dt, dto_dt):
|
|
pricelist_id = self.env['ir.default'].sudo().get(
|
|
'res.config.settings', 'default_pricelist_id')
|
|
if pricelist_id:
|
|
pricelist_id = int(pricelist_id)
|
|
|
|
room_types_ids = self.env['hotel.room.type'].search([])
|
|
|
|
dfrom_str = dfrom_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
|
dto_str = dto_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
|
|
|
self.env.cr.execute('''
|
|
WITH RECURSIVE gen_table_days AS (
|
|
SELECT hrt.id, %s::Date AS date
|
|
FROM hotel_room_type AS hrt
|
|
UNION ALL
|
|
SELECT hrt.id, (td.date + INTERVAL '1 day')::Date
|
|
FROM gen_table_days as td
|
|
LEFT JOIN hotel_room_type AS hrt ON hrt.id=td.id
|
|
WHERE td.date < %s
|
|
)
|
|
SELECT
|
|
TO_CHAR(gtd.date, 'DD/MM/YYYY') as date, gtd.id as room_type_id,
|
|
pt.name, ppi.fixed_price as price, pt.list_price
|
|
FROM gen_table_days AS gtd
|
|
LEFT JOIN hotel_room_type AS hrt ON hrt.id = gtd.id
|
|
LEFT JOIN product_product AS pp ON pp.id = hrt.product_id
|
|
LEFT JOIN product_template AS pt ON pt.id = pp.product_tmpl_id
|
|
LEFT JOIN product_pricelist_item AS ppi ON ppi.date_start = gtd.date AND ppi.date_end = gtd.date AND ppi.product_tmpl_id = pt.id
|
|
WHERE gtd.id IN %s
|
|
ORDER BY gtd.id ASC, gtd.date ASC
|
|
''', (dfrom_str, dto_str, tuple(room_types_ids.ids)))
|
|
query_results = self.env.cr.dictfetchall()
|
|
|
|
json_data = {}
|
|
for results in query_results:
|
|
if results['room_type_id'] not in json_data:
|
|
json_data.setdefault(results['room_type_id'], {}).update({
|
|
'title': results['name'],
|
|
'room': results['room_type_id'],
|
|
})
|
|
json_data[results['room_type_id']].setdefault('days', {}).update({
|
|
results['date']: results['price'] or results['list_price']
|
|
})
|
|
|
|
json_rooms_prices = {}
|
|
for prices in list(json_data.values()):
|
|
json_rooms_prices.setdefault(pricelist_id, []).append(prices)
|
|
return json_rooms_prices
|
|
|
|
@api.model
|
|
def get_hcalendar_restrictions_data(self, dfrom_dt, dto_dt):
|
|
restriction_id = self.env['ir.default'].sudo().get(
|
|
'res.config.settings', 'default_restriction_id')
|
|
if restriction_id:
|
|
restriction_id = int(restriction_id)
|
|
|
|
# Get Restrictions
|
|
json_rooms_rests = {}
|
|
room_typed_ids = self.env['hotel.room.type'].search(
|
|
[],
|
|
order='sequence ASC')
|
|
room_type_rest_obj = self.env['hotel.room.type.restriction.item']
|
|
rtype_rest_ids = room_type_rest_obj.search([
|
|
('room_type_id', 'in', room_typed_ids.ids),
|
|
('date', '>=', dfrom_dt),
|
|
('date', '<=', dto_dt),
|
|
('restriction_id', '=', restriction_id)
|
|
])
|
|
|
|
for room_type in room_typed_ids:
|
|
days = {}
|
|
rest_ids = rtype_rest_ids.filtered(
|
|
lambda x: x.room_type_id == room_type)
|
|
for rest_id in rest_ids:
|
|
days.update({
|
|
fields.Date.from_string(rest_id.date).strftime("%d/%m/%Y"): (
|
|
rest_id.min_stay,
|
|
rest_id.min_stay_arrival,
|
|
rest_id.max_stay,
|
|
rest_id.max_stay_arrival,
|
|
rest_id.closed,
|
|
rest_id.closed_arrival,
|
|
rest_id.closed_departure)
|
|
})
|
|
json_rooms_rests.update({room_type.id: days})
|
|
return json_rooms_rests
|
|
|
|
@api.model
|
|
def get_hcalendar_events_data(self, dfrom_dt, dto_dt):
|
|
user_id = self.env['res.users'].browse(self.env.uid)
|
|
domain = [
|
|
'|', '&',
|
|
('start', '<=', dto_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)),
|
|
('stop', '>=', dfrom_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)),
|
|
'&',
|
|
('start', '>=', dfrom_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)),
|
|
('stop', '<=', dto_dt.strftime(DEFAULT_SERVER_DATE_FORMAT))
|
|
]
|
|
if user_id.pms_allowed_events_tags:
|
|
domain.append(('categ_ids', 'in', user_id.pms_allowed_events_tags))
|
|
if user_id.pms_denied_events_tags:
|
|
domain.append(
|
|
('categ_ids', 'not in', user_id.pms_denied_events_tags))
|
|
events_raw = self.env['calendar.event'].search(domain)
|
|
return self._hcalendar_event_data(events_raw)
|
|
|
|
@api.model
|
|
def get_hcalendar_settings(self):
|
|
user_id = self.env['res.users'].browse(self.env.uid)
|
|
type_move = user_id.pms_type_move
|
|
return {
|
|
'divide_rooms_by_capacity': user_id.pms_divide_rooms_by_capacity,
|
|
'eday_week': user_id.pms_end_day_week,
|
|
'eday_week_offset': user_id.pms_end_day_week_offset,
|
|
'days': user_id.pms_default_num_days,
|
|
'allow_invalid_actions': type_move == 'allow_invalid',
|
|
'assisted_movement': type_move == 'assisted',
|
|
'default_arrival_hour': self.env['ir.default'].sudo().get(
|
|
'res.config.settings', 'default_arrival_hour'),
|
|
'default_departure_hour': self.env['ir.default'].sudo().get(
|
|
'res.config.settings', 'default_departure_hour'),
|
|
'show_notifications': user_id.pms_show_notifications,
|
|
'show_pricelist': user_id.pms_show_pricelist,
|
|
'show_availability': user_id.pms_show_availability,
|
|
'show_num_rooms': user_id.pms_show_num_rooms,
|
|
}
|
|
|
|
@api.model
|
|
def get_hcalendar_all_data(self, dfrom, dto, withRooms=True):
|
|
if not dfrom or not dto:
|
|
raise ValidationError(_('Input Error: No dates defined!'))
|
|
|
|
dfrom_dt = fields.Date.from_string(dfrom)
|
|
dto_dt = fields.Date.from_string(dto)
|
|
rooms = self.env['hotel.room'].search([], order='sequence ASC')
|
|
|
|
json_res, json_res_tooltips = self.get_hcalendar_reservations_data(
|
|
dfrom_dt, dto_dt, rooms)
|
|
|
|
vals = {
|
|
'rooms': withRooms and self._hcalendar_room_data(rooms) or [],
|
|
'reservations': json_res,
|
|
'tooltips': json_res_tooltips,
|
|
'pricelist': self.get_hcalendar_pricelist_data(dfrom_dt, dto_dt),
|
|
'restrictions': self.get_hcalendar_restrictions_data(dfrom_dt,
|
|
dto_dt),
|
|
'events': self.get_hcalendar_events_data(dfrom_dt, dto_dt),
|
|
'calendars': withRooms and self.get_hcalendar_calendar_data()
|
|
or []
|
|
}
|
|
|
|
return vals
|
|
|
|
@api.multi
|
|
def generate_bus_values(self, naction, ntype, ntitle=''):
|
|
self.ensure_one()
|
|
return {
|
|
'action': naction,
|
|
'type': ntype,
|
|
'title': ntitle,
|
|
'room_id': self.room_id.id,
|
|
'reserv_id': self.id,
|
|
'folio_name': self.folio_id.name,
|
|
'partner_name': (self.closure_reason_id.name or _('Out of service'))
|
|
if self.reservation_type == 'out' else self.partner_id.name,
|
|
'adults': self.adults,
|
|
'children': self.children,
|
|
'checkin': self.checkin,
|
|
'checkout': self.checkout,
|
|
'arrival_hour': self.arrival_hour,
|
|
'departure_hour': self.departure_hour,
|
|
'folio_id': self.folio_id.id,
|
|
'reserve_color': self.reserve_color,
|
|
'reserve_color_text': self.reserve_color_text,
|
|
'splitted': self.splitted,
|
|
'parent_reservation': self.parent_reservation
|
|
and self.parent_reservation.id or 0,
|
|
'room_name': self.room_id.name,
|
|
'room_type_name': self.room_type_id.name,
|
|
'partner_phone': self.partner_id.mobile
|
|
or self.partner_id.phone or _('Undefined'),
|
|
'partner_email': self.partner_id.email or _('Undefined'),
|
|
'state': self.state,
|
|
'fix_days': self.splitted,
|
|
'overbooking': self.overbooking,
|
|
'price_room_services_set': self.price_room_services_set,
|
|
'invoices_paid': self.folio_id.invoices_paid,
|
|
'pending_amount': self.folio_id.pending_amount,
|
|
'reservation_type': self.reservation_type or 'normal',
|
|
'closure_reason': self.closure_reason_id.name,
|
|
'out_service_description': self.out_service_description
|
|
or _('No reason given'),
|
|
'real_dates': [self.real_checkin, self.real_checkout],
|
|
'channel_type': self.channel_type,
|
|
'board_service_name': self.board_service_room_id.hotel_board_service_id.name or _('No board services'),
|
|
'services': [service.product_id.name for service in self.service_ids
|
|
if service.product_id.show_in_calendar] or False,
|
|
}
|
|
|
|
@api.multi
|
|
def send_bus_notification(self, naction, ntype, ntitle=''):
|
|
hotel_cal_obj = self.env['bus.hotel.calendar']
|
|
for record in self:
|
|
if not isinstance(record.id, models.NewId) \
|
|
and not isinstance(record.folio_id.id, models.NewId) \
|
|
and not isinstance(record.partner_id.id, models.NewId):
|
|
hotel_cal_obj.send_reservation_notification(
|
|
record.generate_bus_values(naction, ntype, ntitle))
|
|
|
|
@api.model
|
|
def swap_reservations(self, fromReservsIds, toReservsIds):
|
|
from_reservs = self.env['hotel.reservation'].browse(fromReservsIds)
|
|
to_reservs = self.env['hotel.reservation'].browse(toReservsIds)
|
|
|
|
if not any(from_reservs) or not any(to_reservs):
|
|
raise ValidationError(_("Invalid swap parameters"))
|
|
|
|
max_from_persons = max(
|
|
from_reservs.mapped(lambda x: x.adults))
|
|
max_to_persons = max(
|
|
to_reservs.mapped(lambda x: x.adults))
|
|
|
|
from_room = from_reservs[0].room_id
|
|
to_room = to_reservs[0].room_id
|
|
from_overbooking = from_reservs[0].overbooking
|
|
to_overbooking = to_reservs[0].overbooking
|
|
|
|
if max_from_persons > to_room.capacity or \
|
|
max_to_persons > from_room.capacity:
|
|
raise ValidationError("Invalid swap operation: wrong capacity")
|
|
|
|
for record in from_reservs:
|
|
record.with_context({'ignore_avail_restrictions': True}).write({
|
|
'room_id': to_room.id,
|
|
'overbooking': to_overbooking,
|
|
})
|
|
for record in to_reservs:
|
|
record.with_context({'ignore_avail_restrictions': True}).write({
|
|
'room_id': from_room.id,
|
|
'overbooking': from_overbooking,
|
|
})
|
|
|
|
return True
|
|
|
|
@api.model
|
|
def create(self, vals):
|
|
reservation_id = super(HotelReservation, self).create(vals)
|
|
reservation_id.send_bus_notification('create',
|
|
'notify',
|
|
_("Reservation Created"))
|
|
return reservation_id
|
|
|
|
@api.multi
|
|
def write(self, vals):
|
|
_logger.info("RESERV WRITE")
|
|
ret = super(HotelReservation, self).write(vals)
|
|
self.send_bus_notification('write', 'noshow')
|
|
return ret
|
|
|
|
@api.multi
|
|
def unlink(self):
|
|
self.send_bus_notification('unlink',
|
|
'warn',
|
|
_("Reservation Deleted"))
|
|
return super(HotelReservation, self).unlink()
|