Changeset 25547 in project for gazette/src/issues/20.wiki


Ignore:
Timestamp:
11/21/11 21:48:09 (9 years ago)
Author:
Alaric Snell-Pym
Message:

gazette: Split the omelette (insert pun about breaking eggs) between issues 20 and 21, at a suitable point near half-way, with a bit of link text.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • gazette/src/issues/20.wiki

    r23724 r25547  
    313313   * bank.balance -230
    314314
    315 Note a few tricky things. Each transactions "splits", as the lines
     315Note a few tricky things. Each transaction's "splits", as the lines
    316316within them are known, have to sum to zero for everything to balance
    317317correctly, which tells us that nothing has gone missing. So when we
     
    394394third party.
    395395
    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...
     396Accounts are the organisational structure that turns a sea of financial
     397events into something we can start to make sense of. In the next thrilling
     398installment, we will look at actually entering transactions, and turning them into
     399a data structure from which useful reports can be produced...
    679400
    680401== 4. About the Chicken Gazette
Note: See TracChangeset for help on using the changeset viewer.