From 1a0922b778b7997429397e2218ad1545d8c00c00 Mon Sep 17 00:00:00 2001
From: Nicolaj Hartmann
Date: Fri, 6 Feb 2026 01:15:45 +0100
Subject: [PATCH] Audit v3: VAT alignment, security, encoding, UX, compliance
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
VAT System Alignment (LEGAL - Critical):
- Align frontend VAT codes with backend (S25→U25, K25→I25, etc.)
- Add missing codes: UEU, IVV, IVY, REP
- Fix output VAT account 5710→5611 to match StandardDanishAccounts
- Invoice posting now checks fiscal year status before allowing send
- Disallow custom invoice number override (always use auto-numbering)
Security:
- Fix open redirect in AuthController (validate returnUrl is local)
- Store seller CVR/name/address on invoice events (Momsloven §52)
Backend Compliance:
- Add description validation at posting (Bogføringsloven §7)
- SAF-T: add DefaultCurrencyCode, TaxAccountingBasis to header
- SAF-T: add TaxTable to MasterFiles with all VAT codes
- SAF-T: always write balance elements even when zero
- Add financial income account 9100 Renteindtægter
Danish Encoding (~25 fixes):
- Kassekladde: Bogført, Bogføring, Vælg, være, på, Tilføj, Differens
- AttachmentUpload: træk, Understøtter, påkrævet, Bogføringsloven
- keyboardShortcuts: Bogfør, Bogføring display name
- ShortcutsHelpModal: åbne
- DataTable: Genindlæs
- documentProcessing: være
- CloseFiscalYearWizard: årsafslutning
Bugs Fixed:
- Non-null assertion crashes in Kunder.tsx and Produkter.tsx (company!.id)
- StatusBadge typo "Succces"→"Succes"
- HTML entity ø in Kassekladde→proper UTF-8
- AmountText showSign prop was dead code (true || showSign)
UX Improvements:
- Add PageHeader to Bankafstemning and Dashboard loading/empty states
- Responsive columns in Bankafstemning (xs/sm/lg breakpoints)
- Disable misleading buttons: Settings preferences, Kontooversigt edit,
Loenforstaelse export — with tooltips explaining status
- Add DemoDataDisclaimer to UserSettings
- Fix breadcrumb self-references on 3 pages
- Replace Dashboard fake progress bar with honest message
- Standardize date format DD-MM-YYYY in Bankafstemning and Ordrer
- Replace Input type="number" with InputNumber in Ordrer
Quality:
- Remove 8 redundant console.error statements
- Fix Kreditnotaer breadcrumb "Salg"→"Fakturering" for consistency
Co-Authored-By: Claude Opus 4.6
---
.beads/issues.jsonl | 3 +
.../Invoices/InvoiceCommandHandlers.cs | 74 ++++++++---
.../Books.Api/Controllers/AuthController.cs | 3 +
.../Invoices/Events/InvoiceCreatedEvent.cs | 20 ++-
.../Domain/Invoices/InvoiceAggregate.cs | 10 +-
.../JournalEntryDraftAggregate.cs | 9 ++
.../Subscribers/StandardDanishAccounts.cs | 3 +-
.../Books.Api/Saft/Services/SaftXmlBuilder.cs | 64 ++++++++--
frontend/src/api/documentProcessing.ts | 2 +-
.../modals/CloseFiscalYearWizard.tsx | 2 +-
.../modals/CreateFiscalYearModal.tsx | 1 -
.../settings/BankConnectionsTab.tsx | 6 -
frontend/src/components/shared/AmountText.tsx | 2 +-
.../components/shared/AttachmentUpload.tsx | 8 +-
.../components/shared/ShortcutsHelpModal.tsx | 2 +-
.../src/components/shared/StatusBadge.tsx | 2 +-
frontend/src/components/tables/DataTable.tsx | 2 +-
frontend/src/lib/keyboardShortcuts.ts | 4 +-
frontend/src/lib/vatCodes.ts | 119 +++++++++++-------
frontend/src/pages/Bankafstemning.tsx | 48 ++++---
frontend/src/pages/CompanySetupWizard.tsx | 1 -
frontend/src/pages/Dashboard.tsx | 18 ++-
frontend/src/pages/Fakturaer.tsx | 2 +-
frontend/src/pages/Kassekladde.tsx | 26 ++--
frontend/src/pages/Kontooversigt.tsx | 16 +--
frontend/src/pages/Kreditnotaer.tsx | 2 +-
frontend/src/pages/Kunder.tsx | 6 +-
frontend/src/pages/Loenforstaelse.tsx | 5 +-
frontend/src/pages/Ordrer.tsx | 11 +-
frontend/src/pages/Produkter.tsx | 6 +-
frontend/src/pages/Settings.tsx | 22 +---
frontend/src/pages/UserSettings.tsx | 3 +
frontend/src/types/vat.ts | 25 ++--
33 files changed, 355 insertions(+), 172 deletions(-)
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 5c03f1d..6b7f9b7 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -5,7 +5,9 @@
{"id":"books-5tg","title":"opret et backend job med hangfir\ne som sikrer, at bankkonto altid stemmer med den pågældende konto","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:24:01.505911+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:37:08.293897+01:00","closed_at":"2026-01-30T14:37:08.293897+01:00","close_reason":"Closed"}
{"id":"books-8ea","title":"fjern brugers navn fra højre hjørne ved profile ikonet","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:20:16.406033+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:22:59.64468+01:00","closed_at":"2026-01-30T14:22:59.64468+01:00","close_reason":"Closed"}
{"id":"books-8lo","title":"revisit the laytoug and desig nfor kontooversigten.","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:25:06.620288+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:47:52.365315+01:00","closed_at":"2026-01-30T14:47:52.365315+01:00","close_reason":"Closed"}
+{"id":"books-8n5","title":"v3 Phase 1+3: Frontend VAT alignment + encoding + bugs","description":"Align frontend VAT codes with backend, fix ~20 Danish encoding issues, fix non-null assertions, StatusBadge typo, HTML entity","status":"in_progress","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-02-06T01:06:57.325868+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-02-06T01:07:01.858975+01:00"}
{"id":"books-9ig","title":"Phase 3: Critical frontend bugs","description":"Fix closing wizard entries, create-next-year checkbox, Dashboard a-href, Kassekladde filters, edit draft form population, Vis detaljer action, Flere filtre button, duplicate migration","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-02-05T22:16:57.507387+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-02-06T00:18:23.721813+01:00","closed_at":"2026-02-06T00:18:23.721813+01:00","close_reason":"Closed"}
+{"id":"books-9te","title":"v3 Phase 1+2+6: Backend VAT, security, compliance","description":"Fix invoice fiscal year check, disable custom invoice numbers, open redirect, OAuth CSRF, cross-company validation, description validation, SAF-T fixes, chart of accounts, seller CVR on invoices","status":"in_progress","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-02-06T01:06:57.223084+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-02-06T01:07:01.78072+01:00"}
{"id":"books-bj6","title":"Test automatisk pickup","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:04:40.572496+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:05:44.401903+01:00","closed_at":"2026-01-30T14:05:44.401903+01:00","close_reason":"completed"}
{"id":"books-byl","title":"opret giv et shortlink på frontenden til backend på /hangfire","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:24:34.946139+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:40:44.62014+01:00","closed_at":"2026-01-30T14:40:44.62014+01:00","close_reason":"Closed"}
{"id":"books-cdf","title":"opret","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:23:39.411558+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T17:45:05.363658+01:00","closed_at":"2026-01-30T17:45:05.363658+01:00","close_reason":"Skipped - task description too vague"}
@@ -13,6 +15,7 @@
{"id":"books-cws","title":"Phase 3: Accounting compliance fixes","description":"Balanced entry enforcement, VAT code unification, invoice numbering, fiscal year gap/overlap checks, posting date tracking, SAF-T fixes.","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-02-05T21:12:09.362182+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-02-05T21:35:30.369857+01:00","closed_at":"2026-02-05T21:35:30.369857+01:00","close_reason":"Closed"}
{"id":"books-h6e","title":"fjern hurtig bogføring og den visning der høre dertil","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:14:50.436314+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:18:09.911294+01:00","closed_at":"2026-01-30T14:18:09.911294+01:00","close_reason":"Closed"}
{"id":"books-hzt","title":"fix bug med tilføj brugere står forkert med encoded tegn","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:21:34.556319+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:28:31.320973+01:00","closed_at":"2026-01-30T14:28:31.320973+01:00","close_reason":"Closed"}
+{"id":"books-i6l","title":"v3 Phase 4+5: UX, loading states, quality","description":"Fix loading/empty states, disclaimers, breadcrumbs, responsive columns, dead code, console.error cleanup, InputNumber","status":"in_progress","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-02-06T01:06:57.418109+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-02-06T01:07:01.943749+01:00"}
{"id":"books-k95","title":"Phase 4: UX consistency \u0026 bug fixes","description":"Danish character encoding, DemoDataDisclaimer deployment, PageHeader adoption, mobile responsiveness, mock data removal, dead buttons.","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-02-05T21:12:09.471301+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-02-05T21:35:30.433843+01:00","closed_at":"2026-02-05T21:35:30.433843+01:00","close_reason":"Closed"}
{"id":"books-ley","title":"Phase 1: GraphQL Authentication \u0026 Authorization","description":"Add authentication to GraphQL endpoint and authorization checks to all resolvers. Fix: S-01 through S-06, RBAC always returning owner, admin hardcoded email check.","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-02-05T21:12:09.131213+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-02-05T21:35:30.243779+01:00","closed_at":"2026-02-05T21:35:30.243779+01:00","close_reason":"Closed"}
{"id":"books-ljg","title":"Fjern mock data og kobl frontend til backend GraphQL","description":"Frontend bruger ~2000 linjer hardcoded mock data i stedet for at bruge de eksisterende GraphQL hooks.\n\n## Problem\n- Backend GraphQL API er klar med queries og mutations\n- Frontend har hooks skrevet (useAccounts, useFiscalYears, etc.)\n- Men pages bruger hardcoded mock data i stedet for at kalde hooks\n\n## Filer der skal opdateres\n1. Dashboard.tsx - mock metrics, charts, transactions\n2. Kassekladde.tsx - mock accounts og posteringer \n3. Kontooversigt.tsx - mock kontoplan og balancer\n4. Bankafstemning.tsx - mock bank accounts og transaktioner\n5. FiscalYearSelector.tsx - mock fiscal years\n6. CompanySwitcher.tsx - mock companies\n7. Stores (companyStore, periodStore) - skal initialiseres fra API\n\n## Acceptkriterier\n- Al mock data fjernet fra frontend\n- Alle pages bruger GraphQL hooks til at hente data\n- Stores initialiseres korrekt ved app start\n- Data vises fra backend i UI","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T22:27:49.225279+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T22:42:04.17437+01:00","closed_at":"2026-01-30T22:42:04.17437+01:00","close_reason":"Closed"}
diff --git a/backend/Books.Api/Commands/Invoices/InvoiceCommandHandlers.cs b/backend/Books.Api/Commands/Invoices/InvoiceCommandHandlers.cs
index ce7c5ab..e3b09a4 100644
--- a/backend/Books.Api/Commands/Invoices/InvoiceCommandHandlers.cs
+++ b/backend/Books.Api/Commands/Invoices/InvoiceCommandHandlers.cs
@@ -8,7 +8,7 @@ namespace Books.Api.Commands.Invoices;
///
/// Command handler for creating invoices.
-/// Auto-assigns a sequential invoice number if one is not provided.
+/// Always auto-assigns a sequential invoice number (Momsloven §52 - sequential numbering required).
/// Validates the company has a CVR number (required for invoicing).
///
public class CreateInvoiceCommandHandler(
@@ -39,15 +39,16 @@ public class CreateInvoiceCommandHandler(
"Virksomheden skal have et CVR-nummer for at oprette fakturaer. Opdater venligst virksomhedsindstillinger.");
}
- // Auto-assign invoice number if not provided
- var invoiceNumber = command.InvoiceNumber;
- if (string.IsNullOrWhiteSpace(invoiceNumber))
- {
- invoiceNumber = await invoiceNumberService.GetNextInvoiceNumberAsync(
- command.CompanyId,
- command.InvoiceDate.Year,
- cancellationToken);
- }
+ // Always auto-assign invoice number (Momsloven §52 requires unbroken sequential numbering)
+ var invoiceNumber = await invoiceNumberService.GetNextInvoiceNumberAsync(
+ command.CompanyId,
+ command.InvoiceDate.Year,
+ cancellationToken);
+
+ // Build seller address from company info
+ var sellerAddress = string.Join(", ",
+ new[] { company.Address, company.PostalCode, company.City }
+ .Where(s => !string.IsNullOrWhiteSpace(s)));
aggregate.Create(
command.CompanyId,
@@ -63,7 +64,10 @@ public class CreateInvoiceCommandHandler(
command.VatCode,
command.Notes,
command.Reference,
- command.CreatedBy);
+ command.CreatedBy,
+ company.Cvr,
+ company.Name,
+ sellerAddress);
}
}
@@ -124,19 +128,59 @@ public class RemoveInvoiceLineCommandHandler
}
}
-public class MarkInvoiceSentCommandHandler
+///
+/// Command handler for marking an invoice as sent.
+/// Validates fiscal year status before allowing the invoice to be sent.
+///
+public class MarkInvoiceSentCommandHandler(
+ IInvoiceRepository invoiceRepository,
+ IFiscalYearRepository fiscalYearRepository)
: CommandHandler
{
- public override Task ExecuteAsync(
+ public override async Task ExecuteAsync(
InvoiceAggregate aggregate,
MarkInvoiceSentCommand command,
CancellationToken cancellationToken)
{
+ // Load the invoice read model to get fiscal year ID
+ var invoice = await invoiceRepository.GetByIdAsync(
+ aggregate.Id.Value, cancellationToken);
+
+ var fiscalYearId = invoice?.FiscalYearId;
+
+ // Validate fiscal year is set
+ if (string.IsNullOrWhiteSpace(fiscalYearId))
+ {
+ throw new DomainException(
+ "FISCAL_YEAR_REQUIRED",
+ "Fiscal year is required for sending an invoice",
+ "Regnskabsår er påkrævet for afsendelse af en faktura");
+ }
+
+ // Fetch and validate fiscal year
+ var fiscalYear = await fiscalYearRepository.GetByIdAsync(
+ fiscalYearId, cancellationToken);
+
+ if (fiscalYear == null)
+ {
+ throw new DomainException(
+ "FISCAL_YEAR_NOT_FOUND",
+ $"Fiscal year '{fiscalYearId}' not found",
+ $"Regnskabsår '{fiscalYearId}' blev ikke fundet");
+ }
+
+ // Validate fiscal year is open (not Closed or Locked)
+ if (fiscalYear.Status != "Open")
+ {
+ throw new DomainException(
+ "FISCAL_YEAR_NOT_OPEN",
+ $"Fiscal year is {fiscalYear.Status}. Only open fiscal years allow posting.",
+ $"Regnskabsåret er {fiscalYear.Status}. Kun åbne regnskabsår tillader bogføring.");
+ }
+
aggregate.Send(
command.LedgerTransactionId,
command.SentBy);
-
- return Task.CompletedTask;
}
}
diff --git a/backend/Books.Api/Controllers/AuthController.cs b/backend/Books.Api/Controllers/AuthController.cs
index 998973f..0a9c628 100644
--- a/backend/Books.Api/Controllers/AuthController.cs
+++ b/backend/Books.Api/Controllers/AuthController.cs
@@ -16,6 +16,9 @@ public class AuthController : ControllerBase
{
// The [Authorize] attribute triggers the OIDC challenge if not authenticated.
// If we reach here, the user is authenticated - redirect back to the app.
+ // Validate returnUrl to prevent open redirect attacks
+ if (returnUrl != null && !Url.IsLocalUrl(returnUrl))
+ returnUrl = "/";
return Redirect(returnUrl ?? "/");
}
diff --git a/backend/Books.Api/Domain/Invoices/Events/InvoiceCreatedEvent.cs b/backend/Books.Api/Domain/Invoices/Events/InvoiceCreatedEvent.cs
index 1a9be5b..e498c87 100644
--- a/backend/Books.Api/Domain/Invoices/Events/InvoiceCreatedEvent.cs
+++ b/backend/Books.Api/Domain/Invoices/Events/InvoiceCreatedEvent.cs
@@ -25,7 +25,10 @@ public class InvoiceCreatedEvent(
InvoiceType type = InvoiceType.Invoice,
string? originalInvoiceId = null,
string? originalInvoiceNumber = null,
- string? creditReason = null) : AggregateEvent
+ string? creditReason = null,
+ string? sellerCvr = null,
+ string? sellerName = null,
+ string? sellerAddress = null) : AggregateEvent
{
public string CompanyId { get; } = companyId;
public string FiscalYearId { get; } = fiscalYearId;
@@ -61,4 +64,19 @@ public class InvoiceCreatedEvent(
/// For credit notes: Reason for issuing the credit note.
///
public string? CreditReason { get; } = creditReason;
+
+ ///
+ /// Seller CVR number (company registration number).
+ ///
+ public string? SellerCvr { get; } = sellerCvr;
+
+ ///
+ /// Seller company name.
+ ///
+ public string? SellerName { get; } = sellerName;
+
+ ///
+ /// Seller company address.
+ ///
+ public string? SellerAddress { get; } = sellerAddress;
}
diff --git a/backend/Books.Api/Domain/Invoices/InvoiceAggregate.cs b/backend/Books.Api/Domain/Invoices/InvoiceAggregate.cs
index 4cc147b..5def74d 100644
--- a/backend/Books.Api/Domain/Invoices/InvoiceAggregate.cs
+++ b/backend/Books.Api/Domain/Invoices/InvoiceAggregate.cs
@@ -138,7 +138,10 @@ public class InvoiceAggregate(InvoiceId id) : AggregateRoot _lines = [];
public string CompanyId => _companyId;
@@ -48,6 +49,7 @@ public class JournalEntryDraftAggregate(JournalEntryDraftId id)
public void Apply(JournalEntryDraftUpdatedEvent e)
{
_fiscalYearId = e.FiscalYearId;
+ _description = e.Description;
_lines = e.Lines.ToList();
}
@@ -184,6 +186,13 @@ public class JournalEntryDraftAggregate(JournalEntryDraftId id)
"Posted by is required",
"Bogført af er påkrævet");
+ // Validate description is set (Bogføringsloven §7 stk. 1 nr. 3 requires a text describing the transaction)
+ if (string.IsNullOrWhiteSpace(_description))
+ throw new DomainException(
+ "DESCRIPTION_REQUIRED",
+ "A description is required for posting. Each registration must have a text describing the transaction (Bogføringsloven §7 stk. 1 nr. 3).",
+ "En beskrivelse er påkrævet for bogføring. Hver registrering skal have en tekst, der beskriver transaktionen (Bogføringsloven §7 stk. 1 nr. 3).");
+
// Validate minimum number of lines for double-entry bookkeeping
if (_lines.Count < 2)
throw new DomainException(
diff --git a/backend/Books.Api/EventFlow/Subscribers/StandardDanishAccounts.cs b/backend/Books.Api/EventFlow/Subscribers/StandardDanishAccounts.cs
index b810d27..842e76a 100644
--- a/backend/Books.Api/EventFlow/Subscribers/StandardDanishAccounts.cs
+++ b/backend/Books.Api/EventFlow/Subscribers/StandardDanishAccounts.cs
@@ -221,8 +221,9 @@ public static class StandardDanishAccounts
// =========================================
// FINANSIELLE POSTER (Financial) - 9xxx
- // Standard: 3670 = Øvrige finansielle omkostninger
+ // Standard: 3510 = Finansielle indtægter, 3670 = Øvrige finansielle omkostninger
// =========================================
+ yield return new("9100", "Renteindtægter", AccountType.Revenue, "Finansielle indtægter", null, "9100");
yield return new("9200", "Bankrenter", AccountType.Financial, null, null, "3670");
yield return new("9210", "Leverandører mv.", AccountType.Financial, "Renter til leverandører", null, "3670");
yield return new("9220", "Ikke-fradragsberettigede renter", AccountType.Financial, null, null, "3670");
diff --git a/backend/Books.Api/Saft/Services/SaftXmlBuilder.cs b/backend/Books.Api/Saft/Services/SaftXmlBuilder.cs
index 5dab855..fa6b083 100644
--- a/backend/Books.Api/Saft/Services/SaftXmlBuilder.cs
+++ b/backend/Books.Api/Saft/Services/SaftXmlBuilder.cs
@@ -53,9 +53,15 @@ public class SaftXmlBuilder
writer.WriteElementString("SoftwareID", header.SoftwareID);
writer.WriteElementString("SoftwareVersion", header.SoftwareVersion);
+ // SAF-T requires DefaultCurrencyCode (ISO 4217)
+ writer.WriteElementString("DefaultCurrencyCode", "DKK");
+
WriteCompany(writer, header.Company);
WriteSelectionCriteria(writer, header.SelectionCriteria);
+ // SAF-T requires TaxAccountingBasis: A = Accrual, C = Cash, O = Other
+ writer.WriteElementString("TaxAccountingBasis", "A");
+
writer.WriteEndElement(); // Header
}
@@ -133,10 +139,52 @@ public class SaftXmlBuilder
WriteGeneralLedgerAccounts(writer, masterFiles.GeneralLedgerAccounts);
WriteCustomers(writer, masterFiles.Customers);
WriteSuppliers(writer, masterFiles.Suppliers);
+ WriteTaxTable(writer);
writer.WriteEndElement(); // MasterFiles
}
+ ///
+ /// Writes the TaxTable element declaring all Danish VAT codes.
+ /// Required by SAF-T schema to describe the tax codes used in transactions.
+ ///
+ private static void WriteTaxTable(XmlWriter writer)
+ {
+ writer.WriteStartElement("TaxTable");
+ writer.WriteStartElement("TaxTableEntry");
+
+ writer.WriteElementString("TaxType", "MVA");
+ writer.WriteElementString("Description", "Dansk moms (VAT)");
+
+ // Output VAT codes
+ WriteTaxCodeDetails(writer, "U25", "Udgående moms 25%", 25.00m);
+ WriteTaxCodeDetails(writer, "UEU", "EU-salg (momsfrit)", 0.00m);
+ WriteTaxCodeDetails(writer, "UEXP", "Eksport uden for EU (momsfrit)", 0.00m);
+
+ // Input VAT codes
+ WriteTaxCodeDetails(writer, "I25", "Indgående moms 25%", 25.00m);
+ WriteTaxCodeDetails(writer, "IEUV", "EU-erhvervelse varer (reverse charge)", 25.00m);
+ WriteTaxCodeDetails(writer, "IEUY", "EU-erhvervelse ydelser (reverse charge)", 25.00m);
+ WriteTaxCodeDetails(writer, "IVV", "Import varer fra verden", 0.00m);
+ WriteTaxCodeDetails(writer, "IVY", "Import ydelser fra verden", 0.00m);
+
+ // Special codes
+ WriteTaxCodeDetails(writer, "REP", "Repræsentation (25% fradrag)", 25.00m);
+ WriteTaxCodeDetails(writer, "INGEN", "Ingen moms", 0.00m);
+
+ writer.WriteEndElement(); // TaxTableEntry
+ writer.WriteEndElement(); // TaxTable
+ }
+
+ private static void WriteTaxCodeDetails(XmlWriter writer, string taxCode, string description, decimal taxPercentage)
+ {
+ writer.WriteStartElement("TaxCodeDetails");
+ writer.WriteElementString("TaxCode", taxCode);
+ writer.WriteElementString("Description", description);
+ writer.WriteElementString("TaxPercentage", FormatDecimal(taxPercentage));
+ writer.WriteEndElement(); // TaxCodeDetails
+ }
+
private static void WriteGeneralLedgerAccounts(XmlWriter writer, List accounts)
{
if (accounts.Count == 0) return;
@@ -160,17 +208,13 @@ public class SaftXmlBuilder
writer.WriteElementString("AccountType", account.AccountType);
- // Opening balances
- if (account.OpeningDebitBalance != 0)
- writer.WriteElementString("OpeningDebitBalance", FormatDecimal(account.OpeningDebitBalance));
- if (account.OpeningCreditBalance != 0)
- writer.WriteElementString("OpeningCreditBalance", FormatDecimal(account.OpeningCreditBalance));
+ // Opening balances (SAF-T schema requires these elements even when zero)
+ writer.WriteElementString("OpeningDebitBalance", FormatDecimal(account.OpeningDebitBalance));
+ writer.WriteElementString("OpeningCreditBalance", FormatDecimal(account.OpeningCreditBalance));
- // Closing balances
- if (account.ClosingDebitBalance != 0)
- writer.WriteElementString("ClosingDebitBalance", FormatDecimal(account.ClosingDebitBalance));
- if (account.ClosingCreditBalance != 0)
- writer.WriteElementString("ClosingCreditBalance", FormatDecimal(account.ClosingCreditBalance));
+ // Closing balances (SAF-T schema requires these elements even when zero)
+ writer.WriteElementString("ClosingDebitBalance", FormatDecimal(account.ClosingDebitBalance));
+ writer.WriteElementString("ClosingCreditBalance", FormatDecimal(account.ClosingCreditBalance));
writer.WriteEndElement(); // Account
}
diff --git a/frontend/src/api/documentProcessing.ts b/frontend/src/api/documentProcessing.ts
index 1bdaf83..c7704ae 100644
--- a/frontend/src/api/documentProcessing.ts
+++ b/frontend/src/api/documentProcessing.ts
@@ -117,7 +117,7 @@ export async function processDocument(
}
// Fallback error handling
if (response.status === 401) {
- throw new DocumentProcessingApiError('NOT_AUTHENTICATED', 'Du skal vaere logget ind');
+ throw new DocumentProcessingApiError('NOT_AUTHENTICATED', 'Du skal være logget ind');
}
if (response.status === 403) {
throw new DocumentProcessingApiError('FORBIDDEN', 'Du har ikke adgang til denne virksomhed');
diff --git a/frontend/src/components/modals/CloseFiscalYearWizard.tsx b/frontend/src/components/modals/CloseFiscalYearWizard.tsx
index eb04291..65f1e6d 100644
--- a/frontend/src/components/modals/CloseFiscalYearWizard.tsx
+++ b/frontend/src/components/modals/CloseFiscalYearWizard.tsx
@@ -238,7 +238,7 @@ export default function CloseFiscalYearWizard({
onSuccess?.();
} catch (error) {
if (error instanceof Error) {
- message.error(`Fejl ved arsafslutning: ${error.message}`);
+ message.error(`Fejl ved årsafslutning: ${error.message}`);
}
console.error('Failed to close fiscal year:', error);
} finally {
diff --git a/frontend/src/components/modals/CreateFiscalYearModal.tsx b/frontend/src/components/modals/CreateFiscalYearModal.tsx
index e7065ac..74b3435 100644
--- a/frontend/src/components/modals/CreateFiscalYearModal.tsx
+++ b/frontend/src/components/modals/CreateFiscalYearModal.tsx
@@ -154,7 +154,6 @@ export default function CreateFiscalYearModal({
if (error instanceof Error) {
message.error(`Fejl ved oprettelse: ${error.message}`);
}
- console.error('Failed to create fiscal year:', error);
} finally {
setIsSubmitting(false);
}
diff --git a/frontend/src/components/settings/BankConnectionsTab.tsx b/frontend/src/components/settings/BankConnectionsTab.tsx
index df7e5f7..5de2d18 100644
--- a/frontend/src/components/settings/BankConnectionsTab.tsx
+++ b/frontend/src/components/settings/BankConnectionsTab.tsx
@@ -158,7 +158,6 @@ export default function BankConnectionsTab({ companyId }: BankConnectionsTabProp
setSelectedLinkedAccount(newAccount.id);
} catch (error) {
showError(error, 'Kunne ikke oprette bankkonto');
- console.error('Failed to create bank account:', error);
}
};
@@ -192,7 +191,6 @@ export default function BankConnectionsTab({ companyId }: BankConnectionsTabProp
setImportFromDate(dayjs());
} catch (error) {
showError(error, 'Kunne ikke koble bankkonto');
- console.error('Failed to link account:', error);
}
};
@@ -218,7 +216,6 @@ export default function BankConnectionsTab({ companyId }: BankConnectionsTabProp
window.location.href = result.authorizationUrl;
} catch (error) {
showError(error, 'Kunne ikke starte bankforbindelse');
- console.error('Failed to start bank connection:', error);
}
};
@@ -230,7 +227,6 @@ export default function BankConnectionsTab({ companyId }: BankConnectionsTabProp
showSuccess('Bankforbindelse afbrudt');
} catch (error) {
showError(error, 'Kunne ikke afbryde bankforbindelse');
- console.error('Failed to disconnect:', error);
}
};
@@ -255,7 +251,6 @@ export default function BankConnectionsTab({ companyId }: BankConnectionsTabProp
window.location.href = result.authorizationUrl;
} catch (error) {
showError(error, 'Kunne ikke genoptage bankforbindelse');
- console.error('Failed to reconnect bank connection:', error);
}
};
@@ -267,7 +262,6 @@ export default function BankConnectionsTab({ companyId }: BankConnectionsTabProp
showSuccess('Bankforbindelse arkiveret');
} catch (error) {
showError(error, 'Kunne ikke arkivere bankforbindelse');
- console.error('Failed to archive:', error);
}
};
diff --git a/frontend/src/components/shared/AmountText.tsx b/frontend/src/components/shared/AmountText.tsx
index 8ec0d6c..2aa746a 100644
--- a/frontend/src/components/shared/AmountText.tsx
+++ b/frontend/src/components/shared/AmountText.tsx
@@ -80,7 +80,7 @@ export function AmountText({
const formatted = formatCurrency(Math.abs(amount));
// Always show +/- prefix for non-zero amounts (accessibility: not color-only)
// When showSign is explicitly true, same behavior; kept for API compatibility
- const alwaysSign = true || showSign;
+ const alwaysSign = showSign;
const sign = alwaysSign && amount !== 0 ? (amount > 0 ? '+' : '-') : amount < 0 ? '-' : '';
const suffix = showCurrency ? ` ${currencySuffix}` : '';
diff --git a/frontend/src/components/shared/AttachmentUpload.tsx b/frontend/src/components/shared/AttachmentUpload.tsx
index b2dcab8..c5a13cd 100644
--- a/frontend/src/components/shared/AttachmentUpload.tsx
+++ b/frontend/src/components/shared/AttachmentUpload.tsx
@@ -63,7 +63,7 @@ const getFileIcon = (fileType: string) => {
/**
* Bilag (Document/Attachment) upload component.
* Supports drag-drop, multiple file upload, and preview.
- * Required by Bogforingsloven § 6 for document retention.
+ * Required by Bogføringsloven § 6 for document retention.
*/
export function AttachmentUpload({
attachments = [],
@@ -182,10 +182,10 @@ export function AttachmentUpload({
- Klik eller traek filer hertil for at uploade bilag
+ Klik eller træk filer hertil for at uploade bilag
- Understotter PDF, billeder og Office-dokumenter (max {formatFileSize(maxFileSize)})
+ Understøtter PDF, billeder og Office-dokumenter (max {formatFileSize(maxFileSize)})
@@ -210,7 +210,7 @@ export function AttachmentUpload({
{/* Required warning */}
{required && attachments.length === 0 && fileList.length === 0 && (
- Bilag er pakraevet iht. Bogforingsloven § 6
+ Bilag er påkrævet iht. Bogføringsloven § 6
)}
diff --git a/frontend/src/components/shared/ShortcutsHelpModal.tsx b/frontend/src/components/shared/ShortcutsHelpModal.tsx
index 349a43f..617f5bf 100644
--- a/frontend/src/components/shared/ShortcutsHelpModal.tsx
+++ b/frontend/src/components/shared/ShortcutsHelpModal.tsx
@@ -71,7 +71,7 @@ export function ShortcutsHelpModal({ open, onClose }: ShortcutsHelpModalProps) {
- Tip: Tryk ⌘K for at abne
+ Tip: Tryk ⌘K for at åbne
kommandopaletten og hurtigt navigere til enhver side.
diff --git a/frontend/src/components/shared/StatusBadge.tsx b/frontend/src/components/shared/StatusBadge.tsx
index 1f2ccf2..31a7da2 100644
--- a/frontend/src/components/shared/StatusBadge.tsx
+++ b/frontend/src/components/shared/StatusBadge.tsx
@@ -70,7 +70,7 @@ const statusConfig: Record<
success: {
color: 'green',
icon: ,
- defaultText: 'Succces',
+ defaultText: 'Succes',
},
warning: {
color: 'orange',
diff --git a/frontend/src/components/tables/DataTable.tsx b/frontend/src/components/tables/DataTable.tsx
index a2917bd..13ec9eb 100644
--- a/frontend/src/components/tables/DataTable.tsx
+++ b/frontend/src/components/tables/DataTable.tsx
@@ -247,7 +247,7 @@ export default function DataTable({
{toolbarActions}
{refreshable && hookEnabled && (
-
+
}
onClick={() => refetch()}
diff --git a/frontend/src/lib/keyboardShortcuts.ts b/frontend/src/lib/keyboardShortcuts.ts
index 8d1d331..2f5f7ef 100644
--- a/frontend/src/lib/keyboardShortcuts.ts
+++ b/frontend/src/lib/keyboardShortcuts.ts
@@ -153,7 +153,7 @@ export const shortcuts: Record = {
postDraft: {
id: 'postDraft',
keys: 'mod+enter',
- label: 'Bogfor kladde',
+ label: 'Bogfør kladde',
category: 'bogforing',
scope: 'page',
},
@@ -290,7 +290,7 @@ export function formatShortcutForTooltip(shortcutId: string): string | null {
export const categoryNames: Record = {
global: 'Globale',
navigation: 'Navigation',
- bogforing: 'Bogforing',
+ bogforing: 'Bogføring',
faktura: 'Fakturering',
bank: 'Bank',
kunder: 'Kunder',
diff --git a/frontend/src/lib/vatCodes.ts b/frontend/src/lib/vatCodes.ts
index 15c6d93..ce1272d 100644
--- a/frontend/src/lib/vatCodes.ts
+++ b/frontend/src/lib/vatCodes.ts
@@ -13,9 +13,9 @@ import type { VATPeriodicitet } from '@/types/periods';
/**
* Complete VAT code configuration for Danish bookkeeping
*/
-export const VAT_CODE_CONFIG: Record = {
- S25: {
- code: 'S25',
+export const VAT_CODE_CONFIG: Record = {
+ U25: {
+ code: 'U25',
nameDanish: 'Udgående moms 25%',
nameEnglish: 'Output VAT 25%',
rate: 0.25,
@@ -28,8 +28,34 @@ export const VAT_CODE_CONFIG: Record = {
deductible: false,
description: 'Moms på salg af varer og ydelser i Danmark',
},
- K25: {
- code: 'K25',
+ UEU: {
+ code: 'UEU',
+ nameDanish: 'EU-salg',
+ nameEnglish: 'EU sales (0%)',
+ rate: 0,
+ type: 'exempt',
+ affectsBoxes: {
+ basisBox: '2',
+ },
+ reverseCharge: false,
+ deductible: false,
+ description: 'Salg af varer og ydelser til andre EU-lande',
+ },
+ UEXP: {
+ code: 'UEXP',
+ nameDanish: 'Eksport',
+ nameEnglish: 'Export (0%)',
+ rate: 0,
+ type: 'exempt',
+ affectsBoxes: {
+ basisBox: '2',
+ },
+ reverseCharge: false,
+ deductible: false,
+ description: 'Eksport til lande uden for EU',
+ },
+ I25: {
+ code: 'I25',
nameDanish: 'Indgående moms 25%',
nameEnglish: 'Input VAT 25%',
rate: 0.25,
@@ -41,10 +67,10 @@ export const VAT_CODE_CONFIG: Record = {
deductible: true,
description: 'Fradragsberettiget moms på køb',
},
- EU_VARE: {
- code: 'EU_VARE',
- nameDanish: 'EU-varekøb (erhvervelsesmoms)',
- nameEnglish: 'EU goods purchase (acquisition VAT)',
+ IEUV: {
+ code: 'IEUV',
+ nameDanish: 'EU-erhvervelse varer',
+ nameEnglish: 'EU goods acquisition (reverse charge)',
rate: 0.25,
type: 'reverse_charge',
affectsBoxes: {
@@ -52,13 +78,13 @@ export const VAT_CODE_CONFIG: Record = {
basisBox: '3',
},
reverseCharge: true,
- deductible: true, // Both output and input VAT
+ deductible: true,
description: 'Køb af varer fra andre EU-lande med omvendt betalingspligt',
},
- EU_YDELSE: {
- code: 'EU_YDELSE',
- nameDanish: 'EU-ydelseskøb (omvendt betalingspligt)',
- nameEnglish: 'EU services purchase (reverse charge)',
+ IEUY: {
+ code: 'IEUY',
+ nameDanish: 'EU-erhvervelse ydelser',
+ nameEnglish: 'EU services acquisition (reverse charge)',
rate: 0.25,
type: 'reverse_charge',
affectsBoxes: {
@@ -69,34 +95,43 @@ export const VAT_CODE_CONFIG: Record = {
deductible: true,
description: 'Køb af ydelser fra udlandet med omvendt betalingspligt',
},
- MOMSFRI: {
- code: 'MOMSFRI',
- nameDanish: 'Momsfritaget',
- nameEnglish: 'VAT exempt',
+ IVV: {
+ code: 'IVV',
+ nameDanish: 'Import varer verden',
+ nameEnglish: 'Import goods (world)',
rate: 0,
- type: 'exempt',
- affectsBoxes: {
- basisBox: '2',
- },
+ type: 'input',
+ affectsBoxes: {},
reverseCharge: false,
deductible: false,
- description: 'Momsfritaget salg (sundhed, undervisning, mv.)',
+ description: 'Import af varer fra lande uden for EU',
},
- EKSPORT: {
- code: 'EKSPORT',
- nameDanish: 'Eksport (0%)',
- nameEnglish: 'Export (0%)',
+ IVY: {
+ code: 'IVY',
+ nameDanish: 'Import ydelser verden',
+ nameEnglish: 'Import services (world)',
rate: 0,
- type: 'exempt',
- affectsBoxes: {
- basisBox: '2',
- },
+ type: 'input',
+ affectsBoxes: {},
reverseCharge: false,
deductible: false,
- description: 'Eksport til lande uden for EU',
+ description: 'Import af ydelser fra lande uden for EU',
},
- NONE: {
- code: 'NONE',
+ REP: {
+ code: 'REP',
+ nameDanish: 'Repræsentation',
+ nameEnglish: 'Representation (25%, 25% deductible)',
+ rate: 0.25,
+ type: 'input',
+ affectsBoxes: {
+ vatBox: 'B',
+ },
+ reverseCharge: false,
+ deductible: true,
+ description: 'Repræsentationsudgifter med 25% fradrag',
+ },
+ INGEN: {
+ code: 'INGEN',
nameDanish: 'Ingen moms',
nameEnglish: 'No VAT',
rate: 0,
@@ -231,7 +266,7 @@ export const SKAT_VAT_BOXES: Record = {
*/
export const VAT_ACCOUNTS = {
inputVAT: '5610', // Indgående moms (fradrag)
- outputVAT: '5710', // Udgående moms (skyld)
+ outputVAT: '5611', // Udgående moms (skyld)
euVAT: '5620', // EU-moms (erhvervelsesmoms)
} as const;
@@ -243,7 +278,7 @@ export const VAT_ACCOUNTS = {
* Valid VAT code values for validation
*/
export const VALID_VAT_CODES: readonly VATCode[] = [
- 'S25', 'K25', 'EU_VARE', 'EU_YDELSE', 'MOMSFRI', 'EKSPORT', 'NONE'
+ 'U25', 'UEU', 'UEXP', 'I25', 'IEUV', 'IEUY', 'IVV', 'IVY', 'REP', 'INGEN'
] as const;
/**
@@ -254,17 +289,17 @@ export function isValidVATCode(code: unknown): code is VATCode {
}
/**
- * Safely convert a string to VATCode, returns 'NONE' if invalid
+ * Safely convert a string to VATCode, returns 'INGEN' if invalid
*/
export function toVATCode(code: unknown): VATCode {
- return isValidVATCode(code) ? code : 'NONE';
+ return isValidVATCode(code) ? code : 'INGEN';
}
/**
* Get VAT code configuration
*/
export function getVATCodeConfig(code: VATCode): VATCodeConfig {
- return VAT_CODE_CONFIG[code];
+ return VAT_CODE_CONFIG[code] ?? VAT_CODE_CONFIG['INGEN'];
}
/**
@@ -328,21 +363,21 @@ export function getSKATBox(boxId: VATBoxId | BasisBoxId): SKATVATBox {
* Check if a VAT code is deductible (affects input VAT)
*/
export function isVATDeductible(code: VATCode): boolean {
- return VAT_CODE_CONFIG[code].deductible;
+ return VAT_CODE_CONFIG[code]?.deductible ?? false;
}
/**
* Check if a VAT code is reverse charge
*/
export function isReverseCharge(code: VATCode): boolean {
- return VAT_CODE_CONFIG[code].reverseCharge;
+ return VAT_CODE_CONFIG[code]?.reverseCharge ?? false;
}
/**
* Get VAT rate for a code
*/
export function getVATRate(code: VATCode): number {
- return VAT_CODE_CONFIG[code].rate;
+ return VAT_CODE_CONFIG[code]?.rate ?? 0;
}
/**
diff --git a/frontend/src/pages/Bankafstemning.tsx b/frontend/src/pages/Bankafstemning.tsx
index d04572e..4f1f3de 100644
--- a/frontend/src/pages/Bankafstemning.tsx
+++ b/frontend/src/pages/Bankafstemning.tsx
@@ -30,6 +30,7 @@ import {
BulbOutlined,
} from '@ant-design/icons';
import dayjs from 'dayjs';
+import { useNavigate } from 'react-router-dom';
import { useCompany } from '@/hooks/useCompany';
import { useReconciliationStore } from '@/stores/reconciliationStore';
import { useCompanyStore } from '@/stores/companyStore';
@@ -67,6 +68,7 @@ interface MatchSuggestion {
export default function Bankafstemning() {
const { company } = useCompany();
+ const navigate = useNavigate();
const { activeCompany } = useCompanyStore();
// Fetch data from API
@@ -218,10 +220,15 @@ export default function Bankafstemning() {
if (isLoading) {
return (
);
@@ -230,9 +237,20 @@ export default function Bankafstemning() {
// Empty state - no bank connections
if (bankAccounts.length === 0) {
return (
-
+
+
+
+
+
+
);
}
@@ -241,7 +259,7 @@ export default function Bankafstemning() {
{/* Summary */}
-
+
-
+
-
+
formatCurrency(value as number)}
@@ -372,7 +390,7 @@ export default function Bankafstemning() {
{/* Side-by-side panels */}
{/* Bank Transactions */}
-
+
@@ -483,7 +501,7 @@ export default function Bankafstemning() {
{/* Ledger Entries */}
-
+
@@ -496,7 +514,7 @@ export default function Bankafstemning() {
>
{ledgerEntries.length === 0 ? (
) : (
@@ -650,7 +668,7 @@ export default function Bankafstemning() {
label="Dato"
rules={[{ required: true }]}
>
-
+
-
+
@@ -367,14 +371,8 @@ export default function Dashboard() {
value={metrics.unreconciledCount}
prefix={}
/>
-
-
- {metrics.unreconciledCount === 0 ? '100% afstemt' : 'Bankafstemning ikke implementeret endnu'}
+
+ Bankafstemning er ikke tilgængelig endnu
diff --git a/frontend/src/pages/Fakturaer.tsx b/frontend/src/pages/Fakturaer.tsx
index 0c0e06d..fc80eec 100644
--- a/frontend/src/pages/Fakturaer.tsx
+++ b/frontend/src/pages/Fakturaer.tsx
@@ -469,7 +469,7 @@ export default function Fakturaer() {
} onClick={handleCreateInvoice}>
Ny fakturakladde
diff --git a/frontend/src/pages/Kassekladde.tsx b/frontend/src/pages/Kassekladde.tsx
index 73d963c..58bca7f 100644
--- a/frontend/src/pages/Kassekladde.tsx
+++ b/frontend/src/pages/Kassekladde.tsx
@@ -199,7 +199,7 @@ export default function Kassekladde() {
return Annulleret;
}
return value ? (
- Bogfort
+ Bogført
) : (
Kladde
);
@@ -301,7 +301,7 @@ export default function Kassekladde() {
case 'void':
Modal.confirm({
title: 'Annuller bilag',
- content: `Er du sikker pa at du vil annullere bilag ${record.transactionNumber}?`,
+ content: `Er du sikker på at du vil annullere bilag ${record.transactionNumber}?`,
okText: 'Annuller bilag',
okType: 'danger',
cancelText: 'Fortryd',
@@ -352,7 +352,7 @@ export default function Kassekladde() {
const validation = validateDoubleEntry(lines as TransactionLine[]);
if (!validation.valid) {
message.error(
- `Debet (${formatCurrency(validation.totalDebit)}) skal vaere lig kredit (${formatCurrency(validation.totalCredit)})`
+ `Debet (${formatCurrency(validation.totalDebit)}) skal være lig kredit (${formatCurrency(validation.totalCredit)})`
);
return;
}
@@ -430,7 +430,7 @@ export default function Kassekladde() {
const getStatusLabel = (status: JournalEntryDraftStatus): { label: string; color: string } => {
switch (status) {
- case 'posted': return { label: 'Bogfort', color: 'green' };
+ case 'posted': return { label: 'Bogført', color: 'green' };
case 'discarded': return { label: 'Annulleret', color: 'red' };
case 'draft': return { label: 'Kladde', color: 'orange' };
case 'pending_review': return { label: 'Afventer gennemgang', color: 'blue' };
@@ -447,7 +447,7 @@ export default function Kassekladde() {
@@ -459,7 +459,7 @@ export default function Kassekladde() {
setStatusFilter(value ?? null)}
options={[
- { value: 'posted', label: 'Bogfort' },
+ { value: 'posted', label: 'Bogført' },
{ value: 'draft', label: 'Kladde' },
{ value: 'discarded', label: 'Annulleret' },
]}
@@ -569,12 +569,12 @@ export default function Kassekladde() {
{detailDraft.isReconciled && detailDraft.postedAt && (
-
+
{dayjs(detailDraft.postedAt).format('DD-MM-YYYY HH:mm')}
)}
{detailDraft.postedBy && (
-
+
{detailDraft.postedBy}
)}
@@ -647,7 +647,7 @@ export default function Kassekladde() {
@@ -683,7 +683,7 @@ export default function Kassekladde() {
|