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:
@@ -0,0 +1,56 @@
|
||||
// Recurring template create/edit form — type toggle + dynamic bill line items
|
||||
|
||||
function initRecurringForm(initialType) {
|
||||
const radios = document.querySelectorAll('input[name="TemplateType"]');
|
||||
radios.forEach(r => r.addEventListener('change', () => updateSections(parseInt(r.value))));
|
||||
updateSections(initialType);
|
||||
}
|
||||
|
||||
function updateSections(type) {
|
||||
const billSection = document.getElementById('billSection');
|
||||
const expSection = document.getElementById('expenseSection');
|
||||
if (type === 1) {
|
||||
billSection.classList.remove('d-none');
|
||||
expSection.classList.add('d-none');
|
||||
} else {
|
||||
billSection.classList.add('d-none');
|
||||
expSection.classList.remove('d-none');
|
||||
}
|
||||
}
|
||||
|
||||
function addBillLine() {
|
||||
const container = document.getElementById('billLines');
|
||||
const idx = container.querySelectorAll('.bill-line').length;
|
||||
|
||||
const accountOptions = (window.allExpenseAccounts || [])
|
||||
.map(a => `<option value="${a.value}">${a.text}</option>`)
|
||||
.join('');
|
||||
|
||||
const html = `
|
||||
<div class="row g-2 mb-2 bill-line">
|
||||
<div class="col-4">
|
||||
<select name="LineItems[${idx}].AccountId" class="form-select form-select-sm">
|
||||
<option value="">— Account —</option>${accountOptions}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input name="LineItems[${idx}].Description" type="text" class="form-control form-control-sm"
|
||||
placeholder="Description" />
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input name="LineItems[${idx}].Quantity" type="number" step="0.01" min="0"
|
||||
class="form-control form-control-sm" placeholder="Qty" value="1" />
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<input name="LineItems[${idx}].UnitPrice" type="number" step="0.01" min="0"
|
||||
class="form-control form-control-sm" placeholder="Price" />
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger"
|
||||
onclick="this.closest('.bill-line').remove()">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
container.insertAdjacentHTML('beforeend', html);
|
||||
}
|
||||
Reference in New Issue
Block a user