diff options
author | John Wiegley <johnw@newartisans.com> | 2003-09-29 07:06:02 +0000 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2003-09-29 07:06:02 +0000 |
commit | e95ea133d0953953ba74f4e5c6163706194971cb (patch) | |
tree | b134c7092820e96d4da40c0b56bc8493e0043085 /ledger.h | |
download | fork-ledger-e95ea133d0953953ba74f4e5c6163706194971cb.tar.gz fork-ledger-e95ea133d0953953ba74f4e5c6163706194971cb.tar.bz2 fork-ledger-e95ea133d0953953ba74f4e5c6163706194971cb.zip |
Initial revision
Diffstat (limited to 'ledger.h')
-rw-r--r-- | ledger.h | 270 |
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 |