trytonpsk-hotel/booking.py

2049 lines
74 KiB
Python
Raw Normal View History

2020-04-16 14:45:13 +02:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2022-07-24 14:17:39 +02:00
import os
2022-03-19 17:40:54 +01:00
from datetime import datetime, timedelta, date
2020-04-16 14:45:13 +02:00
from decimal import Decimal
2021-09-20 23:35:34 +02:00
2022-07-24 14:17:39 +02:00
from trytond.tools import file_open
2020-04-16 14:45:13 +02:00
from trytond.model import Workflow, ModelView, ModelSQL, fields
2021-10-09 19:49:43 +02:00
from trytond.wizard import (
Wizard, StateView, Button, StateTransition, StateReport
)
2020-04-16 14:45:13 +02:00
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
2022-03-16 00:19:31 +01:00
from .constants import (
2021-11-24 22:49:39 +01:00
STATE_BOOKING, REGISTRATION_STATE, REASON, GUARANTEE, SATISFACTION,
2022-03-20 01:11:41 +01:00
MEDIA, PLAN, INVOICE_METHOD, COMPLEMENTARY, PAYMENT_METHOD_CHANNEL,
2022-03-24 18:17:50 +01:00
TYPE_DOCUMENT
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.")
2021-11-24 22:49:39 +01:00
party = fields.Many2One('party.party', 'Customer', required=False,
2020-04-16 14:45:13 +02:00
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
})
2021-11-24 22:49:39 +01:00
contact = fields.Char('Contact', states=STATES_CHECKIN,
help='Main contact or person how request booking')
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)
2021-10-16 05:54:52 +02:00
type_complementary = fields.Selection(COMPLEMENTARY, 'Type Complementary',
states={
'invisible': ~Bool(Eval('complementary')),
'required': Bool(Eval('complementary')),
2020-07-18 17:18:29 +02:00
})
2022-03-19 06:21:49 +01:00
# rename to channel
2022-03-19 17:40:54 +01:00
channel = fields.Many2One('hotel.channel', 'Channel',
2022-03-20 01:11:41 +01:00
states={
'invisible': Eval('media') != 'ota',
'readonly': Eval('state').in_(['confirmed', 'cancelled']),
},
help="Agency or channel that do reservation.")
2021-10-16 05:54:52 +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,
2022-07-21 16:03:54 +02:00
help="Way through which the booking arrives.")
2020-04-16 14:45:13 +02:00
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')
comments = fields.Text('Comments', states=STATES_CHECKIN)
2022-07-22 17:36:13 +02:00
reason = fields.Selection(REASON, 'Tourism Segment', states=STATES_CHECKIN)
2021-11-24 22:49:39 +01:00
reason_string = reason.translated('segment')
2022-06-08 15:30:16 +02:00
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')
2022-07-22 17:36:13 +02:00
collect_amount = fields.Function(fields.Numeric('Collect Amount',
digits=(16, 2), depends=['lines']), 'get_collect_amount')
2020-04-16 14:45:13 +02:00
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',
2022-07-22 17:36:13 +02:00
'voucher', 'Vouchers', states={'readonly': False})
2020-04-16 14:45:13 +02:00
vip = fields.Boolean('V.I.P. Customer', states=STATES)
2022-03-20 01:11:41 +01:00
ota_booking_code = fields.Char('OTA Code', select=True,
2022-03-20 14:46:25 +01:00
states={'invisible': Eval('media') != 'ota'}
2022-03-20 01:11:41 +01:00
)
2022-07-08 06:51:11 +02:00
payments = fields.Many2Many('hotel.booking-statement.line', 'booking',
'statement_line', 'Payments', states=STATES_CHECKIN, readonly=True,
depends=['party'])
2020-04-16 14:45:13 +02:00
vehicles_num = fields.Integer('Vehicles Number', states=STATES,
help="Number of vehicles that bring with guests.")
travel_cause = fields.Char('Travel Cause', states=STATES)
taxes_exception = fields.Boolean('Taxes Exception', states=STATES)
2021-10-09 19:00:26 +02:00
total_advance = fields.Function(fields.Numeric('Total Advance',
digits=(16, 2)), 'get_total_advance')
pending_to_pay = fields.Function(fields.Numeric('Pending to Pay',
2021-10-16 05:54:52 +02:00
digits=(16, 2)), 'get_pending_to_pay')
2021-09-16 19:47:03 +02:00
breakfast_included = fields.Boolean('Breakfast Included')
2022-03-19 06:21:49 +01:00
channel_commission = fields.Function(fields.Numeric('Channel Commission',
digits=(16, 2), depends=['lines']), 'get_channel_commission')
2022-03-20 14:46:25 +01:00
channel_invoice = fields.Many2One('account.invoice', 'Channel Invoice',
states={
'invisible': ~Eval('channel'),
'readonly': True
})
2022-03-20 01:11:41 +01:00
channel_paymode = fields.Selection(PAYMENT_METHOD_CHANNEL,
'Channel Paymode', states={'invisible': ~Eval('channel')},
2022-03-20 14:46:25 +01:00
depends=['channel']
2022-03-20 01:11:41 +01:00
)
2022-03-24 18:17:50 +01:00
invoices = fields.Function(fields.Many2Many('account.invoice',
None, None, 'Invoices'), 'get_invoices')
2022-05-18 00:34:42 +02:00
extra_commissions = fields.Many2Many('hotel.booking-channel.commission',
'booking', 'commission', 'Channel Commission', domain=[
('channel', '=', Eval('channel'))
])
2022-07-11 02:56:01 +02:00
stock_moves = fields.Function(fields.One2Many('stock.move', 'origin', 'Moves',
readonly=True), 'get_stock_moves')
2022-07-24 14:17:39 +02:00
channel_icon = fields.Function(fields.Char('Channel Icon'),
'get_channel_icon')
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'),
('offer', 'cancelled'),
2021-10-09 19:00:26 +02:00
('confirmed', 'offer'),
2020-06-28 04:35:07 +02:00
('cancelled', 'offer'),
2020-04-16 14:45:13 +02:00
('confirmed', 'cancelled'),
2021-10-09 19:00:26 +02:00
('confirmed', 'not_show'),
2022-05-17 17:14:08 +02:00
('not_show', 'confirmed'),
2020-04-16 14:45:13 +02:00
))
cls._buttons.update({
'select_rooms': {
2021-10-09 19:49:43 +02:00
'invisible': Eval('state').in_(['finished', 'cancelled', 'not_show']),
},
2022-03-24 18:17:50 +01:00
'update_holder': {
'invisible': Eval('state').in_(['finished', 'cancelled', 'not_show']),
2020-04-16 14:45:13 +02:00
},
'cancel': {
2021-10-09 19:49:43 +02:00
'invisible': Eval('state').in_(
['cancelled', 'not_show', 'finished'])
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'])
2020-04-16 14:45:13 +02:00
},
'confirm': {
2021-10-09 19:00:26 +02:00
'invisible': ~Eval('state').in_(['offer', 'not_show'])
2020-04-16 14:45:13 +02:00
},
2021-10-09 19:00:26 +02:00
'not_show': {
'invisible': Eval('state') != 'confirmed',
2020-06-28 04:35:07 +02:00
},
2022-07-08 06:51:11 +02:00
'do_payment': {
'invisible': Eval('state').in_(['offer', 'cancelled']),
},
2021-02-02 18:39:37 +01:00
'send_email': {
'invisible': Eval('state') != 'confirmed'
},
2021-10-16 05:54:52 +02:00
'bill': {
2022-03-19 15:46:34 +01:00
'invisible': ~Eval('state').in_(['confirmed', 'not_show']),
2021-10-16 05:54:52 +02:00
},
2022-03-19 17:40:54 +01:00
'bill_to_channel': {
'invisible': ~Eval('channel'),
2022-03-23 00:37:09 +01:00
'readonly': (Eval('channel_paymode') != 'ota_collect') | Eval('channel_invoice', True),
2022-03-19 17:40:54 +01:00
},
2020-04-16 14:45:13 +02:00
})
2022-07-24 14:17:39 +02:00
@staticmethod
def default_payment_term():
Config = Pool().get('hotel.configuration')
config = Config.get_configuration()
if config.payment_term:
return config.payment_term.id
def get_channel_icon(self, name):
2022-07-24 17:02:51 +02:00
name_icon = 'hotel-channel-house'
if self.channel and self.channel.code:
name_icon = f'hotel-channel-{self.channel.code}'
return name_icon
2022-07-24 14:17:39 +02:00
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()
2022-07-11 02:56:01 +02:00
default['moves'] = None
return super(Booking, cls).copy(bookings, default=default)
2021-02-02 18:39:37 +01:00
2022-07-11 02:56:01 +02:00
def get_stock_moves(self, name=None):
moves = []
2022-07-13 23:55:07 +02:00
for folio in self.lines:
for charge in folio.charges:
if charge.move:
moves.append(charge.move.id)
2022-07-11 02:56:01 +02:00
return moves
2022-03-23 00:37:09 +01:00
@classmethod
def search_rec_name(cls, name, clause):
_, operator, value = clause
if operator.startswith('!') or operator.startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
domain = [
bool_op,
('number', operator, value),
('ota_booking_code', operator, value),
('party.name', operator, value),
('contact', operator, value),
]
return domain
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
2021-11-24 17:39:13 +01:00
@staticmethod
def default_plan():
return 'bed_breakfast'
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
2022-03-24 18:17:50 +01:00
def get_invoices(self, name=None):
res = []
for folio in self.lines:
if folio.invoice_line:
2022-05-09 18:01:23 +02:00
res.append(folio.invoice_line.invoice.id)
2022-03-24 18:17:50 +01:00
for charge in folio.charges:
if charge.invoice_line:
2022-05-09 18:01:23 +02:00
res.append(charge.invoice_line.invoice.id)
return list(set(res))
2022-03-24 18:17:50 +01:00
2020-04-16 14:45:13 +02:00
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
2022-03-23 00:37:09 +01:00
@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
2022-07-23 01:22:03 +02:00
def is_person(self):
if self.party.type_document in ('12', '13', '21', '22', '41', '42', '47'):
return True
2022-03-19 17:40:54 +01:00
@fields.depends('party', 'price_list', 'lines')
2020-04-16 14:45:13 +02:00
def on_change_party(self):
2022-03-19 17:40:54 +01:00
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:
2022-07-23 01:22:03 +02:00
if self.is_person():
2022-03-19 17:40:54 +01:00
folio.main_guest = self.party.id
@fields.depends('channel')
def on_change_channel(self):
2022-03-20 14:46:25 +01:00
if self.channel:
2022-07-23 01:07:58 +02:00
if self.channel.invoice_to == 'channel':
self.party = self.channel.agent.party.id
elif self.channel.invoice_to == 'customer':
self.party = None
2022-03-20 14:46:25 +01:00
self.channel_paymode = self.channel.payment_method
if self.channel.price_list:
self.price_list = self.channel.price_list
2021-02-02 18:39:37 +01:00
2020-04-16 14:45:13 +02:00
@classmethod
@ModelView.button_action('hotel.wizard_select_rooms')
2022-07-08 06:51:11 +02:00
def select_rooms(cls, records):
2020-04-16 14:45:13 +02:00
pass
@classmethod
2022-03-24 18:17:50 +01:00
@ModelView.button_action('hotel.wizard_update_holder')
2022-07-08 06:51:11 +02:00
def update_holder(cls, records):
pass
2022-07-08 06:51:11 +02:00
@classmethod
@ModelView.button_action('hotel.wizard_statement_payment_form')
def do_payment(cls, records):
2020-04-16 14:45:13 +02:00
pass
@classmethod
@ModelView.button
2020-06-28 04:35:07 +02:00
@Workflow.transition('offer')
def offer(cls, records):
2021-10-09 19:49:43 +02:00
pass
2020-04-16 14:45:13 +02:00
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
def cancel(cls, records):
2021-11-24 22:49:39 +01:00
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])
2020-04-16 14:45:13 +02:00
2020-06-28 04:35:07 +02:00
@classmethod
@ModelView.button
2021-10-09 19:00:26 +02:00
@Workflow.transition('not_show')
def not_show(cls, records):
2020-06-28 04:35:07 +02:00
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')
2022-07-22 17:36:13 +02:00
cls.create_channel_voucher(rec)
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()
2021-10-16 05:54:52 +02:00
@classmethod
@ModelView.button
def bill(cls, records):
2022-05-19 17:37:36 +02:00
# cls.bill_to_channel(records)
2021-10-16 05:54:52 +02:00
for rec in records:
cls.create_invoice(rec.lines)
2022-07-08 22:09:07 +02:00
# rec.add_payments_invoice()
2021-10-16 05:54:52 +02:00
cls.check_finished(records)
2022-07-22 17:36:13 +02:00
@classmethod
def create_channel_voucher(cls, bk):
Voucher = Pool().get('account.voucher')
# If ota_collect is paymode for folios
if not bk.channel or bk.channel_paymode != 'ota_collect':
return
payment_mode = bk.channel.payment_mode
party = bk.channel.agent.party
account_id = Voucher.get_account('receipt', payment_mode)
amount = bk.collect_amount - bk.channel_commission
lines = [{
'detail': f'OTA Code {bk.ota_booking_code}',
'amount': amount,
'amount_original': amount,
'account': party.account_receivable.id,
}]
to_create = {
'party': party.id,
'voucher_type': 'receipt',
'date': date.today(),
'description': f'Booking {bk.number}',
'payment_mode': payment_mode.id,
'account': account_id,
'journal': payment_mode.journal.id,
'lines': [('create', lines)],
'amount_to_pay': amount,
'method_counterpart': 'one_line',
# 'state': 'draft',
}
voucher, = Voucher.create([to_create])
Voucher.process([voucher])
cls.write([bk], {'vouchers': [('add', [voucher])]})
2022-03-19 17:40:54 +01:00
@classmethod
def bill_to_channel(cls, records):
for rec in records:
2022-03-20 01:11:41 +01:00
if rec.channel_invoice or not rec.channel or \
rec.channel.collection_mode != 'anticipated':
2022-03-19 17:40:54 +01:00
continue
cls.create_channel_invoice(rec)
2022-05-19 17:26:42 +02:00
# cls.create_channel_pay(rec)
2022-03-19 17:40:54 +01:00
2021-10-16 05:54:52 +02:00
@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'})
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})
2021-10-19 04:17:22 +02:00
@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()
2022-03-16 14:16:18 +01:00
account = config.customer_advance_account
2021-10-19 04:17:22 +02:00
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
2021-11-16 15:31:28 +01:00
elif invoice and line.account.id == invoice.account.id:
2021-10-19 04:17:22 +02:00
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)
2021-10-16 05:54:52 +02:00
@classmethod
def get_grouped_invoices(cls, folios, charges):
res = {}
for fo in folios:
2021-12-02 18:28:20 +01:00
if fo.booking.party:
party = fo.booking.party
2021-10-16 05:54:52 +02:00
else:
party = fo.main_guest
2021-11-25 04:52:03 +01:00
if not party:
raise UserError(gettext('hotel.msg_customer_is_required'))
2022-03-20 14:46:25 +01:00
2021-11-25 04:52:03 +01:00
bk = fo.booking
2022-03-19 17:40:54 +01:00
agent_id = bk.channel.agent.id if bk.channel else None
2021-10-16 05:54:52 +02:00
if party.id not in res.keys():
# Add room product to sale
2021-11-18 23:03:24 +01:00
reference = ''
if len(folios) == 1:
reference = fo.registration_card
else:
reference = ','.join(
2022-07-20 19:48:05 +02:00
[fo.registration_card for fo in folios if fo.registration_card or '']
2021-11-18 23:03:24 +01:00
)
2021-10-16 05:54:52 +02:00
res[party.id] = {
'party': party,
2021-11-25 04:52:03 +01:00
'currency': bk.currency.id,
2022-07-24 14:17:39 +02:00
'payment_term': bk.payment_term,
2022-03-16 14:16:18 +01:00
'number': bk.number,
2021-10-16 05:54:52 +02:00
'guests_qty': len(fo.guests) + 1,
2021-11-18 23:03:24 +01:00
'reference': reference,
2021-11-25 04:52:03 +01:00
'agent': agent_id,
2021-10-16 05:54:52 +02:00
'rooms': fo.room.name,
2022-05-19 18:38:10 +02:00
'ota_booking_code': bk.ota_booking_code or '',
2021-11-25 04:52:03 +01:00
'company': bk.company.id,
'price_list': bk.price_list.id if bk.price_list else None,
2021-10-16 05:54:52 +02:00
'add_default_charges': False,
2021-11-25 04:52:03 +01:00
'vouchers': bk.vouchers,
2021-10-16 05:54:52 +02:00
'lines': [{
'folios': [fo],
'description': fo.get_room_info(),
'quantity': fo.nights_quantity,
'product': fo.product,
'unit_price': fo.unit_price,
2022-03-16 14:16:18 +01:00
'origin': str(bk),
2021-11-25 04:52:03 +01:00
'taxes_exception': bk.taxes_exception,
2021-10-16 05:54:52 +02:00
}]
}
else:
res[party.id]['rooms'] += ' ' + fo.room.name
res[party.id]['lines'].append({
'folios': [fo],
'description': fo.get_room_info(),
'quantity': fo.nights_quantity,
2021-10-19 04:17:22 +02:00
'product': fo.product,
2021-10-16 05:54:52 +02:00
'unit_price': fo.unit_price,
2022-03-16 14:16:18 +01:00
'origin': str(bk),
2021-11-25 04:52:03 +01:00
'taxes_exception': bk.taxes_exception,
2021-10-16 05:54:52 +02:00
})
2021-11-28 07:32:38 +01:00
for charge in charges:
2022-03-20 14:46:25 +01:00
bk = charge.folio.booking
2022-07-22 17:38:42 +02:00
invoice_party = fo.booking.party
2022-07-07 15:31:42 +02:00
if charge.invoice_to:
2022-07-22 17:38:42 +02:00
invoice_party = charge.invoice_to
2022-07-07 15:31:42 +02:00
2022-07-22 19:08:06 +02:00
invoice_party_id = invoice_party.id
2021-11-28 07:32:38 +01:00
unit_price = bk.currency.round(charge.unit_price)
2022-03-20 14:46:25 +01:00
if invoice_party_id not in res.keys():
2022-07-22 17:38:42 +02:00
res[invoice_party.id] = {
'party': invoice_party,
'currency': bk.currency.id,
2022-07-24 14:17:39 +02:00
'payment_term': bk.payment_term,
2022-07-22 17:38:42 +02:00
'number': bk.number,
'reference': charge.folio.registration_card,
'lines': [],
2022-03-20 14:46:25 +01:00
}
2021-11-28 07:32:38 +01:00
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,
2022-03-16 14:16:18 +01:00
'origin': str(bk),
2021-11-28 07:32:38 +01:00
'taxes_exception': bk.taxes_exception,
})
2021-10-16 05:54:52 +02:00
return res
@classmethod
2022-03-19 17:40:54 +01:00
def _get_invoice_line(cls, invoice, line, record=None):
2021-10-16 05:54:52 +02:00
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'],
2022-03-16 14:16:18 +01:00
'origin': line['origin'],
2021-10-16 05:54:52 +02:00
}
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
2022-07-11 02:56:01 +02:00
def do_moves(self):
Move = Pool().get('stock.move')
moves = {}
to_create = []
for folo in self.lines:
for charge in folio.charges:
move = self.get_move(charge)
if move:
moves[line.id] = move
for m in moves:
moves[m].state = 'draft'
to_create.append(moves[m]._save_values)
Move.create(to_create)
Move.do(self.moves)
def get_move(self, charge):
'''
Return stock move for charge according a storage
'''
pool = Pool()
Move = pool.get('stock.move')
Date = pool.get('ir.date')
if not self.product or self.product.type != 'goods' and self.quantity <= 0:
return
if not charge.folio.storage:
return None
customer_location = self.party.customer_location
move = Move()
move.quantity = self.quantity
move.uom = self.product.default_uom
move.product = self.product
# Add on create charge take default storage from folio if isn not present
move.from_location = charge.folio.storage
move.to_location = customer_location
move.state = 'draft'
move.company = self.company
move.unit_price = self.unit_price
move.currency = self.company.currency
move.planned_date = self.date_service
move.origin = charge
return move
2021-10-16 05:54:52 +02:00
@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:
2022-03-20 01:11:41 +01:00
if not folio.invoice_line or not folio.to_invoice:
2021-10-16 05:54:52 +02:00
_folios.append(folio)
for charge in folio.charges:
if not charge.invoice_line and charge.to_invoice:
_charges.append(charge)
return _folios, _charges
2022-03-19 17:40:54 +01:00
@classmethod
def create_channel_pay(cls, bk):
2022-07-11 02:56:01 +02:00
pool = Pool()
Note = pool.get('account.note')
NoteLine = pool.get('account.note.line')
Invoice = pool.get('account.invoice')
Config = pool.get('account.voucher_configuration')
2022-03-19 17:40:54 +01:00
config = Config.get_configuration()
lines_to_create = []
2022-05-19 17:20:43 +02:00
commission = bk.currency.round(bk.channel_commission)
2022-03-19 17:40:54 +01:00
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,
2022-05-19 17:20:43 +02:00
'credit': commission,
2022-03-19 17:40:54 +01:00
'party': bk.channel.agent.party.id,
'account': bk.channel_invoice.account.id,
'description': bk.ota_booking_code,
}])
line2, = NoteLine.create([{
'note': note.id,
2022-05-19 17:20:43 +02:00
'debit': commission,
2022-03-19 17:40:54 +01:00
'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')
2022-05-19 17:26:42 +02:00
Folio = pool.get('hotel.folio')
2022-03-19 17:40:54 +01:00
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,
2022-03-20 14:46:25 +01:00
'number': bk.number,
2022-03-19 17:40:54 +01:00
}
invoice = cls._get_new_invoice(data)
invoice.on_change_invoice_type()
invoice.save()
for folio in bk.lines:
2022-03-20 01:11:41 +01:00
if folio.invoice_line:
continue
2022-03-19 17:40:54 +01:00
_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)
])
2022-05-19 17:26:42 +02:00
Folio.write([folio], {'invoice_line': line.id})
2022-03-19 17:40:54 +01:00
invoice.save()
invoice.update_taxes([invoice])
cls.write([bk], {'channel_invoice': invoice.id})
2022-05-19 16:06:22 +02:00
Invoice.validate([invoice])
2022-05-19 16:08:09 +02:00
invoice.save()
2022-05-19 16:57:49 +02:00
invoice, = Invoice.browse([invoice.id])
try:
Invoice.submit(invoices)
invoice.save()
2022-05-19 17:14:40 +02:00
except:
pass
try:
2022-05-19 16:57:49 +02:00
invoice = Invoice(invoice.id)
Invoice.post(invoices)
except:
pass
2022-03-19 17:40:54 +01:00
2021-10-16 05:54:52 +02:00
@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)
2022-07-08 22:09:07 +02:00
booking = folios[0].booking
2021-10-16 05:54:52 +02:00
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)
2021-11-18 23:03:24 +01:00
invoice.on_change_invoice_type()
2021-10-16 05:54:52 +02:00
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']:
2021-11-25 04:52:03 +01:00
taxes_ids = cls.get_taxes(
product, rec['party'], invoice.currency
)
2021-10-16 05:54:52 +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,
2022-03-16 14:16:18 +01:00
'origin': str(self),
2021-10-16 05:54:52 +02:00
}
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([
2022-03-19 17:40:54 +01:00
cls._get_invoice_line(invoice, _line)
2021-10-16 05:54:52 +02:00
])
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()
2021-11-28 07:32:38 +01:00
invoice.update_taxes([invoice])
2021-10-16 05:54:52 +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')
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']
2022-07-22 17:36:13 +02:00
# party is instance or id fixme
if type(party) == int:
party = Party(party)
2022-05-19 18:38:10 +02:00
ota_code = data.get('ota_booking_code', '')
2022-03-19 17:40:54 +01:00
if data.get('description'):
description = data['description']
else:
2022-05-19 18:38:10 +02:00
description = f"{data['number']} | {ota_code} | {data.get('rooms')}"
2021-10-16 05:54:52 +02:00
reference = data.get('reference')
agent = None
if data.get('agent'):
agent = Agent(data['agent'])
2022-03-20 14:46:25 +01:00
journal, = Journal.search([('type', '=', 'revenue')], limit=1)
2021-10-16 05:54:52 +02:00
2021-12-02 20:50:17 +01:00
address = party.address_get(type='invoice')
2021-10-16 05:54:52 +02:00
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',
2022-03-24 18:17:50 +01:00
invoice_type='1',
2021-10-16 05:54:52 +02:00
invoice_address=address.id,
)
2021-10-06 02:27:25 +02:00
def update_folio(self, state):
2022-03-25 16:03:19 +01:00
Folio = Pool().get('hotel.folio')
Folio.write(list(self.lines), {'registration_state': state})
2020-04-16 14:45:13 +02:00
2021-10-16 05:54:52 +02:00
@classmethod
def get_context_price(cls,
2021-11-12 14:22:36 +01:00
product, party=None, currency=None, _date=None, price_list=None,
taxes_exception=False):
2020-04-16 14:45:13 +02:00
context = {}
2021-10-16 05:54:52 +02:00
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
2020-04-16 14:45:13 +02:00
context['uom'] = product.template.default_uom.id
# Set taxes before unit_price to have taxes in context of sale price
taxes = []
pattern = {}
2021-10-16 05:54:52 +02:00
if not taxes_exception and party:
2020-04-16 14:45:13 +02:00
for tax in product.customer_taxes_used:
2021-10-16 05:54:52 +02:00
if party and party.customer_tax_rule:
tax_ids = party.customer_tax_rule.apply(tax, pattern)
2020-04-16 14:45:13 +02:00
if tax_ids:
taxes.extend(tax_ids)
continue
taxes.append(tax.id)
2021-10-16 05:54:52 +02:00
if party and party.customer_tax_rule:
tax_ids = party.customer_tax_rule.apply(None, pattern)
2020-04-16 14:45:13 +02:00
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')
2022-07-08 22:09:07 +02:00
Payments = Pool().get('hotel.booking-statement.line')
2022-07-22 17:36:13 +02:00
advances = Advance.search([('booking', '=', self.id)])
res = sum([ad.voucher.amount_to_pay for ad in advances])
2022-07-08 22:09:07 +02:00
payments = sum([pay.amount for pay in self.payments])
2022-07-24 14:17:39 +02:00
advance_by_channel = _ZERO
if self.channel and self.channel_paymode == 'ota_collect':
advance_by_channel = self.channel_commission
return res + payments + advance_by_channel
2021-02-02 18:39:37 +01:00
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)
2022-03-22 05:30:24 +01:00
return 0
2021-07-31 00:44:32 +02:00
2022-07-22 17:36:13 +02:00
def get_collect_amount(self, name):
return sum(folio.room_amount for folio in self.lines)
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_tax_amount(self, name):
Tax = Pool().get('account.tax')
2021-10-16 05:54:52 +02:00
Booking = Pool().get('hotel.booking')
2020-04-16 14:45:13 +02:00
res = _ZERO
for line in self.lines:
2021-10-16 05:54:52 +02:00
taxes_ids = Booking.get_taxes(line.product)
2020-04-16 14:45:13 +02:00
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:
2022-03-22 20:25:49 +01:00
res += line.total_amount
2020-04-16 14:45:13 +02:00
return res
2022-03-19 06:21:49 +01:00
def get_channel_commission(self, name):
2022-05-18 21:17:40 +02:00
res = [line.commission_amount for line in self.lines if line.commission_amount]
total_commission = sum(res)
base_comm = [folio.on_change_with_room_amount() for folio in self.lines]
for comm in self.extra_commissions:
extras = sum(base_comm) * Decimal(comm.commission / 100)
res.append(extras)
2022-05-22 08:52:05 +02:00
return self.currency.round(sum(res))
2020-04-16 14:45:13 +02:00
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
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)
2022-03-22 05:30:24 +01:00
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)
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-11-12 14:22:36 +01:00
'Select 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')
2021-11-25 05:05:39 +01:00
unit_price = fields.Numeric('Unit Price', digits=(16, 4), required=True)
2020-04-16 14:45:13 +02:00
@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
2021-11-25 05:05:39 +01:00
@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
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 = []
product = self.start.accommodation
for room in self.start.rooms:
values = {
'booking': booking.id,
'product': product.id,
2022-03-23 00:37:09 +01:00
'reference': booking.ota_booking_code,
'contact': booking.contact,
2020-04-16 14:45:13 +02:00
'room': room.id,
'arrival_date': self.start.arrival_date,
'departure_date': self.start.departure_date,
2021-11-25 05:05:39 +01:00
'unit_price': self.start.unit_price,
2020-04-16 14:45:13 +02:00
}
2022-07-23 01:22:03 +02:00
if booking.party and booking.is_person():
2020-04-16 14:45:13 +02:00
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,
}])
2022-07-08 06:51:11 +02:00
class BookingStatementLine(ModelSQL):
'Booking - Statement Line'
__name__ = 'hotel.booking-statement.line'
_table = 'hotel_booking_statement_line_rel'
booking = fields.Many2One('hotel.booking', 'Booking',
ondelete='CASCADE', select=True, required=True)
statement_line = fields.Many2One('account.statement.line', 'Statement Line',
ondelete='RESTRICT', required=True)
2020-04-16 14:45:13 +02:00
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)
2021-10-11 19:03:43 +02:00
dom = [['OR', [
2020-04-16 14:45:13 +02:00
('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 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 = {
2021-10-19 23:38:04 +02:00
'ids': [],
2020-04-16 14:45:13 +02:00
'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')
2021-10-19 23:38:04 +02:00
User = pool.get('res.user')
2020-04-16 14:45:13 +02:00
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-10-19 23:38:04 +02:00
folios = Folio.search([
('arrival_date', '<=', start_date),
2022-06-08 19:49:19 +02:00
('departure_date', '>', start_date),
('registration_state', 'in', ['check_in', 'check_out']),
2020-04-16 14:45:13 +02:00
])
def _get_default_room(r):
res = {
'room': r.name,
'guest': None,
'num_guest': None,
'party': None,
'arrival': None,
'departure': None,
'booking': None,
2021-10-19 23:38:04 +02:00
'registration_card': None,
'amount': 0,
'registration_state': None,
2022-05-17 17:14:08 +02:00
'total_balance': 0,
2020-04-16 14:45:13 +02:00
}
return res
rooms_map = {room.id: _get_default_room(room) for room in all_rooms}
occupancy_rooms = 0
2022-05-17 17:14:08 +02:00
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,
2020-04-16 14:45:13 +02:00
})
occupancy_rooms += 1
if all_rooms:
2021-10-19 23:38:04 +02:00
occupancy_rate = (float(len(folios)) / len(all_rooms)) * 100
2020-04-16 14:45:13 +02:00
else:
occupancy_rate = 0
2021-10-19 23:38:04 +02:00
report_context['records'] = rooms_map.values()
2020-04-16 14:45:13 +02:00
report_context['occupancy_rate'] = occupancy_rate
report_context['occupancy_rooms'] = occupancy_rooms
2022-05-17 17:14:08 +02:00
report_context['company'] = Company(data['company'])
2020-04-16 14:45:13 +02:00
report_context['date'] = data['date']
2021-10-19 23:38:04 +02:00
report_context['user'] = User(Transaction().user).rec_name
2020-04-16 14:45:13 +02:00
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
2022-05-18 21:17:40 +02:00
report_context['company'] = Company(data['company'])
2020-04-16 14:45:13 +02:00
report_context['date'] = data['date']
return report_context
2021-10-16 05:54:52 +02:00
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()
2022-03-24 18:17:50 +01:00
class UpdateHolderStart(ModelView):
'Update Holder Start'
__name__ = 'hotel.update_holder.start'
name = fields.Char('Name', required=True)
sex = fields.Selection([
2022-03-25 23:37:55 +01:00
('', ''),
2022-03-24 18:17:50 +01:00
('male', 'Male'),
('female', 'Female'),
2022-03-25 23:37:55 +01:00
], 'Sex')
email = fields.Char('Email', required=True)
mobile = fields.Char('Mobile')
2022-03-25 22:06:00 +01:00
phone = fields.Char('Phone')
2022-03-24 18:17:50 +01:00
birthday = fields.Date('Birthday')
2022-03-25 23:37:55 +01:00
nationality = fields.Many2One('party.nationality', 'Nationality')
2022-03-24 18:17:50 +01:00
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')
2022-03-25 23:37:55 +01:00
subdivision = fields.Many2One('party.department_code', 'Subdivision')
city = fields.Many2One('party.city_code', 'City', domain=[
('department', '=', Eval('subdivision'))
])
2022-03-24 18:17:50 +01:00
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')
2022-03-25 21:28:29 +01:00
customer = fields.Many2One('party.party', 'Party')
2022-03-24 18:17:50 +01:00
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')
2022-05-22 08:52:05 +02:00
main_guest = fields.Boolean('Main Guest')
2022-07-13 23:55:07 +02:00
vehicle_plate = fields.Char('Vehicle Plate')
2022-03-24 18:17:50 +01:00
2022-07-09 08:03:58 +02:00
@fields.depends('id_number','name', 'sex', 'email', 'mobile',
'visa_number', 'visa_date', 'address', 'birthday', 'nationality')
def on_change_id_number(self):
if self.id_number:
2022-07-18 05:37:35 +02:00
pool = Pool()
Party = pool.get('party.party')
Guest = pool.get('hotel.folio.guest')
2022-07-09 08:03:58 +02:00
parties = Party.search([
('id_number', '=', self.id_number)
])
2022-07-18 05:37:35 +02:00
if not parties:
parties = Guest.search([
('id_number', '=', self.id_number)
])
if not parties:
return
address = ''
country = None
subdivision = None
party = parties[0]
if hasattr(party, 'addresses') and party.addresses:
_address = party.addresses[0]
address = _address.street
country = _address.country_code and _address.country_code.id
subdivision = _address.department_code and _address.department_code.id
self.name = party.name
self.sex = party.sex
self.email = party.email
self.mobile = party.mobile
self.visa_number = party.visa_number
self.visa_date = party.visa_date
self.birthday = party.birthday
self.nationality = party.nationality and party.nationality.id
self.country = country
self.subdivision = subdivision
self.address = address
2022-07-09 08:03:58 +02:00
2022-03-24 18:17:50 +01:00
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 = {}
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,
2022-03-25 21:28:29 +01:00
'mobile': party.mobile,
2022-03-24 18:17:50 +01:00
'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
2022-03-25 23:37:55 +01:00
res['subdivision'] = address.department_code.id if address.department_code else None
2022-05-17 23:14:47 +02:00
elif booking.contact:
res = {
'name': booking.contact.upper()
}
2022-03-24 18:17:50 +01:00
return res
2022-03-25 21:28:29 +01:00
def _set_cms(self, action, rec, email, mobile=None, phone=None):
2022-03-24 18:17:50 +01:00
cms = [] # contact_mechanisms
2022-03-25 21:28:29 +01:00
if mobile:
cms.append({'type': 'mobile', 'value': mobile})
2022-03-24 18:17:50 +01:00
if email:
cms.append({'type': 'email', 'value': email})
2022-03-25 21:28:29 +01:00
if phone:
cms.append({'type': 'phone', 'value': phone})
2022-03-24 18:17:50 +01:00
if cms:
rec['contact_mechanisms'] = [(action, cms)]
return
def transition_update(self):
pool = Pool()
Booking = pool.get('hotel.booking')
Folio = pool.get('hotel.folio')
2022-06-09 04:33:03 +02:00
Guest = pool.get('hotel.folio.guest')
2022-03-24 18:17:50 +01:00
Party = pool.get('party.party')
Address = pool.get('party.address')
2022-03-25 23:37:55 +01:00
CM = pool.get('party.contact_mechanism')
2022-03-24 18:17:50 +01:00
active_id = Transaction().context.get('active_id', False)
edit = True
booking = Booking(active_id)
_party = self.start
rec_company = {}
to_folio = {}
2022-07-13 23:55:07 +02:00
if _party.vehicle_plate:
to_folio['vehicle_plate'] = _party.vehicle_plate
2022-05-22 08:52:05 +02:00
nationality_id = _party.nationality.id if _party.nationality else None
2022-03-24 18:17:50 +01:00
rec = {
'name': _party.name.upper(),
2022-05-22 08:52:05 +02:00
'nationality': nationality_id,
2022-03-24 18:17:50 +01:00
'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
2022-03-25 23:37:55 +01:00
subdivision_code = _party.subdivision.id if _party.subdivision else None
2022-03-24 18:17:50 +01:00
street = _party.address.upper() if _party.address else ''
address = {}
address['country_code'] = country_code
address['city_code'] = city_code
2022-03-25 23:37:55 +01:00
address['department_code'] = subdivision_code
2022-03-24 18:17:50 +01:00
address['street'] = street
2022-07-09 08:03:58 +02:00
party = None
2022-03-24 18:17:50 +01:00
if not booking.party:
edit = False
parties = Party.search([
('id_number', '=', _party.id_number),
])
2022-07-09 08:03:58 +02:00
if not parties:
self._set_cms('create', rec, _party.email, _party.mobile, _party.phone)
rec['addresses'] = [('create', [address])]
# raise UserError('Este cliente ya existe!')
else:
party = parties[0]
edit = True
2022-03-24 18:17:50 +01:00
else:
2022-07-09 08:03:58 +02:00
party = booking.party
if party:
if party.addresses:
Address.write(list(party.addresses), address)
2022-03-24 18:17:50 +01:00
else:
Address.create([address])
2022-03-25 23:37:55 +01:00
cms_add = {}
if _party.mobile:
cms_add['mobile'] = _party.mobile
cms_add['phone'] = _party.phone
cms_add['email'] = _party.email
2022-07-09 08:03:58 +02:00
if party.contact_mechanisms:
for cm in party.contact_mechanisms:
2022-03-25 23:37:55 +01:00
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')
2022-03-24 18:17:50 +01:00
cm.save()
2022-03-25 23:37:55 +01:00
if cms_add:
for (key, value) in cms_add.items():
2022-03-27 00:13:22 +01:00
if not value:
continue
2022-07-09 08:03:58 +02:00
cm = CM(party=party.id, type=key, value=value)
2022-03-25 23:37:55 +01:00
cm.save()
2022-03-24 18:17:50 +01:00
else:
2022-03-25 22:23:33 +01:00
self._set_cms('create', rec, _party.email, _party.mobile, _party.phone)
2022-03-24 18:17:50 +01:00
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(
2022-03-25 21:28:29 +01:00
'create',
rec_,
_party.customer_email,
phone=_party.customer_phone,
2022-03-24 18:17:50 +01:00
)
party, = Party.create([rec_])
if edit:
2022-07-09 08:03:58 +02:00
Party.write([party], rec)
if not booking.party:
booking.party = party.id
2022-03-24 18:17:50 +01:00
else:
party, = Party.create([rec])
Booking.write([booking], {'party': party.id})
2022-07-09 08:03:58 +02:00
booking.save()
2022-06-09 04:33:03 +02:00
if _party.type_document != '31':
2022-05-17 23:14:47 +02:00
for folio in booking.lines:
if not folio.main_guest:
folio.main_guest = party.id
folio.save()
2022-06-09 04:33:03 +02:00
_ = rec.pop('contact_mechanisms', None)
_ = rec.pop('addresses', None)
rec['folio'] = folio.id
rec['main_guest'] = True
rec['email'] = _party.email
rec['mobile'] = _party.mobile
Guest.create([rec])
2022-05-17 23:14:47 +02:00
break
2022-03-24 18:17:50 +01:00
for folio in booking.lines:
Folio.write([folio], to_folio)
return 'end'
2022-03-30 20:22:01 +02:00
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'
2022-04-01 06:14:57 +02:00
@classmethod
def get_location(self, party, field):
for address in party.addresses:
if hasattr(address, field):
value = getattr(address, field + '_code')
if value:
return value
2022-03-30 20:22:01 +02:00
@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')
2022-03-31 06:37:56 +02:00
Room = pool.get('hotel.room')
User = pool.get('res.user')
rooms = Room.search([])
2022-03-30 20:22:01 +02:00
total_rooms = len(rooms)
2022-03-31 06:37:56 +02:00
delta_days = (data['end_date'] - data['start_date']).days + 1
2022-03-30 20:22:01 +02:00
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 = {}
2022-03-31 06:37:56 +02:00
rooms_occupied = []
2022-03-30 20:22:01 +02:00
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,
}
2022-03-31 06:37:56 +02:00
local_guests = []
foreign_guests = []
total_income = []
rooms_saled = []
guests_by_country = {}
guests_by_city = {}
2022-03-30 20:22:01 +02:00
for folio in folios:
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)
2022-03-31 06:37:56 +02:00
total_income.append(folio.total_amount)
2022-06-08 15:30:16 +02:00
# parties = [guest.party for guest in folio.guests]
for guest in folio.guests:
2022-04-01 06:14:57 +02:00
if not guest or not guest.nationality:
2022-03-31 06:37:56 +02:00
continue
2022-04-01 06:14:57 +02:00
# print('guest ', guest, folio.booking.number)
2022-03-31 06:37:56 +02:00
if guest.nationality.name == 'COLOMBIA':
2022-06-08 15:30:16 +02:00
city = cls.get_location(folio.main_guest, 'city')
2022-03-31 06:37:56 +02:00
local_guests.append(1)
2022-04-01 06:14:57 +02:00
if not city:
continue
if city not in guests_by_city.keys():
guests_by_city[city] = {
'name': city.name,
2022-03-31 06:37:56 +02:00
'persons': [],
2022-04-01 06:14:57 +02:00
'nights': [],
2022-03-31 06:37:56 +02:00
}
2022-04-01 06:14:57 +02:00
guests_by_city[city]['persons'].append(1)
guests_by_city[city]['nights'].append(folio.nights_quantity)
2022-03-31 06:37:56 +02:00
else:
2022-06-08 15:30:16 +02:00
country = cls.get_location(folio.main_guest, 'country')
2022-03-31 06:37:56 +02:00
foreign_guests.append(1)
2022-04-01 06:14:57 +02:00
if not country:
continue
if country not in guests_by_country.keys():
guests_by_country[country] = {
'name': country.name,
2022-03-31 06:37:56 +02:00
'persons': [],
2022-04-01 06:14:57 +02:00
'nights': [],
2022-03-31 06:37:56 +02:00
}
2022-04-01 06:14:57 +02:00
guests_by_country[country]['persons'].append(1)
guests_by_country[country]['nights'].append(folio.nights_quantity)
2022-03-30 20:22:01 +02:00
for k, v in channels.items():
2022-03-31 06:37:56 +02:00
room_qty = sum(v['room_qty'])
income = sum(v['income'])
v['room_qty'] = room_qty
v['room_rate'] = room_qty / (total_rooms * delta_days)
2022-03-30 20:22:01 +02:00
v['num_adults'] = sum(v['num_adults'])
v['num_children'] = sum(v['num_children'])
2022-03-31 06:37:56 +02:00
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)
2022-04-01 06:14:57 +02:00
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
2022-03-31 06:37:56 +02:00
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)
2022-04-01 06:14:57 +02:00
report_context['gross_occupancy'] = sum(rooms_occupied) / available_nights
report_context['net_occupancy'] = sum(rooms_saled) / available_nights
2022-03-31 06:37:56 +02:00
report_context['total_income'] = sum(total_income)
report_context['local_guests'] = sum(local_guests)
report_context['foreign_guests'] = sum(foreign_guests)
2022-04-01 06:14:57 +02:00
report_context['available_nights'] = available_nights
report_context['available_beds'] = available_beds
2022-03-31 06:37:56 +02:00
report_context['average_price'] = average_price
report_context['guests_by_country'] = guests_by_country.values()
report_context['guests_by_city'] = guests_by_city.values()
2022-04-01 06:14:57 +02:00
report_context['company'] = Company(data['company'])
2022-03-31 06:37:56 +02:00
user_id = Transaction().context.get('user')
report_context['user'] = User(user_id)
2022-03-30 20:22:01 +02:00
return report_context
2022-05-18 00:34:42 +02:00
class BookingChannelCommision(ModelSQL):
'Booking Channel Commision'
__name__ = 'hotel.booking-channel.commission'
_table = 'hotel_booking_channel_commission_rel'
commission = fields.Many2One('hotel.channel.commission', 'Channel Commission',
ondelete='CASCADE', select=True, required=True)
booking = fields.Many2One('hotel.booking', 'Booking', ondelete='RESTRICT',
required=True)
2022-07-08 06:51:11 +02:00
class StatementPaymentForm(ModelView):
'Statement Payment Form'
__name__ = 'sale_shop.payment_form.start'
statement = fields.Many2One('account.statement', 'Statement',
required=True, domain=['OR', [
('create_uid.login', '=', Eval('user')),
('state', '=', 'draft')
], [
Eval('user') == 'admin',
('state', '=', 'draft'),
]])
amount_to_pay = fields.Numeric('Amount to Pay', required=True,
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'])
currency_digits = fields.Integer('Currency Digits')
voucher = fields.Char('Voucher', states={
'required': Bool(Eval('require_voucher')),
}, depends=['require_voucher'])
party = fields.Many2One('party.party', 'Party')
user = fields.Many2One('res.user', 'User', states={
'readonly': True
})
require_voucher = fields.Boolean('Require Voucher',
depends=['statement'])
@classmethod
def default_require_voucher(cls):
return False
@classmethod
def default_user(cls):
user = Pool().get('res.user')(Transaction().user)
return user.id
@fields.depends('statement', 'require_voucher')
def on_change_with_require_voucher(self):
if self.statement:
return self.statement.journal.require_voucher
return False
class WizardStatementPayment(Wizard):
'Wizard Statement Payment'
__name__ = 'sale_shop.payment_form'
start = StateView('sale_shop.payment_form.start',
'hotel.statement_payment_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Pay', 'pay_', 'tryton-ok', default=True),
])
pay_ = StateTransition()
def default_start(self, fields):
pool = Pool()
Booking = pool.get('hotel.booking')
User = pool.get('res.user')
booking = Booking(Transaction().context['active_id'])
user = User(Transaction().user)
2022-07-08 22:36:58 +02:00
if not booking.party:
raise UserError(gettext(
'hotel.msg_missing_party_holder'))
2022-07-24 14:17:39 +02:00
party = None
if booking.channel and booking.channel_paymode == 'ota_collect':
for folio in self.lines:
if folio.main_guest != booking.channel.agent.party:
party = folio.main_guest
break
else:
party = booking.party
if not party:
raise UserError(gettext('hotel.msg_missing_customer'))
2022-07-08 06:51:11 +02:00
return {
'currency_digits': 2,
2022-07-24 14:17:39 +02:00
'party': party.id,
'amount_to_pay': booking.pending_to_pay
2022-07-08 06:51:11 +02:00
}
def transition_pay_(self):
pool = Pool()
User = pool.get('res.user')
user = User(Transaction().user)
Booking = pool.get('hotel.booking')
Statement = pool.get('account.statement')
StatementLine = pool.get('account.statement.line')
BookingStatementLine = pool.get('hotel.booking-statement.line')
active_id = Transaction().context.get('active_id', False)
booking = Booking(active_id)
form = self.start
try:
account = form.party.account_receivable.id
except:
raise UserError(gettext(
'sale_shop.party_without_account_receivable'))
if form.amount_to_pay:
line = StatementLine(
statement=form.statement.id,
date=date.today(),
amount=form.amount_to_pay,
party=form.party.id,
account=account,
2022-07-08 22:36:58 +02:00
number='',
description=booking.number,
2022-07-08 06:51:11 +02:00
voucher=self.start.voucher,
)
line.save()
line.create_move()
BookingStatementLine.create([{
'booking': booking.id,
'statement_line': line.id,
}])
booking.save()
return 'end'