Add revenue segmentation

This commit is contained in:
oscar alvarez 2023-02-01 22:48:35 -05:00
parent 2a3af646c4
commit 2a0277555f
3 changed files with 196 additions and 51 deletions

View File

@ -1,4 +1,4 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# 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 datetime import datetime, timedelta, date
from decimal import Decimal
@ -1129,7 +1129,6 @@ class Booking(Workflow, ModelSQL, ModelView):
res = 0
if name == 'pending_to_pay':
if self.total_amount:
print('total_amount...', self.total_amount, self.total_advances)
res = self.total_amount - (self.total_advances or 0)
if res > -1 and res < 1:
res = 0
@ -2114,11 +2113,9 @@ class ManagerReport(Report):
channels[type_]['num_children'].append(folio.num_children)
channels[type_]['income'].append(folio.total_amount)
total_income.append(folio.total_amount)
# parties = [guest.party for guest in folio.guests]
for guest in folio.guests:
if not guest or not guest.nationality:
continue
# print('guest ', guest, folio.booking.number)
if guest.nationality.name == 'COLOMBIA':
city = cls.get_location(folio.main_guest, 'city')
local_guests.append(1)
@ -2640,9 +2637,11 @@ class OperationForecastReport(Report):
occupancy_rate_forecast = ''
if totals['occupied_rooms_effective'] > 0:
average_price_effective = totals['revenue_effective'] / totals['occupied_rooms_effective']
if totals['occupied_rooms_forecast'] > 0:
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']
if totals['available_rooms_forecast'] > 0:
occupancy_rate_forecast = totals['occupied_rooms_forecast'] / totals['available_rooms_forecast']
totals['occupancy_rate_effective'] = occupancy_rate_effective
@ -2682,22 +2681,21 @@ class RevenueSegmentationStart(ModelView):
kind = fields.Selection([
('by_day', 'By Day'),
('by_months', 'By Months'),
], 'Kind')
], 'Kind', required=True)
period = fields.Many2One('account.period', 'Period', states={
'invisible': Eval('kind') != 'by_day',
'required': Eval('kind') != 'by_day'
'required': Eval('kind') == 'by_day'
},
domain=[
('fiscalyear', '=', Eval('fiscalyear')),
])
periods = fields.Many2Many('account.period', None, None, 'Periods',
states={
'required': Eval('kind') == 'by_month',
'invisible': Eval('kind') == 'by_month'
},
domain=[
('fiscalyear', '=', Eval('fiscalyear')),
])
states={
'required': Eval('kind') == 'by_months',
'invisible': Eval('kind') != 'by_months',
}, domain=[
('fiscalyear', '=', Eval('fiscalyear')),
])
@staticmethod
def default_fiscalyear():
@ -2726,7 +2724,7 @@ class RevenueSegmentation(Wizard):
company = self.start.company
data = {
'company': company.id,
'kind': self.kind,
'kind': self.start.kind,
'period': self.start.period.id if self.start.period else None,
'periods': [period.id for period in self.start.periods],
}
@ -2740,21 +2738,179 @@ class RevenueSegmentationReport(Report):
"Revenue Segmentation Report"
__name__ = 'hotel.revenue_segmentation.report'
@classmethod
def _set_indicators(cls, dict_data, record, price):
record_id = record.id
if record_id not in dict_data.keys():
dict_data[record_id] = {
'name': record.name,
'nights': [],
'amount': [],
'average_price': 0,
}
dict_data[record_id]['amount'].append(price)
dict_data[record_id]['nights'].append(1)
@classmethod
def _compute_segments(cls, data, segments):
for segment in segments:
_type, values = segment
total_amount = []
total_nights = []
for _id, value in values.items():
amount = sum(value['amount'])
nights = sum(value['nights'])
total_amount.append(amount)
total_nights.append(nights)
value['amount'] = amount
value['nights'] = nights
value['average_price'] = amount / nights
data[_type + '_nights'] = sum(total_nights)
data[_type + '_amount'] = sum(total_amount)
average_price = ''
if total_nights:
average_price = sum(total_amount) / sum(total_nights)
data[_type + '_average_price'] = average_price
@classmethod
def _get_days_data(cls, period, _data, default_data):
pool = Pool()
Occupancy = pool.get('hotel.folio.occupancy')
channels = {}
corporative = {}
groups = {}
records = []
start_date = period.start_date
end_date = period.end_date
range_day = (end_date - start_date).days + 1
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:
price = occ.unit_price
booking = occ.folio.booking
field = 'direct'
record = None
if booking.channel:
channel = booking.channel
field = channel.code
seg_data = channels
if field not in ('booking', 'expedia'):
field = 'other'
record = channel
elif booking.corporative:
field = 'corporative'
if booking.party:
record = booking.party
seg_data = corporative
elif booking.group:
field = 'group'
print('booking...', booking.number)
if booking.party:
record = booking.party
seg_data = groups
elif booking.media == 'web':
field = 'web'
if record:
cls._set_indicators(seg_data, record, price)
data_day[field].append(price)
data_day['room_' + field].append(1)
_data[field].append(price)
_data['room_' + field].append(1)
records.append(data_day)
segments = [
('channels', channels),
('corporative', corporative),
('groups', groups),
]
cls._compute_segments(_data, segments)
return records, channels, corporative, groups
@classmethod
def _get_months_data(cls, periods, _data, default_data):
Occupancy = Pool().get('hotel.folio.occupancy')
channels = {}
corporative = {}
groups = {}
records = []
for period in periods:
_month = {
'date': period.name,
}
_month.update(copy.deepcopy(default_data))
start = period.start_date
end = period.end_date
days = (end - start).days + 1
_dates = [start + timedelta(nday) for nday in range(days)]
occupancies = Occupancy.search([
('occupancy_date', 'in', _dates)
])
for occ in occupancies:
price = occ.unit_price
booking = occ.folio.booking
field = 'direct'
record = None
if booking.channel:
field = booking.channel.code
if field not in ('booking', 'expedia'):
field = 'other'
record = booking.channel
seg_data = channels
elif booking.corporative:
field = 'corporative'
record = booking.party
seg_data = corporative
elif booking.group:
field = 'group'
record = booking.party
seg_data = groups
elif booking.media == 'web':
field = 'web'
if record:
cls._set_indicators(seg_data, record, price)
_month[field].append(price)
_month['room_' + field].append(1)
_data[field].append(price)
_data['room_' + field].append(1)
records.append(_month)
segments = [
('channels', channels),
('corporative', corporative),
('groups', groups),
]
cls._compute_segments(_data, segments)
return records, channels, corporative, groups
@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')
channels = []
customers = []
if self.kind == 'by_day':
period = Period(data['period'])
start_date = period.start_date
end_date = period.end_date
range_day = (end_date - start_date).days + 1
data.update({
'channels_nights': 0,
'channels_amount': 0,
'channels_average_price': 0,
'corporative_nights': 0,
'corportative_amount': 0,
'corportative_average_price': 0,
})
default_data = {
'complementary': [],
@ -2777,31 +2933,17 @@ class RevenueSegmentationReport(Report):
_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)
if data['kind'] == 'by_day':
period = Period(data['period'])
records, channels, corporative, groups = cls._get_days_data(
period, _data, default_data
)
desc_periods = period.name
else:
periods = Period.browse(data['periods'])
records, channels, corporative, groups = cls._get_months_data(
periods, _data, default_data)
desc_periods = ' | '.join([pd.name for pd in periods])
for rec in records:
for key, values in rec.items():
@ -2809,13 +2951,16 @@ class RevenueSegmentationReport(Report):
continue
rec[key] = sum(values)
for key, value in _data.items():
for key, value in default_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['channels'] = channels.values()
report_context['corporative'] = corporative.values()
report_context['groups'] = groups.values()
report_context['periods'] = desc_periods
report_context['company'] = Company(data['company'])
return report_context

Binary file not shown.

View File

@ -1,5 +1,5 @@
[tryton]
version=6.0.69
version=6.0.70
depends:
party
company