using Books.Api.Domain; using Books.Api.Domain.JournalEntryDrafts; using Books.Api.EventFlow.Repositories; using EventFlow.Commands; namespace Books.Api.Commands.JournalEntryDrafts; public class CreateJournalEntryDraftCommandHandler : CommandHandler { public override Task ExecuteAsync( JournalEntryDraftAggregate aggregate, CreateJournalEntryDraftCommand command, CancellationToken cancellationToken) { aggregate.Create( command.CompanyId, command.Name, command.CreatedBy, command.VoucherNumber, command.ExtractionData); return Task.CompletedTask; } } public class UpdateJournalEntryDraftCommandHandler : CommandHandler { public override Task ExecuteAsync( JournalEntryDraftAggregate aggregate, UpdateJournalEntryDraftCommand command, CancellationToken cancellationToken) { aggregate.Update( command.Name, command.DocumentDate, command.Description, command.FiscalYearId, command.Lines, command.AttachmentIds); return Task.CompletedTask; } } /// /// Command handler for posting a journal entry draft. /// Validates fiscal year status and date range before allowing the post. /// public class MarkJournalEntryDraftPostedCommandHandler( IJournalEntryDraftRepository draftRepository, IFiscalYearRepository fiscalYearRepository) : CommandHandler { public override async Task ExecuteAsync( JournalEntryDraftAggregate aggregate, MarkJournalEntryDraftPostedCommand command, CancellationToken cancellationToken) { // Load the draft read model to get fiscal year and document date var draft = await draftRepository.GetByIdAsync( aggregate.Id.Value, cancellationToken); var fiscalYearId = draft?.FiscalYearId ?? aggregate.FiscalYearId; // Validate fiscal year is set if (string.IsNullOrWhiteSpace(fiscalYearId)) { throw new DomainException( "FISCAL_YEAR_REQUIRED", "Fiscal year is required for posting a journal entry", "Regnskabsår er påkrævet for bogføring af en postering"); } // 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."); } // Validate document date falls within fiscal year range (if document date is set) if (draft?.DocumentDate != null) { var documentDate = DateOnly.FromDateTime(draft.DocumentDate.Value); var fyStart = DateOnly.FromDateTime(fiscalYear.StartDate); var fyEnd = DateOnly.FromDateTime(fiscalYear.EndDate); if (documentDate < fyStart || documentDate > fyEnd) { throw new DomainException( "DOCUMENT_DATE_OUTSIDE_FISCAL_YEAR", $"Document date {documentDate:yyyy-MM-dd} falls outside the fiscal year ({fyStart:yyyy-MM-dd} to {fyEnd:yyyy-MM-dd})", $"Bilagsdato {documentDate:yyyy-MM-dd} ligger uden for regnskabsåret ({fyStart:yyyy-MM-dd} til {fyEnd:yyyy-MM-dd})"); } } aggregate.MarkPosted( command.TransactionId, command.PostedBy); } } public class DiscardJournalEntryDraftCommandHandler : CommandHandler { public override Task ExecuteAsync( JournalEntryDraftAggregate aggregate, DiscardJournalEntryDraftCommand command, CancellationToken cancellationToken) { aggregate.Discard(command.DiscardedBy); return Task.CompletedTask; } }