mirror of
https://bitbucket.org/presik/trytonpsk-hotel.git
synced 2023-12-14 07:52:52 +01:00
1675 lines
58 KiB
Python
1675 lines
58 KiB
Python
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
|
|
from decimal import Decimal
|
|
from datetime import timedelta, datetime, date
|
|
from trytond.model import ModelView, Workflow, ModelSQL, fields
|
|
from trytond.pyson import Eval, Bool
|
|
from trytond.pool import Pool
|
|
from trytond.report import Report
|
|
from trytond.wizard import (
|
|
Wizard, StateView, StateAction, Button, StateTransition, StateReport
|
|
)
|
|
from trytond.transaction import Transaction
|
|
from trytond.model.exceptions import AccessError
|
|
from trytond.exceptions import UserError
|
|
from trytond.i18n import gettext
|
|
from .constants import (REGISTRATION_STATE, COMPLEMENTARY, INVOICE_STATES,
|
|
COLOR_BOOKING, COLOR_MNT)
|
|
|
|
STATES_CHECKIN = {
|
|
'readonly': Eval('registration_state').in_(
|
|
['check_in', 'no_show', 'cancelled']
|
|
),
|
|
'required': Eval('registration_state') == 'check_in',
|
|
}
|
|
|
|
STATES_CHECKOUT = {
|
|
'readonly': Eval('registration_state').in_(
|
|
['check_out', 'no_show', 'cancelled']
|
|
),
|
|
'required': Eval('registration_state') == 'check_in',
|
|
}
|
|
|
|
STATES_OP = {
|
|
'readonly': Eval('state').in_(['check_out', 'done', 'cancelled'])
|
|
}
|
|
|
|
STATES_MNT = {'readonly': Eval('state') != 'draft'}
|
|
_ZERO = Decimal('0')
|
|
|
|
|
|
class Folio(ModelSQL, ModelView):
|
|
'Folio'
|
|
__name__ = 'hotel.folio'
|
|
STATES = {
|
|
'required': Eval('registration_state') == 'check_in',
|
|
'readonly': Eval('registration_state') == 'check_in',
|
|
}
|
|
booking = fields.Many2One('hotel.booking', 'Booking', ondelete='CASCADE',
|
|
select=True)
|
|
reference = fields.Char('Reference', states=STATES)
|
|
registration_card = fields.Char('Registration Card', readonly=True,
|
|
select=True, help="Unique sequence for card guest registration.")
|
|
room = fields.Many2One('hotel.room', 'Room', select=True,
|
|
states=STATES_CHECKIN)
|
|
arrival_date = fields.Date('Arrival Date', required=True,
|
|
states=STATES_CHECKOUT)
|
|
departure_date = fields.Date('Departure Date', required=True,
|
|
states=STATES_CHECKOUT)
|
|
product = fields.Many2One('product.product', 'Product',
|
|
select=True, domain=[
|
|
('template.type', '=', 'service'),
|
|
('template.kind', '=', 'accommodation'),
|
|
], required=True, states=STATES_CHECKOUT)
|
|
unit_price = fields.Numeric('Unit Price', digits=(16, 4),
|
|
states={
|
|
'required': Bool(Eval('product')),
|
|
'readonly': Eval('registration_state') == 'check_out',
|
|
})
|
|
unit_price_w_tax = fields.Function(fields.Numeric('Unit Price'),
|
|
'get_unit_price_w_tax')
|
|
uom = fields.Many2One('product.uom', 'UOM', readonly=True)
|
|
main_guest = fields.Many2One('party.party', 'Main Guest', select=True,
|
|
states={
|
|
'readonly': True
|
|
}
|
|
)
|
|
contact = fields.Char('Contact', states=STATES_CHECKIN)
|
|
num_children = fields.Function(fields.Integer('No. Children'),
|
|
'get_num_children')
|
|
nights_quantity = fields.Integer('Nights', states={'readonly': True},
|
|
depends=['arrival_date', 'departure_date'])
|
|
host_quantity = fields.Integer('Host', states=STATES_CHECKIN)
|
|
estimated_arrival_time = fields.Time('Estimated Arrival Time',
|
|
states=STATES_CHECKIN)
|
|
unit_digits = fields.Function(fields.Integer('Unit Digits'), 'get_unit_digits')
|
|
notes = fields.Text('Notes')
|
|
total_amount = fields.Function(fields.Numeric('Total Amount',
|
|
digits=(16, 2)), 'get_totals')
|
|
total_balance = fields.Function(fields.Numeric('Total Balance',
|
|
digits=(16, 2)), 'get_totals')
|
|
commission_amount = fields.Numeric('Commission Amount', digits=(16, 2),
|
|
depends=['nights_quantity', 'unit_price'], states={'readonly': True}
|
|
)
|
|
guests = fields.One2Many('hotel.folio.guest', 'folio', 'Guests',
|
|
states={'readonly': Eval('registration_state').in_(['check_out'])})
|
|
# nationality = fields.Many2One('party.nationality', 'Nationality',
|
|
# states=STATES_CHECKIN)
|
|
# origin_country = fields.Many2One('party.nationality', 'Origin Country',
|
|
# select=True, states=STATES_CHECKIN)
|
|
# target_country = fields.Many2One('party.nationality', 'Target Country',
|
|
# select=True, states=STATES_CHECKIN)
|
|
registration_state = fields.Selection(REGISTRATION_STATE,
|
|
'Registration State', readonly=True, select=True)
|
|
registration_state_string = registration_state.translated('registration_state')
|
|
charges = fields.One2Many('hotel.folio.charge', 'folio', 'Charges',
|
|
states={
|
|
'readonly': ~Eval('registration_state').in_(['check_in']),
|
|
})
|
|
party = fields.Many2One('party.party', 'Party to Bill', select=True,
|
|
states={
|
|
'readonly': Eval('registration_state').in_(['check_out']),
|
|
})
|
|
invoice_line = fields.Many2One('account.invoice.line', 'Invoice Line')
|
|
to_invoice = fields.Boolean('To Invoice', states={
|
|
'invisible': Bool(Eval('invoice_line')),
|
|
}, depends=['invoice_line'], help='Mark this checkbox if you want to invoice this item')
|
|
invoice = fields.Function(fields.Many2One('account.invoice', 'Invoice',
|
|
depends=['invoice_line']), 'get_invoice')
|
|
invoice_state = fields.Function(fields.Selection(
|
|
INVOICE_STATES, 'Invoice State'), 'get_invoice_state')
|
|
type_complementary = fields.Selection(COMPLEMENTARY, 'Type Complementary',
|
|
states={
|
|
'invisible': ~Bool(Eval('complementary')),
|
|
'required': Bool(Eval('complementary')),
|
|
})
|
|
breakfast_included = fields.Boolean('Breakfast Included', states={
|
|
'readonly': Eval('registration_state') != 'check_in',
|
|
})
|
|
room_amount = fields.Function(fields.Numeric('Room Amount',
|
|
digits=(16, 2), depends=['nights_quantity', 'unit_price']
|
|
), 'on_change_with_room_amount')
|
|
stock_moves = fields.Many2Many('hotel.folio-stock.move', 'folio',
|
|
'move', 'Stock Moves', states={'readonly': True})
|
|
channel = fields.Function(fields.Many2One('hotel.channel', 'Channel'),
|
|
'get_channel')
|
|
num_children = fields.Function(fields.Integer('Num. Children'),
|
|
'get_num_guests')
|
|
num_adults = fields.Function(fields.Integer('Num. Adults'),
|
|
'get_num_guests')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Folio, cls).__setup__()
|
|
cls._check_modify_exclude = [
|
|
'nationality', 'origin_country', 'target_country',
|
|
'registration_state', 'guests'
|
|
],
|
|
cls._buttons.update({
|
|
'check_in': {
|
|
'invisible': Eval('registration_state').in_(
|
|
['check_in', 'check_out']
|
|
),
|
|
},
|
|
'check_out': {
|
|
'invisible': Eval('registration_state') != 'check_in',
|
|
},
|
|
'bill': {
|
|
'invisible': Eval('registration_state') != 'check_out',
|
|
},
|
|
'pay': {
|
|
'invisible': Eval('registration_state') != 'check_out',
|
|
}
|
|
})
|
|
|
|
def get_num_guests(self, name):
|
|
res = []
|
|
for guest in self.guests:
|
|
if name == 'num_children' and guest.type_guest == 'child':
|
|
res.append(1)
|
|
elif name == 'num_adults' and guest.type_guest == 'adult':
|
|
res.append(1)
|
|
return sum(res)
|
|
|
|
def get_rec_name(self, name=None):
|
|
if self.registration_card:
|
|
name = f'{self.registration_card}:'
|
|
if self.main_guest:
|
|
name = name + self.main_guest.name
|
|
return name
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
_, operator, value = clause
|
|
if operator.startswith('!') or operator.startswith('not '):
|
|
bool_op = 'AND'
|
|
else:
|
|
bool_op = 'OR'
|
|
domain = [
|
|
bool_op,
|
|
('main_guest', operator, value),
|
|
('registration_card', operator, value),
|
|
('product.name', operator, value),
|
|
('party.name', operator, value),
|
|
('reference', operator, value),
|
|
]
|
|
return domain
|
|
|
|
@classmethod
|
|
def create(cls, values):
|
|
folios = super(Folio, cls).create(values)
|
|
for folio in folios:
|
|
folio.update_nights(folio.arrival_date, folio.departure_date)
|
|
folio.update_commission()
|
|
folio.save()
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def check_in(cls, records):
|
|
for rec in records:
|
|
# rec.booking.party.pre_validate()
|
|
if rec.booking.state == 'offer':
|
|
raise UserError(gettext('hotel.msg_missing_confirm_booking'))
|
|
if rec.main_guest is None:
|
|
raise UserError(gettext('hotel.msg_missing_main_guest'))
|
|
if rec.room is None:
|
|
raise UserError(gettext('hotel.msg_missing_select_room'))
|
|
rec.set_registration_number()
|
|
rec.check_room()
|
|
cls.update_room(rec.room, 'dirty')
|
|
cls.write(records, {'registration_state': 'check_in'})
|
|
|
|
def check_room(self):
|
|
Housekeeping = Pool().get('hotel.housekeeping')
|
|
hk, = Housekeeping.search([
|
|
('room', '=', self.room.id)
|
|
])
|
|
if hk.state != 'clean':
|
|
raise UserError(gettext('hotel.msg_room_no_clean', s=hk.room.name))
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def check_out(cls, records):
|
|
for record in records:
|
|
cls.write([record], {'registration_state': 'check_out'})
|
|
cls.update_room(record.room, 'dirty')
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def bill(cls, records):
|
|
for rec in records:
|
|
rec.create_invoice()
|
|
|
|
@classmethod
|
|
@ModelView.button_action('hotel.wizard_booking_advance_voucher')
|
|
def pay(cls, records):
|
|
pass
|
|
|
|
@staticmethod
|
|
def default_to_invoice():
|
|
return True
|
|
|
|
@staticmethod
|
|
def default_registration_state():
|
|
return 'pending'
|
|
|
|
@classmethod
|
|
def update_room(cls, room, state):
|
|
Housekeeping = Pool().get('hotel.housekeeping')
|
|
hk_rooms = Housekeeping.search([
|
|
('room', '=', room.id),
|
|
])
|
|
for hk_room in hk_rooms:
|
|
hk_room.state = state
|
|
hk_room.save()
|
|
|
|
def get_invoice_state(self, name=None):
|
|
if self.invoice_line:
|
|
return self.invoice_line.invoice.state
|
|
|
|
def get_channel(self, name=None):
|
|
if self.booking and self.booking.channel:
|
|
return self.booking.channel.id
|
|
|
|
def set_registration_number(self):
|
|
"""
|
|
Fill the number field for registration card with sequence
|
|
"""
|
|
pool = Pool()
|
|
Config = pool.get('hotel.configuration')
|
|
config = Config.get_configuration()
|
|
|
|
if self.registration_card:
|
|
return
|
|
if not config.registration_card_sequence:
|
|
raise UserError(gettext('hotel.msg_missing_sequence_registration'))
|
|
number = config.registration_card_sequence.get()
|
|
self.registration_card = number
|
|
self.save()
|
|
|
|
def create_invoice(self):
|
|
pool = Pool()
|
|
Booking = pool.get('hotel.booking')
|
|
Booking.create_invoice([self])
|
|
Booking.check_finished([self.booking])
|
|
|
|
def get_unit_price_w_tax(self, name=None):
|
|
Tax = Pool().get('account.tax')
|
|
res = self.unit_price or 0
|
|
if self.unit_price:
|
|
values = Tax.compute(
|
|
self.product.template.customer_taxes_used,
|
|
self.unit_price, 1)
|
|
if values:
|
|
value = values[0]
|
|
res = value['base'] + value['amount']
|
|
return res
|
|
|
|
@fields.depends('unit_price', 'nights_quantity', 'arrival_date',
|
|
'departure_date')
|
|
def on_change_with_room_amount(self, name=None):
|
|
res = 0
|
|
if self.unit_price and self.nights_quantity:
|
|
res = self.unit_price * self.nights_quantity
|
|
return round(res, Folio.total_amount.digits[1])
|
|
|
|
@staticmethod
|
|
def default_main_guest():
|
|
party = Transaction().context.get('party')
|
|
return party
|
|
|
|
@staticmethod
|
|
def default_host_quantity():
|
|
return 1
|
|
|
|
@staticmethod
|
|
def default_accommodation():
|
|
Configuration = Pool().get('hotel.configuration')
|
|
configuration = Configuration.get_configuration()
|
|
if configuration.default_accommodation:
|
|
return configuration.default_accommodation.id
|
|
|
|
@staticmethod
|
|
def default_breakfast_included():
|
|
return True
|
|
|
|
@classmethod
|
|
def validate(cls, lines):
|
|
super(Folio, cls).validate(lines)
|
|
for line in lines:
|
|
# line.check_method()
|
|
pass
|
|
|
|
def get_invoice(self, name=None):
|
|
if self.invoice_line and self.invoice_line.invoice:
|
|
return self.invoice_line.invoice.id
|
|
|
|
def get_room_info(self):
|
|
description = ' \n'.join([
|
|
self.product.rec_name,
|
|
'Huesped Principal: ' + self.main_guest.name if self.main_guest else '',
|
|
'Habitacion: ' + self.room.name,
|
|
'Llegada: ' + str(self.arrival_date),
|
|
'Salida: ' + str(self.departure_date),
|
|
])
|
|
return description
|
|
|
|
@fields.depends('product', 'unit_price', 'uom', 'booking',
|
|
'nights_quantity', 'commission_amount')
|
|
def on_change_product(self):
|
|
Product = Pool().get('product.product')
|
|
if self.product and self.booking:
|
|
self.uom = self.product.default_uom.id
|
|
with Transaction().set_context(
|
|
self.booking.get_context_price(self.product)):
|
|
self.unit_price = Product.get_sale_price([self.product],
|
|
self.nights_quantity or 0)[self.product.id]
|
|
if self.unit_price:
|
|
self.unit_price = self.booking.currency.round(self.unit_price)
|
|
else:
|
|
self.unit_price = self.product.list_price
|
|
self.update_commission()
|
|
|
|
def update_commission(self):
|
|
if self.nights_quantity and self.unit_price and self.booking:
|
|
if not self.booking.channel:
|
|
return
|
|
amount = self.on_change_with_room_amount()
|
|
self.commission_amount = self.booking.channel.compute(amount)
|
|
|
|
@fields.depends('arrival_date', 'departure_date', 'unit_price', 'booking')
|
|
def on_change_arrival_date(self):
|
|
if self.arrival_date and self.departure_date:
|
|
self.update_nights(self.arrival_date, self.departure_date)
|
|
self.update_commission()
|
|
|
|
@fields.depends('arrival_date', 'departure_date', 'unit_price', 'booking')
|
|
def on_change_departure_date(self):
|
|
if self.arrival_date and self.departure_date:
|
|
self.update_nights(self.arrival_date, self.departure_date)
|
|
self.update_commission()
|
|
|
|
def check_method(self):
|
|
"""
|
|
Check the methods.
|
|
"""
|
|
Date = Pool().get('ir.date')
|
|
if self.registration_state in (['check_in', 'check_out']):
|
|
raise UserError(gettext('hotel.msg_reservation_checkin'))
|
|
if self.arrival_date < Date.today():
|
|
raise UserError(gettext('hotel.msg_invalid_arrival_date'))
|
|
if self.arrival_date >= self.departure_date:
|
|
raise UserError(gettext('hotel.msg_invalid_date'))
|
|
|
|
def get_state(self, name):
|
|
if self.booking:
|
|
return self.booking.state
|
|
|
|
def get_host_quantity(self, name):
|
|
res = 0
|
|
if self.num_adult:
|
|
res += self.num_adult
|
|
if self.num_children:
|
|
res += self.num_children
|
|
return res
|
|
|
|
@classmethod
|
|
def get_available_rooms(cls, start_date, end_date, rooms_ids=[], oper=None):
|
|
"""
|
|
Look for available rooms.
|
|
|
|
given the date interval, return a list of room ids.
|
|
|
|
a room is available if it has no operation that overlaps
|
|
with the given date interval.
|
|
|
|
the optional 'rooms' list is a list of room instance, is an
|
|
additional filter, specifying the ids of the desirable rooms.
|
|
|
|
the optional 'oper' is an operation object that has to be
|
|
filtered out of the test. it is useful for validating an already
|
|
existing operation.
|
|
"""
|
|
|
|
if start_date >= end_date:
|
|
raise UserError(gettext('hotel.msg_invalid_date_range'))
|
|
|
|
# define the domain of the operations that find a
|
|
# room to be available
|
|
dom = ['AND', ['OR',
|
|
[
|
|
('arrival_date', '>=', start_date),
|
|
('arrival_date', '<', end_date),
|
|
], [
|
|
('departure_date', '<=', end_date),
|
|
('departure_date', '>', start_date),
|
|
], [
|
|
('arrival_date', '<=', start_date),
|
|
('departure_date', '>=', end_date),
|
|
],
|
|
]]
|
|
|
|
## If oper was specified, do not compare the operations with it
|
|
if oper is not None:
|
|
dom.append(('id', '!=', oper.id))
|
|
|
|
if rooms_ids:
|
|
dom.append(('room', 'in', rooms_ids))
|
|
|
|
folios = cls.search(dom)
|
|
rooms_not_available_ids = [folio.room.id for folio in folios]
|
|
rooms_available_ids = set(rooms_ids) - set(rooms_not_available_ids)
|
|
return list(rooms_available_ids)
|
|
|
|
def update_nights(self, arrival_date, departure_date):
|
|
self.nights_quantity = (departure_date - arrival_date).days
|
|
|
|
def get_totals(self, name):
|
|
"""
|
|
The total amount of booking based on room flat price.
|
|
TODO: If room fee is applied should be used for total price calculation
|
|
instead of flat price. Fee is linked to channel management.
|
|
"""
|
|
res = []
|
|
amount_nights = 0
|
|
if self.nights_quantity and self.unit_price_w_tax:
|
|
amount_nights = self.nights_quantity * self.unit_price_w_tax
|
|
if name == 'total_amount' or (name == 'total_balance' and not self.invoice_line):
|
|
res.append(amount_nights)
|
|
for charge in self.charges:
|
|
res.append(charge.amount)
|
|
res = round(sum(res), Folio.total_amount.digits[1])
|
|
return res
|
|
|
|
@fields.depends('nights_quantity', 'unit_price', 'booking')
|
|
def on_change_with_commission_amount(self):
|
|
"""
|
|
Calculation of commission amount for channel based on booking
|
|
"""
|
|
res = Decimal(0)
|
|
if self.nights_quantity and self.unit_price and self.booking.channel:
|
|
amount = self.on_change_with_room_amount()
|
|
res = self.booking.channel.compute(amount)
|
|
return res
|
|
|
|
@classmethod
|
|
def _get_check_rooms(cls, kind):
|
|
# Dash Report
|
|
# kind : arrival or departure
|
|
field_target = kind + '_date'
|
|
folios = cls.search_read([
|
|
(field_target, '=', date.today())
|
|
], fields_names=['id'])
|
|
return len(folios)
|
|
|
|
@classmethod
|
|
def report_check_in_today(cls, args):
|
|
# Dash Report
|
|
value = cls._get_check_rooms('arrival')
|
|
res = {
|
|
'value': value,
|
|
'description': '',
|
|
'selector': {},
|
|
'default_option': None,
|
|
'meta': 'prueba 1',
|
|
'in_thousands': None
|
|
}
|
|
return res
|
|
|
|
@classmethod
|
|
def report_check_out_today(cls, args):
|
|
# Dash Report
|
|
value = cls._get_check_rooms('departure')
|
|
res = {
|
|
'value': value,
|
|
'description': '',
|
|
'selector': {},
|
|
'default_option': None,
|
|
'meta': 'prueba 1',
|
|
'in_thousands': None
|
|
}
|
|
return res
|
|
|
|
@classmethod
|
|
def _get_occupation(cls):
|
|
today = date.today()
|
|
dom = [
|
|
('arrival_date', '<=', today),
|
|
('departure_date', '>', today),
|
|
]
|
|
folios = cls.search_read(dom, fields_names=['id', 'guests'])
|
|
return folios
|
|
|
|
@classmethod
|
|
def report_occupation_by_room(cls, args):
|
|
# Dash Report
|
|
folios = cls._get_occupation()
|
|
value = len(folios)
|
|
res = {
|
|
'value': value,
|
|
'description': '',
|
|
'selector': {},
|
|
'default_option': None,
|
|
'meta': 'prueba 1',
|
|
'in_thousands': None
|
|
}
|
|
return res
|
|
|
|
@classmethod
|
|
def report_number_guests(cls, args):
|
|
# Dash Report
|
|
folios = cls._get_occupation()
|
|
value = 0
|
|
for folio in folios:
|
|
value += len(folio['guests'])
|
|
|
|
res = {
|
|
'value': value,
|
|
'description': '',
|
|
'selector': {},
|
|
'default_option': None,
|
|
'meta': 'prueba 1',
|
|
'in_thousands': None
|
|
}
|
|
return res
|
|
|
|
|
|
class FolioGuest(ModelSQL, ModelView):
|
|
'Folio Guest'
|
|
__name__ = 'hotel.folio.guest'
|
|
folio = fields.Many2One('hotel.folio', 'Folio', required=True,
|
|
ondelete='CASCADE')
|
|
main_guest = fields.Boolean('Main Guest')
|
|
type_guest = fields.Selection([
|
|
('adult', 'Adult'),
|
|
('child', 'Child'),
|
|
], 'Type Guest', required=True)
|
|
type_guest_string = type_guest.translated('type_guest')
|
|
nationality = fields.Many2One('party.nationality', 'Nationality')
|
|
origin_country = fields.Many2One('party.nationality', 'Origin Country',
|
|
select=True)
|
|
target_country = fields.Many2One('party.nationality', 'Target Country',
|
|
select=True)
|
|
# New fields for speed reason
|
|
type_document = fields.Selection([
|
|
('12', 'Tarjeta de Identidad'),
|
|
('13', 'Cedula de Ciudadania'),
|
|
('21', 'Tarjeta de Extranjeria'),
|
|
('22', 'Cedula de Extranjeria'),
|
|
('41', 'Pasaporte'),
|
|
], 'Document Type', required=True)
|
|
type_document_string = type_document.translated('type_document')
|
|
id_number = fields.Char('Id Number', select=True, required=True)
|
|
name = fields.Char('Name', select=True, required=True)
|
|
mobile = fields.Char('Mobile', select=True, states={
|
|
'required': Eval('main_guest', False)
|
|
})
|
|
email = fields.Char('Email', select=True, states={
|
|
'required': Eval('main_guest', False)
|
|
})
|
|
profession = fields.Char('Profession', select=True)
|
|
birthday = fields.Date('Birthday', select=True)
|
|
visa_date = fields.Date('Visa Date', select=True)
|
|
visa_category = fields.Char('Visa Category')
|
|
visa_number = fields.Char('Visa Number')
|
|
sex = fields.Selection([
|
|
('female', 'Female'),
|
|
('male', 'Male'),
|
|
('', ''),
|
|
], 'Sex', required=True)
|
|
sex_string = sex.translated('sex')
|
|
first_name = fields.Char('First Name')
|
|
second_name = fields.Char('Second Name')
|
|
first_family_name = fields.Char('First Family Name')
|
|
second_family_name = fields.Char('Second Family Name')
|
|
notes = fields.Text('Notes')
|
|
|
|
@fields.depends('name', 'first_name', 'second_name',
|
|
'first_family_name', 'second_family_name')
|
|
def on_change_name(self):
|
|
second_family_name = None
|
|
first_family_name = None
|
|
second_name = None
|
|
first_name = None
|
|
if self.name:
|
|
names = self.name.split(' ')
|
|
first_name = names[0]
|
|
second_family_name = names[-1]
|
|
if len(names) > 1:
|
|
first_family_name = names[-2]
|
|
if len(names) == 2:
|
|
second_family_name = None
|
|
first_family_name = names[1]
|
|
elif len(names) == 5:
|
|
second_name = names[1] + ' ' + names[2]
|
|
elif len(names) == 4:
|
|
second_name = names[1]
|
|
|
|
self.second_family_name = second_family_name
|
|
self.first_family_name = first_family_name
|
|
self.second_name = second_name
|
|
self.first_name = first_name
|
|
|
|
@staticmethod
|
|
def default_type_guest():
|
|
return 'adult'
|
|
|
|
@staticmethod
|
|
def default_sex():
|
|
return 'male'
|
|
|
|
@staticmethod
|
|
def default_type_document():
|
|
return '13'
|
|
|
|
@fields.depends('nationality', 'origin_country', 'target_country')
|
|
def on_change_nationality(self):
|
|
if self.nationality:
|
|
self.target_country = self.nationality.id
|
|
self.origin_country = self.nationality.id
|
|
|
|
@classmethod
|
|
def manage_party(cls, v):
|
|
Party = Pool().get('party.party')
|
|
is_main_guest = v.pop('main_guest')
|
|
folio = v.pop('folio', None)
|
|
for val in ('type_guest', 'origin_country', 'target_country', 'profession'):
|
|
_ = v.pop(val, None)
|
|
|
|
if is_main_guest:
|
|
parties = Party.search([
|
|
('id_number', '=', v['id_number']),
|
|
])
|
|
email = v.pop('email', '')
|
|
mobile = v.pop('mobile', '')
|
|
v['type_person'] = 'persona_natural'
|
|
if not parties:
|
|
v['contact_mechanisms'] = [
|
|
('create', [
|
|
{'type': 'email', 'value': email},
|
|
{'type': 'mobile', 'value': mobile},
|
|
])
|
|
]
|
|
party, = Party.create([v])
|
|
else:
|
|
Party.write(parties, v)
|
|
party = parties[0]
|
|
has_email = False
|
|
has_mobile = False
|
|
for cm in party.contact_mechanisms:
|
|
if email and cm.type == 'email':
|
|
cm.value = email
|
|
has_email = True
|
|
elif mobile and cm.type == 'mobile':
|
|
cm.value = mobile
|
|
has_mobile = True
|
|
cm.save()
|
|
to_write = []
|
|
if not has_mobile and mobile:
|
|
to_write.append({'type': 'mobile', 'value': mobile})
|
|
if not has_email and email:
|
|
to_write.append({'type': 'email', 'value': email})
|
|
if to_write:
|
|
Party.write(
|
|
parties,
|
|
{'contact_mechanisms': [('create', to_write)]}
|
|
)
|
|
|
|
if folio:
|
|
Folio = Pool().get('hotel.folio')
|
|
folio = Folio(folio)
|
|
folio.main_guest = party.id
|
|
folio.save()
|
|
booking = folio.booking
|
|
if not booking.party:
|
|
booking.party = party.id
|
|
booking.save()
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
super(FolioGuest, cls).create(vlist)
|
|
for v in vlist:
|
|
cls.manage_party(v)
|
|
|
|
@classmethod
|
|
def write(cls, records, values):
|
|
super(FolioGuest, cls).write(records, values)
|
|
fields = cls.fields_get()
|
|
for val in ('id', 'rec_name', 'write_date', 'write_uid', 'create_date'):
|
|
fields.pop(val)
|
|
data = {}
|
|
fields_list = fields.keys()
|
|
rec = records[0]
|
|
for field in fields_list:
|
|
data[field] = getattr(rec, field)
|
|
|
|
data.update(values)
|
|
cls.manage_party(data)
|
|
|
|
|
|
class OperationMaintenance(Workflow, ModelSQL, ModelView):
|
|
'Operation Maintenance'
|
|
__name__ = 'hotel.operation.maintenance'
|
|
operation = fields.Many2One('hotel.operation', 'Operation', states=STATES_MNT)
|
|
room = fields.Many2One('hotel.room', 'Room', states=STATES_MNT, select=True,
|
|
required=True)
|
|
kind = fields.Selection([
|
|
('maintenance', 'Maintenance'),
|
|
], 'Kind', readonly=True, select=True)
|
|
start_date = fields.Date('Start Date', states=STATES_MNT, required=True,
|
|
select=True)
|
|
end_date = fields.Date('End Date', states=STATES_MNT, required=True,
|
|
select=True)
|
|
description = fields.Text('Description', states=STATES_MNT, select=True)
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('confirmed', 'Confirmed'),
|
|
('finished', 'Finished'),
|
|
('cancelled', 'Canceled'),
|
|
], 'State', readonly=True, select=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(OperationMaintenance, cls).__setup__()
|
|
cls._transitions |= set((
|
|
('draft', 'confirmed'),
|
|
('draft', 'cancelled'),
|
|
('confirmed', 'finished'),
|
|
('confirmed', 'cancelled'),
|
|
))
|
|
cls._buttons.update({
|
|
'cancel': {
|
|
'invisible': ~Eval('state').in_(['draft', 'confirmed']),
|
|
},
|
|
'draft': {
|
|
'invisible': Eval('state') != 'confirmed',
|
|
},
|
|
'confirm': {
|
|
'invisible': Eval('state') != 'draft',
|
|
},
|
|
'finish': {
|
|
'invisible': Eval('state') != 'confirmed',
|
|
},
|
|
})
|
|
|
|
@staticmethod
|
|
def default_kind():
|
|
return 'maintenance'
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('draft')
|
|
def draft(cls, records):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('cancelled')
|
|
def cancel(cls, records):
|
|
for mant in records:
|
|
mant.delete_operation()
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('confirmed')
|
|
def confirm(cls, records):
|
|
for mant in records:
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('finished')
|
|
def finish(cls, records):
|
|
for mant in records:
|
|
pass
|
|
|
|
@classmethod
|
|
def write(cls, records, values):
|
|
Room = Pool().get('hotel.room')
|
|
for rec in records:
|
|
if values.get('start_date') or values.get('end_date') or values.get('room'):
|
|
start_date = values.get('start_date') or rec.start_date
|
|
end_date = values.get('end_date') or rec.end_date
|
|
room = Room(values.get('room') or rec.room)
|
|
# Operation().check_dates(start_date, end_date, room, rec.operation)
|
|
# rec.update_operation({
|
|
# 'start_date': start_date,
|
|
# 'end_date': end_date,
|
|
# 'room': room.id,
|
|
# })
|
|
super(OperationMaintenance, cls).write(records, values)
|
|
|
|
def check_method(self):
|
|
"""
|
|
Check the methods.
|
|
"""
|
|
Operation = Pool().get('hotel.operation')
|
|
Operation().check_dates(self.start_date, self.end_date, self.room,
|
|
self.operation)
|
|
|
|
|
|
class FolioCharge(Workflow, ModelSQL, ModelView):
|
|
'Folio Charge'
|
|
__name__ = 'hotel.folio.charge'
|
|
folio = fields.Many2One('hotel.folio', 'Folio', required=True)
|
|
date_service = fields.Date('Date Service', select=True, required=True)
|
|
product = fields.Many2One('product.product', 'Product',
|
|
domain=[('salable', '=', True)], required=True)
|
|
quantity = fields.Integer('Quantity', required=True)
|
|
invoice_to = fields.Many2One('party.party', 'Invoice To',
|
|
required=False,
|
|
context={
|
|
'folio': Eval('_parent_folio', {}).get('main_guest'),
|
|
})
|
|
unit_price = fields.Numeric('Unit Price', digits=(16, 4),
|
|
required=True)
|
|
unit_price_w_tax = fields.Function(fields.Numeric('Unit Price'),
|
|
'get_unit_price_w_tax')
|
|
order = fields.Char('Order', select=True)
|
|
description = fields.Char('Description', select=True, depends=['product'])
|
|
state = fields.Selection(INVOICE_STATES, 'State', readonly=True)
|
|
state_string = state.translated('state')
|
|
invoice_line = fields.Many2One('account.invoice.line', 'Invoice Line',
|
|
readonly=True)
|
|
amount = fields.Function(fields.Numeric('Amount',
|
|
digits=(16, 2)), 'get_amount')
|
|
taxed_amount = fields.Function(fields.Numeric('Amount with Tax',
|
|
digits=(16, 2)), 'get_taxed_amount')
|
|
to_invoice = fields.Boolean('To Invoice', states={
|
|
'invisible': Bool(Eval('invoice_line')),
|
|
}, depends=['invoice_line'])
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(FolioCharge, cls).__setup__()
|
|
cls._buttons.update({
|
|
'transfer': {
|
|
'invisible': True,
|
|
},
|
|
'bill': {
|
|
'invisible': Eval('invoice_state') is not None,
|
|
},
|
|
})
|
|
|
|
@staticmethod
|
|
def default_quantity():
|
|
return 1
|
|
|
|
@staticmethod
|
|
def default_to_invoice():
|
|
return True
|
|
|
|
@staticmethod
|
|
def default_date_service():
|
|
today = Pool().get('ir.date').today()
|
|
return today
|
|
|
|
def get_amount(self, name=None):
|
|
if self.quantity and self.unit_price:
|
|
return self.quantity * self.unit_price_w_tax
|
|
return 0
|
|
|
|
def get_unit_price_w_tax(self, name=None):
|
|
Tax = Pool().get('account.tax')
|
|
res = self.unit_price or 0
|
|
if self.unit_price:
|
|
values = Tax.compute(self.product.template.customer_taxes_used,
|
|
self.unit_price, 1)
|
|
if values:
|
|
value = values[0]
|
|
res = value['base'] + value['amount']
|
|
return res
|
|
|
|
def get_taxed_amount(self, name=None):
|
|
if self.quantity and self.unit_price:
|
|
return self.quantity * self.unit_price
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def bill(cls, records):
|
|
cls.create_sales(records)
|
|
|
|
@classmethod
|
|
@ModelView.button_action('hotel.wizard_operation_line_transfer')
|
|
def transfer(cls, records):
|
|
pass
|
|
|
|
@fields.depends('unit_price', 'folio', 'product', 'description',
|
|
'invoice_to', '_parent_folio.main_guest')
|
|
def on_change_product(self):
|
|
if self.product:
|
|
self.unit_price = self.product.template.list_price
|
|
self.description = self.product.template.name
|
|
|
|
if self.folio:
|
|
self.invoice_to = self.folio.main_guest.id
|
|
|
|
|
|
class OpenMigrationStart(ModelView):
|
|
'Open Migration Start'
|
|
__name__ = 'hotel.open_migration.start'
|
|
start_date = fields.Date('Start Date', required=True)
|
|
end_date = fields.Date('End Date', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class OpenMigration(Wizard):
|
|
'Open Migration'
|
|
__name__ = 'hotel.open_migration'
|
|
start = StateView('hotel.open_migration.start',
|
|
'hotel.open_migration_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Open', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateAction('hotel.report_migration')
|
|
|
|
def do_print_(self, action):
|
|
data = {
|
|
'start_date': self.start.start_date,
|
|
'end_date': self.start.end_date,
|
|
'company': self.start.company.id,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class Migration(Report):
|
|
'Hotel Migration'
|
|
__name__ = 'hotel.migration'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
Folio = Pool().get('hotel.folio')
|
|
start = data['start_date']
|
|
end = data['end_date']
|
|
report_context['records'] = Folio.search([
|
|
('arrival_date', '>=', start),
|
|
('arrival_date', '<=', end),
|
|
('main_guest', '!=', None),
|
|
('registration_state', 'in', ['check_in', 'check_out']),
|
|
])
|
|
return report_context
|
|
|
|
|
|
class OperationReport(Report):
|
|
__name__ = 'hotel.operation'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
user = Pool().get('res.user')(Transaction().user)
|
|
report_context['company'] = user.company
|
|
return report_context
|
|
|
|
|
|
class CheckOutOperationFailed(ModelView):
|
|
'Check Out Operation Failed'
|
|
__name__ = 'hotel.operation.check_out.failed'
|
|
|
|
|
|
class CheckOutOperation(Wizard):
|
|
'Check Out Operation'
|
|
__name__ = 'hotel.operation.check_out'
|
|
start = StateTransition()
|
|
failed = StateView('hotel.operation.check_out.failed',
|
|
'hotel.operation_check_out_failed_view_form', [
|
|
Button('Force Check Out', 'force', 'tryton-forward'),
|
|
Button('Cancel', 'end', 'tryton-cancel', True),
|
|
])
|
|
force = StateTransition()
|
|
|
|
def transition_start(self):
|
|
Operation = Pool().get('hotel.operation')
|
|
active_id = Transaction().context['active_id']
|
|
operations = Operation.browse([active_id])
|
|
if Operation.validate_check_out_date(operations):
|
|
return 'end'
|
|
else:
|
|
return 'failed'
|
|
|
|
def transition_force(self):
|
|
Operation = Pool().get('hotel.operation')
|
|
active_id = Transaction().context['active_id']
|
|
operations = Operation.browse([active_id])
|
|
Operation.check_out(operations)
|
|
return 'end'
|
|
|
|
|
|
class ChangeRoomStart(ModelView):
|
|
'Change Room'
|
|
__name__ = 'hotel.operation.change_room.ask'
|
|
from_date = fields.Date('From Date', required=True)
|
|
room = fields.Many2One('hotel.room', 'Room', required=True, domain=[
|
|
# ('id', 'in', Eval('targets')),
|
|
])
|
|
accommodation = fields.Many2One('product.product',
|
|
'Accommodation', domain=[
|
|
('template.kind', '=', 'accommodation'),
|
|
], required=True)
|
|
targets = fields.Function(fields.Many2Many('hotel.room', None, None,
|
|
'Targets'), 'on_change_with_targets')
|
|
tranfer_charges = fields.Boolean('Transfer Charges')
|
|
|
|
@staticmethod
|
|
def default_from_date():
|
|
today = Pool().get('ir.date').today()
|
|
return today
|
|
|
|
@fields.depends('from_date', 'accommodation')
|
|
def on_change_with_targets(self, name=None):
|
|
pool = Pool()
|
|
Operation = pool.get('hotel.operation')
|
|
RoomTemplate = pool.get('hotel.room-product.template')
|
|
operation = Operation(Transaction().context.get('active_id'))
|
|
res = []
|
|
if not self.accommodation or not self.from_date:
|
|
return res
|
|
|
|
room_templates = RoomTemplate.search([
|
|
('template.accommodation_capacity', '>=', self.accommodation.accommodation_capacity)
|
|
])
|
|
rooms_ids = [t.room.id for t in room_templates]
|
|
|
|
rooms_available_ids = Operation.get_available_rooms(
|
|
self.from_date,
|
|
operation.end_date,
|
|
rooms_ids=rooms_ids
|
|
)
|
|
|
|
return rooms_available_ids
|
|
|
|
|
|
class ChangeRoom(Wizard):
|
|
'Change Room'
|
|
__name__ = 'hotel.folio.change_room'
|
|
"""
|
|
this is the wizard that allows the front desk employee to transfer
|
|
original room, and create a new operation occupany.
|
|
"""
|
|
start = StateView('hotel.folio.change_room.ask',
|
|
'hotel.folio_change_room_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Change', 'change', 'tryton-ok'),
|
|
]
|
|
)
|
|
change = StateTransition()
|
|
|
|
def transition_change(self):
|
|
pool = Pool()
|
|
Folio = pool.get('hotel.folio')
|
|
FolioCharge = pool.get('hotel.folio.charge')
|
|
operation = Folio(Transaction().context.get('active_id'))
|
|
|
|
new_operation = {
|
|
'reference': operation.reference,
|
|
'room': self.start.room.id,
|
|
'start_date': self.start.from_date,
|
|
'end_date': operation.end_date,
|
|
'party': operation.party.id,
|
|
'currency': operation.currency.id,
|
|
'company': operation.company.id,
|
|
'kind': operation.kind,
|
|
'main_guest': operation.main_guest.id,
|
|
'accommodation': self.start.accommodation.id,
|
|
'origin': str(operation.origin),
|
|
'state': 'open',
|
|
'unit_price': operation.unit_price,
|
|
'lines': []
|
|
}
|
|
lines_to_transfer = []
|
|
# _operation, = Operation.create([new_operation])
|
|
# if self.start.tranfer_charges:
|
|
# for line in operation.lines:
|
|
# if line.state is None:
|
|
# lines_to_transfer.append(line)
|
|
#
|
|
# if lines_to_transfer:
|
|
# FolioCharge.write([lines_to_transfer], {'operation': _operation.id})
|
|
#
|
|
# operation.end_date = self.start.from_date
|
|
# operation.state = 'closed'
|
|
# operation.target = _operation.id
|
|
# operation.save()
|
|
return 'end'
|
|
|
|
|
|
class TransferOperationStart(ModelView):
|
|
'Transfer Operation'
|
|
__name__ = 'hotel.folio.transfer_operation.ask'
|
|
folio = fields.Many2One('hotel.folio', 'Folio',
|
|
required=True, domain=[
|
|
('state', 'in', ['draft', 'open']),
|
|
])
|
|
tranfer_charges = fields.Boolean('Transfer Charges')
|
|
|
|
@staticmethod
|
|
def default_tranfer_charges():
|
|
return True
|
|
|
|
|
|
class TransferOperation(Wizard):
|
|
'Transfer Operation'
|
|
__name__ = 'hotel.folio.transfer_operation'
|
|
"""
|
|
this is the wizard that allows the front desk employee to transfer
|
|
original room, and create a new operation occupany.
|
|
"""
|
|
start = StateView('hotel.folio.transfer_operation.ask',
|
|
'hotel.folio_transfer_operation_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Transfer', 'transfer', 'tryton-ok'),
|
|
]
|
|
)
|
|
transfer = StateTransition()
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(TransferOperation, cls).__setup__()
|
|
|
|
def transition_transfer(self):
|
|
pool = Pool()
|
|
Operation = pool.get('hotel.folio')
|
|
FolioCharge = pool.get('hotel.folio.charge')
|
|
current_op = Operation(Transaction().context.get('active_id'))
|
|
target_op = self.start.folio
|
|
if target_op.id == current_op.id:
|
|
raise AccessError(gettext('hotel.msg_folio_current'))
|
|
|
|
lines_to_transfer = []
|
|
if self.start.tranfer_charges:
|
|
for line in current_op.lines:
|
|
if line.state is None:
|
|
lines_to_transfer.append(line)
|
|
|
|
if lines_to_transfer:
|
|
FolioCharge.write(lines_to_transfer, {
|
|
'operation': target_op.id
|
|
})
|
|
|
|
current_op.state = 'transfered'
|
|
current_op.operation_target = target_op.id
|
|
current_op.save()
|
|
target_op.save()
|
|
return 'end'
|
|
|
|
|
|
class TransferChargeStart(ModelView):
|
|
'Transfer Charge'
|
|
__name__ = 'hotel.folio.transfer_charge.ask'
|
|
folio = fields.Many2One('hotel.folio', 'Operation',
|
|
required=True, domain=[('state', '=', 'check_in')])
|
|
|
|
|
|
class TransferCharge(Wizard):
|
|
'Transfer Operation'
|
|
__name__ = 'hotel.folio.transfer_charge'
|
|
start = StateView('hotel.folio.transfer_charge.ask',
|
|
'hotel.folio_transfer_charge_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Transfer', 'transfer', 'tryton-ok'),
|
|
]
|
|
)
|
|
transfer = StateTransition()
|
|
|
|
def transition_transfer(self):
|
|
pool = Pool()
|
|
FolioCharge = pool.get('hotel.folio.charge')
|
|
current_line = FolioCharge(Transaction().context.get('active_id'))
|
|
target_op = self.start.operation
|
|
|
|
current_operation = current_line.operation
|
|
current_line.operation = target_op.id
|
|
current_line.save()
|
|
current_operation.save()
|
|
return 'end'
|
|
|
|
|
|
class OperationByConsumerReport(Report):
|
|
__name__ = 'hotel.folio.charge'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
Folio = pool.get('hotel.folio')
|
|
user = pool.get('res.user')(Transaction().user)
|
|
consumer_lines = []
|
|
total_amount = 0
|
|
if records:
|
|
line = records[0]
|
|
folio = Folio(line.folio.id)
|
|
total_amount = folio.room_amount
|
|
for l in folio.lines:
|
|
if l.invoice_to.id == line.invoice_to.id:
|
|
consumer_lines.append(l)
|
|
total_amount += l.amount
|
|
setattr(folio, 'lines', consumer_lines)
|
|
setattr(folio, 'total_amount', total_amount)
|
|
|
|
report_context['records'] = [folio]
|
|
report_context['company'] = user.company
|
|
return report_context
|
|
|
|
|
|
class OperationBill(Wizard):
|
|
'Operation Bill'
|
|
__name__ = 'hotel.folio.bill'
|
|
start_state = 'create_bill'
|
|
create_bill = StateTransition()
|
|
|
|
def transition_create_bill(self):
|
|
Folio = Pool().get('hotel.folio')
|
|
ids = Transaction().context['active_ids']
|
|
folios = Folio.browse(ids)
|
|
Folio.bill(folios)
|
|
return 'end'
|
|
|
|
|
|
class OperationVoucher(ModelSQL):
|
|
'Operation - Voucher'
|
|
__name__ = 'hotel.folio-account.voucher'
|
|
_table = 'folio_vouchers_rel'
|
|
folio = fields.Many2One('hotel.folio', 'Folio',
|
|
ondelete='CASCADE', required=True)
|
|
voucher = fields.Many2One('account.voucher', 'Voucher', required=True,
|
|
domain=[('voucher_type', '=', 'receipt')], ondelete='RESTRICT')
|
|
|
|
@classmethod
|
|
def set_voucher_origin(cls, voucher_id, folio_id):
|
|
cls.create([{
|
|
'voucher': voucher_id,
|
|
'folio': folio_id,
|
|
}])
|
|
|
|
|
|
class ReverseCheckout(Wizard):
|
|
'Reverse Checkout'
|
|
__name__ = 'hotel.folio.reverse_checkout'
|
|
start_state = 'reverse_checkout'
|
|
reverse_checkout = StateTransition()
|
|
|
|
def transition_reverse_checkout(self):
|
|
Folio = Pool().get('hotel.folio')
|
|
folio_id = Transaction().context['active_id']
|
|
if folio_id:
|
|
Folio.check_in([Folio(folio_id)])
|
|
return 'end'
|
|
|
|
|
|
class FolioStockMove(ModelSQL):
|
|
'Folio - Stock Move'
|
|
__name__ = 'hotel.folio-stock.move'
|
|
_table = 'hotel_folio_stock_move_rel'
|
|
folio = fields.Many2One('hotel.folio', 'Folio',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
move = fields.Many2One('stock.move', 'Move', select=True,
|
|
ondelete='RESTRICT', required=True)
|
|
|
|
|
|
class StatisticsByMonthStart(ModelView):
|
|
'Statistics By Month Start'
|
|
__name__ = 'hotel.statistics_by_month.start'
|
|
period = fields.Many2One('account.period', 'Period', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class StatisticsByMonth(Wizard):
|
|
'Statistics By Month'
|
|
__name__ = 'hotel.statistics_by_month'
|
|
start = StateView('hotel.statistics_by_month.start',
|
|
'hotel.print_hotel_statistics_by_month_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Open', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateAction('hotel.statistics_by_month_report')
|
|
|
|
def do_print_(self, action):
|
|
data = {
|
|
'period': self.start.period.id,
|
|
'company': self.start.company.id,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class StatisticsByMonthReport(Report):
|
|
'Hotel Statistics By Month'
|
|
__name__ = 'hotel.statistics_by_month.report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
Operation = Pool().get('hotel.folio')
|
|
Period = Pool().get('account.period')
|
|
Company = Pool().get('company.company')
|
|
Rooms = Pool().get('hotel.room')
|
|
period = Period(data['period'])
|
|
company = Company(data['company'])
|
|
month_days = (period.end_date - period.start_date).days + 1
|
|
rooms_available = []
|
|
rooms_sold = []
|
|
beds_available = []
|
|
beds_sold = []
|
|
local_guests = []
|
|
foreign_guests = []
|
|
room1_sold = []
|
|
room2_sold = []
|
|
room_suite_sold = []
|
|
room_other_sold = []
|
|
guests_one_ng = []
|
|
guests_two_ng = []
|
|
guests_three_ng = []
|
|
guests_four_ng = []
|
|
guests_five_ng = []
|
|
guests_six_ng = []
|
|
guests_seven_ng = []
|
|
guests_eight_ng = []
|
|
guests_permanent_ng = []
|
|
reason_bussiness = []
|
|
reason_bussiness_foreign = []
|
|
reason_leisure = []
|
|
reason_leisure_foreign = []
|
|
reason_convention = []
|
|
reason_convention_foreign = []
|
|
reason_health = []
|
|
reason_health_foreign = []
|
|
reason_transport = []
|
|
reason_transport_foreign = []
|
|
reason_other = []
|
|
reason_other_foreign = []
|
|
total_guests = []
|
|
folios = Operation.search(['OR',
|
|
[
|
|
('start_date', '>=', period.start_date),
|
|
('start_date', '<=', period.end_date),
|
|
('kind', '=', 'occupancy'),
|
|
], [
|
|
('end_date', '>=', period.start_date),
|
|
('end_date', '<=', period.end_date),
|
|
('kind', '=', 'occupancy'),
|
|
]
|
|
])
|
|
rooms = Rooms.search([])
|
|
rooms_available = [len(rooms) * month_days]
|
|
for r in rooms:
|
|
beds_available.append(
|
|
r.main_accommodation.accommodation_capacity * month_days
|
|
)
|
|
|
|
def _get_ctx_dates(op):
|
|
start_date = op.start_date
|
|
end_date = op.end_date
|
|
if op.start_date < period.start_date:
|
|
start_date = period.start_date
|
|
if op.end_date > period.end_date:
|
|
end_date = period.end_date
|
|
|
|
sub_dates = [
|
|
str(start_date + timedelta(n))
|
|
for n in range((end_date - start_date).days)
|
|
]
|
|
return sub_dates
|
|
|
|
for op in folios:
|
|
room_capacity = op.room.main_accommodation.accommodation_capacity
|
|
ctx_dates = _get_ctx_dates(op)
|
|
len_ctx_dates = len(ctx_dates)
|
|
rooms_sold.append(len_ctx_dates)
|
|
beds_sold.append(len_ctx_dates * room_capacity)
|
|
qty_guests = len(op.guests)
|
|
if not qty_guests:
|
|
qty_guests = 1
|
|
total_guests.append(qty_guests)
|
|
is_local = True
|
|
if op.main_guest.type_document == '41':
|
|
is_local = False
|
|
foreign_guests.append(qty_guests)
|
|
else:
|
|
local_guests.append(qty_guests)
|
|
|
|
if room_capacity == 1:
|
|
room1_sold.append(qty_guests)
|
|
elif room_capacity == 2:
|
|
room2_sold.append(qty_guests)
|
|
elif room_capacity > 2:
|
|
room_suite_sold.append(qty_guests)
|
|
|
|
if len_ctx_dates == 1:
|
|
guests_one_ng.append(qty_guests)
|
|
elif len_ctx_dates == 2:
|
|
guests_two_ng.append(qty_guests)
|
|
elif len_ctx_dates == 3:
|
|
guests_three_ng.append(qty_guests)
|
|
elif len_ctx_dates == 4:
|
|
guests_four_ng.append(qty_guests)
|
|
elif len_ctx_dates == 5:
|
|
guests_five_ng.append(qty_guests)
|
|
elif len_ctx_dates == 6:
|
|
guests_six_ng.append(qty_guests)
|
|
elif len_ctx_dates == 7:
|
|
guests_seven_ng.append(qty_guests)
|
|
elif len_ctx_dates >= 8:
|
|
guests_eight_ng.append(qty_guests)
|
|
|
|
segment = 'bussiness'
|
|
if op.origin:
|
|
if hasattr(op.origin, 'booking'):
|
|
segment = op.origin.booking.segment
|
|
|
|
if segment == 'bussiness':
|
|
if is_local:
|
|
reason_bussiness.append(qty_guests)
|
|
else:
|
|
reason_bussiness_foreign.append(qty_guests)
|
|
elif segment == 'convention':
|
|
if is_local:
|
|
reason_convention.append(qty_guests)
|
|
else:
|
|
reason_convention_foreign.append(qty_guests)
|
|
elif segment == 'health':
|
|
if is_local:
|
|
reason_health.append(qty_guests)
|
|
else:
|
|
reason_health_foreign.append(qty_guests)
|
|
else:
|
|
if is_local:
|
|
reason_leisure.append(qty_guests)
|
|
else:
|
|
reason_leisure_foreign.append(qty_guests)
|
|
|
|
def _get_rate(val):
|
|
res = 0
|
|
if sum_total_guests > 0:
|
|
res = round(float(sum(val)) / sum_total_guests, 4)
|
|
return res
|
|
|
|
sum_total_guests = sum(total_guests)
|
|
rate_guests_one_ng = _get_rate(guests_one_ng)
|
|
rate_guests_two_ng = _get_rate(guests_two_ng)
|
|
rate_guests_three_ng = _get_rate(guests_three_ng)
|
|
rate_guests_four_ng = _get_rate(guests_four_ng)
|
|
rate_guests_five_ng = _get_rate(guests_five_ng)
|
|
rate_guests_six_ng = _get_rate(guests_six_ng)
|
|
rate_guests_seven_ng = _get_rate(guests_seven_ng)
|
|
rate_guests_eight_ng = _get_rate(guests_eight_ng)
|
|
|
|
report_context['period'] = period.name
|
|
report_context['company'] = company.party.name
|
|
report_context['rooms_available'] = sum(rooms_available)
|
|
report_context['rooms_sold'] = sum(rooms_sold)
|
|
report_context['beds_available'] = sum(beds_available)
|
|
report_context['beds_sold'] = sum(beds_sold)
|
|
report_context['local_guests'] = sum(local_guests)
|
|
report_context['foreign_guests'] = sum(foreign_guests)
|
|
report_context['room1_sold'] = sum(room1_sold)
|
|
report_context['room2_sold'] = sum(room2_sold)
|
|
report_context['room_suite_sold'] = sum(room_suite_sold)
|
|
report_context['room_other_sold'] = sum(room_other_sold)
|
|
report_context['guests_one_ng'] = rate_guests_one_ng
|
|
report_context['guests_two_ng'] = rate_guests_two_ng
|
|
report_context['guests_three_ng'] = rate_guests_three_ng
|
|
report_context['guests_four_ng'] = rate_guests_four_ng
|
|
report_context['guests_five_ng'] = rate_guests_five_ng
|
|
report_context['guests_six_ng'] = rate_guests_six_ng
|
|
report_context['guests_seven_ng'] = rate_guests_seven_ng
|
|
report_context['guests_eight_ng'] = rate_guests_eight_ng
|
|
report_context['guests_permanent_ng'] = sum(guests_permanent_ng)
|
|
report_context['reason_bussiness'] = sum(reason_bussiness)
|
|
report_context['reason_bussiness_foreign'] = sum(reason_bussiness_foreign)
|
|
report_context['reason_leisure'] = sum(reason_leisure)
|
|
report_context['reason_leisure_foreign'] = sum(reason_leisure_foreign)
|
|
report_context['reason_convention'] = sum(reason_convention)
|
|
report_context['reason_convention_foreign'] = sum(reason_convention_foreign)
|
|
report_context['reason_health'] = sum(reason_health)
|
|
report_context['reason_health_foreign'] = sum(reason_health_foreign)
|
|
report_context['reason_transport'] = sum(reason_transport)
|
|
report_context['reason_transport_foreign'] = sum(reason_transport_foreign)
|
|
report_context['reason_other_foreign'] = sum(reason_other_foreign)
|
|
report_context['reason_other'] = sum(reason_other)
|
|
|
|
return report_context
|
|
|
|
|
|
class GuestsListStart(ModelView):
|
|
'Guests List Start'
|
|
__name__ = 'hotel.print_guests_list.start'
|
|
date = fields.Date('Date', required=True)
|
|
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 GuestsList(Wizard):
|
|
'Guests List'
|
|
__name__ = 'hotel.print_guests_list'
|
|
start = StateView('hotel.print_guests_list.start',
|
|
'hotel.print_guests_list_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Open', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateReport('hotel.guests_list.report')
|
|
|
|
def do_print_(self, action):
|
|
company = self.start.company
|
|
data = {
|
|
'date': self.start.date,
|
|
'company': company.id,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class GuestsListReport(Report):
|
|
__name__ = 'hotel.guests_list.report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
Folio = pool.get('hotel.folio')
|
|
Room = pool.get('hotel.room')
|
|
User = pool.get('res.user')
|
|
user_id = Transaction().user
|
|
|
|
user = User(user_id)
|
|
# start_date = data['date']
|
|
if data['date'] == date.today():
|
|
dom = ['OR', [
|
|
('arrival_date', '=', data['date']),
|
|
('registration_state', '=', 'check_in'),
|
|
], [
|
|
('departure_date', '=', data['date']),
|
|
('registration_state', '=', 'check_in'),
|
|
], [
|
|
('arrival_date', '<=', data['date']),
|
|
('departure_date', '>=', data['date']),
|
|
('registration_state', '=', 'check_in'),
|
|
]
|
|
]
|
|
else:
|
|
dom = [
|
|
('arrival_date', '<=', data['date']),
|
|
('departure_date', '>=', data['date']),
|
|
]
|
|
folios = Folio.search(dom, order=[('room.code', 'ASC')])
|
|
|
|
total_guests = []
|
|
rooms_occupied = []
|
|
for op in folios:
|
|
total_guests.append(len(op.guests))
|
|
rooms_occupied.append(op.room.id)
|
|
|
|
rooms = Room.search_read([])
|
|
|
|
_rooms_occupied = len(set(rooms_occupied))
|
|
num_rooms = len(rooms)
|
|
if num_rooms:
|
|
occupancy_rate = round(_rooms_occupied / num_rooms, 2)
|
|
else:
|
|
occupancy_rate = 0
|
|
|
|
guests = []
|
|
for op in folios:
|
|
for guest in op.guests:
|
|
guests.append({
|
|
'room': op.room.code,
|
|
'guest': guest.name,
|
|
'party': op.booking.party.name if op.booking.party else '',
|
|
'arrival_date': op.arrival_date,
|
|
'departure_date': op.departure_date,
|
|
'nights_quantity': op.nights_quantity,
|
|
})
|
|
report_context['records'] = guests
|
|
report_context['total_rooms'] = _rooms_occupied
|
|
report_context['total_guests'] = sum(total_guests)
|
|
report_context['occupancy_rate'] = occupancy_rate
|
|
report_context['company'] = Company(data['company'])
|
|
report_context['date'] = data['date']
|
|
report_context['print_date'] = datetime.now()
|
|
report_context['user'] = user.name
|
|
return report_context
|
|
|
|
|
|
class RegistrationCardReport(Report):
|
|
__name__ = 'hotel.folio.registration_card'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
user = Pool().get('res.user')(Transaction().user)
|
|
report_context['company'] = user.company
|
|
_records = []
|
|
for rec in records:
|
|
if not rec.registration_card:
|
|
continue
|
|
_records.append(rec)
|
|
report_context['records'] = _records
|
|
return report_context
|