Phase G: Add Recurring Transactions (BackgroundService + CRUD UI)
- RecurringTemplate entity with Frequency/IntervalCount/NextFireDate/EndDate/MaxOccurrences/TemplateData JSON - RecurringFrequency + RecurringTemplateType enums - RecurringTransactionService BackgroundService: hourly check, creates Draft bills or immediate expenses, advances NextFireDate, auto-deactivates on limits - RecurringTemplatesController: Index/Create/Edit/ToggleActive/Delete/GenerateNow (on-demand fire) - Three views + external JS for type-toggle and dynamic bill line items - Finance sidebar nav: Recurring Transactions - Migration: AddRecurringTemplates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -286,6 +286,38 @@ public class VendorCreditApplication : BaseEntity
|
||||
public virtual Bill Bill { get; set; } = null!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A saved recipe for a document that should be automatically created on a recurring schedule.
|
||||
/// The <see cref="TemplateData"/> column stores a JSON blob whose schema depends on
|
||||
/// <see cref="TemplateType"/>: see <c>RecurringTransactionService</c> for the exact shape.
|
||||
/// <para>
|
||||
/// Bills are created as Draft so the user can review before posting.
|
||||
/// Expenses are created immediately (already-paid transactions).
|
||||
/// </para>
|
||||
/// Numbering: REC-YYMM-####
|
||||
/// </summary>
|
||||
public class RecurringTemplate : BaseEntity
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public RecurringTemplateType TemplateType { get; set; }
|
||||
public RecurringFrequency Frequency { get; set; }
|
||||
/// <summary>Every N periods. E.g. Frequency=Monthly, IntervalCount=3 → quarterly.</summary>
|
||||
public int IntervalCount { get; set; } = 1;
|
||||
/// <summary>UTC date when the template will next fire. Set to the desired first occurrence date on creation.</summary>
|
||||
public DateTime NextFireDate { get; set; }
|
||||
/// <summary>Optional UTC date after which no further occurrences are generated.</summary>
|
||||
public DateTime? EndDate { get; set; }
|
||||
/// <summary>Optional hard cap on total occurrences. Null = unlimited.</summary>
|
||||
public int? MaxOccurrences { get; set; }
|
||||
/// <summary>How many documents have been generated so far.</summary>
|
||||
public int OccurrenceCount { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
/// <summary>JSON payload whose schema matches the TemplateType. See RecurringTransactionService.</summary>
|
||||
public string TemplateData { get; set; } = "{}";
|
||||
/// <summary>Last error from the background service, cleared on next successful fire.</summary>
|
||||
public string? LastError { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A named tax rate (e.g., "CA Sales Tax 8.25%") used to pre-fill the TaxPercent field on
|
||||
/// invoices when a taxable customer is selected. Companies can define multiple rates for
|
||||
|
||||
@@ -94,6 +94,26 @@ public enum VendorCreditStatus
|
||||
Voided = 3
|
||||
}
|
||||
|
||||
/// <summary>Source document type for a recurring template — controls which entity is created on each fire.</summary>
|
||||
public enum RecurringTemplateType
|
||||
{
|
||||
/// <summary>Creates a vendor Bill (Draft, pending user review).</summary>
|
||||
Bill = 1,
|
||||
/// <summary>Creates a direct Expense entry (immediately recorded).</summary>
|
||||
Expense = 2
|
||||
}
|
||||
|
||||
/// <summary>How often a recurring template fires.</summary>
|
||||
public enum RecurringFrequency
|
||||
{
|
||||
Daily = 1,
|
||||
Weekly = 2,
|
||||
BiWeekly = 3,
|
||||
Monthly = 4,
|
||||
Quarterly = 5,
|
||||
Annually = 6
|
||||
}
|
||||
|
||||
/// <summary>Lifecycle state of a Manual Journal Entry.</summary>
|
||||
public enum JournalEntryStatus
|
||||
{
|
||||
|
||||
@@ -106,6 +106,9 @@ public interface IUnitOfWork : IDisposable
|
||||
// Tax Rates
|
||||
IRepository<TaxRate> TaxRates { get; }
|
||||
|
||||
// Recurring Transactions
|
||||
IRepository<RecurringTemplate> RecurringTemplates { get; }
|
||||
|
||||
// Notifications — typed repository for IgnoreQueryFilters-based history lookups
|
||||
INotificationLogRepository NotificationLogs { get; }
|
||||
IRepository<NotificationTemplate> NotificationTemplates { get; }
|
||||
|
||||
Reference in New Issue
Block a user