# 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