source: project/gazette/src/issues/20.wiki @ 23724

Last change on this file since 23724 was 23724, checked in by Christian Kellermann, 10 years ago

gazette 20: Small spelling fixes

File size: 32.4 KB
Line 
1((title . "Issue 20")
2 (authors "Alaric Snell-Pym")
3 (date . 1300960048))
4
5== 0. Introduction
6
7Welcome to the 20th issue of the Chicken Gazette. While the gazette
8was on hiatus the chicken community was definitely not. We are
9expecting a new release and lots of bugs have been addressed in the
10meantime.
11
12Thanks for the recipe go to Alaric. Thanks to the Chicken Team for
13compiling this issue.
14
15Enjoy!
16
17== 1. Hatching Farm
18
19The 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
29The 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
82Chicken 4.7 is about to be released.  The development team is
83performing some final tests to check if some critical bug shows up
84before the release. If you want to help testing the code which is
85going 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]]
87and give it a try.
88
89A Chicken-based commercial application has been added to the
90[[http://wiki.call-cc.org/Software|Software]] wiki page.  According to
91the description, [[http://auscio.com/|AuScio]] ''is simple
92cross-platform metal price monitor combining the
93[[egg:qt-light|qt-light]], tcp and srfi-18 multi-threading eggs. The
94application is deployed as a native executable with Chicken's
95compiler''.
96
97On the hobbyist side
98[[https://github.com/jacktrades/Lispy/commit/0b7d6898afa81ccf05b8d2939b5350266a410c5c|Lispy]],
99a scheme interpreter written in Chicken by
100[[https://github.com/jacktrades|jacktrades]] has been found on the
101net.
102
103Evan Hanson reported a Chicken sighting in the
104[[http://www.mitpressjournals.org/cmj|Computer Music Journal]]'s DVD.
105
106Thanks go also to John Cowan who brought the
107[[http://supertunaman.com/cdl/cdl_v0-1.txt|Chicken Dance License]] as
108a possible new license to our attention :)
109
110Matt Welland announced a new version of chicken-iup
111[[http://lists.nongnu.org/archive/html/chicken-users/2011-03/msg00147.html|here]]. Also
112note that the download link has changed since the last Gazette.
113
114YC started a discussion about a
115[[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00000.html|Generic
116RDBMS Interface]], where lots of people envisioned an easy to use glue
117to access RDBMS. The participants argued over the use of URL encoded
118queries vs. other more lispy forms. Thomas Chust developed
119[[http://www.chust.org/fossils/dbi/index|a prototype]] and is looking
120for feedback. Thanks!
121
122John Magolske shows us a nice trick to
123[[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00015.html|use
124csi within Vim to evaluate visually selected text]]. He also ran into
125issues when installing chicken eggs in a nonstandard location w/o hard
126coding in these paths during compilation. Thanks to the helpful people
127on chicken-users
128[[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00035.html|
129he could solve his issue]].
130
131Matt Welland asks for a way to produce a notification sound in a cross
132platform way
133[[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00029.html|in
134this mail]] but until now we don't have a good answer for this
135yet. Maybe you can help? Matt is also still waiting for an answer to a
136problem with
137[[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00031.html|srfi-19
138under windows]].
139
140Felix announced the release of the hopefully latest development
141snapshot before the next major release. Please test
142[[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00036.html|4.6.7]]
143as hard as you can.
144
145Chris Bolton asked about how to print colored messages to a terminal
146and 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
148sequences]].
149
150John J Foerch asked for a spiffy feature: remote-address should also
151take the X-Forwarded-For header into account when generating an
152address. Peter Bex kindly implemented it.
153
154Alex Shinn announced the
155[[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00062.html|R7RS
156draft]] which we all waited for. Thanks to the scheme-report group!
157
158Steve Graham asked about the possibility of writing android apps in
159chicken. Moritz Heidkamp
160[[http://lists.nongnu.org/archive/html/chicken-users/2011-04/msg00071.html|promised
161to write a gazette recipe]] about it "in the near future".
162
163William Xu asked about a way to upgrade all installed eggs. Felix
164added a '-reinstall' option to chicken-install which you are
165[[http://lists.nongnu.org/archive/html/chicken-users/2011-05/msg00022.html|invited
166to test]].
167
168Thanks to Alan Post, Mario Goulart has been able to fix the links to
169the [[http://tests.call-cc.org/|tests.call-cc.org]] egg feeds.
170
171== 3. Omelette Recipes
172
173The dream of many Schemers is to write Scheme for a living. Due to the
174regrettable tendency of employers to frown upon good ideas, the best
175way to achieve this is to start your own company, so you can be your
176own boss, and frown upon the good ideas of others instead.
177
178However, running your own company comes at a price; and a big part of
179that price is the horror of "book keeping"; the requirement to track
180all flows of money and other, more abstract, forms of value in and out
181of your company.
182
183Some people will tell you book-keeping is simple. "Just keep all your
184receipts and bank statements and bills and invoices", they say. "Then
185send 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
188countries). "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
190to do a job that a computer can easily do in milliseconds?" And then
191the smug smile slowly drips from the face of the "Oh it's easy" crowd.
192
193Clearly, book-keeping is complicated. And yet also simple, in that it
194is determined by sets of rules.
195
196We know what to do, don't we? Let's get coding!
197
198The problem is that book-keeping involves several different kinds of
199inputs - bills (that people send you), invoices (that you send
200people), transfers of money (bills and invoices being paid), loans (to
201and from the company), employees being paid, interest payments from
202the bank, dividend payments, and so on; while it also involves several
203different outputs - tax reports for the various taxes involved (in the
204UK, I had to deal with VAT every three months, income tax and national
205insurance when paying myself and my wife as employees every month, an
206annual filing fee, and annual corporation tax and dividend payments),
207statutory filing of certain financial summaries (generally annually),
208and internal reporting: How much was I spending? How much did each
209client owe? How much should be in the bank by when? Plus, it's nice to
210be able to generate nice invoices to send to folks who owe you
211money. That's a form of specialised report, too, just reporting on a
212single invoice.
213
214Each of the output reports depend in complex ways on different
215information from the inputs. The VAT reports mainly have to add up how
216much VAT I've paid when being billed by others, and how much VAT I've
217charged when invoicing - meaning that VAT needs to be tracked on all
218bills and invoices so it can be extracted. They also want to know
219totals of actual money in and out of the company in the period (even
220stuff where VAT isn't an issue), presumably to check up on
221me. Meanwhile, end of year reports tend to need to know how much I've
222invoiced for various different kinds of work, and what I've spent on
223what kinds of things: buying equipment that will last me for several
224years is handled differently to expenses like travel, or buying stuff
225that I eventually resell to clients (so in our invoices, we need to
226keep track of money charged for services separately to money charged
227for things).
228
229Some reports care about virtual money moving hands. As soon as I
230invoice somebody, then the company now has a virtual asset - some
231money owed to it. That's worth as much as cash in the bank from some
232perspectives (generally, I have to pay tax on it as soon as it's
233invoiced, even if I've not been paid). And yet some care only about
234actual cash changing hands (working out my bank balance, for instance).
235
236Sometimes our clients invite us to incur expenses in doing work for
237them (such as extra travel) and then invoice them on for those
238expenses, so they pay us back - in which case, expenses need to be
239able to be tied to invoices, as well. Sometimes we decide to cancel an
240invoice, which can't be done by just pretending it never existed, for
241audit-trail reasons; we need to issue a "negative" invoice called a
242credit note.
243
244Just to complicate matters more, the actual movement of money isn't
245atomic. If I invoice somebody on date A, they might post me a cheque
246which arrives on date B, which I pay into the bank on date C, which
247actually clears into the account (and thereby appears on my bank
248statement, when I get it) on date D. So at date A the company now has
249a "we are owed" pretend-money asset, which goes through various stages
250until it finally turns into money in the bank on date D.
251
252I handled my book-keeping with some hacky scripts written in Chicken
253Scheme. What I'm going to document here is partly what I've done, and
254partly what I should have done - it was a very iterative process,
255refining the best way to handle stuff, and there's lots of
256improvements I've wanted to make but not had time to. So I'm going to
257describe the ideal case, not the hacky half-way house I actually have
258right now!
259
260The approach I took was to have a file called a "ledger" that I enter
261all my invoices and so on into. This is parsed to build up a bunch of
262data structures in memory, from which the various reports can easily
263be obtained. Firstly, for each kind of input object (invoices, bills,
264etc) there's a list of them, or more often a hashmap to make them easy
265to find by some identifier (I can give my invoices unique symbolic
266names, for instance). That contains the raw data as parsed from the
267ledger file. But then we also create summary structures, which are
268used by the more general reports to generate their output without
269having to special-case each and every different input object type, and
270to enable sharing of common functionality between reports.
271
272The main summary structure is the double-entry transaction list, which
273models the entire financial activity of the company as transfers
274between accounts.
275
276Imagine 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
286As part of the work, I lose a router (worth GBP 350) from my stock,
287and have to spend GBP 35 on a train fare.
288
289This 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
302And, eventually, they might pay me, which hits my bank account some
303time later:
304
305 * 2011-05-07 "Payment from Widget Corp (INV005)"
306   * clients.widgetcorp.balance -1415
307   * bank.balance +1415
308
309And 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
315Note a few tricky things. Each transactions "splits", as the lines
316within them are known, have to sum to zero for everything to balance
317correctly, which tells us that nothing has gone missing. So when we
318start being owed GBP 1,415 by Widget Corp, we need to account for
319where 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
322of this invoice, I can charge the client for) pop into
323existence. "taxes.vat" looks as if VAT is a form of income for me, as
324money comes "from" it in the transaction - which is sort of true; I'm
325charging Widget Corp for some VAT alongside for the actual work
326done. Figuring out what signs to put on all the items in the invoice
327is mind-bending and painful, but if you just concentrate on making it
328all add up to zero in the end and starting from things that are
329obvious (is money going into or out of the bank account, or the "owed
330to me by this customer" account?), you can figure it out.
331
332From 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
354What is an "account"? There's a few kinds, and what kind of account it
355is matters in reporting. Accounts might be assets within the company -
356such as "clients.widgetcorp.balance" or "bank.balance" or
357"stock.balance". Or they may be places where money (be it real or
358virtual) is created from or destroyed by (from the perspective of the
359company), such as "income.work" and "expenses.travel". The important
360difference is that balance-type accounts have a balance that is
361increased when money is sent to them and decreased when it's taken
362out, and that balance is part of the value of the company, while the
363income/expense type accounts don't. In my terminology, these are
364"balance" accounts and "delta" accounts. Each account also begins to a
365group, used to aggregate them in reports: there's income accounts,
366bank accounts, client accounts, and so on. And accounts may be tied to
367a third party - I've given an example of a client above, but also, the
368organisations that send me bills have balances (the money I owe
369them). In general, every third party (be they ones that bill me, or
370ones that I invoice, or both - I've interacted with other freelancers,
371sometimes working for them, sometimes vice versa) has a set of
372accounts attached to them for their balance, expenses I can claim from
373them, 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
393An account's {{third-party}} slot may be {{#f}} if it's not part of a
394third party.
395
396Now, to make things easy, I parse the ledger by just defining a heap
397of procedures and macros, and then diving into the ledger with
398eval. It's just Scheme code that, as it is executed, builds up the
399data structures. We'll need some helpers to set up third parties
400properly:
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
426Now we can work on a way of representing bills and invoices. A nice
427input 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
443The idea is that the sales taxes incurred on a line are specified as a
444list after the amount. If there's no taxes due, then we use an empty
445list. Otherwise we have a list of taxes, which are either plain tax
446names (to have the system compute the tax due itself) or two-element
447lists joining a tax name to a precomputed amount (often, when we pass
448on an expense, we know the tax we paid as it's on the receipt, so we
449should use that (even if they made a mistake working it out) rather
450than calculating our own).
451
452A nice way to implement that might be to make "invoice" a macro that
453absorbs its first three arguments as an invoice code, the name of the
454third party to invoice, and the date; then treats the rest as a body
455to be wrapped in a dynamic environment in which a parameter allows
456{{sale}}, {{expense}}, and {{service}} to add lines to the
457invoice. 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
563We end the expansion of the {{invoice}} macro with a call to
564{{generate-invoice-transactions!}}, which will do the task of creating
565the double-entry transactions for the invoice. Other types of summary
566structure can be added by calling additional generator procedures at
567this point. This is largely a matter of going through the invoice
568lines, handling them on a case-by-case basis to generate lists of
569transaction splits that we can append together to generate the invoice
570transaction. The case of expense lines is interesting, in that an
571extra transaction has to be generated for each expense, to record its
572initial spending, as well as a split to record the expense being
573claimed in the invoice.
574
575For 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
627Feeding in the above example invoice, then checking out the resulting
628double-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
652We've ended up with multiple splits for the same account, as we record
653that both VAT and the money due for the service are to come from the
654client's balance account - and other splits will add plenty more. To
655fix this, we need to write a procedure that canonicalises a list of
656splits, and call that on the splits before calling
657{{make-txn}}. Canonicalisation consists of finding all the splits that
658refer to the same account and have the same notes (be it {{#f}} or a
659string) and merging them into one with the total of the amounts. But
660I'll leave that (along with implementing bills, payments, and some
661actual reports) as an exercise to the reader... It's easy to imagine
662how to generate a VAT report from the list of transactions, by
663filtering them for membership of the required date range and looking
664for splits involving "taxes.vat", or to generate a nicely formatted
665invoice by extracting a single invoice record, or to work out the
666balance of an account at any point in time by adding up all the
667transaction splits that involve it up to that point in time. Also, the
668core engine needs to be wrapped up in a module that only exposes the
669required bindings, and hides internals.
670
671Having automated one's book-keeping and financial reporting, many
672operations (such as the VAT returns) can be done without involving an
673accountant; in my case, the accountant is only needed to help with the
674annual corporation tax computation and filing of official accounts,
675which requires deep understanding of the UK tax system to do
676everything properly. Having said that, if I studied the system
677properly (and tracked the changes each year), I'm sure I could
678automate that, too...
679
680== 4. About the Chicken Gazette
681
682The Gazette is produced occasionally by a volunteer from the Chicken
683community. The latest issue can be found at
684[[http://gazette.call-cc.org]] or you can follow it in your feed
685reader at [[http://gazette.call-cc.org/feed.atom]]. If you'd like to
686write an issue, [[http://wiki.call-cc.org/gazette|consult the wiki]]
687for the schedule and instructions!
Note: See TracBrowser for help on using the repository browser.