Add forecast operation

This commit is contained in:
oscar alvarez 2023-01-29 17:50:49 -05:00
parent e95848ff84
commit 293dafdc07
13 changed files with 419 additions and 15 deletions

View File

@ -61,6 +61,8 @@ def register():
booking.BookingVoucher,
booking.RoomsOccupancyStart,
booking.RevenueForecastStart,
booking.OperationForecastStart,
booking.RevenueSegmentationStart,
booking.UpdateHolderStart,
booking.BookingChannelCommision,
booking.BillBookingStart,
@ -103,6 +105,8 @@ def register():
Pool.register(
booking.BookingReport,
booking.RevenueForecastReport,
booking.OperationForecastReport,
booking.RevenueSegmentationReport,
booking.RoomsOccupancyReport,
booking.BookingStatusReport,
booking.ManagerReport,
@ -119,6 +123,8 @@ def register():
Pool.register(
booking.SelectRooms,
booking.RevenueForecast,
booking.OperationForecast,
booking.RevenueSegmentation,
booking.RoomsOccupancy,
booking.BookingStatus,
booking.UpdateHolder,

View File

@ -2,6 +2,7 @@
# this repository contains the full copyright notices and license terms.
from datetime import datetime, timedelta, date
from decimal import Decimal
import copy
from trytond.model import Workflow, ModelView, ModelSQL, fields
from trytond.wizard import (
@ -59,6 +60,7 @@ class Booking(Workflow, ModelSQL, ModelView):
children_num = fields.Function(fields.Integer('Children Number'),
'get_person_num')
group = fields.Boolean('Group', states=STATES_BLOCKED)
corporative = fields.Boolean('Corporative', states=STATES_BLOCKED)
complementary = fields.Boolean('Complementary', states=STATES_BLOCKED)
type_complementary = fields.Selection(COMPLEMENTARY, 'Type Complementary',
states=STATES_BLOCKED)
@ -183,6 +185,8 @@ class Booking(Workflow, ModelSQL, ModelView):
('post_checkin', 'Post Checkin'),
], 'Collection Mode', required=False,
help="Commission collection mode")
link_web_checkin = fields.Function(fields.Char('Link Web Check-in'),
'get_link_web_checkin')
@classmethod
def __setup__(cls):
@ -939,6 +943,14 @@ class Booking(Workflow, ModelSQL, ModelView):
invoice.save()
invoice.update_taxes([invoice])
def get_link_web_checkin(self, name=None):
transaction = Transaction().context
pool = Pool()
host, _ = transaction['_request']['http_host'].split(":")
db = pool.database_name
link = f'http://{host}:3100/app/{db}/web_checkin/{self.id}'
return link
@classmethod
def _get_new_invoice(cls, data):
pool = Pool()
@ -1384,9 +1396,9 @@ class RevenueForecastStart(ModelView):
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscalyear',
required=True)
period = fields.Many2One('account.period', 'Period', required=True,
domain=[
('fiscalyear', '=', Eval('fiscalyear')),
])
domain=[
('fiscalyear', '=', Eval('fiscalyear')),
])
@staticmethod
def default_fiscalyear():
@ -2450,3 +2462,325 @@ class BillBooking(Wizard):
Booking.concile_charges([bk])
Booking.check_finished([bk])
return 'end'
class OperationForecastStart(ModelView):
'Operation Forecast Start'
__name__ = 'hotel.print_operation_forecast.start'
company = fields.Many2One('company.company', 'Company', required=True)
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscalyear',
required=True)
period = fields.Many2One('account.period', 'Period', required=True,
domain=[
('fiscalyear', '=', Eval('fiscalyear')),
])
@staticmethod
def default_fiscalyear():
Fiscalyear = Pool().get('account.fiscalyear')
fiscalyear, = Fiscalyear.search([], limit=1, order=[
('start_date', 'DESC')])
return fiscalyear.id
@staticmethod
def default_company():
return Transaction().context.get('company')
class OperationForecast(Wizard):
'Operation Forecast'
__name__ = 'hotel.print_operation_forecast'
start = StateView(
'hotel.print_operation_forecast.start',
'hotel.print_operation_forecast_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
])
print_ = StateReport('hotel.operation_forecast.report')
def do_print_(self, action):
company = self.start.company
data = {
'period': self.start.period.id,
'company': company.id,
}
return action, data
def transition_print_(self):
return 'end'
class OperationForecastReport(Report):
"Operation Forecast Report"
__name__ = 'hotel.operation_forecast.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
Period = pool.get('account.period')
Room = pool.get('hotel.room')
Occupancy = pool.get('hotel.folio.occupancy')
period = Period(data['period'])
start_date = period.start_date
end_date = period.end_date
range_day = (end_date - start_date).days + 1
default_data = {
'available_rooms': 0,
'occupied_rooms': 0,
'pax': 0,
'rooms_complementary': 0,
'info_complementary': '',
'rooms_maintenance': 0,
'occupancy_rate': 0,
'revenue': 0,
'balance': 0,
'average_price': 0,
}
records = []
rooms = Room.search([])
available_rooms = len(rooms)
pax = []
occupied_rooms = []
revenue = []
balance = 0
totals = copy.deepcopy(default_data)
today = date.today()
for key, value in default_data.items():
totals[key + '_effective'] = []
totals[key + '_forecast'] = []
for nday in range(range_day):
rooms_complementary = 0
rooms_maintenance = 0
tdate = start_date + timedelta(nday)
data_day = copy.deepcopy(default_data)
data_day['date'] = tdate
is_sunday = 'no'
if tdate.weekday() == 6:
is_sunday = 'yes'
data_day['sunday'] = is_sunday
occupancies = Occupancy.search([
('occupancy_date', '=', str(tdate))
])
occ_rooms = len(occupancies)
data_day['available_rooms'] = available_rooms
data_day['occupied_rooms'] = occ_rooms
occupied_rooms.append(occ_rooms)
sum_pax = sum([len(occ.folio.guests) for occ in occupancies])
data_day['pax'] = sum_pax
pax.append(sum_pax)
amounts = []
for occ in occupancies:
if occ.charge:
amount = occ.charge.unit_price
else:
amount = occ.unit_price
amounts.append(amount)
total_amounts = sum(amounts)
data_day['revenue'] = total_amounts
revenue.append(total_amounts)
balance += total_amounts
data_day['balance'] = balance
data_day['occupancy_rate'] = round(
occ_rooms / available_rooms, 2)
average_price = None
if occ_rooms:
average_price = round(total_amounts / occ_rooms, 2)
data_day['average_price'] = average_price
records.append(data_day)
if tdate < today:
row = '_effective'
else:
row = '_forecast'
totals['available_rooms' + row].append(available_rooms)
totals['occupied_rooms' + row].append(occ_rooms)
totals['pax' + row].append(sum_pax)
totals['rooms_complementary' + row].append(rooms_complementary)
totals['rooms_maintenance' + row].append(rooms_maintenance)
totals['revenue' + row].append(total_amounts)
for field in (
'available_rooms', 'occupied_rooms', 'pax',
'rooms_complementary', 'revenue', 'rooms_maintenance'):
_effective = field + '_effective'
_forecast = field + '_forecast'
totals[_effective] = sum(totals[_effective])
totals[_forecast] = sum(totals[_forecast])
average_price_effective = ''
average_price_forecast = ''
occupancy_rate_effective = ''
occupancy_rate_forecast = ''
if totals['occupied_rooms_effective'] > 0:
average_price_effective = totals['revenue_effective'] / totals['occupied_rooms_effective']
average_price_forecast = totals['revenue_forecast'] / totals['occupied_rooms_forecast']
if totals['available_rooms_effective'] > 0:
occupancy_rate_effective = totals['occupied_rooms_effective'] / totals['available_rooms_effective']
occupancy_rate_forecast = totals['occupied_rooms_forecast'] / totals['available_rooms_forecast']
totals['occupancy_rate_effective'] = occupancy_rate_effective
totals['occupancy_rate_forecast'] = occupancy_rate_forecast
totals['average_price_effective'] = average_price_effective
totals['average_price_forecast'] = average_price_forecast
data.update(totals)
total_available_rooms = len(rooms) * range_day
total_occupied_rooms = sum(occupied_rooms)
data['available_rooms'] = total_available_rooms
data['occupied_rooms'] = total_occupied_rooms
data['pax'] = sum(pax)
data['revenue'] = sum(revenue)
data['balance'] = balance
_average_price = None
_occupancy_rate = None
if total_occupied_rooms > 0:
_average_price = sum(revenue) / sum(occupied_rooms)
if total_available_rooms > 0:
_occupancy_rate = round(
total_occupied_rooms / total_available_rooms, 2)
data['average_price'] = _average_price
data['occupancy_rate'] = _occupancy_rate
report_context['records'] = records
report_context['period'] = period.name
report_context['company'] = Company(data['company'])
return report_context
class RevenueSegmentationStart(ModelView):
'Revenue Segmentation Start'
__name__ = 'hotel.revenue_segmentation.start'
company = fields.Many2One('company.company', 'Company', required=True)
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscalyear',
required=True)
period = fields.Many2One('account.period', 'Period', required=True,
domain=[
('fiscalyear', '=', Eval('fiscalyear')),
])
@staticmethod
def default_fiscalyear():
Fiscalyear = Pool().get('account.fiscalyear')
fiscalyear, = Fiscalyear.search([], limit=1, order=[
('start_date', 'DESC')])
return fiscalyear.id
@staticmethod
def default_company():
return Transaction().context.get('company')
class RevenueSegmentation(Wizard):
'Revenue Segmentation'
__name__ = 'hotel.revenue_segmentation'
start = StateView(
'hotel.revenue_segmentation.start',
'hotel.revenue_segmentation_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
])
print_ = StateReport('hotel.revenue_segmentation.report')
def do_print_(self, action):
company = self.start.company
data = {
'period': self.start.period.id,
'company': company.id,
}
return action, data
def transition_print_(self):
return 'end'
class RevenueSegmentationReport(Report):
"Revenue Segmentation Report"
__name__ = 'hotel.revenue_segmentation.report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
Period = pool.get('account.period')
Occupancy = pool.get('hotel.folio.occupancy')
period = Period(data['period'])
start_date = period.start_date
end_date = period.end_date
range_day = (end_date - start_date).days + 1
default_data = {
'complementary': [],
'room_complementary': [],
'booking': [],
'room_booking': [],
'expedia': [],
'room_expedia': [],
'other': [],
'room_other': [],
'web': [],
'room_web': [],
'direct': [],
'room_direct': [],
'corporative': [],
'room_corporative': [],
'group': [],
'room_group': [],
}
_data = copy.deepcopy(default_data)
records = []
for nday in range(range_day):
tdate = start_date + timedelta(nday)
data_day = copy.deepcopy(default_data)
data_day['date'] = tdate
occupancies = Occupancy.search([
('occupancy_date', '=', str(tdate))
])
for occ in occupancies:
booking = occ.folio.booking
field = 'direct'
if booking.channel and booking.channel.code in ('booking', 'expedia'):
field = booking.channel.code
else:
if booking.media == 'web':
field = 'web'
data_day[field].append(occ.unit_price)
data_day['room_' + field].append(1)
_data[field].append(occ.unit_price)
_data['room_' + field].append(1)
records.append(data_day)
for rec in records:
for key, values in rec.items():
if key == 'date':
continue
rec[key] = sum(values)
for key, value in _data.items():
if key == 'date':
continue
_data[key] = sum(_data[key])
data.update(_data)
report_context['records'] = records
report_context['period'] = period.name
report_context['company'] = Company(data['company'])
return report_context

View File

@ -342,5 +342,44 @@ this repository contains the full copyright notices and license terms. -->
<field name="type">form</field>
<field name="name">bill_booking_start_form</field>
</record>
<record model="ir.action.report" id="report_operation_forecast">
<field name="name">Operation Forecast Report</field>
<field name="model"></field>
<field name="report_name">hotel.operation_forecast.report</field>
<field name="report">hotel/operation_forecast.fods</field>
<field name="template_extension">ods</field>
</record>
<record model="ir.ui.view" id="print_operation_forecast_start_view_form">
<field name="model">hotel.print_operation_forecast.start</field>
<field name="type">form</field>
<field name="name">operation_forecast_start_form</field>
</record>
<record model="ir.action.wizard" id="wizard_print_operation_forecast">
<field name="name">Operation Forecast</field>
<field name="wiz_name">hotel.print_operation_forecast</field>
</record>
<menuitem parent="hotel.menu_reporting" id="menu_operation_forecast"
action="wizard_print_operation_forecast"/>
<record model="ir.action.report" id="report_revenue_segmentation">
<field name="name">Revenue Segmentation Report</field>
<field name="model"></field>
<field name="report_name">hotel.revenue_segmentation.report</field>
<field name="report">hotel/revenue_segmentation.fods</field>
<field name="template_extension">ods</field>
</record>
<record model="ir.ui.view" id="revenue_segmentation_start_view_form">
<field name="model">hotel.revenue_segmentation.start</field>
<field name="type">form</field>
<field name="name">revenue_segmentation_start_form</field>
</record>
<record model="ir.action.wizard" id="wizard_revenue_segmentation">
<field name="name">Revenue Segmentation</field>
<field name="wiz_name">hotel.revenue_segmentation</field>
</record>
<menuitem parent="hotel.menu_reporting" id="menu_revenue_segmentation"
action="wizard_revenue_segmentation"/>
</data>
</tryton>

View File

@ -55,11 +55,11 @@ MEDIA = [
('fax', 'Fax'),
('mail', 'Mail'),
('chat', 'Chat'),
('direct', 'Direct'),
('walking', 'Walking'),
('web', 'Web'),
('channel_manager', 'Channel Manager'),
('other', 'Other'),
('ota', 'OTA'),
('other', 'Other'),
]
PLAN = [

View File

@ -1169,7 +1169,7 @@ class FolioGuest(ModelSQL, ModelView):
def default_country():
Config = Pool().get('hotel.configuration')
config = Config.get_configuration()
if config.country:
if config and config.country:
return config.country.id
@staticmethod

BIN
operation_forecast.fods Normal file

Binary file not shown.

Binary file not shown.

BIN
revenue_segmentation.fods Normal file

Binary file not shown.

View File

@ -1,5 +1,5 @@
[tryton]
version=6.0.67
version=6.0.68
depends:
party
company

View File

@ -38,10 +38,10 @@ this repository contains the full copyright notices and license terms. -->
<group col="6" id="add_info_group_taxes" colspan="2">
<label name="group"/>
<field name="group"/>
<label name="vip"/>
<field name="vip"/>
<label name="taxes_exception"/>
<field name="taxes_exception"/>
<label name="corporative"/>
<field name="corporative"/>
<label name="link_web_checkin"/>
<field name="link_web_checkin" widget="url"/>
</group>
<notebook colspan="6">
<page string="Lines" id="lines">
@ -56,9 +56,6 @@ this repository contains the full copyright notices and license terms. -->
<page string="Stock" id="stock_moves">
<field name="stock_moves" colspan="4"/>
</page>
<page string="Account Moves" id="account_moves">
<field name="income_moves" colspan="4"/>
</page>
<page string="Additional Info" id="additional_info">
<label name="company"/>
<field name="company"/>
@ -70,6 +67,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="guarantee"/>
<label name="reason"/>
<field name="reason"/>
<label name="vip"/>
<field name="vip"/>
<group col="6" id="people_num" colspan="4">
<label name="guests_num"/>
<field name="guests_num"/>
@ -108,6 +107,9 @@ this repository contains the full copyright notices and license terms. -->
<page string="Notifications" id="notifications">
<field name="emails" colspan="4"/>
</page>
<page string="Account Moves" id="account_moves">
<field name="income_moves" colspan="4"/>
</page>
</notebook>
<group col="4" colspan="3" id="buttons">
<button name="cancel" string="Cancel" icon="tryton-cancel"/>

View File

@ -7,10 +7,11 @@ this repository contains the full copyright notices and license terms. -->
<field name="party" expand="1"/>
<field name="contact" expand="1"/>
<field name="booking_date" widget="date"/>
<!-- <field name="channel" expand="1"/> -->
<field name="ota_booking_code"/>
<field name="media"/>
<field name="total_amount"/>
<field name="total_advances"/>
<field name="state" expand="1"/>
<field name="group" expand="1"/>
<field name="corporative" expand="1"/>
</tree>

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="fiscalyear"/>
<field name="fiscalyear" widget="selection"/>
<label name="company"/>
<field name="company" widget="selection"/>
<label name="period"/>
<field name="period" widget="selection"/>
</form>

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="fiscalyear"/>
<field name="fiscalyear" widget="selection"/>
<label name="company"/>
<field name="company" widget="selection"/>
<label name="period"/>
<field name="period" widget="selection"/>
</form>