Phase G: Add Budgeting and Year-End Close
Budgeting: - Budget + BudgetLine entities with Jan–Dec monthly columns per GL account - BudgetsController: Index, Create, Edit, SetDefault, Copy, Delete - Copy action rolls a budget forward to a new fiscal year - Budget vs. Actual report (BudgetVsActual): compares monthly budget amounts to real P&L by calling GetProfitAndLossAsync once per month; variance shown as favorable/unfavorable; year + budget selectors in header - Views: Budgets/Index, Create, Edit with inline annual totals via budget-edit.js - Nav link + report card on Landing Year-End Close: - YearEndClose entity records each closed year + JE reference for audit trail - AccountsController.YearEndClose GET (history + form) + CloseYear POST - Close zeroes all Revenue and Expense/COGS account balances into Retained Earnings via IAccountBalanceService and posts a supporting JE dated Dec 31 - Idempotency: rejects attempt to close an already-closed year - Pre-close checklist in view to guide the workflow - Nav link under Finance Migration AddBudgetsAndYearEndClose applied Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -343,6 +343,13 @@ public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IDataPro
|
||||
/// <summary>One record per asset per period for each depreciation posting; soft-delete only.</summary>
|
||||
public DbSet<FixedAssetDepreciationEntry> FixedAssetDepreciationEntries { get; set; }
|
||||
|
||||
/// <summary>Named annual budgets with monthly amounts per GL account; tenant-filtered with soft delete.</summary>
|
||||
public DbSet<Budget> Budgets { get; set; }
|
||||
/// <summary>One row per account per Budget; contains Jan–Dec decimal columns.</summary>
|
||||
public DbSet<BudgetLine> BudgetLines { get; set; }
|
||||
/// <summary>Audit trail of completed year-end closes; tenant-filtered with soft delete.</summary>
|
||||
public DbSet<YearEndClose> YearEndCloses { get; set; }
|
||||
|
||||
/// <summary>Credit notes received from vendors (returned goods, pricing disputes); tenant-filtered with soft delete.</summary>
|
||||
public DbSet<VendorCredit> VendorCredits { get; set; }
|
||||
/// <summary>Expense-reversal line items on a vendor credit; soft-delete only.</summary>
|
||||
@@ -687,6 +694,27 @@ public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IDataPro
|
||||
.HasForeignKey(e => e.JournalEntryId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
// Budgets: tenant-filtered; BudgetLines soft-delete only
|
||||
modelBuilder.Entity<Budget>().HasQueryFilter(e =>
|
||||
!e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
|
||||
modelBuilder.Entity<BudgetLine>().HasQueryFilter(e => !e.IsDeleted);
|
||||
|
||||
// BudgetLine → Account: Restrict delete so removing an account doesn't cascade into budget data
|
||||
modelBuilder.Entity<BudgetLine>()
|
||||
.HasOne(bl => bl.Account)
|
||||
.WithMany()
|
||||
.HasForeignKey(bl => bl.AccountId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// YearEndClose: tenant-filtered; links to a specific JE
|
||||
modelBuilder.Entity<YearEndClose>().HasQueryFilter(e =>
|
||||
!e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
|
||||
modelBuilder.Entity<YearEndClose>()
|
||||
.HasOne(y => y.JournalEntry)
|
||||
.WithMany()
|
||||
.HasForeignKey(y => y.JournalEntryId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// Vendor Credits: tenant-filtered; child rows soft-delete only
|
||||
modelBuilder.Entity<VendorCredit>().HasQueryFilter(e =>
|
||||
!e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
|
||||
|
||||
Reference in New Issue
Block a user