diff --git a/src/ledger/file.go b/src/ledger/file.go index f62fd8c..287f7c1 100644 --- a/src/ledger/file.go +++ b/src/ledger/file.go @@ -26,7 +26,53 @@ func NewFiles(p string, q ...string) (Files, error) { } func (files Files) Amend(old, now Delta) error { - return io.EOF + xactions, err := files.transactions() + if err != nil { + return err + } + var transaction transaction + for _, xaction := range xactions { + if xaction.name != old.transaction { + continue + } + transaction = xaction + break + } + + inverted := transaction.deltas() + for i := range inverted { + inverted[i].Value *= -1 + } + + if now.isSet { + return fmt.Errorf("cannot ammend: immutable isSet is set") + } + transaction.date = now.Date + transaction.description = now.Description + if transaction.payee == old.Name { + if len(transaction.recipients) != 1 { + return fmt.Errorf("cannot amend: modifying original payee, but many recipients cant share new value") + } + old.Value *= -1 + old.Name = transaction.recipients[0].name + } + idx := -1 + for i, recipient := range transaction.recipients { + if recipient.name == old.Name && recipient.value == old.Value { + idx = i + } + } + if idx == -1 { + return fmt.Errorf("cannot amend: no recipient with name %s found to set new value", old.Name) + } + transaction.recipients[idx].name = now.Name + transaction.recipients[idx].value = now.Value + transaction.recipients[idx].currency = string(now.Currency) + amendedDeltas := transaction.deltas().Like(func(d Delta) bool { + return d.Name != transaction.payee + }) + + return files.Add(transaction.payee, append(inverted, amendedDeltas...)...) } func (files Files) TempGetLastNLines(n int) ([]string, error) { @@ -134,7 +180,16 @@ func (files Files) paths() []string { return result } -func (files Files) Add(payee string, delta Delta) error { +func (files Files) Add(payee string, deltas ...Delta) error { + for _, delta := range deltas { + if err := files.add(payee, delta); err != nil { + return err + } + } + return nil +} + +func (files Files) add(payee string, delta Delta) error { currencyValue := fmt.Sprintf("%s%.2f", delta.Currency, delta.Value) if delta.Currency != USD { currencyValue = fmt.Sprintf("%.2f %s", delta.Value, delta.Currency) @@ -214,39 +269,7 @@ func (files Files) Deltas(like ...Like) (Deltas, error) { result := make(Deltas, 0, len(transactions)*2) for _, transaction := range transactions { - sums := map[string]float64{} - for _, recipient := range transaction.recipients { - sums[recipient.currency] += recipient.value - delta := newDelta( - transaction.name, - transaction.date, - transaction.description, - recipient.name, - recipient.value, - recipient.currency, - recipient.isSet, - ) - result = append(result, delta) - } - for currency, value := range sums { - if value == 0 { - continue - } - if transaction.payee == "" { - //return nil, fmt.Errorf("didnt find net zero and no dumping ground payee set: %+v", transaction) - } else { - delta := newDelta( - transaction.name, - transaction.date, - transaction.description, - transaction.payee, - -1.0*value, - currency, - false, - ) - result = append(result, delta) - } - } + result = append(result, transaction.deltas()...) } balances := make(Balances) diff --git a/src/ledger/file_test.go b/src/ledger/file_test.go index 0ec6d3c..511ad19 100644 --- a/src/ledger/file_test.go +++ b/src/ledger/file_test.go @@ -11,12 +11,61 @@ import ( ) func TestFileAmend(t *testing.T) { - // given real xaction - // find it - // invert it - // modify original delta to requested - // modify counterpart - // inbox.txt has xaction inverted and xaction' + cases := map[string]struct { + from string + old Delta + now Delta + want string + }{ + //"was set payee": { + //"payee": { + //"recipient": { + //"multi recipient": { + "was set": { + from: ` + 2006-01-02 description + recipient $3.45 + payee + `, + old: Delta{ + Date: "2006-01-02", + Name: "recipient", + Value: 3.45, + Currency: "$", + Description: "description", + }, + now: Delta{ + Date: "2106-11-12", + Name: "recipient", + Value: 3.45, + Currency: "$", + Description: "description", + }, + }, + } + + for name, d := range cases { + c := d + t.Run(name, func(t *testing.T) { + p := path.Join(t.TempDir(), "dat") + if err := os.WriteFile(p, bytes.TrimSpace([]byte(c.from)), os.ModePerm); err != nil { + t.Fatal(err) + } + + files, err := NewFiles(p) + if err != nil { + t.Fatal(err) + } + + if err := files.Amend(c.old, c.now); err != nil { + t.Fatal(err) + } else if b, err := os.ReadFile(path.Join(path.Dir(p), "inbox.dat")); err != nil { + t.Fatal(err) + } else if b := bytes.TrimSpace(b); string(b) != c.want { + t.Fatalf("expected \n\t%s\nbut got\n\t%s", c.want, b) + } + }) + } } func TestFileAdd(t *testing.T) { diff --git a/src/ledger/transaction.go b/src/ledger/transaction.go index ddfbf26..e1a9f2c 100644 --- a/src/ledger/transaction.go +++ b/src/ledger/transaction.go @@ -30,6 +30,42 @@ type transactionRecipient struct { isSet bool } +func (t transaction) deltas() Deltas { + result := []Delta{} + sums := map[string]float64{} + for _, recipient := range t.recipients { + sums[recipient.currency] += recipient.value + result = append(result, newDelta( + t.name, + t.date, + t.description, + recipient.name, + recipient.value, + recipient.currency, + recipient.isSet, + )) + } + for currency, value := range sums { + if value == 0 { + continue + } + if t.payee == "" { + //return nil, fmt.Errorf("didnt find net zero and no dumping ground payee set: %+v", transaction) + } else { + result = append(result, newDelta( + t.name, + t.date, + t.description, + t.payee, + -1.0*value, + currency, + false, + )) + } + } + return result +} + func (t transactionRecipient) empty() bool { return t == (transactionRecipient{}) }