aboutsummaryrefslogtreecommitdiffstats
path: root/README.md
blob: 1311a9d9247b56ca60e974329b79b43d65f80d7f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# accounted-multicase-intgr

Example integration that sends vouchers (bilag) from a [Multicase](https://www.multicase.no)
"buntfil" export to [Accounted](https://accounted.se) through its v1 REST API.

It reads the `.xls` export, groups the posting lines into vouchers, maps them to
Accounted journal entries and creates them as drafts, optionally posting them.

## Install

```sh
go install simenandre.no/accounted-multicase-intgr@latest
```

## Usage

```sh
cp config.example.json config.json   # then adjust the mapping

# Parse and map every voucher, no network access
accounted-multicase-intgr check "MC Buntfil 01102026-12102026.xls"
accounted-multicase-intgr check -v "MC Buntfil 01102026-12102026.xls"   # print each entry

export ACCOUNTED_API_KEY=gnubok_sk_...      # scope bookkeeping:write
export ACCOUNTED_COMPANY_ID=<company uuid>
# export ACCOUNTED_URL=https://app.accounted.se

accounted-multicase-intgr push -dry-run export.xls   # server validates, nothing is saved
accounted-multicase-intgr push export.xls            # create drafts
accounted-multicase-intgr push -commit export.xls    # create and post, assigning voucher numbers
```

`push` validates everything before it writes anything: if any voucher has an
unmapped account or VAT code, or a date outside an open fiscal period, nothing
is sent.

## How vouchers are mapped

- **Grouping.** Lines are grouped by `BilagsArt` and `Bilagsnummer`. Inventory
  corrections (Lagerkorrigering) carry Bilagsnummer 0 and are grouped per
  batch (`BuntHodeID`) instead.
- **Amounts.** `Belop` is used, in the Multicase company's base currency.
  Positive is debit, negative is credit. Amounts are rounded to two decimals;
  Multicase writes currency conversions with three, so a voucher can end up a
  few öre off. That difference, up to `max_rounding_ore`, is posted to
  `rounding_account`. Zero-amount lines are dropped.
- **Accounts.** A line whose `Konto` equals the customer number (`Faktura
  kundenr`) or supplier number (`LevNr.`) is a subledger line and is posted to
  `customer_ledger_account` / `supplier_ledger_account`. Every other account
  must be listed in `accounts`.
- **VAT.** Multicase posts revenue and cost lines gross with an MVA code and
  leaves the split to the receiving ledger. For codes with a `rate`, the net
  amount stays on the mapped account and the VAT goes to the code's `account`.
  Codes with rate 0 pass through untouched.
- **Dimensions.** `AvdNr.` and `Prosjekt` are sent as the SIE dimensions named
  in `dimensions` (by default 1, kostnadsställe, and 6, projekt).

`config.example.json` maps the accounts found in one Print Supplies AS export
to BAS 2026 accounts. It is a starting point only: have the mapping, and in
particular the VAT codes, checked by whoever keeps the books.

## Not booking twice

Each voucher is sent with `source_id` `multicase:<BilagsArt>:<Bilagsnummer>`
and a deterministic `Idempotency-Key`, so retries after a timeout are replayed
by the server rather than repeated. Accounted keeps idempotency keys for 24
hours, so `push` also records every draft and posted voucher in
`multicase-state.json`. Re-running an overlapping export skips what was sent.

A voucher that changed in Multicase after it was sent is reported and skipped.
Posted vouchers cannot be edited; correct them in Accounted (storno or rättelse).

## Limitations

- Accounted keeps its ledger in SEK. Amounts are sent as they appear in
  Multicase, so the export's base currency must match the Accounted company's.
- Only the first worksheet of the export is read, and only Excel 97-2003
  (`.xls`) files are supported. The bundled BIFF8 reader exists because the
  available Go libraries decode negative whole-number cells as large positive
  numbers.
- Requests are throttled to stay under the default limit of 100 requests per
  minute per API key; 429 and 5xx responses are retried.