easy
is a command line utility which generates artifacts typically
used in accounting (but also other fields of business like invoicing)
based on events provided as YAML files.
In principle easy
is used to transform the business events given in
YAML into other business artifacts like invoices (PDF documents) or
journal files for use with ledger (a command line utility for plain
text accounting.) Processing of events happens in 5 steps:
- Read input events
- Check input events against spec
- Augment input events
- Calculate derived values
- Resolve related events
- Check augmented event against spec
- Render event(s) through templates
You can have as many YAML files as you want. Each YAML file contains one or more events. You are also free to categorize your YAML as you please. (E.g. We have one file for each customers invoices and a separate file for all settlements of the invcoices.)
All events are spec’ed in Clojure Specs. Some of the fields are mandatory, some are optional. See the specs for details. Here are some example Specs:
The easy code base is structured in a way that nested name spaces
reflect the data structure that is used when writing events. (E.g. an
event of type revenue has a field items, the spec for items will
be in the name space revenue.item
.)
This is what a typical easy YAML document with one event might look like.
--- - type: revenue date: 2017-12-04 customer: 6 number: 2 version: 1 deadline: 30 items: - rate: 200 hours: 13.5 beneficiary: phil discount: 0 timesheet: path/to/timesheet.csv
Easy runs on lumo and uses node packages available via npm, as well as ClojureScript packages available via clojars (maven).
Hence you will need to install the npm dependencies via npm install
.
ClojureScript dependencies tracked in deps.edn
are installed via
clj
(aka. Clojure CLI). Please install clj
as described here.
To render invcoice via latex you need to have at least the follwoing packages installed (Debian naming):
- texlive-latex-base
- texlive-latex-recommended
Easy provides two command line utilities: easy
itself and collect
.
Here are some examples on how to use these.
You have to have some files in place in order to use easy. Please find the example directory of this repo and to run these examples.
Render ledger entries from events and run the through ledger to create a balance:
easy ledger -i events/invoices.yml | ledger bal -f -
Render invoice 18.2.1 to latex:
easy invoice -i events/invoices.yml -n 1.1.1
Sidenote: Easy invoice identifiers (invoice-no) are divided into three
segments <customer-id>.<invoice-number>.<version>
. In events thei
can be given as invoice-no
in aggregated form or separately as
customer
, number
, and version
.
Run a transform to see the transformed events, which is handy for writing templates:
easy transform -i events/invoices.yml
Or, just validate the input and exit:
easy validate -i events/invoices.yml
Easy alternatively takes events via STDIN:
cat events/invoices.yml | easy transform
This comes in particularly handy, when working with multiple event sources (i.e. YAML files)
cat events/*.yml | easy ledger | ledger -f - bal
(Please be ware that concatinating YAML files for easy only works if
the files lack the YAML’s document prefix ---
. The reason for this
is explained in “On Frontmatter Templates”. And it doens’t really
matter as you won’t be using cat
anyways, just keep reading.)
Finally easy comes with its own utility to collect multiple event
sources into a single event stream. Given a root directory for events
it will find all files matching *.yml
, load these, annotate each
event with its origin (for locating issues quickly), and outputs a
sorted stream of events.
collect events | easy ledger -y 2019
Yes, you guessed it, you can use -y <year>
to filter events by year.
A typical event source for easy might look like this:
- type: expense
account: Aufwand:6940-Bankspesen
payer: Joint
amount: 5
date: 2018-05-31
description: Bankgebühren
- type: expense
account: Aufwand:6940-Bankspesen
payer: Joint
amount: 5
date: 2018-06-30
description: Bankgebühren
Imaging the file going on like this. These are monthly occurring
events with very high redundancy. In these cases you might want to use
collect
’s (and easy
’s) capability to work with templates. This
allows you to define common attributes of the list of events as a
template using multiple YAML documents in one YAML file. That way you
store the same information by writing:
---
type: expense
account: Aufwand:6940-Bankspesen
payer: Joint
description: Bankgebühren
---
- date: 2018-05-31
amount: 5
- date: 2018-06-30
amount: 5
Any list will be interpreted as a list of events, likewise any associative will be interpreted as template. Templates will completely replace any previous template.
Here is another example:
---
a: 1
b: 2
---
- c: 3
---
b: 4
d: 5
---
- e: 6
- f: 7
Will result in the following data structure:
- a: 1
b: 2
c: 3
- b: 4
d: 5
e: 6
- b: 4
d: 5
f: 7
Neat, isn’t it?
This does only concern you if you are developing easy.
Easy uses a multimethod transform
to transform events into a
augmented form. Augmented meaning it has all the details
calculated and associated data added to be used in a template.
The transformation of a single event might happen within a context
.
The context
is the set of all events within a processing run.
(Technically the context
is not a set, but a map, where the values
are lists of events and the keys are their corresponding type
. This
is for convenience because in almost all cases you only want to have
events of a single type
when using the context to resolve associated
events. E.g. for a given invoice you want to resolve its settlement,
or the other way round.)
Associated data is added via resolve-fns. The resolve-fns use the context to lookup other events. As the context contains only “mildy” and not fully transformed events the resolve-fn will very likely have to transform the resolved event in order to make it augmented. If this would mean that another resolve-fn is triggerd this will likely trigger an endless recursion of resolves.
To prevent this from happening you have to adhere to the following
conventions: (A) Resolve functions that receive nil
as the context
should return the event untouched. (B) When calling transform
from a
resolve-fn, always pass nil
as the context
.