blob: 2c3f97ab83fe07644dcbfd4fa74ac76ac3c8c810 [file] [log] [blame]
#
# FY23 budget worksheet
#
class FY23 < Vue
def initialize
@budget = (Minutes.started && Minutes.get('budget')) || {
donations: 220,
sponsorship: 1665,
infrastructure: 1099,
publicity: 387,
brandManagement: 225,
conferences: 60,
travelAssistance: 25,
treasury: 61,
fundraising: 283,
generalAndAdministrative: 44,
chairman: 10,
}
if User.role == :secretary or not Minutes.started
@disabled = false
else
@disabled = true
end
end
def render
_style %{
.table thead tr th {text-align: right}
.table tbody tr td {text-align: left}
.table tbody tr td.num {text-align: right}
.table tbody tr td.indented {padding-left: 2em}
.table tbody tr td input {align: right; text-align: right}
.table tbody tr td a {color: blue; text-decoration:underline}
}
_p "Instructions: change any input field and press the tab key to see " +
"new results. Try to keep FY23 Budget Cash non-negative."
_table.table.table_sm.table_striped do
_thead do
_tr do
_th
_th do
_a 'FY18', href: 'https://whimsy.apache.org/board/minutes/Budget.html#2017-04-19'
end
_th do
_a 'FY22', href: 'https://whimsy.apache.org/board/minutes/Discussion_Items.html#2017-02-27'
end
_th 'FY23 Budget'
_td
end
end
_tbody do
_tr do
_td 'Income', colspan: 4
end
_tr do
_td.indented do
_a 'Total Public Donations', href: 'https://s.apache.org/zFgy'
end
_td.num 111
_td.num 135
_td.num do
_input.donations! onBlur: self.change, disabled: @disabled,
value: @budget.donations.toLocaleString()
end
end
_tr do
_td.indented do
_a 'Total Sponsorship', href: 'https://s.apache.org/zFgy'
end
_td.num 1_084.toLocaleString()
_td.num 1_500.toLocaleString()
_td.num do
_input.sponsorship! onBlur: self.change, disabled: @disabled,
value: @budget.sponsorship.toLocaleString()
end
end
_tr do
_td.indented 'Total Programs'
_td.num 28
_td.num 28
_td.num 28
end
_tr do
_td.indented 'Interest Income'
_td.num 4
_td.num 4
_td.num 4
end
_tr do
_td
_td.num '----'
_td.num '----'
_td.num '----'
end
_tr do
_td.indented 'Total Income'
_td.num 1_227.toLocaleString()
_td.num 1_667.toLocaleString()
_td.num.income! @budget.income.toLocaleString()
end
_tr do
_td colspan: 4
end
_tr do
_td 'Expense', colspan: 4
end
_tr do
_td.indented do
_a 'Infrastructure', href: 'https://s.apache.org/Db5J'
end
_td.num 818
_td.num 868
_td.num do
_input.infrastructure! onBlur: self.change, disabled: @disabled,
value: @budget.infrastructure.toLocaleString()
end
end
_tr do
_td.indented 'Program Expenses'
_td.num 27
_td.num 27
_td.num 27
end
_tr do
_td.indented do
_a 'Publicity', href: 'https://s.apache.org/OTCg'
end
_td.num 182
_td.num 352
_td.num do
_input.publicity! onBlur: self.change, disabled: @disabled,
value: @budget.publicity.toLocaleString()
end
end
_tr do
_td.indented do
_a 'Brand Management', href: 'https://s.apache.org/qHyk'
end
_td.num 89
_td.num 141
_td.num do
_input.brandManagement! onBlur: self.change, disabled: @disabled,
value: @budget.brandManagement.toLocaleString()
end
end
_tr do
_td.indented do
_a 'Conferences', href: 'https://s.apache.org/wPGQ'
end
_td.num 60
_td.num 12
_td.num do
_input.conferences! onBlur: self.change, disabled: @disabled,
value: @budget.conferences.toLocaleString()
end
end
_tr do
_td.indented do
_a 'Travel Assistance', href: 'https://s.apache.org/22KK'
end
_td.num 50
_td.num 79
_td.num do
_input.travelAssistance! onBlur: self.change, disabled: @disabled,
value: @budget.travelAssistance.toLocaleString()
end
end
_tr do
_td.indented do
_a 'Treasury', href: 'https://s.apache.org/zFgy'
end
_td.num 49
_td.num 51
_td.num do
_input.treasury! onBlur: self.change, disabled: @disabled,
value: @budget.treasury.toLocaleString()
end
end
_tr do
_td.indented do
_a 'Fundraising', href: 'https://s.apache.org/7kuk'
end
_td.num 46
_td.num 53
_td.num do
_input.fundraising! onBlur: self.change, disabled: @disabled,
value: @budget.fundraising.toLocaleString()
end
end
_tr do
_td.indented do
_a 'General & Administrative', href: 'https://s.apache.org/4LdI'
end
_td.num 118
_td.num 139
_td.num do
_input.generalAndAdministrative! onBlur: self.change,
disabled: @disabled,
value: @budget.generalAndAdministrative.toLocaleString()
end
end
_tr do
_td.indented do
_ "Chairman's Discretionary"
end
_td.num 10
_td.num 0
_td.num do
_input.generalAndAdministrative! onBlur: self.change,
disabled: @disabled,
value: @budget.chairman.toLocaleString()
end
end
_tr do
_td
_td.num '----'
_td.num '----'
_td.num '----'
end
_tr do
_td.indented 'Total Expense'
_td.num 1_418.toLocaleString()
_td.num 1_722.toLocaleString()
_td.num.expense! @budget.expense.toLocaleString()
end
_tr do
_td colspan: 4
end
_tr do
_td 'Net'
_td.num -212
_td.num -55
_td.num.net! @budget.net.toLocaleString()
end
_tr do
_td colspan: 4
end
_tr do
_td 'Cash'
_td.num 1_767.toLocaleString()
_td.num 595
_td.num.cash! @budget.cash.toLocaleString(),
class: (@budget.cash < 0 ? 'danger' : 'success')
end
end
end
_p "Units are in thousands of dollars US."
end
# evaluate computed fields
def recalc()
@budget.income = @budget.donations + @budget.sponsorship + 28 + 4
@budget.expense = @budget.infrastructure + 27 + @budget.publicity +
@budget.brandManagement + @budget.conferences +
@budget.travelAssistance + @budget.treasury + @budget.fundraising +
@budget.generalAndAdministrative;
@budget.net = @budget.income - @budget.expense
@budget.cash = 1767 + # Virtual's projection for cash on hand at the end
# of FY18.
(2*-212) + (3*@budget.net) +
# linear projection for expenses from FY18 to FY23.
# Presuming:
# FY19 = FY18 + 1 * (FY23-FY18)/5
# FY20 = FY18 + 2 * (FY23-FY18)/5
# FY21 = FY18 + 3 * (FY23-FY18)/5
# FY22 = FY18 + 4 * (FY23-FY18)/5
# FY23 = FY18 + 5 * (FY23-FY18)/5
# Total = 5*Fy18 + 15*(FY23-FY18/5
# = 5*Fy18 + 3*(FY23-FY18)
# = 5*Fy18 + 3*FY23 - 3 * FY18
# = 2*Fy18 + 3*FY23
2*100 +
# EA adjustment: instead of ramping down, the
# reduction in expense is immediate and constant.
600
# BTC adjustment: we expect to see a minimum of $600K
# from the recent PineApple Fund BTC donation.
end
# update budget item when an input field changes
def change(event)
console.log 'event.target.id'
@budget[event.target.id] = parseInt(event.target.value.gsub(/\D/, '')) || 0
event.target.value = @budget[event.target.id].toLocaleString()
self.recalc()
if User.role == :secretary and Minutes.started
post 'budget', agenda: Agenda.file, budget: @budget do |budget|
@budget = budget if budget
end
end
Vue.forceUpdate()
end
# receive updated budget values
def created()
self.recalc();
budget = Minutes.get('budget')
if budget and budget != @budget and Minutes.started
budget.each_pair do |item, date|
element = document.getElementById(item)
if element.tagName == 'INPUT'
element.value = date.toLocaleString()
else
element.textContent = date.toLocaleString()
end
end
@budget = budget
@disabled = true unless User.role == :secretary
end
end
end