1 | ((title . "Issue 20") |
---|
2 | (authors "Alaric Snell-Pym") |
---|
3 | (date . 1300960048)) |
---|
4 | |
---|
5 | == 0. Introduction |
---|
6 | |
---|
7 | Welcome to the 20th issue of the Chicken Gazette. While the gazette |
---|
8 | was on hiatus the chicken community was definitely not. We are |
---|
9 | expecting a new release and lots of bugs have been addressed in the |
---|
10 | meantime. |
---|
11 | |
---|
12 | Thanks for the recipe go to Alaric. Thanks to the Chicken Team for |
---|
13 | compiling this issue. |
---|
14 | |
---|
15 | Enjoy! |
---|
16 | |
---|
17 | == 1. Hatching Farm |
---|
18 | |
---|
19 | The following new extensions were added: |
---|
20 | |
---|
21 | * [[egg:canvas-draw|canvas-draw]]: Bindings to the CanvasDraw graphics library ([[user:thomas-chust|Thomas Chust]]) |
---|
22 | * [[egg:bindings|bindings]]: light-weight alternative to the matchable egg together with some enhancements ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
23 | * [[egg:tuples|tuples]]: a tuple datatype implementation ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
24 | * [[egg:svn-egg-author|svn-egg-author]]: makes it easier for people still using svn to tag/release eggs ([[user:peter-bex|Peter Bex]]) |
---|
25 | * [[egg:memcached|memcached]]: memcached client interface ([[user:ivan-raikov|Ivan Raikov]]) |
---|
26 | * [[egg:tree-rewrite|tree-rewrite]]: initial import of tree-rewrite, an adaptation of Oleg Kiselyov's term rewriting system PostL ([[user:ivan-raikov|Ivan Raikov]]) |
---|
27 | * [[egg:srfi-71|srfi-71]] '''1.0''': import some of srfi-71 (evhan) |
---|
28 | |
---|
29 | The following new versions have been released: |
---|
30 | |
---|
31 | * [[egg:hen|hen]] '''0.5''': Create tag 0.5 (joseph.gay) |
---|
32 | * [[egg:sequences|sequences]] '''0.3''': bugfixes; replicate ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
33 | * [[egg:fastcgi|fastcgi]] '''1.1.1''': pointer->c-pointer fix; output strings can contain NULs (thanks to Peter Danenberg) |
---|
34 | * [[egg:contracts|contracts]] '''0.4''': some enhancements ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
35 | * [[egg:sequences|sequences]] '''0.4''': performance tuning, some renaming ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
36 | * [[egg:message-digest|message-digest]] '''2.3.5''': reverted def alloc of ctx & no str use in u8vector restyp creation. ([[user:kon-lovett|Kon Lovett]]) |
---|
37 | * [[egg:md5|md5]] '''2.5.1''': Rel 2.5.1 w/ minor bug fix. ([[user:kon-lovett|Kon Lovett]]) |
---|
38 | * [[egg:sha1|sha1]] '''2.3.1''': Fix correct context field clear (was not walking over memory since sufficient padding in struct, still it was wrong.) Rmvd ctx clear since unneeded. ([[user:kon-lovett|Kon Lovett]]) |
---|
39 | * [[egg:spock|spock]] '''0.03''': renamed some API procedures; bugfix in driver (thanks to Supermario) ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
40 | * [[egg:amb|amb]] '''2.1.4''': Wrong dep verno for condition-utils & check-errors in .meta. Missing import for srfi-1 member use in amb-extras [reported by Markus Klotzbuecher]. ([[user:kon-lovett|Kon Lovett]]) |
---|
41 | * [[egg:mojo|mojo]] '''0.0.1''': first release ([[user:moritz-heidkamp|Moritz Heidkamp]]) |
---|
42 | * [[egg:sdl|sdl]] '''0.5.3''': new release ([[user:christian-kellermann|Christian Kellermann]]) |
---|
43 | * [[egg:tuples|tuples]] '''0.2''': tuples 0.2 ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
44 | * [[egg:progress-indicators|progress-indicators]] '''0.3''': added missing licensing information to meta file (thanks to mario) ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
45 | * [[egg:spiffy-request-vars|spiffy-request-vars]] '''0.13''': test fixes ([[user:mario-domenech-goulart|Mario Domenech Goulart]]) |
---|
46 | * [[egg:html-tags|html-tags]] '''0.10''': added generate-sxml? parameter support ([[user:mario-domenech-goulart|Mario Domenech Goulart]]) |
---|
47 | * [[egg:blas|blas]] '''3.0''': Chicken 4 cleanup ([[user:ivan-raikov|Ivan Raikov]]) |
---|
48 | * [[egg:atlas-lapack|atlas-lapack]] '''2.0''': Chicken 4 cleanup ([[user:ivan-raikov|Ivan Raikov]]) |
---|
49 | * [[egg:probdist|probdist]] '''1.7''': updated use of blas and atlas-lapack ([[user:ivan-raikov|Ivan Raikov]]) |
---|
50 | * [[egg:hyde|hyde]] '''0.15''': simpler atom feed generation ([[user:moritz-heidkamp|Moritz Heidkamp]]) |
---|
51 | * [[egg:signal-diagram|signal-diagram]] '''1.1''': added reduce combinator ([[user:ivan-raikov|Ivan Raikov]]) |
---|
52 | * [[egg:awful-sql-de-lite|awful-sql-de-lite]] '''0.4''': See http://wiki.call-cc.org/egg/awful-sql-de-lite#version-04 for the latest changes. ([[user:mario-domenech-goulart|Mario Domenech Goulart]]) |
---|
53 | * [[egg:awful-postgresql|awful-postgresql]] '''0.4''': See http://wiki.call-cc.org/egg/awful-postgresql#version-04 for the latest changes. ([[user:mario-domenech-goulart|Mario Domenech Goulart]]) |
---|
54 | * [[egg:awful-sqlite3|awful-sqlite3]] '''0.4''': See http://wiki.call-cc.org/egg/awful-sqlite3#version-04 for the latest changes. ([[user:mario-domenech-goulart|Mario Domenech Goulart]]) |
---|
55 | * [[egg:awful|awful]] '''0.31''': See http://wiki.call-cc.org/egg/awful#version-031 for the latest awful changes. ([[user:mario-domenech-goulart|Mario Domenech Goulart]]) |
---|
56 | * [[egg:spock|spock]] '''0.04''': removed explicit version number from setup script (reported to mario) |
---|
57 | * [[egg:spock|spock]] '''0.05''': compiled code runs somewhat on IE; slightly more verbose setup-script ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
58 | * [[egg:memcached|memcached]] '''1.0''': first release ([[user:ivan-raikov|Ivan Raikov]]) |
---|
59 | * [[egg:locale|locale]] '''0.6.11''': check-locale-component for 'name had bad value ('name itself rather than value variable). with-tzset was leaving TZ set when no original TZ value. Proc for envvar LANGUAGE (gnu) was using code that deref'ed nonexisting 'language locale component; part of code to substitute primary locale region for possible nonexisting 'country in LANGUAGE locale, which can be subset - just language. (This caused the Error: (string-parse-start+end) bad argument type - not a string: #f from string-upcase.) ([[user:kon-lovett|Kon Lovett]]) |
---|
60 | * [[egg:locale|locale]] '''0.6.12''': Use 'current locale category for primary locale, rather than 'messages. locale-timezone has proc for offset & name. ([[user:kon-lovett|Kon Lovett]]) |
---|
61 | * [[egg:matrix-utils|matrix-utils]] '''1.12''': updated use of blas ([[user:ivan-raikov|Ivan Raikov]]) |
---|
62 | * [[egg:iexpr|iexpr]] '''1.2''': fixed unit tests ([[user:ivan-raikov|Ivan Raikov]]) |
---|
63 | * [[egg:mpi|mpi]] '''1.8''': ported to Chicken 4 ([[user:ivan-raikov|Ivan Raikov]]) |
---|
64 | * [[egg:bloom-filter|bloom-filter]] '''1.1.4''': Use of round -> ceiling (can't lose a bit). Added different way to calc M & K. Added variant signature for make-bloom-filter to take N & P. ([[user:kon-lovett|Kon Lovett]]) |
---|
65 | * [[egg:ripemd|ripemd]] '''1.0.2''': Added ident to digest prim. ([[user:kon-lovett|Kon Lovett]]) |
---|
66 | * [[egg:setup-helper|setup-helper]] '''1.3.1''': Fix for trashing the .o when static+shared module setup. "static module" added. ([[user:kon-lovett|Kon Lovett]]) |
---|
67 | * [[egg:slime|slime]] '''1.1''': add swank:documentation-symbol (from Jean-Christophe Petkovich) and preliminary inspector implementation ([[user:christian-kellermann|Christian Kellermann]]) |
---|
68 | * [[egg:format|format]] '''3.1.5''': first release ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
69 | * [[egg:neuromorpho|neuromorpho]] '''1.12''': small fixes ([[user:ivan-raikov|Ivan Raikov]]) |
---|
70 | * [[egg:yelp|yelp]] '''1.0.2''': add note to tests about Yelp API key (ddp) |
---|
71 | * [[egg:format-compiler-base|format-compiler-base]] '''17017.1''': Replaces "use" of "extras" unit with "import", as the current implementation modified the imported binding to "format" and thus overwrote the toplevel binding. ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
72 | * [[egg:embedded-test|embedded-test]] '''17018.1''': fixed long open bug and tagged 17018.1 ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
73 | * [[egg:ssax|ssax]] '''5.0.4''': added export for ssax:warn and tagged 5.0.4 ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
74 | * [[egg:rpc|rpc]] '''1.1.2''': removed use of project procedure ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
75 | * [[egg:flsim|flsim]] '''1.4''': fixes related to Octave code generation ([[user:ivan-raikov|Ivan Raikov]]) |
---|
76 | * [[egg:opengl|opengl]] '''1.19''': opengl 1.19: properly link -lGLU (thanks to Moritz Heidkamp) ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
77 | * [[egg:iset|iset]] '''1.8''': fix typo in optimization option ([[user:felix-winkelmann|Felix Winkelmann]]) |
---|
78 | * [[egg:hen|hen]] '''0.6''': various fixes and additions (Joseph Gay) |
---|
79 | |
---|
80 | == 2. Chicken Talk |
---|
81 | |
---|
82 | Chicken 4.7 is about to be released. The development team is |
---|
83 | performing some final tests to check if some critical bug shows up |
---|
84 | before the release. If you want to help testing the code which is |
---|
85 | going to be the next Chicken version, get the |
---|
86 | [[http://code.call-cc.org/dev-snapshots/2011/04/08/chicken-4.6.7.tar.gz|latest development snapshot]] |
---|
87 | and give it a try. |
---|
88 | |
---|
89 | A Chicken-based commercial application has been added to the |
---|
90 | [[http://wiki.call-cc.org/Software|Software]] wiki page. According to |
---|
91 | the description, [[http://auscio.com/|AuScio]] ''is simple |
---|
92 | cross-platform metal price monitor combining the |
---|
93 | [[egg:qt-light|qt-light]], tcp and srfi-18 multi-threading eggs. The |
---|
94 | application is deployed as a native executable with Chicken's |
---|
95 | compiler''. |
---|
96 | |
---|
97 | On the hobbyist side |
---|
98 | [[https://github.com/jacktrades/Lispy/commit/0b7d6898afa81ccf05b8d2939b5350266a410c5c|Lispy]], |
---|
99 | a scheme interpreter written in Chicken by |
---|
100 | [[https://github.com/jacktrades|jacktrades]] has been found on the |
---|
101 | net. |
---|
102 | |
---|
103 | Evan Hanson reported a Chicken sighting in the |
---|
104 | [[http://www.mitpressjournals.org/cmj|Computer Music Journal]]'s DVD. |
---|
105 | |
---|
106 | Thanks go also to John Cowan who brought the |
---|
107 | [[http://supertunaman.com/cdl/cdl_v0-1.txt|Chicken Dance License]] as |
---|
108 | a possible new license to our attention :) |
---|
109 | |
---|
110 | Matt Welland announced a new version of chicken-iup |
---|
111 | [[http://lists.nongnu.org/archive/html/chicken-users/2011-03/msg00147.html|here]]. Also |
---|
112 | note that the download link has changed since the last Gazette. |
---|
113 | |
---|
114 | YC started a discussion about a |
---|
115 | [[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00000.html|Generic |
---|
116 | RDBMS Interface]], where lots of people envisioned an easy to use glue |
---|
117 | to access RDBMS. The participants argued over the use of URL encoded |
---|
118 | queries vs. other more lispy forms. Thomas Chust developed |
---|
119 | [[http://www.chust.org/fossils/dbi/index|a prototype]] and is looking |
---|
120 | for feedback. Thanks! |
---|
121 | |
---|
122 | John Magolske shows us a nice trick to |
---|
123 | [[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00015.html|use |
---|
124 | csi within Vim to evaluate visually selected text]]. He also ran into |
---|
125 | issues when installing chicken eggs in a nonstandard location w/o hard |
---|
126 | coding in these paths during compilation. Thanks to the helpful people |
---|
127 | on chicken-users |
---|
128 | [[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00035.html| |
---|
129 | he could solve his issue]]. |
---|
130 | |
---|
131 | Matt Welland asks for a way to produce a notification sound in a cross |
---|
132 | platform way |
---|
133 | [[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00029.html|in |
---|
134 | this mail]] but until now we don't have a good answer for this |
---|
135 | yet. Maybe you can help? Matt is also still waiting for an answer to a |
---|
136 | problem with |
---|
137 | [[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00031.html|srfi-19 |
---|
138 | under windows]]. |
---|
139 | |
---|
140 | Felix announced the release of the hopefully latest development |
---|
141 | snapshot before the next major release. Please test |
---|
142 | [[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00036.html|4.6.7]] |
---|
143 | as hard as you can. |
---|
144 | |
---|
145 | Chris Bolton asked about how to print colored messages to a terminal |
---|
146 | and learned about [[http://api.call-cc.org/doc/fmt|the fmt egg]] and |
---|
147 | [[http://api.call-cc.org/doc/ansi-escape-sequences|ansi escape |
---|
148 | sequences]]. |
---|
149 | |
---|
150 | John J Foerch asked for a spiffy feature: remote-address should also |
---|
151 | take the X-Forwarded-For header into account when generating an |
---|
152 | address. Peter Bex kindly implemented it. |
---|
153 | |
---|
154 | Alex Shinn announced the |
---|
155 | [[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00062.html|R7RS |
---|
156 | draft]] which we all waited for. Thanks to the scheme-report group! |
---|
157 | |
---|
158 | Steve Graham asked about the possibility of writing android apps in |
---|
159 | chicken. Moritz Heidkamp |
---|
160 | [[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00071.html|promised |
---|
161 | to write a gazette recipe]] about it "in the near future". |
---|
162 | |
---|
163 | William Xu asked about a way to upgrade all installed eggs. Felix |
---|
164 | added a '-reinstall' option to chicken-install which you are |
---|
165 | [[http://lists.nongnu.org/archive/html/chicken-users/2011-05/msg00022.html|invited |
---|
166 | to test]]. |
---|
167 | |
---|
168 | Thanks to Alan Post, Mario Goulart has been able to fix the links to |
---|
169 | the [[http://tests.call-cc.org/|tests.call-cc.org]] egg feeds. |
---|
170 | |
---|
171 | == 3. Omelette Recipes |
---|
172 | |
---|
173 | The dream of many Schemers is to write Scheme for a living. Due to the |
---|
174 | regrettable tendency of employers to frown upon good ideas, the best |
---|
175 | way to achieve this is to start your own company, so you can be your |
---|
176 | own boss, and frown upon the good ideas of others instead. |
---|
177 | |
---|
178 | However, running your own company comes at a price; and a big part of |
---|
179 | that price is the horror of "book keeping"; the requirement to track |
---|
180 | all flows of money and other, more abstract, forms of value in and out |
---|
181 | of your company. |
---|
182 | |
---|
183 | Some people will tell you book-keeping is simple. "Just keep all your |
---|
184 | receipts and bank statements and bills and invoices", they say. "Then |
---|
185 | send them to your accountant at the end of the year." |
---|
186 | |
---|
187 | "But what about value-added tax?", you ask (or "sales tax" in some |
---|
188 | countries). "And what about income tax paid at source for my employees |
---|
189 | (including myself)?". "And why do I need to pay an accountant so much |
---|
190 | to do a job that a computer can easily do in milliseconds?" And then |
---|
191 | the smug smile slowly drips from the face of the "Oh it's easy" crowd. |
---|
192 | |
---|
193 | Clearly, book-keeping is complicated. And yet also simple, in that it |
---|
194 | is determined by sets of rules. |
---|
195 | |
---|
196 | We know what to do, don't we? Let's get coding! |
---|
197 | |
---|
198 | The problem is that book-keeping involves several different kinds of |
---|
199 | inputs - bills (that people send you), invoices (that you send |
---|
200 | people), transfers of money (bills and invoices being paid), loans (to |
---|
201 | and from the company), employees being paid, interest payments from |
---|
202 | the bank, dividend payments, and so on; while it also involves several |
---|
203 | different outputs - tax reports for the various taxes involved (in the |
---|
204 | UK, I had to deal with VAT every three months, income tax and national |
---|
205 | insurance when paying myself and my wife as employees every month, an |
---|
206 | annual filing fee, and annual corporation tax and dividend payments), |
---|
207 | statutory filing of certain financial summaries (generally annually), |
---|
208 | and internal reporting: How much was I spending? How much did each |
---|
209 | client owe? How much should be in the bank by when? Plus, it's nice to |
---|
210 | be able to generate nice invoices to send to folks who owe you |
---|
211 | money. That's a form of specialised report, too, just reporting on a |
---|
212 | single invoice. |
---|
213 | |
---|
214 | Each of the output reports depend in complex ways on different |
---|
215 | information from the inputs. The VAT reports mainly have to add up how |
---|
216 | much VAT I've paid when being billed by others, and how much VAT I've |
---|
217 | charged when invoicing - meaning that VAT needs to be tracked on all |
---|
218 | bills and invoices so it can be extracted. They also want to know |
---|
219 | totals of actual money in and out of the company in the period (even |
---|
220 | stuff where VAT isn't an issue), presumably to check up on |
---|
221 | me. Meanwhile, end of year reports tend to need to know how much I've |
---|
222 | invoiced for various different kinds of work, and what I've spent on |
---|
223 | what kinds of things: buying equipment that will last me for several |
---|
224 | years is handled differently to expenses like travel, or buying stuff |
---|
225 | that I eventually resell to clients (so in our invoices, we need to |
---|
226 | keep track of money charged for services separately to money charged |
---|
227 | for things). |
---|
228 | |
---|
229 | Some reports care about virtual money moving hands. As soon as I |
---|
230 | invoice somebody, then the company now has a virtual asset - some |
---|
231 | money owed to it. That's worth as much as cash in the bank from some |
---|
232 | perspectives (generally, I have to pay tax on it as soon as it's |
---|
233 | invoiced, even if I've not been paid). And yet some care only about |
---|
234 | actual cash changing hands (working out my bank balance, for instance). |
---|
235 | |
---|
236 | Sometimes our clients invite us to incur expenses in doing work for |
---|
237 | them (such as extra travel) and then invoice them on for those |
---|
238 | expenses, so they pay us back - in which case, expenses need to be |
---|
239 | able to be tied to invoices, as well. Sometimes we decide to cancel an |
---|
240 | invoice, which can't be done by just pretending it never existed, for |
---|
241 | audit-trail reasons; we need to issue a "negative" invoice called a |
---|
242 | credit note. |
---|
243 | |
---|
244 | Just to complicate matters more, the actual movement of money isn't |
---|
245 | atomic. If I invoice somebody on date A, they might post me a cheque |
---|
246 | which arrives on date B, which I pay into the bank on date C, which |
---|
247 | actually clears into the account (and thereby appears on my bank |
---|
248 | statement, when I get it) on date D. So at date A the company now has |
---|
249 | a "we are owed" pretend-money asset, which goes through various stages |
---|
250 | until it finally turns into money in the bank on date D. |
---|
251 | |
---|
252 | I handled my book-keeping with some hacky scripts written in Chicken |
---|
253 | Scheme. What I'm going to document here is partly what I've done, and |
---|
254 | partly what I should have done - it was a very iterative process, |
---|
255 | refining the best way to handle stuff, and there's lots of |
---|
256 | improvements I've wanted to make but not had time to. So I'm going to |
---|
257 | describe the ideal case, not the hacky half-way house I actually have |
---|
258 | right now! |
---|
259 | |
---|
260 | The approach I took was to have a file called a "ledger" that I enter |
---|
261 | all my invoices and so on into. This is parsed to build up a bunch of |
---|
262 | data structures in memory, from which the various reports can easily |
---|
263 | be obtained. Firstly, for each kind of input object (invoices, bills, |
---|
264 | etc) there's a list of them, or more often a hashmap to make them easy |
---|
265 | to find by some identifier (I can give my invoices unique symbolic |
---|
266 | names, for instance). That contains the raw data as parsed from the |
---|
267 | ledger file. But then we also create summary structures, which are |
---|
268 | used by the more general reports to generate their output without |
---|
269 | having to special-case each and every different input object type, and |
---|
270 | to enable sharing of common functionality between reports. |
---|
271 | |
---|
272 | The main summary structure is the double-entry transaction list, which |
---|
273 | models the entire financial activity of the company as transfers |
---|
274 | between accounts. |
---|
275 | |
---|
276 | Imagine I invoice Widget Corp for setting up and installing a router: |
---|
277 | |
---|
278 | INVOICE INV005: Issued 2011-04-25 |
---|
279 | Router setup and installation: GBP 800 |
---|
280 | 1 router from my stock: GBP 350 |
---|
281 | 1 train ticket for me to go to their site: GBP 35 (no VAT due) |
---|
282 | Subtotal: GBP 1,185 |
---|
283 | VAT on the above: GBP 230 |
---|
284 | Total due: GBP 1,415 |
---|
285 | |
---|
286 | As part of the work, I lose a router (worth GBP 350) from my stock, |
---|
287 | and have to spend GBP 35 on a train fare. |
---|
288 | |
---|
289 | This might expand into the following transactions: |
---|
290 | |
---|
291 | * 2011-03-02: "Expense for Widget Corp (INV005)" |
---|
292 | * expenses.travel +35 "Travel to site" |
---|
293 | * cash -35 |
---|
294 | |
---|
295 | * 2011-04-25 "Invoice Widget Corp (INV005)" |
---|
296 | * income.work -800 (Router set up and installation) |
---|
297 | * stock.balances -350 (1 of LX300 router, serial number 0343248) |
---|
298 | * clients.widgetcorp.expenses -35 (Travel 2011-03-02) |
---|
299 | * taxes.vat -230 |
---|
300 | * clients.widgetcorp.balance +1415 |
---|
301 | |
---|
302 | And, eventually, they might pay me, which hits my bank account some |
---|
303 | time later: |
---|
304 | |
---|
305 | * 2011-05-07 "Payment from Widget Corp (INV005)" |
---|
306 | * clients.widgetcorp.balance -1415 |
---|
307 | * bank.balance +1415 |
---|
308 | |
---|
309 | And then one day I'll pay my VAT bill, which will look something like: |
---|
310 | |
---|
311 | * 2011-06-01 "VAT payment for period from 2011-03-01 to 2011-06-01" |
---|
312 | * taxes.vat 230 |
---|
313 | * bank.balance -230 |
---|
314 | |
---|
315 | Note a few tricky things. Each transactions "splits", as the lines |
---|
316 | within them are known, have to sum to zero for everything to balance |
---|
317 | correctly, which tells us that nothing has gone missing. So when we |
---|
318 | start being owed GBP 1,415 by Widget Corp, we need to account for |
---|
319 | where that asset has come from. Special accounts with names such as |
---|
320 | "income.work" (for value generated by me working) and |
---|
321 | "clients.widgetcorp.expenses" (for previously-paid expenses that, as |
---|
322 | of this invoice, I can charge the client for) pop into |
---|
323 | existence. "taxes.vat" looks as if VAT is a form of income for me, as |
---|
324 | money comes "from" it in the transaction - which is sort of true; I'm |
---|
325 | charging Widget Corp for some VAT alongside for the actual work |
---|
326 | done. Figuring out what signs to put on all the items in the invoice |
---|
327 | is mind-bending and painful, but if you just concentrate on making it |
---|
328 | all add up to zero in the end and starting from things that are |
---|
329 | obvious (is money going into or out of the bank account, or the "owed |
---|
330 | to me by this customer" account?), you can figure it out. |
---|
331 | |
---|
332 | From the above, we can start to flesh out some data structures: |
---|
333 | |
---|
334 | <enscript> |
---|
335 | (define-record txn |
---|
336 | date customer code description splits) |
---|
337 | |
---|
338 | (define-record txn-split |
---|
339 | account amount notes) |
---|
340 | |
---|
341 | |
---|
342 | (define *txns* (make-hash-table)) |
---|
343 | (define (register-txn! txn) |
---|
344 | (if (hash-table-exists? *txns* (txn-date txn)) |
---|
345 | (begin |
---|
346 | (set! (hash-table-ref *txns* (txn-date txn)) |
---|
347 | (cons txn |
---|
348 | (hash-table-ref *txns* (txn-date txn))))) |
---|
349 | (begin |
---|
350 | (set! (hash-table-ref *txns* (txn-date txn)) |
---|
351 | (list txn))))) |
---|
352 | </enscript> |
---|
353 | |
---|
354 | What is an "account"? There's a few kinds, and what kind of account it |
---|
355 | is matters in reporting. Accounts might be assets within the company - |
---|
356 | such as "clients.widgetcorp.balance" or "bank.balance" or |
---|
357 | "stock.balance". Or they may be places where money (be it real or |
---|
358 | virtual) is created from or destroyed by (from the perspective of the |
---|
359 | company), such as "income.work" and "expenses.travel". The important |
---|
360 | difference is that balance-type accounts have a balance that is |
---|
361 | increased when money is sent to them and decreased when it's taken |
---|
362 | out, and that balance is part of the value of the company, while the |
---|
363 | income/expense type accounts don't. In my terminology, these are |
---|
364 | "balance" accounts and "delta" accounts. Each account also begins to a |
---|
365 | group, used to aggregate them in reports: there's income accounts, |
---|
366 | bank accounts, client accounts, and so on. And accounts may be tied to |
---|
367 | a third party - I've given an example of a client above, but also, the |
---|
368 | organisations that send me bills have balances (the money I owe |
---|
369 | them). In general, every third party (be they ones that bill me, or |
---|
370 | ones that I invoice, or both - I've interacted with other freelancers, |
---|
371 | sometimes working for them, sometimes vice versa) has a set of |
---|
372 | accounts attached to them for their balance, expenses I can claim from |
---|
373 | them, and so on. That implies another set of record types: |
---|
374 | |
---|
375 | <enscript> |
---|
376 | (define-record third-party |
---|
377 | name address balance-account expenses-account) |
---|
378 | |
---|
379 | (define-record account |
---|
380 | name type group third-party) |
---|
381 | |
---|
382 | |
---|
383 | (define *third-parties* (make-hash-table)) |
---|
384 | (define *accounts* (make-hash-table)) |
---|
385 | |
---|
386 | (define (find-account acct-name) |
---|
387 | (hash-table-ref *accounts* acct-name)) |
---|
388 | |
---|
389 | (define (register-account! acct) |
---|
390 | (set! (hash-table-ref *accounts* (account-name acct)) acct)) |
---|
391 | </enscript> |
---|
392 | |
---|
393 | An account's {{third-party}} slot may be {{#f}} if it's not part of a |
---|
394 | third party. |
---|
395 | |
---|
396 | Now, to make things easy, I parse the ledger by just defining a heap |
---|
397 | of procedures and macros, and then diving into the ledger with |
---|
398 | eval. It's just Scheme code that, as it is executed, builds up the |
---|
399 | data structures. We'll need some helpers to set up third parties |
---|
400 | properly: |
---|
401 | |
---|
402 | <enscript> |
---|
403 | (define (define-third-party name address group) |
---|
404 | (let* ((balance-account |
---|
405 | (make-account |
---|
406 | (string-append name ".balance") |
---|
407 | 'balance |
---|
408 | group |
---|
409 | #f)) |
---|
410 | (expenses-account |
---|
411 | (make-account |
---|
412 | (string-append name ".expenses") |
---|
413 | 'delta |
---|
414 | 'expenses-reclaimed |
---|
415 | #f)) |
---|
416 | (third-party |
---|
417 | (make-third-party |
---|
418 | name address balance-account expenses-account))) |
---|
419 | (account-third-party-set! balance-account third-party) |
---|
420 | (account-third-party-set! expenses-account third-party) |
---|
421 | (register-account! balance-account) |
---|
422 | (register-account! expenses-account) |
---|
423 | (set! (hash-table-ref *third-parties* name) third-party))) |
---|
424 | </enscript> |
---|
425 | |
---|
426 | Now we can work on a way of representing bills and invoices. A nice |
---|
427 | input syntax would be: |
---|
428 | |
---|
429 | <enscript> |
---|
430 | (define-third-party "clients.widgetcorp" "123 Any Street" 'clients) |
---|
431 | (register-account! (make-account "income.work" 'delta 'income #f)) |
---|
432 | (register-account! (make-account "expenses.travel" 'delta 'travel #f)) |
---|
433 | (register-account! (make-account "stock.balance" 'balance 'stock #f)) |
---|
434 | (register-account! (make-account "cash" 'balance 'cash #f)) |
---|
435 | (register-account! (make-account "taxes.vat" 'balance 'vat #f)) |
---|
436 | |
---|
437 | (invoice "INV005" "clients.widgetcorp" (ymd 2011 04 25) |
---|
438 | (service "income.work" 800 (vat20) "Router setup and installation") |
---|
439 | (sale "stock.balance" 350 (vat20) "1 of LX300 router, serial number 0343248") |
---|
440 | (expense (ymd 2011 03 02) "expenses.travel" "cash" 35 () "Travel to site")) |
---|
441 | </enscript> |
---|
442 | |
---|
443 | The idea is that the sales taxes incurred on a line are specified as a |
---|
444 | list after the amount. If there's no taxes due, then we use an empty |
---|
445 | list. Otherwise we have a list of taxes, which are either plain tax |
---|
446 | names (to have the system compute the tax due itself) or two-element |
---|
447 | lists joining a tax name to a precomputed amount (often, when we pass |
---|
448 | on an expense, we know the tax we paid as it's on the receipt, so we |
---|
449 | should use that (even if they made a mistake working it out) rather |
---|
450 | than calculating our own). |
---|
451 | |
---|
452 | A nice way to implement that might be to make "invoice" a macro that |
---|
453 | absorbs its first three arguments as an invoice code, the name of the |
---|
454 | third party to invoice, and the date; then treats the rest as a body |
---|
455 | to be wrapped in a dynamic environment in which a parameter allows |
---|
456 | {{sale}}, {{expense}}, and {{service}} to add lines to the |
---|
457 | invoice. This is easily arranged: |
---|
458 | |
---|
459 | <enscript> |
---|
460 | (define-record date year month day) |
---|
461 | |
---|
462 | (define-record invoice |
---|
463 | name date third-party lines) |
---|
464 | |
---|
465 | (define (register-line! invoice line) |
---|
466 | (invoice-lines-set! invoice |
---|
467 | (cons line (invoice-lines invoice)))) |
---|
468 | |
---|
469 | ;; Compute sales taxes |
---|
470 | (define (compute-tax tax amount) |
---|
471 | (case tax |
---|
472 | ((vat20) (* 0.20 amount)) ;; Current UK rate |
---|
473 | ((vat15) (* 0.15 amount)) ;; Previous UK rate |
---|
474 | ((vat175) (* 0.175 amount)))) ;; Previous UK rate |
---|
475 | |
---|
476 | ;; Expand a list of taxes, some of which might be bare symbols |
---|
477 | ;; naming taxes to work out, or (<tax> <amount>) lists for |
---|
478 | ;; ready-computed taxes, into an alist of tax names to tax amounts |
---|
479 | (define (resolve-taxes amount taxes) |
---|
480 | (map (lambda (tax-desc) |
---|
481 | (if (list? tax-desc) |
---|
482 | (cons (car tax-desc) (cadr tax-desc)) |
---|
483 | (cons tax-desc (compute-tax tax-desc amount)))) |
---|
484 | taxes)) |
---|
485 | |
---|
486 | (define-record invoice-service-line |
---|
487 | income-account amount taxes description) |
---|
488 | |
---|
489 | (define-syntax service |
---|
490 | (syntax-rules () |
---|
491 | ((service income-account amount taxes description) |
---|
492 | (register-service! (*current-invoice*) income-account amount 'taxes description)))) |
---|
493 | |
---|
494 | (define (register-service! invoice income-account amount taxes description) |
---|
495 | (let ((service |
---|
496 | (make-invoice-service-line |
---|
497 | (find-account income-account) |
---|
498 | amount |
---|
499 | (resolve-taxes amount taxes) |
---|
500 | description))) |
---|
501 | (register-line! invoice service))) |
---|
502 | |
---|
503 | (define-record invoice-sale-line |
---|
504 | stock-account amount taxes description) |
---|
505 | |
---|
506 | (define (register-sale! invoice stock-account amount taxes description) |
---|
507 | (let ((sale |
---|
508 | (make-invoice-sale-line |
---|
509 | (find-account stock-account) |
---|
510 | amount |
---|
511 | (resolve-taxes amount taxes) |
---|
512 | description))) |
---|
513 | (register-line! invoice sale))) |
---|
514 | |
---|
515 | (define-syntax sale |
---|
516 | (syntax-rules () |
---|
517 | ((sale stock-account amount taxes description) |
---|
518 | (register-sale! (*current-invoice*) stock-account amount 'taxes description)))) |
---|
519 | |
---|
520 | (define-record invoice-expense-line |
---|
521 | expense-account payment-account amount taxes description) |
---|
522 | |
---|
523 | (define (register-expense! invoice date expense-account payment-account amount taxes description) |
---|
524 | (let ((expense |
---|
525 | (make-invoice-expense-line |
---|
526 | (find-account expense-account) |
---|
527 | (find-account payment-account) |
---|
528 | amount |
---|
529 | (resolve-taxes amount taxes) |
---|
530 | description))) |
---|
531 | (register-line! invoice expense))) |
---|
532 | |
---|
533 | (define-syntax expense |
---|
534 | (syntax-rules () |
---|
535 | ((expense (ymd year month day) expense-account payment-account amount taxes description) |
---|
536 | (register-expense! |
---|
537 | (*current-invoice*) |
---|
538 | (make-date year month day) |
---|
539 | expense-account |
---|
540 | payment-account |
---|
541 | amount |
---|
542 | 'taxes |
---|
543 | description)))) |
---|
544 | |
---|
545 | (define *current-invoice* (make-parameter #f)) |
---|
546 | (define *invoices* (make-hash-table)) |
---|
547 | |
---|
548 | (define-syntax invoice |
---|
549 | (syntax-rules (service sale expense) |
---|
550 | ((invoice name third-party (ymd year month day) body ...) |
---|
551 | (let ((inv |
---|
552 | (make-invoice name |
---|
553 | (make-date year month day) |
---|
554 | (hash-table-ref *third-parties* third-party) |
---|
555 | '()))) |
---|
556 | (parameterize |
---|
557 | ((*current-invoice* inv)) |
---|
558 | (begin body ...)) |
---|
559 | (set! (hash-table-ref *invoices* name) inv) |
---|
560 | (generate-invoice-transactions! inv))))) |
---|
561 | </enscript> |
---|
562 | |
---|
563 | We end the expansion of the {{invoice}} macro with a call to |
---|
564 | {{generate-invoice-transactions!}}, which will do the task of creating |
---|
565 | the double-entry transactions for the invoice. Other types of summary |
---|
566 | structure can be added by calling additional generator procedures at |
---|
567 | this point. This is largely a matter of going through the invoice |
---|
568 | lines, handling them on a case-by-case basis to generate lists of |
---|
569 | transaction splits that we can append together to generate the invoice |
---|
570 | transaction. The case of expense lines is interesting, in that an |
---|
571 | extra transaction has to be generated for each expense, to record its |
---|
572 | initial spending, as well as a split to record the expense being |
---|
573 | claimed in the invoice. |
---|
574 | |
---|
575 | For now, let's just handle one case: |
---|
576 | |
---|
577 | <enscript> |
---|
578 | (define (generate-invoice-transactions! inv) |
---|
579 | (register-txn! (make-txn |
---|
580 | (invoice-date inv) |
---|
581 | (string-append "Invoice " (invoice-name inv) " for " |
---|
582 | (third-party-full-name (invoice-third-party inv))) |
---|
583 | (let ((txn-balance-account |
---|
584 | (third-party-balance-account |
---|
585 | (invoice-third-party inv)))) |
---|
586 | (flatten |
---|
587 | (map |
---|
588 | (lambda (line) |
---|
589 | (cond |
---|
590 | ((invoice-expense-line? line) |
---|
591 | (list)) ;; FIXME: Not implemented |
---|
592 | ((invoice-sale-line? line) |
---|
593 | (list)) ;; FIXME: Not implemented |
---|
594 | ((invoice-service-line? line) |
---|
595 | (list |
---|
596 | (make-txn-split |
---|
597 | (invoice-service-line-income-account line) |
---|
598 | (- (invoice-service-line-amount line)) |
---|
599 | (invoice-service-line-description line)) |
---|
600 | (make-tax-splits |
---|
601 | (invoice-service-line-taxes line) |
---|
602 | txn-balance-account) |
---|
603 | (make-txn-split |
---|
604 | txn-balance-account |
---|
605 | (invoice-service-line-amount line) |
---|
606 | #f))))) |
---|
607 | (invoice-lines inv))))))) |
---|
608 | |
---|
609 | (define (make-tax-splits taxes txn-balance-account) |
---|
610 | (map (lambda (tax) |
---|
611 | (let ((tax-type (car tax)) |
---|
612 | (tax-amount (cdr tax))) |
---|
613 | (case tax-type |
---|
614 | ((vat20 vat15 vat175) |
---|
615 | (list |
---|
616 | (make-txn-split |
---|
617 | (find-account "taxes.vat") |
---|
618 | (- tax-amount) |
---|
619 | #f) |
---|
620 | (make-txn-split |
---|
621 | txn-balance-account |
---|
622 | tax-amount |
---|
623 | #f)))))) |
---|
624 | taxes)) |
---|
625 | </enscript> |
---|
626 | |
---|
627 | Feeding in the above example invoice, then checking out the resulting |
---|
628 | double-entry transaction list, shows that it worked: |
---|
629 | |
---|
630 | <enscript> |
---|
631 | (hash-table-for-each *txns* |
---|
632 | (lambda (date txns) |
---|
633 | (for-each |
---|
634 | (lambda (txn) |
---|
635 | (printf "Date: ~A Desc: ~A\n" |
---|
636 | (txn-date txn) |
---|
637 | (txn-description txn)) |
---|
638 | (for-each (lambda (split) |
---|
639 | (printf "Acct: ~A Delta: ~A Notes: ~A\n" |
---|
640 | (account-name (txn-split-account split)) |
---|
641 | (txn-split-amount split) |
---|
642 | (txn-split-notes split))) (txn-splits txn))) |
---|
643 | txns))) |
---|
644 | </enscript> |
---|
645 | |
---|
646 | date: #<date> Desc: Invoice INV005 for Widget Corp |
---|
647 | Acct: income.work Delta: -800 Notes: Router setup and installation |
---|
648 | Acct: taxes.vat Delta: -160.0 Notes: #f |
---|
649 | Acct: clients.widgetcorp.balance Delta: 160.0 Notes: #f |
---|
650 | Acct: clients.widgetcorp.balance Delta: 800 Notes: #f |
---|
651 | |
---|
652 | We've ended up with multiple splits for the same account, as we record |
---|
653 | that both VAT and the money due for the service are to come from the |
---|
654 | client's balance account - and other splits will add plenty more. To |
---|
655 | fix this, we need to write a procedure that canonicalises a list of |
---|
656 | splits, and call that on the splits before calling |
---|
657 | {{make-txn}}. Canonicalisation consists of finding all the splits that |
---|
658 | refer to the same account and have the same notes (be it {{#f}} or a |
---|
659 | string) and merging them into one with the total of the amounts. But |
---|
660 | I'll leave that (along with implementing bills, payments, and some |
---|
661 | actual reports) as an exercise to the reader... It's easy to imagine |
---|
662 | how to generate a VAT report from the list of transactions, by |
---|
663 | filtering them for membership of the required date range and looking |
---|
664 | for splits involving "taxes.vat", or to generate a nicely formatted |
---|
665 | invoice by extracting a single invoice record, or to work out the |
---|
666 | balance of an account at any point in time by adding up all the |
---|
667 | transaction splits that involve it up to that point in time. Also, the |
---|
668 | core engine needs to be wrapped up in a module that only exposes the |
---|
669 | required bindings, and hides internals. |
---|
670 | |
---|
671 | Having automated one's book-keeping and financial reporting, many |
---|
672 | operations (such as the VAT returns) can be done without involving an |
---|
673 | accountant; in my case, the accountant is only needed to help with the |
---|
674 | annual corporation tax computation and filing of official accounts, |
---|
675 | which requires deep understanding of the UK tax system to do |
---|
676 | everything properly. Having said that, if I studied the system |
---|
677 | properly (and tracked the changes each year), I'm sure I could |
---|
678 | automate that, too... |
---|
679 | |
---|
680 | == 4. About the Chicken Gazette |
---|
681 | |
---|
682 | The Gazette is produced occasionally by a volunteer from the Chicken |
---|
683 | community. The latest issue can be found at |
---|
684 | [[http://gazette.call-cc.org]] or you can follow it in your feed |
---|
685 | reader at [[http://gazette.call-cc.org/feed.atom]]. If you'd like to |
---|
686 | write an issue, [[http://wiki.call-cc.org/gazette|consult the wiki]] |
---|
687 | for the schedule and instructions! |
---|