Phase D: Add Vendor Credits (AP cycle completion)

- VendorCredit, VendorCreditLineItem, VendorCreditApplication entities
- VendorCreditStatus enum (Open, PartiallyApplied, Applied, Voided)
- Migration AddVendorCredits: three new tables
- IUnitOfWork/UnitOfWork wired with all three repositories
- VendorCreditsController: Index (status tabs), Create, Details, Post, Apply, Void
- Post action: DR AP, CR each expense line (reverses original expense)
- Apply action: links credit to bill, updates Bill.AmountPaid and bill status
- Views: Index (summary cards + table), Create (dynamic line grid), Details (apply panel)
- Nav: Vendor Credits added to Finance section in _Layout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 00:03:14 -04:00
parent a33687f7bd
commit cf9dcfb4c1
13 changed files with 11387 additions and 3 deletions
@@ -200,3 +200,59 @@ public class JournalEntryLine : BaseEntity
public virtual JournalEntry JournalEntry { get; set; } = null!;
public virtual Account Account { get; set; } = null!;
}
/// <summary>
/// A credit note received from a vendor (returned goods, pricing dispute, short-ship).
/// Reduces Accounts Payable and reverses the original expense/COGS when posted.
/// Numbering: VC-YYMM-####
/// </summary>
public class VendorCredit : BaseEntity
{
public string CreditNumber { get; set; } = string.Empty;
public int VendorId { get; set; }
/// <summary>AP account this credit reduces (default: Accounts Payable 2000).</summary>
public int APAccountId { get; set; }
public DateTime CreditDate { get; set; } = DateTime.UtcNow;
public VendorCreditStatus Status { get; set; } = VendorCreditStatus.Open;
public decimal Total { get; set; }
public decimal RemainingAmount { get; set; }
public string? Memo { get; set; }
// Navigation
public virtual Vendor Vendor { get; set; } = null!;
public virtual Account APAccount { get; set; } = null!;
public virtual ICollection<VendorCreditLineItem> LineItems { get; set; } = new List<VendorCreditLineItem>();
public virtual ICollection<VendorCreditApplication> Applications { get; set; } = new List<VendorCreditApplication>();
}
/// <summary>
/// A single line on a vendor credit, each reversing a specific expense/COGS account.
/// </summary>
public class VendorCreditLineItem : BaseEntity
{
public int VendorCreditId { get; set; }
/// <summary>Expense/COGS account being reversed by this line.</summary>
public int? AccountId { get; set; }
public string Description { get; set; } = string.Empty;
public decimal Amount { get; set; }
// Navigation
public virtual VendorCredit VendorCredit { get; set; } = null!;
public virtual Account? Account { get; set; }
}
/// <summary>
/// Records the application of a vendor credit against a specific vendor bill.
/// No additional GL posting is needed — AP was already adjusted when the credit was posted.
/// </summary>
public class VendorCreditApplication : BaseEntity
{
public int VendorCreditId { get; set; }
public int BillId { get; set; }
public decimal Amount { get; set; }
public DateTime AppliedDate { get; set; } = DateTime.UtcNow;
// Navigation
public virtual VendorCredit VendorCredit { get; set; } = null!;
public virtual Bill Bill { get; set; } = null!;
}
@@ -80,6 +80,14 @@ public enum AccountingMethod
Accrual = 1
}
public enum VendorCreditStatus
{
Open = 0,
PartiallyApplied = 1,
Applied = 2,
Voided = 3
}
/// <summary>Lifecycle state of a Manual Journal Entry.</summary>
public enum JournalEntryStatus
{
@@ -95,6 +95,11 @@ public interface IUnitOfWork : IDisposable
IRepository<JournalEntry> JournalEntries { get; }
IRepository<JournalEntryLine> JournalEntryLines { get; }
// Vendor Credits
IRepository<VendorCredit> VendorCredits { get; }
IRepository<VendorCreditLineItem> VendorCreditLineItems { get; }
IRepository<VendorCreditApplication> VendorCreditApplications { get; }
// Notifications — typed repository for IgnoreQueryFilters-based history lookups
INotificationLogRepository NotificationLogs { get; }
IRepository<NotificationTemplate> NotificationTemplates { get; }