Add revenue segmentation
This commit is contained in:
parent
2a3af646c4
commit
2a0277555f
245
booking.py
245
booking.py
|
@ -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.
|
@ -1,5 +1,5 @@
|
|||
[tryton]
|
||||
version=6.0.69
|
||||
version=6.0.70
|
||||
depends:
|
||||
party
|
||||
company
|
||||
|
|
Loading…
Reference in New Issue