Phase C: Add Manual Journal Entries (double-entry GL)
- JournalEntry + JournalEntryLine entities with Draft/Posted/Reversed lifecycle - JournalEntryStatus enum (Draft, Posted, Reversed) - Migration AddJournalEntries: two new tables with self-referencing reversal FK - IUnitOfWork/UnitOfWork wired with JournalEntries + JournalEntryLines repos - ApplicationDbContext: DbSets, tenant query filters, reversal FK config - LedgerService: JE lines added as 10th source in GetAccountLedgerAsync and ComputePriorBalanceAsync - JournalEntriesController: Index (All/Draft/Posted tabs), Create, Details, Post, Reverse, Delete - Views: Index, Create (dynamic balanced line grid with running debit/credit totals), Details - journal-entry-create.js: dynamic line management with balance indicator - Nav: Journal Entries added to Finance section in _Layout Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -156,3 +156,47 @@ public class Expense : BaseEntity
|
||||
public virtual Account PaymentAccount { get; set; } = null!;
|
||||
public virtual Job? Job { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manual double-entry journal entry. Lines must balance (sum of debits == sum of credits)
|
||||
/// before posting. Once posted the entry is immutable — use Reverse to correct it.
|
||||
/// Entry numbering follows the pattern JE-YYMM-#### scoped per company.
|
||||
/// </summary>
|
||||
public class JournalEntry : BaseEntity
|
||||
{
|
||||
public string EntryNumber { get; set; } = string.Empty;
|
||||
public DateTime EntryDate { get; set; } = DateTime.UtcNow;
|
||||
public string? Reference { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public JournalEntryStatus Status { get; set; } = JournalEntryStatus.Draft;
|
||||
|
||||
/// <summary>True if this entry was machine-generated as a reversal of another entry.</summary>
|
||||
public bool IsReversal { get; set; } = false;
|
||||
/// <summary>FK to the original entry being reversed. Null for normal entries.</summary>
|
||||
public int? ReversalOfId { get; set; }
|
||||
|
||||
public DateTime? PostedAt { get; set; }
|
||||
public string? PostedBy { get; set; }
|
||||
|
||||
// Navigation
|
||||
public virtual ICollection<JournalEntryLine> Lines { get; set; } = new List<JournalEntryLine>();
|
||||
public virtual JournalEntry? ReversalOf { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One debit or credit line within a <see cref="JournalEntry"/>. Either DebitAmount or CreditAmount
|
||||
/// should be non-zero per line (not both). LineOrder controls display sequence.
|
||||
/// </summary>
|
||||
public class JournalEntryLine : BaseEntity
|
||||
{
|
||||
public int JournalEntryId { get; set; }
|
||||
public int AccountId { get; set; }
|
||||
public decimal DebitAmount { get; set; }
|
||||
public decimal CreditAmount { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public int LineOrder { get; set; }
|
||||
|
||||
// Navigation
|
||||
public virtual JournalEntry JournalEntry { get; set; } = null!;
|
||||
public virtual Account Account { get; set; } = null!;
|
||||
}
|
||||
|
||||
@@ -79,3 +79,14 @@ public enum AccountingMethod
|
||||
/// <summary>Revenue and expenses recognised when earned/incurred (default).</summary>
|
||||
Accrual = 1
|
||||
}
|
||||
|
||||
/// <summary>Lifecycle state of a Manual Journal Entry.</summary>
|
||||
public enum JournalEntryStatus
|
||||
{
|
||||
/// <summary>Not yet posted — can still be edited or deleted.</summary>
|
||||
Draft = 0,
|
||||
/// <summary>Posted to the GL — immutable; can only be reversed.</summary>
|
||||
Posted = 1,
|
||||
/// <summary>A reversal JE has been created and posted for this entry.</summary>
|
||||
Reversed = 2
|
||||
}
|
||||
|
||||
@@ -91,6 +91,10 @@ public interface IUnitOfWork : IDisposable
|
||||
IRepository<BillPayment> BillPayments { get; }
|
||||
IRepository<Expense> Expenses { get; }
|
||||
|
||||
// Manual Journal Entries
|
||||
IRepository<JournalEntry> JournalEntries { get; }
|
||||
IRepository<JournalEntryLine> JournalEntryLines { get; }
|
||||
|
||||
// Notifications — typed repository for IgnoreQueryFilters-based history lookups
|
||||
INotificationLogRepository NotificationLogs { get; }
|
||||
IRepository<NotificationTemplate> NotificationTemplates { get; }
|
||||
|
||||
Reference in New Issue
Block a user