Posteringer
Documented against app commit ed7d6935. The same text lives in docs/agent/transactions.md in the app repo.
Posting shape
Section titled “Posting shape”A bogføring is one header row in bogforings plus one or more lines in bogforing_transactions. Every live list that shows a konto and a modkonto is showing those lines after they have been folded. The raw lines stay in the database.
Use this file when fixing a total, a sign, or a column, and when adding a new way to create a bilag. Screen specs name their entry function and then point here. They do not restate this shape.
Header
Section titled “Header”BogforingService::store (app/Services/BogforingService.php) inserts the header and returns its id. The manual bogføring form, faktura-indbakke, and salg invoice all arrive here with a request object on $data['data'] and the active company on $data['accountId'].
Fields written on create:
| Column | Source |
|---|---|
account_id | Active company |
number | Bilagsnr (number) |
order_number | ordernumber |
bogforing_category_id | Daglig, Indbetaling, or Lønninger |
bogforing_type_id | See header types below |
date | bogfoDate, stored as a database date |
text | Posting text |
forfaldsdato | Optional |
fakturanr | Optional |
leverandorer_id | Set for supplier types |
kunder_id | Set for customer types |
terms_of_payment_id | betalingstype |
csv_amount | Original CSV amount when the row came from an import. 0 otherwise |
amount | Document-currency beløb (belob), parsed from Danish decimal text |
currency_id | Selected valuta |
conversion_rate | Rate applied to reach the company currency. Default 1 |
invoice_img_id | Set when a scanned or uploaded image is attached |
invoice_id | Set when the row comes from a salg invoice |
bank_csv_id | Set when the row comes from a bank CSV |
is_approve | 1 unless the caller passes otherwise |
is_bogfo_visible | 1 unless the request sends bogfovisible |
BogforingService::update changes the same header fields except csv_amount and bank_csv_id. It can also set invoice_img_id when the edit carries an image. It does not replace the lines. The controller deletes and recreates lines, or calls BogforingTransactionService::store again, after the header update. Check the caller before assuming lines are left in place.
Bank import does not use this store for the first insert. storeBankCsvRows inserts a header per unmatched CSV row with bogforing_category_id = 1, bogforing_type_id = 1, is_active = 0, and is_bogfo_visible = 0. original_text keeps the CSV text. Those rows stay off the bogføring list until updateBankBogforingPost sets is_active = 1 and is_bogfo_visible = 1.
Header types and categories
Section titled “Header types and categories”Seeded in database/seeders/BogforingTypeSeeder.php. Ids follow insert order:
| Id | Name | Abbreviation | Shown as a choice |
|---|---|---|---|
| 1 | Finansbilag | FB | yes |
| 2 | Leverandørfaktura | LF | yes |
| 3 | Leverandørbetaling | LB | yes |
| 4 | Kundeindbetaling | KB | yes |
| 5 | Manuel kundefaktura | MK | yes |
| 6 | Kundefaktura | KF | no |
Categories (BogforingCategorySeeder): 1 Daglig, 2 Indbetaling, 3 Lønninger.
On the manual create path the controller sets flipValue when the type and the kontoplan type disagree in these two cases:
- type
4(Kundeindbetaling) and kontoplan type2 - type
3(Leverandørbetaling) and kontoplan type1
computeTransaction then reverses the beløb once more. Do not add a third reversal in the form.
BogforingTransactionService::store runs after the header exists. $data['id'] is the new header id. $data['baseAmounts'] comes from computeTransaction. The request still carries the Danish-formatted line amounts (kontoBelob, momskodeBelob, modkontoBelob, momskodeModkontoBelob).
Seeded line types (BogforingTransactionTypeSeeder), in insert order:
| Id | Name | Parent |
|---|---|---|
| 1 | kontoplan | none |
| 2 | modkonto | none |
| 3 | momskode | the type-1 line, via bogforing_transaction_id |
| 4 | modmomskode | the type-2 line, via bogforing_transaction_id |
Insert order inside store:
- Type
1.kontoplan_idis the chosen konto.amountiskontoBelob.base_amountisbaseAmounts['belob'].momskode_idis null. - Type
3, only when a momskode is selected andgetMomskodeKontoIdresolves its first konto.bogforing_transaction_idpoints at the type-1 row. If the momskode also has a second konto, this line’samountandbase_amountare multiplied by-1. - A second type
3when that second konto exists. Same parent id, same momskode, the unflipped moms amounts, andkontoplan_idset to the second konto. - Type
2, only when a modkonto is selected.bogforing_transaction_idis null.amountismodkontoBelob.base_amountisbaseAmounts['modBelob'](already negative; see Amounts). - Type
4for the modkonto’s momskode, with the same double-konto sign rule as step 2. Parent is the type-2 row. - A second type
4when the modkonto momskode has a second konto.
getMomskodeKontoId looks up momskodeMap (first konto) or momskodeDoubleMap (second konto) and then kontoplanMap. A missing map entry skips that moms line. It does not fail the whole store. store returns true even when optional lines were skipped.
Bank completion uses a second writer, simplifiedStore, fed by buildBankBogforingTransactions. That path still produces types 1, 3, and 2 (and a second type 3 when the momskode has two konti). It does not invent a different shape. The bank edit screen must keep calling this path so the folded columns match a manually entered bilag.
Amounts
Section titled “Amounts”amount on a line is the document currency. base_amount is the figure in the company currency after conversion.
computeTransaction starts from belob:
- If
flipOptionis true (the default) and the kontoplan type id is1, the beløb is multiplied by-1. - If the controller set
flipValue, the beløb is multiplied by-1again. - A normal momskode (no second konto) takes VAT out of the konto amount:
moms = belob * (vat / (vat + 100)), and the kontobelobis reduced by that moms. - A momskode with a second konto (
momskodeDoubleMap) does not reduce the konto amount. Moms isbelob * (vat / 100). The line writer then stores one positive and one negative moms line. - Modkonto starts as the same signed beløb. Its moms, if any, is split the same way. The returned
modBelobandmodMomsare multiplied by-1so the modkonto side faces the konto side.
The returned keys are belob, moms, modBelob, and modMoms. A new screen that creates a posting must pass the same request fields (belob, kontoplan, momskode, modkonto, modMomskode, and the four line beløb) and must call computeTransaction before store. Writing lines with only the header amount will not match the columns the lists display.
Danish input is parsed with currencyToDecimal before it is stored. Do not store a string such as 12500,00 in amount or base_amount.
What the screens show
Section titled “What the screens show”transformTransactions walks each header’s lines and builds four bags: kontoplans, modkontos, momskodes, modMomskodes.
- Type
1starts a konto column entry. Its combined amount starts as that line alone. - Type
3is appended tomomskodesand added into the current konto column’s combined amount. - Type
2starts a modkonto column entry. - Type
4is appended tomodMomskodesand added into the modkonto column’s combined amount.
getBankBogforingCombinedAmounts is the same fold for one header: types 1+3 are the konto total, types 2+4 are the modkonto total.
These live surfaces display the folded pair, not the raw lines:
- Bogføring list and the bogføring form
- Saldobalance and detailed saldobalance
- Leverandørkontokort and kundekontokort
- Sammenligning and its pair-transaction modal
- Bank import edit
- Kassekladde export
Saldobalance totals that go through getTotalPerFilteredTransactions skip headers where is_approve is not 1, and they only include lines whose header is is_active = 1 in the given year. A new report has to use this fold. Summing bogforings.amount instead will disagree with the konto column wherever moms was split out.
getDkkBelob is the display conversion used while folding. If the company currency is DKK, it returns the line amount. If the document currency is DKK and the company is not, it returns base_amount. Otherwise it looks up the Friday-adjusted rate for the line date. Sammenligning can pass flip_value on the header so the displayed amount is reversed when the match is on the modkonto.
Visibility
Section titled “Visibility”| Flag | Effect |
|---|---|
is_active | getAllFiltered requires 1. Inactive headers are absent from the bogføring list and from konto totals. Bank import creates rows at 0. Soft-hide paths in BogforingService set it back to 0 rather than deleting the row. |
is_bogfo_visible | Applied only when the caller passes bogfoVisible. The bogføring list does this so a hidden header stays out of the daybook. Bank import creates rows at 0 and updateBankBogforingPost sets it to 1 when the line is finished. |
is_approve | Optional filter on the list. Faktura-indbakke uses it for approval. Konto totals ignore unapproved headers. Default on a normal store is 1. |
is_ignore | Set from bank completion when the user marks a row to ignore. It does not by itself remove the row from getAllFiltered. |
A new producer that should appear on the bogføring list must set is_active = 1 and is_bogfo_visible = 1. Leaving the bank-import defaults in place will hide the bilag.
Side effects
Section titled “Side effects”Creates, updates, and deletes of a header or its lines are written to bogforing_change_logs by database triggers (database/migrations/2026_08_05_000002_create_bogforing_audit_triggers.php and the later audit patches). The application does not insert those rows itself, except for modfaktura.
BogforingAuditLogWriter::logModfakturaGenerated writes one modfaktura_generated row on the source header, with related_bogforing_id pointing at the generated bilag. The Ændringslog page and the per-bilag log inside the bogføring form read this table. See docs/agent/bogforing.md for the page.
SetAuditUserMiddleware and FlushBogforingAuditStagingMiddleware run on web requests so the trigger can see the user and so staged trigger rows are flushed. A new write path that uses a raw connection and skips those middleware will log the change with no user or will leave the staging table unflushed.
Currency rates used by the bogføring and faktura forms come from Api\ConversionController@getExchangeRateDB (GET /api/currencyexchange/db). The scheduled command currencyexchange:get-daily-conversion-rates fills those rates. The curl fetcher and the historical endpoint are not called by a live screen.
Producers
Section titled “Producers”Each of these ends in the same header-plus-lines shape. The module file owns the steps before store.
| Producer | Entry | Module |
|---|---|---|
| Manual bogføring | BogforingController@store then BogforingService::store and BogforingTransactionService::store | bogforing.md |
| Kassekladde CSV | Imports\BogforingController@store | kassekladde.md |
| Bank CSV | storeBankCsvRows, then simplifiedStore on edit | rules.md |
| Faktura-indbakke | FakturaIndbakkeController@store / @update | faktura-indbakke.md |
| Faktura-scan | InvoiceScanController@createInvoices | faktura-scan.md |
| Salg invoice | BogfoInvoiceController@store / @update | salg.md |
| Modfaktura | ModfakturaService from leverandør or kunde kontokort | levkun.md |
Checklist for a new producer
Section titled “Checklist for a new producer”- Insert the header with
BogforingService::store, or an insert that sets the same columns, includingaccount_id, type, category, date, text, amount, currency, and conversion rate. - Set
flipValueonly for the type and kontoplan-type pairs listed above. - Call
computeTransaction, thenBogforingTransactionService::store, passing the four line beløb and the momskode maps. Do not insert a single line with the header amount. - Set
is_active = 1andis_bogfo_visible = 1when the bilag should show on the bogføring list. Leave them at0only while the row is still a draft, as bank import does. - Expect an Ændringslog row from the triggers. Do not write a second log row unless the action is a new kind of event, the way modfaktura is.
- Read the result back through
transformTransactionsbefore adding a column. The screen total is the folded konto or modkonto, notbogforings.amount.