2648 lines
93 KiB
Python
2648 lines
93 KiB
Python
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
|
|
from decimal import Decimal
|
|
from datetime import timedelta, datetime, date
|
|
from collections import OrderedDict
|
|
import calendar
|
|
|
|
from trytond.model import ModelView, Workflow, ModelSQL, fields
|
|
from trytond.pyson import Eval, Bool
|
|
from trytond.pool import Pool
|
|
from trytond.report import Report
|
|
from trytond.wizard import (
|
|
Wizard, StateView, StateAction, Button, StateTransition, StateReport
|
|
)
|
|
from trytond.transaction import Transaction
|
|
from trytond.exceptions import UserError
|
|
from trytond.i18n import gettext
|
|
from .constants import (
|
|
REGISTRATION_STATE, COMPLEMENTARY, INVOICE_STATES, TYPE_DOCUMENT, PLAN,
|
|
)
|
|
from .room import ROOM_STATUS
|
|
from .siat import send_siat
|
|
|
|
|
|
STATES_CHECKIN = {
|
|
'readonly': Eval('registration_state').in_(
|
|
['check_in', 'no_show', 'cancelled']
|
|
),
|
|
'required': Eval('registration_state') == 'check_in',
|
|
}
|
|
|
|
|
|
STATES_OP = {
|
|
'readonly': Eval('state').in_(['check_out', 'done', 'cancelled'])
|
|
}
|
|
|
|
STATES_MNT = {'readonly': Eval('state') != 'draft'}
|
|
_ZERO = Decimal('0')
|
|
ROUND_TWO = Decimal('0.01')
|
|
|
|
|
|
class Folio(ModelSQL, ModelView):
|
|
'Folio'
|
|
__name__ = 'hotel.folio'
|
|
STATES = {
|
|
'required': Eval('registration_state') == 'check_in',
|
|
'readonly': Eval('registration_state') == 'check_in',
|
|
}
|
|
_CHECKOUT = {
|
|
'readonly': Eval('registration_state').in_(
|
|
['check_out', 'no_show', 'cancelled']
|
|
),
|
|
'required': Eval('registration_state') == 'check_in',
|
|
}
|
|
booking = fields.Many2One('hotel.booking', 'Booking', ondelete='CASCADE')
|
|
reference = fields.Char('Reference', states={
|
|
'readonly': Eval('registration_state').in_(['check_in', 'check_out'])
|
|
})
|
|
registration_card = fields.Char('Registration Card', readonly=True,
|
|
help="Unique sequence for card guest registration.")
|
|
room = fields.Many2One('hotel.room', 'Room', states=_CHECKOUT)
|
|
arrival_date = fields.Date('Arrival Date', required=True,
|
|
states={
|
|
'readonly': Eval('registration_state').in_(['check_out', 'check_in'])
|
|
})
|
|
departure_date = fields.Date('Departure Date', required=True,
|
|
states={
|
|
'readonly': Eval('registration_state').in_(['check_out', 'check_in'])
|
|
})
|
|
product = fields.Many2One('product.product', 'Product',
|
|
domain=[
|
|
('template.type', '=', 'service'),
|
|
('template.kind', '=', 'accommodation'),
|
|
], required=True, states={'readonly': True})
|
|
unit_price = fields.Numeric('Unit Price', digits=(16, 2),
|
|
states={
|
|
'required': Bool(Eval('product')),
|
|
'readonly': Eval('registration_state') == 'check_out',
|
|
})
|
|
unit_price_w_tax = fields.Function(fields.Numeric('Unit Price w Tax',
|
|
digits=(16, 2)), 'get_unit_price_w_tax')
|
|
uom = fields.Many2One('product.uom', 'UOM', readonly=True)
|
|
main_guest = fields.Many2One('party.party', 'Main Guest',
|
|
states={'readonly': True})
|
|
contact = fields.Char('Contact', states={
|
|
'readonly': Eval('registration_state').in_(['check_in', 'check_out'])}
|
|
)
|
|
party_holder = fields.Function(fields.Many2One('party.party',
|
|
'Party Holder'), 'get_party_holder')
|
|
num_children = fields.Function(fields.Integer('No. Children'),
|
|
'get_num_children')
|
|
nights_quantity = fields.Integer('Nights', states={'readonly': True},
|
|
depends=['arrival_date', 'departure_date'])
|
|
host_quantity = fields.Integer('Host', states=STATES_CHECKIN)
|
|
estimated_arrival_time = fields.Time('Estimated Arrival Time', states={
|
|
'readonly': Eval('registration_state').in_(
|
|
['check_out', 'no_show', 'cancelled']
|
|
)})
|
|
unit_digits = fields.Function(fields.Integer('Unit Digits'),
|
|
'get_unit_digits')
|
|
notes = fields.Text('Notes')
|
|
total_amount = fields.Function(fields.Numeric('Total Amount',
|
|
digits=(16, 2)), 'get_total_amount')
|
|
commission_amount = fields.Numeric('Commission Amount', digits=(16, 2),
|
|
depends=['nights_quantity', 'unit_price'], states={'readonly': True}
|
|
)
|
|
guests = fields.One2Many('hotel.folio.guest', 'folio', 'Guests',
|
|
states={'readonly': Eval('registration_state').in_(['check_out'])})
|
|
storage = fields.Many2One('stock.location', 'Storage',
|
|
domain=[('type', '=', 'storage')], states={
|
|
'readonly': Eval('registration_state') != 'check_in',
|
|
},
|
|
depends=['registration_state'])
|
|
registration_state = fields.Selection(REGISTRATION_STATE,
|
|
'Registration State', readonly=True)
|
|
registration_state_string = registration_state.translated(
|
|
'registration_state')
|
|
charges = fields.One2Many('hotel.folio.charge', 'folio', 'Charges',
|
|
states={
|
|
'readonly': ~Eval('registration_state').in_(
|
|
['pending', 'check_in']),
|
|
})
|
|
party = fields.Many2One('party.party', 'Party to Bill',
|
|
states={
|
|
'readonly': Eval('registration_state').in_(['check_out']),
|
|
})
|
|
invoice_line = fields.Many2One('account.invoice.line', 'Invoice Line')
|
|
to_invoice = fields.Boolean('To Invoice', states={
|
|
'invisible': Bool(Eval('invoice_line')),
|
|
}, depends=['invoice_line'],
|
|
help='Mark this checkbox if you want to invoice this item')
|
|
invoice = fields.Function(fields.Many2One('account.invoice', 'Invoice',
|
|
depends=['invoice_line']), 'get_invoice')
|
|
invoice_state = fields.Function(fields.Selection(
|
|
INVOICE_STATES, 'Invoice State'), 'get_invoice_state')
|
|
invoice_state_string = invoice_state.translated('invoice_state')
|
|
type_complementary = fields.Selection(COMPLEMENTARY, 'Type Complementary',
|
|
states={
|
|
'invisible': ~Bool(Eval('complementary')),
|
|
'required': Bool(Eval('complementary')),
|
|
})
|
|
breakfast_included = fields.Boolean('Breakfast Included', states={
|
|
'readonly': Eval('registration_state') != 'check_in',
|
|
})
|
|
room_amount = fields.Function(fields.Numeric('Room Amount',
|
|
digits=(16, 2), depends=['nights_quantity', 'unit_price']
|
|
), 'on_change_with_room_amount')
|
|
channel = fields.Function(fields.Many2One('hotel.channel', 'Channel'),
|
|
'get_channel')
|
|
num_children = fields.Function(fields.Integer('Num. Children'),
|
|
'get_num_guests')
|
|
num_adults = fields.Function(fields.Integer('Num. Adults'),
|
|
'get_num_guests')
|
|
stock_moves = fields.Function(fields.One2Many('stock.move', 'origin',
|
|
'Moves'), 'get_stock_moves')
|
|
vehicle_plate = fields.Char('Vehicle Plate')
|
|
room_status = fields.Function(fields.Selection(ROOM_STATUS,
|
|
'Room Status'), 'get_room_status')
|
|
payment_status = fields.Selection([
|
|
(None, ''),
|
|
('pending', 'Pending'),
|
|
('paid', 'Paid'),
|
|
], 'Acco. Payment Status', readonly=True)
|
|
payments = fields.One2Many('account.statement.line', 'source',
|
|
'Payments', readonly=True)
|
|
occupancy = fields.One2Many('hotel.folio.occupancy', 'folio', 'Occupancy',
|
|
readonly=True)
|
|
pending_accommodation = fields.Function(fields.Numeric('Pending to Pay',
|
|
digits=(16, 2)), 'get_pending_to_pay')
|
|
pending_charges = fields.Function(fields.Numeric('Pending to Pay',
|
|
digits=(16, 2)), 'get_pending_to_pay')
|
|
pending_total = fields.Function(fields.Numeric('Pending to Pay',
|
|
digits=(16, 2)), 'get_pending_to_pay')
|
|
total_advances = fields.Function(fields.Numeric('Total Advances',
|
|
digits=(16, 2)), 'get_total_advances')
|
|
pax = fields.Integer('PAX', states={
|
|
'readonly': Eval('registration_state').in_(
|
|
['check_out', 'no_show', 'cancelled']
|
|
)}, help="Number of persons in house setted manually.")
|
|
invoices = fields.Function(fields.Many2Many('account.invoice',
|
|
None, None, 'Invoices'), 'get_invoices')
|
|
charges_blocked = fields.Boolean('Charges Blocked')
|
|
taxes_exception = fields.Boolean('Taxes Exception')
|
|
plan = fields.Function(fields.Selection(PLAN, 'Commercial Plan'),
|
|
'get_plan')
|
|
group = fields.Function(fields.Boolean('Group'),
|
|
'get_group')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Folio, cls).__setup__()
|
|
cls._buttons.update({
|
|
'check_in': {
|
|
'invisible': Eval('registration_state').in_(
|
|
['check_in', 'check_out']
|
|
),
|
|
},
|
|
'check_out': {
|
|
'invisible': Eval('registration_state') != 'check_in',
|
|
},
|
|
'bill': {
|
|
'invisible': Eval('registration_state') != 'check_out',
|
|
},
|
|
'load_accommodation': {
|
|
'invisible': ~Eval('registration_state'),
|
|
},
|
|
'do_payment': {
|
|
'invisible': Eval('registration_state').in_([
|
|
'no_show', 'cancelled', 'pending'
|
|
]),
|
|
}
|
|
})
|
|
|
|
@staticmethod
|
|
def default_to_invoice():
|
|
return True
|
|
|
|
@staticmethod
|
|
def default_payment_status():
|
|
return 'pending'
|
|
|
|
@staticmethod
|
|
def default_registration_state():
|
|
return 'pending'
|
|
|
|
@staticmethod
|
|
def default_pax():
|
|
return 1
|
|
|
|
@classmethod
|
|
def delete(cls, records):
|
|
for folio in records:
|
|
for occ in folio.occupancy:
|
|
if occ.charge:
|
|
raise UserError(gettext('hotel.msg_folio_with_charges'))
|
|
super().delete(records)
|
|
|
|
@classmethod
|
|
@ModelView.button_action('hotel.wizard_statement_payment_form')
|
|
def do_payment(cls, records):
|
|
pass
|
|
|
|
def get_group(self, name=None):
|
|
if self.booking:
|
|
return self.booking.group
|
|
|
|
def get_total_advances(self, name=None):
|
|
res = []
|
|
for pay in self.payments:
|
|
res.append(pay.amount)
|
|
return sum(res)
|
|
|
|
def pending_occupancy(self, name=None):
|
|
res = []
|
|
for occ in self.occupancy:
|
|
if occ.charge:
|
|
continue
|
|
res.append({
|
|
'name': self.product.template.name,
|
|
'date': occ.occupancy_date,
|
|
'quantity': 1,
|
|
'unit_price': occ.unit_price_w_tax,
|
|
'amount': occ.unit_price_w_tax,
|
|
})
|
|
return res
|
|
|
|
def get_plan(self, name=None):
|
|
if self.booking:
|
|
return self.booking.plan
|
|
|
|
def _get_paid(self, kind):
|
|
res = []
|
|
for charge in self.charges:
|
|
if charge.status == 'paid' and charge.kind == kind:
|
|
res.append(charge.taxed_amount or 0)
|
|
return sum(res)
|
|
|
|
def get_pending_to_pay(self, name=None):
|
|
res = 0
|
|
if name == 'pending_accommodation':
|
|
res = self.get_total_accommodation()
|
|
charge_paid = self._get_paid('accommodation')
|
|
res = res - charge_paid
|
|
elif name == 'pending_charges':
|
|
res = self.get_total_products()
|
|
charge_paid = self._get_paid('product')
|
|
res = res - charge_paid
|
|
elif name == 'pending_total':
|
|
res = self.total_amount - self.total_advances
|
|
res = round(res, 2)
|
|
return res
|
|
|
|
def get_num_guests(self, name):
|
|
res = []
|
|
|
|
for guest in self.guests:
|
|
if name == 'num_children' and guest.type_guest == 'child':
|
|
res.append(1)
|
|
elif name == 'num_adults' and guest.type_guest == 'adult':
|
|
res.append(1)
|
|
return sum(res)
|
|
|
|
def get_party_holder(self, name=None):
|
|
if self.booking.party:
|
|
return self.booking.party.id
|
|
|
|
def get_rec_name(self, name=None):
|
|
if self.room:
|
|
name = f'{self.room.code}:'
|
|
if self.main_guest:
|
|
name = name + self.main_guest.name
|
|
return name
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
_, operator, value = clause
|
|
if operator.startswith('!') or operator.startswith('not '):
|
|
bool_op = 'AND'
|
|
else:
|
|
bool_op = 'OR'
|
|
domain = [
|
|
bool_op,
|
|
('main_guest', operator, value),
|
|
('registration_card', operator, value),
|
|
('product.name', operator, value),
|
|
('party.name', operator, value),
|
|
('booking.party.name', operator, value),
|
|
('reference', operator, value),
|
|
]
|
|
return domain
|
|
|
|
def get_invoices(self, name=None):
|
|
res = []
|
|
for charge in self.charges:
|
|
if charge.invoice_line:
|
|
res.append(charge.invoice_line.invoice.id)
|
|
return list(set(res))
|
|
|
|
@classmethod
|
|
def create(cls, values):
|
|
folios = super(Folio, cls).create(values)
|
|
for folio in folios:
|
|
folio.update_nights(folio.arrival_date, folio.departure_date)
|
|
folio.update_commission()
|
|
folio.save()
|
|
|
|
@classmethod
|
|
def get_current_folios(cls):
|
|
res = cls.search_read([
|
|
('registration_state', '=', 'check_in'),
|
|
('arrival_date', '<=', date.today()),
|
|
('departure_date', '>=', date.today()),
|
|
('charges_blocked', '=', False),
|
|
], fields_names=[
|
|
'booking.number',
|
|
'main_guest.name',
|
|
'main_guest.id_number',
|
|
'main_guest.phone',
|
|
'main_guest.account_receivable',
|
|
'main_guest.mobile',
|
|
'main_guest.account_payable',
|
|
'main_guest.address',
|
|
'main_guest.addresses',
|
|
'room.code',
|
|
'room.name',
|
|
'product.template.name',
|
|
'registration_state',
|
|
'arrival_date',
|
|
'departure_date',
|
|
])
|
|
return res
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def check_in(cls, records):
|
|
for record in records:
|
|
record.validate_check_in()
|
|
if record.booking.state == 'offer':
|
|
raise UserError(gettext('hotel.msg_missing_confirm_booking'))
|
|
if record.main_guest is None:
|
|
raise UserError(gettext('hotel.msg_missing_main_guest'))
|
|
if record.room is None:
|
|
raise UserError(gettext('hotel.msg_missing_select_room'))
|
|
record.set_registration_number()
|
|
record.check_room()
|
|
record.add_occupancy('check_in')
|
|
cls.update_room(record, 'check_in')
|
|
cls.write(records, {'registration_state': 'check_in'})
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def load_accommodation(cls, records):
|
|
for folio in records:
|
|
folio.add_charge_occupancy(ctx='allow')
|
|
|
|
def check_room(self):
|
|
if self.room.state != 'clean':
|
|
raise UserError(
|
|
gettext('hotel.msg_room_no_clean', room=self.room.name)
|
|
)
|
|
|
|
def get_room_status(self, name):
|
|
if self.room:
|
|
return self.room.state
|
|
|
|
def validate_check_in(self):
|
|
if self.arrival_date > date.today():
|
|
raise UserError(gettext('hotel.msg_cannot_check_in_future'))
|
|
|
|
def try_reconcile(self):
|
|
# Try mark as paid all charges of the folio if there is
|
|
# not amount pending to pay
|
|
if self.booking.pending_to_pay == 0:
|
|
for charge in self.charges:
|
|
charge.status = 'paid'
|
|
charge.save()
|
|
|
|
def validate_check_out(self):
|
|
bk = self.booking
|
|
for occ in self.occupancy:
|
|
if not occ.charge:
|
|
raise UserError(
|
|
gettext('hotel.msg_the_accommodation_not_charged'))
|
|
|
|
def _check_accommodation(folio):
|
|
if folio.booking.complementary:
|
|
return
|
|
for charge in folio.charges:
|
|
if charge.status != 'paid':
|
|
raise UserError(
|
|
gettext('hotel.msg_accommodation_not_paid'))
|
|
|
|
def _check_charges(folio):
|
|
complementary = folio.booking.complementary
|
|
for charge in folio.charges:
|
|
if complementary and charge.product.template.kind == 'accommodation':
|
|
return
|
|
if charge.status != 'paid':
|
|
raise UserError(gettext('hotel.msg_charges_not_paid'))
|
|
|
|
if bk.responsible_payment == 'guest':
|
|
_check_charges(self)
|
|
_check_accommodation(self)
|
|
elif bk.responsible_payment == 'holder':
|
|
if bk.payment_term and bk.payment_term.payment_type == 2:
|
|
return
|
|
|
|
if self.main_guest == bk.party:
|
|
for folio in bk.lines:
|
|
if bk.pending_to_pay == 0:
|
|
for charge in folio.charges:
|
|
charge.status = 'paid'
|
|
charge.save()
|
|
continue
|
|
_check_charges(folio)
|
|
_check_accommodation(folio)
|
|
else:
|
|
_check_charges(self)
|
|
_check_accommodation(self)
|
|
else:
|
|
_check_charges(self)
|
|
self.sync_send_siat()
|
|
|
|
def sync_send_siat(self):
|
|
pool = Pool()
|
|
config = pool.get('hotel.configuration').get_configuration()
|
|
company = self.booking.company
|
|
try:
|
|
res = send_siat(company, self.main_guest, self, config.token_siat)
|
|
if res:
|
|
now = datetime.now()
|
|
for guest in self.guests:
|
|
if not guest.siat_send_date:
|
|
guest.siat_send_date = now
|
|
guest.siat_id = res['code']
|
|
guest.save()
|
|
except Exception as error:
|
|
print("Error en plataforma SIAT...!", error)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def check_out(cls, records):
|
|
for record in records:
|
|
record.try_reconcile()
|
|
record.validate_check_out()
|
|
cls.write([record], {'registration_state': 'check_out'})
|
|
cls.update_room(record, 'check_out')
|
|
|
|
@classmethod
|
|
@ModelView.button_action('hotel.wizard_bill_booking')
|
|
def bill(cls, records):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button_action('hotel.wizard_booking_advance_voucher')
|
|
def pay(cls, records):
|
|
pass
|
|
|
|
@classmethod
|
|
def update_room(cls, folio, status):
|
|
pool = Pool()
|
|
Configuration = pool.get('hotel.configuration')
|
|
config = Configuration.get_configuration()
|
|
cleaning_type_id = None
|
|
room = folio.room
|
|
if status in 'check_in' and config.cleaning_occupied:
|
|
cleaning_type_id = config.cleaning_occupied.id
|
|
room.last_check_in = datetime.now()
|
|
room.last_check_out = None
|
|
room.check_in_today = datetime.now().time()
|
|
elif status in 'check_out' and config.cleaning_check_out:
|
|
cleaning_type_id = config.cleaning_check_out.id
|
|
room.last_check_out = datetime.now()
|
|
room.last_check_in = None
|
|
room.check_out_today = datetime.now().time()
|
|
|
|
room.state = 'dirty'
|
|
if cleaning_type_id:
|
|
room.cleaning_type = cleaning_type_id
|
|
room.save()
|
|
|
|
def get_invoice_state(self, name=None):
|
|
if self.invoice_line:
|
|
return self.invoice_line.invoice.state
|
|
|
|
def get_channel(self, name=None):
|
|
if self.booking and self.booking.channel:
|
|
return self.booking.channel.id
|
|
|
|
def set_registration_number(self):
|
|
"""
|
|
Fill the number field for registration card with sequence
|
|
"""
|
|
pool = Pool()
|
|
Config = pool.get('hotel.configuration')
|
|
config = Config.get_configuration()
|
|
|
|
if self.registration_card:
|
|
return
|
|
if not config.registration_card_sequence:
|
|
raise UserError(gettext('hotel.msg_missing_sequence_registration'))
|
|
number = config.registration_card_sequence.get()
|
|
self.registration_card = number
|
|
self.save()
|
|
|
|
def get_stock_moves(self, name=None):
|
|
moves = []
|
|
for charge in self.charges:
|
|
if charge.move:
|
|
moves.append(charge.move.id)
|
|
return moves
|
|
|
|
def get_unit_price_w_tax(self, name=None):
|
|
Tax = Pool().get('account.tax')
|
|
res = self.unit_price or 0
|
|
if self.unit_price and not self.taxes_exception:
|
|
values = Tax.compute(
|
|
self.product.template.customer_taxes_used,
|
|
self.unit_price, 1)
|
|
if values:
|
|
value = values[0]
|
|
res = value['base'] + value['amount']
|
|
return res
|
|
|
|
@fields.depends('unit_price', 'nights_quantity')
|
|
def on_change_with_room_amount(self, name=None):
|
|
res = 0
|
|
if self.unit_price and self.nights_quantity:
|
|
res = self.unit_price * self.nights_quantity
|
|
return Decimal(round(res, Folio.total_amount.digits[1]))
|
|
|
|
@staticmethod
|
|
def default_main_guest():
|
|
party = Transaction().context.get('party')
|
|
return party
|
|
|
|
@staticmethod
|
|
def default_host_quantity():
|
|
return 1
|
|
|
|
@staticmethod
|
|
def default_accommodation():
|
|
Configuration = Pool().get('hotel.configuration')
|
|
configuration = Configuration.get_configuration()
|
|
if configuration.default_accommodation:
|
|
return configuration.default_accommodation.id
|
|
|
|
@staticmethod
|
|
def default_breakfast_included():
|
|
return True
|
|
|
|
@classmethod
|
|
def validate(cls, lines):
|
|
super(Folio, cls).validate(lines)
|
|
for line in lines:
|
|
# line.check_method()
|
|
pass
|
|
|
|
def get_invoice(self, name=None):
|
|
if self.invoice_line and self.invoice_line.invoice:
|
|
return self.invoice_line.invoice.id
|
|
|
|
def get_room_info(self):
|
|
description = ' \n'.join([
|
|
self.product.rec_name,
|
|
'Huesped Principal: ' + self.main_guest.name if self.main_guest else '',
|
|
'Habitacion: ' + self.room.name,
|
|
'Llegada: ' + str(self.arrival_date),
|
|
'Salida: ' + str(self.departure_date),
|
|
])
|
|
return description
|
|
|
|
@fields.depends('product', 'unit_price', 'uom', 'booking',
|
|
'nights_quantity', 'commission_amount', 'occupancy')
|
|
def on_change_product(self):
|
|
Product = Pool().get('product.product')
|
|
unit_price = None
|
|
if self.product and self.booking:
|
|
self.uom = self.product.default_uom.id
|
|
with Transaction().set_context(
|
|
self.booking.get_context_price(self.product)):
|
|
unit_price = Product.get_sale_price([self.product],
|
|
self.nights_quantity or 0)[self.product.id]
|
|
# self.taxes = [tax.id for tax in self.product.customer_taxes_used]
|
|
else:
|
|
unit_price = self.product.list_price
|
|
if unit_price and self.booking:
|
|
unit_price = self.booking.currency.round(self.unit_price)
|
|
self.update_occupancy(price=unit_price)
|
|
self.update_commission()
|
|
|
|
def update_commission(self):
|
|
if self.nights_quantity and self.unit_price and self.booking:
|
|
if not self.booking.channel:
|
|
return
|
|
amount = self.on_change_with_room_amount()
|
|
self.commission_amount = self.booking.channel.compute(amount)
|
|
|
|
@fields.depends('arrival_date', 'departure_date', 'unit_price', 'booking')
|
|
def on_change_arrival_date(self):
|
|
if self.arrival_date and self.departure_date:
|
|
self.update_nights(self.arrival_date, self.departure_date)
|
|
self.update_commission()
|
|
|
|
@fields.depends('arrival_date', 'departure_date', 'unit_price', 'booking')
|
|
def on_change_departure_date(self):
|
|
if self.arrival_date and self.departure_date:
|
|
self.update_nights(self.arrival_date, self.departure_date)
|
|
self.update_commission()
|
|
|
|
def check_method(self):
|
|
"""
|
|
Check the methods.
|
|
"""
|
|
Date = Pool().get('ir.date')
|
|
if self.registration_state in (['check_in', 'check_out']):
|
|
raise UserError(gettext('hotel.msg_reservation_checkin'))
|
|
if self.arrival_date < Date.today():
|
|
raise UserError(gettext('hotel.msg_invalid_arrival_date'))
|
|
if self.arrival_date >= self.departure_date:
|
|
raise UserError(gettext('hotel.msg_invalid_date'))
|
|
|
|
def get_state(self, name):
|
|
if self.booking:
|
|
return self.booking.state
|
|
|
|
def get_host_quantity(self, name):
|
|
res = 0
|
|
if self.num_adult:
|
|
res += self.num_adult
|
|
if self.num_children:
|
|
res += self.num_children
|
|
return res
|
|
|
|
@classmethod
|
|
def get_available_rooms(cls, start_date, end_date, rooms_ids=[], oper=None):
|
|
"""
|
|
Look for available rooms.
|
|
|
|
given the date interval, return a list of room ids.
|
|
|
|
A room is available if it has no operation that overlaps
|
|
with the given date interval.
|
|
|
|
the optional 'rooms' list is a list of room instance, is an
|
|
additional filter, specifying the ids of the desirable rooms.
|
|
|
|
the optional 'oper' is an operation object that has to be
|
|
filtered out of the test. it is useful for validating an already
|
|
existing operation.
|
|
"""
|
|
Maintenance = Pool().get('hotel.maintenance')
|
|
if start_date >= end_date:
|
|
raise UserError(gettext('hotel.msg_invalid_date_range'))
|
|
|
|
# define the domain of the operations that find a
|
|
# room to be available
|
|
dom = ['AND', ['OR',
|
|
[
|
|
('arrival_date', '>=', start_date),
|
|
('arrival_date', '<', end_date),
|
|
], [
|
|
('departure_date', '<=', end_date),
|
|
('departure_date', '>', start_date),
|
|
], [
|
|
('arrival_date', '<=', start_date),
|
|
('departure_date', '>=', end_date),
|
|
],
|
|
]]
|
|
|
|
## If oper was specified, do not compare the operations with it
|
|
if oper is not None:
|
|
dom.append(('id', '!=', oper.id))
|
|
|
|
if rooms_ids:
|
|
dom.append(('room', 'in', rooms_ids))
|
|
|
|
folios = cls.search(dom)
|
|
rooms_not_available_ids = [folio.room.id for folio in folios]
|
|
dom = ['AND', ['OR',
|
|
[
|
|
('start_date', '>=', start_date),
|
|
('start_date', '<=', end_date),
|
|
], [
|
|
('end_date', '<=', end_date),
|
|
('end_date', '>=', start_date),
|
|
], [
|
|
('start_date', '<=', start_date),
|
|
('end_date', '>=', end_date),
|
|
]],
|
|
('state', 'in', ('in_progress', 'finished')),
|
|
]
|
|
maintenance = Maintenance.search(dom)
|
|
rooms_not_available_ids.extend(mnt.room.id for mnt in maintenance)
|
|
|
|
rooms_available_ids = set(rooms_ids) - set(rooms_not_available_ids)
|
|
return list(rooms_available_ids)
|
|
|
|
def update_nights(self, arrival_date, departure_date):
|
|
self.nights_quantity = (departure_date - arrival_date).days
|
|
|
|
def add_charge_occupancy(self, ctx=None):
|
|
pool = Pool()
|
|
Charge = pool.get('hotel.folio.charge')
|
|
Date = pool.get('ir.date')
|
|
# Add transaction context for hotel price list for current day
|
|
unit_price = Decimal(round(self.unit_price, 2))
|
|
space = self.booking.space
|
|
today = Date.today()
|
|
for occ in self.occupancy:
|
|
if occ.charge:
|
|
continue
|
|
if not ctx and occ.occupancy_date > today:
|
|
continue
|
|
analytic_account = space.analytic_account if space else None
|
|
description = f"{self.room.name} | {self.registration_card}"
|
|
rec = {
|
|
'folio': self.id,
|
|
'product': self.product.id,
|
|
'unit_price': unit_price,
|
|
'date_service': occ.occupancy_date,
|
|
'quantity': 1,
|
|
'status': 'pending',
|
|
'state': '',
|
|
'analytic_account': analytic_account,
|
|
'kind': 'accommodation',
|
|
'description': description,
|
|
'taxes': [],
|
|
}
|
|
if not self.taxes_exception:
|
|
rec['taxes'] = [('add', [
|
|
tax.id for tax in self.product.customer_taxes_used
|
|
])
|
|
]
|
|
charge, = Charge.create([rec])
|
|
occ.charge = charge.id
|
|
occ.save()
|
|
|
|
def add_occupancy(self, next=None):
|
|
FolioOccupancy = Pool().get('hotel.folio.occupancy')
|
|
if self.occupancy:
|
|
return
|
|
|
|
res = []
|
|
delta = (self.departure_date - self.arrival_date).days
|
|
current = [oc.occupancy_date for oc in self.occupancy]
|
|
unit_price = round(self.unit_price, 2)
|
|
for day in range(delta):
|
|
_date = self.arrival_date + timedelta(days=day)
|
|
if _date in current:
|
|
continue
|
|
res.append({
|
|
'folio': self.id,
|
|
'occupancy_date': _date,
|
|
'unit_price': unit_price,
|
|
'state': 'draft',
|
|
})
|
|
FolioOccupancy.create(res)
|
|
|
|
@classmethod
|
|
def get_folios(cls, args):
|
|
pool = Pool()
|
|
Maintenance = pool.get('hotel.maintenance')
|
|
Room = pool.get('hotel.room')
|
|
start = datetime.strptime(args['start_date'], "%Y-%m-%d")
|
|
start_date = (start + timedelta(days=-4)).date()
|
|
end_date = (start + timedelta(days=30)).date()
|
|
dom = ['OR', [
|
|
('arrival_date', '>=', start_date),
|
|
('arrival_date', '<=', end_date)
|
|
], [
|
|
('departure_date', '>=', start_date),
|
|
('departure_date', '<=', end_date)
|
|
]]
|
|
|
|
fields_names = [
|
|
"room.name",
|
|
"arrival_date",
|
|
"departure_date",
|
|
"main_guest.name",
|
|
"product.rec_name",
|
|
"unit_price_w_tax",
|
|
"pending_total",
|
|
"plan",
|
|
"guests",
|
|
"charges",
|
|
"booking.contact",
|
|
"booking.party.name",
|
|
"booking.number",
|
|
"booking.media",
|
|
"booking.channel.rec_name",
|
|
"booking.channel.code",
|
|
"registration_state",
|
|
"registration_card",
|
|
"nights_quantity",
|
|
"notes",
|
|
"vehicle_plate",
|
|
]
|
|
rooms = Room.search_read([], fields_names=['code', 'name'])
|
|
folios = cls.search_read(dom, fields_names=fields_names)
|
|
occ_rate = cls.get_occupation_rate(start_date, end_date)
|
|
dom_maint = ['OR', [
|
|
('start_date', '>=', start_date),
|
|
('start_date', '<=', end_date),
|
|
('state', 'in', ('in_progress', 'finished', 'confirmed')),
|
|
('start_date', '!=', None),
|
|
('end_date', '!=', None),
|
|
], [
|
|
('end_date', '>=', start_date),
|
|
('end_date', '<=', end_date),
|
|
('state', 'in', ('in_progress', 'finished', 'confirmed')),
|
|
('start_date', '!=', None),
|
|
('end_date', '!=', None),
|
|
],
|
|
]
|
|
fields_mnt = [
|
|
'start_date', 'end_date', 'room', 'room.name', 'issue',
|
|
'create_uid.name'
|
|
]
|
|
mnts = Maintenance.search_read(dom_maint, fields_names=fields_mnt)
|
|
res = {
|
|
'rooms': rooms,
|
|
'folios': folios,
|
|
'mnts': mnts,
|
|
'occ_rate': occ_rate
|
|
}
|
|
return res
|
|
|
|
def get_total_accommodation(self):
|
|
res = []
|
|
if self.booking.channel and \
|
|
self.booking.channel_payment_method == 'ota_collect':
|
|
return 0
|
|
if self.registration_state in ['check_in', 'check_out']:
|
|
for charge in self.charges:
|
|
if charge.kind == 'accommodation':
|
|
taxed_amount = charge.taxed_amount or 0
|
|
res.append(taxed_amount)
|
|
for occ in self.occupancy:
|
|
if not occ.charge:
|
|
unit_price_w_tax = occ.unit_price_w_tax or 0
|
|
res.append(unit_price_w_tax)
|
|
else:
|
|
if self.nights_quantity and self.unit_price_w_tax:
|
|
res = [self.nights_quantity * self.unit_price_w_tax]
|
|
res = round(sum(res), 2)
|
|
return res
|
|
|
|
@classmethod
|
|
def get_occupation_rate(cls, start_date, end_date):
|
|
Room = Pool().get('hotel.room')
|
|
alldays = OrderedDict()
|
|
rooms = Room.search_read([], fields_names=['code', 'name'])
|
|
n_rooms = len(rooms)
|
|
delta = (end_date - start_date).days
|
|
_date = start_date
|
|
for nd in range(delta):
|
|
alldays[_date] = {
|
|
'occ_rate': 0
|
|
}
|
|
_date = _date + timedelta(days=1)
|
|
|
|
dom = [
|
|
['OR', [
|
|
('arrival_date', '<=', start_date),
|
|
('departure_date', '>=', end_date),
|
|
], [
|
|
('arrival_date', '>=', start_date),
|
|
('departure_date', '<=', end_date),
|
|
], [
|
|
('arrival_date', '<=', end_date),
|
|
('departure_date', '>=', end_date),
|
|
], [
|
|
('arrival_date', '<=', start_date),
|
|
('departure_date', '>=', end_date),
|
|
]],
|
|
('booking.state', 'not in', ['no_show', 'cancelled'])
|
|
]
|
|
|
|
nfields = ['arrival_date', 'departure_date', 'nights_quantity']
|
|
folios = cls.search_read(dom, fields_names=nfields)
|
|
bag_data = []
|
|
extend = bag_data.extend
|
|
for folio in folios:
|
|
extend([folio['arrival_date'] + timedelta(days=n)
|
|
for n in range(folio['nights_quantity'])
|
|
])
|
|
|
|
for _day, dvalue in alldays.items():
|
|
dvalue['occ_rate'] = int(bag_data.count(_day) * 100 / n_rooms)
|
|
return alldays
|
|
|
|
@classmethod
|
|
def set_offset_commission_move(cls, folios_lines, bk):
|
|
"""
|
|
Create Offset Move (Asiento de cruce) in order to cross the advance
|
|
anticipated to the channel (supplier) against the account
|
|
receivable in the sale invoice
|
|
"""
|
|
pool = Pool()
|
|
Move = pool.get('account.move')
|
|
Period = pool.get('account.period')
|
|
MoveLine = pool.get('account.move.line')
|
|
Config = pool.get('hotel.configuration')
|
|
config = Config.get_configuration()
|
|
|
|
period_id = Period.find(bk.company.id, date=date.today())
|
|
description = bk.number
|
|
if bk.ota_booking_code:
|
|
description += ' | ' + bk.ota_booking_code
|
|
|
|
move, = Move.create([{
|
|
'journal': config.offset_journal.id,
|
|
'period': period_id,
|
|
'date': date.today(),
|
|
# 'origin': str(self),
|
|
'state': 'draft',
|
|
'description': description,
|
|
}])
|
|
|
|
lines_ids = []
|
|
party_channel = bk.channel.agent.party
|
|
party = bk.party
|
|
|
|
for folio, inv_line in folios_lines:
|
|
if not folio.commission_amount:
|
|
return
|
|
|
|
move_line, = MoveLine.create([{
|
|
'description': folio.registration_card,
|
|
'account': party.account_receivable_used,
|
|
'party': party.id,
|
|
'debit': 0,
|
|
'credit': folio.commission_amount,
|
|
'move': move.id,
|
|
}])
|
|
lines_ids.append(move_line.id)
|
|
move_line, = MoveLine.create([{
|
|
'description': folio.registration_card,
|
|
'account': party_channel.account_payable_used,
|
|
'party': party.id,
|
|
'debit': folio.commission_amount,
|
|
'credit': 0,
|
|
'move': move.id,
|
|
}])
|
|
inv_line.offset_move_line = move_line.id
|
|
inv_line.save()
|
|
|
|
Move.post([move])
|
|
return lines_ids
|
|
|
|
def get_total_products(self):
|
|
res = []
|
|
for charge in self.charges:
|
|
if charge.kind == 'product':
|
|
res.append(charge.taxed_amount)
|
|
return sum(res)
|
|
|
|
def get_total_amount(self, name):
|
|
"""
|
|
The total amount of folio based on room flat price.
|
|
TODO: If room fee is applied should be used for total price calculation
|
|
instead of flat price. Fee is linked to channel management.
|
|
"""
|
|
res = []
|
|
res.append(self.get_total_accommodation())
|
|
res.append(self.get_total_products())
|
|
res = round(sum(res), Folio.total_amount.digits[1])
|
|
return res
|
|
|
|
@fields.depends('nights_quantity', 'unit_price', 'booking')
|
|
def on_change_with_commission_amount(self):
|
|
"""
|
|
Compute of commission amount for channel based on booking
|
|
"""
|
|
res = Decimal(0)
|
|
if self.nights_quantity and self.unit_price and self.booking and \
|
|
self.booking.channel:
|
|
amount = self.on_change_with_room_amount()
|
|
res = self.booking.channel.compute(amount)
|
|
return res
|
|
|
|
def _create_occ(self, occ_date, unit_price):
|
|
return {
|
|
'occupancy_date': occ_date,
|
|
'folio': self.id,
|
|
'unit_price': unit_price,
|
|
'state': 'draft',
|
|
}
|
|
|
|
@classmethod
|
|
def write(cls, records, *args):
|
|
super(Folio, cls).write(records, *args)
|
|
for values in args:
|
|
if not isinstance(values, dict):
|
|
continue
|
|
arr_date = values.get('arrival_date', None)
|
|
dep_date = values.get('departure_date', None)
|
|
unit_price = values.get('unit_price', None)
|
|
if isinstance(arr_date, str):
|
|
arr_date = datetime.strptime(arr_date, '%Y-%m-%d').date()
|
|
if isinstance(dep_date, str):
|
|
dep_date = datetime.strptime(dep_date, '%Y-%m-%d').date()
|
|
if arr_date or dep_date or unit_price:
|
|
for folio in records:
|
|
folio.update_occupancy(
|
|
arr_date=arr_date,
|
|
dep_date=dep_date,
|
|
price=unit_price)
|
|
|
|
def update_occupancy(self, arr_date=None, dep_date=None, price=None):
|
|
Occupancy = Pool().get('hotel.folio.occupancy')
|
|
to_delete = []
|
|
to_create = []
|
|
price = price or self.unit_price
|
|
arr_date = arr_date or self.arrival_date
|
|
dep_date = dep_date or self.departure_date
|
|
if dep_date or arr_date:
|
|
self.update_nights(arr_date, dep_date)
|
|
delta = (dep_date - arr_date).days
|
|
current_dates = {occ.occupancy_date: occ for occ in self.occupancy}
|
|
new_dates = []
|
|
for day in range(delta):
|
|
new_dates.append(arr_date + timedelta(day))
|
|
|
|
for new_date in new_dates:
|
|
if new_date not in current_dates.keys():
|
|
to_create.append(self._create_occ(new_date, price))
|
|
|
|
for curr_date, occ in current_dates.items():
|
|
if curr_date not in new_dates:
|
|
if not occ.charge:
|
|
to_delete.append(occ)
|
|
continue
|
|
raise UserError(gettext(
|
|
'hotel.msg_can_not_change_departure'))
|
|
|
|
if to_create:
|
|
self.occupancy
|
|
Occupancy.create(to_create)
|
|
if to_delete:
|
|
Occupancy.delete(to_delete)
|
|
|
|
if price:
|
|
self.unit_price = price
|
|
for occ in self.occupancy:
|
|
occ.unit_price = price
|
|
occ.save()
|
|
if occ.charge:
|
|
occ.charge.unit_price = price
|
|
occ.charge.save()
|
|
|
|
@classmethod
|
|
def _get_check_rooms(cls, kind):
|
|
# Dash Report
|
|
# kind : arrival or departure
|
|
field_target = kind + '_date'
|
|
folios = cls.search_read([
|
|
(field_target, '=', date.today())
|
|
], fields_names=['id'])
|
|
return len(folios)
|
|
|
|
@classmethod
|
|
def report_check_in_today(cls, args):
|
|
# Dash Report
|
|
value = cls._get_check_rooms('arrival')
|
|
today = date.today()
|
|
res = {
|
|
'value': value,
|
|
'header_meta': str(today),
|
|
}
|
|
return res
|
|
|
|
@classmethod
|
|
def report_check_out_today(cls, args):
|
|
# Dash Report
|
|
value = cls._get_check_rooms('departure')
|
|
today = date.today()
|
|
res = {
|
|
'value': value,
|
|
'header_meta': str(today),
|
|
}
|
|
return res
|
|
|
|
@classmethod
|
|
def _get_occupation(cls, today):
|
|
dom = [
|
|
('arrival_date', '<=', today),
|
|
('departure_date', '>', today),
|
|
]
|
|
folios = cls.search_read(dom, fields_names=['id', 'guests'])
|
|
return folios
|
|
|
|
@classmethod
|
|
def report_occupation_by_room(cls, args):
|
|
# Dash Report
|
|
today = date.today()
|
|
folios = cls._get_occupation(today)
|
|
value = len(folios)
|
|
res = {
|
|
'value': value,
|
|
'header_meta': str(today),
|
|
}
|
|
return res
|
|
|
|
@classmethod
|
|
def report_number_guests(cls, args):
|
|
# Dash Report
|
|
today = date.today()
|
|
folios = cls._get_occupation(today)
|
|
value = 0
|
|
for folio in folios:
|
|
value += len(folio['guests'])
|
|
|
|
res = {
|
|
'value': value,
|
|
'header_meta': str(today),
|
|
}
|
|
return res
|
|
|
|
@classmethod
|
|
def _get_range_dates(cls, folio, mode=None):
|
|
start_date = folio['arrival_date']
|
|
res = []
|
|
_append = res.append
|
|
for dt in range(folio['nights_quantity']):
|
|
_date = start_date + timedelta(days=dt)
|
|
if mode == 'string':
|
|
_date = str(_date)
|
|
_append(_date)
|
|
return set(res)
|
|
|
|
@classmethod
|
|
def report_month_occupancy_rate(cls, args):
|
|
pool = Pool()
|
|
Room = pool.get('hotel.room')
|
|
Folio = pool.get('hotel.folio')
|
|
today = date.today()
|
|
first, last = calendar.monthrange(today.year, today.month)
|
|
labels = []
|
|
alldays = OrderedDict()
|
|
dates_of_month = []
|
|
today = date.today()
|
|
for nd in range(last):
|
|
_day = str(date(today.year, today.month, nd + 1))
|
|
labels.append(str(nd + 1))
|
|
dates_of_month.append(_day)
|
|
alldays[_day] = []
|
|
|
|
date_start = date(today.year, today.month, 1)
|
|
date_end = date(today.year, today.month, last)
|
|
|
|
dom = [
|
|
['OR', [
|
|
('arrival_date', '<=', date_start),
|
|
('departure_date', '>=', date_start),
|
|
], [
|
|
('arrival_date', '>=', date_start),
|
|
('departure_date', '<=', date_end),
|
|
], [
|
|
('arrival_date', '<=', date_end),
|
|
('departure_date', '>=', date_end),
|
|
], [
|
|
('arrival_date', '<=', date_start),
|
|
('departure_date', '>=', date_end),
|
|
]],
|
|
('booking.state', 'not in', ['no_show', 'cancelled'])
|
|
]
|
|
|
|
folios = Folio.search_read(dom, fields_names=[
|
|
'arrival_date', 'nights_quantity'])
|
|
|
|
for folio in folios:
|
|
folio_dates = cls._get_range_dates(folio, mode='string')
|
|
dates_target = set(dates_of_month) & folio_dates
|
|
for dt in dates_target:
|
|
alldays[dt].append(1)
|
|
|
|
rooms = len(Room.search_read([]))
|
|
values = [sum(fo_num) * 100 / rooms for _, fo_num in alldays.items()]
|
|
month_name = today.strftime("%b %Y")
|
|
label_item = '% Occupancy Rate'
|
|
res = {
|
|
'label_item': label_item,
|
|
'labels': labels,
|
|
'values': values,
|
|
'header_meta': month_name,
|
|
'min_value': 1,
|
|
'max_value': 100,
|
|
}
|
|
return res
|
|
|
|
@classmethod
|
|
def report_current_occupancy_rate(cls, args):
|
|
pool = Pool()
|
|
Room = pool.get('hotel.room')
|
|
rooms = Room.search_read([
|
|
('active', '=', True),
|
|
])
|
|
today = date.today()
|
|
dates_arrival = []
|
|
dates_departure = []
|
|
first, last = calendar.monthrange(today.year, today.month)
|
|
day_one = date(today.year, today.month, 1)
|
|
for md in range(last):
|
|
dates_arrival.append(day_one + timedelta(days=(md+1)))
|
|
dates_departure.append(day_one + timedelta(days=(md+2)))
|
|
|
|
rooms_available = len(rooms) * last
|
|
dom = ['OR', [
|
|
('arrival_date', 'in', dates_arrival),
|
|
('booking.state', 'not in', ['no_show', 'cancelled']),
|
|
], [
|
|
('departure_date', 'in', dates_departure),
|
|
('booking.state', 'not in', ['no_show', 'cancelled']),
|
|
]
|
|
]
|
|
folios = cls.search_read(dom, fields_names=[
|
|
'guests', 'arrival_date', 'nights_quantity'])
|
|
occupation = []
|
|
month_dates = set(dates_arrival)
|
|
for folio in folios:
|
|
_dates = cls._get_range_dates(folio)
|
|
occupation.append(len(_dates & month_dates))
|
|
|
|
value = (sum(occupation) * 100) / rooms_available
|
|
month_name = today.strftime("%b %Y")
|
|
value = f'{round(value, 2)}%'
|
|
res = {
|
|
'value': value,
|
|
'header_meta': month_name,
|
|
}
|
|
return res
|
|
|
|
|
|
class FolioGuest(ModelSQL, ModelView):
|
|
'Folio Guest'
|
|
__name__ = 'hotel.folio.guest'
|
|
REQ_MAIN_GUEST = {
|
|
'required': Eval('main_guest', False)
|
|
}
|
|
folio = fields.Many2One('hotel.folio', 'Folio')
|
|
main_guest = fields.Boolean('Main Guest')
|
|
type_guest = fields.Selection([
|
|
('adult', 'Adult'),
|
|
('child', 'Child'),
|
|
], 'Type Guest', required=True)
|
|
type_guest_string = type_guest.translated('type_guest')
|
|
nationality = fields.Many2One('country.country', 'Nationality')
|
|
origin_country = fields.Many2One('country.country', 'Origin Country')
|
|
target_country = fields.Many2One('country.country', 'Target Country')
|
|
# New fields for speed reason
|
|
type_document = fields.Selection(TYPE_DOCUMENT, 'Document Type',
|
|
required=True)
|
|
type_document_string = type_document.translated('type_document')
|
|
id_number = fields.Char('Id Number', required=True)
|
|
name = fields.Char('Name', required=True)
|
|
mobile = fields.Char('Mobile', states=REQ_MAIN_GUEST)
|
|
email = fields.Char('Email', states=REQ_MAIN_GUEST)
|
|
address = fields.Char('Address', states=REQ_MAIN_GUEST)
|
|
profession = fields.Char('Profession')
|
|
birthday = fields.Date('Birthday')
|
|
visa_date = fields.Date('Visa Date')
|
|
visa_category = fields.Char('Visa Category')
|
|
visa_number = fields.Char('Visa Number')
|
|
sex = fields.Selection([
|
|
('female', 'Female'),
|
|
('male', 'Male'),
|
|
('', ''),
|
|
], 'Sex', required=True)
|
|
sex_string = sex.translated('sex')
|
|
first_name = fields.Char('First Name')
|
|
second_name = fields.Char('Second Name')
|
|
first_family_name = fields.Char('First Family Name')
|
|
second_family_name = fields.Char('Second Family Name')
|
|
notes = fields.Text('Notes')
|
|
country = fields.Many2One('party.country_code', 'Country',
|
|
states=REQ_MAIN_GUEST)
|
|
subdivision = fields.Many2One('party.department_code', 'Subdivision')
|
|
city = fields.Many2One('party.city_code', 'City',
|
|
domain=[('department', '=', Eval('subdivision'))])
|
|
tags = fields.Many2Many('hotel.tag.guest', 'guest', 'tag', 'Tags')
|
|
siat_send_date = fields.DateTime('Siat Send Date',
|
|
states={'readonly': True})
|
|
siat_id = fields.Char('SIAT Id.', states={'readonly': True})
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
_, operator, value = clause
|
|
if operator.startswith('!') or operator.startswith('not '):
|
|
bool_op = 'AND'
|
|
else:
|
|
bool_op = 'OR'
|
|
domain = [
|
|
bool_op,
|
|
('id_number', operator, value),
|
|
('name', operator, value),
|
|
('mobile', operator, value),
|
|
]
|
|
return domain
|
|
|
|
@classmethod
|
|
def get_splitted_name(cls, name):
|
|
first_name = None
|
|
first_family_name = None
|
|
second_name = None
|
|
second_family_name = None
|
|
full_names = {
|
|
'second_family_name': None,
|
|
'first_family_name': None,
|
|
'second_name': None,
|
|
'first_name': None,
|
|
}
|
|
if name:
|
|
names = name.split(' ')
|
|
first_name = names[0]
|
|
second_family_name = names[-1]
|
|
if len(names) > 1:
|
|
first_family_name = names[-2]
|
|
if len(names) == 2:
|
|
second_family_name = None
|
|
first_family_name = names[1]
|
|
elif len(names) == 5:
|
|
second_name = names[1] + ' ' + names[2]
|
|
elif len(names) == 4:
|
|
second_name = names[1]
|
|
|
|
full_names['second_family_name'] = second_family_name
|
|
full_names['first_family_name'] = first_family_name
|
|
full_names['second_name'] = second_name
|
|
full_names['first_name'] = first_name
|
|
return full_names
|
|
|
|
@fields.depends('name', 'first_name', 'second_name',
|
|
'first_family_name', 'second_family_name')
|
|
def on_change_name(self):
|
|
names = self.get_splitted_name(self.name)
|
|
self.second_family_name = names['second_family_name']
|
|
self.first_family_name = names['first_family_name']
|
|
self.second_name = names['second_name']
|
|
self.first_name = names['first_name']
|
|
|
|
@staticmethod
|
|
def default_type_guest():
|
|
return 'adult'
|
|
|
|
@staticmethod
|
|
def default_nationality():
|
|
Config = Pool().get('hotel.configuration')
|
|
config = Config.get_configuration()
|
|
if config.nationality:
|
|
return config.nationality.id
|
|
|
|
@staticmethod
|
|
def default_country():
|
|
Config = Pool().get('hotel.configuration')
|
|
config = Config.get_configuration()
|
|
if config and config.country:
|
|
return config.country.id
|
|
|
|
@staticmethod
|
|
def default_type_document():
|
|
return '13'
|
|
|
|
@fields.depends('nationality', 'origin_country', 'target_country')
|
|
def on_change_nationality(self):
|
|
if self.nationality:
|
|
self.target_country = self.nationality.id
|
|
self.origin_country = self.nationality.id
|
|
|
|
@fields.depends('id_number', 'name', 'sex', 'email', 'mobile',
|
|
'visa_number', 'visa_date', 'birthday', 'nationality')
|
|
def on_change_id_number(self):
|
|
if self.id_number:
|
|
Guest = Pool().get('hotel.folio.guest')
|
|
Party = Pool().get('party.party')
|
|
guest = None
|
|
guests = Guest.search([
|
|
('id_number', '=', self.id_number)
|
|
])
|
|
if guests:
|
|
guest = guests[0]
|
|
else:
|
|
parties = Party.search([
|
|
('id_number', '=', self.id_number)
|
|
])
|
|
if parties:
|
|
guest = parties[0]
|
|
if guest:
|
|
self.name = guest.name.upper()
|
|
self.sex = guest.sex
|
|
self.type_document = guest.type_document
|
|
self.email = guest.email and guest.email.lower()
|
|
self.mobile = guest.mobile
|
|
self.visa_number = guest.visa_number
|
|
self.visa_date = guest.visa_date
|
|
self.birthday = guest.birthday
|
|
self.nationality = guest.nationality and guest.nationality.id
|
|
self.profession = guest.profession
|
|
self.first_name = guest.first_name
|
|
self.second_name = guest.second_name
|
|
self.first_family_name = guest.first_family_name
|
|
self.second_family_name = guest.second_family_name
|
|
self.subdivision = guest.subdivision and guest.subdivision.id
|
|
self.city = guest.city and guest.city.id
|
|
address = ''
|
|
if hasattr(guest, 'address'):
|
|
address = guest.address and guest.address.upper()
|
|
elif hasattr(guest, 'addresses') and guest.addresses:
|
|
address = guest.addresses[0].street
|
|
self.address = address and address.upper()
|
|
|
|
@classmethod
|
|
def manage_party(cls, v):
|
|
Party = Pool().get('party.party')
|
|
is_main_guest = v.pop('main_guest', False)
|
|
folio = v.pop('folio', None)
|
|
party_fields = Party.fields_get().keys()
|
|
form_fields = set(v.keys())
|
|
to_remove = form_fields - party_fields
|
|
|
|
def clean_fields():
|
|
# Remove all fields that are not part of the party model
|
|
for val in to_remove:
|
|
_ = v.pop(val, None)
|
|
|
|
if is_main_guest:
|
|
parties = Party.search([
|
|
('id_number', '=', v['id_number']),
|
|
])
|
|
email = v.pop('email', '').lower()
|
|
mobile = v.pop('mobile', '').upper()
|
|
address = v.pop('address', '').upper()
|
|
nationality = v.pop('nationality')
|
|
country = v.pop('country', None)
|
|
subdivision = v.pop('subdivision', None)
|
|
city = v.pop('city', None)
|
|
v['type_person'] = 'persona_natural'
|
|
_ = v.pop('tags', None)
|
|
if not parties:
|
|
v['contact_mechanisms'] = [
|
|
('create', [
|
|
{'type': 'email', 'value': email},
|
|
{'type': 'mobile', 'value': mobile},
|
|
])
|
|
]
|
|
v['addresses'] = [
|
|
('create', [{
|
|
'street': address,
|
|
'country_code': country,
|
|
'department_code': subdivision,
|
|
'city_code': city,
|
|
}])
|
|
]
|
|
# FIXME add tags to party and remove this lines
|
|
clean_fields()
|
|
party, = Party.create([v])
|
|
else:
|
|
clean_fields()
|
|
Party.write(parties, v)
|
|
party = parties[0]
|
|
has_email = False
|
|
has_mobile = False
|
|
to_write = {}
|
|
if party.addresses:
|
|
for addr in party.addresses:
|
|
addr.street = address
|
|
addr.country_code = country
|
|
addr.department_code = subdivision
|
|
addr.city_code = city
|
|
addr.save()
|
|
else:
|
|
to_write['addresses'] = [('create', [{
|
|
'street': address,
|
|
'country_code': nationality,
|
|
}])]
|
|
|
|
for cm in party.contact_mechanisms:
|
|
if email and cm.type == 'email':
|
|
cm.value = email
|
|
has_email = True
|
|
elif mobile and cm.type == 'mobile':
|
|
cm.value = mobile
|
|
has_mobile = True
|
|
cm.save()
|
|
to_write_cm = []
|
|
if not has_mobile and mobile:
|
|
to_write_cm.append({'type': 'mobile', 'value': mobile})
|
|
if not has_email and email:
|
|
to_write_cm.append({'type': 'email', 'value': email})
|
|
|
|
if to_write_cm:
|
|
to_write['contact_mechanisms'] = [('create', to_write_cm)]
|
|
if to_write:
|
|
Party.write(parties, to_write)
|
|
|
|
if folio:
|
|
Folio = Pool().get('hotel.folio')
|
|
folio = Folio(folio)
|
|
folio.main_guest = party.id
|
|
folio.save()
|
|
booking = folio.booking
|
|
if not booking.party:
|
|
booking.party = party.id
|
|
booking.save()
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
for values in vlist:
|
|
first_name = values.get('first_name', None)
|
|
if not first_name:
|
|
names = cls.get_splitted_name(values['name'])
|
|
values.update(names)
|
|
res = super(FolioGuest, cls).create(vlist)
|
|
|
|
for v in vlist:
|
|
cls.manage_party(v)
|
|
return res
|
|
|
|
@classmethod
|
|
def write(cls, records, values):
|
|
super(FolioGuest, cls).write(records, values)
|
|
fields = cls.fields_get()
|
|
for val in ('id', 'rec_name', 'write_date', 'write_uid', 'create_date'):
|
|
fields.pop(val)
|
|
data = {}
|
|
fields_list = fields.keys()
|
|
rec = records[0]
|
|
for field in fields_list:
|
|
data[field] = getattr(rec, field)
|
|
|
|
data.update(values)
|
|
cls.manage_party(data)
|
|
|
|
|
|
class FolioCharge(Workflow, ModelSQL, ModelView):
|
|
'Folio Charge'
|
|
__name__ = 'hotel.folio.charge'
|
|
folio = fields.Many2One('hotel.folio', 'Folio', required=True,
|
|
ondelete='CASCADE')
|
|
number = fields.Char('Number', readonly=True, required=False)
|
|
date_service = fields.Date('Date Service', required=True)
|
|
product = fields.Many2One('product.product', 'Product',
|
|
domain=[
|
|
('salable', '=', True),
|
|
('active', '=', True)
|
|
], required=True)
|
|
quantity = fields.Integer('Quantity', required=True)
|
|
invoice_to = fields.Many2One('party.party', 'Invoice To')
|
|
unit_price = fields.Numeric('Unit Price', digits=(16, 2),
|
|
required=True)
|
|
unit_price_w_tax = fields.Function(fields.Numeric('Unit Price w Tax',
|
|
digits=(16, 2)), 'get_unit_price_w_tax', setter='set_unit_price_w_tax')
|
|
order = fields.Char('Order')
|
|
description = fields.Char('Description', depends=['product'])
|
|
state = fields.Selection(INVOICE_STATES, 'Invoice State', readonly=True)
|
|
state_string = state.translated('state')
|
|
invoice_line = fields.Many2One('account.invoice.line', 'Invoice Line',
|
|
readonly=True)
|
|
amount = fields.Function(fields.Numeric('Amount', digits=(16, 2)),
|
|
'get_amount')
|
|
amount_w_tax = fields.Function(fields.Numeric('Amount w Tax',
|
|
digits=(16, 2)), 'get_amount_w_tax')
|
|
taxed_amount = fields.Function(fields.Numeric('Amount with Tax',
|
|
digits=(16, 2)), 'get_taxed_amount')
|
|
kind = fields.Selection([
|
|
('accommodation', 'Accommodation'),
|
|
('product', 'Product'),
|
|
], 'Kind')
|
|
# Remove for deprecation
|
|
to_invoice = fields.Boolean('To Invoice', states={
|
|
'invisible': Bool(Eval('invoice_line')),
|
|
}, depends=['invoice_line'])
|
|
############################
|
|
storage = fields.Many2One('stock.location', 'Storage', domain=[
|
|
('type', '=', 'storage')], states={
|
|
'readonly': Bool(Eval('invoice_line')),
|
|
})
|
|
move = fields.Many2One('stock.move', 'Move', states={'readonly': True})
|
|
status = fields.Selection([
|
|
('paid', 'Paid'),
|
|
('pending', 'Pending'),
|
|
], 'Status', help="Status of Payment")
|
|
status_string = status.translated('status')
|
|
line_move = fields.Many2One('account.move.line', 'Line Acc. Move')
|
|
analytic_account = fields.Many2One('analytic_account.account',
|
|
'Analytic Account', domain=[
|
|
('type', '=', 'normal')
|
|
])
|
|
taxes = fields.Many2Many('hotel.folio_charge-account.tax', 'charge', 'tax',
|
|
'Taxes', order=[('tax.sequence', 'ASC'), ('tax.id', 'ASC')],
|
|
domain=[
|
|
('parent', '=', None), [
|
|
'OR',
|
|
('group', '=', None),
|
|
('group.kind', 'in', ['sale', 'both'])
|
|
],
|
|
],
|
|
)
|
|
origin = fields.Reference('Origin', selection='get_origin', readonly=True)
|
|
|
|
# @classmethod
|
|
# def delete(cls, records):
|
|
# target = []
|
|
# for rec in records:
|
|
# if rec.invoice_line or rec.move:
|
|
# continue
|
|
# target.append(rec)
|
|
# super().delete(target)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(FolioCharge, cls).__setup__()
|
|
cls._buttons.update({
|
|
'bill': {
|
|
'invisible': Eval('invoice_state') is not None,
|
|
},
|
|
})
|
|
|
|
@classmethod
|
|
def trigger_create(cls, records):
|
|
cls.set_number(records)
|
|
|
|
@classmethod
|
|
def set_number(cls, charges):
|
|
"""
|
|
Fill the number field with the charge sequence
|
|
"""
|
|
pool = Pool()
|
|
Config = pool.get('hotel.configuration')
|
|
config = Config.get_configuration()
|
|
|
|
for charge in charges:
|
|
if charge.number or not config.charge_sequence:
|
|
continue
|
|
number = config.charge_sequence.get()
|
|
cls.write([charge], {'number': number})
|
|
|
|
@classmethod
|
|
def _get_origin(cls):
|
|
'Return list of Model names for origin Reference'
|
|
return ['sale.sale', 'sale.line']
|
|
|
|
@classmethod
|
|
def get_origin(cls):
|
|
Model = Pool().get('ir.model')
|
|
get_name = Model.get_name
|
|
models = cls._get_origin()
|
|
return [(None, '')] + [(m, get_name(m)) for m in models]
|
|
|
|
@classmethod
|
|
def _add_taxes(cls, vlist):
|
|
pool = Pool()
|
|
Folio = pool.get('hotel.folio')
|
|
Product = pool.get('product.product')
|
|
for value in vlist:
|
|
folio_id = value.get('folio', None)
|
|
if folio_id:
|
|
folio = Folio(folio_id)
|
|
if folio.taxes_exception:
|
|
continue
|
|
product = Product(value['product'])
|
|
value['taxes'] = [('add', [
|
|
tax.id for tax in product.customer_taxes_used
|
|
])]
|
|
|
|
@classmethod
|
|
def _check_create(cls, vlist):
|
|
Folio = Pool().get('hotel.folio')
|
|
for value in vlist:
|
|
folio_id = value.get('folio', None)
|
|
if folio_id:
|
|
folio = Folio(folio_id)
|
|
if folio.charges_blocked:
|
|
raise UserError(gettext('hotel.msg_folio_charges_blocked'))
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
cls._check_create(vlist)
|
|
cls._add_taxes(vlist)
|
|
return super().create(vlist)
|
|
|
|
@staticmethod
|
|
def default_quantity():
|
|
return 1
|
|
|
|
@staticmethod
|
|
def default_kind():
|
|
return 'product'
|
|
|
|
@staticmethod
|
|
def default_status():
|
|
return 'pending'
|
|
|
|
@staticmethod
|
|
def default_to_invoice():
|
|
return True
|
|
|
|
@staticmethod
|
|
def default_date_service():
|
|
today = Pool().get('ir.date').today()
|
|
return today
|
|
|
|
def get_amount(self, name=None):
|
|
res = 0
|
|
if self.quantity and self.unit_price:
|
|
res = self.quantity * self.unit_price
|
|
return res
|
|
|
|
@classmethod
|
|
def set_unit_price_w_tax(cls, charges, name, value):
|
|
Tax = Pool().get('account.tax')
|
|
to_write = []
|
|
rvalue = Decimal(value).quantize(ROUND_TWO)
|
|
for charge in charges:
|
|
taxes = charge.product.customer_taxes_used
|
|
unit_price = Tax.reverse_compute(rvalue, taxes)
|
|
unit_price = Decimal(unit_price).quantize(ROUND_TWO)
|
|
to_write.extend([[charge], {
|
|
'unit_price': unit_price,
|
|
}])
|
|
cls.write(*to_write)
|
|
|
|
def get_amount_w_tax(self, name=None):
|
|
res = 0
|
|
if self.quantity and self.unit_price_w_tax:
|
|
res = self.quantity * self.unit_price_w_tax
|
|
return res
|
|
|
|
def get_unit_price_w_tax(self, name=None):
|
|
Tax = Pool().get('account.tax')
|
|
res = self.unit_price or 0
|
|
if self.unit_price and not self.folio.taxes_exception:
|
|
_taxes = Tax.compute(
|
|
self.product.template.customer_taxes_used,
|
|
self.unit_price, 1)
|
|
for tax in _taxes:
|
|
res += tax['amount']
|
|
return round(res, 2)
|
|
|
|
def do_stock_move(self, name=None):
|
|
pool = Pool()
|
|
Location = pool.get('stock.location')
|
|
Move = pool.get('stock.move')
|
|
Config = Pool().get('hotel.configuration')
|
|
# FIXME add origin
|
|
|
|
locs_customer = Location.search([
|
|
('type', '=', 'customer')
|
|
])
|
|
customer_loc_id = locs_customer[0].id
|
|
|
|
config = Config.get_configuration()
|
|
if not self.storage and config.storage_by_default:
|
|
storage_id = config.storage_by_default.id
|
|
else:
|
|
storage_id = self.storage.id
|
|
|
|
move, = Move.create([{
|
|
'product': self.product.id,
|
|
'effective_date': self.date_service,
|
|
'quantity': self.quantity,
|
|
'unit_price': self.product.cost_price,
|
|
'uom': self.product.default_uom.id,
|
|
'from_location': storage_id,
|
|
'to_location': customer_loc_id,
|
|
'origin': str(self),
|
|
}])
|
|
self.move = move.id
|
|
Move.do([move])
|
|
self.save()
|
|
|
|
def get_taxed_amount(self, name=None):
|
|
res = 0
|
|
if self.quantity and self.unit_price_w_tax:
|
|
res = self.quantity * self.unit_price_w_tax
|
|
return res
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
def bill(cls, records):
|
|
cls.create_sales(records)
|
|
|
|
@fields.depends('unit_price', 'folio', 'product', 'description', 'taxes')
|
|
def on_change_product(self):
|
|
if self.product:
|
|
self.unit_price = round(self.product.template.list_price, 2)
|
|
self.unit_price_w_tax = round(self.product.sale_price_taxed, 2)
|
|
self.description = self.product.template.name
|
|
if self.folio and not self.folio.taxes_exception:
|
|
self.taxes = [
|
|
tax.id for tax in self.product.customer_taxes_used
|
|
]
|
|
|
|
def get_move_line(self, account, party, amount):
|
|
debit = credit = _ZERO
|
|
if amount[0] == 'debit':
|
|
debit = amount[1]
|
|
else:
|
|
credit = amount[1]
|
|
res = {
|
|
'description': self.description,
|
|
'debit': debit,
|
|
'credit': credit,
|
|
'account': account.id,
|
|
'party': party.id,
|
|
}
|
|
line = self._get_entry(res)
|
|
return line
|
|
|
|
def get_analytic_lines(self, account, line, date):
|
|
"Yield analytic lines for the accounting line and the date"
|
|
lines = []
|
|
amount = line['debit'] or line['credit']
|
|
for account, amount in account.distribute(amount):
|
|
analytic_line = {}
|
|
analytic_line['debit'] = amount if line['debit'] else Decimal(0)
|
|
analytic_line['credit'] = amount if line['credit'] else Decimal(0)
|
|
analytic_line['account'] = account
|
|
analytic_line['date'] = date
|
|
lines.append(analytic_line)
|
|
return lines
|
|
|
|
def _get_entry(self, line_move):
|
|
line_move['analytic_lines'] = []
|
|
if self.analytic_account:
|
|
entry = self.analytic_account
|
|
to_create = []
|
|
# Just debits must to create entries, credits not
|
|
if entry.account and line_move['credit'] > 0:
|
|
to_create.extend(self.get_analytic_lines(
|
|
entry.account,
|
|
line_move, self.date_service
|
|
))
|
|
if to_create:
|
|
line_move['analytic_lines'] = [('create', to_create)]
|
|
return line_move
|
|
|
|
|
|
class FolioOccupancy(ModelSQL, ModelView):
|
|
'Folio Occupancy'
|
|
__name__ = 'hotel.folio.occupancy'
|
|
folio = fields.Many2One('hotel.folio', 'Folio', required=True,
|
|
ondelete='CASCADE')
|
|
occupancy_date = fields.Date('Occupancy Date', required=True)
|
|
charge = fields.Many2One('hotel.folio.charge', 'Charge')
|
|
price_list = fields.Many2One('hotel.price_list', 'Price List')
|
|
unit_price = fields.Numeric('Unit Price', digits=(16, 2), states={
|
|
'readonly': True,
|
|
})
|
|
unit_price_w_tax = fields.Function(
|
|
fields.Numeric('Unit Price W Tax'), 'get_unit_price_w_tax')
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('confirmed', 'Confirmed'),
|
|
('invoiced', 'Invoiced'),
|
|
], 'State', readonly=True)
|
|
|
|
def get_unit_price_w_tax(self, name=None):
|
|
Tax = Pool().get('account.tax')
|
|
res = self.unit_price or 0
|
|
if self.unit_price and not self.folio.taxes_exception:
|
|
values = Tax.compute(
|
|
self.folio.product.template.customer_taxes_used,
|
|
self.unit_price, 1)
|
|
if values:
|
|
value = values[0]
|
|
res = value['base'] + value['amount']
|
|
return res
|
|
|
|
|
|
class OpenMigrationStart(ModelView):
|
|
'Open Migration Start'
|
|
__name__ = 'hotel.open_migration.start'
|
|
start_date = fields.Date('Start Date', required=True)
|
|
end_date = fields.Date('End Date', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class OpenMigration(Wizard):
|
|
'Open Migration'
|
|
__name__ = 'hotel.open_migration'
|
|
start = StateView(
|
|
'hotel.open_migration.start',
|
|
'hotel.open_migration_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Open', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateAction('hotel.report_migration')
|
|
|
|
def do_print_(self, action):
|
|
data = {
|
|
'start_date': self.start.start_date,
|
|
'end_date': self.start.end_date,
|
|
'company': self.start.company.id,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class Migration(Report):
|
|
'Hotel Migration'
|
|
__name__ = 'hotel.migration'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
Folio = pool.get('hotel.folio')
|
|
Company = pool.get('company.company')
|
|
records = Folio.search([
|
|
('arrival_date', '>=', data['start_date']),
|
|
('arrival_date', '<=', data['end_date']),
|
|
('main_guest', '!=', None),
|
|
('registration_state', 'in', ['check_in', 'check_out']),
|
|
], order=[('arrival_date', 'ASC')])
|
|
end_date = data['end_date']
|
|
_records = []
|
|
company = Company(data['company'])
|
|
mig_code = company.mig_code
|
|
city_code = company.city_code
|
|
for folio in records:
|
|
for guest in folio.guests:
|
|
for move in ('E', 'S'):
|
|
if move == 'S' and folio.departure_date > end_date:
|
|
continue
|
|
|
|
if move == 'E':
|
|
move_date = folio.arrival_date
|
|
else:
|
|
move_date = folio.departure_date
|
|
|
|
if not all([
|
|
guest.origin_country,
|
|
guest.target_country,
|
|
guest.nationality
|
|
]):
|
|
continue
|
|
_records.append({
|
|
'mig_code': mig_code,
|
|
'city_code': city_code,
|
|
'type_doc': guest.type_document,
|
|
'id_number': guest.id_number,
|
|
'nationality': guest.nationality.code_numeric,
|
|
'first_family_name': guest.first_family_name,
|
|
'second_family_name': guest.second_family_name,
|
|
'first_name': guest.first_name,
|
|
'move': move,
|
|
'move_date': move_date,
|
|
'origin': guest.origin_country.code_numeric,
|
|
'target': guest.target_country.code_numeric,
|
|
'birthday': guest.birthday,
|
|
})
|
|
report_context['records'] = _records
|
|
return report_context
|
|
|
|
|
|
class OperationReport(Report):
|
|
__name__ = 'hotel.operation'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
user = Pool().get('res.user')(Transaction().user)
|
|
report_context['company'] = user.company
|
|
return report_context
|
|
|
|
|
|
class CheckOutOperationFailed(ModelView):
|
|
'Check Out Operation Failed'
|
|
__name__ = 'hotel.operation.check_out.failed'
|
|
|
|
|
|
class CheckOutOperation(Wizard):
|
|
'Check Out Operation'
|
|
__name__ = 'hotel.operation.check_out'
|
|
start = StateTransition()
|
|
failed = StateView(
|
|
'hotel.operation.check_out.failed',
|
|
'hotel.operation_check_out_failed_view_form', [
|
|
Button('Force Check Out', 'force', 'tryton-forward'),
|
|
Button('Cancel', 'end', 'tryton-cancel', True),
|
|
])
|
|
force = StateTransition()
|
|
|
|
def transition_start(self):
|
|
Operation = Pool().get('hotel.operation')
|
|
active_id = Transaction().context['active_id']
|
|
operations = Operation.browse([active_id])
|
|
if Operation.validate_check_out_date(operations):
|
|
return 'end'
|
|
else:
|
|
return 'failed'
|
|
|
|
def transition_force(self):
|
|
Operation = Pool().get('hotel.operation')
|
|
active_id = Transaction().context['active_id']
|
|
operations = Operation.browse([active_id])
|
|
Operation.check_out(operations)
|
|
return 'end'
|
|
|
|
|
|
class UpdateOccupancyStart(ModelView):
|
|
'Update Occupancy'
|
|
__name__ = 'hotel.folio_update_occupancy.start'
|
|
departure_date = fields.Date('Departure Date', states={
|
|
'readonly': Bool(Eval('room'))
|
|
})
|
|
room = fields.Many2One('hotel.room', 'New Room', domain=[
|
|
('id', 'in', Eval('targets')),
|
|
])
|
|
unit_price = fields.Numeric('Unit Price', digits=(16, 2))
|
|
accommodation = fields.Many2One('product.product', 'Accommodation',
|
|
domain=[
|
|
('template.kind', '=', 'accommodation'),
|
|
])
|
|
targets = fields.Function(fields.Many2Many('hotel.room', None, None,
|
|
'Targets', depends=['departure_date', 'accommodation', 'room']),
|
|
'on_change_with_targets')
|
|
# tranfer_charges = fields.Boolean('Transfer Charges')
|
|
|
|
@fields.depends('departure_date', 'accommodation', 'room')
|
|
def on_change_with_targets(self, name=None):
|
|
pool = Pool()
|
|
RoomTemplate = pool.get('hotel.room-product.template')
|
|
Folio = pool.get('hotel.folio')
|
|
folio = Folio(Transaction().context.get('active_id'))
|
|
res = []
|
|
|
|
acco = folio.product
|
|
if self.accommodation:
|
|
acco = self.accommodation
|
|
|
|
departure_date = folio.departure_date
|
|
if self.departure_date:
|
|
departure_date = self.departure_date
|
|
|
|
room_templates = RoomTemplate.search([
|
|
('template.accommodation_capacity', '>=', acco.accommodation_capacity),
|
|
('template', '=', acco.template.id)
|
|
])
|
|
rooms_ids = [t.room.id for t in room_templates]
|
|
|
|
res = Folio.get_available_rooms(
|
|
folio.arrival_date,
|
|
departure_date,
|
|
rooms_ids=rooms_ids
|
|
)
|
|
return res
|
|
|
|
|
|
class UpdateOccupancy(Wizard):
|
|
'Update Occupancy'
|
|
__name__ = 'hotel.folio_update_occupancy'
|
|
"""
|
|
this is the wizard that allows to the frontdesk employee to change
|
|
original room o departure date.
|
|
"""
|
|
start = StateView(
|
|
'hotel.folio_update_occupancy.start',
|
|
'hotel.folio_update_occupancy_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Change', 'update', 'tryton-ok'),
|
|
]
|
|
)
|
|
update = StateTransition()
|
|
|
|
def default_start(self, fields):
|
|
targets = self.start.on_change_with_targets()
|
|
res = {
|
|
'targets': targets
|
|
}
|
|
return res
|
|
|
|
def transition_update(self):
|
|
pool = Pool()
|
|
Folio = pool.get('hotel.folio')
|
|
folio, = Folio.browse([Transaction().context.get('active_id')])
|
|
dep_date = self.start.departure_date
|
|
price = self.start.unit_price
|
|
room = self.start.room
|
|
folio.update_occupancy(dep_date=dep_date, price=price)
|
|
if dep_date:
|
|
folio.departure_date = dep_date
|
|
if price:
|
|
folio.unit_price = price
|
|
if room:
|
|
folio.room = room.id
|
|
folio.save()
|
|
return 'end'
|
|
|
|
|
|
class OperationByConsumerReport(Report):
|
|
__name__ = 'hotel.folio.charge'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
Folio = pool.get('hotel.folio')
|
|
user = pool.get('res.user')(Transaction().user)
|
|
consumer_lines = []
|
|
total_amount = 0
|
|
# if records:
|
|
# line = records[0]
|
|
# folio = Folio(line.folio.id)
|
|
# total_amount = folio.room_amount
|
|
# for ch in folio.lines:
|
|
# if ch.invoice_to and ch.invoice_to.id == line.invoice_to.id:
|
|
# consumer_lines.append(l)
|
|
# total_amount += l.amount
|
|
# setattr(folio, 'lines', consumer_lines)
|
|
# setattr(folio, 'total_amount', total_amount)
|
|
#
|
|
# report_context['records'] = [folio]
|
|
report_context['company'] = user.company
|
|
return report_context
|
|
|
|
|
|
class OperationBill(Wizard):
|
|
'Operation Bill'
|
|
__name__ = 'hotel.folio.bill'
|
|
start_state = 'create_bill'
|
|
create_bill = StateTransition()
|
|
|
|
def transition_create_bill(self):
|
|
Folio = Pool().get('hotel.folio')
|
|
ids = Transaction().context['active_ids']
|
|
folios = Folio.browse(ids)
|
|
Folio.bill(folios)
|
|
return 'end'
|
|
|
|
|
|
class OperationVoucher(ModelSQL):
|
|
'Operation - Voucher'
|
|
__name__ = 'hotel.folio-account.voucher'
|
|
_table = 'folio_vouchers_rel'
|
|
folio = fields.Many2One('hotel.folio', 'Folio', ondelete='CASCADE',
|
|
required=True)
|
|
voucher = fields.Many2One('account.voucher', 'Voucher', required=True,
|
|
domain=[('voucher_type', '=', 'receipt')], ondelete='RESTRICT')
|
|
|
|
@classmethod
|
|
def set_voucher_origin(cls, voucher_id, folio_id):
|
|
cls.create([{
|
|
'voucher': voucher_id,
|
|
'folio': folio_id,
|
|
}])
|
|
|
|
|
|
class ReverseCheckout(Wizard):
|
|
'Reverse Checkout'
|
|
__name__ = 'hotel.folio.reverse_checkout'
|
|
start_state = 'reverse_checkout'
|
|
reverse_checkout = StateTransition()
|
|
|
|
def transition_reverse_checkout(self):
|
|
Folio = Pool().get('hotel.folio')
|
|
folio_id = Transaction().context['active_id']
|
|
if folio_id:
|
|
Folio.check_in([Folio(folio_id)])
|
|
return 'end'
|
|
|
|
|
|
class FolioStockMove(ModelSQL):
|
|
'Folio - Stock Move'
|
|
__name__ = 'hotel.folio-stock.move'
|
|
_table = 'hotel_folio_stock_move_rel'
|
|
folio = fields.Many2One('hotel.folio', 'Folio',
|
|
ondelete='CASCADE', required=True)
|
|
move = fields.Many2One('stock.move', 'Move',
|
|
ondelete='RESTRICT', required=True)
|
|
|
|
|
|
class StatisticsByMonthStart(ModelView):
|
|
'Statistics By Month Start'
|
|
__name__ = 'hotel.statistics_by_month.start'
|
|
period = fields.Many2One('account.period', 'Period', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class StatisticsByMonth(Wizard):
|
|
'Statistics By Month'
|
|
__name__ = 'hotel.statistics_by_month'
|
|
start = StateView(
|
|
'hotel.statistics_by_month.start',
|
|
'hotel.print_hotel_statistics_by_month_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Open', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateAction('hotel.statistics_by_month_report')
|
|
|
|
def do_print_(self, action):
|
|
data = {
|
|
'period': self.start.period.id,
|
|
'company': self.start.company.id,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class StatisticsByMonthReport(Report):
|
|
'Hotel Statistics By Month'
|
|
__name__ = 'hotel.statistics_by_month.report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
Operation = Pool().get('hotel.folio')
|
|
Period = Pool().get('account.period')
|
|
Company = Pool().get('company.company')
|
|
Rooms = Pool().get('hotel.room')
|
|
period = Period(data['period'])
|
|
company = Company(data['company'])
|
|
month_days = (period.end_date - period.start_date).days + 1
|
|
rooms_available = []
|
|
rooms_sold = []
|
|
beds_available = []
|
|
beds_sold = []
|
|
local_guests = []
|
|
foreign_guests = []
|
|
room1_sold = []
|
|
room2_sold = []
|
|
room_suite_sold = []
|
|
room_other_sold = []
|
|
guests_one_ng = []
|
|
guests_two_ng = []
|
|
guests_three_ng = []
|
|
guests_four_ng = []
|
|
guests_five_ng = []
|
|
guests_six_ng = []
|
|
guests_seven_ng = []
|
|
guests_eight_ng = []
|
|
guests_permanent_ng = []
|
|
reason_bussiness = []
|
|
reason_bussiness_foreign = []
|
|
reason_leisure = []
|
|
reason_leisure_foreign = []
|
|
reason_convention = []
|
|
reason_convention_foreign = []
|
|
reason_health = []
|
|
reason_health_foreign = []
|
|
reason_transport = []
|
|
reason_transport_foreign = []
|
|
reason_other = []
|
|
reason_other_foreign = []
|
|
total_guests = []
|
|
folios = Operation.search(['OR', [
|
|
('start_date', '>=', period.start_date),
|
|
('start_date', '<=', period.end_date),
|
|
('kind', '=', 'occupancy'),
|
|
], [
|
|
('end_date', '>=', period.start_date),
|
|
('end_date', '<=', period.end_date),
|
|
('kind', '=', 'occupancy'),
|
|
]
|
|
])
|
|
rooms = Rooms.search([])
|
|
rooms_available = [len(rooms) * month_days]
|
|
for r in rooms:
|
|
beds_available.append(
|
|
r.main_accommodation.accommodation_capacity * month_days
|
|
)
|
|
|
|
def _get_ctx_dates(op):
|
|
start_date = op.start_date
|
|
end_date = op.end_date
|
|
if op.start_date < period.start_date:
|
|
start_date = period.start_date
|
|
if op.end_date > period.end_date:
|
|
end_date = period.end_date
|
|
|
|
sub_dates = [
|
|
str(start_date + timedelta(n))
|
|
for n in range((end_date - start_date).days)
|
|
]
|
|
return sub_dates
|
|
|
|
for op in folios:
|
|
room_capacity = op.room.main_accommodation.accommodation_capacity
|
|
ctx_dates = _get_ctx_dates(op)
|
|
len_ctx_dates = len(ctx_dates)
|
|
rooms_sold.append(len_ctx_dates)
|
|
beds_sold.append(len_ctx_dates * room_capacity)
|
|
qty_guests = len(op.guests)
|
|
if not qty_guests:
|
|
qty_guests = 1
|
|
total_guests.append(qty_guests)
|
|
is_local = True
|
|
if op.main_guest.type_document == '41':
|
|
is_local = False
|
|
foreign_guests.append(qty_guests)
|
|
else:
|
|
local_guests.append(qty_guests)
|
|
|
|
if room_capacity == 1:
|
|
room1_sold.append(qty_guests)
|
|
elif room_capacity == 2:
|
|
room2_sold.append(qty_guests)
|
|
elif room_capacity > 2:
|
|
room_suite_sold.append(qty_guests)
|
|
|
|
if len_ctx_dates == 1:
|
|
guests_one_ng.append(qty_guests)
|
|
elif len_ctx_dates == 2:
|
|
guests_two_ng.append(qty_guests)
|
|
elif len_ctx_dates == 3:
|
|
guests_three_ng.append(qty_guests)
|
|
elif len_ctx_dates == 4:
|
|
guests_four_ng.append(qty_guests)
|
|
elif len_ctx_dates == 5:
|
|
guests_five_ng.append(qty_guests)
|
|
elif len_ctx_dates == 6:
|
|
guests_six_ng.append(qty_guests)
|
|
elif len_ctx_dates == 7:
|
|
guests_seven_ng.append(qty_guests)
|
|
elif len_ctx_dates >= 8:
|
|
guests_eight_ng.append(qty_guests)
|
|
|
|
segment = 'bussiness'
|
|
if op.origin:
|
|
if hasattr(op.origin, 'booking'):
|
|
segment = op.origin.booking.segment
|
|
|
|
if segment == 'bussiness':
|
|
if is_local:
|
|
reason_bussiness.append(qty_guests)
|
|
else:
|
|
reason_bussiness_foreign.append(qty_guests)
|
|
elif segment == 'convention':
|
|
if is_local:
|
|
reason_convention.append(qty_guests)
|
|
else:
|
|
reason_convention_foreign.append(qty_guests)
|
|
elif segment == 'health':
|
|
if is_local:
|
|
reason_health.append(qty_guests)
|
|
else:
|
|
reason_health_foreign.append(qty_guests)
|
|
else:
|
|
if is_local:
|
|
reason_leisure.append(qty_guests)
|
|
else:
|
|
reason_leisure_foreign.append(qty_guests)
|
|
|
|
def _get_rate(val):
|
|
res = 0
|
|
if sum_total_guests > 0:
|
|
res = round(float(sum(val)) / sum_total_guests, 4)
|
|
return res
|
|
|
|
sum_total_guests = sum(total_guests)
|
|
rate_guests_one_ng = _get_rate(guests_one_ng)
|
|
rate_guests_two_ng = _get_rate(guests_two_ng)
|
|
rate_guests_three_ng = _get_rate(guests_three_ng)
|
|
rate_guests_four_ng = _get_rate(guests_four_ng)
|
|
rate_guests_five_ng = _get_rate(guests_five_ng)
|
|
rate_guests_six_ng = _get_rate(guests_six_ng)
|
|
rate_guests_seven_ng = _get_rate(guests_seven_ng)
|
|
rate_guests_eight_ng = _get_rate(guests_eight_ng)
|
|
|
|
report_context['period'] = period.name
|
|
report_context['company'] = company.party.name
|
|
report_context['rooms_available'] = sum(rooms_available)
|
|
report_context['rooms_sold'] = sum(rooms_sold)
|
|
report_context['beds_available'] = sum(beds_available)
|
|
report_context['beds_sold'] = sum(beds_sold)
|
|
report_context['local_guests'] = sum(local_guests)
|
|
report_context['foreign_guests'] = sum(foreign_guests)
|
|
report_context['room1_sold'] = sum(room1_sold)
|
|
report_context['room2_sold'] = sum(room2_sold)
|
|
report_context['room_suite_sold'] = sum(room_suite_sold)
|
|
report_context['room_other_sold'] = sum(room_other_sold)
|
|
report_context['guests_one_ng'] = rate_guests_one_ng
|
|
report_context['guests_two_ng'] = rate_guests_two_ng
|
|
report_context['guests_three_ng'] = rate_guests_three_ng
|
|
report_context['guests_four_ng'] = rate_guests_four_ng
|
|
report_context['guests_five_ng'] = rate_guests_five_ng
|
|
report_context['guests_six_ng'] = rate_guests_six_ng
|
|
report_context['guests_seven_ng'] = rate_guests_seven_ng
|
|
report_context['guests_eight_ng'] = rate_guests_eight_ng
|
|
report_context['guests_permanent_ng'] = sum(guests_permanent_ng)
|
|
report_context['reason_bussiness'] = sum(reason_bussiness)
|
|
report_context['reason_bussiness_foreign'] = sum(reason_bussiness_foreign)
|
|
report_context['reason_leisure'] = sum(reason_leisure)
|
|
report_context['reason_leisure_foreign'] = sum(reason_leisure_foreign)
|
|
report_context['reason_convention'] = sum(reason_convention)
|
|
report_context['reason_convention_foreign'] = sum(reason_convention_foreign)
|
|
report_context['reason_health'] = sum(reason_health)
|
|
report_context['reason_health_foreign'] = sum(reason_health_foreign)
|
|
report_context['reason_transport'] = sum(reason_transport)
|
|
report_context['reason_transport_foreign'] = sum(reason_transport_foreign)
|
|
report_context['reason_other_foreign'] = sum(reason_other_foreign)
|
|
report_context['reason_other'] = sum(reason_other)
|
|
|
|
return report_context
|
|
|
|
|
|
class GuestsListStart(ModelView):
|
|
'Guests List Start'
|
|
__name__ = 'hotel.print_guests_list.start'
|
|
date = fields.Date('Date', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
|
|
@staticmethod
|
|
def default_date():
|
|
Date_ = Pool().get('ir.date')
|
|
return Date_.today()
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class GuestsList(Wizard):
|
|
'Guests List'
|
|
__name__ = 'hotel.print_guests_list'
|
|
start = StateView(
|
|
'hotel.print_guests_list.start',
|
|
'hotel.print_guests_list_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Open', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateReport('hotel.guests_list.report')
|
|
|
|
def do_print_(self, action):
|
|
company = self.start.company
|
|
data = {
|
|
'date': self.start.date,
|
|
'company': company.id,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class GuestsListReport(Report):
|
|
__name__ = 'hotel.guests_list.report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
Folio = pool.get('hotel.folio')
|
|
Room = pool.get('hotel.room')
|
|
User = pool.get('res.user')
|
|
user_id = Transaction().user
|
|
user = User(user_id)
|
|
dom = [
|
|
('arrival_date', '<=', data['date']),
|
|
('departure_date', '>', data['date']),
|
|
]
|
|
folios = Folio.search(dom, order=[('room.code', 'ASC')])
|
|
|
|
total_guests = []
|
|
rooms_occupied = []
|
|
for op in folios:
|
|
total_guests.append(len(op.guests))
|
|
rooms_occupied.append(op.room.id)
|
|
|
|
rooms = Room.search_read([])
|
|
|
|
_rooms_occupied = len(set(rooms_occupied))
|
|
num_rooms = len(rooms)
|
|
if num_rooms:
|
|
occupancy_rate = round(_rooms_occupied / num_rooms, 2)
|
|
else:
|
|
occupancy_rate = 0
|
|
|
|
guests = []
|
|
for fo in folios:
|
|
for guest in fo.guests:
|
|
guests.append({
|
|
'room': fo.room.code,
|
|
'guest': guest.name,
|
|
'party': fo.booking.party.name if fo.booking.party else '',
|
|
'arrival_date': fo.arrival_date,
|
|
'departure_date': fo.departure_date,
|
|
'nights_quantity': fo.nights_quantity,
|
|
'state': fo.registration_state,
|
|
})
|
|
report_context['records'] = guests
|
|
report_context['total_rooms'] = _rooms_occupied
|
|
report_context['total_guests'] = sum(total_guests)
|
|
report_context['occupancy_rate'] = occupancy_rate
|
|
report_context['company'] = Company(data['company'])
|
|
report_context['date'] = data['date']
|
|
report_context['print_date'] = datetime.now()
|
|
report_context['user'] = user.name
|
|
return report_context
|
|
|
|
|
|
class RegistrationCardReport(Report):
|
|
__name__ = 'hotel.folio.registration_card'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
user = Pool().get('res.user')(Transaction().user)
|
|
report_context['company'] = user.company
|
|
_records = []
|
|
for rec in records:
|
|
if not rec.registration_card:
|
|
continue
|
|
_records.append(rec)
|
|
report_context['records'] = _records
|
|
return report_context
|
|
|
|
|
|
class HotelFolioTax(ModelSQL):
|
|
'Hotel Folio - Tax'
|
|
__name__ = 'hotel.folio-account.tax'
|
|
_table = 'hotel_folio_account_tax'
|
|
folio = fields.Many2One('hotel.folio', 'Folio', ondelete='CASCADE',
|
|
required=True)
|
|
tax = fields.Many2One('account.tax', 'Tax', ondelete='RESTRICT',
|
|
required=True)
|
|
|
|
|
|
class HotelFolioChargeTax(ModelSQL):
|
|
'Hotel Folio Charge - Tax'
|
|
__name__ = 'hotel.folio_charge-account.tax'
|
|
_table = 'hotel_folio_charge_account_tax'
|
|
charge = fields.Many2One('hotel.folio.charge', 'Charge',
|
|
ondelete='CASCADE', required=True)
|
|
tax = fields.Many2One('account.tax', 'Tax', ondelete='RESTRICT',
|
|
required=True)
|
|
|
|
|
|
class FolioAuditStart(ModelView):
|
|
'Folio Audit Start'
|
|
__name__ = 'hotel.folio_audit.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)
|
|
registration_state = fields.Selection([
|
|
('check_in', 'Check In'),
|
|
('check_out', 'Check Out'),
|
|
('', ''),
|
|
], 'Reg. State')
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class FolioAudit(Wizard):
|
|
'Folio Audit'
|
|
__name__ = 'hotel.folio_audit'
|
|
start = StateView(
|
|
'hotel.folio_audit.start',
|
|
'hotel.print_folio_audit_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Open', 'print_', 'tryton-print', default=True),
|
|
])
|
|
print_ = StateReport('hotel.folio_audit.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,
|
|
'state': self.start.registration_state,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class FolioAuditReport(Report):
|
|
__name__ = 'hotel.folio_audit.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')
|
|
dom = [
|
|
('arrival_date', '>=', data['start_date']),
|
|
('arrival_date', '<=', data['end_date']),
|
|
]
|
|
if data['state']:
|
|
dom.append(
|
|
('state', '=', data['state'])
|
|
)
|
|
_records = Folio.search(dom, order=[('arrival_date', 'ASC')])
|
|
report_context['records'] = _records
|
|
report_context['company'] = Company(data['company'])
|
|
return report_context
|