trytonpsk-hotel/room.py

501 lines
17 KiB
Python

# This file is part of Presik. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import copy
from datetime import datetime, date, timedelta
from trytond.model import ModelView, ModelSQL, Workflow, fields
from trytond.pyson import Eval
from trytond.wizard import Wizard, StateView, Button, StateReport
from trytond.pool import Pool
from trytond.report import Report
from trytond.transaction import Transaction
ROOM_STATUS = [
('inspected', 'Inspected'),
('not_authorized', 'Not Authorized'),
('dirty', 'Dirty'),
('clean', 'Clean'),
('maintenance', 'Maintenance'),
]
class CleaningType(ModelSQL, ModelView):
"Room Cleaning Type"
__name__ = "hotel.room.cleaning_type"
name = fields.Char('Name', required=True)
description = fields.Text('Description')
kind = fields.Selection([
('tweak', 'Tweak'),
('deep', 'Deep'),
('standard', 'Standard'),
], 'Kind', required=True)
class Room(Workflow, ModelSQL, ModelView):
'Hotel Room'
__name__ = 'hotel.room'
name = fields.Char('Name', required=True)
code = fields.Char('Code', required=True)
active = fields.Boolean('Active')
templates = fields.Many2Many('hotel.room-product.template', 'room',
'template', 'Hotel Room - Product Accommodation', domain=[
('type', '=', 'service'),
('kind', '=', 'accommodation'),
])
classification = fields.Many2One('hotel.room.classification',
'Classification', required=True)
location = fields.Many2One('hotel.location', 'Location')
amenities = fields.Many2Many('hotel.room-hotel.amenities', 'room',
'amenities', 'Amenities')
space = fields.Integer('Space', help='Space on m2')
channel_id = fields.Char('Channel Manager ID')
main_accommodation = fields.Many2One('product.template',
'Main Accommodation', ondelete='RESTRICT', depends=['templates'],
domain=[('id', 'in', Eval('templates', []))], required=True)
# TODO: Maybe add require a Current State field
min_unit_price = fields.Numeric('Min. Unit Price', digits=(16, 4))
max_unit_price = fields.Numeric('Max. Unit Price', digits=(16, 4))
state = fields.Selection(ROOM_STATUS, 'Status', required=True, readonly=True)
state_string = state.translated('state')
cleaning_type = fields.Function(fields.Many2One(
'hotel.room.cleaning_type', 'Cleaning Type', required=False),
'get_cleaning_type')
last_check_in = fields.DateTime('Last Check In')
last_check_out = fields.DateTime('Last Check Out')
last_clean = fields.DateTime('Last Clean')
housekeeping = fields.Many2One('company.employee', 'Employee')
notes = fields.Text('Notes')
max_accommodation = fields.Integer('Max. Accommodation')
guests = fields.Function(fields.Integer('Guests'), 'get_guests')
current_guests = fields.Function(fields.Integer('Guests'),
'get_current_guests')
check_out_today = fields.Time('Check Out Today', states={
"readonly": True,
})
arrival_today = fields.Function(fields.Boolean(
'Arrival Today'), 'get_is_checked_today')
check_in_today = fields.Time('Check In Today', states={
"readonly": True,
})
departure_today = fields.Function(fields.Boolean(
'Departure Today'), 'get_is_checked_today')
occupied_today = fields.Function(fields.Boolean(
'Occupied Today'), 'get_occupied_today')
@classmethod
def __setup__(cls):
super(Room, cls).__setup__()
cls._transitions |= set((
('clean', 'dirty'),
('clean', 'inspected'),
('clean', 'maintenance'),
('not_authorized', 'clean'),
('not_authorized', 'dirty'),
('dirty', 'clean'),
('dirty', 'maintenance'),
('dirty', 'inspected'),
('dirty', 'not_authorized'),
('maintenance', 'inspected'),
('maintenance', 'dirty'),
('inspected', 'dirty'),
('inspected', 'maintenance'),
('inspected', 'clean'),
))
cls._order = [('code', 'DESC')]
cls._buttons.update({
'clean': {
'invisible': Eval('state').in_(['maintenance', 'clean']),
},
'dirty': {
'invisible': Eval('state').in_(['dirty']),
},
'inspected': {
'invisible': Eval('state').in_(['inspected']),
},
'maintenance': {
'invisible': Eval('state').in_(['maintenance']),
},
'not_authorized': {
'invisible': Eval('state').in_(['maintenance']),
},
})
@staticmethod
def default_active():
return True
@staticmethod
def default_state():
return 'dirty'
@classmethod
@ModelView.button
@Workflow.transition('clean')
def clean(cls, records):
for rec in records:
rec.last_clean = datetime.now()
rec.cleaning_type = None
rec.save()
@classmethod
@ModelView.button
@Workflow.transition('not_authorized')
def not_authorized(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('dirty')
def dirty(cls, records):
Config = Pool().get('hotel.configuration')
config = Config.get_configuration()
for rec in records:
rec.cleaning_type = config.cleaning_occupied.id
@classmethod
@ModelView.button
@Workflow.transition('maintenance')
def maintenance(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('inspected')
def inspected(cls, records):
pass
@classmethod
def get_stock(cls, acco_ids=None):
dom = []
# acco_ids == [product.id, ...]
if acco_ids:
dom.append(
('main_accommodation.products', 'in', acco_ids)
)
rooms = cls.search(dom)
accos = {}
for room in rooms:
product = room.main_accommodation.products[0]
try:
accos[product.id].append(1)
except:
accos[product.id] = [1]
return accos
@classmethod
def get_faster_availability(cls, start_date, end_date, accos):
Occupancy = Pool().get('hotel.folio.occupancy')
delta = range((end_date - start_date).days)
range_dates = [
start_date + timedelta(days=dt)
for dt in delta
]
fmt_dates = [str(dt) for dt in range_dates]
occupancy = Occupancy.search_read([
('occupancy_date', 'in', fmt_dates),
('folio.product', 'in', accos),
], fields_names=['folio.product', 'occupancy_date'])
res = {}
stock = cls.get_stock(accos)
for acc_id in accos:
res[acc_id] = {}
for dt in range_dates:
res[acc_id][dt] = copy.deepcopy(stock[acc_id])
for occ in occupancy:
product_id = occ['folio.']['product']
res[product_id][occ['occupancy_date']].append(-1)
return res
@classmethod
def available_by_classification(cls, start_date, end_date, rate_plan=None):
pool = Pool()
Folio = pool.get('hotel.folio')
RatePlan = pool.get('hotel.rate_plan')
fmt = "%Y-%m-%d"
# FIXME: Add estimation of price_list based on plan and arrival date
args = {
'rate_plan_id': rate_plan,
'arrival_date': start_date,
}
price_list = RatePlan.best_price_list(args)
if isinstance(start_date, str):
start_date = datetime.strptime(start_date, fmt).date()
end_date = datetime.strptime(end_date, fmt).date()
all_rooms = [ro['id'] for ro in Room.search_read([], fields_names=[])]
rooms_available = Folio.get_available_rooms(
start_date, end_date, all_rooms)
clssf = {}
rooms = cls.browse(rooms_available)
best_price = RatePlan.best_price
for room in rooms:
product = room.main_accommodation.products[0]
price_taxed = best_price(product, price_list)
_room = {
"id": room.id,
"name": room.name,
"max_accommodation": room.max_accommodation,
"amenities": [
{"name": ame.name, "type_icon": ame.type_icon} for ame in room.amenities
]
}
try:
clssf[product.id]['available'].append(1)
clssf[product.id]['rooms'].append(_room)
except:
clssf[product.id] = {
"product": {
"id": product.id,
"name": product.name,
"images": [img.image_url for img in product.images]
},
"sale_price_taxed": price_taxed,
"available": [1],
"rooms": [_room],
"rate_plan": price_list, # ????
}
for k, v in clssf.items():
v['available'] = sum(v['available'])
return list(clssf.values())
def get_current_guests(self, name=None):
pool = Pool()
Folio = pool.get('hotel.folio')
today = date.today()
folios = Folio.search(['OR', [
('room', '=', self.id),
('arrival_date', '=', today),
('registration_state', '=', 'check_in'),
], [
('room', '=', self.id),
('departure_date', '=', today),
('registration_state', '=', 'check_in'),
], [
('room', '=', self.id),
('arrival_date', '>', today),
('departure_date', '<', today),
]
])
res = []
for folio in folios:
res.append(len(folio.guests))
return sum(res)
def get_occupied_today(self, name=None):
Folio = Pool().get('hotel.folio')
res = Folio.search_read([
('room', '=', self.id),
('arrival_date', '<', date.today()),
('departure_date', '>', date.today()),
], fields_names=['id'])
return bool(res)
def get_is_checked_today(self, name=None):
pool = Pool()
Folio = pool.get('hotel.folio')
if name == 'arrival_today':
column = "arrival_date"
else:
column = "departure_date"
res = Folio.search_read([
('room', '=', self.id),
(column, '=', date.today()),
], fields_names=['id'])
return bool(res)
def get_guests(self, name=None):
pool = Pool()
Folio = pool.get('hotel.folio')
folios = Folio.search([
('room', '=', self.id),
('arrival_date', '<=', date.today()),
('departure_date', '>', date.today())
])
res = []
for folio in folios:
res.append(len(folio.guests))
return sum(res)
def get_cleaning_type(self, name=None):
pool = Pool()
Config = pool.get('hotel.configuration')
Folio = pool.get('hotel.folio')
config = Config.get_configuration()
today = date.today()
read = Folio.search_read
folios = read([
('room', '=', self.id),
('departure_date', '=', today)
], fields_names=['registration_state'],
)
if folios:
return config.cleaning_check_out.id
else:
folios = read([
('room', '=', self.id),
('departure_date', '>', today),
('arrival_date', '<', today),
], fields_names=['registration_state'],
)
if folios:
return config.cleaning_occupied.id
else:
return config.cleaning_check_in.id
@classmethod
def set_dirty_rooms(cls, name=None):
rooms = cls.search([])
cls.write(rooms, {'state': 'dirty'})
class RoomAmenities(ModelSQL):
'Room - Amenities'
__name__ = 'hotel.room-hotel.amenities'
_rec_name = 'name'
room = fields.Many2One('hotel.room', 'Room', required=True,
ondelete='CASCADE')
amenities = fields.Many2One('hotel.amenities', 'Amenities',
required=True, ondelete='CASCADE')
class Amenities(ModelSQL, ModelView):
'Amenities'
__name__ = 'hotel.amenities'
_rec_name = 'name'
name = fields.Char('Name', required=True)
type_icon = fields.Char('Type icon')
notes = fields.Text('Notes')
type = fields.Selection([
('', ''),
('lingerie', 'Lingerie Room'),
('service', 'Service'),
], 'Type of Service')
cleaning_days = fields.One2Many('hotel.cleaning_days', 'amenities',
'Cleanning Days', states={
'invisible': Eval('type') != 'lingerie',
}, depends=['type'])
class RoomClassification(ModelSQL, ModelView):
'Room Classification'
__name__ = 'hotel.room.classification'
name = fields.Char('Name', required=True)
class CleanningDays(ModelSQL, ModelView):
'Cleanning Days'
__name__ = 'hotel.cleaning_days'
amenities = fields.Many2One('hotel.amenities', 'Amenities',
required=True, ondelete='CASCADE')
weekday = fields.Selection([
('1', 'Monday'),
('2', 'Tuesday'),
('3', 'Wednesday'),
('4', 'Thursday'),
('5', 'Friday'),
('6', 'Saturday'),
('7', 'Sunday'),
], 'Weekday', required=True)
note = fields.Text('Note')
class RoomTemplate(ModelSQL):
'Room - Template'
__name__ = 'hotel.room-product.template'
room = fields.Many2One('hotel.room', 'Room', required=True,
ondelete='CASCADE')
template = fields.Many2One('product.template', 'Product Template',
required=True, ondelete='CASCADE')
class HousekeepingStart(ModelView):
'Print Housekeeping Service Start'
__name__ = 'hotel.print_housekeeping.start'
date = fields.Date('Date', required=True)
employee = fields.Many2One('company.employee', 'Employee')
company = fields.Many2One('company.company', 'Company', required=True)
@staticmethod
def default_date():
Date_ = Pool().get('ir.date')
return Date_.today()
@staticmethod
def default_company():
return Transaction().context.get('company')
class Housekeeping(Wizard):
'Housekeeping Service'
__name__ = 'hotel.print_housekeeping'
start = StateView(
'hotel.print_housekeeping.start',
'hotel.print_housekeeping_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Open', 'print_', 'tryton-print', default=True),
])
print_ = StateReport('hotel.print_housekeeping.report')
def do_print_(self, action):
company = self.start.company
data = {
'date': self.start.date,
'employee': self.start.employee.id if self.start.employee else None,
'company': company.id,
}
return action, data
def transition_print_(self):
return 'end'
class HousekeepingReport(Report):
__name__ = 'hotel.print_housekeeping.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
Room = pool.get('hotel.room')
dom = []
if data['employee']:
dom.append(('employee.id', '=', data['employee']))
rooms = Room.search(dom)
total_guests = []
for room in rooms:
total_guests.append(room.guests)
report_context['records'] = rooms
report_context['company'] = Company(data['company'])
report_context['date'] = datetime.now()
report_context['total_guests'] = sum(total_guests)
return report_context
class HotelTask(ModelSQL, ModelView):
"Hotel Task"
__name__ = "hotel.task"
name = fields.Char('Name Task', required=True)
frecuency = fields.Integer('Frecuency', help='In days')
quantity = fields.Integer('Quantity')
# class HotelHousekeepingTask(ModelView, ModelSQL):
# 'Hotel Housekeeping Task'
# __name__ = 'hotel.housekeeping.task'
# room = fields.Many2One('hotel.room', 'Hotel Housekeeping',
# ondelete='CASCADE', select=True, required=True)
# task = fields.Many2One('hotel.task', 'Task', select=True, required=True)
# frecuency = fields.Integer('Frecuency', select=True, help='In days')
# quantity = fields.Integer('Quantity', select=True)
#
# @fields.depends('task', 'frecuency')
# def on_change_task(self, name=None):
# if self.task:
# self.frecuency = self.task.frecuency