...
This commit is contained in:
@@ -10,7 +10,7 @@ Next will define an OEM product in month 10, 1 Million EUR, ... cogs is a percen
|
||||
!!bizmodel.revenue_define bizname:'test' name:'oem1'
|
||||
descr:'OEM Deals'
|
||||
revenue:'10:1000000EUR,15:3333,20:1200000'
|
||||
cogs_percent: '1:20%,20:10%'
|
||||
cogs_percent: '1:20%,20:15%' cogs_delay:1
|
||||
|
||||
|
||||
This time we have the cogs defined in fixed manner, the default currency is USD doesn't have to be mentioned.
|
||||
@@ -25,4 +25,4 @@ bizmodel.play(heroscript:heroscript)!
|
||||
|
||||
mut bm:=bizmodel.get("test")!
|
||||
|
||||
bm.sheet.pprint()!
|
||||
bm.sheet.pprint(nr_columns:40)!
|
||||
23
examples/biztools/bizmodel2.vsh
Executable file
23
examples/biztools/bizmodel2.vsh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.biz.bizmodel
|
||||
import os
|
||||
|
||||
heroscript:="
|
||||
|
||||
!!bizmodel.revenue_define bizname:'test' name:'nodes'
|
||||
descr:'Node Sales'
|
||||
nr_sold: '0:10,20:10'
|
||||
revenue_item_setup:'0:1000,20:1200' revenue_item_setup_delay:1
|
||||
revenue_item_monthly:'0:5' revenue_item_monthly_delay:1
|
||||
cogs_item_monthly_rev_perc: '40%'
|
||||
cogs_item_delay:1
|
||||
cogs_item_setup_rev_perc: '80%'
|
||||
//revenue_item_monthly_perc:'3%'
|
||||
"
|
||||
|
||||
bizmodel.play(heroscript:heroscript)!
|
||||
|
||||
mut bm:=bizmodel.get("test")!
|
||||
|
||||
bm.sheet.pprint(nr_columns:30)!
|
||||
@@ -1,28 +0,0 @@
|
||||
import freeflowuniverse.herolib.biz.bizmodel
|
||||
import os
|
||||
|
||||
fn main() ! {
|
||||
heroscript:="
|
||||
|
||||
Next will define an OEM product in month 10, 1 Million EUR, ... cogs is a percent which is 20% at start but goes to 10% after 20 months.
|
||||
|
||||
!!bizmodel.revenue_define bizname:'test' name:'oem1'
|
||||
descr:'OEM Deals'
|
||||
revenue:'10:1000000EUR,15:3333,20:1200000'
|
||||
cogs_percent: '1:20%,20:10%'
|
||||
|
||||
|
||||
This time we have the cogs defined in fixed manner, the default currency is USD doesn't have to be mentioned.
|
||||
|
||||
!!bizmodel.revenue_define bizname:'test' name:'oem2'
|
||||
descr:'OEM Deals'
|
||||
revenue:'10:1000000EUR,15:3333,20:1200000'
|
||||
cogs: '10:100000,15:1000,20:120000'
|
||||
"
|
||||
|
||||
bizmodel.play(heroscript:heroscript)!
|
||||
|
||||
mut bm:=bizmodel.get("test")!
|
||||
|
||||
bm.sheet.pprint()!
|
||||
}
|
||||
@@ -16,6 +16,8 @@ pub fn (mut m BizModel) act(action Action) !Action {
|
||||
}
|
||||
'revenue_define' {
|
||||
m.revenue_action(action)!
|
||||
m.revenue_item_action(action)!
|
||||
m.revenue_name_total(action)!
|
||||
}
|
||||
'costcenter_define' {
|
||||
m.costcenter_define_action(action)!
|
||||
|
||||
@@ -9,34 +9,20 @@
|
||||
- bizname, is the name of the biz model we are populating
|
||||
- name, name of product, project
|
||||
- descr, description of the revenue line item
|
||||
- nr_months_recurring: e.g. 60 is 5 years
|
||||
|
||||
## descrete revenue/cogs (not per item)
|
||||
## discrete revenue/cogs (not per item)
|
||||
|
||||
cogs stands for cost of goods
|
||||
|
||||
- revenue: one of revenue, is not extrapolated, a deal at certain time
|
||||
- revenue_growth: is a revenue stream which is being extrapolated
|
||||
- revenue: one of revenue, can be extrapolated if specified
|
||||
- cogs: cost of goods, this is the cost of the revenue, can be extrapolated if specified
|
||||
- cogs_percent: percent of revenue
|
||||
- cogs_delay: delay in months between cogs and revenue
|
||||
|
||||
## grouped per items sold
|
||||
if you want to extrapolate cogs or revenue do extrapolate:1
|
||||
|
||||
- nr_sold: how many do we sell per month (is in growth format e.g. 10:100,20:200, default is 1)
|
||||
- nr_months_recurring: e.g. 60 is 5 years
|
||||
|
||||
- revenue_item_setup, revenue for 1 item '1000usd'
|
||||
- revenue_item_setup_delay, delay between sell and recognition of sale in months
|
||||
- revenue_item_monthly, revenue per month for 1 item
|
||||
- revenue_item_monthly_delay, how many months before monthly revenue starts
|
||||
- revenue_item_maintenance_perc, how much percent of revenue_item_setup will come back over months
|
||||
- cogs_item_setup, cost of good for 1 item at setup
|
||||
- cogs_item_setup_delay, how many months before setup cogs starts, after sales
|
||||
- cogs_item_setup_perc: what is percentage of the cogs (can change over time) for setup e.g. 0:50%
|
||||
- cogs_item_monthly, cost of goods for the monthly per 1 item
|
||||
- cogs_item_monthly_delay, how many months before monthly cogs starts, after sales
|
||||
- cogs_item_monthly_perc: what is percentage of the cogs (can change over time) for monthly e.g. 0:5%,12:10%
|
||||
|
||||
## results in
|
||||
### results in
|
||||
|
||||
follow rows in sheets
|
||||
|
||||
@@ -44,6 +30,33 @@ follow rows in sheets
|
||||
- {name}_revenue_total
|
||||
- {name}_cogs_total
|
||||
|
||||
## grouped per items sold
|
||||
|
||||
- nr_sold: how many do we sell per month (is in growth format e.g. 10:100,20:200, default is 1)
|
||||
- revenue_item_setup, revenue for 1 item '1000usd'
|
||||
- revenue_item_setup_delay, delay between sell and recognition of sale in months e.g. 1
|
||||
- revenue_item_monthly, revenue per month for 1 item
|
||||
- revenue_item_monthly_delay, how many months before monthly revenue starts
|
||||
- revenue_item_monthly_perc, how much percent of revenue_item_setup will come back over months e.g. 20%
|
||||
- cogs_item_setup, cost of good for 1 item at setup
|
||||
- cogs_item_setup_rev_perc: what is percentage of the revenue which is cogs, e.g. 2%
|
||||
- cogs_item_monthly, cost of goods for the monthly per 1 item
|
||||
- cogs_item_monthly_rev_perc: what is percentage of the monthly revenue which is cogs, e.g. 10%
|
||||
- cogs_item_delay, how many months before cogs starts after sales
|
||||
|
||||
|
||||
|
||||
### results in
|
||||
|
||||
follow rows in sheets
|
||||
|
||||
- {name}_ + all the arg names as mentioned above...
|
||||
- {name}_revenue_item_setup_total
|
||||
- {name}_revenue_item_monthly_total
|
||||
- {name}_revenue_item_total
|
||||
|
||||
- {name}_cogs_item_total
|
||||
|
||||
## to use
|
||||
|
||||
### basic example
|
||||
|
||||
@@ -5,354 +5,63 @@ import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
// see lib/biz/bizmodel/docs/revenue.md
|
||||
fn (mut m BizModel) revenue_action(action Action) !Action {
|
||||
mut name := action.params.get_default('name', '')!
|
||||
mut descr := action.params.get_default('descr', '')!
|
||||
if descr.len == 0 {
|
||||
descr = action.params.get_default('description', '')!
|
||||
}
|
||||
if name.len == 0 {
|
||||
// make name ourselves
|
||||
name = texttools.name_fix(descr)
|
||||
}
|
||||
|
||||
name = texttools.name_fix(name)
|
||||
if name.len == 0 {
|
||||
return error('name and description is empty for ${action}')
|
||||
}
|
||||
name2 := name.replace('_', ' ').replace('-', ' ')
|
||||
descr = descr.replace('_', ' ').replace('-', ' ')
|
||||
mut r := get_action_descr(action)!
|
||||
|
||||
mut product := Product{
|
||||
name: name
|
||||
title: action.params.get_default('title', name)!
|
||||
description: descr
|
||||
name: r.name
|
||||
title: action.params.get_default('title', r.name)!
|
||||
description: r.description
|
||||
}
|
||||
m.products[name] = &product
|
||||
m.products[r.name] = &product
|
||||
|
||||
mut nr_months_recurring := action.params.get_int_default('nr_months_recurring', 60)!
|
||||
product.nr_months_recurring = nr_months_recurring
|
||||
|
||||
mut revenue := m.sheet.row_new(
|
||||
name: '${name}_revenue_total'
|
||||
growth: '0:0'
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'Revenue for ${name2}'
|
||||
extrapolate: false
|
||||
)!
|
||||
|
||||
// Handle revenue_items parameter (non-recurring revenue items)
|
||||
mut revenue_items := m.sheet.row_new(
|
||||
name: '${name}_revenue'
|
||||
name: '${r.name}_revenue'
|
||||
growth: action.params.get_default('revenue', '0:0')!
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'Revenue items for ${name2}'
|
||||
extrapolate: false
|
||||
tags: 'rev rev:${r.name} name:${r.name}'
|
||||
descr: 'Revenue items for ${r.name}'
|
||||
extrapolate: action.params.get_default_false('extrapolate')
|
||||
)!
|
||||
|
||||
// Handle revenue_growth parameter
|
||||
mut revenue_growth := m.sheet.row_new(
|
||||
name: '${name}_revenue_growth'
|
||||
growth: action.params.get_default('revenue_growth', '0:0')!
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'Revenue growth for ${name2}'
|
||||
extrapolate: true
|
||||
)!
|
||||
|
||||
// Handle revenue_nr parameter (number of revenue items)
|
||||
mut revenue_nr := m.sheet.row_new(
|
||||
name: '${name}_nr_sold'
|
||||
growth: action.params.get_default('nr_sold', '0:0')!
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'Items sold per month for ${name2}'
|
||||
extrapolate: false
|
||||
)!
|
||||
|
||||
mut nr_sold := m.sheet.row_new(
|
||||
name: '${name}_nr_sold'
|
||||
growth: action.params.get_default('nr_sold', '0')!
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'nr of items sold/month for ${name2}'
|
||||
aggregatetype: .avg
|
||||
)!
|
||||
|
||||
if nr_sold.max() > 0 {
|
||||
product.has_items = true
|
||||
}
|
||||
|
||||
// Handle revenue_item parameter (singular item)
|
||||
mut revenue_item := m.sheet.row_new(
|
||||
name: '${name}_revenue_item_setup'
|
||||
growth: action.params.get_default('revenue_item_setup', '0:0')!
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'Item Revenue for ${name2}'
|
||||
extrapolate: false
|
||||
)!
|
||||
|
||||
mut revenue_setup := m.sheet.row_new(
|
||||
name: '${name}_revenue_setup'
|
||||
growth: action.params.get_default('revenue_setup', '0:0')!
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'Setup Sales price for ${name2}'
|
||||
aggregatetype: .avg
|
||||
)!
|
||||
|
||||
mut revenue_setup_delay := action.params.get_int_default('revenue_setup_delay', 0)!
|
||||
|
||||
mut revenue_monthly := m.sheet.row_new(
|
||||
name: '${name}_revenue_monthly'
|
||||
growth: action.params.get_default('revenue_monthly', '0:0')!
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'Monthly Sales price for ${name2}'
|
||||
aggregatetype: .avg
|
||||
)!
|
||||
|
||||
mut revenue_monthly_delay := action.params.get_int_default('revenue_monthly_delay',
|
||||
1)!
|
||||
|
||||
mut cogs := m.sheet.row_new(
|
||||
name: '${name}_cogs_total'
|
||||
mut cogs_param := m.sheet.row_new(
|
||||
name: '${r.name}_cogs_param'
|
||||
growth: action.params.get_default('cogs', '0:0')!
|
||||
tags: 'cogs name:${name}'
|
||||
descr: 'COGS for ${name2}'
|
||||
extrapolate: false
|
||||
tags: 'name:${r.name}'
|
||||
descr: 'COGS for ${r.name}'
|
||||
extrapolate: action.params.get_default_false('extrapolate')
|
||||
)!
|
||||
|
||||
if revenue.max() > 0 || cogs.max() > 0 {
|
||||
product.has_oneoffs = true
|
||||
}
|
||||
|
||||
_ := m.sheet.row_new(
|
||||
name: '${name}_cogs_perc'
|
||||
growth: action.params.get_default('cogs_perc', '0')!
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'COGS as percent of revenue for ${name2}'
|
||||
mut cogs_perc := m.sheet.row_new(
|
||||
name: '${r.name}_cogs_perc'
|
||||
growth: action.params.get_default('cogs_percent', '0')!
|
||||
tags: 'name:${r.name}'
|
||||
descr: 'COGS as percent of revenue for ${r.name}'
|
||||
aggregatetype: .avg
|
||||
)!
|
||||
|
||||
mut cogs_setup := m.sheet.row_new(
|
||||
name: '${name}_cogs_setup'
|
||||
growth: action.params.get_default('cogs_setup', '0:0')!
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'COGS for ${name2} Setup'
|
||||
aggregatetype: .avg
|
||||
)!
|
||||
|
||||
mut cogs_setup_delay := action.params.get_int_default('cogs_setup_delay', 1)!
|
||||
|
||||
mut cogs_setup_perc := m.sheet.row_new(
|
||||
name: '${name}_cogs_setup_perc'
|
||||
growth: action.params.get_default('cogs_setup_perc', '0')!
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'COGS as percent of revenue for ${name2} Setup'
|
||||
aggregatetype: .avg
|
||||
)!
|
||||
|
||||
mut cogs_monthly := m.sheet.row_new(
|
||||
name: '${name}_cogs_monthly'
|
||||
growth: action.params.get_default('cogs_monthly', '0:0')!
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'Cost of Goods (COGS) for ${name2} Monthly'
|
||||
aggregatetype: .avg
|
||||
)!
|
||||
|
||||
mut cogs_monthly_delay := action.params.get_int_default('cogs_monthly_delay', 1)!
|
||||
|
||||
mut cogs_monthly_perc := m.sheet.row_new(
|
||||
name: '${name}_cogs_monthly_perc'
|
||||
growth: action.params.get_default('cogs_monthly_perc', '0')!
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'COGS as percent of revenue for ${name2} Monthly'
|
||||
aggregatetype: .avg
|
||||
)!
|
||||
|
||||
// CALCULATE THE TOTAL (multiply with nr sold)
|
||||
|
||||
mut revenue_setup_total := revenue_setup.action(
|
||||
name: '${name}_revenue_setup_total'
|
||||
descr: 'Setup sales for ${name2} total'
|
||||
action: .multiply
|
||||
rows: [nr_sold]
|
||||
delaymonths: revenue_setup_delay
|
||||
)!
|
||||
|
||||
mut revenue_monthly_total := revenue_monthly.action(
|
||||
name: '${name}_revenue_monthly_total'
|
||||
descr: 'Monthly sales for ${name2} total'
|
||||
action: .multiply
|
||||
rows: [nr_sold]
|
||||
delaymonths: revenue_monthly_delay
|
||||
)!
|
||||
|
||||
mut cogs_setup_total := cogs_setup.action(
|
||||
name: '${name}_cogs_setup_total'
|
||||
descr: 'Setup COGS for ${name2} total'
|
||||
action: .multiply
|
||||
rows: [nr_sold]
|
||||
delaymonths: cogs_setup_delay
|
||||
)!
|
||||
|
||||
mut cogs_monthly_total := cogs_monthly.action(
|
||||
name: '${name}_cogs_monthly_total'
|
||||
descr: 'Monthly COGS for ${name2} total'
|
||||
action: .multiply
|
||||
rows: [nr_sold]
|
||||
delaymonths: cogs_monthly_delay
|
||||
)!
|
||||
|
||||
// DEAL WITH RECURRING
|
||||
|
||||
if nr_months_recurring > 0 {
|
||||
revenue_monthly_total = revenue_monthly_total.recurring(
|
||||
name: '${name}_revenue_monthly_recurring'
|
||||
descr: 'Revenue monthly recurring for ${name2}'
|
||||
nrmonths: nr_months_recurring
|
||||
)!
|
||||
cogs_monthly_total = cogs_monthly_total.recurring(
|
||||
name: '${name}_cogs_monthly_recurring'
|
||||
descr: 'COGS recurring for ${name2}'
|
||||
nrmonths: nr_months_recurring
|
||||
)!
|
||||
|
||||
_ := nr_sold.recurring(
|
||||
name: '${name}_nr_sold_recurring'
|
||||
descr: 'Nr products active because of recurring for ${name2}'
|
||||
nrmonths: nr_months_recurring
|
||||
aggregatetype: .max
|
||||
)!
|
||||
}
|
||||
|
||||
// cogs as percentage of revenue
|
||||
mut cogs_setup_from_perc := cogs_setup_perc.action(
|
||||
mut cogs_percent_temp := cogs_perc.action(
|
||||
action: .multiply
|
||||
rows: [revenue_setup_total]
|
||||
name: '${name}_cogs_setup_from_perc'
|
||||
)!
|
||||
mut cogs_monthly_from_perc := cogs_monthly_perc.action(
|
||||
action: .multiply
|
||||
rows: [revenue_monthly_total]
|
||||
name: '${name}_cogs_monthly_from_perc'
|
||||
rows: [revenue]
|
||||
name: '${r.name}_cogs_percent_temp'
|
||||
)!
|
||||
cogs_percent_temp.delay(action.params.get_int_default('cogs_delay', 0)!)!
|
||||
|
||||
// mut cogs_from_perc:=cogs_perc.action(action:.multiply,rows:[revenue],name:"cogs_from_perc")!
|
||||
cogs_percent_temp.delete()
|
||||
|
||||
// DEAL WITH MAINTENANCE
|
||||
|
||||
// make sum of all past revenue (all one off revenue, needed to calculate maintenance)
|
||||
mut temp_past := revenue.recurring(
|
||||
nrmonths: nr_months_recurring
|
||||
name: 'temp_past'
|
||||
// delaymonths:4
|
||||
)!
|
||||
|
||||
mut maintenance_month_perc := action.params.get_percentage_default('maintenance_month_perc',
|
||||
'0%')!
|
||||
|
||||
mut maintenance_month := m.sheet.row_new(
|
||||
name: '${name}_maintenance_month'
|
||||
growth: '0:${maintenance_month_perc:.2f}'
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'maintenance fee for ${name2}'
|
||||
)!
|
||||
|
||||
maintenance_month.action(action: .multiply, rows: [temp_past])!
|
||||
|
||||
// temp_past.delete()
|
||||
|
||||
// Process revenue_item and revenue_nr if they are provided
|
||||
mut revenue_item_total := m.sheet.row_new(
|
||||
name: '${name}_revenue_item_total'
|
||||
growth: '0:0'
|
||||
tags: 'rev name:${name}'
|
||||
descr: 'Revenue item total for ${name2}'
|
||||
)!
|
||||
|
||||
// If revenue_item and revenue_nr are provided, multiply them
|
||||
if revenue_item.max() > 0 && revenue_nr.max() > 0 {
|
||||
revenue_item_total = revenue_item.action(
|
||||
name: '${name}_revenue_item_total'
|
||||
descr: 'Revenue item total for ${name2}'
|
||||
action: .multiply
|
||||
rows: [revenue_nr]
|
||||
)!
|
||||
}
|
||||
|
||||
// TOTALS
|
||||
|
||||
mut revenue_total := m.sheet.row_new(
|
||||
name: '${name}_revenue_total'
|
||||
growth: '0:0'
|
||||
tags: 'rev revtotal name:${name}'
|
||||
descr: 'Revenue total for ${name2}.'
|
||||
)!
|
||||
|
||||
mut cogs_total := m.sheet.row_new(
|
||||
name: '${name}_cogs_total'
|
||||
growth: '0:0'
|
||||
tags: 'rev cogstotal name:${name}'
|
||||
descr: 'COGS total for ${name2}.'
|
||||
)!
|
||||
|
||||
if revenue_total.max() > 0.0 || cogs_total.max() > 0.0 {
|
||||
product.has_revenue
|
||||
}
|
||||
|
||||
revenue_total = revenue_total.action(
|
||||
mut cogs := cogs_param.action(
|
||||
action: .add
|
||||
rows: [revenue, revenue_items, revenue_growth, revenue_item_total, revenue_monthly_total,
|
||||
revenue_setup_total, maintenance_month]
|
||||
rows: [cogs_percent_temp]
|
||||
name: '${r.name}_cogs'
|
||||
tags: 'cogs cogs:${r.name} name:${r.name}'
|
||||
)!
|
||||
|
||||
if revenue_total.max() > 0 {
|
||||
if revenue.max() > 0 {
|
||||
product.has_revenue = true
|
||||
}
|
||||
|
||||
cogs_total = cogs_total.action(
|
||||
action: .add
|
||||
rows: [cogs, cogs_monthly_total, cogs_setup_total, cogs_setup_from_perc,
|
||||
cogs_monthly_from_perc]
|
||||
)!
|
||||
|
||||
// if true{
|
||||
// //println(m.sheet)
|
||||
// println(revenue_total)
|
||||
// println(cogs_total)
|
||||
// println(cogs)
|
||||
// println(cogs_monthly_total)
|
||||
// println(cogs_setup_total)
|
||||
// println(cogs_setup_from_perc)
|
||||
// println(cogs_monthly_from_perc)
|
||||
// panic("sdsd")
|
||||
|
||||
// }
|
||||
return action
|
||||
}
|
||||
|
||||
// revenue_total calculates and aggregates the total revenue and cost of goods sold (COGS) for the business model
|
||||
fn (mut sim BizModel) revenue_total() ! {
|
||||
// Create a new row in the sheet to represent the total revenue across all products
|
||||
sim.sheet.group2row(
|
||||
name: 'revenue_total'
|
||||
tags: ''
|
||||
descr: 'total revenue.'
|
||||
)!
|
||||
|
||||
// Create a new row in the sheet to represent the total COGS across all products
|
||||
sim.sheet.group2row(
|
||||
name: 'cogs_total'
|
||||
tags: ''
|
||||
descr: 'total cogs.'
|
||||
)!
|
||||
|
||||
// Note: The following commented-out code block seems to be for debugging or future implementation
|
||||
// It demonstrates how to create a smaller version of the sheet with specific filters
|
||||
// if true{
|
||||
// // name string
|
||||
// // namefilter []string // only include the exact names as specified for the rows
|
||||
// // includefilter []string // matches for the tags
|
||||
// // excludefilter []string // matches for the tags
|
||||
// // period_months int = 12
|
||||
// mut r:=sim.sheet.tosmaller(name:"tmp",includefilter:["cogstotal"],period_months:12)!
|
||||
// println(r)
|
||||
// panic("sdsd")
|
||||
// }
|
||||
}
|
||||
}
|
||||
203
lib/biz/bizmodel/play_product_revenue_item.v
Normal file
203
lib/biz/bizmodel/play_product_revenue_item.v
Normal file
@@ -0,0 +1,203 @@
|
||||
module bizmodel
|
||||
|
||||
import freeflowuniverse.herolib.core.playbook { Action }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
// see lib/biz/bizmodel/docs/revenue.md
|
||||
fn (mut m BizModel) revenue_item_action(action Action) !Action {
|
||||
|
||||
mut r := get_action_descr(action)!
|
||||
mut product := m.products[r.name]
|
||||
|
||||
mut nr_sold := m.sheet.row_new(
|
||||
name: '${r.name}_nr_sold'
|
||||
growth: action.params.get_default('nr_sold', '0')!
|
||||
tags: 'name:${r.name}'
|
||||
descr: 'nr of items sold/month for ${r.name}'
|
||||
aggregatetype: .avg
|
||||
extrapolate: true
|
||||
)!
|
||||
|
||||
if nr_sold.max() > 0 {
|
||||
product.has_items = true
|
||||
}
|
||||
|
||||
mut revenue_item_setup_param := m.sheet.row_new(
|
||||
name: '${r.name}_revenue_item_setup'
|
||||
growth: action.params.get_default('revenue_item_setup', '0:0')!
|
||||
tags: 'name:${r.name}'
|
||||
descr: 'Item Revenue setup for ${r.name} Param'
|
||||
extrapolate: true
|
||||
)!
|
||||
|
||||
mut revenue_item_monthly_param := m.sheet.row_new(
|
||||
name: '${r.name}_revenue_item_monthly_param'
|
||||
growth: action.params.get_default('revenue_item_monthly', '0:0')!
|
||||
tags: 'name:${r.name}'
|
||||
descr: 'Item Revenue monthly for ${r.name} Param'
|
||||
extrapolate: true
|
||||
)!
|
||||
|
||||
|
||||
mut revenue_item_monthly_perc_temp := revenue_item_setup_param.action(
|
||||
name: '${r.name}_revenue_item_monthly_perc_temp'
|
||||
descr: 'Monthly sales as percentage from Setup Revenue for ${r.name}'
|
||||
action: .multiply
|
||||
val: action.params.get_float_default('revenue_item_monthly_perc', 0.0)!
|
||||
tags: 'name:${r.name}'
|
||||
)!
|
||||
|
||||
mut revenue_item_monthly := revenue_item_monthly_param.action(
|
||||
name: '${r.name}_revenue_item_monthly'
|
||||
descr: 'Item Revenue monthly for ${r.name}'
|
||||
action: .add
|
||||
rows: [revenue_item_monthly_perc_temp]
|
||||
tags: 'name:${r.name}'
|
||||
)!
|
||||
|
||||
|
||||
revenue_item_monthly_perc_temp.delete()
|
||||
|
||||
mut cogs_item_setup_param := m.sheet.row_new(
|
||||
name: '${r.name}_cogs_item_setup_param'
|
||||
growth: action.params.get_default('cogs_item_setup', '0:0')!
|
||||
tags: 'name:${r.name}'
|
||||
descr: 'Item COGS setup for ${r.name} parameter'
|
||||
extrapolate: true
|
||||
)!
|
||||
|
||||
mut cogs_item_monthly_param := m.sheet.row_new(
|
||||
name: '${r.name}_cogs_item_monthly_param'
|
||||
growth: action.params.get_default('cogs_item_monthly', '0:0')!
|
||||
tags: 'name:${r.name}'
|
||||
descr: 'Item COGS monthly for ${r.name} parameter'
|
||||
extrapolate: true
|
||||
)!
|
||||
|
||||
|
||||
mut cogs_item_setup_rev_perc_temp := revenue_item_setup_param.action(
|
||||
name: '${r.name}_cogs_item_setup_rev_perc_temp'
|
||||
descr: 'Setup cogs as percentage from Setup for ${r.name}'
|
||||
action: .multiply
|
||||
val: action.params.get_float_default('cogs_item_setup_rev_perc', 0.0)!
|
||||
tags: 'name:${r.name}'
|
||||
)!
|
||||
|
||||
mut cogs_item_monthly_rev_perc_temp := revenue_item_monthly_param.action(
|
||||
name: '${r.name}_cogs_item_monthly_rev_perc_temp'
|
||||
descr: 'Monthly cogs as percentage from Monthly for ${r.name}'
|
||||
action: .multiply
|
||||
val: action.params.get_float_default('cogs_item_monthly_rev_perc', 0.0)!
|
||||
tags: 'name:${r.name}'
|
||||
)!
|
||||
|
||||
mut cogs_item_setup1 := cogs_item_setup_param.action(
|
||||
name: '${r.name}_cogs_item_setup1'
|
||||
descr: 'Item COGS setup for ${r.name}'
|
||||
action: .add
|
||||
rows: [cogs_item_setup_rev_perc_temp]
|
||||
tags: 'name:${r.name}'
|
||||
)!
|
||||
|
||||
mut cogs_item_monthly := cogs_item_monthly_param.action(
|
||||
name: '${r.name}_cogs_item_monthly'
|
||||
descr: 'Item COGS monthly for ${r.name}'
|
||||
action: .add
|
||||
rows: [cogs_item_monthly_rev_perc_temp]
|
||||
tags: 'name:${r.name}'
|
||||
)!
|
||||
|
||||
cogs_item_setup_rev_perc_temp.delete()
|
||||
cogs_item_monthly_rev_perc_temp.delete()
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// CALCULATE THE TOTAL (multiply with nr sold)
|
||||
|
||||
mut revenue_setup := revenue_item_setup_param.action(
|
||||
name: '${r.name}_revenue_setup'
|
||||
descr: 'Setup sales for ${r.name} total'
|
||||
action: .multiply
|
||||
rows: [nr_sold]
|
||||
tags: 'name:${r.name}'
|
||||
delaymonths: action.params.get_int_default('revenue_item_setup_delay', 0)!
|
||||
)!
|
||||
|
||||
|
||||
mut revenue_monthly_total := revenue_item_monthly.action(
|
||||
name: '${r.name}_revenue_monthly_total'
|
||||
descr: 'Monthly sales for ${r.name} total'
|
||||
action: .multiply
|
||||
rows: [nr_sold]
|
||||
tags: 'name:${r.name}'
|
||||
delaymonths: action.params.get_int_default('revenue_item_monthly_delay', 0)!
|
||||
)!
|
||||
|
||||
mut cogs_setup := cogs_item_setup1.action(
|
||||
name: '${r.name}_cogs_setup'
|
||||
descr: 'Setup COGS for ${r.name} total'
|
||||
action: .multiply
|
||||
rows: [nr_sold]
|
||||
tags: 'name:${r.name}'
|
||||
delaymonths: action.params.get_int_default('cogs_item_delay', 0)!
|
||||
)!
|
||||
|
||||
|
||||
mut cogs_monthly_total := cogs_item_monthly.action(
|
||||
name: '${r.name}_cogs_monthly_total'
|
||||
descr: 'Monthly COGS for ${r.name} total'
|
||||
action: .multiply
|
||||
rows: [nr_sold]
|
||||
tags: 'name:${r.name}'
|
||||
delaymonths: action.params.get_int_default('cogs_item_delay', 0)!
|
||||
)!
|
||||
|
||||
// DEAL WITH RECURRING
|
||||
|
||||
|
||||
mut revenue_monthly_recurring := revenue_monthly_total.recurring(
|
||||
name: '${r.name}_revenue_monthly'
|
||||
descr: 'Revenue monthly recurring for ${r.name}'
|
||||
nrmonths: product.nr_months_recurring
|
||||
)!
|
||||
|
||||
revenue_monthly_total.delete()
|
||||
|
||||
mut cogs_monthly_recurring := cogs_monthly_total.recurring(
|
||||
name: '${r.name}_cogs_monthly'
|
||||
descr: 'COGS monthly recurring for ${r.name}'
|
||||
nrmonths: product.nr_months_recurring
|
||||
)!
|
||||
|
||||
cogs_monthly_total.delete()
|
||||
|
||||
_ := nr_sold.recurring(
|
||||
name: '${r.name}_nr_active'
|
||||
descr: 'Nr products active because of recurring for ${r.name}'
|
||||
nrmonths: product.nr_months_recurring
|
||||
aggregatetype: .max
|
||||
delaymonths: action.params.get_int_default('revenue_item_monthly_delay', 0)!
|
||||
)!
|
||||
|
||||
//DEAL WITH MARGIN
|
||||
|
||||
mut margin_setup_total := revenue_setup.action(
|
||||
name: '${r.name}_margin_setup'
|
||||
descr: 'Setup margin for ${r.name}'
|
||||
action: .substract
|
||||
rows: [cogs_setup]
|
||||
tags: 'name:${r.name}'
|
||||
)!
|
||||
|
||||
|
||||
mut margin_monthly_total := revenue_monthly_recurring.action(
|
||||
name: '${r.name}_margin_monthly'
|
||||
descr: 'Monthly margin for ${r.name}'
|
||||
action: .substract
|
||||
rows: [cogs_monthly_recurring]
|
||||
tags: 'name:${r.name}'
|
||||
)!
|
||||
|
||||
|
||||
return action
|
||||
}
|
||||
28
lib/biz/bizmodel/play_product_revenue_total.v
Normal file
28
lib/biz/bizmodel/play_product_revenue_total.v
Normal file
@@ -0,0 +1,28 @@
|
||||
module bizmodel
|
||||
|
||||
import freeflowuniverse.herolib.core.playbook { Action }
|
||||
|
||||
// revenue_total calculates and aggregates the total revenue and cost of goods sold (COGS) for the business model
|
||||
fn (mut sim BizModel) revenue_total() ! {
|
||||
|
||||
mut sheet:= sim.sheet
|
||||
|
||||
mut revenue_total := sheet.group2row(name:"revenue_total", include:['rev'], tags:"total", descr:'total revenue.')!
|
||||
mut cogs_total := sheet.group2row(name:"cogs_total", include:['cogs'], tags:"total", descr:'total cogs.')!
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
fn (mut sim BizModel) revenue_name_total(action Action) !Action {
|
||||
|
||||
mut r := get_action_descr(action)!
|
||||
mut product := sim.products[r.name]
|
||||
|
||||
mut sheet:= sim.sheet
|
||||
|
||||
mut revenue_total := sheet.group2row(name:"${r.name}_revenue_total", include:['rev:${r.name}'], tags:"name:${r.name}", descr:'total revenue for ${r.name}.')!
|
||||
mut cogs_total := sheet.group2row(name:"${r.name}_cogs_total", include:['cogs:${r.name}'], tags:"name:${r.name}", descr:'total cogs for ${r.name}.')!
|
||||
|
||||
return action
|
||||
}
|
||||
37
lib/biz/bizmodel/play_tools.v
Normal file
37
lib/biz/bizmodel/play_tools.v
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
module bizmodel
|
||||
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.playbook { Action }
|
||||
|
||||
pub struct RowDescrFields{
|
||||
pub mut:
|
||||
name string
|
||||
title string
|
||||
description string
|
||||
}
|
||||
|
||||
fn get_action_descr(action Action) !RowDescrFields {
|
||||
|
||||
mut r:=RowDescrFields{}
|
||||
|
||||
r.name= action.params.get_default('name', '')!
|
||||
r.description= action.params.get_default('descr', '')!
|
||||
if r.description.len == 0 {
|
||||
r.description = action.params.get_default('description', '')!
|
||||
}
|
||||
if r.name.len == 0 {
|
||||
// make name ourselves
|
||||
r.name = texttools.name_fix(r.description)
|
||||
}
|
||||
|
||||
r.name = texttools.name_fix(r.name)
|
||||
if r.name.len == 0 {
|
||||
return error('name and description is empty for ${action}')
|
||||
}
|
||||
r.name = r.name.replace('_', ' ').replace('-', ' ')
|
||||
r.description = r.description.replace('_', ' ').replace('-', ' ')
|
||||
|
||||
return r
|
||||
|
||||
}
|
||||
@@ -19,7 +19,7 @@ pub struct RowActionArgs {
|
||||
pub mut:
|
||||
name string
|
||||
action RowAction
|
||||
val f64
|
||||
val ?f64
|
||||
rows []&Row
|
||||
tags string
|
||||
descr string
|
||||
@@ -98,15 +98,16 @@ pub fn (mut r Row) action(args_ RowActionArgs) !&Row {
|
||||
}
|
||||
}
|
||||
}
|
||||
if args.val > 0.0 {
|
||||
val := args.val or { 999999999 }
|
||||
if val != 999999999 {
|
||||
if args.action == .add {
|
||||
row_result.cells[x].val = row_result.cells[x].val + args.val
|
||||
row_result.cells[x].val = row_result.cells[x].val + val
|
||||
} else if args.action == .substract {
|
||||
row_result.cells[x].val = row_result.cells[x].val - args.val
|
||||
row_result.cells[x].val = row_result.cells[x].val - val
|
||||
} else if args.action == .multiply {
|
||||
row_result.cells[x].val = row_result.cells[x].val * args.val
|
||||
row_result.cells[x].val = row_result.cells[x].val * val
|
||||
} else if args.action == .divide {
|
||||
row_result.cells[x].val = row_result.cells[x].val / args.val
|
||||
row_result.cells[x].val = row_result.cells[x].val / val
|
||||
} else if args.action == .aggregate {
|
||||
row_result.cells[x].val = row_result.cells[x].val + prevval
|
||||
prevval = row_result.cells[x].val
|
||||
@@ -115,12 +116,12 @@ pub fn (mut r Row) action(args_ RowActionArgs) !&Row {
|
||||
} else if args.action == .roundint {
|
||||
row_result.cells[x].val = int(row_result.cells[x].val)
|
||||
} else if args.action == .max {
|
||||
if args.val > row_result.cells[x].val {
|
||||
row_result.cells[x].val = args.val
|
||||
if val > row_result.cells[x].val {
|
||||
row_result.cells[x].val = val
|
||||
}
|
||||
} else if args.action == .min {
|
||||
if args.val < row_result.cells[x].val {
|
||||
row_result.cells[x].val = args.val
|
||||
if val < row_result.cells[x].val {
|
||||
row_result.cells[x].val = val
|
||||
}
|
||||
} else {
|
||||
return error('Action wrongly specified for ${r} with\nargs:${args}')
|
||||
@@ -141,9 +142,8 @@ pub fn (mut r Row) action(args_ RowActionArgs) !&Row {
|
||||
return row_result
|
||||
}
|
||||
|
||||
// pub fn (mut r Row) add(name string, r2 Row) !&Row {
|
||||
// return r.action(name:name, rows:[]r2, tags:r.tags)
|
||||
// }
|
||||
|
||||
|
||||
pub fn (mut r Row) delay(monthdelay int) ! {
|
||||
mut todelay := []f64{}
|
||||
for x in 0 .. r.sheet.nrcol {
|
||||
|
||||
@@ -23,7 +23,7 @@ pub mut:
|
||||
nr_columns int = 0 //number of columns to show in the table, 0 is all
|
||||
description bool //show description in the table
|
||||
aggrtype bool = true //show aggregate type in the table
|
||||
tags bool //show tags in the table
|
||||
tags bool = true //show tags in the table
|
||||
subgroup bool //show subgroup in the table
|
||||
}
|
||||
// calculate_column_widths calculates the maximum width for each column
|
||||
@@ -31,8 +31,14 @@ fn calculate_column_widths(rows [][]string) []int {
|
||||
if rows.len == 0 {
|
||||
return []int{}
|
||||
}
|
||||
mut max_nr_cols := 0
|
||||
for row in rows {
|
||||
if row.len > max_nr_cols {
|
||||
max_nr_cols = row.len
|
||||
}
|
||||
}
|
||||
|
||||
mut widths := []int{len: rows[0].len, init: 0}
|
||||
mut widths := []int{len: max_nr_cols, init: 0}
|
||||
for _, row in rows {
|
||||
for i, cell in row {
|
||||
if cell.len > widths[i] {
|
||||
@@ -124,6 +130,32 @@ pub fn (mut s Sheet) pprint(args PPrintArgs) ! {
|
||||
}
|
||||
}
|
||||
|
||||
if args.nr_columns > 0 {
|
||||
mut data_start_index := 1 // for row.name
|
||||
if args.description {
|
||||
data_start_index++
|
||||
}
|
||||
if args.aggrtype {
|
||||
data_start_index++
|
||||
}
|
||||
if args.tags {
|
||||
data_start_index++
|
||||
}
|
||||
if args.subgroup {
|
||||
data_start_index++
|
||||
}
|
||||
max_cols := data_start_index + args.nr_columns
|
||||
mut new_all_rows := [][]string{}
|
||||
for i, row in all_rows {
|
||||
if row.len > max_cols {
|
||||
new_all_rows << row[0..max_cols]
|
||||
} else {
|
||||
new_all_rows << row
|
||||
}
|
||||
}
|
||||
all_rows = new_all_rows.clone()
|
||||
}
|
||||
|
||||
// Calculate column widths
|
||||
widths := calculate_column_widths(all_rows)
|
||||
|
||||
|
||||
@@ -50,7 +50,15 @@ pub fn (params &Params) get_int(key string) !int {
|
||||
}
|
||||
|
||||
pub fn (params &Params) get_float(key string) !f64 {
|
||||
valuestr := params.get(key)!
|
||||
mut valuestr := params.get(key)!
|
||||
if valuestr.contains("%"){
|
||||
valuestr = valuestr.replace('%', '')
|
||||
mut vs:=strconv.atof64(valuestr) or {
|
||||
return error('Parameter ${key} = ${valuestr} is not a valid 64-bit float')
|
||||
}
|
||||
vs=vs/100
|
||||
return vs
|
||||
}
|
||||
return strconv.atof64(valuestr) or {
|
||||
return error('Parameter ${key} = ${valuestr} is not a valid 64-bit float')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user