source: project/gazette/src/issues/ @ 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
1((title . "Issue 20")
2 (authors "Alaric Snell-Pym")
3 (date . 1300960048))
5== 0. Introduction
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
12Thanks for the recipe go to Alaric. Thanks to the Chicken Team for
13compiling this issue.
17== 1. Hatching Farm
19The following new extensions were added:
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)
29The following new versions have been released:
31* [[egg:hen|hen]] '''0.5''': Create tag 0.5 (
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 for the latest changes. ([[user:mario-domenech-goulart|Mario Domenech Goulart]])
53* [[egg:awful-postgresql|awful-postgresql]] '''0.4''': See for the latest changes. ([[user:mario-domenech-goulart|Mario Domenech Goulart]])
54* [[egg:awful-sqlite3|awful-sqlite3]] '''0.4''': See for the latest changes. ([[user:mario-domenech-goulart|Mario Domenech Goulart]])
55* [[egg:awful|awful]] '''0.31''': See 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)
80== 2. Chicken Talk
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[[|latest development snapshot]]
87and give it a try.
89A Chicken-based commercial application has been added to the
90[[|Software]] wiki page.  According to
91the description, [[|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
97On the hobbyist side
99a scheme interpreter written in Chicken by
100[[|jacktrades]] has been found on the
103Evan Hanson reported a Chicken sighting in the
104[[|Computer Music Journal]]'s DVD.
106Thanks go also to John Cowan who brought the
107[[|Chicken Dance License]] as
108a possible new license to our attention :)
110Matt Welland announced a new version of chicken-iup
111[[|here]]. Also
112note that the download link has changed since the last Gazette.
114YC started a discussion about a
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[[|a prototype]] and is looking
120for feedback. Thanks!
122John Magolske shows us a nice trick to
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
129he could solve his issue]].
131Matt Welland asks for a way to produce a notification sound in a cross
132platform way
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
138under windows]].
140Felix announced the release of the hopefully latest development
141snapshot before the next major release. Please test
143as hard as you can.
145Chris Bolton asked about how to print colored messages to a terminal
146and learned about [[|the fmt egg]] and
147[[|ansi escape
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.
154Alex Shinn announced the
156draft]] which we all waited for. Thanks to the scheme-report group!
158Steve Graham asked about the possibility of writing android apps in
159chicken. Moritz Heidkamp
161to write a gazette recipe]] about it "in the near future".
163William Xu asked about a way to upgrade all installed eggs. Felix
164added a '-reinstall' option to chicken-install which you are
166to test]].
168Thanks to Alan Post, Mario Goulart has been able to fix the links to
169the [[|]] egg feeds.
171== 3. Omelette Recipes
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.
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.
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."
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.
193Clearly, book-keeping is complicated. And yet also simple, in that it
194is determined by sets of rules.
196We know what to do, don't we? Let's get coding!
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.
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).
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).
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.
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.
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!
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.
272The main summary structure is the double-entry transaction list, which
273models the entire financial activity of the company as transfers
274between accounts.
276Imagine I invoice Widget Corp for setting up and installing a router:
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
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.
289This might expand into the following transactions:
291 * 2011-03-02: "Expense for Widget Corp (INV005)"
292   * +35 "Travel to site"
293   * cash -35
295 * 2011-04-25 "Invoice Widget Corp (INV005)"
296   * -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
302And, eventually, they might pay me, which hits my bank account some
303time later:
305 * 2011-05-07 "Payment from Widget Corp (INV005)"
306   * clients.widgetcorp.balance -1415
307   * bank.balance +1415
309And then one day I'll pay my VAT bill, which will look something like:
311 * 2011-06-01 "VAT payment for period from 2011-03-01 to 2011-06-01"
312   * taxes.vat 230
313   * bank.balance -230
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"" (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.
332From the above, we can start to flesh out some data structures:
335(define-record txn
336               date customer code description splits)
338(define-record txn-split
339               account amount notes)
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)))))
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 "" and "". 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:
376(define-record third-party
377               name address balance-account expenses-account)
379(define-record account
380               name type group third-party)
383(define *third-parties* (make-hash-table))
384(define *accounts* (make-hash-table))
386(define (find-account acct-name)
387  (hash-table-ref *accounts* acct-name))
389(define (register-account! acct)
390  (set! (hash-table-ref *accounts* (account-name acct)) acct))
393An account's {{third-party}} slot may be {{#f}} if it's not part of a
394third party.
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
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)))
426Now we can work on a way of representing bills and invoices. A nice
427input syntax would be:
430(define-third-party "clients.widgetcorp" "123 Any Street" 'clients)
431(register-account! (make-account "" 'delta 'income #f))
432(register-account! (make-account "" '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))
437(invoice "INV005" "clients.widgetcorp" (ymd 2011 04 25)
438         (service "" 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) "" "cash" 35 () "Travel to site"))
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).
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:
460(define-record date year month day)
462(define-record invoice
463  name date third-party lines)
465(define (register-line! invoice line)
466  (invoice-lines-set! invoice
467                      (cons line (invoice-lines invoice))))
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
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))
486(define-record invoice-service-line
487  income-account amount taxes description)
489(define-syntax service
490  (syntax-rules ()
491    ((service income-account amount taxes description)
492     (register-service! (*current-invoice*) income-account amount 'taxes description))))
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)))
503(define-record invoice-sale-line
504  stock-account amount taxes description)
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)))
515(define-syntax sale
516  (syntax-rules ()
517    ((sale stock-account amount taxes description)
518     (register-sale! (*current-invoice*) stock-account amount 'taxes description))))
520(define-record invoice-expense-line
521  expense-account payment-account amount taxes description)
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)))
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))))
545(define *current-invoice* (make-parameter #f))
546(define *invoices* (make-hash-table))
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)))))
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.
575For now, let's just handle one case:
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)))))))
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))
627Feeding in the above example invoice, then checking out the resulting
628double-entry transaction list, shows that it worked:
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)))
646   date: #<date> Desc: Invoice INV005 for Widget Corp
647   Acct: 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
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.
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...
680== 4. About the Chicken Gazette
682The Gazette is produced occasionally by a volunteer from the Chicken
683community. The latest issue can be found at
684[[]] or you can follow it in your feed
685reader at [[]]. If you'd like to
686write an issue, [[|consult the wiki]]
687for the schedule and instructions!
Note: See TracBrowser for help on using the repository browser.