Calculating future revenue projection on recurring payments with various terms - recurring

I have a service with various packages, that can all be purchased monthly, quarterly, semi-annually, and yearly.
Each package has a due_on date which I increment when someone renews.
It's easy to calculate how much revenue I can roughly expect this month, by checking who has a due_on date this month.
Where I'm running into trouble is calculating how much revenue I can expect each month over the next year. I can't base it on due_on, because some people will be paying 12 times in the next year, and some 6, etc.
What is the best way to do this?
Note: for this purpose I am ignoring attrition.
I am working in PHP and MySQL, but I'm asking for theory so that shouldn't matter too much.

Let's take the case of figuring out monthly revenue for the next 12 months.
I can see two basic ways to do it:
Create an array (or hash) of buckets for each of the next 12 months. Then, iterate through each active subscription and for each subscription add the expected revenue from that to every appropriate bucket. For example, if the terms are monthly, add that payment to every bucket; if the term is quarterly, add that payment to the current due_date month's bucket and the buckets for 3, 6, and 9 months after that; etc. This is fairly straightforward to code, but with enough customers generating the report could take some time.
Create a temporary table in your mysql database that has columns of (month, amount), fill it with a series of insert statements (there are a bunch; I'll get to them in a minute), and then do a report over that table.
This is a bit more unusual, so I'm going to spell it out in more detail. I'm making some assumptions about your SQL table structure, but I'm trying to keep it as generic as possible.
So first you need the temporary table, and we'll fill it initially with per-month revenue based on the current due_on date:
CREATE TEMPORARY TABLE FutureRevenueReport
SELECT
CONCAT(YEAR(s.due_on), '-', MONTH(s.due_on)) as revenue_month,
s.due_amount as revenue_amount
FROM subscriptions s WHERE s.active = 'True';
Now in the same mysql session, execute this series of insert statements to fill in future revenue:
INSERT INTO FutureRevenueReport(revenue_month, revenue_amount)
SELECT CONCAT(YEAR(s.due_on + INTERVAL 1 MONTH), '-',
MONTH(s.due_on + INTERVAL 1 MONTH)), s.due_amount
FROM subscriptions s WHERE s.active = 'True' AND s.term = 'MONTHLY';
INSERT INTO FutureRevenueReport(revenue_month, revenue_amount)
SELECT CONCAT(YEAR(s.due_on + INTERVAL 2 MONTH), '-',
MONTH(s.due_on + INTERVAL 2 MONTH)), s.due_amount
FROM subscriptions s WHERE s.active = 'True' AND s.term = 'MONTHLY';
INSERT INTO FutureRevenueReport(revenue_month, revenue_amount)
SELECT CONCAT(YEAR(s.due_on + INTERVAL 3 MONTH), '-',
MONTH(s.due_on + INTERVAL 3 MONTH)), s.due_amount
FROM subscriptions s WHERE s.active = 'True' AND s.term = 'MONTHLY';
-- etc, up to + INTERVAL 11 MONTH
-- Now the quarterly:
INSERT INTO FutureRevenueReport(revenue_month, revenue_amount)
SELECT CONCAT(YEAR(s.due_on + INTERVAL 3 MONTH), '-',
MONTH(s.due_on + INTERVAL 3 MONTH)), s.due_amount
FROM subscriptions s WHERE s.active = 'True' AND s.term = 'QUARTERLY';
INSERT INTO FutureRevenueReport(revenue_month, revenue_amount)
SELECT CONCAT(YEAR(s.due_on + INTERVAL 6 MONTH), '-',
MONTH(s.due_on + INTERVAL 6 MONTH)), s.due_amount
FROM subscriptions s WHERE s.active = 'True' AND s.term = 'QUARTERLY';
-- And the same for 9 months
-- Then do the same thing for SEMI-ANNUALLY and + INTERVAL 6 MONTH
-- And now the report:
SELECT revenue_month, sum(revenue_amount) as revenue from FutureRevenueReport
GROUP BY revenue_month;

Related

NetSuite formula for dates without transactions

I’m trying to create a report that has everyday of the month listed as a column and sums the transaction quantity for that day if transactions exist and enters a 0 in the column if no transactions exist for that day.
Example:
Day
1 2 3 … 28 29 30 31
Sales Rep.
John Doe. 5 0 0… 10 15 0 30
Any ideas how to make this work? I thought about extracting the day from the transaction date but I can’t figure out how to get the days without any transactions. I thought about using current date to extract day but then the formulas would change every day. Ugh!! Help me please!
You can do this with 31 formula fields.
To avoid overlapping months add a formula text column with a formula TO_CHAR({trandate}, 'YYYY-MM')
Create a saved search on sales orders that filters to the month of interest and includes sales rep, date, document number and amount (at least)
for each date you want to report on include a formula numberic column with a formula like case when TO_number(TO_CHAR({trandate}, 'DD')) = 14 then {amount} else 0 end. Give it a custom label of the day number (e.g. 14)
Set summary columns as follows:
Sales Rep - Group
Document Number - Count
Formula Text - Group
each Formula Numeric - Sum
That's all you need to do but the 31 formula columns are tedious so when I do this sort of thing I do the following to create a template of the saved search and then add and adjust the date range (e.g. add filters in the displayed filter region)
Open any scripted record. Usually just view a sales order. If the step below fails try in a sales order in Edit mode. You won't be saving or updating the order
Right click and select 'Inspect'. This opens the developer tools
Find the 'console'. You should see some clear space at the bottom.
Copy and paste the text below into the console and press your enter key.
If all went well the script will print a number
You can then take the search title (Daily Rep Sales in the example code) and paste it in the NS global search. If you run the search you'll see the last 30 days of sales totalled by rep. Add and update filters (e.g. for different date ranges or only for last month or only for this month etc) and then make the search public to share it (or share it with certain users etc)
require(['N/search'], search =>{
var dateCols = [];
for(var i = 1; i< 32; i++){
dateCols.push(search.createColumn({
name:'formulanumeric',
summary:search.Summary.SUM,
label:'Day '+ i,
formula: "case when TO_number(TO_CHAR({trandate}, 'DD')) = "+ i + " then {amount} else 0 end"
}));
}
const searchId = search.create({
type:'salesorder',
title:'Daily Rep Sales',
filters:[
['mainline', 'is', 'T'], 'AND',
['trandate', 'onorafter', 'daysago30']
],
columns:[
search.createColumn({name:'formulatext', formula:"TO_CHAR({trandate}, 'YYYY-MM')", summary:search.Summary.GROUP}),
search.createColumn({name:'salesrep', summary:search.Summary.GROUP}),
search.createColumn({name:'tranid', summary:search.Summary.COUNT}),
search.createColumn({name:'trandate'}),
search.createColumn({name:'amount'})
// whatever drill down fields you want without summary
].concat(dateCols)
}).save();
console.log(searchId);
})

FY vs last FY Cognos 11

I am looking to generate report in Cognos 11 for attended appointments for this FY vs last FY for the same time frame. For example of I am running report on 1st June 2021, I should get data for sum of appointments of April and May 2021 and sum of appointments of April and May 2020.
Please let me know how can I set the date filter.
Thanks
You didn't provide any context. There are many ways to do this. Just providing a filter expression won't work because I don't know what the rest of your report looks like.
Describing such a general solution for Cognos in plain text is not simple. I'll also assume that, since you are using Cognos, the database structure is a star schema containing a time dimension with columns named [FiscalYear] and [FiscalMonthNumber] that are integers. Also, that your fiscal year runs from January 1 to December 31.
Using Cognos functions:
[FiscalYear] * 100 + [FiscalMonthNumber]
between
_year(_add_months(current_date, -2)) * 100 +
_month(_add_months(current_date, -2))
and
_year(_add_months(current_date, -1)) * 100 +
_month(_add_months(current_date, -1))
or
[FiscalYear] * 100 + [FiscalMonthNumber] + 100
between
_year(_add_months(current_date, -2)) * 100 +
_month(_add_months(current_date, -2))
and
_year(_add_months(current_date, -1)) * 100 +
_month(_add_months(current_date, -1))
Using Cognos macros:
[FiscalYear] * 100 + [FiscalMonthNumber]
between
#timestampMask (_add_months($current_timestamp, -2), 'yyyymm')#
and
#timestampMask (_add_months($current_timestamp, -1), 'yyyymm')#
or
[FiscalYear] * 100 + [FiscalMonthNumber] + 100
between
#timestampMask (_add_months($current_timestamp, -2), 'yyyymm')#
and
#timestampMask (_add_months($current_timestamp, -1), 'yyyymm')#
The advantage of using the Cognos macro is that the value is computed once before sending to the database server, rather than the database server computing it for every row.
Assumption that you have a data item like invoice date, etc, for the example this will be [Date] but you could change the filter to what makes sense based on your data items
The filter could use a date prompt with a parm i.e. PrmDate or use Current_date
see explanation below
Filter should look like:
([Date] between [CurrentStart] and [CurrentEnd])
OR
([Date] between [PriorStart] and [PriorEnd])
Create some data items to make this a little easier
Offset, note: the offset allows you to change your starting month/period
This would be for January
extract(month, current_date) -1
This would be for April
extract(month, current_date) -4
CurrentStart
_first_of_month (_add_months(current_date, -[Offset]))
CurrentEnd, note: if you don't want a prompt, just swap ?PrmDate? with Current_date
_add_months(?PrmDate?,-1)
PriorStart
_add_years([CurrentStart], -1)
PriorEnd
_add_years([CurrentEnd],-1)

DAX. Problem with subtotals and grand totals

hope you are doing well and can help solve this puzzle in DAX for PowerBI and PowerPivot.
I'm having troubles with my measure in the subtotals and grand totals. My scene is the following:
I have 3 tables (I share a link below with a test file so you can see it and work there :robothappy:):
1) "Data" (where every register is a sold ticket from a bus company);
2) "Km" (where I have every possible track that the bus can do with their respective kilometer). Related to "Data";
3) and a "Calendar". Related to "Data".
In "Data" I have all the tickets sold from a period with their price, the track that the passenger bought and the departure time of that track.
Each track can have more than 1 departure time (we can call it a service) but only have a specific lenght in kilometers (their kilometers are specified in the "Km" table). 
Basically what I need is to calculate the revenue per kilometer for each service in a period (year, month, day).
The calculation should be, basically:
Sum of [Price] (each ticket sold in the period) / Sum of [Km] (of the period considerating the services with their respective kilometers)
I managed to calculate it for the day granularity with the following logic and measures:
Revenue = SUM(Data[Price])
Unique dates = DISTINCTCOUNT(Data[Date])
Revenue/Km = DIVIDE([Revenue]; SUM(Km[Km])*[Unique dates]; 0)
I created [Unique dates] to calculate it because I tried to managed the subtotals of track granularity taking into account that you can have more than 1 day with services within the period. For example:
For "Track 1" we have registered:
1 service on monday (lunes) at 5:00am.
Revenue = $1.140.
Km = 115.
Tickets = 6.
Revenue/Km = 1.140/115 = 9,91.
1 service on tuesday (martes) at 5:00am.
Revenue = $67.
Km = 115.
Tickets = 2.
Revenue/Km = 67/115 = 0,58.
"Subtotal Track 1" should be:
Revenue = 1.140 + 67 = 1.207.
Km = 115 + 115 = 230.
Tickets = 6 + 2 = 8.
Revenue/Km = 1.207/230 = 5,25.
So at that instance someone can think my formula worked, but the problem you can see it when I have more than 1 service per day, for example for Track 3. And also this impact in the grand total of march (marzo).
I understand that the problem is to calculate the correct kilometers for each track in each period. If you check the column "Sum[Km]" is also wrong.
Here is a table (excel file to download - tab "Goal") with the values that should appear: 
[goal] https://drive.google.com/file/d/1PMrc-IUnTz0354Ko6q3ZvkxEcnns1RFM/view?usp=sharing
[pbix sample file] https://drive.google.com/file/d/14NBM9a_Frib55fvL-2ybVMhxGXN5Vkf-/view?usp=sharing
Hope you can understand my problem. If you need more details please let me know.
Thank you very much in advance!!!
Andy.-
Delete "Sum of Km" - you should always write DAX measures instead.
Create a new measure for the km traveled:
Total Km =
SUMX (
SUMMARIZE (
Data,
Data[Track],
Data[Date],
Data[Time],
"Total_km", DISTINCT ( Data[Kilometers Column] )
),
[Total_km]
)
Then, change [Revenue/Km] measure:
Revenue/Km = DIVIDE([Revenue], [Total Km])
Result:
The measure correctly calculates km on both subtotal and total levels.
The way it works:
First, we use SUMMARIZE to group records by trips (where trip is a unique combination of track, date and time). Then, we add a column to the summary that contains km for each trip. Finally, we use SUMX to iterate the summary record by record, and sum up trip distances.
The solution should work, although I would recommend to give more thoughts to the data model design. You need to build a better star schema, or DAX will continue to be challenging. For example, I'd consider adding something like "Trip Id" to each record - it will be much easier to iterate over such ids instead of grouping records all the time. Also, more descriptive names can help make DAX clean (names like km[km] look a bit strange :)

Add x number of working days to a date in a custom column

I'm trying to add a custom column in Power Query that adds 3 business days if a condition is met, else add 2 business days.
I can have it conditionally add days without issue, but am having trouble adding workdays instead. I know this is easily done in excel using =IF X = 1,WORKDAY([REFERENCE],3),WORKDAY([REFERENCE],2) but how can I do the same thing as a custom column in the query editor?
Below is what I have, which does days including weekends:
=if [REF]="1" then Date.AddDays([ETA],3) else Date.AddDays([ETA],2)
I wrote a custom function that solves this problem, unless you need to take holidays into account too. It is a bit universal, it works with 2 or 200 or 2000 added days. Equals =WORKDAY(date;days) function in Excel.
UPDATE: Bibake Uppal suggested there should be subtraction supported. Added subtraction for negative days.
Here it is:
(startDate, days) =>
let
Sign = if days < 0 then -1 else 1, //add a multiplier to enable negative 'days' to be subtracted rather than added
Step1 = List.Dates(Date.AddDays(startDate, Sign), Sign*days + Number.RoundUp(Sign*days/7*3+4,0), #duration(Sign,0,0,0)), //provision list of added days with 3 more days per each added week, starting from startDate + 1 day; This is a bit over-provisioning, but ensures the list is long enough.
Step2 = List.Select(Step1, (item) => Date.DayOfWeek(item, Day.Monday) < 5), // select only workdays
Step3 = List.FirstN(Step2, Sign*days), //select required number of workdays
Output = if days = 0 then startDate else List.Last(Step3)
in
Output
You can save it as a query, name it, say, AddWorkdays, and use as this:
YourStepName = Table.AddColumn(yourTable, "CustomColumnName",
each AddWorkdays([ETA], if [REF]="1" then 3 else 2)
//note that [REF]="1" filters a text value, not number!
Otherwise you can insert this function in your code as
fnAddWorkdays = (startDate, days) =>
let
Sign = if days < 0 then -1 else 1, //add a multiplier to enable negative 'days' to be subtracted rather than added
Step1 = List.Dates(Date.AddDays(startDate, Sign), Sign*days + Number.RoundUp(Sign*days/7*3+4,0), #duration(Sign,0,0,0)), //provision list of added days with 3 more days per each added week, starting from startDate + 1 day; This is a bit over-provisioning, but ensures the list is long enough.
Step2 = List.Select(Step1, (item) => Date.DayOfWeek(item, Day.Monday) < 5), // select only workdays
Step3 = List.FirstN(Step2, Sign*days), //select required number of workdays
Output = if days = 0 then startDate else List.Last(Step3)
in
Output
A solution is to break it down into 2 components:
Based on your initial condition, add 2 or 3 days
If this ends on a Saturday or Sunday, add 2 additional days to skip over the weekend
You have already implemented Step 1.
You now just need to implement Step 2, which is to add the 2 additional days if required. You can use DayOfWeek function to determine where you started, to determine if you'll need the extra 2 days:
If you're adding 3 business days, then you'll need to add 2 additional days if the initial day is Wed, Thu or Fri:
if Date.DayOfWeek([ETA], Day.Wednesday) <= 2 then {Add 2 more days}
If you're adding 2 business days, then you'll need to add 2 additional days if the initial day is Thu or Fri:
if Date.DayOfWeek([ETA], Day.Thursday) <= 1 then {Add 2 more days}
You can incorporate these into your initial statement.
Slight improvement on Eugene's response to allow for the subtraction of workdays as well.
= (startDate, days) =>
let
Step1 = if days >= 0 then List.Dates(Date.AddDays(startDate, 1), days + Number.RoundUp(days/7*3+4,0), #duration(1,0,0,0))
else List.Dates(Date.AddDays(startDate, -1), -days + Number.RoundUp(-days/7*3+4,0), #duration(-1,0,0,0)), //provision list of added days with 3 more days per each added week, starting from startDate + 1 day
Step2 = List.Select(Step1, (item) => Date.DayOfWeek(item, Day.Monday) < 5), // select only workdays
Step3 = List.FirstN(Step2, Number.Abs(days)), //select required number of workdays
Output = List.Last(Step3)
in
Output
Simply put in a negative value for days.

How do I get the Month End Personal Time Off Days used?

Using the Start Date and End Date of PTO - Personal Time Off Days Used only count days used up to end of prior month, excluding weekends and U.S Holidays in that certain month. Example of a Holiday is Sept 7th 2015 in the United States.
My goals are:
Create a Data Item Month End Personal Time Off Days used.
Of course it should be getting the number of PTO Days USED from the prior month only.
Exclude weekends in that certain month. So if the Resource takes a Leave on Friday and Monday, Saturday and Sunday should not be excluded in the computation.
How to exclude U.S Holidays, if this is possible that's great but if it's not possible then I'm okay with numbers 1, 2 and 3.
I have created a Data Item column that captures the PTO days used. But this is good for Year to date.
Case when [PTO Info].[PTO Audit].[PTOAuditTypeId] = 31571
and [PTO Info].[PTO Audit].[TimeOffTypeId] = 31566
then [PTO Info].[PTO Audit].[PTODays]
when [PTO Info].[PTO Audit].[PTOAuditTypeId]=31572
and [PTO Info].[PTO Audit].[TimeOffTypeId] = 31566
and [PTO Info].[PTO Audit].[PTODays] < 0
then abs([PTO Info].[PTO Audit].[PTODays] )
else 0 end
I'm not sure if the query below can help.
A calendar table is really going to help you out here. Assuming it has one record per calendar date, you can use this table to note weekends, holidays, fiscal periods vs Calendar periods, beginning of month/end of month dates. A number of things that can help simplify your date based queries.
See this question here for an example on creating a calendar table.
The main point is to create a data set with 1 record per date, with information about each date including Month, Day of Week, Holiday status, etc.
Without a calendar table, you can use database functions to generate your set of dates on the fly.
Getting the Month number for a date can be done with
extract([Month], <date field goes here> )
Getting a list of values from nothing will be required to generate your list of dates (if you don't have a calendar table with 1 record per date to use) will vary depending on your source database. In oracle I use a 'select from all_objects' type query to achieve this.
An example from Ask Tom:
select to_date(:start_date,'dd-mon-yyyy') + rownum -1
from all_objects
where rownum <=
to_date(:end_date,'dd-mon-yyyy')-to_date(:start_date,'dd-mon-yyyy')+1
For Sql Server refer to this stackoverflow question here.
Once you have a data set with your calendar type information, you can join it to your query above:
join mycalendar cal on cal.date >= c.PTOStartDate
and cal.date <= c.PTOEndDate
Also note, _add_days is a Cognos function. When building your source queries, try and use Native functions, like in oracle you can 'c.PTOStartDate + a.PTODays'. Mixing Cognos functions with native functions will sometime force parts of your queries to be processed locally on the Cognos server. Generally speaking, the more work that happens on the database, the faster your reports will run.
Once you have joined to the calendar data, you are going to have your records multiplied out so that you have 1 record per date. (You would not want to be doing any summary math on PTODays here, as it will be inflated.)
Now you can add clauses to track your rules.
where cal.Day_Of_Week between 2 and 6
and cal.Is_Holiday = 'N'
Now if you are pulling a specific month, you can add that to the criteria:
and cal.CalendarPeriod = '201508'
Or if you are covering a longer period, but wanting to report a summary per month, you can group by month.
Final query could look something like this:
select c.UserID, cal.CalendarPeriod, count(*) PTO_Days
from dbo.PTOCalendar c
join myCalendar cal on on cal.date >= c.PTOStartDate
and cal.date <= c.PTOEndDate
where cal.day_of_week between 2 and 6
and cal.Is_Holiday = 'N'
group by c.UserID, cal.CalendarPeriod
So if employee with UserID 1234 Took a 7 day vacation from thursday June 25th to Friday July 3th, that covered 9 days, the result you get here will be:
1234 201506 4
1234 201507 3
You can join these results to your final query above to track days off per month.

Resources