trytonpsk-hotel/booking.py

1714 lines
62 KiB
Python

# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from __future__ import with_statement
from datetime import datetime, timedelta, date
from decimal import Decimal
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
from trytond.exceptions import UserError
from trytond.i18n import gettext
from .constants import (
STATE_BOOKING, REGISTRATION_STATE, REASON, GUARANTEE, SATISFACTION,
MEDIA, PLAN, INVOICE_METHOD, COMPLEMENTARY, PAYMENT_METHOD_CHANNEL,
TYPE_DOCUMENT
)
STATES = {
'readonly': Eval('state') != 'offer',
}
STATES_CONFIRMED = {
'readonly': Eval('state') != 'offer',
'required': Eval('state') == 'confirmed',
}
STATES_CHECKIN = {
'readonly': Eval('registration_state').in_(
['check_in', 'no_show', 'cancelled']
),
'required': Eval('registration_state') == 'check_in',
}
_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', 'Customer', required=False,
select=True, help="Person or company owner of the booking.",
states={
'required': Eval('state') == 'check_in',
'readonly': Not(In(Eval('state'), ['offer', 'confirmed'])),
})
contact = fields.Char('Contact', states=STATES_CHECKIN,
help='Main contact or person how request booking')
payment_term = fields.Many2One('account.invoice.payment_term',
'Payment Term', states=STATES_CHECKIN)
booking_date = fields.DateTime('Booking Date', readonly=False, states=STATES)
person_num = fields.Integer('Person Number', states=STATES)
group = fields.Boolean('Group', states=STATES)
complementary = fields.Boolean('Complementary', states=STATES)
type_complementary = fields.Selection(COMPLEMENTARY, 'Type Complementary',
states={
'invisible': ~Bool(Eval('complementary')),
'required': Bool(Eval('complementary')),
})
# rename to channel
channel = fields.Many2One('hotel.channel', 'Channel',
states={
'invisible': Eval('media') != 'ota',
'readonly': Eval('state').in_(['confirmed', 'cancelled']),
},
help="Agency or channel that do reservation.")
state = fields.Selection(STATE_BOOKING, 'State', readonly=True,
required=True)
registration_state = fields.Selection(REGISTRATION_STATE, 'State Registration',
readonly=True, depends=['lines'])
state_string = state.translated('state')
price_list = fields.Many2One('product.price_list', 'Price List',
states={
'readonly': Or(Not(Equal(Eval('state'), 'offer')),
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)
lines = fields.One2Many('hotel.folio', 'booking', 'Lines',
states={
'required': Eval('state') == 'confirmed',
'readonly': Eval('registration_state').in_(['check_in', 'check_out']),
}, depends=['state', 'party'], context={'party': Eval('party')})
cancellation_policy = fields.Many2One('hotel.policy.cancellation',
'Cancellation Policy', states=STATES_CHECKIN)
currency = fields.Many2One('currency.currency', 'Currency',
required=True, states={
'readonly': (Eval('state') != 'offer') |
(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,
help="Media from booking coming from.")
media_string = media.translated('media')
plan = fields.Selection(PLAN, 'Commercial Plan', states=STATES_CHECKIN,
help="Plans offered by hotel and selected by guest for booking.")
plan_string = plan.translated('plan')
comments = fields.Text('Comments', states=STATES_CHECKIN)
reason = fields.Selection(REASON, 'Tourism Segment',
states=STATES_CHECKIN)
reason_string = reason.translated('segment')
guarantee = fields.Selection(GUARANTEE, 'Guarantee',
states=STATES_CHECKIN)
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=[
], depends=['party'])
vip = fields.Boolean('V.I.P. Customer', states=STATES)
ota_booking_code = fields.Char('OTA Code', select=True,
states={'invisible': Eval('media') != 'ota'}
)
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)
total_advance = fields.Function(fields.Numeric('Total Advance',
digits=(16, 2)), 'get_total_advance')
pending_to_pay = fields.Function(fields.Numeric('Pending to Pay',
digits=(16, 2)), 'get_pending_to_pay')
breakfast_included = fields.Boolean('Breakfast Included')
channel_commission = fields.Function(fields.Numeric('Channel Commission',
digits=(16, 2), depends=['lines']), 'get_channel_commission')
channel_invoice = fields.Many2One('account.invoice', 'Channel Invoice',
states={
'invisible': ~Eval('channel'),
'readonly': True
})
channel_paymode = fields.Selection(PAYMENT_METHOD_CHANNEL,
'Channel Paymode', states={'invisible': ~Eval('channel')},
depends=['channel']
)
invoices = fields.Function(fields.Many2Many('account.invoice',
None, None, 'Invoices'), 'get_invoices')
@classmethod
def __setup__(cls):
super(Booking, cls).__setup__()
cls._order.insert(0, ('create_date', 'DESC'))
cls._transitions |= set((
('offer', 'confirmed'),
('offer', 'cancelled'),
('confirmed', 'offer'),
('cancelled', 'offer'),
('confirmed', 'cancelled'),
('confirmed', 'not_show'),
('not_show', 'confirmed'),
))
cls._buttons.update({
'select_rooms': {
'invisible': Eval('state').in_(['finished', 'cancelled', 'not_show']),
},
'update_holder': {
'invisible': Eval('state').in_(['finished', 'cancelled', 'not_show']),
},
'cancel': {
'invisible': Eval('state').in_(
['cancelled', 'not_show', 'finished'])
},
'offer': {
'invisible': Eval('state').in_(['offer', 'confirmed'])
},
'confirm': {
'invisible': ~Eval('state').in_(['offer', 'not_show'])
},
'not_show': {
'invisible': Eval('state') != 'confirmed',
},
'do_pay': {
'invisible': Eval('state').in_(['offer', 'cancelled']),
},
'send_email': {
'invisible': Eval('state') != 'confirmed'
},
'bill': {
'invisible': ~Eval('state').in_(['confirmed', 'not_show']),
},
'bill_to_channel': {
'invisible': ~Eval('channel'),
'readonly': (Eval('channel_paymode') != 'ota_collect') | Eval('channel_invoice', True),
},
})
@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)
@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,
('number', operator, value),
('ota_booking_code', operator, value),
('party.name', operator, value),
('contact', operator, value),
]
return domain
@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():
return 'offer'
@staticmethod
def default_plan():
return 'bed_breakfast'
@staticmethod
def default_invoice_method():
return 'by_booking'
@staticmethod
def default_booking_date():
now = datetime.now()
return now
def get_invoices(self, name=None):
res = []
for folio in self.lines:
if folio.invoice_line:
res.append(folio.invoice_line.invoice.id)
for charge in folio.charges:
if charge.invoice_line:
res.append(charge.invoice_line.invoice.id)
return list(set(res))
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('ota_booking_code', 'lines')
def on_change_ota_booking_code(self):
if self.ota_booking_code:
for line in self.lines:
line.reference = self.ota_booking_code
@fields.depends('party', 'price_list', 'lines')
def on_change_party(self):
if self.party:
if 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
for folio in self.lines:
if self.party.type_document != '31':
folio.main_guest = self.party.id
@fields.depends('channel')
def on_change_channel(self):
if self.channel:
self.channel_paymode = self.channel.payment_method
if self.channel.price_list:
self.price_list = self.channel.price_list
@classmethod
@ModelView.button_action('hotel.wizard_select_rooms')
def select_rooms(cls, bookings):
pass
@classmethod
@ModelView.button_action('hotel.wizard_update_holder')
def update_holder(cls, bookings):
pass
@classmethod
@ModelView.button_action('hotel.wizard_booking_advance_voucher')
def do_pay(cls, bookings):
pass
@classmethod
@ModelView.button
@Workflow.transition('offer')
def offer(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
def cancel(cls, records):
for rec in records:
for folio in rec.lines:
if folio.registration_state in ['check_in', 'check_out']:
raise UserError(gettext('hotel.msg_no_delete_folios'))
else:
folio.delete([folio])
@classmethod
@ModelView.button
@Workflow.transition('not_show')
def not_show(cls, records):
pass
@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
rec.update_folio('pending')
@classmethod
@ModelView.button
def no_show(cls, records):
for record in records:
cls.write([record], {'registration_state': 'no_show'})
record.cancel_occupancy()
@classmethod
@ModelView.button
def send_email(cls, records):
for reserve in records:
if reserve.state == 'confirmed':
reserve.send_email_to()
@classmethod
@ModelView.button
def bill(cls, records):
for rec in records:
cls.create_invoice(rec.lines)
cls.check_finished(records)
@classmethod
@ModelView.button
def bill_to_channel(cls, records):
for rec in records:
if rec.channel_invoice or not rec.channel or \
rec.channel.collection_mode != 'anticipated':
continue
cls.create_channel_invoice(rec)
cls.create_channel_pay(rec)
@classmethod
def check_finished(cls, records):
for rec in records:
_folios, _charges = cls.pending_to_invoice(rec.lines)
if not _folios and not _charges:
cls.write([rec], {'state': 'finished'})
@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
number = config.booking_sequence.get()
cls.write([booking], {'number': number})
@classmethod
def reconcile(cls, booking, voucher):
pool = Pool()
VoucherConfig = pool.get('account.voucher_configuration')
MoveLine = pool.get('account.move.line')
invoice = None
config = VoucherConfig.get_configuration()
account = config.customer_advance_account
for folio in booking.lines:
invoice = folio.invoice
if invoice and invoice.state == 'paid':
continue
advances = []
payments = []
for voucher in booking.vouchers:
for line in voucher.move.lines:
if line.account.id == account.id and not line.reconciliation:
advances.append(voucher)
break
elif invoice and line.account.id == invoice.account.id:
payments.append(line)
if invoice:
to_reconcile_lines = []
if advances:
invoice.create_move_advance(advances)
invoice.save()
if payments:
invoice.add_payment_lines({invoice: payments})
invoice.save()
# to_reconcile_lines.extend(payments)
if invoice.amount_to_pay == Decimal(0):
# invoice.paid([invoice])
for ml in invoice.payment_lines:
if not ml.reconciliation:
to_reconcile_lines.append(ml)
for ml in invoice.move.lines:
if not ml.reconciliation and ml.account.id == invoice.account.id:
to_reconcile_lines.append(ml)
if to_reconcile_lines:
MoveLine.reconcile(to_reconcile_lines)
@classmethod
def get_grouped_invoices(cls, folios, charges):
res = {}
for fo in folios:
if fo.booking.party:
party = fo.booking.party
else:
party = fo.main_guest
if not party:
raise UserError(gettext('hotel.msg_customer_is_required'))
bk = fo.booking
agent_id = bk.channel.agent.id if bk.channel else None
if party.id not in res.keys():
# Add room product to sale
reference = ''
if len(folios) == 1:
reference = fo.registration_card
else:
reference = ','.join(
[fo.registration_card for fo in folios]
)
res[party.id] = {
'party': party,
'currency': bk.currency.id,
'payment_term': None,
'number': bk.number,
'guests_qty': len(fo.guests) + 1,
'reference': reference,
'agent': agent_id,
'rooms': fo.room.name,
'company': bk.company.id,
'price_list': bk.price_list.id if bk.price_list else None,
'add_default_charges': False,
'vouchers': bk.vouchers,
'lines': [{
'folios': [fo],
'description': fo.get_room_info(),
'quantity': fo.nights_quantity,
'product': fo.product,
'unit_price': fo.unit_price,
'origin': str(bk),
'taxes_exception': bk.taxes_exception,
}]
}
else:
res[party.id]['rooms'] += ' ' + fo.room.name
res[party.id]['lines'].append({
'folios': [fo],
'description': fo.get_room_info(),
'quantity': fo.nights_quantity,
'product': fo.product,
'unit_price': fo.unit_price,
'origin': str(bk),
'taxes_exception': bk.taxes_exception,
})
for charge in charges:
bk = charge.folio.booking
invoice_party_id = charge.invoice_to.id
unit_price = bk.currency.round(charge.unit_price)
if invoice_party_id not in res.keys():
res[invoice_party_id] = {
'party': charge.invoice_to,
'currency': bk.currency.id,
'payment_term': None,
'number': bk.number,
'reference': charge.folio.registration_card,
'lines': [],
}
res[invoice_party_id]['lines'].append({
'description': ' | '.join([
str(charge.date_service),
charge.order or '',
charge.description or ''
]),
'quantity': charge.quantity,
'product': charge.product,
'unit_price': unit_price,
'charge': charge,
'origin': str(bk),
'taxes_exception': bk.taxes_exception,
})
return res
@classmethod
def _get_invoice_line(cls, invoice, line, record=None):
product = line['product']
new_line = {
'type': 'line',
'invoice': invoice.id,
'unit': product.template.default_uom.id,
'account': product.template.account_category.account_revenue_used.id,
'invoice_type': 'out',
'quantity': line['quantity'],
'unit_price': line['unit_price'],
'product': product.id,
'party': invoice.party.id,
'description': line['description'],
'origin': line['origin'],
}
if not line['taxes_exception']:
taxes_ids = cls.get_taxes(line['product'], invoice.party, invoice.currency)
if taxes_ids:
new_line.update({'taxes': [('add', taxes_ids)]})
return new_line
@classmethod
def get_taxes(cls, product, party=None, currency=None):
ctx = cls.get_context_price(product, party, currency)
return ctx['taxes']
@classmethod
def pending_to_invoice(cls, folios):
_folios = []
_charges = []
for folio in folios:
if not folio.invoice_line or not folio.to_invoice:
_folios.append(folio)
for charge in folio.charges:
if not charge.invoice_line and charge.to_invoice:
_charges.append(charge)
return _folios, _charges
@classmethod
def create_channel_pay(cls, bk):
Note = Pool().get('account.note')
NoteLine = Pool().get('account.note.line')
Invoice = Pool().get('account.invoice')
Config = Pool().get('account.voucher_configuration')
config = Config.get_configuration()
lines_to_create = []
note, = Note.create([{
'description': bk.number,
'journal': config.default_journal_note.id,
'date': date.today(),
'state': 'draft',
}])
line1, = NoteLine.create([{
'note': note.id,
'debit': 0,
'credit': bk.channel_commission,
'party': bk.channel.agent.party.id,
'account': bk.channel_invoice.account.id,
'description': bk.ota_booking_code,
}])
line2, = NoteLine.create([{
'note': note.id,
'debit': bk.channel_commission,
'credit': 0,
'party': bk.channel.agent.party.id,
'account': bk.channel.debit_account.id,
'description': bk.number,
}])
Note.write([note], {'lines': [('add', [line1, line2])]})
Note.post([note])
lines_to_add = []
for line in note.move.lines:
if line.account.id == bk.channel_invoice.account.id:
lines_to_add.append(line.id)
Invoice.write([bk.channel_invoice], {
'payment_lines': [('add', lines_to_add)],
})
bk.channel_invoice.save()
@classmethod
def create_channel_invoice(cls, bk):
pool = Pool()
Invoice = pool.get('account.invoice')
InvoiceLine = pool.get('account.invoice.line')
Foilo = pool.get('hotel.folio')
if not bk.channel:
return
data = {
'party': bk.channel.agent.party,
'reference': bk.number,
'description': f"{bk.ota_booking_code} | {bk.party.name}",
'payment_term': bk.payment_term,
'number': bk.number,
}
invoice = cls._get_new_invoice(data)
invoice.on_change_invoice_type()
invoice.save()
for folio in bk.lines:
if folio.invoice_line:
continue
_folio = {
'product': folio.product,
'quantity': folio.nights_quantity,
'unit_price': folio.unit_price,
'description': folio.room.name,
'origin': str(bk),
'taxes_exception': bk.taxes_exception,
}
line, = InvoiceLine.create([
cls._get_invoice_line(invoice, _folio)
])
Foilo.write([folio], {'invoice_line': line.id})
invoice.save()
invoice.update_taxes([invoice])
cls.write([bk], {'channel_invoice': invoice.id})
Invoice.post([invoice])
@classmethod
def create_invoice(cls, folios):
pool = Pool()
Configuration = pool.get('hotel.configuration')
Folio = pool.get('hotel.folio')
FolioCharge = pool.get('hotel.folio.charge')
InvoiceLine = pool.get('account.invoice.line')
config = Configuration.get_configuration()
invoice = {}
_folios, _charges = cls.pending_to_invoice(folios)
if not _folios and not _charges:
return
invoice_to_create = cls.get_grouped_invoices(_folios, _charges)
for rec in invoice_to_create.values():
invoice = cls._get_new_invoice(rec)
invoice.on_change_invoice_type()
invoice.save()
# Add and create default charges lines if exists
if rec.get('guests_qty') and rec.get('add_default_charges'):
for product in config.default_charges:
if rec['party']:
taxes_ids = cls.get_taxes(
product, rec['party'], invoice.currency
)
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,
'origin': str(self),
}
if taxes_ids:
new_line.update({'taxes': [('add', taxes_ids)]})
if new_line:
InvoiceLine.create([new_line])
for _line in rec['lines']:
line, = InvoiceLine.create([
cls._get_invoice_line(invoice, _line)
])
if _line.get('folios'):
Folio.write(_line.get('folios'), {
'invoice_line': line.id,
})
else:
FolioCharge.write([_line.get('charge')], {
'invoice_line': line.id,
})
invoice.save()
invoice.update_taxes([invoice])
@classmethod
def _get_new_invoice(cls, data):
pool = Pool()
Invoice = pool.get('account.invoice')
Party = pool.get('party.party')
Agent = pool.get('commission.agent')
Journal = pool.get('account.journal')
PaymentTerm = pool.get('account.invoice.payment_term')
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')
party = data['party']
if data.get('description'):
description = data['description']
else:
description = f"{data['number']} | {data.get('rooms')}"
reference = data.get('reference')
agent = None
if data.get('agent'):
agent = Agent(data['agent'])
journal, = Journal.search([('type', '=', 'revenue')], limit=1)
address = party.address_get(type='invoice')
payment_term = data.get('payment_term', None)
if not payment_term:
payment_terms = PaymentTerm.search([])
payment_term = payment_terms[0]
return Invoice(
company=company_id,
payment_term=payment_term.id,
party=party.id,
account=party.account_receivable_used.id,
invoice_date=date_,
description=description,
state='draft',
reference=reference,
agent=agent,
journal=journal,
type='out',
invoice_type='1',
invoice_address=address.id,
)
def update_folio(self, state):
Folio = Pool().get('hotel.folio')
Folio.write(list(self.lines), {'registration_state': state})
def check_rooms(self):
Housekeeping = Pool().get('hotel.housekeeping')
rooms_ids = []
for line in self.lines:
rooms_ids.append(line.room.id)
housekeepings = Housekeeping.search([
('state', '!=', 'clean')
])
for hk in housekeepings:
if hk.room.id in rooms_ids:
raise UserError(gettext('hotel.msg_room_no_clean', s=hk.room.name))
@classmethod
def get_context_price(cls,
product, party=None, currency=None, _date=None, price_list=None,
taxes_exception=False):
context = {}
if currency:
context['currency'] = currency.id
if party:
context['customer'] = party.id
if _date:
context['sale_date'] = _date
if price_list:
context['price_list'] = 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 taxes_exception and party:
for tax in product.customer_taxes_used:
if party and party.customer_tax_rule:
tax_ids = party.customer_tax_rule.apply(tax, pattern)
if tax_ids:
taxes.extend(tax_ids)
continue
taxes.append(tax.id)
if party and party.customer_tax_rule:
tax_ids = party.customer_tax_rule.apply(None, pattern)
if tax_ids:
taxes.extend(tax_ids)
context['taxes'] = taxes
return context
def get_total_advance(self, name):
Advance = Pool().get('hotel.booking-account.voucher')
vouchers = Advance.search([('booking', '=', self.id)])
res = sum([voucher.voucher.amount_to_pay for voucher in vouchers])
return res
def get_pending_to_pay(self, name):
if self.total_amount:
return self.total_amount - (self.total_advance or 0)
return 0
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_tax_amount(self, name):
Tax = Pool().get('account.tax')
Booking = Pool().get('hotel.booking')
res = _ZERO
for line in self.lines:
taxes_ids = Booking.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_channel_commission(self, name):
res = sum(line.commission_amount for line in self.lines if
line.commission_amount)
return res
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:
raise UserError(gettext('El cliente no tiene un correo asociado'))
@fields.depends('price_list', 'breakfast_included')
def on_change_price_list(self):
if self.price_list:
self.breakfast_included = self.price_list.breakfast_included
class BookingReport(Report):
__name__ = 'hotel.booking'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
user = Pool().get('res.user')(Transaction().user)
report_context['company'] = user.company
return report_context
class BookingStatementReport(Report):
__name__ = 'hotel.booking_statement'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
user = Pool().get('res.user')(Transaction().user)
report_context['company'] = user.company
return report_context
class SelectRoomsAsk(ModelView):
'Select Rooms Assistant'
__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')
unit_price = fields.Numeric('Unit Price', digits=(16, 4), required=True)
@staticmethod
def default_accommodation():
Configuration = Pool().get('hotel.configuration')
config = Configuration.get_configuration()
if config.default_accommodation:
return config.default_accommodation.id
@fields.depends('accommodation', 'departure_date', 'arrival_date')
def on_change_with_unit_price(self):
Booking = Pool().get('hotel.booking')
booking = Booking(Transaction().context.get('active_id'))
ctx = {}
if booking.price_list:
ctx['price_list'] = booking.price_list
ctx['sale_date'] = self.arrival_date
ctx['currency'] = booking.currency.id
if booking.party:
ctx['customer'] = booking.party.id
if self.accommodation and self.departure_date and self.arrival_date:
product = self.accommodation
unit_price = product.template.list_price
quantity = (self.departure_date - self.arrival_date).days
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)
return unit_price
@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')
Folio = pool.get('hotel.folio')
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]
rooms_available_ids = Folio.get_available_rooms(
self.arrival_date,
self.departure_date,
rooms_ids=rooms_ids
)
return rooms_available_ids
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()
Line = pool.get('hotel.folio')
Booking = pool.get('hotel.booking')
booking = Booking(Transaction().context.get('active_id'))
lines_to_create = []
product = self.start.accommodation
for room in self.start.rooms:
values = {
'booking': booking.id,
'product': product.id,
'reference': booking.ota_booking_code,
'contact': booking.contact,
'room': room.id,
'arrival_date': self.start.arrival_date,
'departure_date': self.start.departure_date,
'unit_price': self.start.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 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
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
MAX_DAYS = 30
pool = Pool()
Company = pool.get('company.company')
Room = pool.get('hotel.room')
BookingFolio = pool.get('hotel.folio')
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'])]
lines = BookingFolio.search(dom)
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 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 = {
'ids': [],
'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
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
User = pool.get('res.user')
Room = pool.get('hotel.room')
Folio = pool.get('hotel.folio')
start_date = data['date']
all_rooms = Room.search([], order=[('code', 'ASC')])
folios = Folio.search([
('arrival_date', '<=', start_date),
('departure_date', '>=', start_date),
('registration_state', '=', 'check_in'),
])
def _get_default_room(r):
res = {
'room': r.name,
'guest': None,
'num_guest': None,
'party': None,
'arrival': None,
'departure': None,
'booking': None,
'registration_card': None,
'amount': 0,
'registration_state': None,
'total_balance': 0,
}
return res
rooms_map = {room.id: _get_default_room(room) for room in all_rooms}
occupancy_rooms = 0
for fo in folios:
rooms_map[fo.room.id].update({
'guest': fo.main_guest.name,
'num_guest': len(fo.guests),
'party': fo.booking.party.name if fo.booking.party else '',
'arrival': fo.arrival_date,
'departure': fo.departure_date,
'registration_card': fo.registration_card,
'amount': fo.total_amount,
'booking': fo.booking.number,
'registration_state': fo.registration_state_string,
'total_balance': fo.total_balance,
})
occupancy_rooms += 1
if all_rooms:
occupancy_rate = (float(len(folios)) / len(all_rooms)) * 100
else:
occupancy_rate = 0
report_context['records'] = rooms_map.values()
report_context['occupancy_rate'] = occupancy_rate
report_context['occupancy_rooms'] = occupancy_rooms
report_context['company'] = Company(data['company'])
report_context['date'] = data['date']
report_context['user'] = User(Transaction().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
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
Folio = pool.get('hotel.folio')
records = Folio.search([
('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
class InvoicePaymentForm(ModelView):
'Invoice Payment Form'
__name__ = 'invoice.payment.form'
payment_mode = fields.Many2One('account.voucher.paymode', 'Payment Mode',
domain=[], required=True)
payment_amount = fields.Numeric('Payment amount', digits=(16, 2),
required=True)
party = fields.Many2One('party.party', 'Party', required=True)
pay_date = fields.Date('Advance Date', required=True)
reference = fields.Char('Reference')
@classmethod
def default_advance_date(cls):
Date = Pool().get('ir.date')
return Date.today()
class UpdateHolderStart(ModelView):
'Update Holder Start'
__name__ = 'hotel.update_holder.start'
name = fields.Char('Name', required=True)
sex = fields.Selection([
('', ''),
('male', 'Male'),
('female', 'Female'),
], 'Sex')
email = fields.Char('Email', required=True)
mobile = fields.Char('Mobile')
phone = fields.Char('Phone')
birthday = fields.Date('Birthday')
nationality = fields.Many2One('party.nationality', 'Nationality')
origin_country = fields.Many2One('party.country_code', 'Origin Country')
target_country = fields.Many2One('party.country_code', 'Target Country')
country = fields.Many2One('party.country_code', 'Country')
subdivision = fields.Many2One('party.department_code', 'Subdivision')
city = fields.Many2One('party.city_code', 'City', domain=[
('department', '=', Eval('subdivision'))
])
address = fields.Char('Address')
type_document = fields.Selection(TYPE_DOCUMENT, 'Tipo de Documento',
required=True)
id_number = fields.Char('Id Number', required=True)
visa_number = fields.Char('Visa Number')
visa_date = fields.Date('Visa Date')
notes = fields.Text('Notes')
customer = fields.Many2One('party.party', 'Party')
customer_id_number = fields.Char('Customer Id Number')
customer_name = fields.Char('Customer Name')
customer_country = fields.Many2One('party.country_code', 'Customer Country')
customer_subdivision = fields.Many2One('party.department_code',
'Customer Subdivision')
customer_city = fields.Many2One('party.city_code', 'Customer City')
customer_address = fields.Char('Customer Address')
customer_phone = fields.Char('Customer Phone')
customer_email = fields.Char('Customer Email')
customer_type_document = fields.Selection(TYPE_DOCUMENT, 'Tipo de Documento Cliente')
class UpdateHolder(Wizard):
'Update Holder'
__name__ = 'hotel.update_holder'
start = StateView('hotel.update_holder.start',
'hotel.update_holder_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Update', 'update', 'tryton-ok', default=True),
])
update = StateTransition()
def default_start(self, fields):
pool = Pool()
Booking = pool.get('hotel.booking')
booking = Booking(Transaction().context['active_id'])
res = {}
print(booking.party)
party = booking.party
if party and party.id > 0:
address = party.addresses[0] if party.addresses else None
res = {
'name': party.name,
'nationality': party.nationality.id if party.nationality else None,
'id_number': party.id_number,
'type_document': party.type_document,
'sex': party.sex,
'mobile': party.mobile,
'phone': party.phone,
'email': party.email,
'visa_number': party.visa_number,
'visa_date': party.visa_date,
'birthday': party.birthday,
'notes': party.notes,
}
if address:
res['country'] = address.country_code.id if address.country_code else None
res['city'] = address.city_code.id if address.city_code else None
res['subdivision'] = address.department_code.id if address.department_code else None
return res
def _set_cms(self, action, rec, email, mobile=None, phone=None):
cms = [] # contact_mechanisms
if mobile:
cms.append({'type': 'mobile', 'value': mobile})
if email:
cms.append({'type': 'email', 'value': email})
if phone:
cms.append({'type': 'phone', 'value': phone})
if cms:
rec['contact_mechanisms'] = [(action, cms)]
return
def transition_update(self):
pool = Pool()
Booking = pool.get('hotel.booking')
Folio = pool.get('hotel.folio')
Party = pool.get('party.party')
Address = pool.get('party.address')
CM = pool.get('party.contact_mechanism')
active_id = Transaction().context.get('active_id', False)
edit = True
booking = Booking(active_id)
_party = self.start
rec_company = {}
to_folio = {}
rec = {
'name': _party.name.upper(),
'nationality': _party.nationality.id if _party.nationality else None,
'sex': _party.sex,
'birthday': _party.birthday,
'type_document': _party.type_document,
'id_number': _party.id_number,
'visa_number': _party.visa_number,
'visa_date': _party.visa_date,
'notes': _party.notes,
}
country_code = _party.country.id if _party.country else None
city_code = _party.city.id if _party.city else None
subdivision_code = _party.subdivision.id if _party.subdivision else None
street = _party.address.upper() if _party.address else ''
address = {}
address['country_code'] = country_code
address['city_code'] = city_code
address['department_code'] = subdivision_code
address['street'] = street
if not booking.party:
edit = False
parties = Party.search([
('id_number', '=', _party.id_number),
])
if parties:
raise UserError('Este cliente ya existe!')
self._set_cms('create', rec, _party.email, _party.mobile, _party.phone)
rec['addresses'] = [('create', [address])]
else:
if booking.party.addresses:
Address.write(list(booking.party.addresses), address)
else:
Address.create([address])
cms_add = {}
if _party.mobile:
cms_add['mobile'] = _party.mobile
cms_add['phone'] = _party.phone
cms_add['email'] = _party.email
if booking.party.contact_mechanisms:
for cm in booking.party.contact_mechanisms:
if cm.type == 'mobile' and _party.mobile:
cm.value = cms_add.pop('mobile')
elif cm.type == 'phone' and _party.phone:
cm.value = cms_add.pop('phone')
elif cm.type == 'email' and _party.email:
cm.value = cms_add.pop('email')
cm.save()
if cms_add:
for (key, value) in cms_add.items():
if not value:
continue
cm = CM(party=booking.party.id, type=key, value=value)
cm.save()
else:
self._set_cms('create', rec, _party.email, _party.mobile, _party.phone)
# to_folio['origin_country'] = _party.origin_country,
# to_folio['target_country'] = _party.target_country,
# rec['party_obligation_tax'] = [('add', [116])]
rec_ = None
if _party.customer_id_number and _party.customer_name and _party.customer_type_document:
rec_ = {
'name': _party.customer_name,
'id_number': _party.customer_id_number,
'type_document': _party.customer_type_document,
}
address_cust = {
'country_code': None,
'city_code': None,
'street': '',
}
if _party.customer_country:
address_cust['country_code'] = _party.customer_country.id
if _party.customer_city:
address_cust['city_code'] = _party.customer_city.id
if _party.customer_address:
address_cust['street'] = _party.customer_address.upper()
rec_['addresses'] = [('create', [address_cust])]
self._set_cms(
'create',
rec_,
_party.customer_email,
phone=_party.customer_phone,
)
party, = Party.create([rec_])
# rec['party_obligation_tax'] = [('add', [116])]
if edit:
Party.write([booking.party], rec)
else:
party, = Party.create([rec])
Booking.write([booking], {'party': party.id})
booking.save()
for folio in booking.lines:
Folio.write([folio], to_folio)
return 'end'
class ManagerStart(ModelView):
'Manager Start'
__name__ = 'hotel.print_manager.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 Manager(Wizard):
'Manager'
__name__ = 'hotel.print_manager'
start = StateView('hotel.print_manager.start',
'hotel.print_manager_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Open', 'print_', 'tryton-print', default=True),
])
print_ = StateReport('hotel.manager.report')
def do_print_(self, action):
company = self.start.company
data = {
'start_date': self.start.start_date,
'end_date': self.start.end_date,
'company': company.id,
}
return action, data
def transition_print_(self):
return 'end'
class ManagerReport(Report):
__name__ = 'hotel.manager.report'
@classmethod
def get_location(self, party, field):
for address in party.addresses:
print('.........', address, field)
if hasattr(address, field):
value = getattr(address, field + '_code')
print('>>>>',value)
if value:
return value
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
Folio = pool.get('hotel.folio')
Room = pool.get('hotel.room')
User = pool.get('res.user')
rooms = Room.search([])
total_rooms = len(rooms)
delta_days = (data['end_date'] - data['start_date']).days + 1
folios = Folio.search([
('arrival_date', '>=', data['start_date']),
('arrival_date', '<=', data['end_date']),
])
list_channels = [
('ota', 'OTAS'),
('direct', 'DIRECTOS'),
('special_price', 'TARIFA ESPECIAL'),
('courtesy', 'CORTESIA'),
('house_use', 'USO DE CASA'),
]
channels = {}
rooms_occupied = []
for ch, desc in list_channels:
channels[ch] = {
'name': desc,
'room_qty': [],
'room_rate': 0,
'num_adults': [],
'num_children': [],
'income': [],
'adr': 0,
'rev_par': 0,
}
local_guests = []
foreign_guests = []
total_income = []
rooms_saled = []
guests_by_country = {}
guests_by_city = {}
for folio in folios:
parties = [folio.main_guest]
if folio.booking.channel:
type_ = 'ota'
else:
type_ = 'direct'
channels[type_]['room_qty'].append(folio.nights_quantity)
channels[type_]['num_adults'].append(folio.num_adults)
channels[type_]['num_children'].append(folio.num_children)
channels[type_]['income'].append(folio.total_amount)
total_income.append(folio.total_amount)
parties = set([guest.party for guest in folio.guests] + parties)
for guest in parties:
if not guest or not guest.nationality:
continue
# print('guest ', guest, folio.booking.number)
if guest.nationality.name == 'COLOMBIA':
city = cls.get_location(guest, 'city')
local_guests.append(1)
print('city ', city)
if not city:
continue
if city not in guests_by_city.keys():
guests_by_city[city] = {
'name': city.name,
'persons': [],
'nights': [],
}
guests_by_city[city]['persons'].append(1)
guests_by_city[city]['nights'].append(folio.nights_quantity)
else:
country = cls.get_location(guest, 'country')
print('country ', country)
foreign_guests.append(1)
if not country:
continue
if country not in guests_by_country.keys():
guests_by_country[country] = {
'name': country.name,
'persons': [],
'nights': [],
}
guests_by_country[country]['persons'].append(1)
guests_by_country[country]['nights'].append(folio.nights_quantity)
for k, v in channels.items():
room_qty = sum(v['room_qty'])
income = sum(v['income'])
v['room_qty'] = room_qty
v['room_rate'] = room_qty / (total_rooms * delta_days)
v['num_adults'] = sum(v['num_adults'])
v['num_children'] = sum(v['num_children'])
v['income'] = income
if income > 0:
v['adr'] = income / room_qty
v['rev_par'] = income / delta_days
rooms_occupied.append(room_qty)
if k in ('direct', 'ota'):
rooms_saled.append(room_qty)
print(guests_by_country)
available_nights = total_rooms * delta_days
beds_capacity = []
for room in rooms:
beds_capacity.append(room.main_accommodation.accommodation_capacity or 0)
available_beds = sum(beds_capacity) * delta_days
print(total_rooms, delta_days)
average_price = sum(total_income) / sum(rooms_saled)
report_context['records'] = channels.values()
report_context['rooms_occupied'] = sum(rooms_occupied)
report_context['rooms_saled'] = sum(rooms_saled)
report_context['gross_occupancy'] = sum(rooms_occupied) / available_nights
report_context['net_occupancy'] = sum(rooms_saled) / available_nights
report_context['total_income'] = sum(total_income)
report_context['local_guests'] = sum(local_guests)
report_context['foreign_guests'] = sum(foreign_guests)
report_context['available_nights'] = available_nights
report_context['available_beds'] = available_beds
report_context['average_price'] = average_price
report_context['guests_by_country'] = guests_by_country.values()
report_context['guests_by_city'] = guests_by_city.values()
report_context['company'] = Company(data['company'])
user_id = Transaction().context.get('user')
print(user_id)
report_context['user'] = User(user_id)
return report_context