Skip to content

Posteringer

Documented against app commit ed7d6935. The same text lives in docs/agent/transactions.md in the app repo.

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.

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:

ColumnSource
account_idActive company
numberBilagsnr (number)
order_numberordernumber
bogforing_category_idDaglig, Indbetaling, or Lønninger
bogforing_type_idSee header types below
datebogfoDate, stored as a database date
textPosting text
forfaldsdatoOptional
fakturanrOptional
leverandorer_idSet for supplier types
kunder_idSet for customer types
terms_of_payment_idbetalingstype
csv_amountOriginal CSV amount when the row came from an import. 0 otherwise
amountDocument-currency beløb (belob), parsed from Danish decimal text
currency_idSelected valuta
conversion_rateRate applied to reach the company currency. Default 1
invoice_img_idSet when a scanned or uploaded image is attached
invoice_idSet when the row comes from a salg invoice
bank_csv_idSet when the row comes from a bank CSV
is_approve1 unless the caller passes otherwise
is_bogfo_visible1 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.

Seeded in database/seeders/BogforingTypeSeeder.php. Ids follow insert order:

IdNameAbbreviationShown as a choice
1FinansbilagFByes
2LeverandørfakturaLFyes
3LeverandørbetalingLByes
4KundeindbetalingKByes
5Manuel kundefakturaMKyes
6KundefakturaKFno

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 type 2
  • type 3 (Leverandørbetaling) and kontoplan type 1

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:

IdNameParent
1kontoplannone
2modkontonone
3momskodethe type-1 line, via bogforing_transaction_id
4modmomskodethe type-2 line, via bogforing_transaction_id

Insert order inside store:

  1. Type 1. kontoplan_id is the chosen konto. amount is kontoBelob. base_amount is baseAmounts['belob']. momskode_id is null.
  2. Type 3, only when a momskode is selected and getMomskodeKontoId resolves its first konto. bogforing_transaction_id points at the type-1 row. If the momskode also has a second konto, this line’s amount and base_amount are multiplied by -1.
  3. A second type 3 when that second konto exists. Same parent id, same momskode, the unflipped moms amounts, and kontoplan_id set to the second konto.
  4. Type 2, only when a modkonto is selected. bogforing_transaction_id is null. amount is modkontoBelob. base_amount is baseAmounts['modBelob'] (already negative; see Amounts).
  5. Type 4 for the modkonto’s momskode, with the same double-konto sign rule as step 2. Parent is the type-2 row.
  6. A second type 4 when 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.

amount on a line is the document currency. base_amount is the figure in the company currency after conversion.

computeTransaction starts from belob:

  • If flipOption is true (the default) and the kontoplan type id is 1, the beløb is multiplied by -1.
  • If the controller set flipValue, the beløb is multiplied by -1 again.
  • A normal momskode (no second konto) takes VAT out of the konto amount: moms = belob * (vat / (vat + 100)), and the konto belob is reduced by that moms.
  • A momskode with a second konto (momskodeDoubleMap) does not reduce the konto amount. Moms is belob * (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 modBelob and modMoms are multiplied by -1 so 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.

transformTransactions walks each header’s lines and builds four bags: kontoplans, modkontos, momskodes, modMomskodes.

  • Type 1 starts a konto column entry. Its combined amount starts as that line alone.
  • Type 3 is appended to momskodes and added into the current konto column’s combined amount.
  • Type 2 starts a modkonto column entry.
  • Type 4 is appended to modMomskodes and 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.

FlagEffect
is_activegetAllFiltered 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_visibleApplied 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_approveOptional filter on the list. Faktura-indbakke uses it for approval. Konto totals ignore unapproved headers. Default on a normal store is 1.
is_ignoreSet 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.

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.

Each of these ends in the same header-plus-lines shape. The module file owns the steps before store.

ProducerEntryModule
Manual bogføringBogforingController@store then BogforingService::store and BogforingTransactionService::storebogforing.md
Kassekladde CSVImports\BogforingController@storekassekladde.md
Bank CSVstoreBankCsvRows, then simplifiedStore on editrules.md
Faktura-indbakkeFakturaIndbakkeController@store / @updatefaktura-indbakke.md
Faktura-scanInvoiceScanController@createInvoicesfaktura-scan.md
Salg invoiceBogfoInvoiceController@store / @updatesalg.md
ModfakturaModfakturaService from leverandør or kunde kontokortlevkun.md
  1. Insert the header with BogforingService::store, or an insert that sets the same columns, including account_id, type, category, date, text, amount, currency, and conversion rate.
  2. Set flipValue only for the type and kontoplan-type pairs listed above.
  3. Call computeTransaction, then BogforingTransactionService::store, passing the four line beløb and the momskode maps. Do not insert a single line with the header amount.
  4. Set is_active = 1 and is_bogfo_visible = 1 when the bilag should show on the bogføring list. Leave them at 0 only while the row is still a draft, as bank import does.
  5. 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.
  6. Read the result back through transformTransactions before adding a column. The screen total is the folded konto or modkonto, not bogforings.amount.