mirror of
https://github.com/aclindsa/moneygo.git
synced 2024-12-26 15:42:27 -05:00
Add markdown documentation
This commit is contained in:
parent
aeed78f0b7
commit
5047dc6948
@ -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
|
This project is in active development and is not yet ready to be relied upon as
|
||||||
your primary accounting software.
|
your primary accounting software.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
[Documentation in markdown](./docs/index.md)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
First, install npm, python, curl, and go >= 1.7 in your distribution. Here is
|
First, install npm, python, curl, and go >= 1.7 in your distribution. Here is
|
||||||
|
@ -107,6 +107,8 @@ func luaAccount__index(L *lua.LState) int {
|
|||||||
} else {
|
} else {
|
||||||
panic("SecurityId not in lua security_map")
|
panic("SecurityId not in lua security_map")
|
||||||
}
|
}
|
||||||
|
case "SecurityId", "securityid":
|
||||||
|
L.Push(lua.LNumber(float64(a.SecurityId)))
|
||||||
case "Parent", "parent", "ParentAccount", "parentaccount":
|
case "Parent", "parent", "ParentAccount", "parentaccount":
|
||||||
if a.ParentAccountId == -1 {
|
if a.ParentAccountId == -1 {
|
||||||
L.Push(lua.LNil)
|
L.Push(lua.LNil)
|
||||||
|
4
docs/index.md
Normal file
4
docs/index.md
Normal 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
155
docs/lua_reports.md
Normal 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`.
|
@ -37,6 +37,36 @@ func luaContextGetSecurities(L *lua.LState) (map[int64]*Security, error) {
|
|||||||
return security_map, nil
|
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 {
|
func luaGetSecurities(L *lua.LState) int {
|
||||||
security_map, err := luaContextGetSecurities(L)
|
security_map, err := luaContextGetSecurities(L)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -62,9 +92,13 @@ func luaRegisterSecurities(L *lua.LState) {
|
|||||||
L.SetField(mt, "__metatable", lua.LString("protected"))
|
L.SetField(mt, "__metatable", lua.LString("protected"))
|
||||||
getSecuritiesFn := L.NewFunction(luaGetSecurities)
|
getSecuritiesFn := L.NewFunction(luaGetSecurities)
|
||||||
L.SetField(mt, "get_all", getSecuritiesFn)
|
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_securities", getSecuritiesFn)
|
||||||
|
L.SetGlobal("get_default_currency", getDefaultCurrencyFn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SecurityToLua(L *lua.LState, security *Security) *lua.LUserData {
|
func SecurityToLua(L *lua.LState, security *Security) *lua.LUserData {
|
||||||
|
Loading…
Reference in New Issue
Block a user