books/backend/Books.Api/EventFlow/ReadModels/JournalEntryDraftReadModelDto.cs
Nicolaj Hartmann 8e05171b66 Full product audit: fix security, compliance, UX, and wire broken features
Security (Phase 1):
- Add authentication middleware on /graphql endpoint
- Filter company queries by user access (prevent IDOR)
- Add role-based authorization on mutations (owner/accountant)
- Reduce API key cache TTL from 24h to 5 minutes
- Hide exception details in production GraphQL errors
- Fix RBAC in frontend companyStore (was hardcoded)

Wiring broken features (Phase 2):
- Wire Kassekladde submit/void/copy to GraphQL mutations
- Wire Kontooversigt account creation to createAccount mutation
- Wire Settings save to updateCompany mutation
- Wire CreateFiscalYearModal and CloseFiscalYearWizard to mutations
- Replace Momsindberetning mock data with real useVatReport query
- Remove Dashboard hardcoded percentages and fake VAT deadline
- Fix Kreditnotaer invoice selector to use real data
- Fix mutation retry from 1 to 0 (prevent duplicate operations)

Accounting compliance (Phase 3):
- Add balanced entry validation (debit==credit) in JournalEntryDraftAggregate
- Add fiscal year boundary enforcement (status, date range checks)
- Add PostedAt timestamp to posted events (Bogføringsloven §7)
- Add account number uniqueness check within company
- Add fiscal year overlap and gap checks
- Add sequential invoice auto-numbering
- Fix InvoiceLine VAT rate to use canonical VatCodes
- Fix SAF-T account type mapping (financial → Expense)
- Add DraftLine validation (cannot have both debit and credit > 0)

UX improvements (Phase 4):
- Fix Danish character encoding across 15+ files (ø, æ, å)
- Deploy DemoDataDisclaimer on pages with mock/incomplete data
- Adopt PageHeader component universally across all pages
- Standardize active/inactive filtering to Switch pattern
- Fix dead buttons in Header (Help, Notifications)
- Remove hardcoded mock data from Settings
- Fix Sidebar controlled state and Kontooversigt navigation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 21:35:26 +01:00

64 lines
2.2 KiB
C#

using Books.Api.Domain.JournalEntryDrafts;
namespace Books.Api.EventFlow.ReadModels;
/// <summary>
/// DTO for reading journal entry draft data from the database.
/// </summary>
public class JournalEntryDraftReadModelDto
{
public string Id { get; set; } = string.Empty;
public string CompanyId { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
/// <summary>
/// Bilagsnummer - unique document number required by Bogføringsloven § 7, Stk. 4
/// </summary>
public string VoucherNumber { get; set; } = string.Empty;
/// <summary>
/// Bilagsdato - the date of the transaction/document (e.g., invoice date)
/// </summary>
public DateTime? DocumentDate { get; set; }
public string? Description { get; set; }
public string? FiscalYearId { get; set; }
public string Lines { get; set; } = "[]";
/// <summary>
/// JSON array of attachment IDs (bilag references)
/// </summary>
public string AttachmentIds { get; set; } = "[]";
public string Status { get; set; } = "active";
public string? TransactionId { get; set; }
/// <summary>
/// The exact timestamp when the draft was posted to the ledger.
/// </summary>
public DateTimeOffset? PostedAt { get; set; }
public string CreatedBy { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
/// <summary>
/// Full AI extraction data stored as JSON string.
/// Contains vendor CVR, amounts, VAT, due date, payment reference, line items, etc.
/// </summary>
public string? ExtractionData { get; set; }
/// <summary>
/// Deserialize lines from JSON to list of DraftLine objects.
/// </summary>
public List<DraftLine> GetLines()
{
if (string.IsNullOrEmpty(Lines))
return [];
return System.Text.Json.JsonSerializer.Deserialize<List<DraftLine>>(Lines) ?? [];
}
/// <summary>
/// Deserialize attachment IDs from JSON.
/// </summary>
public List<string> GetAttachmentIds()
{
if (string.IsNullOrEmpty(AttachmentIds))
return [];
return System.Text.Json.JsonSerializer.Deserialize<List<string>>(AttachmentIds) ?? [];
}
}