mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
1299 lines
56 KiB
Python
1299 lines
56 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2017 Alexandre Díaz
|
|
# Copyright 2017 Dario Lodeiros
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
from odoo.exceptions import except_orm, UserError, ValidationError
|
|
from odoo.tools import (
|
|
misc,
|
|
DEFAULT_SERVER_DATE_FORMAT,
|
|
DEFAULT_SERVER_DATETIME_FORMAT)
|
|
from odoo import models, fields, api, _
|
|
from decimal import Decimal
|
|
from dateutil.relativedelta import relativedelta
|
|
from datetime import datetime, timedelta, date
|
|
from odoo.addons.hotel import date_utils
|
|
import pytz
|
|
import time
|
|
import logging
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
from odoo.addons import decimal_precision as dp
|
|
|
|
class HotelReservation(models.Model):
|
|
|
|
@api.multi
|
|
def _generate_color(self):
|
|
self.ensure_one()
|
|
now_utc_dt = date_utils.now()
|
|
# unused variables
|
|
# diff_checkin_now = date_utils.date_diff(now_utc_dt, self.checkin,
|
|
# hours=False)
|
|
# diff_checkout_now = date_utils.date_diff(now_utc_dt, self.checkout,
|
|
# hours=False)
|
|
|
|
ir_values_obj = self.env['ir.default']
|
|
reserv_color = '#FFFFFF'
|
|
reserv_color_text = '#000000'
|
|
# FIXME added for migration
|
|
return ('#4E9DC4', '#000000')
|
|
|
|
if self.reservation_type == 'staff':
|
|
reserv_color = ir_values_obj.get('res.config.settings',
|
|
'color_staff')
|
|
reserv_color_text = ir_values_obj.get(
|
|
'res.config.settings',
|
|
'color_letter_staff')
|
|
elif self.reservation_type == 'out':
|
|
reserv_color = ir_values_obj.get('res.config.settings',
|
|
'color_dontsell')
|
|
reserv_color_text = ir_values_obj.get(
|
|
'res.config.settings',
|
|
'color_letter_dontsell')
|
|
elif self.to_assign:
|
|
reserv_color = ir_values_obj.get('res.config.settings',
|
|
'color_to_assign')
|
|
reserv_color_text = ir_values_obj.get(
|
|
'res.config.settings',
|
|
'color_letter_to_assign')
|
|
elif self.state == 'draft':
|
|
reserv_color = ir_values_obj.get('res.config.settings',
|
|
'color_pre_reservation')
|
|
reserv_color_text = ir_values_obj.get(
|
|
'res.config.settings',
|
|
'color_letter_pre_reservation')
|
|
elif self.state == 'confirm':
|
|
if self.folio_id.invoices_amount == 0:
|
|
reserv_color = ir_values_obj.get(
|
|
'res.config.settings', 'color_reservation_pay')
|
|
reserv_color_text = ir_values_obj.get(
|
|
'res.config.settings', 'color_letter_reservation_pay')
|
|
else:
|
|
reserv_color = ir_values_obj.get(
|
|
'res.config.settings', 'color_reservation')
|
|
reserv_color_text = ir_values_obj.get(
|
|
'res.config.settings', 'color_letter_reservation')
|
|
elif self.state == 'booking':
|
|
if self.folio_id.invoices_amount == 0:
|
|
reserv_color = ir_values_obj.get(
|
|
'res.config.settings', 'color_stay_pay')
|
|
reserv_color_text = ir_values_obj.get(
|
|
'res.config.settings', 'color_letter_stay_pay')
|
|
else:
|
|
reserv_color = ir_values_obj.get(
|
|
'res.config.settings', 'color_stay')
|
|
reserv_color_text = ir_values_obj.get(
|
|
'res.config.settings', 'color_letter_stay')
|
|
else:
|
|
if self.folio_id.invoices_amount == 0:
|
|
reserv_color = ir_values_obj.get(
|
|
'res.config.settings', 'color_checkout')
|
|
reserv_color_text = ir_values_obj.get(
|
|
'res.config.settings', 'color_letter_checkout')
|
|
else:
|
|
reserv_color = ir_values_obj.get(
|
|
'res.config.settings', 'color_payment_pending')
|
|
reserv_color_text = ir_values_obj.get(
|
|
'res.config.settings', 'color_letter_payment_pending')
|
|
return (reserv_color, reserv_color_text)
|
|
|
|
@api.depends('state', 'reservation_type', 'folio_id.invoices_amount', 'to_assign')
|
|
def _compute_color(self):
|
|
_logger.info('_compute_color')
|
|
for rec in self:
|
|
colors = rec._generate_color()
|
|
rec.update({
|
|
'reserve_color': colors[0],
|
|
'reserve_color_text': colors[1],
|
|
})
|
|
rec.folio_id.color = colors[0]
|
|
|
|
# hotel_reserv_obj = self.env['hotel.reservation']
|
|
# if rec.splitted:
|
|
# master_reservation = rec.parent_reservation or rec
|
|
# splitted_reservs = hotel_reserv_obj.search([
|
|
# ('splitted', '=', True),
|
|
# '|', ('parent_reservation', '=', master_reservation.id),
|
|
# ('id', '=', master_reservation.id),
|
|
# ('folio_id', '=', rec.folio_id.id),
|
|
# ('id', '!=', rec.id),
|
|
# ])
|
|
# splitted_reservs.write({'reserve_color': rec.reserve_color})
|
|
|
|
@api.multi
|
|
def copy(self, default=None):
|
|
'''
|
|
@param self: object pointer
|
|
@param default: dict of default values to be set
|
|
'''
|
|
|
|
return super(HotelReservation, self).copy(default=default)
|
|
|
|
@api.multi
|
|
def _amount_line(self, field_name, arg):
|
|
'''
|
|
@param self: object pointer
|
|
@param field_name: Names of fields.
|
|
@param arg: User defined arguments
|
|
'''
|
|
return False
|
|
# return self.env['sale.order.line']._amount_line(field_name, arg)
|
|
|
|
@api.multi
|
|
def _number_packages(self, field_name, arg):
|
|
'''
|
|
@param self: object pointer
|
|
@param field_name: Names of fields.
|
|
@param arg: User defined arguments
|
|
'''
|
|
return False
|
|
# return self.env['sale.order.line']._number_packages(field_name, arg)
|
|
|
|
@api.multi
|
|
def set_call_center_user(self):
|
|
user = self.env['res.users'].browse(self.env.uid)
|
|
rec.call_center = user.has_group('hotel.group_hotel_call')
|
|
|
|
@api.multi
|
|
def _get_default_checkin(self):
|
|
folio = False
|
|
default_arrival_hour = self.env['ir.default'].sudo().get(
|
|
'res.config.settings', 'default_arrival_hour')
|
|
if 'folio_id' in self._context:
|
|
folio = self.env['hotel.folio'].search([
|
|
('id', '=', self._context['folio_id'])
|
|
])
|
|
if folio and folio.room_lines:
|
|
return folio.room_lines[0].checkin
|
|
else:
|
|
tz_hotel = self.env['ir.default'].sudo().get(
|
|
'res.config.settings', 'tz_hotel')
|
|
now_utc_dt = date_utils.now()
|
|
ndate = "%s %s:00" % \
|
|
(now_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT),
|
|
default_arrival_hour)
|
|
ndate_dt = date_utils.get_datetime(ndate, stz=tz_hotel)
|
|
ndate_dt = date_utils.dt_as_timezone(ndate_dt, 'UTC')
|
|
return ndate_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
|
|
|
@api.model
|
|
def _get_default_checkout(self):
|
|
folio = False
|
|
default_departure_hour = self.env['ir.default'].sudo().get(
|
|
'res.config.settings', 'default_departure_hour')
|
|
if 'folio_id' in self._context:
|
|
folio = self.env['hotel.folio'].search([
|
|
('id', '=', self._context['folio_id'])
|
|
])
|
|
if folio and folio.room_lines:
|
|
return folio.room_lines[0].checkout
|
|
else:
|
|
tz_hotel = self.env['ir.default'].sudo().get(
|
|
'res.config.settings', 'tz_hotel')
|
|
now_utc_dt = date_utils.now() + timedelta(days=1)
|
|
ndate = "%s %s:00" % \
|
|
(now_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT),
|
|
default_departure_hour)
|
|
ndate_dt = date_utils.get_datetime(ndate, stz=tz_hotel)
|
|
ndate_dt = date_utils.dt_as_timezone(ndate_dt, 'UTC')
|
|
return ndate_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
|
|
|
# @api.constrains('checkin', 'checkout') #Why dont run api.depends?¿?
|
|
# def _computed_nights(self):
|
|
# for res in self:
|
|
# if res.checkin and res.checkout:
|
|
# nights = days_diff = date_utils.date_diff(
|
|
# self.checkin,
|
|
# self.checkout, hours=False)
|
|
# res.nights = nights
|
|
|
|
@api.model
|
|
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
|
if args is None:
|
|
args = []
|
|
if not(name == '' and operator == 'ilike'):
|
|
args += [
|
|
'|',
|
|
('folio_id.name', operator, name)
|
|
# FIXME Remove product inheritance
|
|
# ('product_id.name', operator, name)
|
|
]
|
|
return super(HotelReservation, self).name_search(
|
|
name='', args=args, operator='ilike', limit=limit)
|
|
|
|
@api.multi
|
|
def name_get(self):
|
|
# FIXME Remove product inheritance
|
|
result = []
|
|
for res in self:
|
|
name = u'%s (%s)' % (res.folio_id.name, res.room_id.name)
|
|
result.append((res.id, name))
|
|
return result
|
|
|
|
# FIXME added for migration
|
|
def _compute_qty_delivered_updateable(self):
|
|
pass
|
|
# FIXME added for migration
|
|
def _compute_invoice_status(self):
|
|
pass
|
|
|
|
_name = 'hotel.reservation'
|
|
_description = 'Hotel Reservation'
|
|
_inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin']
|
|
_order = "last_updated_res desc, name"
|
|
|
|
# The record's name should now be used for description of the reservation ?
|
|
name = fields.Text('Reservation Description', required=True)
|
|
|
|
# _defaults = {
|
|
# 'product_id': False
|
|
# }
|
|
|
|
room_id = fields.Many2one('hotel.room', string='Room')
|
|
|
|
|
|
reservation_no = fields.Char('Reservation No', size=64, readonly=True)
|
|
adults = fields.Integer('Adults', size=64, readonly=False,
|
|
track_visibility='onchange',
|
|
help='List of adults there in guest list. ')
|
|
children = fields.Integer('Children', size=64, readonly=False,
|
|
track_visibility='onchange',
|
|
help='Number of children there in guest list.')
|
|
to_assign = fields.Boolean('To Assign', track_visibility='onchange')
|
|
state = fields.Selection([('draft', 'Pre-reservation'), ('confirm', 'Pending Entry'),
|
|
('booking', 'On Board'), ('done', 'Out'),
|
|
('cancelled', 'Cancelled')],
|
|
'State', readonly=True,
|
|
default=lambda *a: 'draft',
|
|
track_visibility='onchange')
|
|
reservation_type = fields.Selection(related='folio_id.reservation_type',
|
|
default=lambda *a: 'normal')
|
|
cancelled_reason = fields.Selection([
|
|
('late', 'Late'),
|
|
('intime', 'In time'),
|
|
('noshow', 'No Show')], 'Cause of cancelled')
|
|
out_service_description = fields.Text('Cause of out of service')
|
|
|
|
folio_id = fields.Many2one('hotel.folio', string='Folio',
|
|
ondelete='cascade')
|
|
|
|
checkin = fields.Datetime('Check In', required=True,
|
|
default=_get_default_checkin,
|
|
track_visibility='onchange')
|
|
checkout = fields.Datetime('Check Out', required=True,
|
|
default=_get_default_checkout,
|
|
track_visibility='onchange')
|
|
room_type_id = fields.Many2one('hotel.room.type', string='Room Type',
|
|
required=True, track_visibility='onchange')
|
|
partner_id = fields.Many2one(related='folio_id.partner_id')
|
|
company_id = fields.Many2one('res.company', 'Company')
|
|
reservation_line_ids = fields.One2many('hotel.reservation.line',
|
|
'reservation_id',
|
|
readonly=True, required=True,
|
|
states={
|
|
'draft': [('readonly', False)],
|
|
'sent': [('readonly', False)],
|
|
'confirm': [('readonly', False)],
|
|
'booking': [('readonly', False)],
|
|
})
|
|
reserve_color = fields.Char(compute='_compute_color', string='Color',
|
|
store=True)
|
|
reserve_color_text = fields.Char(compute='_compute_color', string='Color',
|
|
store=True)
|
|
service_line_ids = fields.One2many('hotel.service', 'ser_room_line')
|
|
|
|
# pricelist_id = fields.Many2one('product.pricelist',
|
|
# related='folio_id.pricelist_id',
|
|
# readonly="1")
|
|
cardex_ids = fields.One2many('cardex', 'reservation_id')
|
|
# TODO: As cardex_count is a computed field, it can't not be used in a domain filer
|
|
# Non-stored field hotel.reservation.cardex_count cannot be searched
|
|
# searching on a computed field can also be enabled by setting the search parameter.
|
|
# The value is a method name returning a Domains
|
|
cardex_count = fields.Integer('Cardex counter',
|
|
compute='_compute_cardex_count')
|
|
cardex_pending = fields.Boolean('Cardex Pending',
|
|
compute='_compute_cardex_count',
|
|
search='_search_cardex_pending')
|
|
cardex_pending_num = fields.Integer('Cardex Pending Num',
|
|
compute='_compute_cardex_count')
|
|
# check_rooms = fields.Boolean('Check Rooms')
|
|
is_checkin = fields.Boolean()
|
|
is_checkout = fields.Boolean()
|
|
splitted = fields.Boolean('Splitted', default=False)
|
|
parent_reservation = fields.Many2one('hotel.reservation',
|
|
'Parent Reservation')
|
|
overbooking = fields.Boolean('Is Overbooking', default=False)
|
|
# To show de total amount line in read_only mode
|
|
amount_reservation = fields.Float('Total',
|
|
compute='_computed_amount_reservation',
|
|
store=True)
|
|
amount_reservation_services = fields.Float('Services Amount',
|
|
compute='_computed_amount_reservation',
|
|
store=True)
|
|
amount_room = fields.Float('Amount Room', compute="_computed_amount_reservation",
|
|
store=True)
|
|
amount_discount = fields.Float('Room with Discount', compute="_computed_amount_reservation",
|
|
store=True)
|
|
discount_type = fields.Selection([
|
|
('percent', 'Percent'),
|
|
('fixed', 'Fixed')], 'Discount Type', default=lambda *a: 'percent')
|
|
discount_fixed = fields.Float('Fixed Discount')
|
|
|
|
nights = fields.Integer('Nights', compute='_computed_nights', store=True)
|
|
channel_type = fields.Selection([
|
|
('door', 'Door'),
|
|
('mail', 'Mail'),
|
|
('phone', 'Phone'),
|
|
('call', 'Call Center'),
|
|
('web', 'Web')], 'Sales Channel', default='door')
|
|
last_updated_res = fields.Datetime('Last Updated')
|
|
# Monetary to Float
|
|
folio_pending_amount = fields.Float(related='folio_id.invoices_amount')
|
|
segmentation_ids = fields.Many2many(related='folio_id.segmentation_ids')
|
|
shared_folio = fields.Boolean(compute='_computed_shared')
|
|
#Used to notify is the reservation folio has other reservations or services
|
|
email = fields.Char('E-mail', related='partner_id.email')
|
|
mobile = fields.Char('Mobile', related='partner_id.mobile')
|
|
phone = fields.Char('Phone', related='partner_id.phone')
|
|
partner_internal_comment = fields.Text(string='Internal Partner Notes',
|
|
related='partner_id.comment')
|
|
folio_internal_comment = fields.Text(string='Internal Folio Notes',
|
|
related='folio_id.internal_comment')
|
|
preconfirm = fields.Boolean('Auto confirm to Save', default=True)
|
|
call_center = fields.Boolean(compute='set_call_center_user')
|
|
to_send = fields.Boolean('To Send', default=True)
|
|
has_confirmed_reservations_to_send = fields.Boolean(
|
|
related='folio_id.has_confirmed_reservations_to_send',
|
|
readonly=True)
|
|
has_cancelled_reservations_to_send = fields.Boolean(
|
|
related='folio_id.has_cancelled_reservations_to_send',
|
|
readonly=True)
|
|
has_checkout_to_send = fields.Boolean(
|
|
related='folio_id.has_checkout_to_send',
|
|
readonly=True)
|
|
# fix_total = fields.Boolean(compute='_compute_fix_total')
|
|
# fix_folio_pending = fields.Boolean(related='folio_id.fix_price')
|
|
|
|
# order_line = fields.One2many('sale.order.line', 'order_id', string='Order Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True, auto_join=True)
|
|
# product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product')
|
|
# product_uom = fields.Many2one('product.uom', string='Unit of Measure', required=True)
|
|
# product_uom_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True, default=1.0)
|
|
|
|
# currency_id = fields.Many2one('res.currency',
|
|
# related='pricelist_id.currency_id',
|
|
# string='Currency', readonly=True, required=True)
|
|
# invoice_status = fields.Selection([
|
|
# ('upselling', 'Upselling Opportunity'),
|
|
# ('invoiced', 'Fully Invoiced'),
|
|
# ('to invoice', 'To Invoice'),
|
|
# ('no', 'Nothing to Invoice')
|
|
# ], string='Invoice Status', compute='_compute_invoice_status', store=True, readonly=True, default='no')
|
|
tax_id = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
|
|
# qty_to_invoice = fields.Float(
|
|
# string='To Invoice', store=True, readonly=True,
|
|
# digits=dp.get_precision('Product Unit of Measure'))
|
|
# qty_invoiced = fields.Float(
|
|
# compute='_get_invoice_qty', string='Invoiced', store=True, readonly=True,
|
|
# digits=dp.get_precision('Product Unit of Measure'))
|
|
# qty_delivered = fields.Float(string='Delivered', copy=False, digits=dp.get_precision('Product Unit of Measure'), default=0.0)
|
|
# qty_delivered_updateable = fields.Boolean(compute='_compute_qty_delivered_updateable', string='Can Edit Delivered', readonly=True, default=True)
|
|
price_unit = fields.Float('Unit Price', required=True, digits=dp.get_precision('Product Price'), default=0.0)
|
|
# Monetary to Float
|
|
price_subtotal = fields.Float(compute='_compute_amount', string='Subtotal', readonly=True, store=True)
|
|
# Monetary to Float
|
|
price_total = fields.Float(compute='_compute_amount', string='Total', readonly=True, store=True)
|
|
|
|
# FIXME discount per night
|
|
# discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0)
|
|
|
|
# analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags')
|
|
|
|
|
|
def action_recalcule_payment(self):
|
|
for record in self:
|
|
for res in record.folio_id.room_lines:
|
|
res.on_change_checkin_checkout_product_id()
|
|
|
|
def _computed_folio_name(self):
|
|
for res in self:
|
|
res.folio_name = res.folio_id.name + '-' + \
|
|
res.folio_id.date_order
|
|
|
|
@api.multi
|
|
def send_reservation_mail(self):
|
|
return self.folio_id.send_reservation_mail()
|
|
|
|
@api.multi
|
|
def send_exit_mail(self):
|
|
return self.folio_id.send_exit_mail()
|
|
|
|
@api.multi
|
|
def send_cancel_mail(self):
|
|
return self.folio_id.send_cancel_mail()
|
|
|
|
@api.multi
|
|
def action_checks(self):
|
|
self.ensure_one()
|
|
return {
|
|
'name': _('Cardexs'),
|
|
'view_type': 'form',
|
|
'view_mode': 'tree,form',
|
|
'res_model': 'cardex',
|
|
'type': 'ir.actions.act_window',
|
|
'domain': [('reservation_id', '=', self.id)],
|
|
'target': 'new',
|
|
}
|
|
|
|
@api.multi
|
|
def _computed_shared(self):
|
|
for record in self:
|
|
if record.folio_id:
|
|
if len(record.folio_id.room_lines) > 1 or \
|
|
record.folio_id.service_line_ids.filtered(lambda x: (
|
|
x.ser_room_line != record.id)):
|
|
record.shared_folio = True
|
|
else:
|
|
record.shared_folio = False
|
|
|
|
@api.depends('checkin', 'checkout')
|
|
def _computed_nights(self):
|
|
for res in self:
|
|
if res.checkin and res.checkout:
|
|
nights = days_diff = date_utils.date_diff(
|
|
res.checkin,
|
|
res.checkout, hours=False)
|
|
res.nights = nights
|
|
|
|
@api.model
|
|
def recompute_reservation_totals(self):
|
|
reservations = self.env['hotel.reservation'].search([])
|
|
for res in reservations:
|
|
if res.folio_id.state not in ('done','cancel'):
|
|
_logger.info('---------BOOK-----------')
|
|
_logger.info(res.amount_reservation)
|
|
_logger.info(res.id)
|
|
res._computed_amount_reservation()
|
|
_logger.info(res.amount_reservation)
|
|
_logger.info('---------------------------')
|
|
|
|
@api.depends('reservation_line_ids.price')
|
|
def _computed_amount_reservation(self):
|
|
_logger.info('_computed_amount_reservation')
|
|
# FIXME commented during migration
|
|
# import wdb; wdb.set_trace()
|
|
# for res in self:
|
|
# amount_service = amount_room = 0
|
|
# for line in res.reservation_line_ids:
|
|
# amount_room += line.price
|
|
# for service in res.service_line_ids:
|
|
# # We must calc the line to can show the price in edit mode
|
|
# # on smartbutton whithout having to wait to save.
|
|
# total_line = service.price_unit * service.product_uom_qty
|
|
# discount = (service.discount * total_line) / 100
|
|
# amount_service += total_line - discount
|
|
# res.amount_room = amount_room #To view price_unit with read_only
|
|
# if res.discount_type == 'fixed' and amount_room > 0:
|
|
# res.discount = (res.discount_fixed * 100) / amount_room # WARNING Posible division by zero
|
|
# else:
|
|
# res.discount_fixed = (res.discount * amount_room) / 100
|
|
# res.amount_discount = amount_room - res.discount_fixed
|
|
# res.price_unit = amount_room
|
|
# res.amount_reservation_services = amount_service
|
|
# res.amount_reservation = res.amount_discount + amount_service #To the smartbutton
|
|
|
|
@api.multi
|
|
def _compute_cardex_count(self):
|
|
_logger.info('_compute_cardex_count')
|
|
for res in self:
|
|
res.cardex_count = len(res.cardex_ids)
|
|
res.cardex_pending_num = (res.adults + res.children) \
|
|
- len(res.cardex_ids)
|
|
if (res.adults + res.children - len(res.cardex_ids)) <= 0:
|
|
res.cardex_pending = False
|
|
else:
|
|
res.cardex_pending = True
|
|
|
|
# https://www.odoo.com/es_ES/forum/ayuda-1/question/calculated-fields-in-search-filter-possible-118501
|
|
@api.multi
|
|
def _search_cardex_pending(self, operator, value):
|
|
recs = self.search([]).filtered(lambda x: x.cardex_pending is True)
|
|
if recs:
|
|
return [('id', 'in', [x.id for x in recs])]
|
|
|
|
@api.multi
|
|
def action_pay_folio(self):
|
|
self.ensure_one()
|
|
return self.folio_id.action_pay()
|
|
|
|
@api.multi
|
|
def action_pay_reservation(self):
|
|
self.ensure_one()
|
|
partner = self.partner_id.id
|
|
amount = min(self.amount_reservation, self.folio_pending_amount)
|
|
note = self.folio_id.name + ' (' + self.name + ')'
|
|
view_id = self.env.ref('hotel.view_account_payment_folio_form').id
|
|
return{
|
|
'name': _('Register Payment'),
|
|
'view_type': 'form',
|
|
'view_mode': 'form',
|
|
'res_model': 'account.payment',
|
|
'type': 'ir.actions.act_window',
|
|
'view_id': view_id,
|
|
'context': {
|
|
'default_folio_id': self.folio_id.id,
|
|
'default_room_id': self.id,
|
|
'default_amount': amount,
|
|
'default_payment_type': 'inbound',
|
|
'default_partner_type': 'customer',
|
|
'default_partner_id': partner,
|
|
'default_communication': note,
|
|
},
|
|
'target': 'new',
|
|
}
|
|
|
|
@api.model
|
|
def daily_plan(self):
|
|
_logger.info('daily_plan')
|
|
today_utc_dt = date_utils.now()
|
|
yesterday_utc_dt = today_utc_dt - timedelta(days=1)
|
|
hotel_tz = self.env['ir.default'].sudo().get('res.config.settings',
|
|
'tz_hotel')
|
|
today_dt = date_utils.dt_as_timezone(today_utc_dt, hotel_tz)
|
|
yesterday_dt = date_utils.dt_as_timezone(yesterday_utc_dt, hotel_tz)
|
|
|
|
today_str = today_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
|
yesterday_str = yesterday_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
|
reservations_to_checkout = self.env['hotel.reservation'].search([
|
|
('state', 'not in', ['done']),
|
|
('checkout', '<', today_str)
|
|
])
|
|
for res in reservations_to_checkout:
|
|
res.action_reservation_checkout()
|
|
|
|
reservations = self.env['hotel.reservation'].search([
|
|
('reservation_line_ids.date', 'in', [today_str,
|
|
yesterday_str]),
|
|
('state', 'in', ['confirm', 'booking'])
|
|
])
|
|
self._cr.execute("update hotel_reservation set is_checkin = False, \
|
|
is_checkout = False where is_checkin = True or \
|
|
is_checkout = True")
|
|
checkins_res = reservations.filtered(lambda x: (
|
|
x.state in ('confirm','draft')
|
|
and date_utils.date_compare(x.checkin, today_str, hours=False)
|
|
and x.reservation_type == 'normal'))
|
|
checkins_res.write({'is_checkin': True})
|
|
checkouts_res = reservations.filtered(lambda x: (
|
|
x.state not in ('done','cancelled')
|
|
and date_utils.date_compare(x.checkout, today_str,
|
|
hours=False)
|
|
and x.reservation_type == 'normal'))
|
|
checkouts_res.write({'is_checkout': True})
|
|
self.env['hotel.folio'].daily_plan()
|
|
return True
|
|
|
|
@api.model
|
|
def checkin_is_today(self):
|
|
self.ensure_one()
|
|
date_now_str = date_utils.now().strftime(
|
|
DEFAULT_SERVER_DATE_FORMAT)
|
|
return date_utils.date_compare(self.checkin, date_now_str, hours=False)
|
|
|
|
@api.model
|
|
def checkout_is_today(self):
|
|
self.ensure_one()
|
|
date_now_str = date_utils.now().strftime(
|
|
DEFAULT_SERVER_DATE_FORMAT)
|
|
return date_utils.date_compare(self.checkout, date_now_str,
|
|
hours=False)
|
|
|
|
@api.multi
|
|
def action_cancel(self):
|
|
for record in self:
|
|
record.write({
|
|
'state': 'cancelled',
|
|
'discount': 100.0,
|
|
})
|
|
if record.checkin_is_today:
|
|
record.is_checkin = False
|
|
folio = self.env['hotel.folio'].browse(record.folio_id.id)
|
|
folio.checkins_reservations = folio.room_lines.search_count([
|
|
('folio_id', '=', folio.id),
|
|
('is_checkin', '=', True)
|
|
])
|
|
|
|
if record.splitted:
|
|
master_reservation = record.parent_reservation or record
|
|
splitted_reservs = self.env['hotel.reservation'].search([
|
|
('splitted', '=', True),
|
|
'|', ('parent_reservation', '=', master_reservation.id),
|
|
('id', '=', master_reservation.id),
|
|
('folio_id', '=', record.folio_id.id),
|
|
('id', '!=', record.id),
|
|
('state', '!=', 'cancelled')
|
|
])
|
|
splitted_reservs.action_cancel()
|
|
record.folio_id.compute_invoices_amount()
|
|
|
|
@api.multi
|
|
def draft(self):
|
|
for record in self:
|
|
record.write({'state': 'draft'})
|
|
|
|
if record.splitted:
|
|
master_reservation = record.parent_reservation or record
|
|
splitted_reservs = self.env['hotel.reservation'].search([
|
|
('splitted', '=', True),
|
|
'|', ('parent_reservation', '=', master_reservation.id),
|
|
('id', '=', master_reservation.id),
|
|
('folio_id', '=', record.folio_id.id),
|
|
('id', '!=', record.id),
|
|
('state', '!=', 'draft')
|
|
])
|
|
splitted_reservs.draft()
|
|
|
|
@api.multi
|
|
def action_reservation_checkout(self):
|
|
for record in self:
|
|
record.state = 'done'
|
|
if record.checkout_is_today():
|
|
record.is_checkout = False
|
|
folio = self.env['hotel.folio'].browse(self.folio_id.id)
|
|
folio.checkouts_reservations = folio.room_lines.search_count([
|
|
('folio_id', '=', folio.id),
|
|
('is_checkout', '=', True)
|
|
])
|
|
|
|
@api.multi
|
|
def overbooking_button(self):
|
|
self.ensure_one()
|
|
return self.write({'overbooking': not self.overbooking})
|
|
|
|
@api.multi
|
|
def open_master(self):
|
|
self.ensure_one()
|
|
if not self.parent_reservation:
|
|
raise ValidationError(_("This is the parent reservation"))
|
|
action = self.env.ref('hotel.open_hotel_reservation_form_tree_all').read()[0]
|
|
action['views'] = [(self.env.ref('hotel.view_hotel_reservation_form').id, 'form')]
|
|
action['res_id'] = self.parent_reservation.id
|
|
return action
|
|
|
|
@api.multi
|
|
def open_folio(self):
|
|
action = self.env.ref('hotel.open_hotel_folio1_form_tree_all').read()[0]
|
|
if self.folio_id:
|
|
action['views'] = [(self.env.ref('hotel.view_hotel_folio1_form').id, 'form')]
|
|
action['res_id'] = self.folio_id.id
|
|
else:
|
|
action = {'type': 'ir.actions.act_window_close'}
|
|
return action
|
|
|
|
@api.multi
|
|
def open_reservation_form(self):
|
|
action = self.env.ref('hotel.open_hotel_reservation_form_tree_all').read()[0]
|
|
action['views'] = [(self.env.ref('hotel.view_hotel_reservation_form').id, 'form')]
|
|
action['res_id'] = self.id
|
|
return action
|
|
|
|
@api.multi
|
|
def get_real_checkin_checkout(self):
|
|
self.ensure_one()
|
|
if not self.splitted:
|
|
return (self.checkin, self.checkout)
|
|
|
|
master_reservation = self.parent_reservation or self
|
|
splitted_reservs = self.env['hotel.reservation'].search([
|
|
('splitted', '=', True),
|
|
('folio_id', '=', self.folio_id.id),
|
|
'|',
|
|
('parent_reservation', '=', master_reservation.id),
|
|
('id', '=', master_reservation.id)
|
|
])
|
|
last_checkout = splitted_reservs[0].checkout
|
|
first_checkin = splitted_reservs[0].checkin
|
|
for reserv in splitted_reservs:
|
|
if last_checkout < reserv.checkout:
|
|
last_checkout = reserv.checkout
|
|
if first_checkin > reserv.checkin:
|
|
first_checkin = reserv.checkin
|
|
return (first_checkin, last_checkout)
|
|
|
|
@api.multi
|
|
def unify(self):
|
|
# FIXME Remove product inheritance
|
|
pass
|
|
# self.ensure_one()
|
|
# if not self.splitted:
|
|
# raise ValidationError(_("This reservation can't be unified"))
|
|
#
|
|
# master_reservation = self.parent_reservation or self
|
|
# self_is_master = (master_reservation == self)
|
|
#
|
|
# splitted_reservs = self.env['hotel.reservation'].search([
|
|
# ('splitted', '=', True),
|
|
# ('folio_id', '=', self.folio_id.id),
|
|
# '|',
|
|
# ('parent_reservation', '=', master_reservation.id),
|
|
# ('id', '=', master_reservation.id)
|
|
# ])
|
|
#
|
|
# rooms_products = splitted_reservs.mapped('product_id.id')
|
|
# if len(rooms_products) > 1 or \
|
|
# (len(rooms_products) == 1
|
|
# and master_reservation.product_id.id != rooms_products[0]):
|
|
# raise ValidationError(_("This reservation can't be unified: They \
|
|
# all need to be in the same room"))
|
|
#
|
|
# # Search checkout
|
|
# last_checkout = splitted_reservs[0].checkout
|
|
# for reserv in splitted_reservs:
|
|
# if last_checkout < reserv.checkout:
|
|
# last_checkout = reserv.checkout
|
|
#
|
|
# # Agrupate reservation lines
|
|
# reservation_line_ids = splitted_reservs.mapped('reservation_line_ids')
|
|
# reservation_line_ids.sorted(key=lambda r: r.date)
|
|
# rlines = [(5, False, False)]
|
|
# tprice = 0.0
|
|
# for rline in reservation_line_ids:
|
|
# rlines.append((0, False, {
|
|
# 'date': rline.date,
|
|
# 'price': rline.price,
|
|
# }))
|
|
# tprice += rline.price
|
|
#
|
|
# # Unify
|
|
# folio = self.folio_id # FIX: To Allow Unify confirm reservations
|
|
# state = folio.state # FIX
|
|
# folio.state = 'draft' # FIX
|
|
# osplitted_reservs = splitted_reservs - master_reservation
|
|
# osplitted_reservs.sudo().unlink()
|
|
# folio.state = state # FIX
|
|
#
|
|
# # FIXME: Two writes because checkout regenerate reservation lines
|
|
# master_reservation.write({
|
|
# 'checkout': last_checkout,
|
|
# 'splitted': False,
|
|
# })
|
|
# master_reservation.write({
|
|
# 'reservation_line_ids': rlines,
|
|
# 'price_unit': tprice,
|
|
# })
|
|
# if not self_is_master:
|
|
# return {'type': 'ir.actions.act_window_close'}
|
|
# return True
|
|
#
|
|
# '''
|
|
# Created this because "copy()" function create a new record
|
|
# and collide with date restrictions.
|
|
# This function generate a usable dictionary with reservation values
|
|
# for copy purposes.
|
|
# '''
|
|
@api.multi
|
|
def generate_copy_values(self, checkin=False, checkout=False):
|
|
self.ensure_one()
|
|
return {
|
|
'name': self.name,
|
|
'adults': self.adults,
|
|
'children': self.children,
|
|
'checkin': checkin or self.checkin,
|
|
'checkout': checkout or self.checkout,
|
|
'folio_id': self.folio_id.id,
|
|
# 'product_id': self.product_id.id,
|
|
'parent_reservation': self.parent_reservation.id,
|
|
'state': self.state,
|
|
'overbooking': self.overbooking,
|
|
'price_unit': self.price_unit,
|
|
'splitted': self.splitted,
|
|
# 'virtual_room_id': self.virtual_room_id.id,
|
|
'room_type_id': self.room_type_id.id,
|
|
}
|
|
|
|
@api.model
|
|
def create(self, vals):
|
|
"""
|
|
Overrides orm create method.
|
|
@param self: The object pointer
|
|
@param vals: dictionary of fields value.
|
|
@return: new record set for hotel folio line.
|
|
"""
|
|
# import wdb; wdb.set_trace()
|
|
if not 'reservation_type' in vals or not vals.get('reservation_type'):
|
|
vals.update({'reservation_type': 'normal'})
|
|
if 'folio_id' in vals:
|
|
folio = self.env["hotel.folio"].browse(vals['folio_id'])
|
|
# vals.update({'order_id': folio.order_id.id,
|
|
# 'channel_type': folio.channel_type})
|
|
vals.update({'channel_type': folio.channel_type})
|
|
elif 'partner_id' in vals:
|
|
folio_vals = {'partner_id':int(vals.get('partner_id')),
|
|
'channel_type': vals.get('channel_type')}
|
|
folio = self.env["hotel.folio"].create(folio_vals)
|
|
# vals.update({'order_id': folio.order_id.id,
|
|
# 'folio_id': folio.id,
|
|
# 'reservation_type': vals.get('reservation_type'),
|
|
# 'channel_type': vals.get('channel_type')})
|
|
vals.update({'folio_id': folio.id,
|
|
'reservation_type': vals.get('reservation_type'),
|
|
'channel_type': vals.get('channel_type')})
|
|
user = self.env['res.users'].browse(self.env.uid)
|
|
if user.has_group('hotel.group_hotel_call'):
|
|
vals.update({'to_assign': True,
|
|
'channel_type': 'call'})
|
|
vals.update({
|
|
'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
|
})
|
|
if folio:
|
|
record = super(HotelReservation, self).create(vals)
|
|
# Check Capacity
|
|
# NOTE the room is not a product anymore
|
|
# room = self.env['hotel.room'].search([
|
|
# ('product_id', '=', record.product_id.id)
|
|
# ])
|
|
#persons = record.adults # Not count childrens
|
|
if record.adults > record.room_id.capacity:
|
|
raise ValidationError(
|
|
_("Reservation persons can't be higher than room capacity"))
|
|
if record.adults == 0:
|
|
raise ValidationError(_("Reservation has no adults"))
|
|
if (record.state == 'draft' and record.folio_id.state == 'sale') or \
|
|
record.preconfirm:
|
|
record.confirm()
|
|
record._compute_color()
|
|
return record
|
|
|
|
@api.multi
|
|
def write(self, vals):
|
|
for record in self:
|
|
if ('checkin' in vals and record.checkin != vals['checkin']) or \
|
|
('checkout' in vals and record.checkout != vals['checkout']) or \
|
|
('state' in vals and record.state != vals['state']) or \
|
|
('amount_discount' in vals and record.amount_discount != vals['amount_discount']):
|
|
vals.update({'to_send': True})
|
|
|
|
pricesChanged = ('checkin' in vals or \
|
|
'checkout' in vals or \
|
|
'discount' in vals)
|
|
# vals.update({
|
|
# 'edit_room': False,
|
|
# })
|
|
# if pricesChanged or 'state' in vals or 'virtual_room_id' in vals or 'to_assign' in vals:
|
|
if pricesChanged or 'state' in vals or 'room_type_id' in vals or 'to_assign' in vals:
|
|
vals.update({
|
|
'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
|
})
|
|
user = self.env['res.users'].browse(self.env.uid)
|
|
if user.has_group('hotel.group_hotel_call'):
|
|
vals.update({
|
|
'to_read': True,
|
|
'to_assign': True,
|
|
})
|
|
res = super(HotelReservation, self).write(vals)
|
|
if pricesChanged:
|
|
for record in self:
|
|
if record.reservation_type in ('staff', 'out'):
|
|
record.update({'price_unit': 0})
|
|
record.folio_id.compute_invoices_amount()
|
|
checkin = vals.get('checkin', record.checkin)
|
|
checkout = vals.get('checkout', record.checkout)
|
|
days_diff = date_utils.date_diff(checkin,
|
|
checkout, hours=False)
|
|
rlines = record.prepare_reservation_lines(checkin, days_diff)
|
|
record.update({
|
|
'reservation_line_ids': rlines['commands'],
|
|
'price_unit': rlines['total_price'],
|
|
})
|
|
return res
|
|
|
|
# @api.multi
|
|
# def uos_change(self, product_uos, product_uos_qty=0, product_id=None):
|
|
# '''
|
|
# @param self: object pointer
|
|
# '''
|
|
# # for folio in self:
|
|
# # line = folio.order_line_id
|
|
# # line.uos_change(product_uos, product_uos_qty=0,
|
|
# # product_id=None)
|
|
# return True
|
|
|
|
# FIXME add room.id to on change after removing inheritance
|
|
@api.onchange('adults', 'children')
|
|
def check_capacity(self):
|
|
if self.room_id:
|
|
persons = self.adults + self.children
|
|
if self.room_id.capacity < persons:
|
|
self.adults = self.room_id.capacity
|
|
self.children = 0
|
|
raise UserError(
|
|
_('%s people do not fit in this room! ;)') % (persons))
|
|
|
|
@api.onchange('room_type_id')
|
|
# def on_change_virtual_room_id(self):
|
|
def on_change_room_type_id(self):
|
|
if not self.checkin:
|
|
self.checkin = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
|
if not self.checkout:
|
|
self.checkout = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
|
days_diff = date_utils.date_diff(
|
|
self.checkin, self.checkout, hours=False)
|
|
rlines = self.prepare_reservation_lines(
|
|
self.checkin,
|
|
days_diff,
|
|
update_old_prices=True)
|
|
self.reservation_line_ids = rlines['commands']
|
|
|
|
if self.reservation_type in ['staff', 'out']:
|
|
self.price_unit = 0.0
|
|
self.cardex_pending = 0
|
|
else:
|
|
self.price_unit = rlines['total_price']
|
|
|
|
@api.onchange('checkin', 'checkout', 'room_id',
|
|
'reservation_type', 'room_type_id')
|
|
def on_change_checkin_checkout_product_id(self):
|
|
_logger.info('on_change_checkin_checkout_product_id')
|
|
# import wdb; wdb.set_trace()
|
|
if not self.checkin:
|
|
self.checkin = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
|
if not self.checkout:
|
|
self.checkout = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
|
# WARNING Need a review
|
|
# if self.product_id:
|
|
# self.tax_id = [(6, False, self.virtual_room_id.product_id.taxes_id.ids)]
|
|
# room = self.env['hotel.room'].search([
|
|
# ('product_id', '=', self.product_id.id)
|
|
# ])
|
|
# if self.adults == 0:
|
|
# self.adults = room.capacity
|
|
# if not self.virtual_room_id and room.price_virtual_room:
|
|
# self.virtual_room_id = room.price_virtual_room.id
|
|
if self.room_id:
|
|
# self.tax_id = [(6, False, self.room_type_id.product_id.taxes_id.ids)]
|
|
if self.adults == 0:
|
|
self.adults = self.room_id.capacity
|
|
if not self.room_type_id:
|
|
self.room_type_id = self.room_id.room_type_id
|
|
self.tax_id = [(6, False, self.room_id.room_type_id.taxes_id.ids)]
|
|
|
|
# UTC -> Hotel tz
|
|
tz = self.env['ir.default'].sudo().get('res.config.settings',
|
|
'tz_hotel')
|
|
chkin_utc_dt = date_utils.get_datetime(self.checkin)
|
|
chkout_utc_dt = date_utils.get_datetime(self.checkout)
|
|
|
|
if self.room_type_id:
|
|
checkin_str = chkin_utc_dt.strftime('%d/%m/%Y')
|
|
checkout_str = chkout_utc_dt.strftime('%d/%m/%Y')
|
|
self.name = self.room_type_id.name + ': ' + checkin_str + ' - '\
|
|
+ checkout_str
|
|
# self.product_uom = self.product_id.uom_id
|
|
|
|
if chkin_utc_dt >= chkout_utc_dt:
|
|
dpt_hour = self.env['ir.default'].sudo().get(
|
|
'res.config.settings', 'default_departure_hour')
|
|
checkout_str = (chkin_utc_dt + timedelta(days=1)).strftime(
|
|
DEFAULT_SERVER_DATE_FORMAT)
|
|
checkout_str = "%s %s:00" % (checkout_str, dpt_hour)
|
|
checkout_dt = date_utils.get_datetime(checkout_str, stz=tz)
|
|
checkout_utc_dt = date_utils.dt_as_timezone(checkout_dt, 'UTC')
|
|
self.checkout = checkout_utc_dt.strftime(
|
|
DEFAULT_SERVER_DATETIME_FORMAT)
|
|
|
|
if self.state == 'confirm' and self.checkin_is_today():
|
|
self.is_checkin = True
|
|
folio = self.env['hotel.folio'].browse(self.folio_id.id)
|
|
if folio:
|
|
folio.checkins_reservations = folio.room_lines.search_count([
|
|
('folio_id', '=', folio.id), ('is_checkin', '=', True)
|
|
])
|
|
|
|
if self.state == 'booking' and self.checkout_is_today():
|
|
self.is_checkout = False
|
|
folio = self.env['hotel.folio'].browse(self.folio_id.id)
|
|
if folio:
|
|
folio.checkouts_reservations = folio.room_lines.search_count([
|
|
('folio_id', '=', folio.id), ('is_checkout', '=', True)
|
|
])
|
|
|
|
days_diff = date_utils.date_diff(
|
|
self.checkin, self.checkout, hours=False)
|
|
rlines = self.prepare_reservation_lines(
|
|
self.checkin,
|
|
days_diff,
|
|
update_old_prices=False)
|
|
self.reservation_line_ids = rlines['commands']
|
|
|
|
if self.reservation_type in ['staff', 'out']:
|
|
self.price_unit = 0.0
|
|
self.cardex_pending = 0
|
|
else:
|
|
self.price_unit = rlines['total_price']
|
|
|
|
# FIXME add room.id to on change after removing inheritance
|
|
@api.model
|
|
def get_availability(self, checkin, checkout, dbchanged=True,
|
|
dtformat=DEFAULT_SERVER_DATE_FORMAT):
|
|
date_start = date_utils.get_datetime(checkin)
|
|
date_end = date_utils.get_datetime(checkout)
|
|
# Not count end day of the reservation
|
|
date_diff = date_utils.date_diff(date_start, date_end, hours=False)
|
|
|
|
hotel_vroom_obj = self.env['hotel.room.type']
|
|
# virtual_room_avail_obj = self.env['hotel.room.type.availability']
|
|
|
|
rooms_avail = []
|
|
# FIXME con una relacion Many2one, cada habitacion está en un solo tipo
|
|
# por lo que la disponibilidad para la habitación se tiene que buscar
|
|
# directamente en ese tipo
|
|
# vrooms = hotel_vroom_obj.search([
|
|
# ('room_ids.product_id', '=', self.room_id)
|
|
# ])
|
|
# FIXME Si lo de arriba es cierto, este bucle sobra. Sólo hay un room_type_id
|
|
for vroom in self.room_type_id:
|
|
rdays = []
|
|
for i in range(0, date_diff):
|
|
ndate_dt = date_start + timedelta(days=i)
|
|
ndate_str = ndate_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
|
avail = len(hotel_vroom_obj.check_availability_virtual_room(
|
|
ndate_str,
|
|
ndate_str,
|
|
room_type_id=vroom.id))
|
|
if not dbchanged:
|
|
avail = avail - 1
|
|
# Can be less than zero because 'avail' can not equal
|
|
# with the real 'avail' (ex. Online Limits)
|
|
avail = max(min(avail, vroom.total_rooms_count), 0)
|
|
rdays.append({
|
|
'date': ndate_dt.strftime(dtformat),
|
|
'avail': avail,
|
|
})
|
|
ravail = {'id': vroom.id, 'days': rdays}
|
|
rooms_avail.append(ravail)
|
|
|
|
return rooms_avail
|
|
|
|
@api.multi
|
|
def prepare_reservation_lines(self, str_start_date_utc, days,
|
|
update_old_prices=False):
|
|
self.ensure_one()
|
|
total_price = 0.0
|
|
cmds = [(5, False, False)]
|
|
# TO-DO: Redesign relation between hotel.reservation
|
|
# and sale.order.line to allow manage days by units in order
|
|
#~ if self.invoice_status == 'invoiced' and not self.splitted:
|
|
#~ raise ValidationError(_("This reservation is already invoiced. \
|
|
#~ To expand it you must create a new reservation."))
|
|
hotel_tz = self.env['ir.default'].sudo().get(
|
|
'res.config.settings', 'hotel_tz')
|
|
start_date_utc_dt = date_utils.get_datetime(str_start_date_utc)
|
|
start_date_dt = date_utils.dt_as_timezone(start_date_utc_dt, hotel_tz)
|
|
|
|
# import wdb; wdb.set_trace()
|
|
|
|
# room = self.env['hotel.room'].search([
|
|
# ('product_id', '=', self.product_id.id)
|
|
# ])
|
|
# product_id = self.room_id.sale_price_type == 'vroom' and self.room_id.price_virtual_room.product_id
|
|
product_id = self.room_type_id
|
|
pricelist_id = self.env['ir.default'].sudo().get(
|
|
'res.config.settings', 'parity_pricelist_id')
|
|
if pricelist_id:
|
|
pricelist_id = int(pricelist_id)
|
|
old_lines_days = self.mapped('reservation_line_ids.date')
|
|
for i in range(0, days):
|
|
ndate = start_date_dt + timedelta(days=i)
|
|
ndate_str = ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
|
_logger.info('ndate_str: %s', ndate_str)
|
|
if update_old_prices or ndate_str not in old_lines_days:
|
|
# prod = product_id.with_context(
|
|
# lang=self.partner_id.lang,
|
|
# partner=self.partner_id.id,
|
|
# quantity=1,
|
|
# date=ndate_str,
|
|
# pricelist=pricelist_id,
|
|
# uom=self.product_uom.id)
|
|
prod = product_id.with_context(
|
|
lang=self.partner_id.lang,
|
|
partner=self.partner_id.id,
|
|
quantity=1,
|
|
date=ndate_str,
|
|
pricelist=pricelist_id)
|
|
line_price = prod.price
|
|
else:
|
|
line = self.reservation_line_ids.filtered(lambda r: r.date == ndate_str)
|
|
line_price = line.price
|
|
cmds.append((0, False, {
|
|
'date': ndate_str,
|
|
'price': line_price
|
|
}))
|
|
total_price += line_price
|
|
return {'total_price': total_price, 'commands': cmds}
|
|
|
|
@api.constrains('adults')
|
|
def check_adults(self):
|
|
if self.adults == 0 and self.room_id:
|
|
# room = self.env['hotel.room'].search([
|
|
# ('product_id', '=', self.product_id.id)
|
|
# ], limit=1)
|
|
self.adults = self.room_id.capacity
|
|
|
|
@api.multi
|
|
@api.onchange('checkin', 'checkout', 'room_type_id', 'room_id')
|
|
def on_change_checkout(self):
|
|
'''
|
|
When you change checkin or checkout it will checked it
|
|
and update the qty of hotel folio line
|
|
-----------------------------------------------------------------
|
|
@param self: object pointer
|
|
'''
|
|
_logger.info('on_change_checkout')
|
|
self.ensure_one()
|
|
now_utc_dt = date_utils.now()
|
|
if not self.checkin:
|
|
self.checkin = now_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
|
if not self.checkout:
|
|
now_utc_dt = date_utils.get_datetime(self.checkin)\
|
|
+ timedelta(days=1)
|
|
self.checkout = now_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
|
if self.overbooking:
|
|
return
|
|
checkout_dt = date_utils.get_datetime(self.checkout)
|
|
occupied = self.env['hotel.reservation'].occupied(
|
|
self.checkin,
|
|
checkout_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)).filtered(
|
|
lambda r: r.id != self._origin.id)
|
|
rooms_occupied = occupied.mapped('room_id.id')
|
|
if self.room_id and self.room_id.id in rooms_occupied:
|
|
warning_msg = _('You tried to change \
|
|
reservation with room those already reserved in this \
|
|
reservation period')
|
|
raise ValidationError(warning_msg)
|
|
domain_rooms = [
|
|
# ('isroom', '=', True),
|
|
('id', 'not in', rooms_occupied)
|
|
]
|
|
# if self.check_rooms:
|
|
# if self.room_type_id:
|
|
# domain_rooms.append(
|
|
# ('categ_id.id', '=', self.room_type_id.cat_id.id)
|
|
# )
|
|
# if self.virtual_room_id:
|
|
# room_categories = self.virtual_room_id.room_type_ids.mapped(
|
|
# 'cat_id.id')
|
|
# link_virtual_rooms = self.virtual_room_id.room_ids\
|
|
# | self.env['hotel.room'].search([
|
|
# ('categ_id.id', 'in', room_categories)])
|
|
# room_ids = link_virtual_rooms.mapped('room_id.id')
|
|
# domain_rooms.append(('id', 'in', room_ids))
|
|
return {'domain': {'room_id': domain_rooms}}
|
|
|
|
@api.multi
|
|
def confirm(self):
|
|
'''
|
|
@param self: object pointer
|
|
'''
|
|
_logger.info('confirm')
|
|
hotel_folio_obj = self.env['hotel.folio']
|
|
hotel_reserv_obj = self.env['hotel.reservation']
|
|
for r in self:
|
|
vals = {}
|
|
if r.cardex_ids:
|
|
vals.update({'state': 'booking'})
|
|
else:
|
|
vals.update({'state': 'confirm'})
|
|
if r.checkin_is_today():
|
|
vals.update({'is_checkin': True})
|
|
folio = hotel_folio_obj.browse(r.folio_id.id)
|
|
folio.checkins_reservations = folio.room_lines.search_count([
|
|
('folio_id', '=', folio.id), ('is_checkin', '=', True)])
|
|
r.write(vals)
|
|
|
|
if r.splitted:
|
|
master_reservation = r.parent_reservation or r
|
|
splitted_reservs = hotel_reserv_obj.search([
|
|
('splitted', '=', True),
|
|
'|', ('parent_reservation', '=', master_reservation.id),
|
|
('id', '=', master_reservation.id),
|
|
('folio_id', '=', r.folio_id.id),
|
|
('id', '!=', r.id),
|
|
('state', '!=', 'confirm')
|
|
])
|
|
splitted_reservs.confirm()
|
|
return True
|
|
|
|
@api.multi
|
|
def button_done(self):
|
|
'''
|
|
@param self: object pointer
|
|
'''
|
|
for res in self:
|
|
res.action_reservation_checkout()
|
|
return True
|
|
|
|
@api.one
|
|
def copy_data(self, default=None):
|
|
'''
|
|
@param self: object pointer
|
|
@param default: dict of default values to be set
|
|
'''
|
|
return False
|
|
# FIXME added for migration
|
|
# line_id = self.order_line_id.id
|
|
# sale_line_obj = self.env['sale.order.line'].browse(line_id)
|
|
# return sale_line_obj.copy_data(default=default)
|
|
|
|
@api.constrains('checkin', 'checkout', 'state', 'room_id', 'overbooking')
|
|
def check_dates(self):
|
|
"""
|
|
1.-When date_order is less then checkin date or
|
|
Checkout date should be greater than the checkin date.
|
|
3.-Check the reservation dates are not occuped
|
|
"""
|
|
chkin_utc_dt = date_utils.get_datetime(self.checkin)
|
|
chkout_utc_dt = date_utils.get_datetime(self.checkout)
|
|
if chkin_utc_dt >= chkout_utc_dt:
|
|
raise ValidationError(_('Room line Check In Date Should be \
|
|
less than the Check Out Date!'))
|
|
if not self.overbooking and not self._context.get("ignore_avail_restrictions", False):
|
|
occupied = self.env['hotel.reservation'].occupied(
|
|
self.checkin,
|
|
chkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT))
|
|
occupied = occupied.filtered(
|
|
lambda r: r.room_id.id == self.room_id.id
|
|
and r.id != self.id)
|
|
occupied_name = ','.join(str(x.room_id.name) for x in occupied)
|
|
if occupied:
|
|
warning_msg = _('You tried to change/confirm \
|
|
reservation with room those already reserved in this \
|
|
reservation period: %s ') % occupied_name
|
|
raise ValidationError(warning_msg)
|
|
|
|
@api.multi
|
|
def unlink(self):
|
|
# for record in self:
|
|
# record.order_line_id.unlink()
|
|
return super(HotelReservation, self).unlink()
|
|
|
|
@api.model
|
|
def occupied(self, str_checkin_utc, str_checkout_utc):
|
|
"""
|
|
Return a RESERVATIONS array between in and out parameters
|
|
IMPORTANT: This function should receive the dates in UTC datetime zone,
|
|
as String format
|
|
"""
|
|
tz_hotel = self.env['ir.default'].sudo().get(
|
|
'res.config.settings', 'tz_hotel')
|
|
checkin_utc_dt = date_utils.get_datetime(str_checkin_utc)
|
|
checkin_dt = date_utils.dt_as_timezone(checkin_utc_dt, tz_hotel)
|
|
days_diff = date_utils.date_diff(str_checkin_utc, str_checkout_utc,
|
|
hours=False)
|
|
dates_list = date_utils.generate_dates_list(checkin_dt, days_diff or 1,
|
|
stz=tz_hotel)
|
|
reservations = self.env['hotel.reservation'].search([
|
|
('reservation_line_ids.date', 'in', dates_list),
|
|
('state', '!=', 'cancelled'),
|
|
('overbooking', '=', False)
|
|
])
|
|
return reservations
|