trytonpsk-hotel/folio.py

2135 lines
74 KiB
Python
Raw Normal View History

2020-04-16 14:45:13 +02:00
# 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
2021-11-17 01:03:10 +01:00
from datetime import timedelta, datetime, date
2022-11-12 15:12:06 +01:00
from collections import OrderedDict
import calendar
2022-07-21 16:03:54 +02:00
2020-04-16 14:45:13 +02:00
from trytond.model import ModelView, Workflow, ModelSQL, fields
2020-07-18 17:18:29 +02:00
from trytond.pyson import Eval, Bool
2020-04-16 14:45:13 +02:00
from trytond.pool import Pool
from trytond.report import Report
2021-10-16 05:54:52 +02:00
from trytond.wizard import (
Wizard, StateView, StateAction, Button, StateTransition, StateReport
)
2020-04-16 14:45:13 +02:00
from trytond.transaction import Transaction
2021-07-21 14:56:53 +02:00
from trytond.model.exceptions import AccessError
2021-10-09 19:00:26 +02:00
from trytond.exceptions import UserError
2021-07-21 14:56:53 +02:00
from trytond.i18n import gettext
2022-10-13 22:27:24 +02:00
from .constants import (
REGISTRATION_STATE, COMPLEMENTARY, INVOICE_STATES
)
2022-08-29 19:28:13 +02:00
from .room import ROOM_STATUS
2021-10-09 19:00:26 +02:00
STATES_CHECKIN = {
'readonly': Eval('registration_state').in_(
['check_in', 'no_show', 'cancelled']
),
'required': Eval('registration_state') == 'check_in',
}
2020-04-16 14:45:13 +02:00
2021-11-17 15:11:48 +01:00
STATES_CHECKOUT = {
'readonly': Eval('registration_state').in_(
['check_out', 'no_show', 'cancelled']
),
'required': Eval('registration_state') == 'check_in',
}
2020-04-16 14:45:13 +02:00
STATES_OP = {
'readonly': Eval('state').in_(['check_out', 'done', 'cancelled'])
}
2022-03-25 16:03:19 +01:00
STATES_MNT = {'readonly': Eval('state') != 'draft'}
2021-10-09 19:00:26 +02:00
_ZERO = Decimal('0')
2020-04-16 14:45:13 +02:00
2020-07-18 17:18:29 +02:00
2021-10-09 19:00:26 +02:00
class Folio(ModelSQL, ModelView):
'Folio'
__name__ = 'hotel.folio'
2022-03-23 00:37:09 +01:00
STATES = {
'required': Eval('registration_state') == 'check_in',
'readonly': Eval('registration_state') == 'check_in',
}
2021-10-09 19:00:26 +02:00
booking = fields.Many2One('hotel.booking', 'Booking', ondelete='CASCADE',
select=True)
2022-03-23 00:37:09 +01:00
reference = fields.Char('Reference', states=STATES)
2021-10-09 19:00:26 +02:00
registration_card = fields.Char('Registration Card', readonly=True,
select=True, help="Unique sequence for card guest registration.")
2022-03-23 00:37:09 +01:00
room = fields.Many2One('hotel.room', 'Room', select=True,
2022-10-15 15:11:33 +02:00
states=STATES_CHECKOUT)
2021-10-09 19:00:26 +02:00
arrival_date = fields.Date('Arrival Date', required=True,
2021-11-17 15:11:48 +01:00
states=STATES_CHECKOUT)
2021-10-09 19:00:26 +02:00
departure_date = fields.Date('Departure Date', required=True,
2021-11-17 15:11:48 +01:00
states=STATES_CHECKOUT)
2021-10-09 19:00:26 +02:00
product = fields.Many2One('product.product', 'Product',
select=True, domain=[
('template.type', '=', 'service'),
('template.kind', '=', 'accommodation'),
2021-11-17 15:11:48 +01:00
], required=True, states=STATES_CHECKOUT)
2021-10-16 05:54:52 +02:00
unit_price = fields.Numeric('Unit Price', digits=(16, 4),
2021-10-09 19:00:26 +02:00
states={
'required': Bool(Eval('product')),
2021-11-19 16:56:52 +01:00
'readonly': Eval('registration_state') == 'check_out',
2021-10-09 19:00:26 +02:00
})
2021-10-11 01:52:01 +02:00
unit_price_w_tax = fields.Function(fields.Numeric('Unit Price'),
'get_unit_price_w_tax')
2021-10-09 19:00:26 +02:00
uom = fields.Many2One('product.uom', 'UOM', readonly=True)
main_guest = fields.Many2One('party.party', 'Main Guest', select=True,
2022-10-13 22:27:24 +02:00
states={'readonly': True})
2021-10-09 19:00:26 +02:00
contact = fields.Char('Contact', states=STATES_CHECKIN)
2022-10-01 07:13:29 +02:00
party_holder = fields.Function(fields.Many2One('party.party',
'Party Holder'), 'get_party_holder')
2021-10-09 19:00:26 +02:00
num_children = fields.Function(fields.Integer('No. Children'),
'get_num_children')
2022-03-20 14:46:25 +01:00
nights_quantity = fields.Integer('Nights', states={'readonly': True},
depends=['arrival_date', 'departure_date'])
2021-10-09 19:00:26 +02:00
host_quantity = fields.Integer('Host', states=STATES_CHECKIN)
2022-10-15 15:11:33 +02:00
estimated_arrival_time = fields.Time('Estimated Arrival Time', states={
'readonly': Eval('registration_state').in_(
['check_out', 'no_show', 'cancelled']
)})
2021-10-09 19:00:26 +02:00
unit_digits = fields.Function(fields.Integer('Unit Digits'), 'get_unit_digits')
notes = fields.Text('Notes')
total_amount = fields.Function(fields.Numeric('Total Amount',
2022-10-13 22:27:24 +02:00
digits=(16, 2)), 'get_total_amount')
2022-03-19 06:21:49 +01:00
commission_amount = fields.Numeric('Commission Amount', digits=(16, 2),
2022-03-20 14:46:25 +01:00
depends=['nights_quantity', 'unit_price'], states={'readonly': True}
)
2021-10-11 19:03:43 +02:00
guests = fields.One2Many('hotel.folio.guest', 'folio', 'Guests',
2021-11-25 05:36:14 +01:00
states={'readonly': Eval('registration_state').in_(['check_out'])})
2022-07-11 02:56:01 +02:00
storage = fields.Many2One('stock.location', 'Storage',
domain=[('type', '=', 'storage')], states={
'readonly': Eval('registration_state') != 'check_in',
},
depends=['registration_state'])
2021-10-09 19:00:26 +02:00
registration_state = fields.Selection(REGISTRATION_STATE,
2021-11-17 01:03:10 +01:00
'Registration State', readonly=True, select=True)
2022-05-17 17:14:08 +02:00
registration_state_string = registration_state.translated('registration_state')
2021-10-09 19:00:26 +02:00
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')
2021-10-14 06:41:15 +02:00
to_invoice = fields.Boolean('To Invoice', states={
'invisible': Bool(Eval('invoice_line')),
2022-07-21 17:17:35 +02:00
}, depends=['invoice_line'],
help='Mark this checkbox if you want to invoice this item')
2021-10-11 01:52:01 +02:00
invoice = fields.Function(fields.Many2One('account.invoice', 'Invoice',
depends=['invoice_line']), 'get_invoice')
2021-10-12 17:25:14 +02:00
invoice_state = fields.Function(fields.Selection(
INVOICE_STATES, 'Invoice State'), 'get_invoice_state')
2021-10-09 19:00:26 +02:00
type_complementary = fields.Selection(COMPLEMENTARY, 'Type Complementary',
states={
'invisible': ~Bool(Eval('complementary')),
'required': Bool(Eval('complementary')),
})
2021-10-12 07:18:02 +02:00
breakfast_included = fields.Boolean('Breakfast Included', states={
'readonly': Eval('registration_state') != 'check_in',
})
2021-10-11 01:52:01 +02:00
room_amount = fields.Function(fields.Numeric('Room Amount',
2022-03-20 14:46:25 +01:00
digits=(16, 2), depends=['nights_quantity', 'unit_price']
), 'on_change_with_room_amount')
2022-03-23 00:37:09 +01:00
channel = fields.Function(fields.Many2One('hotel.channel', 'Channel'),
'get_channel')
2022-03-30 20:22:01 +02:00
num_children = fields.Function(fields.Integer('Num. Children'),
'get_num_guests')
num_adults = fields.Function(fields.Integer('Num. Adults'),
'get_num_guests')
2022-10-19 22:41:02 +02:00
stock_moves = fields.Function(fields.One2Many('stock.move', 'origin',
'Moves'), 'get_stock_moves')
2022-07-13 23:55:07 +02:00
vehicle_plate = fields.Char('Vehicle Plate')
2022-08-04 05:36:12 +02:00
taxes = fields.Many2Many('hotel.folio-account.tax', 'folio', 'tax', 'Taxes',
order=[('tax.sequence', 'ASC'), ('tax.id', 'ASC')],
2022-10-13 22:27:24 +02:00
domain=[
('parent', '=', None), ['OR',
2022-08-04 05:36:12 +02:00
('group', '=', None),
2022-10-13 22:27:24 +02:00
('group.kind', 'in', ['sale', 'both'])
2022-08-04 05:36:12 +02:00
],
2022-10-13 22:27:24 +02:00
],
2022-08-04 05:36:12 +02:00
)
2022-08-29 19:28:13 +02:00
room_status = fields.Function(fields.Selection(ROOM_STATUS,
'Room Status'), 'get_room_status')
2022-10-06 02:39:28 +02:00
payment_status = fields.Selection([
(None, ''),
('pending', 'Pending'),
('paid', 'Paid'),
], 'Acco. Payment Status', readonly=True)
2022-10-07 05:47:14 +02:00
payments = fields.One2Many('account.statement.line', 'source',
'Payments', readonly=True)
2022-10-03 20:33:47 +02:00
pending_accommodation = fields.Function(fields.Numeric('Pending to Pay',
digits=(16, 2)), 'get_pending_to_pay')
pending_charges = fields.Function(fields.Numeric('Pending to Pay',
digits=(16, 2)), 'get_pending_to_pay')
pending_total = fields.Function(fields.Numeric('Pending to Pay',
digits=(16, 2)), 'get_pending_to_pay')
2022-10-23 17:05:03 +02:00
total_advances = fields.Function(fields.Numeric('Total Advances',
digits=(16, 2)), 'get_total_advances')
2022-10-21 14:54:37 +02:00
pax = fields.Integer('PAX', states={
'readonly': Eval('registration_state').in_(
['check_out', 'no_show', 'cancelled']
)}, help="Number of persons in house setted manually.")
2022-10-19 19:16:51 +02:00
invoices = fields.Function(fields.Many2Many('account.invoice',
None, None, 'Invoices'), 'get_invoices')
2022-10-28 18:44:14 +02:00
charges_blocked = fields.Boolean('Charges Blocked')
2021-10-09 19:00:26 +02:00
@classmethod
def __setup__(cls):
super(Folio, cls).__setup__()
2022-07-25 17:29:23 +02:00
# cls._check_modify_exclude = [
# 'nationality', 'origin_country', 'target_country',
# 'registration_state', 'guests'
# ]
2021-10-09 19:00:26 +02:00
cls._buttons.update({
'check_in': {
2021-12-21 03:45:32 +01:00
'invisible': Eval('registration_state').in_(
['check_in', 'check_out']
),
2021-10-09 19:00:26 +02:00
},
'check_out': {
2021-10-09 19:49:43 +02:00
'invisible': Eval('registration_state') != 'check_in',
2021-10-09 19:00:26 +02:00
},
'bill': {
'invisible': Eval('registration_state') != 'check_out',
2021-10-12 17:25:14 +02:00
},
2022-10-03 20:33:47 +02:00
'do_payment': {
2022-10-13 22:27:24 +02:00
'invisible': Eval('registration_state').in_([
'no_show', 'cancelled', 'pending'
]),
2021-10-09 19:00:26 +02:00
}
})
2022-10-10 16:09:55 +02:00
@staticmethod
def default_to_invoice():
return True
@staticmethod
def default_payment_status():
return 'pending'
@staticmethod
def default_registration_state():
return 'pending'
@staticmethod
def default_pax():
return 1
2022-10-03 20:33:47 +02:00
@classmethod
@ModelView.button_action('hotel.wizard_statement_payment_form')
def do_payment(cls, records):
pass
2022-10-23 17:05:03 +02:00
def get_total_advances(self, name=None):
res = []
for pay in self.payments:
res.append(pay.amount)
return sum(res)
2022-10-13 22:27:24 +02:00
def get_pending_to_pay(self, name=None):
res = 0
2022-10-03 20:33:47 +02:00
if name == 'pending_accommodation':
2022-10-23 17:05:03 +02:00
if self.payment_status != 'paid':
2022-10-06 02:39:28 +02:00
res = self.get_total_accommodation()
2022-10-03 20:33:47 +02:00
if name == 'pending_charges':
total_charges = self.get_total_charges()
charge_paid = 0
for charge in self.charges:
2022-10-13 22:27:24 +02:00
if charge.status == 'paid':
2022-10-03 20:33:47 +02:00
charge_paid += charge.amount
res = total_charges - charge_paid
if name == 'pending_total':
2022-10-25 22:14:07 +02:00
res = self.total_amount - self.total_advances
2022-10-03 20:33:47 +02:00
return res
2022-03-30 20:22:01 +02:00
def get_num_guests(self, name):
res = []
2022-08-28 15:56:49 +02:00
2022-03-30 20:22:01 +02:00
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)
2022-10-01 07:13:29 +02:00
def get_party_holder(self, name=None):
if self.booking.party:
return self.booking.party.id
2022-03-30 20:22:01 +02:00
def get_rec_name(self, name=None):
2022-06-15 04:23:38 +02:00
if self.room:
name = f'{self.room.code}:'
2021-10-12 07:43:22 +02:00
if self.main_guest:
name = name + self.main_guest.name
return name
2021-10-09 19:49:43 +02:00
@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),
2022-10-01 07:13:29 +02:00
('booking.party.name', operator, value),
2022-03-23 00:37:09 +01:00
('reference', operator, value),
2021-10-09 19:49:43 +02:00
]
return domain
2022-10-19 19:16:51 +02:00
def get_invoices(self, name=None):
res = []
for charge in self.charges:
if charge.invoice_line:
res.append(charge.invoice_line.invoice.id)
return list(set(res))
2022-03-20 14:46:25 +01:00
@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()
2021-10-09 19:00:26 +02:00
@classmethod
@ModelView.button
def check_in(cls, records):
2022-10-13 22:27:24 +02:00
for record in records:
record.validate_check_in()
if record.booking.state == 'offer':
2021-10-09 19:00:26 +02:00
raise UserError(gettext('hotel.msg_missing_confirm_booking'))
2022-10-13 22:27:24 +02:00
if record.main_guest is None:
2021-10-09 19:00:26 +02:00
raise UserError(gettext('hotel.msg_missing_main_guest'))
2022-10-13 22:27:24 +02:00
if record.room is None:
2021-10-09 19:00:26 +02:00
raise UserError(gettext('hotel.msg_missing_select_room'))
2022-10-13 22:27:24 +02:00
record.set_registration_number()
record.check_room()
cls.update_room(record, 'check_in')
2021-10-09 19:00:26 +02:00
cls.write(records, {'registration_state': 'check_in'})
2022-05-19 23:19:19 +02:00
def check_room(self):
2022-07-27 23:34:13 +02:00
if self.room.state != 'clean':
raise UserError(
gettext('hotel.msg_room_no_clean', s=self.room.name)
)
2022-05-19 23:19:19 +02:00
2022-08-29 19:28:13 +02:00
def get_room_status(self, name):
if self.room:
return self.room.state
2022-08-04 05:36:12 +02:00
2022-10-13 22:27:24 +02:00
def validate_check_in(self):
if self.arrival_date > date.today():
raise UserError(gettext('hotel.msg_cannot_check_in_future'))
def validate_check_out(self):
bk = self.booking
def _check_accommodation(folio):
2022-10-15 15:11:33 +02:00
if folio.payment_status != 'paid':
2022-10-13 22:27:24 +02:00
raise UserError(gettext('hotel.msg_accommodation_not_paid'))
def _check_charges(folio):
for charge in folio.charges:
2022-10-15 15:11:33 +02:00
if charge.status != 'paid':
2022-10-13 22:27:24 +02:00
raise UserError(gettext('hotel.msg_charges_not_paid'))
if bk.responsible_payment == 'guest':
_check_charges(self)
_check_accommodation(self)
elif bk.responsible_payment == 'holder':
if self.main_guest == bk.party:
for folio in bk.lines:
_check_charges(folio)
_check_accommodation(folio)
else:
2022-10-17 19:53:47 +02:00
_check_charges(self)
2022-10-13 22:27:24 +02:00
2021-10-09 19:00:26 +02:00
@classmethod
@ModelView.button
def check_out(cls, records):
for record in records:
2022-10-13 22:27:24 +02:00
record.validate_check_out()
2021-10-09 19:00:26 +02:00
cls.write([record], {'registration_state': 'check_out'})
2022-07-27 23:34:13 +02:00
cls.update_room(record, 'check_out')
2021-10-09 19:00:26 +02:00
@classmethod
2022-10-17 19:53:47 +02:00
@ModelView.button_action('hotel.wizard_bill_booking')
2021-10-09 19:00:26 +02:00
def bill(cls, records):
2022-10-03 02:19:35 +02:00
pass
2021-10-09 19:00:26 +02:00
2021-10-12 17:25:14 +02:00
@classmethod
@ModelView.button_action('hotel.wizard_booking_advance_voucher')
def pay(cls, records):
pass
2022-03-25 16:03:19 +01:00
@classmethod
2022-07-27 23:34:13 +02:00
def update_room(cls, folio, status):
pool = Pool()
Configuration = pool.get('hotel.configuration')
config = Configuration.get_configuration()
cleaning_type_id = None
2022-07-28 06:42:24 +02:00
room = folio.room
2022-07-27 23:34:13 +02:00
if status in 'check_in' and config.cleaning_occupied:
cleaning_type_id = config.cleaning_occupied.id
2022-07-28 06:42:24 +02:00
room.last_check_in = datetime.now()
2022-07-31 01:15:08 +02:00
room.last_check_out = None
2022-07-27 23:34:13 +02:00
elif status in 'check_out' and config.cleaning_check_out:
cleaning_type_id = config.cleaning_check_out.id
2022-07-28 06:42:24 +02:00
room.last_check_out = datetime.now()
2022-07-31 01:15:08 +02:00
room.last_check_in = None
2022-07-27 23:34:13 +02:00
room.state = 'dirty'
if cleaning_type_id:
room.cleaning_type = cleaning_type_id
room.save()
2022-03-25 16:03:19 +01:00
2021-10-12 17:25:14 +02:00
def get_invoice_state(self, name=None):
if self.invoice_line:
return self.invoice_line.invoice.state
2022-03-23 00:37:09 +01:00
def get_channel(self, name=None):
if self.booking and self.booking.channel:
return self.booking.channel.id
2021-10-09 19:00:26 +02:00
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()
2022-07-11 02:56:01 +02:00
def get_stock_moves(self, name=None):
moves = []
for charge in self.charges:
2022-07-13 23:55:07 +02:00
if charge.move:
moves.append(charge.move.id)
2022-07-11 02:56:01 +02:00
return moves
2022-10-17 19:53:47 +02:00
# def create_invoice(self, kind=None):
# pool = Pool()
# Booking = pool.get('hotel.booking')
# Booking.create_invoice([self], kind)
# Booking.check_finished([self.booking])
2021-10-09 19:00:26 +02:00
2021-10-11 01:52:01 +02:00
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
2022-10-13 22:27:24 +02:00
@fields.depends('unit_price', 'nights_quantity')
2022-03-20 14:46:25 +01:00
def on_change_with_room_amount(self, name=None):
2021-10-11 01:52:01 +02:00
res = 0
2022-03-20 14:46:25 +01:00
if self.unit_price and self.nights_quantity:
res = self.unit_price * self.nights_quantity
2021-10-11 01:52:01 +02:00
return round(res, Folio.total_amount.digits[1])
2021-10-09 19:00:26 +02:00
@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
2021-10-11 19:03:43 +02:00
@staticmethod
def default_breakfast_included():
return True
2021-10-09 19:00:26 +02:00
@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:
2021-10-11 01:52:01 +02:00
return self.invoice_line.invoice.id
2021-10-09 19:00:26 +02:00
2021-10-16 05:54:52 +02:00
def get_room_info(self):
2021-10-09 19:00:26 +02:00
description = ' \n'.join([
2021-10-16 05:54:52 +02:00
self.product.rec_name,
2022-05-31 23:26:02 +02:00
'Huesped Principal: ' + self.main_guest.name if self.main_guest else '',
2021-10-16 05:54:52 +02:00
'Habitacion: ' + self.room.name,
'Llegada: ' + str(self.arrival_date),
'Salida: ' + str(self.departure_date),
2021-10-09 19:00:26 +02:00
])
return description
2022-03-20 14:46:25 +01:00
@fields.depends('product', 'unit_price', 'uom', 'booking',
'nights_quantity', 'commission_amount')
2021-10-09 19:00:26 +02:00
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
2022-03-20 14:46:25 +01:00
self.update_commission()
2021-10-09 19:00:26 +02:00
2022-03-20 14:46:25 +01:00
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')
2021-10-09 19:00:26 +02:00
def on_change_arrival_date(self):
2022-03-20 14:46:25 +01:00
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()
2021-10-09 19:00:26 +02:00
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.
"""
2022-07-21 04:08:22 +02:00
Maintenance = Pool().get('hotel.maintenance')
2021-10-09 19:00:26 +02:00
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]
2022-10-13 22:27:24 +02:00
dom = ['AND', ['OR',
[
('start_date', '>=', start_date),
('start_date', '<=', end_date),
], [
('end_date', '<=', end_date),
('end_date', '>=', start_date),
], [
('start_date', '<=', start_date),
('end_date', '>=', end_date),
]],
2022-07-21 04:08:22 +02:00
('state', 'in', ('in_progress', 'finished')),
]
maintenance = Maintenance.search(dom)
rooms_not_available_ids.extend(mnt.room.id for mnt in maintenance)
2021-10-09 19:00:26 +02:00
rooms_available_ids = set(rooms_ids) - set(rooms_not_available_ids)
return list(rooms_available_ids)
2022-03-20 14:46:25 +01:00
def update_nights(self, arrival_date, departure_date):
self.nights_quantity = (departure_date - arrival_date).days
2021-10-09 19:00:26 +02:00
2022-07-21 16:03:54 +02:00
@classmethod
def get_folios(cls, args):
Maintenance = Pool().get('hotel.maintenance')
start = datetime.strptime(args['start_date'], "%Y-%m-%d")
start_date = start + timedelta(days=-5)
end_date = start + timedelta(days=30)
dom = ['OR', [
('arrival_date', '>=', start_date),
('arrival_date', '<=', end_date)
], [
('departure_date', '>=', start_date),
('departure_date', '<=', end_date)
]]
fields_names = [
"room.name",
"arrival_date",
"departure_date",
"main_guest.name",
"product.rec_name",
"booking.number",
"booking.media",
"booking.channel.rec_name",
"booking.channel.code",
"registration_state",
"registration_card",
"nights_quantity",
"notes",
]
folios = cls.search_read(dom, fields_names=fields_names)
dom_maint = ['OR', [
('start_date', '>=', start_date),
('start_date', '<=', end_date),
('state', 'in', ('in_progress', 'finished', 'confirmed')),
('start_date', '!=', None),
('end_date', '!=', None),
], [
('end_date', '>=', start_date),
('end_date', '<=', end_date),
('state', 'in', ('in_progress', 'finished', 'confirmed')),
('start_date', '!=', None),
('end_date', '!=', None),
],
]
fields_maint = [
'start_date', 'end_date', 'room', 'room.name', 'issue',
'create_uid.name'
]
mnts = Maintenance.search_read(dom_maint, fields_names=fields_maint)
2022-09-30 21:22:38 +02:00
res = {'folios': folios, 'mnts': mnts}
return res
2022-07-21 16:03:54 +02:00
2022-10-03 20:33:47 +02:00
def get_total_accommodation(self):
res = 0
if self.nights_quantity and self.unit_price_w_tax:
res = self.nights_quantity * self.unit_price_w_tax
return res
2022-10-21 04:13:30 +02:00
@classmethod
def set_offset_commission_move(cls, folios_lines, bk):
pool = Pool()
Move = pool.get('account.move')
Period = pool.get('account.period')
MoveLine = pool.get('account.move.line')
Config = pool.get('hotel.configuration')
config = Config.get_configuration()
period_id = Period.find(bk.company.id, date=date.today())
description = bk.number
if bk.ota_booking_code:
description += ' | ' + bk.ota_booking_code
move, = Move.create([{
'journal': config.offset_journal.id,
'period': period_id,
'date': date.today(),
# 'origin': str(self),
'state': 'draft',
'description': description,
}])
lines_ids = []
for folio, inv_line in folios_lines:
if not folio.commission_amount:
return
party = bk.channel.agent.party
move_line, = MoveLine.create([{
'description': folio.registration_card,
'account': party.account_receivable_used,
'party': party.id,
'debit': 0,
'credit': folio.commission_amount,
'move': move.id,
}])
lines_ids.append(move_line.id)
move_line, = MoveLine.create([{
'description': folio.registration_card,
'account': party.account_payable_used,
'party': party.id,
'debit': folio.commission_amount,
'credit': 0,
'move': move.id,
}])
inv_line.offset_move_line = move_line.id
inv_line.save()
Move.post([move])
return lines_ids
2022-10-03 20:33:47 +02:00
def get_total_charges(self):
res = []
for charge in self.charges:
res.append(charge.amount)
return sum(res)
2022-10-13 22:27:24 +02:00
def get_total_amount(self, name):
2021-10-09 19:00:26 +02:00
"""
2022-10-03 20:33:47 +02:00
The total amount of folio based on room flat price.
2021-10-09 19:00:26 +02:00
TODO: If room fee is applied should be used for total price calculation
instead of flat price. Fee is linked to channel management.
"""
2021-10-14 06:41:15 +02:00
res = []
2022-10-13 22:27:24 +02:00
res.append(self.get_total_accommodation())
res.append(self.get_total_charges())
2021-10-14 06:41:15 +02:00
res = round(sum(res), Folio.total_amount.digits[1])
2021-10-09 19:00:26 +02:00
return res
2022-03-20 14:46:25 +01:00
@fields.depends('nights_quantity', 'unit_price', 'booking')
2022-03-19 06:21:49 +01:00
def on_change_with_commission_amount(self):
2021-10-09 19:00:26 +02:00
"""
2022-03-19 06:21:49 +01:00
Calculation of commission amount for channel based on booking
2021-10-09 19:00:26 +02:00
"""
res = Decimal(0)
2022-10-13 22:27:24 +02:00
if self.nights_quantity and self.unit_price and self.booking and \
self.booking.channel:
2022-03-20 14:46:25 +01:00
amount = self.on_change_with_room_amount()
res = self.booking.channel.compute(amount)
return res
2021-10-09 19:00:26 +02:00
2022-06-08 15:30:16 +02:00
@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')
2022-11-12 15:12:06 +01:00
today = date.today()
2022-06-08 15:30:16 +02:00
res = {
'value': value,
2022-11-12 15:12:06 +01:00
'header_meta': str(today),
2022-06-08 15:30:16 +02:00
}
return res
@classmethod
def report_check_out_today(cls, args):
# Dash Report
value = cls._get_check_rooms('departure')
2022-11-12 15:12:06 +01:00
today = date.today()
2022-06-08 15:30:16 +02:00
res = {
'value': value,
2022-11-12 15:12:06 +01:00
'header_meta': str(today),
2022-06-08 15:30:16 +02:00
}
return res
@classmethod
2022-11-12 15:12:06 +01:00
def _get_occupation(cls, today):
2022-06-08 15:30:16 +02:00
dom = [
2022-11-12 15:12:06 +01:00
('arrival_date', '<=', today),
('departure_date', '>', today),
2022-06-08 15:30:16 +02:00
]
folios = cls.search_read(dom, fields_names=['id', 'guests'])
return folios
@classmethod
def report_occupation_by_room(cls, args):
# Dash Report
2022-11-12 15:12:06 +01:00
today = date.today()
folios = cls._get_occupation(today)
2022-06-08 15:30:16 +02:00
value = len(folios)
res = {
'value': value,
2022-11-12 15:12:06 +01:00
'header_meta': str(today),
2022-06-08 15:30:16 +02:00
}
return res
@classmethod
def report_number_guests(cls, args):
# Dash Report
2022-11-12 15:12:06 +01:00
today = date.today()
folios = cls._get_occupation(today)
2022-06-08 15:30:16 +02:00
value = 0
for folio in folios:
value += len(folio['guests'])
res = {
'value': value,
2022-11-12 15:12:06 +01:00
'header_meta': str(today),
}
return res
@classmethod
def _get_range_dates(cls, folio, mode=None):
start_date = folio['arrival_date']
res = []
_append = res.append
for dt in range(folio['nights_quantity']):
_date = start_date + timedelta(days=dt)
if mode == 'string':
_date = str(_date)
_append(_date)
return set(res)
@classmethod
def report_month_occupancy_rate(cls, args):
pool = Pool()
Room = pool.get('hotel.room')
Folio = pool.get('hotel.folio')
today = date.today()
first, last = calendar.monthrange(today.year, today.month)
labels = []
alldays = OrderedDict()
dates_of_month = []
today = date.today()
for nd in range(last):
_day = str(date(today.year, today.month, nd + 1))
labels.append(str(nd + 1))
dates_of_month.append(_day)
alldays[_day] = []
date_start = date(today.year, today.month, 1)
date_end = date(today.year, today.month, last)
dom = [
['OR', [
('arrival_date', '<=', date_start),
('departure_date', '>=', date_start),
], [
('arrival_date', '>=', date_start),
('departure_date', '<=', date_end),
], [
('arrival_date', '<=', date_end),
('departure_date', '>=', date_end),
], [
('arrival_date', '<=', date_start),
('departure_date', '>=', date_end),
]],
('booking.state', 'not in', ['no_show', 'cancelled'])
]
folios = Folio.search_read(dom, fields_names=[
'arrival_date', 'nights_quantity'])
for folio in folios:
folio_dates = cls._get_range_dates(folio, mode='string')
dates_target = set(dates_of_month) & folio_dates
for dt in dates_target:
alldays[dt].append(1)
rooms = len(Room.search_read([]))
values = [sum(fo_num) * 100 / rooms for _, fo_num in alldays.items()]
month_name = today.strftime("%b %Y")
label_item = '% Occupancy Rate'
res = {
'label_item': label_item,
'labels': labels,
'values': values,
'header_meta': month_name,
2022-11-12 15:41:15 +01:00
'min_value': 1,
2022-11-12 15:12:06 +01:00
'max_value': 100,
}
return res
@classmethod
def report_current_occupancy_rate(cls, args):
pool = Pool()
Room = pool.get('hotel.room')
rooms = Room.search_read([
('active', '=', True),
])
today = date.today()
dates_arrival = []
dates_departure = []
first, last = calendar.monthrange(today.year, today.month)
day_one = date(today.year, today.month, 1)
for md in range(last):
dates_arrival.append(day_one + timedelta(days=(md+1)))
dates_departure.append(day_one + timedelta(days=(md+2)))
rooms_available = len(rooms) * last
dom = ['OR', [
('arrival_date', 'in', dates_arrival),
('booking.state', 'not in', ['no_show', 'cancelled']),
], [
('departure_date', 'in', dates_departure),
('booking.state', 'not in', ['no_show', 'cancelled']),
]
]
folios = cls.search_read(dom, fields_names=[
'guests', 'arrival_date', 'nights_quantity'])
occupation = []
month_dates = set(dates_arrival)
for folio in folios:
_dates = cls._get_range_dates(folio)
occupation.append(len(_dates & month_dates))
value = (sum(occupation) * 100) / rooms_available
month_name = today.strftime("%b %Y")
2022-11-15 17:18:19 +01:00
value = f'{round(value, 2)}%'
2022-11-12 15:12:06 +01:00
res = {
'value': value,
'header_meta': month_name,
2022-06-08 15:30:16 +02:00
}
return res
2021-10-09 19:00:26 +02:00
2021-10-11 19:03:43 +02:00
class FolioGuest(ModelSQL, ModelView):
'Folio Guest'
__name__ = 'hotel.folio.guest'
2022-09-30 21:22:38 +02:00
REQ_MAIN_GUEST = {
'required': Eval('main_guest', False)
}
2021-11-17 15:11:48 +01:00
folio = fields.Many2One('hotel.folio', 'Folio', required=True,
2021-10-11 19:03:43 +02:00
ondelete='CASCADE')
2022-05-22 08:52:05 +02:00
main_guest = fields.Boolean('Main Guest')
2021-10-11 19:03:43 +02:00
type_guest = fields.Selection([
('adult', 'Adult'),
('child', 'Child'),
2022-05-22 08:52:05 +02:00
], 'Type Guest', required=True)
2021-10-11 19:03:43 +02:00
type_guest_string = type_guest.translated('type_guest')
2022-07-26 19:05:08 +02:00
nationality = fields.Many2One('country.country', 'Nationality')
2022-09-30 21:22:38 +02:00
origin_country = fields.Many2One('country.country', 'Origin Country')
target_country = fields.Many2One('country.country', 'Target Country')
2021-10-11 19:03:43 +02:00
# 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'),
2022-05-22 08:52:05 +02:00
], 'Document Type', required=True)
2022-05-23 23:14:57 +02:00
type_document_string = type_document.translated('type_document')
2022-05-22 08:52:05 +02:00
id_number = fields.Char('Id Number', select=True, required=True)
name = fields.Char('Name', select=True, required=True)
2022-09-30 21:22:38 +02:00
mobile = fields.Char('Mobile', states=REQ_MAIN_GUEST)
email = fields.Char('Email', states=REQ_MAIN_GUEST)
address = fields.Char('Address', states=REQ_MAIN_GUEST)
2022-05-23 23:14:57 +02:00
profession = fields.Char('Profession', select=True)
2021-10-11 19:03:43 +02:00
birthday = fields.Date('Birthday', select=True)
2022-05-23 23:14:57 +02:00
visa_date = fields.Date('Visa Date', select=True)
visa_category = fields.Char('Visa Category')
visa_number = fields.Char('Visa Number')
2021-10-11 19:03:43 +02:00
sex = fields.Selection([
('female', 'Female'),
('male', 'Male'),
('', ''),
2022-05-22 08:52:05 +02:00
], 'Sex', required=True)
2022-05-23 23:14:57 +02:00
sex_string = sex.translated('sex')
2021-10-11 19:03:43 +02:00
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')
2022-05-22 08:52:05 +02:00
notes = fields.Text('Notes')
2022-09-30 21:22:38 +02:00
country = fields.Many2One('party.country_code', 'Country',
states=REQ_MAIN_GUEST)
subdivision = fields.Many2One('party.department_code', 'Subdivision')
city = fields.Many2One('party.city_code', 'City',
domain=[('department', '=', Eval('subdivision'))])
2022-10-07 05:47:14 +02:00
tags = fields.Many2Many('hotel.tag.guest', 'guest', 'tag', 'Tags')
2021-10-11 19:03:43 +02:00
2022-07-18 05:37:35 +02:00
@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,
('id_number', operator, value),
('name', operator, value),
('mobile', operator, value),
]
return domain
2022-09-20 04:26:49 +02:00
@classmethod
def get_splitted_name(cls, name):
first_name = None
2021-10-11 19:03:43 +02:00
first_family_name = None
second_name = None
2022-09-20 04:26:49 +02:00
second_family_name = None
full_names = {
'second_family_name': None,
'first_family_name': None,
'second_name': None,
'first_name': None,
}
if name:
names = name.split(' ')
2021-10-11 19:03:43 +02:00
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]
2022-09-20 04:26:49 +02:00
full_names['second_family_name'] = second_family_name
full_names['first_family_name'] = first_family_name
full_names['second_name'] = second_name
full_names['first_name'] = first_name
return full_names
@fields.depends('name', 'first_name', 'second_name',
'first_family_name', 'second_family_name')
def on_change_name(self):
names = self.get_splitted_name(self.name)
self.second_family_name = names['second_family_name']
self.first_family_name = names['first_family_name']
self.second_name = names['second_name']
self.first_name = names['first_name']
2021-10-11 19:03:43 +02:00
@staticmethod
def default_type_guest():
return 'adult'
@staticmethod
2022-09-30 21:22:38 +02:00
def default_nationality():
Config = Pool().get('hotel.configuration')
config = Config.get_configuration()
if config.nationality:
return config.nationality.id
2022-10-01 15:04:11 +02:00
# @staticmethod
# def default_country():
# Config = Pool().get('hotel.configuration')
# config = Config.get_configuration()
# if config.country:
# return config.country.id
2021-10-11 19:03:43 +02:00
@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
2022-10-13 22:27:24 +02:00
@fields.depends('id_number', 'name', 'sex', 'email', 'mobile',
2022-07-10 18:18:01 +02:00
'visa_number', 'visa_date', 'birthday', 'nationality')
def on_change_id_number(self):
if self.id_number:
Guest = Pool().get('hotel.folio.guest')
Party = Pool().get('party.party')
guest = None
guests = Guest.search([
('id_number', '=', self.id_number)
])
if guests:
guest = guests[0]
else:
parties = Party.search([
('id_number', '=', self.id_number)
])
2022-07-23 15:10:55 +02:00
if parties:
guest = parties[0]
2022-07-10 18:18:01 +02:00
if guest:
self.name = guest.name
self.sex = guest.sex
self.type_document = guest.type_document
self.email = guest.email
self.mobile = guest.mobile
self.visa_number = guest.visa_number
self.visa_date = guest.visa_date
self.birthday = guest.birthday
self.nationality = guest.nationality and guest.nationality.id
self.profession = guest.profession
2022-07-10 18:23:36 +02:00
self.first_name = guest.first_name
self.second_name = guest.second_name
self.first_family_name = guest.first_family_name
self.second_family_name = guest.second_family_name
2022-09-26 18:27:44 +02:00
address = ''
if hasattr(guest, 'address'):
address = guest.address
elif hasattr(guest, 'addresses') and guest.addresses:
address = guest.addresses[0].street
self.address = address
2022-07-10 18:18:01 +02:00
2021-10-11 19:03:43 +02:00
@classmethod
2022-05-22 08:52:05 +02:00
def manage_party(cls, v):
2021-10-11 19:03:43 +02:00
Party = Pool().get('party.party')
2022-05-22 08:52:05 +02:00
is_main_guest = v.pop('main_guest')
folio = v.pop('folio', None)
2022-05-23 23:14:57 +02:00
for val in ('type_guest', 'origin_country', 'target_country', 'profession'):
_ = v.pop(val, None)
2022-05-22 08:52:05 +02:00
if is_main_guest:
parties = Party.search([
('id_number', '=', v['id_number']),
])
email = v.pop('email', '')
mobile = v.pop('mobile', '')
2022-09-26 18:27:44 +02:00
address = v.pop('address', '')
nationality = v.pop('nationality')
2022-09-30 21:22:38 +02:00
country = v.pop('country', None)
subdivision = v.pop('subdivision', None)
city = v.pop('city', None)
2022-05-22 08:52:05 +02:00
v['type_person'] = 'persona_natural'
2022-10-12 15:39:43 +02:00
_ = v.pop('tags', None)
2022-05-22 08:52:05 +02:00
if not parties:
v['contact_mechanisms'] = [
('create', [
{'type': 'email', 'value': email},
{'type': 'mobile', 'value': mobile},
])
]
2022-10-01 15:17:39 +02:00
v['addresses'] = [
2022-09-30 21:22:38 +02:00
('create', [{
'street': address,
'country_code': country,
'department_code': subdivision,
'city_code': city,
}])
2022-09-26 18:27:44 +02:00
]
2022-10-12 00:42:15 +02:00
# FIXME add tags to party and remoce this lines
2022-05-22 08:52:05 +02:00
party, = Party.create([v])
else:
Party.write(parties, v)
party = parties[0]
has_email = False
has_mobile = False
2022-09-26 18:27:44 +02:00
to_write_ad = {}
to_write = {}
if party.addresses:
for addr in party.addresses:
addr.street = address
2022-09-30 21:22:38 +02:00
addr.country_code = country
addr.department_code = subdivision
addr.city_code = city
2022-09-26 18:27:44 +02:00
addr.save()
else:
to_write['addresses'] = [('create', [{
'street': address,
'country_code': nationality,
}])]
2022-05-22 08:52:05 +02:00
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()
2022-09-26 18:27:44 +02:00
to_write_cm = []
2022-05-22 08:52:05 +02:00
if not has_mobile and mobile:
2022-09-26 18:27:44 +02:00
to_write_cm.append({'type': 'mobile', 'value': mobile})
2022-05-22 08:52:05 +02:00
if not has_email and email:
2022-09-26 18:27:44 +02:00
to_write_cm.append({'type': 'email', 'value': email})
if to_write_cm:
to_write['contact_mechanisms'] = [('create', to_write_cm)]
2022-05-22 08:52:05 +02:00
if to_write:
2022-09-26 18:27:44 +02:00
Party.write(parties, to_write)
2022-05-22 08:52:05 +02:00
if folio:
Folio = Pool().get('hotel.folio')
folio = Folio(folio)
folio.main_guest = party.id
folio.save()
2022-06-08 15:30:16 +02:00
booking = folio.booking
if not booking.party:
booking.party = party.id
booking.save()
2022-05-22 08:52:05 +02:00
@classmethod
def create(cls, vlist):
2022-09-20 04:26:49 +02:00
for values in vlist:
first_name = values.get('first_name', None)
if not first_name:
names = cls.get_splitted_name(values['name'])
values.update(names)
res = super(FolioGuest, cls).create(vlist)
2021-10-11 19:03:43 +02:00
for v in vlist:
2022-05-22 08:52:05 +02:00
cls.manage_party(v)
2022-09-20 04:26:49 +02:00
return res
2022-05-22 08:52:05 +02:00
@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)
2021-10-11 19:03:43 +02:00
2021-09-21 06:33:06 +02:00
class FolioCharge(Workflow, ModelSQL, ModelView):
'Folio Charge'
__name__ = 'hotel.folio.charge'
folio = fields.Many2One('hotel.folio', 'Folio', required=True)
2020-04-16 14:45:13 +02:00
date_service = fields.Date('Date Service', select=True, required=True)
product = fields.Many2One('product.product', 'Product',
2021-10-06 02:27:25 +02:00
domain=[('salable', '=', True)], required=True)
2020-04-16 14:45:13 +02:00
quantity = fields.Integer('Quantity', required=True)
2022-07-13 22:20:58 +02:00
invoice_to = fields.Many2One('party.party', 'Invoice To')
2021-10-16 05:54:52 +02:00
unit_price = fields.Numeric('Unit Price', digits=(16, 4),
required=True)
2020-04-16 14:45:13 +02:00
unit_price_w_tax = fields.Function(fields.Numeric('Unit Price'),
'get_unit_price_w_tax')
order = fields.Char('Order', select=True)
2022-03-23 05:53:55 +01:00
description = fields.Char('Description', select=True, depends=['product'])
2020-04-16 14:45:13 +02:00
state = fields.Selection(INVOICE_STATES, 'State', readonly=True)
state_string = state.translated('state')
2021-10-06 02:27:25 +02:00
invoice_line = fields.Many2One('account.invoice.line', 'Invoice Line',
readonly=True)
2020-04-16 14:45:13 +02:00
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')
2022-10-28 18:44:14 +02:00
# Remove for deprecation
2021-10-16 05:54:52 +02:00
to_invoice = fields.Boolean('To Invoice', states={
'invisible': Bool(Eval('invoice_line')),
}, depends=['invoice_line'])
2022-10-28 18:44:14 +02:00
####################
2022-07-11 02:56:01 +02:00
storage = fields.Many2One('stock.location', 'Storage',
domain=[('type', '=', 'storage')], states={
'readonly': Bool(Eval('invoice_line')),
})
move = fields.Many2One('stock.move', 'Move')
2022-10-03 20:33:47 +02:00
status = fields.Selection([
('paid', 'Paid'),
('pending', 'Pending'),
], 'Status', help="Status of payment")
2020-04-16 14:45:13 +02:00
@classmethod
2022-09-30 21:22:38 +02:00
def delete(cls, records):
2022-10-28 18:44:14 +02:00
target = []
for rec in records:
if rec.invoice_line or rec.move:
continue
target.append(rec)
super().delete(target)
2022-09-30 21:22:38 +02:00
@classmethod
2020-04-16 14:45:13 +02:00
def __setup__(cls):
2021-09-21 06:33:06 +02:00
super(FolioCharge, cls).__setup__()
2020-04-16 14:45:13 +02:00
cls._buttons.update({
2022-10-28 18:44:14 +02:00
# 'transfer': {
# 'invisible': True,
# },
2020-04-16 14:45:13 +02:00
'bill': {
'invisible': Eval('invoice_state') is not None,
},
})
2022-10-28 18:44:14 +02:00
@classmethod
def check_create(cls, vlist):
Folio = Pool().get('hotel.folio')
for value in vlist:
folio_id = value.get('folio', None)
if folio_id:
folio = Folio(folio_id)
if folio.charges_blocked:
raise UserError(gettext('hotel.msg_folio_charges_blocked'))
@classmethod
def create(cls, vlist):
cls.check_create(vlist)
return super().create(vlist)
2020-04-16 14:45:13 +02:00
@staticmethod
def default_quantity():
return 1
2022-10-03 20:33:47 +02:00
@staticmethod
def default_status():
return 'pending'
2021-12-28 00:02:47 +01:00
@staticmethod
def default_to_invoice():
return True
2020-04-16 14:45:13 +02:00
@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
2021-07-30 17:34:10 +02:00
return 0
2020-04-16 14:45:13 +02:00
def get_unit_price_w_tax(self, name=None):
Tax = Pool().get('account.tax')
2020-08-17 17:58:45 +02:00
res = self.unit_price or 0
2020-04-16 14:45:13 +02:00
if self.unit_price:
values = Tax.compute(self.product.template.customer_taxes_used,
2020-08-17 17:58:45 +02:00
self.unit_price, 1)
2020-04-16 14:45:13 +02:00
if values:
value = values[0]
res = value['base'] + value['amount']
return res
2022-07-21 17:17:35 +02:00
def do_stock_move(self, name=None):
pool = Pool()
Location = pool.get('stock.location')
Move = pool.get('stock.move')
Config = Pool().get('hotel.configuration')
# FIXME add origin
locs_customer = Location.search([
('type', '=', 'customer')
])
customer_loc_id = locs_customer[0].id
config = Config.get_configuration()
if not self.storage and config.storage_by_default:
storage_id = config.storage_by_default.id
else:
storage_id = self.storage.id
move, = Move.create([{
'product': self.product.id,
'effective_date': self.date_service,
'quantity': self.quantity,
'unit_price': self.product.cost_price,
'uom': self.product.default_uom.id,
'from_location': storage_id,
'to_location': customer_loc_id,
'origin': str(self),
}])
self.move = move.id
Move.do([move])
self.save()
2020-04-16 14:45:13 +02:00
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)
2022-10-28 18:44:14 +02:00
# @classmethod
# @ModelView.button_action('hotel.wizard_operation_line_transfer')
# def transfer(cls, records):
# pass
2020-04-16 14:45:13 +02:00
2022-03-23 05:53:55 +01:00
@fields.depends('unit_price', 'folio', 'product', 'description',
2022-07-13 22:57:41 +02:00
'_parent_folio.main_guest')
2020-04-16 14:45:13 +02:00
def on_change_product(self):
if self.product:
self.unit_price = self.product.template.list_price
2022-03-23 05:53:55 +01:00
self.description = self.product.template.name
2020-04-16 14:45:13 +02:00
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'
2022-10-13 22:27:24 +02:00
start = StateView(
'hotel.open_migration.start',
'hotel.open_migration_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Open', 'print_', 'tryton-print', default=True),
])
2020-04-16 14:45:13 +02:00
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
2021-07-21 14:56:53 +02:00
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
2022-10-26 06:57:30 +02:00
pool = Pool()
Folio = pool.get('hotel.folio')
Company = pool.get('company.company')
records = Folio.search([
('arrival_date', '>=', data['start_date']),
('arrival_date', '<=', data['end_date']),
2021-11-17 15:11:48 +01:00
('main_guest', '!=', None),
2022-03-23 00:37:09 +01:00
('registration_state', 'in', ['check_in', 'check_out']),
2022-10-26 06:57:30 +02:00
], order=[('arrival_date', 'ASC')])
end_date = data['end_date']
_records = []
company = Company(data['company'])
mig_code = company.mig_code
city_code = company.city_code
for folio in records:
for guest in folio.guests:
for move in ('E', 'S'):
if move == 'S' and folio.departure_date > end_date:
continue
if move == 'E':
move_date = folio.arrival_date
else:
move_date = folio.departure_date
if not all([
guest.origin_country,
guest.target_country,
guest.nationality
]):
continue
_records.append({
'mig_code': mig_code,
'city_code': city_code,
'type_doc': guest.type_document,
'id_number': guest.id_number,
'nationality': guest.nationality.code_numeric,
'first_family_name': guest.first_family_name,
'second_family_name': guest.second_family_name,
'first_name': guest.first_name,
'move': move,
'move_date': move_date,
'origin': guest.origin_country.code_numeric,
'target': guest.target_country.code_numeric,
'birthday': guest.birthday,
})
report_context['records'] = _records
2020-04-16 14:45:13 +02:00
return report_context
class OperationReport(Report):
__name__ = 'hotel.operation'
@classmethod
2021-07-21 14:56:53 +02:00
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
2020-04-16 14:45:13 +02:00
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()
2022-10-13 22:27:24 +02:00
failed = StateView(
'hotel.operation.check_out.failed',
2020-04-16 14:45:13 +02:00
'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')),
])
2022-10-13 22:27:24 +02:00
accommodation = fields.Many2One('product.product', 'Accommodation',
domain=[
2020-04-16 14:45:13 +02:00
('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 = []
2022-10-13 22:27:24 +02:00
acco = self.accommodation
if not acco or not self.from_date:
2020-04-16 14:45:13 +02:00
return res
room_templates = RoomTemplate.search([
2022-10-13 22:27:24 +02:00
('template.accommodation_capacity', '>=', acco.accommodation_capacity)
2020-04-16 14:45:13 +02:00
])
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'
2021-10-09 19:49:43 +02:00
__name__ = 'hotel.folio.change_room'
2020-04-16 14:45:13 +02:00
"""
this is the wizard that allows the front desk employee to transfer
original room, and create a new operation occupany.
"""
2022-10-13 22:27:24 +02:00
start = StateView(
'hotel.folio.change_room.ask',
2021-10-09 19:49:43 +02:00
'hotel.folio_change_room_view_form', [
2020-04-16 14:45:13 +02:00
Button('Cancel', 'end', 'tryton-cancel'),
Button('Change', 'change', 'tryton-ok'),
]
)
change = StateTransition()
def transition_change(self):
2022-10-13 22:27:24 +02:00
# pool = Pool()
# Folio = pool.get('hotel.folio')
# 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 = []
2021-10-09 19:49:43 +02:00
# _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)
#
#
# operation.end_date = self.start.from_date
# operation.state = 'closed'
# operation.target = _operation.id
# operation.save()
2020-04-16 14:45:13 +02:00
return 'end'
class TransferOperationStart(ModelView):
'Transfer Operation'
2021-10-09 19:49:43 +02:00
__name__ = 'hotel.folio.transfer_operation.ask'
2022-10-13 22:27:24 +02:00
folio = fields.Many2One('hotel.folio', 'Folio', required=True, domain=[
('state', 'in', ['draft', 'open']),
])
2020-04-16 14:45:13 +02:00
tranfer_charges = fields.Boolean('Transfer Charges')
@staticmethod
def default_tranfer_charges():
return True
class TransferOperation(Wizard):
'Transfer Operation'
2021-10-09 19:49:43 +02:00
__name__ = 'hotel.folio.transfer_operation'
2020-04-16 14:45:13 +02:00
"""
this is the wizard that allows the front desk employee to transfer
original room, and create a new operation occupany.
"""
2022-10-13 22:27:24 +02:00
start = StateView(
'hotel.folio.transfer_operation.ask',
2021-10-09 19:49:43 +02:00
'hotel.folio_transfer_operation_view_form', [
2020-04-16 14:45:13 +02:00
Button('Cancel', 'end', 'tryton-cancel'),
Button('Transfer', 'transfer', 'tryton-ok'),
]
)
transfer = StateTransition()
def transition_transfer(self):
pool = Pool()
2021-10-09 19:49:43 +02:00
Operation = pool.get('hotel.folio')
2021-09-21 06:33:06 +02:00
FolioCharge = pool.get('hotel.folio.charge')
2020-04-16 14:45:13 +02:00
current_op = Operation(Transaction().context.get('active_id'))
2021-10-09 19:49:43 +02:00
target_op = self.start.folio
2020-08-29 17:19:37 +02:00
if target_op.id == current_op.id:
2021-10-09 19:49:43 +02:00
raise AccessError(gettext('hotel.msg_folio_current'))
2020-04-16 14:45:13 +02:00
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:
2021-09-21 06:33:06 +02:00
FolioCharge.write(lines_to_transfer, {
2020-04-16 14:45:13 +02:00
'operation': target_op.id
})
2020-08-15 18:01:37 +02:00
current_op.state = 'transfered'
2020-04-16 14:45:13 +02:00
current_op.operation_target = target_op.id
current_op.save()
target_op.save()
return 'end'
class OperationByConsumerReport(Report):
2021-09-21 06:33:06 +02:00
__name__ = 'hotel.folio.charge'
2020-04-16 14:45:13 +02:00
@classmethod
2021-07-21 14:56:53 +02:00
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
2020-04-16 14:45:13 +02:00
pool = Pool()
2021-10-09 19:49:43 +02:00
Folio = pool.get('hotel.folio')
2020-04-16 14:45:13 +02:00
user = pool.get('res.user')(Transaction().user)
consumer_lines = []
total_amount = 0
if records:
line = records[0]
2021-10-09 19:49:43 +02:00
folio = Folio(line.folio.id)
total_amount = folio.room_amount
2022-10-13 22:27:24 +02:00
for ch in folio.lines:
if ch.invoice_to and ch.invoice_to.id == line.invoice_to.id:
2020-04-16 14:45:13 +02:00
consumer_lines.append(l)
total_amount += l.amount
2021-10-09 19:49:43 +02:00
setattr(folio, 'lines', consumer_lines)
setattr(folio, 'total_amount', total_amount)
2020-04-16 14:45:13 +02:00
2021-10-09 19:49:43 +02:00
report_context['records'] = [folio]
2020-04-16 14:45:13 +02:00
report_context['company'] = user.company
return report_context
class OperationBill(Wizard):
'Operation Bill'
2021-10-09 19:49:43 +02:00
__name__ = 'hotel.folio.bill'
2020-04-16 14:45:13 +02:00
start_state = 'create_bill'
create_bill = StateTransition()
def transition_create_bill(self):
2021-10-09 19:49:43 +02:00
Folio = Pool().get('hotel.folio')
2020-04-16 14:45:13 +02:00
ids = Transaction().context['active_ids']
2021-10-09 19:49:43 +02:00
folios = Folio.browse(ids)
Folio.bill(folios)
2020-04-16 14:45:13 +02:00
return 'end'
class OperationVoucher(ModelSQL):
'Operation - Voucher'
2021-10-09 19:49:43 +02:00
__name__ = 'hotel.folio-account.voucher'
_table = 'folio_vouchers_rel'
2022-10-13 22:27:24 +02:00
folio = fields.Many2One('hotel.folio', 'Folio', ondelete='CASCADE',
required=True)
2022-03-19 15:46:34 +01:00
voucher = fields.Many2One('account.voucher', 'Voucher', required=True,
2022-10-13 22:27:24 +02:00
domain=[('voucher_type', '=', 'receipt')], ondelete='RESTRICT')
2020-04-16 14:45:13 +02:00
@classmethod
2021-10-09 19:49:43 +02:00
def set_voucher_origin(cls, voucher_id, folio_id):
2020-04-16 14:45:13 +02:00
cls.create([{
'voucher': voucher_id,
2021-10-09 19:49:43 +02:00
'folio': folio_id,
2020-04-16 14:45:13 +02:00
}])
2022-03-19 15:46:34 +01:00
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'
2021-10-11 19:03:43 +02:00
class FolioStockMove(ModelSQL):
'Folio - Stock Move'
__name__ = 'hotel.folio-stock.move'
_table = 'hotel_folio_stock_move_rel'
folio = fields.Many2One('hotel.folio', 'Folio',
2022-10-13 22:27:24 +02:00
ondelete='CASCADE', select=True, required=True)
2021-10-11 19:03:43 +02:00
move = fields.Many2One('stock.move', 'Move', select=True,
ondelete='RESTRICT', required=True)
2020-04-16 14:45:13 +02:00
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'
2022-10-13 22:27:24 +02:00
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),
])
2020-04-16 14:45:13 +02:00
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
2021-07-21 14:56:53 +02:00
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
2021-10-09 19:49:43 +02:00
Operation = Pool().get('hotel.folio')
2020-04-16 14:45:13 +02:00
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 = []
2022-10-13 22:27:24 +02:00
folios = Operation.search(['OR', [
2020-04-16 14:45:13 +02:00
('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
2021-11-17 15:11:48 +01:00
for op in folios:
2020-04-16 14:45:13 +02:00
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
2021-10-11 19:03:43 +02:00
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'
2022-10-13 22:27:24 +02:00
start = StateView(
'hotel.print_guests_list.start',
2021-10-11 19:03:43 +02:00
'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')
2021-10-20 00:36:12 +02:00
Folio = pool.get('hotel.folio')
2021-10-11 19:03:43 +02:00
Room = pool.get('hotel.room')
User = pool.get('res.user')
user_id = Transaction().user
user = User(user_id)
2021-11-17 01:03:10 +01:00
# 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')])
2021-10-20 00:36:12 +02:00
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,
2022-06-08 19:49:19 +02:00
'guest': guest.name,
'party': op.booking.party.name if op.booking.party else '',
2021-10-20 00:36:12 +02:00
'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
2022-06-08 19:49:19 +02:00
report_context['company'] = Company(data['company'])
2021-10-20 00:36:12 +02:00
report_context['date'] = data['date']
report_context['print_date'] = datetime.now()
report_context['user'] = user.name
2021-10-11 19:03:43 +02:00
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
2021-11-17 01:03:10 +01:00
_records = []
for rec in records:
if not rec.registration_card:
continue
_records.append(rec)
report_context['records'] = _records
2021-10-11 19:03:43 +02:00
return report_context
2022-08-04 05:36:12 +02:00
class HotelFolioTax(ModelSQL):
'Hotel Folio - Tax'
__name__ = 'hotel.folio-account.tax'
_table = 'hotel_folio_account_tax'
folio = fields.Many2One('hotel.folio', 'Folio', ondelete='CASCADE',
2022-10-13 22:27:24 +02:00
required=True)
2022-08-04 05:36:12 +02:00
tax = fields.Many2One('account.tax', 'Tax', ondelete='RESTRICT',
2022-10-13 22:27:24 +02:00
required=True)