summaryrefslogtreecommitdiff
path: root/ledger.h
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2003-09-29 07:06:02 +0000
committerJohn Wiegley <johnw@newartisans.com>2003-09-29 07:06:02 +0000
commite95ea133d0953953ba74f4e5c6163706194971cb (patch)
treeb134c7092820e96d4da40c0b56bc8493e0043085 /ledger.h
downloadfork-ledger-e95ea133d0953953ba74f4e5c6163706194971cb.tar.gz
fork-ledger-e95ea133d0953953ba74f4e5c6163706194971cb.tar.bz2
fork-ledger-e95ea133d0953953ba74f4e5c6163706194971cb.zip
Initial revision
Diffstat (limited to 'ledger.h')
-rw-r--r--ledger.h270
1 files changed, 270 insertions, 0 deletions
diff --git a/ledger.h b/ledger.h
new file mode 100644
index 00000000..0e261ceb
--- /dev/null
+++ b/ledger.h
@@ -0,0 +1,270 @@
+#ifndef _LEDGER_H
+#define _LEDGER_H "$Revision: 1.1 $"
+
+//////////////////////////////////////////////////////////////////////
+//
+// ledger: Double-entry ledger accounting
+//
+// by John Wiegley <johnw@newartisans.com>
+//
+// Copyright (c) 2003 New Artisans, Inc. All Rights Reserved.
+
+#include <iostream>
+#include <string>
+#include <list>
+#include <map>
+#include <ctime>
+
+namespace ledger {
+
+// Format of a ledger entry (GNUcash account files are also supported):
+//
+// DATE [CLEARED] (CODE) DESCRIPTION
+// ACCOUNT AMOUNT [; NOTE]
+// ACCOUNT AMOUNT [; NOTE]
+// ...
+//
+// The DATE can be YYYY.MM.DD or YYYY/MM/DD or MM/DD.
+// The CLEARED bit is a '*' if the account has been cleared.
+// The CODE can be anything, but must be enclosed in parenthesis.
+// The DESCRIPTION can be anything, up to a newline.
+//
+// The ACCOUNT is a colon-separated string naming the account.
+// The AMOUNT follows the form:
+// [COMM][WS]QUANTITY[WS][COMM][[WS]@[WS][COMM]PRICE[COMM]]
+// For example:
+// 200 AAPL @ $40.00
+// $50.00
+// DM 12.54
+// DM 12.54 @ $1.20
+// The NOTE can be anything.
+//
+// All entries must balance to 0.0, in every commodity. This means
+// that a transaction with mixed commodities must balance by
+// converting one of those commodities to the other. As a
+// convenience, this is done automatically for you in the case where
+// exactly two commodities are referred to, in which case the second
+// commodity is converted into the first by computing which the price
+// must have been in order to balance the transaction. Example:
+//
+// 2004.06.18 c (BUY) Apple Computer
+// Assets:Brokerage $-200.00
+// Assets:Brokerage 100 AAPL
+//
+// What this transaction says is that $200 was paid from the
+// brokerage account to buy 100 shares of Apple stock, and then place
+// those same shares back in the brokerage account. From this point
+// forward, the account "Assets:Brokerage" will have two balance
+// totals: The number of dollars in the account, and the number of
+// apple shares.
+// In terms of the transaction, however, it must balance to zero,
+// otherwise it would mean that something had been lost without
+// accouting for it. So in this case what ledger will do is divide
+// 100 by $200, to arrive at a per-share price of $2 for the APPL
+// stock, and it will read this transaction as if it had been
+// written:
+//
+// 2004.06.18 c (BUY) Apple Computer
+// Assets:Brokerage $-200
+// Assets:Brokerage 100 AAPL @ $2
+//
+// If you then wanted to give some of the shares to someone, in
+// exchange for services rendered, use the regular single-commodity
+// form of transaction:
+//
+// 2004.07.11 c A kick-back for the broker
+// Assets:Brokerage -10 AAPL
+// Expenses:Broker's Fees 10 AAPL
+//
+// This transaction does not need to know the price of AAPL on the
+// given day, because none of the shares are being converted to
+// another commodity. It simply directly affects the total number of
+// AAPL shares held in "Assets:Brokerage".
+
+struct commodity
+{
+ std::string name;
+ std::string symbol;
+
+ bool prefix;
+ bool separate;
+
+ int precision;
+
+ commodity() : prefix(false), separate(true) {}
+ commodity(const std::string& sym, bool pre, bool sep, int prec)
+ : symbol(sym), prefix(pre), separate(sep), precision(prec) {}
+};
+
+typedef std::map<const std::string, commodity *> commodities_t;
+typedef commodities_t::iterator commodities_iterator;
+typedef std::pair<const std::string, commodity *> commodities_entry;
+
+extern commodities_t commodities;
+extern commodity * commodity_usd;
+
+class amount
+{
+ public:
+ virtual ~amount() {}
+
+ virtual const std::string& comm_symbol() const = 0;
+ virtual amount * copy() const = 0;
+ virtual amount * value() const = 0;
+
+ // Test if non-zero
+
+ virtual operator bool() const = 0;
+
+ // Assignment
+
+ virtual void credit(const amount * other) = 0;
+ virtual void operator+=(const amount& other) = 0;
+
+ // String conversion routines
+
+ virtual void parse(const char * num) = 0;
+ virtual amount& operator=(const char * num) = 0;
+ virtual operator std::string() const = 0;
+};
+
+template<class Traits>
+std::basic_ostream<char, Traits> &
+operator<<(std::basic_ostream<char, Traits>& out, const amount& a) {
+ return (out << std::string(a));
+}
+
+extern amount * create_amount(const char * value, const amount * price = NULL);
+
+struct account;
+struct transaction
+{
+ account * acct;
+ amount * cost;
+
+ std::string note;
+
+ transaction() : acct(NULL), cost(NULL) {}
+
+ ~transaction() {
+ if (cost)
+ delete cost;
+ }
+};
+
+struct entry
+{
+ std::time_t date;
+ std::string code;
+ std::string desc;
+
+ bool cleared;
+
+ std::list<transaction *> xacts;
+
+ entry() : cleared(false) {}
+ ~entry() {
+ for (std::list<transaction *>::iterator i = xacts.begin();
+ i != xacts.end();
+ i++) {
+ delete *i;
+ }
+ }
+
+ void print(std::ostream& out) const;
+ bool validate() const;
+};
+
+struct cmp_entry_date {
+ bool operator()(const entry * left, const entry * right) {
+ return std::difftime(left->date, right->date) < 0;
+ }
+};
+
+class totals
+{
+ typedef std::map<const std::string, amount *> map_t;
+ typedef map_t::iterator iterator_t;
+ typedef map_t::const_iterator const_iterator_t;
+ typedef std::pair<const std::string, amount *> pair_t;
+
+ map_t amounts;
+
+ public:
+ void credit(const amount * val) {
+ std::pair<iterator_t, bool> result =
+ amounts.insert(pair_t(val->comm_symbol(), val->copy()));
+ if (! result.second)
+ amounts[val->comm_symbol()]->credit(val);
+ }
+ void credit(const totals& other);
+
+ operator bool() const;
+
+ void print(std::ostream& out) const;
+
+ // Returns an allocated entity
+ amount * value(const std::string& comm);
+ amount * sum(const std::string& comm) {
+ return amounts[comm];
+ }
+};
+
+struct account
+{
+ std::string name;
+ commodity * comm; // default commodity for this account
+
+ struct account * parent;
+
+ typedef std::map<const std::string, struct account *> map;
+ typedef map::iterator iterator;
+ typedef map::const_iterator const_iterator;
+ typedef std::pair<const std::string, struct account *> pair;
+
+ map children;
+
+ // Balance totals, by commodity
+ totals future;
+ totals current;
+ totals cleared;
+
+ account(const std::string& _name, struct account * _parent = NULL)
+ : name(_name), parent(_parent) {}
+
+ void credit(const entry * ent, const amount * amt) {
+ for (account * acct = this; acct; acct = acct->parent) {
+ acct->future.credit(amt);
+
+ if (difftime(ent->date, std::time(NULL)) < 0)
+ acct->current.credit(amt);
+
+ if (ent->cleared)
+ acct->cleared.credit(amt);
+ }
+ }
+
+ operator std::string() const {
+ if (! parent)
+ return name;
+ else
+ return std::string(*parent) + ":" + name;
+ }
+};
+
+template<class Traits>
+std::basic_ostream<char, Traits> &
+operator<<(std::basic_ostream<char, Traits>& out, const account& a) {
+ return (out << std::string(a));
+}
+
+
+typedef std::map<const std::string, account *> accounts_t;
+typedef accounts_t::iterator accounts_iterator;
+typedef std::pair<const std::string, account *> accounts_entry;
+
+extern accounts_t accounts;
+
+} // namespace ledger
+
+#endif // _LEDGER_H