Add markdown documentation

This commit is contained in:
Aaron Lindsay 2017-06-25 06:14:44 -04:00
parent aeed78f0b7
commit 5047dc6948
5 changed files with 200 additions and 1 deletions

View File

@ -9,6 +9,10 @@ OFX (via [ofxgo](https://github.com/aclindsa/ofxgo)).
This project is in active development and is not yet ready to be relied upon as
your primary accounting software.
## Documentation
[Documentation in markdown](./docs/index.md)
## Installation
First, install npm, python, curl, and go >= 1.7 in your distribution. Here is

View File

@ -107,6 +107,8 @@ func luaAccount__index(L *lua.LState) int {
} else {
panic("SecurityId not in lua security_map")
}
case "SecurityId", "securityid":
L.Push(lua.LNumber(float64(a.SecurityId)))
case "Parent", "parent", "ParentAccount", "parentaccount":
if a.ParentAccountId == -1 {
L.Push(lua.LNil)

4
docs/index.md Normal file
View File

@ -0,0 +1,4 @@
# MoneyGo Documentation
* [Lua Reports](lua_reports.md)
* [Importing Transactions using OFX](ofx_imports.md)

155
docs/lua_reports.md Normal file
View File

@ -0,0 +1,155 @@
# Lua Reports
MoneyGo reports are written in [Lua](https://lua.org), as implemented by
[github.com/yuin/gopher-lua](https://github.com/yuin/gopher-lua), with hooks
added to query the necessary MoneyGo state to generate the report.
## An Example: Monthly Cash Flow Report
Before diving into the details, here's an example report that calculates the
difference between income and expenses for each month in the current year:
```
function generate()
year = date.now().year
accounts = get_accounts()
t = tabulation.new(12)
t:title(year .. " Monthly Cash Flow")
series = t:series("Income minus expenses")
for month=1,12 do
begin_date = date.new(year, month, 1)
end_date = date.new(year, month+1, 1)
t:label(month, tostring(begin_date))
cash_flow = 0
for id, acct in pairs(accounts) do
if acct.type == account.Expense or acct.type == account.Income then
balance = acct:balance(begin_date, end_date)
--[[
Note: We should convert balance.amount to the user's default currency
before proceeding here
--]]
cash_flow = cash_flow - balance.amount
end
end
series:value(month, cash_flow)
end
return t
end
```
## Basic Operation
The lua code behind a report *must* contain a `generate()` function which takes
no arguments. This function is called when generating a report, and must return
a `tabulation` object, created by calling `t = tabulation:new(n)`, where `n` is
the integer number of data values in each of the series of this tabulation (all
series in the same tabulation must have the same number of values).
### Titles and Labels
Assuming your tabulation object is `t`, you should then call `t.label(m,
"some_string")` for each value of `m` in `[1, n]` to set the label for the 'm'th
data element in each series. You do not need to do this before creating series,
and can do it lazily as you generate data, if needed. Titles, subtitles, and the
y-axis label (the units) can be set as follows on a tabulation object named `t`:
* `t.title("The title of my report")`
* `t.subtitle("The subtitle of my report")`
* `t.units("USD ($)")`
### Data Series
To create a new top-level series, call `s = t:series("series name")` where `t`
is a tabulation object. Just as for labels for tabulation objects, you set
`s:value(m, number)` for each value of `m` in `[1, n]` (where `n` is the same
integer used to create the tabulation object to which this series belongs).
Nested series can be created by calling `s2 = s:series("nested series name")`,
where `s` is any already-created series object. Nested series allow for drilling
down to explore the information in more detail. In the web interface, they are
clickable and cause the charts to display the selected series as the new top
level.
It is assumed that nested series' reported values are not already included in
their parents' values. When being displayed, all the children series values are
added into the parent's. This means that a series may have no values of its own,
but still show up in a chart because it is reporting the sum of its children's
values.
## Gathering Data
Collecting/tabulating the data is up to you (chasing this flexibility was the
impetus behind Lua reports in the first place).
### Accounts and Balances
You can get a table of account objects for each of your accounts (indexed by
account ID) by calling the global function, `get_accounts()`. Each of these
accounts has several fields describing it:
* `a.Name` returns the account's name
* `a.Description` returns the account's description
* `a.Type` returns the account's type, as an integer constant. The account type
constants are available on the top-level 'account' object
* `account.Bank`
* `account.Cash`
* `account.Asset`
* `account.Liability`
* `account.Investment`
* `account.Income`
* `account.Expense`
* `account.Trading`
* `account.Equity`
* `account.Receivable`
* `account.Payable`
* `a.TypeName` returns a string representation of the account's type
* `a.Security` returns a security object representing the currency, stock, etc.
of this account.
* `a.Parent` returns this account's parent account, or nil if the account has no
parent.
* `a:Balance` is a function which returns the account balance in the account's
security, optionally over a date range. If no arguments are provided, the
total account balance as of the end of time is returned. If one date is
provided, the balance as of that date is returned. If two dates are provided,
the difference in balances between the first and second dates is returned.
### Securities
You can get a table containing all the securities/currencies registered to an
account using the global function `get_securities()`. Each of these securities
has several fields describing it:
* `s.SecurityId`
* `s.Name`
* `s.Description`
* `s.Symbol` returns the symbol traditionally associated with that security
(i.e. '$' for USD, or BRK.B for class B shares of Berkshire Hathaway)
* `s.Precision` returns the number of digits of precision past the decimal point
that this currency allows for (i.e. 2 for USD)
* `s.Type` returns an int constant which represents what type of security it is
(i.e. stock or currency)
You can also query for an account's default currency using the global
`get_default_currency()` function.
### Dates
In order to make it easier to do operations like finding account balances for a
month at a time, MoneyGo implements it's own date type (eschewing the
traditional Lua implementation). You *must* use the MoneyGo date types when
passing them to any MoneyGo lua functions. To create a date object, you can use
one of two methods:
1. `date.now()` returns the current date
2. `date.new(2017, 7, 5)` returns a date object representing July 5th, 2017.
Note that this method also accepts a single argument of a table with the
'year', 'month', and 'day' fields set to int's.
In addition to supporting conversion to a string, addition, subtraction, and
comparison operators, dates support returning their constituent parts using
`d.Year`, `d.Month`, and `d.Day`.

View File

@ -37,6 +37,36 @@ func luaContextGetSecurities(L *lua.LState) (map[int64]*Security, error) {
return security_map, nil
}
func luaContextGetDefaultCurrency(L *lua.LState) (*Security, error) {
security_map, err := luaContextGetSecurities(L)
if err != nil {
return nil, err
}
ctx := L.Context()
user, ok := ctx.Value(userContextKey).(*User)
if !ok {
return nil, errors.New("Couldn't find User in lua's Context")
}
if security, ok := security_map[user.DefaultCurrency]; ok {
return security, nil
} else {
return nil, errors.New("DefaultCurrency not in lua security_map")
}
}
func luaGetDefaultCurrency(L *lua.LState) int {
defcurrency, err := luaContextGetDefaultCurrency(L)
if err != nil {
panic("luaGetDefaultCurrency couldn't fetch default currency")
}
L.Push(SecurityToLua(L, defcurrency))
return 1
}
func luaGetSecurities(L *lua.LState) int {
security_map, err := luaContextGetSecurities(L)
if err != nil {
@ -62,9 +92,13 @@ func luaRegisterSecurities(L *lua.LState) {
L.SetField(mt, "__metatable", lua.LString("protected"))
getSecuritiesFn := L.NewFunction(luaGetSecurities)
L.SetField(mt, "get_all", getSecuritiesFn)
getDefaultCurrencyFn := L.NewFunction(luaGetDefaultCurrency)
L.SetField(mt, "get_default", getDefaultCurrencyFn)
// also register the get_securities function as a global in its own right
// also register the get_securities and get_default functions as globals in
// their own right
L.SetGlobal("get_securities", getSecuritiesFn)
L.SetGlobal("get_default_currency", getDefaultCurrencyFn)
}
func SecurityToLua(L *lua.LState, security *Security) *lua.LUserData {