trytonpsk-hotel/booking.py

1810 lines
64 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 __future__ import with_statement
from datetime import datetime, timedelta
from decimal import Decimal
2021-09-20 23:35:34 +02:00
2020-04-16 14:45:13 +02:00
from trytond.model import Workflow, ModelView, ModelSQL, fields
from trytond.wizard import Wizard, StateView, Button, StateTransition, StateReport
from trytond.report import Report
from trytond.pyson import Eval, If, In, Get, Not, Or, Equal, Bool
from trytond.transaction import Transaction
from trytond.pool import Pool
2021-07-31 00:44:32 +02:00
from trytond.exceptions import UserError
2021-10-08 22:43:48 +02:00
from trytond.i18n import gettext
from constants import (
STATE_BOOKING, REGISTRATION_STATE, SEGMENT, GUARANTEE, SATISFACTION,
2021-10-09 06:18:30 +02:00
MEDIA, PLAN, SOURCE, INVOICE_METHOD, COMPLEMENTARY, INVOICE_STATES,
2021-10-08 22:43:48 +02:00
)
2020-06-28 04:35:07 +02:00
2020-04-16 14:45:13 +02:00
STATES = {
2020-06-28 04:35:07 +02:00
'readonly': Eval('state') != 'offer',
2020-04-16 14:45:13 +02:00
}
STATES_CONFIRMED = {
2020-06-28 04:35:07 +02:00
'readonly': Eval('state') != 'offer',
2020-04-16 14:45:13 +02:00
'required': Eval('state') == 'confirmed',
}
STATES_CHECKIN = {
2021-10-08 22:43:48 +02:00
'readonly': Eval('registration_state').in_(
['check_in', 'no_show', 'cancelled']
),
'required': Eval('registration_state') == 'check_in',
2020-04-16 14:45:13 +02:00
}
_ZERO = Decimal('0.0')
class Booking(Workflow, ModelSQL, ModelView):
'Booking'
__name__ = 'hotel.booking'
_rec_name = 'number'
number = fields.Char('Number', readonly=True, select=True,
help="Sequence of reservation.")
party = fields.Many2One('party.party', 'Party', required=False,
select=True, help="Person or company owner of the booking.",
states={
'required': Eval('state') == 'check_in',
2020-06-28 04:35:07 +02:00
'readonly': Not(In(Eval('state'), ['offer', 'confirmed'])),
2020-04-16 14:45:13 +02:00
})
contact = fields.Char('Contact', states=STATES_CHECKIN)
2020-04-16 14:45:13 +02:00
payment_term = fields.Many2One('account.invoice.payment_term',
'Payment Term', states=STATES_CHECKIN)
2020-12-04 21:05:03 +01:00
booking_date = fields.DateTime('Booking Date', readonly=False, states=STATES)
2020-04-16 14:45:13 +02:00
person_num = fields.Integer('Person Number', states=STATES)
group = fields.Boolean('Group', states=STATES)
2020-07-18 17:18:29 +02:00
complementary = fields.Boolean('Complementary', states=STATES)
type_complementary = fields.Selection(COMPLEMENTARY, 'Type Complementary', states={
'invisible': ~Bool(Eval('complementary')),
'required': Bool(Eval('complementary')),
})
2020-04-16 14:45:13 +02:00
party_seller = fields.Many2One('hotel.channel', 'Channel',
states=STATES_CHECKIN, help="Agency or channel that do reservation.")
2021-10-08 22:43:48 +02:00
state = fields.Selection(STATE_BOOKING, 'State', readonly=True, required=True)
2020-06-28 04:35:07 +02:00
registration_state = fields.Selection(REGISTRATION_STATE, 'State Registration',
2021-07-31 00:44:32 +02:00
readonly=True, depends=['lines'])
2020-04-16 14:45:13 +02:00
state_string = state.translated('state')
price_list = fields.Many2One('product.price_list', 'Price List',
states={
2020-06-28 04:35:07 +02:00
'readonly': Or(Not(Equal(Eval('state'), 'offer')),
2020-04-16 14:45:13 +02:00
Bool(Eval('lines'))),
}, depends=['state', 'company', 'lines'])
company = fields.Many2One('company.company', 'Company', required=True,
states=STATES, domain=[('id', If(In('company',
Eval('context', {})), '=', '!='), Get(Eval('context', {}),
'company', 0))], readonly=True)
2021-09-20 23:35:34 +02:00
lines = fields.One2Many('hotel.folio', 'booking', 'Lines',
2020-04-16 14:45:13 +02:00
states={
'required': Eval('state') == 'confirmed',
'readonly': Eval('registration_state').in_(['check_in', 'check_out']),
2020-04-16 14:45:13 +02:00
}, depends=['state', 'party'], context={'party': Eval('party')})
cancellation_policy = fields.Many2One('hotel.policy.cancellation',
'Cancellation Policy', states=STATES_CHECKIN)
2020-04-16 14:45:13 +02:00
currency = fields.Many2One('currency.currency', 'Currency',
required=True, states={
2020-06-28 04:35:07 +02:00
'readonly': (Eval('state') != 'offer') |
2020-04-16 14:45:13 +02:00
(Eval('lines', [0]) & Eval('currency', 0)),
}, depends=['state'])
invoice_method = fields.Selection(INVOICE_METHOD, 'Invoice Method',
required=False, states=STATES_CONFIRMED)
satisfaction = fields.Selection(SATISFACTION, 'Satisfaction')
media = fields.Selection(MEDIA, 'Media', states=STATES_CHECKIN,
2020-04-16 14:45:13 +02:00
help="Media from booking coming from.")
media_string = media.translated('media')
plan = fields.Selection(PLAN, 'Commercial Plan', states=STATES_CHECKIN,
2020-04-16 14:45:13 +02:00
help="Plans offered by hotel and selected by guest for booking.")
plan_string = plan.translated('plan')
source_contact = fields.Selection(SOURCE, 'Source Contact',
states=STATES_CHECKIN,
2020-04-16 14:45:13 +02:00
help="Advertising source that create booking opportunity by guest.")
source_contact_string = source_contact.translated('source_contact')
comments = fields.Text('Comments', states=STATES_CHECKIN)
2020-04-16 14:45:13 +02:00
segment = fields.Selection(SEGMENT, 'Tourism Segment',
states=STATES_CHECKIN)
2020-04-16 14:45:13 +02:00
segment_string = segment.translated('segment')
guarantee = fields.Selection(GUARANTEE, 'Guarantee',
states=STATES_CHECKIN)
2020-04-16 14:45:13 +02:00
guarantee_string = guarantee.translated('guarantee')
untaxed_amount = fields.Function(fields.Numeric('Untaxed Amount',
digits=(16, 2), depends=['lines']), 'get_untaxed_amount')
tax_amount = fields.Function(fields.Numeric('Tax Amount',
digits=(16, 2), depends=['lines']), 'get_tax_amount')
total_amount = fields.Function(fields.Numeric('Total Amount',
digits=(16, 2), depends=['lines']), 'get_total_amount')
code = fields.Char('Code', states={'readonly': True})
booker = fields.Many2One('party.party', 'Booker', states={'readonly': True})
created_channel = fields.DateTime('Created Channel', states={'readonly': True})
vouchers = fields.Many2Many('hotel.booking-account.voucher', 'booking',
'voucher', 'Vouchers', states=STATES_CHECKIN, domain=[
2020-04-16 14:45:13 +02:00
], depends=['party'])
vip = fields.Boolean('V.I.P. Customer', states=STATES)
vehicles_num = fields.Integer('Vehicles Number', states=STATES,
help="Number of vehicles that bring with guests.")
vehicle_plate = fields.Integer('Vehicle Plate', states=STATES)
travel_cause = fields.Char('Travel Cause', states=STATES)
taxes_exception = fields.Boolean('Taxes Exception', states=STATES)
2021-07-31 00:44:32 +02:00
total_advance = fields.Function(fields.Numeric('Total Advance'),
'get_total_advance')
pending_to_pay = fields.Function(fields.Numeric('Pending to Pay'),
'get_pending_to_pay')
2021-09-16 19:47:03 +02:00
breakfast_included = fields.Boolean('Breakfast Included')
2021-02-02 18:39:37 +01:00
2020-04-16 14:45:13 +02:00
@classmethod
def __setup__(cls):
super(Booking, cls).__setup__()
cls._order.insert(0, ('create_date', 'DESC'))
cls._transitions |= set((
2020-06-28 04:35:07 +02:00
('offer', 'confirmed'),
2020-07-13 21:48:21 +02:00
('confirmed', 'offer'),
2020-06-28 04:35:07 +02:00
('offer', 'not_confirmed'),
('offer', 'cancelled'),
('cancelled', 'offer'),
2020-04-16 14:45:13 +02:00
('confirmed', 'cancelled'),
2020-06-28 04:35:07 +02:00
('not_confirmed', 'confirmed'),
('not_confirmed', 'cancelled'),
2020-04-16 14:45:13 +02:00
))
cls._buttons.update({
'select_rooms': {
2020-06-28 04:35:07 +02:00
'invisible': Eval('state') != 'offer',
},
'create_guest': {
2021-09-13 14:13:11 +02:00
'invisible': Eval('state') != 'offer',
2020-04-16 14:45:13 +02:00
},
'cancel': {
2021-02-02 18:39:37 +01:00
'invisible': Eval('state').in_(['cancel', '']) and
Eval('registration_state') == 'check_out',
2020-04-16 14:45:13 +02:00
},
2020-06-28 04:35:07 +02:00
'offer': {
2021-10-05 05:16:20 +02:00
'invisible': Eval('state').in_(['offer', 'confirmed'])
or Eval('registration_state').in_(['check_in', 'check_out']),
2020-04-16 14:45:13 +02:00
},
'confirm': {
2020-07-13 21:48:21 +02:00
'invisible': ~Eval('state').in_(['offer', 'not_confirmed'])
2020-06-28 04:35:07 +02:00
},
'not_confirm': {
'invisible': Eval('state') != 'offer',
2020-04-16 14:45:13 +02:00
},
'check_in': {
2020-08-15 18:01:37 +02:00
'invisible': Eval('state') != 'confirmed' and
2021-01-23 16:56:32 +01:00
Eval('registration_state').in_(['check_in', 'check_out']),
2020-04-16 14:45:13 +02:00
},
'no_show': {
2020-08-15 18:01:37 +02:00
'invisible': Eval('state') != 'confirmed' and
2021-01-23 16:56:32 +01:00
Eval('registration_state').in_(['check_in', 'check_out']),
2020-04-16 14:45:13 +02:00
},
2020-06-28 04:35:07 +02:00
'check_out': {
2020-08-15 18:01:37 +02:00
'invisible': Eval('state') != 'confirmed' and
Eval('registration_state') == 'check_out',
2020-06-28 04:35:07 +02:00
},
2020-04-16 14:45:13 +02:00
'pay_advance': {
2021-07-31 00:44:32 +02:00
'invisible':
2021-10-06 05:37:33 +02:00
Eval('registration_state').in_(['check_out', 'offer']),
2020-04-16 14:45:13 +02:00
},
2021-02-02 18:39:37 +01:00
'send_email': {
'invisible': Eval('state') != 'confirmed'
},
2020-04-16 14:45:13 +02:00
})
@classmethod
def trigger_create(cls, records):
cls.set_number(records)
@classmethod
def copy(cls, bookings, default=None):
if default is None:
default = {}
default = default.copy()
default['number'] = None
default['state'] = 'offer'
default['booking_date'] = datetime.now()
return super(Booking, cls).copy(bookings, default=default)
2021-02-02 18:39:37 +01:00
2020-04-16 14:45:13 +02:00
@staticmethod
def default_currency():
Company = Pool().get('company.company')
company = Transaction().context.get('company')
if company:
return Company(company).currency.id
@staticmethod
def default_company():
return Transaction().context.get('company') or False
@staticmethod
def default_state():
2020-06-28 04:35:07 +02:00
return 'offer'
2020-04-16 14:45:13 +02:00
@staticmethod
def default_invoice_method():
return 'by_booking'
@staticmethod
def default_booking_date():
now = datetime.now()
return now
def get_person_num(self, name):
res = 0
for line in self.lines:
if line.num_adult:
res += line.num_adult
if line.num_children:
res += line.num_children
return res
@fields.depends('party', 'price_list')
def on_change_party(self):
if self.party and self.party.sale_price_list:
self.price_list = self.party.sale_price_list.id
self.price_list.rec_name = self.party.sale_price_list.rec_name
2021-02-02 18:39:37 +01:00
@fields.depends('party_seller')
def on_change_party_seller(self):
if self.party_seller and self.party_seller.price_list:
self.price_list = self.party_seller.price_list
2020-04-16 14:45:13 +02:00
@classmethod
@ModelView.button_action('hotel.wizard_select_rooms')
def select_rooms(cls, bookings):
pass
@classmethod
@ModelView.button_action('hotel.wizard_party_guest')
def create_guest(cls, bookings):
pass
2020-04-16 14:45:13 +02:00
@classmethod
@ModelView.button_action('hotel.wizard_booking_advance_voucher')
def pay_advance(cls, bookings):
pass
@classmethod
@ModelView.button
2020-06-28 04:35:07 +02:00
@Workflow.transition('offer')
def offer(cls, records):
2020-04-16 14:45:13 +02:00
for record in records:
record.delete_occupancy()
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
def cancel(cls, records):
for record in records:
record.cancel_occupancy()
2020-06-28 04:35:07 +02:00
@classmethod
@ModelView.button
@Workflow.transition('not_confirmed')
def not_confirm(cls, records):
pass
2020-04-16 14:45:13 +02:00
@classmethod
@ModelView.button
@Workflow.transition('confirmed')
def confirm(cls, records):
cls.set_number(records)
for rec in records:
# FIXME check if does not exist previous occupancy if exist update state
2021-10-06 02:27:25 +02:00
rec.update_folio('pending')
2020-04-16 14:45:13 +02:00
@classmethod
@ModelView.button
def no_show(cls, records):
for record in records:
2020-08-15 18:01:37 +02:00
cls.write([record], {'registration_state': 'no_show'})
2020-04-16 14:45:13 +02:00
record.cancel_occupancy()
2021-02-02 18:39:37 +01:00
@classmethod
@ModelView.button
def send_email(cls, records):
for reserve in records:
if reserve.state == 'confirmed':
reserve.send_email_to()
2020-04-16 14:45:13 +02:00
@classmethod
def set_number(cls, bookings):
"""
Fill the number field with the booking sequence
"""
pool = Pool()
Config = pool.get('hotel.configuration')
config = Config.get_configuration()
for booking in bookings:
if booking.number or not config.booking_sequence:
continue
2021-07-21 14:56:53 +02:00
number = config.booking_sequence.get()
2020-04-16 14:45:13 +02:00
cls.write([booking], {'number': number})
def cancel_occupancy(self):
Operation = Pool().get('hotel.operation')
ops = Operation.search([('origin', '=', str(self))])
if ops:
Operation.cancel(ops)
def delete_occupancy(self):
Operation = Pool().get('hotel.operation')
for l in self.lines:
if l.operation:
Operation.delete([l.operation])
2021-10-06 02:27:25 +02:00
def update_folio(self, state):
Line = Pool().get('hotel.folio')
Line.write(list(self.lines), {'registration_state': state})
2020-04-16 14:45:13 +02:00
def check_rooms(self):
2020-07-13 21:48:21 +02:00
pool = Pool()
rooms_ids = []
for line in self.lines:
2020-07-18 17:18:29 +02:00
rooms_ids.append(line.room.id)
2020-07-13 21:48:21 +02:00
Housekeeping = pool.get('hotel.housekeeping')
2020-04-16 14:45:13 +02:00
housekeepings = Housekeeping.search([
('state', '!=', 'clean')
])
for hk in housekeepings:
if hk.room.id in rooms_ids:
2021-10-08 22:43:48 +02:00
raise UserError(gettext('hotel.msg_room_no_clean', s=hk.room.name))
2020-04-16 14:45:13 +02:00
def get_context_price(self, product):
context = {}
if getattr(self, 'currency', None):
context['currency'] = self.currency.id
if getattr(self, 'party', None):
context['customer'] = self.party.id
if getattr(self, 'booking_date', None):
context['sale_date'] = self.booking_date
if getattr(self, 'price_list', None):
context['price_list'] = self.price_list.id
context['uom'] = product.template.default_uom.id
# Set taxes before unit_price to have taxes in context of sale price
taxes = []
pattern = {}
if not self.taxes_exception:
for tax in product.customer_taxes_used:
if self.party and self.party.customer_tax_rule:
tax_ids = self.party.customer_tax_rule.apply(tax, pattern)
if tax_ids:
taxes.extend(tax_ids)
continue
taxes.append(tax.id)
if self.party and self.party.customer_tax_rule:
tax_ids = self.party.customer_tax_rule.apply(None, pattern)
if tax_ids:
taxes.extend(tax_ids)
context['taxes'] = taxes
return context
2021-02-02 18:39:37 +01:00
def get_total_advance(self, name):
Advance = Pool().get('hotel.booking-account.voucher')
2021-07-31 00:44:32 +02:00
vouchers = Advance.search([('booking', '=', self.id)])
res = sum([voucher.voucher.amount_to_pay for voucher in vouchers])
2021-02-02 18:39:37 +01:00
return res
2021-07-31 00:44:32 +02:00
def get_pending_to_pay(self, name):
if self.total_amount:
return self.total_amount - (self.total_advance or 0)
2020-04-16 14:45:13 +02:00
def get_total_amount(self, name):
res = 0
if self.tax_amount or self.untaxed_amount:
res = self.tax_amount + self.untaxed_amount
return res
def get_taxes(self, product):
ctx = self.get_context_price(product)
return ctx['taxes']
def get_tax_amount(self, name):
Tax = Pool().get('account.tax')
res = _ZERO
for line in self.lines:
taxes_ids = self.get_taxes(line.product)
if taxes_ids:
taxes = Tax.browse(taxes_ids)
tax_list = Tax.compute(
taxes, line.unit_price or _ZERO, line.nights_quantity or 0
)
tax_amount = sum([t['amount'] for t in tax_list], _ZERO)
res += tax_amount
res = Decimal(round(res, 2))
return res
def get_untaxed_amount(self, name):
res = _ZERO
for line in self.lines:
res += line.total_amount
return res
def get_occupancies(self, name):
occupancies = set()
for line in self.lines:
if line.occupancy_line:
occupancies.add(line.occupancy_line.id)
return list(occupancies)
2021-02-02 18:39:37 +01:00
def send_email_to(self):
pool = Pool()
config = pool.get('hotel.configuration')(1)
Template = pool.get('email.template')
email = self.party.email
if email:
Template.send(config.booking_email_template, self, email)
else:
2021-10-08 22:43:48 +02:00
raise UserError(gettext('El cliente no tiene un correo asociado'))
2021-02-02 18:39:37 +01:00
2021-09-16 19:47:03 +02:00
@fields.depends('price_list', 'breakfast_included')
def on_change_price_list(self):
if self.price_list:
self.breakfast_included = self.price_list.breakfast_included
2020-04-16 14:45:13 +02:00
2021-09-21 06:33:06 +02:00
class Folio(ModelSQL, ModelView):
'Folio'
2021-09-20 23:35:34 +02:00
__name__ = 'hotel.folio'
booking = fields.Many2One('hotel.booking', 'Booking', ondelete='CASCADE',
select=True)
2021-10-09 06:18:30 +02:00
registration_card = fields.Char('Registration Card', readonly=True,
select=True, help="Unique sequence for card guest registration.")
2020-04-16 14:45:13 +02:00
room = fields.Many2One('hotel.room', 'Room', select=True, states={
'required': Eval('registration_state') == 'check_in',
'readonly': Eval('registration_state') == 'check_in',
2020-04-16 14:45:13 +02:00
})
arrival_date = fields.Date('Arrival Date', required=True,
states=STATES_CHECKIN)
departure_date = fields.Date('Departure Date', required=True,
states=STATES_CHECKIN)
2020-04-16 14:45:13 +02:00
product = fields.Many2One('product.product', 'Product',
select=True, domain=[
('template.type', '=', 'service'),
('template.kind', '=', 'accommodation'),
], required=True, states=STATES_CHECKIN)
2021-10-06 05:37:33 +02:00
unit_price = fields.Numeric('Unit Price', digits=(16, 4),
2020-04-16 14:45:13 +02:00
states={
'required': Bool(Eval('product')),
'readonly': Eval('registration_state') == 'check_in',
2020-04-16 14:45:13 +02:00
})
uom = fields.Many2One('product.uom', 'UOM', readonly=True)
main_guest = fields.Many2One('party.party', 'Main Guest', select=True,
states={
'required': Eval('registration_state') == 'check_in',
'readonly': Eval('registration_state') == 'check_in',
2020-04-16 14:45:13 +02:00
}
)
contact = fields.Char('Contact', states=STATES_CHECKIN)
2020-04-16 14:45:13 +02:00
num_children = fields.Function(fields.Integer('No. Children'),
'get_num_children')
2020-04-16 14:45:13 +02:00
nights_quantity = fields.Function(fields.Integer('Nights'),
'get_nights_quantity')
host_quantity = fields.Integer('Host', states=STATES_CHECKIN)
2020-04-16 14:45:13 +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',
digits=(16, 2)), 'get_total_amount')
2020-04-16 14:45:13 +02:00
total_commission = fields.Function(fields.Numeric('Channel Commission',
digits=(16, 2)), 'get_channel_commission')
guests = fields.One2Many('hotel.booking.guest', 'booking_line', 'Guests',
2021-10-06 02:27:25 +02:00
states={
'readonly': ~Eval('registration_state').in_(['check_in']),
})
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)
2021-07-31 00:44:32 +02:00
registration_state = fields.Selection(REGISTRATION_STATE,
'Registration State', readonly=True)
2021-09-21 06:33:06 +02:00
charges = fields.One2Many('hotel.folio.charge', 'folio', 'Charges',
states={
2021-10-06 02:27:25 +02:00
'readonly': ~Eval('registration_state').in_(['check_in']),
})
2021-10-06 05:37:33 +02:00
party = fields.Many2One('party.party', 'Party to Bill', select=True,
states={
'readonly': Eval('registration_state').in_(['check_out']),
})
2021-10-08 22:43:48 +02:00
invoice_line = fields.Many2One('account.invoice.line', 'Invoice Line')
invoice = fields.Function(fields.Many2One('account.invoice', 'Invoice'),
'get_invoice')
2021-10-09 06:18:30 +02:00
invoice_state = fields.Selection(INVOICE_STATES, 'Invoice State', readonly=True)
2020-04-16 14:45:13 +02:00
@classmethod
def __setup__(cls):
2021-09-21 06:33:06 +02:00
super(Folio, cls).__setup__()
2021-04-07 21:03:51 +02:00
cls._check_modify_exclude = [
'nationality', 'origin_country', 'target_country',
2021-04-20 23:07:22 +02:00
'registration_state', 'guests'
2021-04-07 21:03:51 +02:00
],
cls._buttons.update({
'check_in': {
2021-10-06 05:37:33 +02:00
'invisible': Eval('registration_state').in_(['check_in', 'check_out']),
},
2021-09-21 06:33:06 +02:00
'check_out': {
2021-10-06 05:37:33 +02:00
'invisible': Eval('registration_state').in_(['check_out', 'pending']),
2021-10-08 00:10:46 +02:00
},
'bill': {
'invisible': Eval('registration_state') != 'check_out',
2021-09-21 06:33:06 +02:00
}
})
2020-04-16 14:45:13 +02:00
@classmethod
@ModelView.button
def check_in(cls, records):
config_party = Pool().get('party.configuration')(1)
validate_party = config_party.validate_party
2021-04-20 23:07:22 +02:00
if not validate_party:
config_party.validate_party = True
config_party.save()
2021-07-31 00:44:32 +02:00
booking = records[0].booking
# booking.party.pre_validate()
2021-10-08 22:43:48 +02:00
for rec in records:
rec.set_registration_card_number()
line = records[0]
2021-10-08 22:43:48 +02:00
# if line.state == 'offer':
# raise UserError(gettext('hotel.msg_missing_confirm_booking'))
if line.main_guest is None:
2021-07-31 00:44:32 +02:00
raise UserError(gettext('hotel.msg_missing_main_guest'))
if line.room is None:
2021-07-31 00:44:32 +02:00
raise UserError(gettext('hotel.msg_missing_select_room'))
booking.check_rooms()
cls.write([records[0]], {'registration_state': 'check_in'})
2021-07-31 00:44:32 +02:00
change_state = all(
[rl.registration_state == 'check_in' for rl in booking.lines]
)
print('change_state..', change_state)
if change_state:
booking.registration_state = 'check_in'
booking.save()
if config_party.validate_party != validate_party:
config_party.validate_party = validate_party
config_party.save()
@classmethod
@ModelView.button
def check_out(cls, records):
for record in records:
cls.write([record], {'registration_state': 'check_out'})
2021-10-08 00:10:46 +02:00
@classmethod
@ModelView.button
def bill(cls, records):
for rec in records:
# if rec.complementary:
# return
2021-10-08 22:43:48 +02:00
cls.create_invoice(rec)
def set_registration_card_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()
2021-10-08 00:10:46 +02:00
@classmethod
2021-10-08 22:43:48 +02:00
def create_invoice(cls, record):
2021-10-08 00:10:46 +02:00
pool = Pool()
Date = pool.get('ir.date')
InvoiceLine = pool.get('account.invoice.line')
FolioCharge = pool.get('hotel.folio.charge')
Configuration = pool.get('hotel.configuration')
configuration = Configuration.get_configuration()
2021-10-08 22:43:48 +02:00
# SaleVoucher = pool.get('sale.sale-account.voucher')
# date_ = Date.today()
# ctx = {}
invoice_to_create = cls.get_grouped_invoices([record])
print(invoice_to_create)
for rec in invoice_to_create.values():
# if rec.get('price_list'):
# ctx['price_list'] = rec.get('price_list')
# ctx['sale_date'] = date_
# ctx['currency'] = rec['currency']
# ctx['customer'] = rec['party'].id
2021-10-08 00:10:46 +02:00
invoice = cls._get_new_invoice(rec)
invoice.save()
# Here add payments to invoice
# if rec.get('vouchers'):
# for v in rec['vouchers']:
# SaleVoucher.create([{
# 'voucher': v.id,
# 'sale': sale.id
# }])
# Add and create default charges lines if exists
if rec.get('guests_qty') and rec.get('add_default_charges'):
for product in configuration.default_charges:
2021-10-08 22:43:48 +02:00
if rec['party']:
taxes_ids = cls.get_taxes(invoice, product, rec)
2021-10-08 00:10:46 +02:00
new_line = {
'invoice': invoice.id,
'type': 'line',
'unit': product.template.default_uom.id,
'quantity': rec['guests_qty'],
'unit_price': product.template.list_price,
'product': product.id,
'description': product.rec_name,
}
if taxes_ids:
new_line.update({'taxes': [('add', taxes_ids)]})
if new_line:
InvoiceLine.create([new_line])
2021-10-08 22:43:48 +02:00
print('XXXXX', rec)
for _line in rec['lines']:
line, = InvoiceLine.create([
cls._get_invoice_line(invoice, _line, rec)
])
2021-10-08 00:10:46 +02:00
if _line.get('folios'):
cls.write(_line.get('folios'), {
'invoice_line': line.id,
2021-10-08 22:43:48 +02:00
# 'invoice_state': 'in_process'
2021-10-08 00:10:46 +02:00
})
else:
FolioCharge.write([_line.get('charge')], {
'invoice_line': line.id,
'state': 'invoiced',
})
2021-10-08 22:43:48 +02:00
@classmethod
def _get_invoice_line(cls, invoice, line, record):
product = line['product']
new_line = {
# 'invoice_line': invoice.id,
'type': 'line',
'invoice': invoice.id,
'unit': product.template.default_uom.id,
'account': product.template.account_category.account_revenue_used.id,
'invoice_type': 'out',
'operation_center': 1,
'quantity': line['quantity'],
'unit_price': line['unit_price'],
'product': product.id,
'party': invoice.party.id,
'description': line['description'],
}
if not line['taxes_exception']:
taxes_ids = cls.get_taxes(invoice, line['product'], record)
if taxes_ids:
new_line.update({'taxes': [('add', taxes_ids)]})
return new_line
@classmethod
def get_taxes(cls, invoice, product, rec):
ctx = cls.get_context_price(invoice, product, rec)
return ctx['taxes']
@classmethod
def get_context_price(cls, invoice, product, rec):
context = {}
context['currency'] = rec['currency']
context['customer'] = rec['party'].id
context['price_list'] = rec['price_list']
context['uom'] = product.template.default_uom.id
# Set taxes before unit_price to have taxes in context of sale price
taxes = []
pattern = {}
for tax in product.customer_taxes_used:
if invoice.party and invoice.party.customer_tax_rule:
tax_ids = invoice.party.customer_tax_rule.apply(tax, pattern)
if tax_ids:
taxes.extend(tax_ids)
continue
taxes.append(tax.id)
if invoice.party and invoice.party.customer_tax_rule:
tax_ids = invoice.party.customer_tax_rule.apply(None, pattern)
if tax_ids:
taxes.extend(tax_ids)
context['taxes'] = taxes
return context
2020-04-16 14:45:13 +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
@classmethod
def validate(cls, lines):
2021-09-21 06:33:06 +02:00
super(Folio, cls).validate(lines)
2020-04-16 14:45:13 +02:00
for line in lines:
2021-04-07 21:03:51 +02:00
# line.check_method()
2020-12-04 21:08:40 +01:00
pass
2020-04-16 14:45:13 +02:00
2021-10-08 22:43:48 +02:00
def get_invoice(self, name=None):
if self.invoice_line and self.invoice_line.invoice:
self.invoice_line.invoice.id
# @fields.depends('accommodation', 'product')
# def on_change_accommodation(self):
# if not self.accommodation:
# self.product = None
@classmethod
def get_room_info(cls, fo):
description = ' \n'.join([
fo.product.rec_name,
'Huesped Principal: ' + fo.main_guest.name,
'Habitacion: ' + fo.room.name,
'Llegada: ' + str(fo.arrival_date),
'Salida: ' + str(fo.departure_date),
])
return description
2020-04-16 14:45:13 +02:00
2021-10-08 00:10:46 +02:00
@classmethod
def get_grouped_invoices(cls, folios):
res = {}
2021-10-08 22:43:48 +02:00
# transfered = []
2021-10-08 00:10:46 +02:00
# for op in folios:
# for top in op.transfered_operations:
# top.party = op.party
# transfered.append(top)
#
# if transfered:
# folios.extend(transfered)
2021-10-08 22:43:48 +02:00
ffols = [fol for fol in folios if not fol.invoice_line]
for fo in ffols:
if fo.party:
party = fo.party
else:
party = fo.main_guest
booking = fo.booking
agent_id = booking.party_seller.party.id if booking.party_seller else None
if party.id not in res.keys():
2021-10-08 00:10:46 +02:00
# Add room product to sale
2021-10-08 22:43:48 +02:00
res[party.id] = {
'party': party,
'currency': booking.currency.id,
2021-10-08 00:10:46 +02:00
'payment_term': None,
2021-10-08 22:43:48 +02:00
'guests_qty': len(fo.guests) + 1,
'reference': '',
'agent': agent_id,
'rooms': fo.room.name,
'company': booking.company.id,
'price_list': booking.price_list.id if booking.price_list else None,
'add_default_charges': False,
'vouchers': booking.vouchers,
'lines': [{
'folios': [fo],
'description': cls.get_room_info(fo),
'quantity': fo.nights_quantity,
'product': fo.product,
'unit_price': fo.unit_price,
'taxes_exception': booking.taxes_exception,
2021-10-08 00:10:46 +02:00
}]
}
else:
2021-10-08 22:43:48 +02:00
res[party.id]['rooms'] += ' ' + fo.room.name
res[party.id]['lines'].append({
'folios': [fo],
'description': cls.get_room_info(fo),
'quantity': fo.nights_quantity,
'product': fo.accommodation,
'unit_price': fo.unit_price,
'taxes_exception': booking.taxes_exception,
2021-10-08 00:10:46 +02:00
})
2021-10-08 22:43:48 +02:00
for charge in fo.charges:
2021-10-08 00:10:46 +02:00
if charge.invoice_line:
continue
invoice_party_id = charge.invoice_to.id
2021-10-08 22:43:48 +02:00
unit_price = booking.currency.round(charge.unit_price)
if invoice_party_id != party.id:
2021-10-08 00:10:46 +02:00
if invoice_party_id not in res.keys():
res[invoice_party_id] = {
'party': charge.invoice_to.id,
2021-10-08 22:43:48 +02:00
'currency': booking.currency.id,
2021-10-08 00:10:46 +02:00
'payment_term': None,
2021-10-08 22:43:48 +02:00
'lines': [],
2021-10-08 00:10:46 +02:00
}
2021-10-08 22:43:48 +02:00
res[invoice_party_id]['lines'].append({
2021-10-08 00:10:46 +02:00
'description': ' | '.join([
str(charge.date_service),
charge.order or '',
charge.description or ''
]),
'quantity': charge.quantity,
'product': charge.product,
'unit_price': unit_price,
'charge': charge,
2021-10-08 22:43:48 +02:00
'taxes_exception': booking.taxes_exception,
2021-10-08 00:10:46 +02:00
})
return res
2020-04-16 14:45:13 +02:00
@fields.depends('product', 'unit_price', 'uom', 'booking', 'nights_quantity')
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
@fields.depends('arrival_date', 'departure_date')
def on_change_arrival_date(self):
2021-07-31 00:44:32 +02:00
if not self.arrival_date or (
self.departure_date and self.departure_date > self.arrival_date):
2020-04-16 14:45:13 +02:00
return
self.departure_date = self.arrival_date + timedelta(days=1)
def check_method(self):
"""
Check the methods.
"""
2020-07-18 17:18:29 +02:00
Date = Pool().get('ir.date')
2021-04-20 23:07:22 +02:00
if self.registration_state in (['check_in', 'check_out']):
2021-10-08 22:43:48 +02:00
raise UserError(gettext('hotel.msg_reservation_checkin'))
2020-07-18 17:18:29 +02:00
if self.arrival_date < Date.today():
2021-10-08 22:43:48 +02:00
raise UserError(gettext('hotel.msg_invalid_arrival_date'))
2020-04-16 14:45:13 +02:00
if self.arrival_date >= self.departure_date:
2021-10-08 22:43:48 +02:00
raise UserError(gettext('hotel.msg_invalid_date'))
2021-09-21 06:33:06 +02:00
# Operation = Pool().get('hotel.operation')
# operations = Operation.search([
# ('room', '=', self.room.id),
# ['AND',
# ['OR', [
# ('start_date', '>=', self.arrival_date),
# ('end_date', '<=', self.arrival_date),
# ], [
# ('start_date', '>=', self.departure_date),
# ('end_date', '<=', self.departure_date),
# ]]
# ]
# ])
# if operations:
# raise AccessError(gettext('hotel.msg_occupied_room', s=self.departure_date))
# config = Pool().get('hotel.configuration')(1)
# quarantine_days = config.quarantine_rooms
# room_id = self.room.id
# if quarantine_days:
# delta = timedelta(days=int(quarantine_days))
# _date = self.arrival_date - delta
# operations = Operation.search([
# ['AND',
# ['OR', [
# ('room', '=', room_id),
# ('end_date', '=', _date)
# ],
# [
# ('room', '=', room_id),
# ('end_date', '=', self.arrival_date)
# ]]]])
# if operations:
# raise AccessError(gettext('hotel.msg_restring_room', s=self.room.name))
2020-07-18 17:18:29 +02:00
2020-04-16 14:45:13 +02:00
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
2021-10-05 05:16:20 +02:00
@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:
2021-10-08 22:43:48 +02:00
raise UserError(gettext('hotel.msg_invalid_date_range'))
2021-10-05 05:16:20 +02:00
# 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)
2021-07-25 18:00:02 +02:00
@fields.depends('arrival_date', 'departure_date')
2020-04-16 14:45:13 +02:00
def get_nights_quantity(self, name=None):
"""
Compute nights between start and end
return a integer the mean days of occupancy.
"""
nights = 0
if not self.arrival_date or not self.departure_date:
return nights
nights = (self.departure_date - self.arrival_date).days
return nights
def get_total_amount(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 = _ZERO
if self.nights_quantity and self.unit_price:
res = self.nights_quantity * self.unit_price
return res
def get_channel_commission(self, name):
"""
Calculation of sale commission for channel based on booking total amount
"""
res = Decimal(0)
if self.total_amount and self.booking.party_seller and \
self.booking.party_seller.party.sale_commission:
res = self.total_amount * self.booking.party_seller.sale_commission / 100
return res
2021-10-08 00:10:46 +02:00
@classmethod
def _get_new_invoice(cls, data):
pool = Pool()
Invoice = pool.get('account.invoice')
Party = pool.get('party.party')
Agent = pool.get('commission.agent')
2021-10-08 22:43:48 +02:00
Journal = pool.get('account.journal')
PaymentTerm = pool.get('account.invoice.payment_term')
2021-10-08 00:10:46 +02:00
Date = pool.get('ir.date')
date_ = Date.today()
price_list_id = None
if data.get('price_list'):
price_list_id = data['price_list']
company_id = Transaction().context.get('company')
2021-10-08 22:43:48 +02:00
party = data['party']
2021-10-08 00:10:46 +02:00
description = data.get('rooms')
reference = data.get('reference')
agent = None
if data.get('agent'):
agent = Agent(data['agent'])
2021-10-08 22:43:48 +02:00
journal, = Journal.search([
('type', '=', 'revenue'),
], limit=1)
address = Party.address_get(party, type='invoice')
payment_term = data.get('payment_term', None)
if not payment_term:
payment_terms = PaymentTerm.search([])
payment_term = payment_terms[0]
2021-10-08 00:10:46 +02:00
return Invoice(
company=company_id,
2021-10-08 22:43:48 +02:00
payment_term=payment_term.id,
2021-10-08 00:10:46 +02:00
party=party.id,
2021-10-08 22:43:48 +02:00
account=party.account_receivable_used.id,
2021-10-08 00:10:46 +02:00
invoice_date=date_,
description=description,
state='draft',
reference=reference,
agent=agent,
2021-10-08 22:43:48 +02:00
journal=journal,
type='out',
invoice_type='1',
invoice_address=address.id,
2021-10-08 00:10:46 +02:00
)
2020-04-16 14:45:13 +02:00
class BookingReport(Report):
__name__ = 'hotel.booking'
@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 SelectRoomsAsk(ModelView):
2021-10-05 05:16:20 +02:00
'Seelct Rooms Assistant'
2020-04-16 14:45:13 +02:00
__name__ = 'hotel.booking.select_rooms.ask'
arrival_date = fields.Date('Arrival Date', required=True)
departure_date = fields.Date('Departure Date', required=True)
accommodation = fields.Many2One('product.product',
'Accommodation', domain=[
('template.kind', '=', 'accommodation'),
])
rooms = fields.Many2Many('hotel.room', None, None,
'Rooms', domain=[
('id', 'in', Eval('targets')),
])
overbooking = fields.Boolean('Overbooking')
targets = fields.Function(fields.Many2Many('hotel.room', None, None,
'Targets'), 'on_change_with_targets')
@staticmethod
def default_accommodation():
Configuration = Pool().get('hotel.configuration')
2021-10-05 05:16:20 +02:00
config = Configuration.get_configuration()
if config.default_accommodation:
return config.default_accommodation.id
2020-04-16 14:45:13 +02:00
@fields.depends('arrival_date', 'departure_date', 'accommodation', 'overbooking')
def on_change_with_targets(self, name=None):
pool = Pool()
RoomTemplate = pool.get('hotel.room-product.template')
Room = pool.get('hotel.room')
2021-10-05 05:16:20 +02:00
Folio = pool.get('hotel.folio')
2020-04-16 14:45:13 +02:00
res = []
if not self.accommodation or not self.arrival_date or not self.departure_date:
return res
if self.overbooking:
return [r.id for r in Room.search([])]
room_templates = RoomTemplate.search([
('template.accommodation_capacity', '>=', self.accommodation.accommodation_capacity)
])
rooms_ids = [t.room.id for t in room_templates]
2021-10-05 05:16:20 +02:00
rooms_available_ids = Folio.get_available_rooms(
self.arrival_date,
self.departure_date,
rooms_ids=rooms_ids
)
return rooms_available_ids
2020-04-16 14:45:13 +02:00
class SelectRooms(Wizard):
'Select Rooms'
__name__ = 'hotel.booking.select_rooms'
"""
this is the wizard that allows the front desk employee to select
rooms, based on the requirements listed by the customer.
"""
start = StateView('hotel.booking.select_rooms.ask',
'hotel.view_select_rooms_form', [
Button('Exit', 'end', 'tryton-cancel'),
Button('Add and Continue', 'add_continue', 'tryton-forward'),
Button('Add', 'add_rooms', 'tryton-ok'),
]
)
add_rooms = StateTransition()
add_continue = StateTransition()
def transition_add_rooms(self):
self._add_rooms()
return 'end'
def transition_add_continue(self):
self._add_rooms()
return 'start'
def _add_rooms(self):
pool = Pool()
2021-09-20 23:35:34 +02:00
Line = pool.get('hotel.folio')
2020-04-16 14:45:13 +02:00
Booking = pool.get('hotel.booking')
booking = Booking(Transaction().context.get('active_id'))
lines_to_create = []
ctx = {}
if booking.price_list:
ctx['price_list'] = booking.price_list
ctx['sale_date'] = self.start.arrival_date
ctx['currency'] = booking.currency.id
if booking.party:
ctx['customer'] = booking.party.id
product = self.start.accommodation
quantity = (self.start.departure_date - self.start.arrival_date).days
unit_price = product.template.list_price
if booking.price_list:
with Transaction().set_context(ctx):
unit_price = booking.price_list.compute(
booking.party, product, unit_price,
quantity, product.default_uom)
unit_price = booking.currency.round(unit_price)
for room in self.start.rooms:
values = {
'booking': booking.id,
'product': product.id,
'room': room.id,
'arrival_date': self.start.arrival_date,
'departure_date': self.start.departure_date,
'unit_price': unit_price,
}
if booking.party:
values['main_guest'] = booking.party.id
values.update({'product': product.id})
lines_to_create.append(values)
Line.create(lines_to_create)
booking.save()
class BookingVoucher(ModelSQL):
'Booking - Voucher'
__name__ = 'hotel.booking-account.voucher'
_table = 'booking_vouchers_rel'
booking = fields.Many2One('hotel.booking', 'Booking',
ondelete='CASCADE', select=True, required=True)
voucher = fields.Many2One('account.voucher', 'Voucher', select=True,
domain=[('voucher_type', '=', 'receipt')], ondelete='RESTRICT',
required=True)
@classmethod
def set_voucher_origin(cls, voucher_id, booking_id):
cls.create([{
'voucher': voucher_id,
'booking': booking_id,
}])
class Guest(ModelSQL, ModelView):
'Guest'
__name__ = 'hotel.booking.guest'
_rec_name = 'party'
2021-09-20 23:35:34 +02:00
booking_line = fields.Many2One('hotel.folio', 'Booking Line',
2020-04-16 14:45:13 +02:00
required=True, ondelete='CASCADE')
party = fields.Many2One('party.party', 'Party', required=False)
type_guest = fields.Selection([
('adult', 'Adult'),
('child', 'Child'),
], 'Type Guest')
type_guest_string = type_guest.translated('type_guest')
nationality = fields.Many2One('party.nationality', 'Nationality')
origin_country = fields.Many2One('party.nationality', 'Origin Country',
2020-04-16 14:45:13 +02:00
select=True)
target_country = fields.Many2One('party.nationality', 'Target Country',
2020-04-16 14:45:13 +02:00
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')
doc_number = fields.Char('Doc. Id', select=True)
name = fields.Char('Name', select=True)
mobile = fields.Char('Mobile', select=True)
email = fields.Char('Email', select=True)
birthday = fields.Date('Birthday', select=True)
sex = fields.Selection([
('female', 'Female'),
('male', 'Male'),
('', ''),
], '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')
type_person = fields.Selection([
('persona_natural', 'Persona Natural'),
('persona_juridica', 'Persona Juridica'),
], 'Type Person')
@fields.depends('name', 'first_name', 'second_name',
'first_family_name', 'second_family_name', 'type_person')
def on_change_name(self):
second_family_name = None
first_family_name = None
second_name = None
first_name = None
if self.name and self.type_person == 'persona_natural':
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
2020-04-16 14:45:13 +02:00
def get_rec_name(self, name):
if self.party:
return self.party.name
@staticmethod
def default_type_guest():
return 'adult'
@staticmethod
def default_sex():
return 'male'
@staticmethod
def default_type_person():
return 'persona_natural'
2020-04-16 14:45:13 +02:00
@staticmethod
def default_type_document():
return '13'
@staticmethod
def default_principal_guest():
return False
@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
2020-04-16 14:45:13 +02:00
@classmethod
def create(cls, vlist):
Party = Pool().get('party.party')
new_values = []
for v in vlist:
# if not v.get('doc_number'):
# continue
party = v.get('party')
if not party:
parties = Party.search([
('id_number', '=', v['doc_number']),
])
if parties:
party = parties[0]
else:
party, = Party.create([{
'name': v['name'],
'id_number': v['doc_number'],
'type_document': v['type_document'],
'birthday': v['birthday'],
'sex': v['sex'],
'first_name': v['first_name'],
'second_name': v['second_name'],
'first_family_name': v['first_family_name'],
'second_family_name': v['second_family_name'],
'type_person': v['type_person'],
2020-04-16 14:45:13 +02:00
'contact_mechanisms': [
('create', [
{'type': 'email', 'value': v['email']},
{'type': 'mobile', 'value': v['mobile']},
])
]
}])
v['party'] = party.id
new_values.append(v)
super(Guest, cls).create(new_values)
class BookingForecastStart(ModelView):
'Booking Forecast Start'
__name__ = 'hotel.print_booking_forecast.start'
date = fields.Date('Start 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 BookingForecast(Wizard):
'Booking Forecast'
__name__ = 'hotel.print_booking_forecast'
start = StateView('hotel.print_booking_forecast.start',
'hotel.print_booking_forecast_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
])
print_ = StateReport('hotel.booking_forecast.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 BookingForecastReport(Report):
__name__ = 'hotel.booking_forecast.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)
2020-04-16 14:45:13 +02:00
MAX_DAYS = 30
pool = Pool()
Company = pool.get('company.company')
Room = pool.get('hotel.room')
2021-09-21 06:33:06 +02:00
BookingFolio = pool.get('hotel.folio')
2020-04-16 14:45:13 +02:00
rooms = Room.search([])
alldays = {}
alldays_convert = {}
for nd in range(MAX_DAYS):
day_n = 'day' + str((nd + 1))
tdate = data['date'] + timedelta(nd)
data[day_n] = tdate
data['total_' + day_n] = 0
data[('revenue_' + day_n)] = 0
data[('rate_' + day_n)] = 0
alldays[day_n] = ''
alldays_convert[tdate] = day_n
date_init = data['date']
date_limit = data['date'] + timedelta(MAX_DAYS)
dom = [['OR',
[
('arrival_date', '<=', date_init),
('departure_date', '>=', date_init),
], [
('arrival_date', '>=', date_init),
('arrival_date', '<=', date_limit),
],
], ('booking.state', 'not in', ['no_show', 'cancelled'])]
2021-09-21 06:33:06 +02:00
lines = BookingFolio.search(dom)
2020-04-16 14:45:13 +02:00
drooms = {}
for room in rooms:
drooms[room.id] = {'name': room.name}
drooms[room.id].update(alldays.copy())
for line in lines:
_delta = (line.departure_date - line.arrival_date).days
for i in range(_delta):
dt = line.arrival_date + timedelta(i)
if dt >= date_init and dt < date_limit \
and dt >= data['date']:
dayn = alldays_convert[dt]
drooms[line.room.id][dayn] = "X"
data['total_' + dayn] += 1
data['revenue_' + dayn] += float(line.unit_price) / 1000000
for i in range(MAX_DAYS):
day_n = 'day' + str((i + 1))
data['rate_' + day_n] = (data['total_' + day_n] * 100.0) / len(rooms)
report_context['records'] = list(drooms.values())
report_context['date'] = data['date']
report_context['company'] = Company(data['company']).party.name
return report_context
class RegistrationCardReport(Report):
__name__ = 'hotel.occupancy.registration_card'
@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 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
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()
Company = pool.get('company.company')
2021-09-21 06:33:06 +02:00
# Operation = pool.get('hotel.operation')
2020-04-16 14:45:13 +02:00
Room = pool.get('hotel.room')
User = pool.get('res.user')
user_id = Transaction().user
user = User(user_id)
start_date = data['date']
2021-09-21 06:33:06 +02:00
# operations = Operation.search([
# ('start_date', '<=', start_date),
# ('kind', '=', 'occupancy'),
# ('state', '=', 'open'),
# ], order=[('room.code', 'ASC')])
#
# total_guests = []
# rooms_occupied = []
# for op in operations:
# 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
#
# operations_ = []
# for op in operations:
# for guest in op.guests:
# operations_.append({
# 'room': op.room.code,
# 'guest': guest.party.name,
# 'party': op.party.code,
# 'start_date': op.start_date,
# 'end_date': op.end_date,
# 'nights_quantity': op.nights_quantity,
# })
# report_context['records'] = operations_
# 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']).party.name
# report_context['date'] = data['date']
# report_context['print_date'] = datetime.now()
# report_context['user'] = user.name
2020-04-16 14:45:13 +02:00
return report_context
class RoomsOccupancyStart(ModelView):
'Rooms Occupancy Start'
__name__ = 'hotel.print_rooms_occupancy.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 RoomsOccupancy(Wizard):
'Rooms Occupancy'
__name__ = 'hotel.print_rooms_occupancy'
start = StateView('hotel.print_rooms_occupancy.start',
'hotel.print_rooms_occupancy_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Open', 'print_', 'tryton-print', default=True),
])
print_ = StateReport('hotel.rooms_occupancy.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 RoomsOccupancyReport(Report):
__name__ = 'hotel.rooms_occupancy.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)
2020-04-16 14:45:13 +02:00
pool = Pool()
Company = pool.get('company.company')
Room = pool.get('hotel.room')
2021-09-21 06:33:06 +02:00
Folio = pool.get('hotel.folio')
2020-04-16 14:45:13 +02:00
start_date = data['date']
all_rooms = Room.search([], order=[('code', 'ASC')])
2021-09-21 06:33:06 +02:00
operations = Folio.search([
2020-04-16 14:45:13 +02:00
('start_date', '<=', start_date),
2021-08-03 18:51:43 +02:00
('state', '=', 'open'),
2020-04-16 14:45:13 +02:00
('kind', '=', 'occupancy'),
])
def _get_default_room(r):
res = {
'room': r.name,
'guest': None,
'num_guest': None,
'party': None,
'arrival': None,
'departure': None,
'booking': None,
'reference': None,
'amount': None,
'balance': None,
'state': None,
}
return res
rooms_map = {room.id: _get_default_room(room) for room in all_rooms}
occupancy_rooms = 0
for op in operations:
2021-07-31 17:13:49 +02:00
name = op.main_guest.name if op.main_guest else op.party.name
2020-04-16 14:45:13 +02:00
rooms_map[op.room.id].update({
2021-08-03 18:51:43 +02:00
'guest': name,
2020-04-16 14:45:13 +02:00
'num_guest': len(op.guests),
'party': op.party.name,
'arrival': op.start_date,
'departure': op.end_date,
'reference': op.reference,
'amount': op.total_amount,
'balance': op.total_amount_today,
'state': op.state,
})
occupancy_rooms += 1
if all_rooms:
occupancy_rate = (float(len(operations)) / len(all_rooms)) * 100
else:
occupancy_rate = 0
user = Pool().get('res.user')(Transaction().user)
2021-07-31 17:13:49 +02:00
recs = rooms_map.values()
2020-04-16 14:45:13 +02:00
report_context['records'] = rooms_map
report_context['occupancy_rate'] = occupancy_rate
report_context['occupancy_rooms'] = occupancy_rooms
report_context['company'] = Company(data['company']).party.name
report_context['date'] = data['date']
report_context['user'] = user.rec_name
return report_context
class BookingDailyStart(ModelView):
'Booking Daily Start'
__name__ = 'hotel.print_booking_daily.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 BookingDaily(Wizard):
'Rooms Occupancy'
__name__ = 'hotel.booking_daily'
start = StateView('hotel.print_booking_daily.start',
'hotel.print_booking_daily_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Open', 'print_', 'tryton-print', default=True),
])
print_ = StateReport('hotel.booking_daily.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 BookingDailyReport(Report):
__name__ = 'hotel.booking_daily.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)
2020-04-16 14:45:13 +02:00
pool = Pool()
Company = pool.get('company.company')
2021-10-09 06:18:30 +02:00
Folio = pool.get('hotel.folio')
records = Folio.search([
2020-04-16 14:45:13 +02:00
('arrival_date', '=', data['date']),
], order=[('room.code', 'ASC')])
report_context['records'] = records
report_context['company'] = Company(data['company']).party.name
report_context['date'] = data['date']
return report_context
2021-09-20 23:35:34 +02:00
class HotelCharge(Workflow, ModelSQL, ModelView):
'Hotel Charge'
__name__ = 'hotel.charge'
booking_line = fields.Many2One('', 'Booking Line',
required=True)
service_date = fields.Date('Service Date', 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=True)
unit_price = fields.Numeric('Unit Price', 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)
state = fields.Selection(INVOICE_STATES, 'State', readonly=True)
state_string = state.translated('state')
sale_line = fields.Many2One('sale.line', 'Sale 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')
@classmethod
def __setup__(cls):
2021-10-08 22:43:48 +02:00
super(HotelCharge, cls).__setup__()
2021-09-20 23:35:34 +02:00
cls._buttons.update({
'transfer': {
'invisible': True,
},
2021-10-08 22:43:48 +02:00
# 'bill': {
# 'invisible': Eval('invoice_state') is not None,
# },
2021-09-20 23:35:34 +02:00
})
@staticmethod
def default_quantity():
return 1
@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
# def get_sale(self, name=None):
# if self.sale_line:
# return self.sale_line.sale.id
# def compute_amount_with_tax(line):
# tax_amount = _ZERO
# amount = _ZERO
# if line.taxes:
# tax_list = Tax.compute(line.taxes, line.unit_price or _ZERO,
# line.quantity or 0.0)
#
# tax_amount = sum([t['amount'] for t in tax_list], _ZERO)
#
# if line.unit_price:
# amount = line.unit_price * Decimal(line.quantity)
# return amount + tax_amount
@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', 'product')
def on_change_product(self):
if self.product:
self.unit_price = self.product.template.list_price
self.description = self.product.description