trytonpsk-hotel/room.py

501 lines
17 KiB
Python
Raw Normal View History

2022-10-13 22:27:24 +02:00
# This file is part of Presik. The COPYRIGHT file at the top level of
2020-04-16 14:45:13 +02:00
# this repository contains the full copyright notices and license terms.
2023-11-15 00:14:34 +01:00
import copy
from datetime import datetime, date, timedelta
2022-07-28 06:42:24 +02:00
2022-07-27 23:34:13 +02:00
from trytond.model import ModelView, ModelSQL, Workflow, fields
2020-04-16 14:45:13 +02:00
from trytond.pyson import Eval
2022-10-13 22:27:24 +02:00
from trytond.wizard import Wizard, StateView, Button, StateReport
2022-07-27 23:34:13 +02:00
from trytond.pool import Pool
from trytond.report import Report
2022-07-28 06:42:24 +02:00
from trytond.transaction import Transaction
2020-04-16 14:45:13 +02:00
2022-08-29 19:28:13 +02:00
ROOM_STATUS = [
('inspected', 'Inspected'),
('not_authorized', 'Not Authorized'),
('dirty', 'Dirty'),
('clean', 'Clean'),
('maintenance', 'Maintenance'),
]
2020-04-16 14:45:13 +02:00
2022-07-27 23:34:13 +02:00
class CleaningType(ModelSQL, ModelView):
"Room Cleaning Type"
__name__ = "hotel.room.cleaning_type"
2023-10-04 05:31:09 +02:00
name = fields.Char('Name', required=True)
2022-07-27 23:34:13 +02:00
description = fields.Text('Description')
kind = fields.Selection([
('tweak', 'Tweak'),
('deep', 'Deep'),
('standard', 'Standard'),
], 'Kind', required=True)
2020-04-16 14:45:13 +02:00
2022-07-27 23:34:13 +02:00
class Room(Workflow, ModelSQL, ModelView):
2022-08-14 19:29:49 +02:00
'Hotel Room'
2020-04-16 14:45:13 +02:00
__name__ = 'hotel.room'
name = fields.Char('Name', required=True)
2023-10-31 22:14:43 +01:00
code = fields.Char('Code', required=True)
2020-04-16 14:45:13 +02:00
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',
2023-10-31 22:14:43 +01:00
'Classification', required=True)
2023-10-20 05:10:41 +02:00
location = fields.Many2One('hotel.location', 'Location')
2020-04-16 14:45:13 +02:00
amenities = fields.Many2Many('hotel.room-hotel.amenities', 'room',
'amenities', 'Amenities')
space = fields.Integer('Space', help='Space on m2')
2022-07-19 17:46:45 +02:00
channel_id = fields.Char('Channel Manager ID')
2020-04-16 14:45:13 +02:00
main_accommodation = fields.Many2One('product.template',
'Main Accommodation', ondelete='RESTRICT', depends=['templates'],
2023-10-31 22:14:43 +01:00
domain=[('id', 'in', Eval('templates', []))], required=True)
2020-04-16 14:45:13 +02:00
# 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))
2022-08-29 19:28:13 +02:00
state = fields.Selection(ROOM_STATUS, 'Status', required=True, readonly=True)
2022-07-27 23:34:13 +02:00
state_string = state.translated('state')
2022-07-31 01:15:08 +02:00
cleaning_type = fields.Function(fields.Many2One(
'hotel.room.cleaning_type', 'Cleaning Type', required=False),
'get_cleaning_type')
2022-07-27 23:34:13 +02:00
last_check_in = fields.DateTime('Last Check In')
last_check_out = fields.DateTime('Last Check Out')
2022-07-28 06:42:24 +02:00
last_clean = fields.DateTime('Last Clean')
2022-07-27 23:34:13 +02:00
housekeeping = fields.Many2One('company.employee', 'Employee')
notes = fields.Text('Notes')
2022-10-04 06:17:36 +02:00
max_accommodation = fields.Integer('Max. Accommodation')
2022-08-09 22:11:25 +02:00
guests = fields.Function(fields.Integer('Guests'), 'get_guests')
2022-10-10 16:09:55 +02:00
current_guests = fields.Function(fields.Integer('Guests'),
'get_current_guests')
2023-07-01 00:34:49 +02:00
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')
2023-07-04 18:37:01 +02:00
occupied_today = fields.Function(fields.Boolean(
'Occupied Today'), 'get_occupied_today')
2020-04-16 14:45:13 +02:00
@classmethod
def __setup__(cls):
super(Room, cls).__setup__()
2022-07-27 23:34:13 +02:00
cls._transitions |= set((
('clean', 'dirty'),
('clean', 'inspected'),
('clean', 'maintenance'),
2022-08-28 00:50:00 +02:00
('not_authorized', 'clean'),
('not_authorized', 'dirty'),
2022-07-27 23:34:13 +02:00
('dirty', 'clean'),
('dirty', 'maintenance'),
('dirty', 'inspected'),
2022-08-28 00:50:00 +02:00
('dirty', 'not_authorized'),
2022-07-27 23:34:13 +02:00
('maintenance', 'inspected'),
('maintenance', 'dirty'),
('inspected', 'dirty'),
('inspected', 'maintenance'),
('inspected', 'clean'),
))
2022-11-01 04:53:08 +01:00
cls._order = [('code', 'DESC')]
2022-07-27 23:34:13 +02:00
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']),
},
2022-08-28 00:50:00 +02:00
'not_authorized': {
'invisible': Eval('state').in_(['maintenance']),
},
2022-07-27 23:34:13 +02:00
})
2020-04-16 14:45:13 +02:00
@staticmethod
def default_active():
return True
2022-07-27 23:34:13 +02:00
@staticmethod
def default_state():
return 'dirty'
@classmethod
@ModelView.button
@Workflow.transition('clean')
def clean(cls, records):
for rec in records:
2022-07-31 01:15:08 +02:00
rec.last_clean = datetime.now()
2022-07-27 23:34:13 +02:00
rec.cleaning_type = None
rec.save()
2022-08-28 00:50:00 +02:00
@classmethod
@ModelView.button
@Workflow.transition('not_authorized')
def not_authorized(cls, records):
pass
2022-07-27 23:34:13 +02:00
@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
2022-07-19 17:46:45 +02:00
2023-11-15 00:14:34 +01:00
@classmethod
2023-11-15 00:17:45 +01:00
def get_stock(cls, acco_ids=None):
2023-11-15 00:14:34 +01:00
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 = {}
2023-11-15 00:17:45 +01:00
stock = cls.get_stock(accos)
2023-11-15 00:14:34 +01:00
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
2023-07-10 23:30:13 +02:00
@classmethod
2023-11-05 21:19:13 +01:00
def available_by_classification(cls, start_date, end_date, rate_plan=None):
2023-07-10 23:30:13 +02:00
pool = Pool()
Folio = pool.get('hotel.folio')
2023-07-26 16:46:44 +02:00
RatePlan = pool.get('hotel.rate_plan')
fmt = "%Y-%m-%d"
2023-11-05 21:19:13 +01:00
# 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)
2023-07-26 16:46:44 +02:00
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=[])]
2023-11-05 21:19:13 +01:00
rooms_available = Folio.get_available_rooms(
start_date, end_date, all_rooms)
2023-07-10 23:30:13 +02:00
2023-07-26 16:46:44 +02:00
clssf = {}
2023-07-10 23:30:13 +02:00
rooms = cls.browse(rooms_available)
2023-11-05 21:19:13 +01:00
best_price = RatePlan.best_price
2023-07-10 23:30:13 +02:00
for room in rooms:
2023-07-26 16:46:44 +02:00
product = room.main_accommodation.products[0]
2023-11-05 21:19:13 +01:00
price_taxed = best_price(product, price_list)
2023-07-26 16:46:44 +02:00
_room = {
"id": room.id,
"name": room.name,
"max_accommodation": room.max_accommodation,
2023-10-31 22:14:43 +01:00
"amenities": [
{"name": ame.name, "type_icon": ame.type_icon} for ame in room.amenities
]
2023-07-26 16:46:44 +02:00
}
try:
clssf[product.id]['available'].append(1)
clssf[product.id]['rooms'].append(_room)
except:
clssf[product.id] = {
2023-11-05 21:19:13 +01:00
"product": {
"id": product.id,
"name": product.name,
"images": [img.image_url for img in product.images]
},
"sale_price_taxed": price_taxed,
2023-07-26 16:46:44 +02:00
"available": [1],
"rooms": [_room],
2023-11-05 21:19:13 +01:00
"rate_plan": price_list, # ????
2023-07-26 16:46:44 +02:00
}
2023-07-10 23:30:13 +02:00
for k, v in clssf.items():
v['available'] = sum(v['available'])
2023-07-11 19:28:07 +02:00
return list(clssf.values())
2023-07-10 23:30:13 +02:00
2022-10-10 16:09:55 +02:00
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)
2023-07-04 18:37:01 +02:00
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)
2023-07-01 00:34:49 +02:00
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)
2022-08-09 22:11:25 +02:00
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)
2022-07-31 01:15:08 +02:00
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
2022-08-14 19:29:49 +02:00
@classmethod
def set_dirty_rooms(cls, name=None):
rooms = cls.search([])
cls.write(rooms, {'state': 'dirty'})
2020-04-16 14:45:13 +02:00
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')
2020-04-16 14:45:13 +02:00
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'),
2022-07-19 17:46:45 +02:00
], 'Weekday', required=True)
2020-04-16 14:45:13 +02:00
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')
2022-07-27 23:34:13 +02:00
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'
2022-10-13 22:27:24 +02:00
start = StateView(
'hotel.print_housekeeping.start',
2022-07-27 23:34:13 +02:00
'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')
2022-07-28 06:42:24 +02:00
Room = pool.get('hotel.room')
2022-07-27 23:34:13 +02:00
dom = []
if data['employee']:
dom.append(('employee.id', '=', data['employee']))
2022-07-28 06:42:24 +02:00
rooms = Room.search(dom)
2022-08-09 22:11:25 +02:00
total_guests = []
for room in rooms:
total_guests.append(room.guests)
2022-07-28 06:42:24 +02:00
report_context['records'] = rooms
2022-07-27 23:34:13 +02:00
report_context['company'] = Company(data['company'])
report_context['date'] = datetime.now()
2022-08-09 22:11:25 +02:00
report_context['total_guests'] = sum(total_guests)
2022-07-27 23:34:13 +02:00
return report_context
class HotelTask(ModelSQL, ModelView):
"Hotel Task"
__name__ = "hotel.task"
2023-10-20 05:10:41 +02:00
name = fields.Char('Name Task', required=True)
frecuency = fields.Integer('Frecuency', help='In days')
quantity = fields.Integer('Quantity')
2022-07-27 23:34:13 +02:00
# 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