using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json; using Books.Api.Domain.JournalEntryDrafts; using Books.Api.Domain.JournalEntryDrafts.Events; using EventFlow.Aggregates; using EventFlow.PostgreSql.ReadStores.Attributes; using EventFlow.ReadStores; namespace Books.Api.EventFlow.ReadModels; [Table("journal_entry_draft_read_models")] public class JournalEntryDraftReadModel : IReadModel, IAmReadModelFor, IAmReadModelFor, IAmReadModelFor, IAmReadModelFor { [PostgreSqlReadModelIdentityColumn] public string AggregateId { get; set; } = string.Empty; public DateTimeOffset CreateTime { get; set; } public DateTimeOffset UpdatedTime { get; set; } [PostgreSqlReadModelVersionColumn] public int LastAggregateSequenceNumber { get; set; } // Business fields public string CompanyId { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; /// /// Bilagsnummer - unique document number per company (required by Bogføringsloven § 7) /// public string VoucherNumber { get; set; } = string.Empty; /// /// Bilagsdato - the date of the transaction/document (e.g., invoice date) /// public DateTime? DocumentDate { get; set; } public string? Description { get; set; } public string? FiscalYearId { get; set; } /// /// JSON array of posting lines with VAT codes /// public string Lines { get; set; } = "[]"; /// /// JSON array of attachment IDs (bilag references, required by Bogføringsloven § 6) /// public string AttachmentIds { get; set; } = "[]"; public string Status { get; set; } = "active"; public string? TransactionId { get; set; } /// /// The exact timestamp when the draft was posted to the ledger. /// public DateTimeOffset? PostedAt { get; set; } public string CreatedBy { get; set; } = string.Empty; /// /// Full AI extraction data stored as JSON string. /// Contains vendor CVR, amounts, VAT, due date, payment reference, line items, etc. /// public string? ExtractionData { get; set; } public Task ApplyAsync( IReadModelContext context, IDomainEvent domainEvent, CancellationToken cancellationToken) { var e = domainEvent.AggregateEvent; AggregateId = domainEvent.AggregateIdentity.Value; CreateTime = domainEvent.Timestamp; UpdatedTime = domainEvent.Timestamp; LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber; CompanyId = e.CompanyId; Name = e.Name; VoucherNumber = e.VoucherNumber; CreatedBy = e.CreatedBy; Status = "active"; Lines = "[]"; AttachmentIds = "[]"; ExtractionData = e.ExtractionData; return Task.CompletedTask; } public Task ApplyAsync( IReadModelContext context, IDomainEvent domainEvent, CancellationToken cancellationToken) { var e = domainEvent.AggregateEvent; UpdatedTime = domainEvent.Timestamp; LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber; if (e.Name != null) Name = e.Name; DocumentDate = e.DocumentDate?.ToDateTime(TimeOnly.MinValue); Description = e.Description; FiscalYearId = e.FiscalYearId; Lines = JsonSerializer.Serialize(e.Lines); AttachmentIds = JsonSerializer.Serialize(e.AttachmentIds); return Task.CompletedTask; } public Task ApplyAsync( IReadModelContext context, IDomainEvent domainEvent, CancellationToken cancellationToken) { UpdatedTime = domainEvent.Timestamp; LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber; Status = "posted"; TransactionId = domainEvent.AggregateEvent.TransactionId; PostedAt = domainEvent.AggregateEvent.PostedAt; return Task.CompletedTask; } public Task ApplyAsync( IReadModelContext context, IDomainEvent domainEvent, CancellationToken cancellationToken) { UpdatedTime = domainEvent.Timestamp; LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber; Status = "discarded"; return Task.CompletedTask; } }