Compare commits
11 Commits
ea83759be2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e23cce19ad | ||
|
|
a8157b2f1e | ||
|
|
0939bc0c41 | ||
|
|
3e9349078c | ||
|
|
1345071f0a | ||
|
|
03ec3247f9 | ||
|
|
8a7f97d230 | ||
|
|
273b466b43 | ||
|
|
01fda04fde | ||
|
|
114a17245e | ||
|
|
f7dd025347 |
1
.gitignore
vendored
Normal file → Executable file
1
.gitignore
vendored
Normal file → Executable file
@@ -1 +1,2 @@
|
|||||||
**/*.sw*
|
**/*.sw*
|
||||||
|
ledger-ui
|
||||||
|
|||||||
0
balance.go
Normal file → Executable file
0
balance.go
Normal file → Executable file
10
go.mod
Normal file
10
go.mod
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
module gogs.inhome.blapointe.com/bel/ledger-ui
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/uuid v1.3.0
|
||||||
|
gogs.inhome.blapointe.com/local/args v0.0.0-20230410154220-44370f257b34
|
||||||
|
)
|
||||||
|
|
||||||
|
require gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
8
go.sum
Normal file
8
go.sum
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
gogs.inhome.blapointe.com/local/args v0.0.0-20230410154220-44370f257b34 h1:0tuX5dfOksiOQD1vbJjVNVTVxTTIng7UrUdSLF5T+Ao=
|
||||||
|
gogs.inhome.blapointe.com/local/args v0.0.0-20230410154220-44370f257b34/go.mod h1:YG9n3Clg7683ohkVnJK2hdX8bBS9EojIsd1qPZumX0Y=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
18
ledger.go
Normal file → Executable file
18
ledger.go
Normal file → Executable file
@@ -8,6 +8,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Ledger struct {
|
type Ledger struct {
|
||||||
@@ -24,6 +25,21 @@ func NewLedger(path string) (Ledger, error) {
|
|||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ledger Ledger) NewTransaction() error {
|
||||||
|
transactions, err := ledger.Transactions()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
transactions = append(transactions, Transaction{
|
||||||
|
Date: time.Now().Format("2006-01-02"),
|
||||||
|
Description: "?",
|
||||||
|
Payer: "?",
|
||||||
|
Payee: "?",
|
||||||
|
Amount: 0,
|
||||||
|
})
|
||||||
|
return ledger.SetTransactions(transactions)
|
||||||
|
}
|
||||||
|
|
||||||
func (ledger Ledger) SetTransaction(i int, transaction Transaction) error {
|
func (ledger Ledger) SetTransaction(i int, transaction Transaction) error {
|
||||||
transactions, err := ledger.Transactions()
|
transactions, err := ledger.Transactions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -37,7 +53,7 @@ func (ledger Ledger) SetTransaction(i int, transaction Transaction) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ledger Ledger) SetTransactions(transactions []Transaction) error {
|
func (ledger Ledger) SetTransactions(transactions []Transaction) error {
|
||||||
f, err := ioutil.TempFile(os.TempDir(), path.Base(ledger.path)+".*")
|
f, err := ioutil.TempFile(path.Dir(ledger.path), path.Base(ledger.path)+".*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
55
ledger_test.go
Normal file → Executable file
55
ledger_test.go
Normal file → Executable file
@@ -2,7 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -10,8 +10,19 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func testLedgerFile(t *testing.T) string {
|
||||||
|
path := path.Join(t.TempDir(), t.Name()+".dat")
|
||||||
|
if b, err := ioutil.ReadFile("./testdata/ledger.dat"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if err := ioutil.WriteFile(path, b, os.ModePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
func TestLedgerTransactionsBalances(t *testing.T) {
|
func TestLedgerTransactionsBalances(t *testing.T) {
|
||||||
ledger, err := NewLedger("./testdata/ledger.dat")
|
path := testLedgerFile(t)
|
||||||
|
ledger, err := NewLedger(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -67,21 +78,7 @@ func TestLedgerSetTransaction(t *testing.T) {
|
|||||||
Amount: 1.23,
|
Amount: 1.23,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
path := path.Join(t.TempDir(), "ledger.dat")
|
path := testLedgerFile(t)
|
||||||
a, err := os.Open("./testdata/ledger.dat")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
b, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if _, err := io.Copy(b, a); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
a.Close()
|
|
||||||
b.Close()
|
|
||||||
|
|
||||||
ledger, err := NewLedger(path)
|
ledger, err := NewLedger(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -109,3 +106,27 @@ func TestLedgerSetTransaction(t *testing.T) {
|
|||||||
t.Fatal(want, got)
|
t.Fatal(want, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLedgerNewTransaction(t *testing.T) {
|
||||||
|
path := testLedgerFile(t)
|
||||||
|
ledger, err := NewLedger(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
transactions, err := ledger.Transactions()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wantTransactions := append(transactions, newStubTransaction())
|
||||||
|
ledger.NewTransaction()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
gotTransactions, err := ledger.Transactions()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if fmt.Sprint(wantTransactions) != fmt.Sprint(gotTransactions) {
|
||||||
|
t.Fatal(wantTransactions, gotTransactions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
4
main.go
Normal file → Executable file
4
main.go
Normal file → Executable file
@@ -2,7 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"local/args"
|
"gogs.inhome.blapointe.com/local/args"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
log.Println("listening on", as.GetInt("p"))
|
||||||
if err := http.ListenAndServe(":"+fmt.Sprint(as.GetInt("p")), Server{ledger: ledger, debug: as.GetBool("debug")}); err != nil {
|
if err := http.ListenAndServe(":"+fmt.Sprint(as.GetInt("p")), Server{ledger: ledger, debug: as.GetBool("debug")}); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
0
public/balances.json
Normal file → Executable file
0
public/balances.json
Normal file → Executable file
80
public/index.html
Normal file → Executable file
80
public/index.html
Normal file → Executable file
@@ -1,7 +1,17 @@
|
|||||||
<html>
|
<html>
|
||||||
<header>
|
<header>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/dark.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/dark.css">
|
||||||
<style>
|
<style>
|
||||||
|
body > div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
#transactions > tbody > tr > td:first-child input {
|
#transactions > tbody > tr > td:first-child input {
|
||||||
padding: 1ch;
|
padding: 1ch;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -30,13 +40,22 @@
|
|||||||
loadBalances(body.Balances)
|
loadBalances(body.Balances)
|
||||||
}, null)
|
}, null)
|
||||||
}
|
}
|
||||||
|
function prettyMoney(money) {
|
||||||
|
try {
|
||||||
|
var f = parseFloat(money)
|
||||||
|
if (f) {
|
||||||
|
return Math.round(100.0*f) / 100.0
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
return money;
|
||||||
|
}
|
||||||
function loadBalances(balances) {
|
function loadBalances(balances) {
|
||||||
var innerHTML = ""
|
var innerHTML = ""
|
||||||
innerHTML += "<table>"
|
innerHTML += "<table>"
|
||||||
|
|
||||||
for(var k in balances) {
|
for(var k in balances) {
|
||||||
if(k.startsWith("Asset"))
|
if(k.startsWith("Asset"))
|
||||||
innerHTML += `<tr><td>${k}</td><td>${balances[k]}</td></tr>`
|
innerHTML += `<tr><td>${k}</td><td>${prettyMoney(balances[k])}</td></tr>`
|
||||||
}
|
}
|
||||||
|
|
||||||
innerHTML += "</table>"
|
innerHTML += "</table>"
|
||||||
@@ -68,7 +87,7 @@
|
|||||||
one += " <tr>"
|
one += " <tr>"
|
||||||
one += " <td></td><td></td>"
|
one += " <td></td><td></td>"
|
||||||
for(var key of ["Payee", "Amount"])
|
for(var key of ["Payee", "Amount"])
|
||||||
one += ` <td contenteditable key=${JSON.stringify(key)}>${transaction[key]}</td>`
|
one += ` <td contenteditable key=${JSON.stringify(key)}>${prettyMoney(transaction[key])}</td>`
|
||||||
one += " </tr>"
|
one += " </tr>"
|
||||||
one += " <tr>"
|
one += " <tr>"
|
||||||
one += " <td></td><td></td>"
|
one += " <td></td><td></td>"
|
||||||
@@ -84,6 +103,25 @@
|
|||||||
|
|
||||||
document.getElementById("transactions").innerHTML = innerHTML
|
document.getElementById("transactions").innerHTML = innerHTML
|
||||||
}
|
}
|
||||||
|
function newTransaction() {
|
||||||
|
var today = new Date()
|
||||||
|
var year = today.getFullYear()
|
||||||
|
var month = today.getMonth()+1
|
||||||
|
var day = today.getDate()
|
||||||
|
if (day < 10) {
|
||||||
|
day = `0${day}`
|
||||||
|
}
|
||||||
|
if (month < 10) {
|
||||||
|
month = `0${month}`
|
||||||
|
}
|
||||||
|
http("post", "/api/transactions", () => {init()}, JSON.stringify({
|
||||||
|
Date: `${year}-${month}-${day}`,
|
||||||
|
Description: "?",
|
||||||
|
Payer: "?",
|
||||||
|
Payee: "?",
|
||||||
|
Amount: 0,
|
||||||
|
}))
|
||||||
|
}
|
||||||
function saveTransaction(row) {
|
function saveTransaction(row) {
|
||||||
const inputs = row.getElementsByTagName("td")
|
const inputs = row.getElementsByTagName("td")
|
||||||
var kvs = {}
|
var kvs = {}
|
||||||
@@ -92,29 +130,48 @@
|
|||||||
if (!key) {
|
if (!key) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const value = inputs[i].innerHTML
|
const value = (inputs[i].textContent || inputs[i].innerText).replaceAll("\n", " ").trim()
|
||||||
kvs[key] = value
|
kvs[key] = value
|
||||||
if (!isNaN(value))
|
if (!isNaN(value))
|
||||||
kvs[key] = parseFloat(value)
|
kvs[key] = parseFloat(value)
|
||||||
}
|
}
|
||||||
http("put", "/api/transactions", () => {init()}, JSON.stringify(kvs))
|
http("put", "/api/transactions", () => {init()}, JSON.stringify(kvs))
|
||||||
}
|
}
|
||||||
function setRowKeyValue(row, wantkey, wantvalue) {
|
function getRowKeyValue(row, wantkey) {
|
||||||
|
const inputs = row.getElementsByTagName("td")
|
||||||
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
|
const key = inputs[i].getAttribute("key")
|
||||||
|
if (key == wantkey) {
|
||||||
|
return inputs[i].innerText + ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
function setRowKeyValues(row, wantKeysWantValues) {
|
||||||
const inputs = row.getElementsByTagName("td")
|
const inputs = row.getElementsByTagName("td")
|
||||||
var kvs = {}
|
var kvs = {}
|
||||||
for (var i = 0; i < inputs.length; i++) {
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
const key = inputs[i].getAttribute("key")
|
const key = inputs[i].getAttribute("key")
|
||||||
if (key == wantkey) {
|
if (key in wantKeysWantValues) {
|
||||||
inputs[i].innerHTML = wantvalue
|
inputs[i].innerHTML = wantKeysWantValues[key]
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
saveTransaction(row)
|
saveTransaction(row)
|
||||||
}
|
}
|
||||||
zachsPayment = (x) => setRowKeyValue(x, "Payer", "AssetAccount:Zach")
|
zachsPayment = (x) => {
|
||||||
belsPayment = (x) => setRowKeyValue(x, "Payer", "AssetAccount:Bel")
|
setRowKeyValues(x, {
|
||||||
zachsCharge = (x) => setRowKeyValue(x, "Payee", "AssetAccount:Zach")
|
"Payee": getRowKeyValue(x, "Payer"),
|
||||||
belsCharge = (x) => setRowKeyValue(x, "Payee", "AssetAccount:Bel")
|
"Payer": "AssetAccount:Zach",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
belsPayment = (x) => {
|
||||||
|
setRowKeyValues(x, {
|
||||||
|
"Payee": getRowKeyValue(x, "Payer"),
|
||||||
|
"Payer": "AssetAccount:Bel",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
zachsCharge = (x) => setRowKeyValues(x, {"Payee": "AssetAccount:Zach"})
|
||||||
|
belsCharge = (x) => setRowKeyValues(x, {"Payee": "AssetAccount:Bel"})
|
||||||
function http(method, remote, callback, body) {
|
function http(method, remote, callback, body) {
|
||||||
var xmlhttp = new XMLHttpRequest();
|
var xmlhttp = new XMLHttpRequest();
|
||||||
xmlhttp.onreadystatechange = function() {
|
xmlhttp.onreadystatechange = function() {
|
||||||
@@ -136,6 +193,7 @@
|
|||||||
<summary>Balances</summary>
|
<summary>Balances</summary>
|
||||||
<div id="balances"></div>
|
<div id="balances"></div>
|
||||||
</details>
|
</details>
|
||||||
|
<div class="right"><button onclick="newTransaction();">New Transaction</button></div>
|
||||||
<table id="transactions"></table>
|
<table id="transactions"></table>
|
||||||
</body>
|
</body>
|
||||||
<footer>
|
<footer>
|
||||||
|
|||||||
26
server.go
Normal file → Executable file
26
server.go
Normal file → Executable file
@@ -22,6 +22,8 @@ func (server Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
switch r.Method + r.URL.Path {
|
switch r.Method + r.URL.Path {
|
||||||
case "GET/api/transactions":
|
case "GET/api/transactions":
|
||||||
server.getTransactions(w, r)
|
server.getTransactions(w, r)
|
||||||
|
case "POST/api/transactions":
|
||||||
|
server.postTransactions(w, r)
|
||||||
case "PUT/api/transactions":
|
case "PUT/api/transactions":
|
||||||
server.putTransactions(w, r)
|
server.putTransactions(w, r)
|
||||||
case "GET/api/balances":
|
case "GET/api/balances":
|
||||||
@@ -50,6 +52,30 @@ func (server Server) getTransactions(w http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(w).Encode(map[string]interface{}{"Transactions": transactions})
|
json.NewEncoder(w).Encode(map[string]interface{}{"Transactions": transactions})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (server Server) postTransactions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var request struct {
|
||||||
|
Transaction
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||||
|
server.err(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := server.ledger.NewTransaction(); err != nil {
|
||||||
|
server.err(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
transactions, err := server.ledger.Transactions()
|
||||||
|
if err != nil {
|
||||||
|
server.err(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := server.ledger.SetTransaction(len(transactions)-1, request.Transaction); err != nil {
|
||||||
|
server.err(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{"ok": true})
|
||||||
|
}
|
||||||
|
|
||||||
func (server Server) putTransactions(w http.ResponseWriter, r *http.Request) {
|
func (server Server) putTransactions(w http.ResponseWriter, r *http.Request) {
|
||||||
var request struct {
|
var request struct {
|
||||||
IDX int `json:"idx"`
|
IDX int `json:"idx"`
|
||||||
|
|||||||
48
server_test.go
Normal file
48
server_test.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServerPostTransactions(t *testing.T) {
|
||||||
|
path := testLedgerFile(t)
|
||||||
|
ledger, err := NewLedger(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
server := Server{ledger: ledger}
|
||||||
|
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{
|
||||||
|
"Date": "2099-02-03",
|
||||||
|
"Description": "test",
|
||||||
|
"Payer": "payer",
|
||||||
|
"Payee": "payee",
|
||||||
|
"Amount": 1.02
|
||||||
|
}`))
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
server.postTransactions(w, r)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatal(w.Code)
|
||||||
|
}
|
||||||
|
if s := strings.TrimSpace(string(w.Body.Bytes())); s != `{"ok":true}` {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions, err := ledger.Transactions()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if transactions[len(transactions)-1] != (Transaction{
|
||||||
|
Date: "2099-02-03",
|
||||||
|
Description: "test",
|
||||||
|
Payer: "payer",
|
||||||
|
Payee: "payee",
|
||||||
|
Amount: 1.02,
|
||||||
|
}) {
|
||||||
|
t.Fatal(transactions[len(transactions)-1])
|
||||||
|
}
|
||||||
|
}
|
||||||
75
testdata/2018-.b.dat
vendored
Normal file
75
testdata/2018-.b.dat
vendored
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
2021-08-07 UTAH INFRASTRUCTURE
|
||||||
|
Withdrawal:UTAHINFRASTRUCTURE $30.00
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-07 HARMONS - OREM
|
||||||
|
Withdrawal:HARMONSOREM $8.73
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-07 MACEY'S 8TH NORTH
|
||||||
|
Withdrawal:MACEYS8THNORTH $18.05
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-07 BEEHIVE BROADBAND LL
|
||||||
|
Withdrawal:BEEHIVEBROADBANDLL $56.38
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-07 CULLIGAN WATER CONDI
|
||||||
|
Withdrawal:CULLIGANWATERCONDI $35.00
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-09 Payment
|
||||||
|
Withdrawal:Payment $121.38
|
||||||
|
AssetAccount:Bel
|
||||||
|
2021-08-12 HARMONS - OREM
|
||||||
|
Withdrawal:HARMONSOREM $12.82
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-12 COSTCO WHSE #0484
|
||||||
|
Withdrawal:COSTCOWHSE0484 $76.02
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-12 TRADER JOE'S #352
|
||||||
|
Withdrawal:TRADERJOES352 $64.57
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-12 TARGET T-1754
|
||||||
|
Withdrawal:TARGETT1754 $56.12
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-13 TARGET T-1754
|
||||||
|
Withdrawal:TARGETT1754 $44.66
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-15 TRADER JOE'S #352
|
||||||
|
Withdrawal:TRADERJOES352 $7.70
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-15 WALGREENS #11150
|
||||||
|
Withdrawal:WALGREENS11150 $16.96
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-15 HARMONS - OREM
|
||||||
|
Withdrawal:HARMONSOREM $35.30
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-16 Payment
|
||||||
|
Withdrawal:Payment $140.49
|
||||||
|
AssetAccount:Bel
|
||||||
|
2021-08-17 Payment
|
||||||
|
Withdrawal:Payment $20.63
|
||||||
|
AssetAccount:Bel
|
||||||
|
2021-08-19 REDMOND FARMS STORE
|
||||||
|
Withdrawal:REDMONDFARMSSTORE $42.07
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-19 TRADER JOE'S #352
|
||||||
|
Withdrawal:TRADERJOES352 $52.93
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-19 COSTCO WHSE #0484
|
||||||
|
Withdrawal:COSTCOWHSE0484 $140.93
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-08-23 Payment
|
||||||
|
Withdrawal:Payment $457.21
|
||||||
|
AssetAccount:Bel
|
||||||
|
2021-08-28 TARGET T-1754
|
||||||
|
Withdrawal:TARGETT1754 $95.34
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-09-01 MACEY'S 8TH NORTH
|
||||||
|
Withdrawal:MACEYS8THNORTH $16.41
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-09-01 COSTCO WHSE #0484
|
||||||
|
Withdrawal:COSTCOWHSE0484 $167.81
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-09-03 WALGREENS #12294
|
||||||
|
Withdrawal:WALGREENS12294 $16.60
|
||||||
|
AssetAccount:Chase:1049
|
||||||
|
2021-09-03 TST* COSTA VIDA - 01
|
||||||
|
Withdrawal:TSTCOSTAVIDA01 $20.78
|
||||||
|
AssetAccount:Chase:1049
|
||||||
2
testdata/ledger-groceries-custom.dat
vendored
Normal file → Executable file
2
testdata/ledger-groceries-custom.dat
vendored
Normal file → Executable file
@@ -1,5 +1,5 @@
|
|||||||
2021-01-01 groceries
|
2021-01-01 groceries
|
||||||
Withdrawal:Target $10
|
Withdrawal:Target $10.0000001
|
||||||
AssetAccount:Chase:8824
|
AssetAccount:Chase:8824
|
||||||
2021-01-05 bel vidya
|
2021-01-05 bel vidya
|
||||||
Withdrawal:Target $1
|
Withdrawal:Target $1
|
||||||
|
|||||||
0
testdata/ledger.dat
vendored
Normal file → Executable file
0
testdata/ledger.dat
vendored
Normal file → Executable file
0
testdata/ledger.json
vendored
Normal file → Executable file
0
testdata/ledger.json
vendored
Normal file → Executable file
15
transaction.go
Normal file → Executable file
15
transaction.go
Normal file → Executable file
@@ -8,6 +8,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Transaction struct {
|
type Transaction struct {
|
||||||
@@ -18,6 +19,20 @@ type Transaction struct {
|
|||||||
Amount float32
|
Amount float32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
UnknownAccount = "?"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newStubTransaction() Transaction {
|
||||||
|
return Transaction{
|
||||||
|
Date: time.Now().Format("2006-01-02"),
|
||||||
|
Description: UnknownAccount,
|
||||||
|
Payer: UnknownAccount,
|
||||||
|
Payee: UnknownAccount,
|
||||||
|
Amount: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func readTransaction(r io.Reader) (Transaction, error) {
|
func readTransaction(r io.Reader) (Transaction, error) {
|
||||||
lines := make([][]byte, 3)
|
lines := make([][]byte, 3)
|
||||||
for i := range lines {
|
for i := range lines {
|
||||||
|
|||||||
18
transaction_test.go
Normal file → Executable file
18
transaction_test.go
Normal file → Executable file
@@ -6,6 +6,24 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestNewStubTransaction(t *testing.T) {
|
||||||
|
want := Transaction{
|
||||||
|
Date: "1",
|
||||||
|
Amount: 0,
|
||||||
|
Payer: UnknownAccount,
|
||||||
|
Payee: UnknownAccount,
|
||||||
|
Description: UnknownAccount,
|
||||||
|
}
|
||||||
|
got := newStubTransaction()
|
||||||
|
if got.Date == "" {
|
||||||
|
t.Fatal(got.Date)
|
||||||
|
}
|
||||||
|
got.Date = want.Date
|
||||||
|
if want != got {
|
||||||
|
t.Fatal(want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWords(t *testing.T) {
|
func TestWords(t *testing.T) {
|
||||||
input := `
|
input := `
|
||||||
hello world
|
hello world
|
||||||
|
|||||||
Reference in New Issue
Block a user