Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Journalminnet

Journalen är kassans fiskala minne. Varje sak som händer i kassan och som har betydelse i efterhand - en inslagen vara, en makulerad rad, en betalning, kontrollenhetens svar, en rapport - skrivs som en rad i journalen i samma ögonblick som den händer. Det är journalen, inte kassans minne eller databaser, som är sanningen om vad som registrerats: X- och Z-rapporternas siffror återskapas ur den vid omstart, och exporten till Skatteverket byggs ur den.

Därför gäller tre principer:

  • Bara tillägg. En rad skrivs aldrig om och tas aldrig bort. Ett misstag rättas med en ny händelse som pekar på den gamla (LINE_CORRECTED), inte genom att den gamla ändras. En ändrad fil går inte att skilja från ett manipulerat kassaregister.
  • Händelsen som den skedde, inte en tolkning. Två slag av samma vara är två LINE_ADDED, även om kvittot visar dem som en rad med antal 2. Kvittots sammanslagning är ren presentation.
  • Först journalen, sedan verkligheten. För de händelser som styr kassans tillstånd skrivs journalraden först, och kassan ändrar sitt tillstånd först när skrivningen lyckats.

Filformat

Journalen ligger i datakatalogens journal/. Det är NDJSON: en JSON-objekt per rad, avslutad med radbrytning, i UTF-8.

Det finns en fil per Z-period:

journal/journal_z000001.ndjson
journal/journal_z000002.ndjson
...

Namnet är journal_z följt av periodnumret med sex siffror (period_file_name). Själva Z_REPORT-händelsen är den sista som skrivs i en fil. Därefter fortsätter journalen i filen med Z-rapportens eget löpnummer (z_report_sequence_number) plus ett. Normalt är det samma sak som nästa fil i ordningen: fil N innehåller perioden som avslutas av Z-rapport nummer N, och nästa händelse hamnar i fil N+1.

Skillnaden syns efter ett avbrutet Z-försök (se Z_REPORT_PRINT_ATTEMPT). Det tar ett Z-nummer utan att stänga perioden, så nästa Z-rapport stänger fil N med nummer N+1, och journalen fortsätter i fil N+2. Filnumreringen får alltså samma lucka som Z-numren, och fil N+1 skapas aldrig. Alla läsare (omstarten, journalutskriften och SKVFS-exporten) hoppar över en fil som saknas. När kassan startar öppnar den filen för next_z_report_number i counters.db och fortsätter skriva i den.

Not för äldre journaler: Kassor före 2026-10-20 räknade alltid upp filnumret med ett efter en Z_REPORT. I sådana journaler följer filnumret inte Z-numret efter ett avbrutet Z-försök, och en period som löpte över en omstart kan vara delad på två filer. En läsare ska därför gå på händelserna, inte förutsätta att fil N slutar med Z-rapport N.

I samma katalog lägger kassan även SIE-filer och PDF-kopior av verifikationer (voucher_z000011.si, voucher_z000011.pdf) och SKVFS-exporter (skvfs_export_<tid>.xml). Läsarna tittar bara på filer som matchar journal_z*.ndjson.

Gemensamma fält

Varje rad är ett platt JSON-objekt. Tre fält finns alltid, först:

fälttypbetydelse
event_idheltalLöpnummer inom perioden. Börjar på 1 i varje ny fil. Sätts av journalen, aldrig av anroparen, och först när händelsen faktiskt accepterats.
event_typesträngHändelsetypen, till exempel "LINE_ADDED".
timestampsträngLokal tid utan tidszon, sekundprecision: "2026-10-06T16:59:26". Sätts vid själva skrivningen, inte när försöket påbörjades.

Resten av fälten är händelsens egna och ligger på samma nivå. Deras ordning är inte fast - de byggs ur en HashMap - så en läsare får aldrig förlita sig på fältordning. Belopp är heltal i öre (fält som slutar på _minor), momssatser är bråk (0.25, inte 25).

Det finns inget gemensamt kvittonummerfält. Händelser som hör till ett visst kvitto bär receipt_number, document_number eller hänger ihop genom ordningen i filen - se varje händelsetyp.

Ordning och perioder

En läsare ska läsa filerna i periodordning och raderna i filordning. Ordningen i filen är den ordning händelserna skrevs; event_id stiger normalt med den. Ett kvitto är inte en egen händelse utan ett förlopp:

LINE_ADDED            en rad slogs in           (event_id 7)
LINE_ADDED                                      (event_id 8)
LINE_CORRECTED        rad 7 makulerades
PAYMENT               betalning, receipt_number 35
CONTROL_UNIT_VERIFICATION   kontrollenhetens svar för 35
RECEIPT_LAYOUT        kvittot som det ser ut, document_number 35
RECEIPT_DISTRIBUTION  papper eller elektroniskt

Alla LINE_ADDED sedan förra PAYMENT hör till kvittot som den PAYMENT stänger. RECEIPT_DISTRIBUTION skrivs när kvittot lämnar “slussen” - när operatören trycker KVITTO (papper) eller går vidare till nästa kund (elektroniskt) - och kan därför komma en stund senare. Den saknar kvittonummer och gäller det senast betalda kvittot.

En dagsavslutning ser ut så här:

CARD_TERMINAL_REPORT     kortterminalens egen Z (om terminal finns)
Z_REPORT_PRINT_ATTEMPT   ett eller flera utskriftsförsök
Z_REPORT                 sista raden i filen
--- ny fil ---
BOOKKEEPING_VOUCHER      första raden i nästa period (om bokföring är på)

Verifikationen för Z-rapport N hamnar alltså som event_id 1 i fil N+1, eftersom den byggs efter att Z_REPORT redan rullat över journalen.

Kön (se nästa avsnitt) gör att ordningen i filen och event_id alltid följs åt: en köad händelse skrivs före allt som kommer efter den.

Hur inget går förlorat

Skrivningen är enkel: för varje händelse öppnas periodfilen i tilläggsläge, raden skrivs med en avslutande radbrytning, och filen tvingas ut till disken (sync_data) innan den stängs. Först när det lyckats räknas skrivningen som gjord. En journalrad som ett strömavbrott kan ta tillbaka är inte en fiskal registrering.

Utöver det skyddar ordningen och beslutsträdet i journal.rs:

  1. Försök skriva. Finns köade händelser (se punkt 3) skrivs de först, äldst först. Därefter skrivs den nya händelsen. Lyckas det får händelsen sitt event_id och sin tid, och räknas som inträffad. Går kön inte att skriva skrivs inte heller den nya händelsen; den går då samma väg som ett misslyckat försök.
  2. Misslyckas det frågar kassan operatören “Försök igen?”. Ja gör om försöket, så länge det behövs.
  3. Nej leder till frågan om händelsen ska köas. Ja lägger den i en kö i minnet (högst 10, MAX_QUEUE_SIZE), ger den ett event_id och låter kassan fortsätta som om den skrivits. Nej ångrar hela handlingen: raden slås inte in, betalningen sker inte, kvittonumret lämnas tillbaka. Är kön full stoppar kassan (HardStop).

Kön finns bara i minnet. Den töms vid nästa skrivning: varje gång journalen ska skriva något - ett nytt record() eller ett “försök igen” (retry()) - skrivs först alla köade händelser, i event_id-ordning. Filen innehåller därför händelserna i event_id-ordning utan luckor, och ingenting nytt hamnar i filen före en äldre köad händelse. Det som fortfarande kan gå förlorat är en kö som aldrig hann skrivas: stängs kassan medan händelser ligger i kön försvinner de, och deras event_id blir en lucka i slutet av filen.

Not för äldre journaler: Före 2026-10-20 skrevs kön aldrig ut. En köad händelse försvann när kassan stängdes, och händelser som skrevs efter den fick högre event_id. Journaler från den tiden kan alltså ha luckor i event_id mitt i en fil.

Detta gäller de styrande händelserna, där kassans tillstånd ändras först efter en lyckad skrivning: LINE_ADDED, LINE_CORRECTED, PAYMENT, PRODUCT_CREATED, X_REPORT, Z_REPORT, JOURNAL_PRINT_ATTEMPT och CHANGE_FUND_REGISTERED. Övriga händelser är observerande: de skrivs med ett enkelt försök, och skrivs inte alls om journalen redan väntar på ett beslut om en annan händelse.

Kontrollenheten är den andra spärren. PAYMENT skrivs innan kontrollenheten tillfrågas, men kvittot byggs, journalförs (RECEIPT_LAYOUT) och skrivs ut först när kontrollenheten svarat ja. Svarar den inte får operatören försöka igen; varje försök blir en egen CONTROL_UNIT_VERIFICATION. Ger operatören upp lämnas kvittonumret tillbaka och inget kvitto skapas - men PAYMENT ligger kvar i journalen, eftersom journalen bara växer. En läsare som vill veta om en betalning blev ett kvitto måste titta efter en CONTROL_UNIT_VERIFICATION med "success": true för samma receipt_number.

När kassan startar läser den igenom den aktuella periodfilen (rebuild_period_state_from_journal) och räknar som kassan gör under drift. Raderna (LINE_ADDED, med LINE_CORRECTED tillämpade) samlas tills ett kvitto godkänts: en PAYMENT följd av en CONTROL_UNIT_VERIFICATION för samma receipt_number med "success": true. Först då läggs kvittot till periodens momsackumulatorer, antal kvitton, sålda varor och tjänster och försäljningen per betalsätt (method). Ett kortköp som varken kunde verifieras eller återföras (felet börjar med "card charge could not be voided", se CONTROL_UNIT_VERIFICATION) räknas också, eftersom pengarna är dragna. Ett avvisat kvitto räknas inte; dess rader står kvar som ett öppet kvitto och räknas med den betalning som till slut godkänns. Innehåller filen ingen CONTROL_UNIT_VERIFICATION alls (journaler från innan händelsen fanns) räknas varje PAYMENT som ett kvitto.

Växelkassan återställs ur den senaste CHANGE_FUND_REGISTERED i filen. En rad som inte går att tolka - till exempel en halvskriven sista rad efter ett strömavbrott - hoppas över, och numreringen fortsätter efter det högsta event_id som finns i filen.

Läsarna

läsareläserhur
X/Z-rapport (till.rs, report.rs)indirektRapporterna räknar ur kassans ackumulatorer i minnet, som byggs upp ur journalen vid start (se ovan). Grand total kommer ur counters.db.
Journalutskrift (receipt_layout/journal.rs)alla händelser i perioderna från-tillVarje rad pretty-printas som JSON med två blankstegs indrag, direkt ur råtexten så att fältordningen bevaras, en utskriftsrad per textrad. Inget tolkas, ingenting märks om. Trasiga rader hoppas över.
SKVFS-export (skvfs_xml_export.rs)period 1 till aktuellBygger kvitton ur LINE_ADDED (+ äldre DISCOUNT_APPLIED) fram till PAYMENT, med LINE_CORRECTED tillämpade på samma sätt som vid omstarten (en borttagen rad försvinner, new_amount_minor ersätter radens belopp). Kompletterar med CONTROL_UNIT_VERIFICATION och RECEIPT_DISTRIBUTION, tar med X_REPORT/Z_REPORT och hämtar kontrollenhetens tillverkningsnummer ur den senaste CONTROL_UNIT_IDENTITY. Allt annat exporteras inte, enligt SKVFS 2021:16 3 kap. 3 §. Startas med koden 2027 + SUBTOTAL utanför ett kvitto.
Kontoplanens förhandsvisning (web_setup/konton.rs)senaste Z_REPORTBygger om rapportdata ur sales_by_group_and_rate för att visa hur kontona skulle bli.
Bokföring-Verifikationen byggs ur Z-rapportens data i minnet och journalförs som BOOKKEEPING_VOUCHER; den läses inte tillbaka ur journalen. R-GO Fortnox läser exportarkivet, inte journalen.

Händelsetyperna

JournalEventType i journal.rs har 18 typer. 16 av dem skrivs av kassan; RECEIPT_OPENED och RECEIPT_CLOSED finns i typen men skrivs aldrig. Dessutom förekommer tre typer i äldre journaler från Python-versionen, se sist.

händelseskrivs närstyrande
LINE_ADDEDen rad slås inja
LINE_CORRECTEDen rad makulerasja
PAYMENTett kvitto betalasja
CONTROL_UNIT_VERIFICATIONkontrollenheten svarar på en betalningnej
RECEIPT_LAYOUTett kvitto godkänts av kontrollenhetennej
RECEIPT_DISTRIBUTIONkvittot lämnar slussennej
X_REPORTX-rapport tasja
X_REPORT_PRINT_ATTEMPTefter X-rapportennej
Z_REPORTZ-rapport tas, rullar över periodenja
Z_REPORT_PRINT_ATTEMPTvarje utskriftsförsök av Z-rapportennej
JOURNAL_PRINT_ATTEMPTjournalutskriftja
CONTROL_UNIT_IDENTITYkassan startarnej
BOOKKEEPING_VOUCHERefter Z-rapporten, om bokföring är pånej
CARD_TERMINAL_REPORTkortterminalens X/Z/avstämningnej
PRODUCT_CREATEDen okänd streckkod läggs upp som varaja
CHANGE_FUND_REGISTEREDväxelkassan registrerasja
RECEIPT_OPENEDaldrig-
RECEIPT_CLOSEDaldrig-

Exemplen nedan är riktiga rader ur testjournaler, med namn, organisationsnummer, serienummer, kontrollkoder och terminal-id utbytta mot platshållare. Exemplen för RECEIPT_LAYOUT och *_PRINT_ATTEMPT är dessutom omskrivna till layoutens nuvarande form.

LINE_ADDED

En rad slogs in - antingen ett belopp på en varugruppstangent eller en vara från varuregistret via streckkod. En tryckning, en händelse.

fälttypbetydelse
product_group_numberheltalVarugruppen.
product_group_namesträngVarugruppens namn vid en gruppförsäljning; vid streckkodsförsäljning artikelns namn (fältnamnet är historiskt).
amount_minorheltalRadens totalbelopp i öre (antal × styckpris).
quantityheltalAntal. Finns bara när det inte är 1; saknas det är antalet 1.
unit_price_minorheltalStyckpris i öre. Finns bara tillsammans med quantity; saknas det är styckpriset lika med amount_minor.
vat_ratetalMomssatsen vid försäljningen, som bråk. Sparas så att en senare ändring av varugruppen inte ändrar historiken.
is_serviceboolOm gruppen räknas som tjänst (för X/Z:s antal varor och tjänster).
skusträngArtikelns identitet. Bara vid streckkodsförsäljning.
barcodesträngArtikelns första registrerade streckkod. Bara vid streckkodsförsäljning, och bara om artikeln har en.

Radens event_id är radens identitet: det är vad LINE_CORRECTED pekar på och vad displayen kallar row_id.

{"event_id":7,"event_type":"LINE_ADDED","timestamp":"2026-10-06T16:59:26","product_group_number":2,"product_group_name":"Grupp 2","vat_rate":0.25,"is_service":false,"amount_minor":12400}
{"event_id":83,"event_type":"LINE_ADDED","timestamp":"2026-10-13T10:25:25","unit_price_minor":4500,"product_group_number":6,"product_group_name":"Grupp 6","vat_rate":0.25,"is_service":false,"amount_minor":18000,"quantity":4}
{"event_id":135,"event_type":"LINE_ADDED","timestamp":"2026-09-29T17:31:39","amount_minor":20000,"product_group_number":1,"product_group_name":"Grupp 1","is_service":false,"sku":"1234567890999","vat_rate":0.25,"barcode":"1234567890999"}

LINE_CORRECTED

En tidigare inslagen rad på det pågående kvittot ändras eller tas bort. Den ursprungliga LINE_ADDED står kvar; det här är ett tillägg om den.

fälttypbetydelse
corrects_event_idheltalevent_id för den LINE_ADDED som rättas, i samma period.
removedbooltrue: raden är makulerad.
new_amount_minorheltalNytt belopp i öre för raden.

Händelsen har antingen removed eller new_amount_minor. I praktiken skrivs bara makuleringar: BACKA tar bort den markerade raden, eller den n:e raden bakifrån. Funktionen som skriver new_amount_minor (Till::correct_line) finns, och både omstartsåterskapandet och SKVFS-exporten läser fältet, men den anropas inte från något tangentflöde i dag. Makulering är bara möjlig innan betalningen påbörjats.

{"event_id":143,"event_type":"LINE_CORRECTED","timestamp":"2026-10-19T17:15:48","removed":true,"corrects_event_id":140}

PAYMENT

Kvittot betalas och får sitt kvittonummer. Stänger kvittot: alla LINE_ADDED sedan förra PAYMENT hör hit. Ett kvitto betalas alltid med ett betalsätt och en PAYMENT - delbetalning finns inte, bland annat för att SKVFS-exporten och periodåterskapandet antar en PAYMENT per kvitto.

fälttypbetydelse
methodsträngBetalsättets fasta kod: CASH, CARD, SWISH, INVOICE eller CREDIT (tillgodo). Översätts aldrig; visningsnamnet (KONTANT osv.) står bara på kvittot.
amount_minorheltalBetalt belopp i öre (kvittots återstående belopp).
tendered_minorheltal eller nullMottaget belopp vid kontant när operatören slagit in det; annars null. Alla andra betalsätt har null.
receipt_numberheltalKvittonumret (löpnummer ur counters.db, taget innan händelsen skrivs).

Vid kortbetalning har kortterminalen redan dragit pengarna när PAYMENT skrivs; kortets uppgifter journalförs inte som egna fält utan finns som textrader i RECEIPT_LAYOUT. INVOICE fakturerar inte - kassan lämnar ett underlag och skriver en följesedel.

Se avsnittet om kontrollenheten ovan: en PAYMENT som aldrig fick ett godkännande ligger kvar, och dess kvittonummer kan återanvändas av nästa betalning.

{"event_id":8,"event_type":"PAYMENT","timestamp":"2026-10-06T16:59:28","tendered_minor":null,"amount_minor":12400,"method":"CASH","receipt_number":35}
{"event_id":66,"event_type":"PAYMENT","timestamp":"2026-10-13T10:23:07","receipt_number":5,"amount_minor":56900,"method":"SWISH","tendered_minor":null}

CONTROL_UNIT_VERIFICATION

Kontrollenhetens (CleanCash) svar på en betalning. En per försök - blir det omförsök finns flera för samma kvitto, och det är den sista som gäller.

fälttypbetydelse
receipt_numberheltalKvittot det gäller.
successboolOm kontrollenheten godkände kvittot.
control_unit_idsträngDet id kontrollenheten själv skickar i svaret, om något; ofta tom sträng. Bara vid success: true.
control_codesträngKontrollkoden som skrivs på kvittot (59 tecken från en riktig CleanCash: 32 + ; + 26). Bara vid success: true.
errorsträngFelet. Bara vid success: false.

Ett specialfall: om operatören ger upp efter ett kortköp och kortköpet inte går att återkalla i terminalen, skrivs en extra CONTROL_UNIT_VERIFICATION med success: false och felet "card charge could not be voided after control unit rejection: ...". Försäljningen räknas då som gjord men inget kvitto skapas. Omstarten känner igen felet och räknar försäljningen på samma sätt.

{"event_id":9,"event_type":"CONTROL_UNIT_VERIFICATION","timestamp":"2026-10-06T16:59:28","success":true,"receipt_number":35,"control_code":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA;BBBBBBBBBBBBBBBBBBBBBBBBBB","control_unit_id":""}
{"event_id":21,"event_type":"CONTROL_UNIT_VERIFICATION","timestamp":"2026-09-03T18:59:41","success":false,"error":"RH NAK: 003","receipt_number":15}

RECEIPT_LAYOUT

Kvittot som det ser ut - samma layout som skickas till skrivaren, inklusive loggans bildrad. Skrivs direkt efter ett godkännande från kontrollenheten, innan kvittot hamnar i slussen. Gör att journalen innehåller vad kunden faktiskt fick, inte bara siffrorna bakom.

fälttypbetydelse
receipt_widthsträng"flexible".
rowslistaKvittots rader, se nedan.
document_numberheltalKvittonumret, samma som PAYMENT:s receipt_number.
footerlista av strängarKvittots fotrader. Saknas när kvittot inte har några.

Fälten är layoutens egen JSON-form, exakt samma som kroppen till POST /print_layout i utskriftsformatet, bara utplattad bland händelsens övriga fält. Allt som står där gäller också här: tomma fält utelämnas (en enkolumnsrad har varken tabs_percent eller alignments, en flerkolumnsrad saknar align), och column_styles och footer följer med. En kolumnstil som det dubbelt breda beloppet på SUBTOTAL syns alltså i journalen. row_id sätts aldrig på ett utskrivet kvitto och förekommer därför inte.

{"event_id":94,"event_type":"RECEIPT_LAYOUT","timestamp":"2026-10-14T16:09:53","receipt_width":"flexible","document_number":9,"rows":[{"type":"image","source":"logo","align":"center","width_percent":60.0},{"type":"text","style":"dbl_size","columns":["KVITTO"],"align":"center"},{"type":"text","style":"normal","columns":["Kvitto nr 9"],"align":"center"},{"type":"text","style":"normal","columns":["Grupp 6","12:00"],"tabs_percent":[0.0,85.0],"alignments":["left","right"]},{"type":"text","style":"bold_total","columns":["SUBTOTAL","12:00"],"tabs_percent":[0.0,85.0],"alignments":["left","right"],"column_styles":[null,"dbl_width"]}],"footer":["Tack för ditt köp!"]}

(Förkortad: en riktig rad har alla kvittots rader, med företagsuppgifter, moms, betalning och kontrollkod.)

Not för äldre journaler: Före 2026-10-20 byggdes journalens form för hand. Där har varje textrad alltid align, tabs_percent och alignments (align är null i flerkolumnsform och tomma listor skrivs som []), medan column_styles, row_id och footer saknas. En läsare som ska klara båda formerna kan behandla null, [] och ett saknat fält som samma sak.

RECEIPT_DISTRIBUTION

Hur det betalda kvittot lämnade slussen. Lagen kräver att ett kvitto skapas, inte att det skrivs ut.

fälttypbetydelse
statussträng"PAPER" - operatören tryckte KVITTO (eller kassan är inställd på att alltid skriva ut). "ELECTRONIC" - operatören gick vidare utan att begära papper.
destinationsträngBara vid ELECTRONIC: vart kvittot gick. I dag alltid "grön papperskorg", en mottagare som tar emot men inte sparar.

Händelsen saknar kvittonummer och gäller det senast betalda kvittot. PAPER betyder att utskrift valdes; om skrivaren sedan lyckades journalförs inte. Ett kvitto som ligger i slussen när kassan stängs får ingen RECEIPT_DISTRIBUTION.

{"event_id":11,"event_type":"RECEIPT_DISTRIBUTION","timestamp":"2026-10-06T16:59:31","status":"PAPER"}
{"event_id":6,"event_type":"RECEIPT_DISTRIBUTION","timestamp":"2026-10-05T12:32:50","destination":"grön papperskorg","status":"ELECTRONIC"}

X_REPORT och Z_REPORT

Rapportens siffror, med samma fält för X och Z. Typen avgör vilken det är. X_REPORT ändrar ingenting; Z_REPORT avslutar perioden, och att den skrivs är det som rullar över journalen till nästa fil. Fälten motsvarar SKVFS 2014:9 7 kap. 2 § (bokstäverna a-v), plus sales_by_group_and_rate för bokföringen.

fälttypbetydelse
company_namesträngFöretagets namn (a).
organisation_numbersträngOrganisationsnummer med bindestreck (a).
produced_atsträngNär rapporten togs fram, samma form som timestamp (b).
register_designationsträngKassabeteckning (d).
total_sales_minorheltalPeriodens försäljning (e).
total_sales_by_main_group_minorobjektPer huvudgrupp (f). Huvudgrupper finns inte - alltid {}.
vat_by_ratelistaMoms per sats (g): {"vat_rate": tal, "vat_minor": heltal, "net_minor": heltal}.
sales_by_group_and_ratelistaPer varugrupp och momssats: {"group_number", "vat_rate", "debit_gross_minor", "debit_vat_minor", "credit_gross_minor", "credit_vat_minor"}. Inget SKVFS-fält; behövs för att kunna återskapa en verifikation ur journalen. Kreditsidan är alltid 0 eftersom returer inte finns.
change_fund_minorheltalVäxelkassa (h); 0 om ingen registrerats.
items_sold_countheltalAntal sålda varor (i) - summa av antal, inte antal rader.
services_sold_countheltalAntal sålda tjänster (j).
receipt_countheltalAntal kvitton (k).
drawer_openings_countheltalLådöppningar (l). Alltid 0.
receipt_copies_count, receipt_copies_total_minorheltalKvittokopior (m). Alltid 0.
training_mode_receipts_count, training_mode_total_minorheltalÖvningsläge (n). Alltid 0.
sales_by_payment_method_minorobjektPer betalsätt (o), nyckel = method-kod: {"CASH": 66600}.
return_count, return_total_minorheltalReturer (p). Alltid 0.
discount_total_minorheltalRabatter (q). Alltid 0.
other_reduction_count, other_reduction_total_minorheltalÖvriga minskningar (r). Alltid 0.
unfinished_sale_count, unfinished_sale_total_minorheltalOavslutade försäljningar (s). Alltid 0.
grand_total_sales_minorheltalGrand total försäljning (t), ur counters.db plus periodens.
grand_total_returns_minorheltalGrand total retur (u).
grand_total_net_minorheltalGrand total netto (v).
z_report_sequence_numberheltal eller nullZ-rapportens löpnummer; null på X.
control_unit_serial_numbersträngKontrollenhetens tillverkningsnummer; tom på X.
control_unit_modelsträngKontrollenhetens modell; tom på X.
control_unit_versionsträngKontrollenhetens version; tom på X.

Fält som anges “alltid 0” finns för att formatet ska vara komplett; kassan har ännu ingen funktion som räknar dem.

{"event_id":18,"event_type":"Z_REPORT","timestamp":"2026-10-06T16:49:30","total_sales_by_main_group_minor":{},"register_designation":"Kassa 1","control_unit_version":"1.1","other_reduction_count":0,"grand_total_sales_minor":1155310,"produced_at":"2026-10-06T16:49:30","training_mode_total_minor":0,"receipt_count":2,"z_report_sequence_number":11,"services_sold_count":0,"drawer_openings_count":0,"control_unit_serial_number":"EXEMPEL00000000001","other_reduction_total_minor":0,"sales_by_payment_method_minor":{"CASH":66600},"discount_total_minor":0,"receipt_copies_total_minor":0,"unfinished_sale_count":0,"training_mode_receipts_count":0,"change_fund_minor":0,"return_count":0,"organisation_number":"556000-0000","receipt_copies_count":0,"total_sales_minor":66600,"unfinished_sale_total_minor":0,"vat_by_rate":[{"vat_rate":0.25,"vat_minor":13320,"net_minor":53280}],"grand_total_returns_minor":0,"items_sold_count":3,"sales_by_group_and_rate":[{"group_number":2,"vat_rate":0.25,"debit_gross_minor":11100,"debit_vat_minor":2220,"credit_gross_minor":0,"credit_vat_minor":0},{"group_number":4,"vat_rate":0.25,"debit_gross_minor":55500,"debit_vat_minor":11100,"credit_gross_minor":0,"credit_vat_minor":0}],"control_unit_model":"Exempelmodell","grand_total_net_minor":1155310,"return_total_minor":0,"company_name":"Exempelbutiken AB"}

En X_REPORT har samma fält med "z_report_sequence_number":null och tomma kontrollenhetsfält.

X_REPORT_PRINT_ATTEMPT

Skrivs efter X_REPORT, med rapporten som den ritades. X-rapporten har ingen omförsöksdialog: ett försök, utfallet journalförs.

fälttypbetydelse
receipt_width, rows, document_numberRapportens layout, i samma form som RECEIPT_LAYOUT. document_number är 0.
successboolOm utskriftsförsöket lyckades.

Rapporter och journalutskrifter skrivs ut genom kassans PrintSink (receipt_sink.rs). När en skrivartjänst är inställd (config/printer_service.json) sätter både web_till och interactive dess layoutskrivare till receiptprinter (till::layout_printer_via_http): layouten skickas med POST /print_layout och avslutas med POST /feed_cut. success speglar alltså den riktiga utskriften: true när receiptprinter svarat 2xx på /print_layout. En misslyckad matning efteråt loggas men ändrar inte utfallet. Utan inställd skrivartjänst skriver PrintSink texten till kassans standardutdata, och då lyckas försöket nästan alltid. Samma sak gäller Z_REPORT_PRINT_ATTEMPT och JOURNAL_PRINT_ATTEMPT.

Not för äldre journaler: Före 2026-10-20 gällde success alltid den interna textutskriften till standardutdata, och den riktiga utskriften till receiptprinter gjordes efteråt utan att journalföras. I sådana journaler säger success: true alltså inget om papperet.

{"event_id":6,"event_type":"X_REPORT_PRINT_ATTEMPT","timestamp":"2026-10-06T16:52:12","receipt_width":"flexible","document_number":0,"success":true,"rows":[{"type":"text","style":"header","columns":["X-DAGRAPPORT"],"align":"center"}]}

(Förkortad.)

Z_REPORT_PRINT_ATTEMPT

Ett per utskriftsförsök av Z-rapporten. Z-rapporten måste skrivas ut innan Z_REPORT journalförs. Svarar skrivaren inte frågar kassan “Z-rapport: försök skriva ut igen?” och väntar på Ja eller Nej; varje försök journalförs. Ger operatören upp (Nej) skrivs ingen Z_REPORT och perioden förblir öppen - men Z-numret är redan taget och lämnas inte tillbaka, så löpnumret får en lucka, och därmed också journalfilernas numrering (se Filformat). Det är avsiktligt: luckan är spåret av ett övergivet försök.

fälttypbetydelse
receipt_width, rows, document_numberRapportens layout, i samma form som RECEIPT_LAYOUT. document_number är 0.
z_report_numberheltalZ-numret försöket gäller.
successboolOm försöket lyckades (se X_REPORT_PRINT_ATTEMPT om vad som räknas).

Om just den här loggningen inte kan skrivas överges den tyst, så att journalen är fri för den Z_REPORT som måste skrivas.

{"event_id":17,"event_type":"Z_REPORT_PRINT_ATTEMPT","timestamp":"2026-10-06T16:49:30","z_report_number":11,"document_number":0,"receipt_width":"flexible","success":true,"rows":[{"type":"text","style":"header","columns":["Z-DAGRAPPORT"],"align":"center"},{"type":"text","style":"normal","columns":["Löpnummer","11"],"tabs_percent":[0.0,50.0],"alignments":["left","left"]}]}

(Förkortad.)

JOURNAL_PRINT_ATTEMPT

Att journalen skrevs ut. Bara faktumet loggas, inte innehållet - det finns ju redan i journalen. Utskriften görs utan siffror för den aktuella perioden, eller med två Z-nummer för ett intervall.

fälttypbetydelse
from_zheltalFörsta perioden.
to_zheltalSista perioden (högst den aktuella).
successboolOm utskriften lyckades (se X_REPORT_PRINT_ATTEMPT om vad som räknas).

Händelsen är styrande: kan den inte skrivas får operatören samma omförsöksdialog som för en betalning, eftersom en journalutskrift i sig är ett spår av att journalen lästes.

{"event_id":3,"event_type":"JOURNAL_PRINT_ATTEMPT","timestamp":"2026-09-03T15:19:03","from_z":1,"to_z":3,"success":true}

CONTROL_UNIT_IDENTITY

Kontrollenhetens identitet, frågad vid varje start av kassan (CCSP IQ/IR), lyckad eller inte. SKVFS-exporten tar tillverkningsnumret ur den senaste lyckade, och vägrar exportera om ingen finns.

fälttypbetydelse
successboolOm enheten svarade.
manufacturing_idsträngTillverkningsnummer. Bara vid success: true.
protocol_versionsträngCCSP-protokollversion. Bara vid success: true.
firmware_versionsträngFirmware. Bara vid success: true.
errorsträngFelet. Bara vid success: false.
{"event_id":17,"event_type":"CONTROL_UNIT_IDENTITY","timestamp":"2026-10-06T17:01:02","success":true,"manufacturing_id":"EXEMPEL00000000001","firmware_version":"R1.07 L1","protocol_version":"2"}
{"event_id":96,"event_type":"CONTROL_UNIT_IDENTITY","timestamp":"2026-10-19T16:55:30","success":false,"error":"connect ws://127.0.0.1:8765/ws: Connection refused (os error 111)"}

BOOKKEEPING_VOUCHER

Verifikationen för bokföringen, byggd ur en Z-rapport. Själva verifikationen journalförs, inte bara filnamnet - filer kan tas bort, journalen finns kvar. Skrivs bara när kontoplanen är påslagen, och hamnar först i nästa periods fil.

fälttypbetydelse
successbooltrue om verifikationen byggdes och SIE-filen skrevs.
seriessträngVerifikationsserie från kontoplanen, t.ex. "Z".
numberheltalVerifikationsnummer = Z-rapportens löpnummer.
datesträngYYYYMMDD, ur rapportens produced_at.
textsträng"Dagsrapport Z<nr>, <kassabeteckning>", med (DIFFERENS) om den inte balanserade.
imbalance_minorheltalDebet minus kredit innan differensraden lades till; 0 om den balanserade av sig själv.
sie_filesträngSIE-filens namn i journalkatalogen, t.ex. "voucher_z000011.si".
lineslistaKonteringsraderna: {"account": sträng, "debit_minor": heltal, "credit_minor": heltal, "text": sträng}.
errorsträngFinns när något gick fel.

Två former: kunde verifikationen inte byggas alls (t.ex. ett fel i kontoplanen) innehåller händelsen bara "success": false och error. Byggdes den men SIE-filen kunde inte skrivas finns alla fält, med "success": false och error. Z-rapporten fälls aldrig av ett fel här.

{"event_id":1,"event_type":"BOOKKEEPING_VOUCHER","timestamp":"2026-10-06T16:49:30","date":"20261006","series":"Z","imbalance_minor":0,"lines":[{"account":"1910","debit_minor":66600,"credit_minor":0,"text":"Försäljning KONTANT"},{"account":"2611","debit_minor":0,"credit_minor":13320,"text":"Utgående moms 25 %"},{"account":"3001","debit_minor":0,"credit_minor":53280,"text":"Försäljning 25 %"}],"text":"Dagsrapport Z11, Kassa 1","number":11,"sie_file":"voucher_z000011.si","success":true}

CARD_TERMINAL_REPORT

En rapport som körts i kortterminalen: dess X, dess Z eller en avstämning mot banken. Terminalens siffror sparas separat från kassans så att de kan jämföras. Kassans X kör terminalens X först, kassans Z kör terminalens Z först (innan något fiskalt börjar), och avstämningen har en egen tangent. En kassa utan kortterminal journalför ingenting här.

fälttypbetydelse
kindsträng"X", "Z" eller "RECONCILIATION".
successboolOm terminalen levererade rapporten.
sitesträngTerminalens butiks-id. Vid success: true.
sessionsträngTerminalens period/session. Vid success: true.
terminal_timestampsträngTerminalens egen tid, som terminalen skickar den (YYYYMMDDHHMMSS i exemplen). Vid success: true.
issuerslistaPer kortutgivare: {"issuer_id": sträng, "transaction_count": heltal, "total_minor": heltal, "cash_minor": heltal, "deposit_minor": heltal}. cash_minor och deposit_minor ingår redan i total_minor.
print_lineslista av strängarTerminalens egen utskrift, rad för rad, oförändrad.
infosträngFritext från värden. Bara när terminalen skickade någon.
errorsträngFelet. Bara vid success: false.
{"event_id":273,"event_type":"CARD_TERMINAL_REPORT","timestamp":"2026-10-02T17:30:42","kind":"RECONCILIATION","site":"000000","terminal_timestamp":"20261002173041","issuers":[{"issuer_id":"05","transaction_count":0,"total_minor":0,"cash_minor":0,"deposit_minor":0},{"issuer_id":"41","transaction_count":9,"total_minor":118700,"cash_minor":0,"deposit_minor":0}],"success":true,"print_lines":["TERM: 00000000-000000","2026-10-02 17:30","VALUTA: SEK","AVST. PERIOD: 439","","DAGSAVSLUT","","STANDARD               9","BELOPP=          1187,00"],"session":"439"}
{"event_id":4,"event_type":"CARD_TERMINAL_REPORT","timestamp":"2026-10-06T16:52:12","error":"kortterminalen är inte ansluten","success":false,"kind":"X"}

(Den första raden är förkortad: en utgivare och terminalens sista utskriftsrader är borttagna.)

PRODUCT_CREATED

En okänd streckkod lästes in och operatören lade upp den som vara (pris och varugrupp). Varuregistret i SQLite får ändras; journalen är beviset på att en vara skapades och hur den såg ut då. Skrivs omedelbart före den LINE_ADDED som säljer varan.

fälttypbetydelse
skusträngArtikelns identitet - den lästa koden.
barcodesträngDen lästa streckkoden.
product_group_numberheltalVarugruppen.
namesträngVarans namn, ärvt från varugruppen.
sale_price_minorheltalFörsäljningspris i öre.
purchase_price_minorheltalInköpspris i öre, räknat ur gruppens marginal.
margin_percenttalMarginal i procent.
vat_ratetalMomssats som bråk.
discountableboolOm varan får rabatteras.

Bara uppläggning från kassan skrivs. Varor som skapas, ändras eller tas bort i inställningsprogrammet (web_setup, produkter.rs) skriver ingen journalhändelse alls - trots att kommentaren vid ProductCreated i journal.rs säger att varje ändring i artikelregistret ska journalföras.

{"event_id":134,"event_type":"PRODUCT_CREATED","timestamp":"2026-09-29T17:31:39","purchase_price_minor":15385,"barcode":"1234567890999","sku":"1234567890999","margin_percent":30.0,"sale_price_minor":20000,"vat_rate":0.25,"discountable":true,"name":"Grupp 1","product_group_number":1}

CHANGE_FUND_REGISTERED

Växelkassan för perioden. Får registreras om fritt fram till periodens första köp, sedan är den låst tills nästa Z-rapport.

fälttypbetydelse
change_fund_minorheltalVäxelkassan i öre.
replaced_minorheltalDet tidigare registrerade beloppet, när en registrering ersätter en annan i samma period.

Händelsen är styrande: kassan sätter växelkassan i minnet först när händelsen skrivits. Misslyckas skrivningen får operatören samma omförsöksdialog som för en betalning, och lyckas ett senare “försök igen” (eller köas händelsen) registreras beloppet då. Vid omstart återställs växelkassan ur den senaste CHANGE_FUND_REGISTERED i periodens fil.

{"event_id":2,"event_type":"CHANGE_FUND_REGISTERED","timestamp":"2026-10-06T16:51:51","change_fund_minor":111100}
{"event_id":3,"event_type":"CHANGE_FUND_REGISTERED","timestamp":"2026-10-06T16:52:08","replaced_minor":111100,"change_fund_minor":123400}

RECEIPT_OPENED och RECEIPT_CLOSED

Finns i JournalEventType men skrivs aldrig. Ett kvittos början och slut framgår av LINE_ADDED och PAYMENT.

Äldre typer: DISCOUNT_APPLIED, X_REPORT_LAYOUT, Z_REPORT_LAYOUT

Journaler från den tidigare Python-versionen (ecrlogik/journal.py) kan innehålla tre typer som Rust-kassan inte skriver. De syns bland annat i testjournaler/.

  • DISCOUNT_APPLIED - en procentrabatt på en rad: corrects_event_id (radens LINE_ADDED), percent, discount_minor. SKVFS-exporten läser den fortfarande och lägger rabatten på raden. Rabatt finns inte i Rust-kassan.
  • X_REPORT_LAYOUT, Z_REPORT_LAYOUT - rapportens layout (receipt_width, rows), ersatta av layouten i *_PRINT_ATTEMPT.

Dessa journaler skrevs också med blanksteg efter : och ,; läsare ska inte förutsätta kompakt JSON.

Värt att veta för en läsare

  • Värdehandlingar journalförs inte som egna händelser. Utfärdade och inlösta tillgodokvitton finns i values.db. I journalen syns en inlösen bara som PAYMENT med "method": "CREDIT".
  • Kunddisplayen och displaytillståndet journalförs inte.
  • Omstart läser den öppna periodens fil. Momsackumulatorerna, antalen, försäljningen per betalsätt och växelkassan återskapas ur den nyaste journalfilen om den inte är stängd med en Z_REPORT, annars ur filen för next_z_report_number (journal::open_period_number). Efter ett avbrutet Z-försök har next_z_report_number redan gått förbi den öppna periodens fil; då fortsätter kassan i den öppna filen.
  • Ett öppet kvitto kommer tillbaka vid omstart. LINE_ADDED efter det senaste godkända kvittot, med LINE_CORRECTED tillämpade, läggs tillbaka på kvittot med sina event_id. Raderna står alltså kvar i kassan och kommer med på nästa PAYMENT - samma kvitto som omstarten och SKVFS-exporten bygger ur journalen. Före 2026-10-21 försvann raderna ur kassan men räknades ändå in i nästa betalda kvitto vid omstart.
  • Elektroniska kvitton i SKVFS-exporten. Exporten markerar ett kvitto som elektroniskt (ElektronisktKassakvitto) när RECEIPT_DISTRIBUTION har status "ELECTRONIC", eller ett värde som börjar på elektron i äldre journaler.