Phase F: Add Invoice Write-Off, Fixed Assets, Period Locking, and 1099 Tracking
- Invoice Write-Off: WriteOff POST action in InvoicesController posts bad-debt JE (DR bad debt expense / CR AR), reduces customer balance, marks invoice WrittenOff; write-off modal added to Invoice Details view with expense account selector - Fixed Assets: FixedAsset + FixedAssetDepreciationEntry entities with straight-line depreciation; FixedAssetsController (Index/Create/Edit/Details/PostDepreciation/Delete); PostDepreciation auto-generates one JE per asset per period, skips already-posted, fully-depreciated, and disposed assets; full CRUD views + nav link - Period Locking: Company.BookLockedThrough field; AccountingPeriodValidator static helper; lock check added to JE Post and Bill Create (blocks backdating into closed periods); SetPeriodLock action + date picker UI in Company Settings Accounting section - 1099 Tracking: Is1099Vendor flag on Vendor entity + DTOs; checkbox in Create/Edit views; TaxReporting1099 report action + view lists payments by year, flags vendors >= $600; report card added to Reports Landing - Migration AddFixedAssetsLockAnd1099 applied Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2118,6 +2118,68 @@ public class ReportsController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
// GET: /Reports/TaxReporting1099
|
||||
/// <summary>
|
||||
/// 1099-NEC report: sums all bill payments + expenses paid to vendors marked Is1099Vendor=true
|
||||
/// for the selected calendar year. Flags vendors that exceed the $600 reporting threshold.
|
||||
/// </summary>
|
||||
public async Task<IActionResult> TaxReporting1099(int? year)
|
||||
{
|
||||
if (!AllowAccounting()) return RedirectToAction(nameof(Landing));
|
||||
|
||||
var companyId = int.TryParse(User.FindFirst("CompanyId")?.Value, out var _rcid) ? _rcid : 0;
|
||||
var reportYear = year ?? DateTime.Now.Year;
|
||||
|
||||
var periodStart = new DateTime(reportYear, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
var periodEnd = new DateTime(reportYear, 12, 31, 23, 59, 59, DateTimeKind.Utc);
|
||||
|
||||
// Load 1099-eligible vendors
|
||||
var vendors = (await _unitOfWork.Vendors.FindAsync(v => v.Is1099Vendor)).ToList();
|
||||
|
||||
var rows = new List<Vendor1099Row>();
|
||||
|
||||
foreach (var vendor in vendors)
|
||||
{
|
||||
// Sum bills paid (using bill payment records) within the year
|
||||
var bills = await _unitOfWork.Bills.FindAsync(
|
||||
b => b.VendorId == vendor.Id,
|
||||
false,
|
||||
b => b.Payments);
|
||||
|
||||
decimal billPaid = bills
|
||||
.SelectMany(b => b.Payments)
|
||||
.Where(p => p.PaymentDate >= periodStart && p.PaymentDate <= periodEnd)
|
||||
.Sum(p => p.Amount);
|
||||
|
||||
// Sum direct expenses for this vendor within the year
|
||||
var expenses = await _unitOfWork.Expenses.FindAsync(
|
||||
e => e.VendorId == vendor.Id && e.Date >= periodStart && e.Date <= periodEnd);
|
||||
|
||||
decimal expensePaid = expenses.Sum(e => e.Amount);
|
||||
|
||||
var total = billPaid + expensePaid;
|
||||
|
||||
rows.Add(new Vendor1099Row
|
||||
{
|
||||
VendorId = vendor.Id,
|
||||
VendorName = vendor.CompanyName,
|
||||
TaxId = vendor.TaxId,
|
||||
Address = string.Join(", ", new[] { vendor.Address, vendor.City, vendor.State, vendor.ZipCode }
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s))),
|
||||
BillsPaid = billPaid,
|
||||
ExpensesPaid = expensePaid,
|
||||
TotalPaid = total,
|
||||
NeedsForm = total >= 600m
|
||||
});
|
||||
}
|
||||
|
||||
ViewBag.ReportYear = reportYear;
|
||||
ViewBag.AvailableYears = Enumerable.Range(DateTime.Now.Year - 5, 6).OrderDescending().ToList();
|
||||
ViewBag.VendorsOver600 = rows.Count(r => r.NeedsForm);
|
||||
|
||||
return View(rows.OrderByDescending(r => r.TotalPaid).ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up the current tenant's company name from the CompanyId claim. Used to inject the
|
||||
/// company name into AI prompts so the generated text refers to the actual business, not a
|
||||
@@ -2245,3 +2307,15 @@ public class AnalyticsDashboardViewModel
|
||||
public int SelectedMonths { get; set; } = 6;
|
||||
}
|
||||
|
||||
public class Vendor1099Row
|
||||
{
|
||||
public int VendorId { get; set; }
|
||||
public string VendorName { get; set; } = string.Empty;
|
||||
public string? TaxId { get; set; }
|
||||
public string? Address { get; set; }
|
||||
public decimal BillsPaid { get; set; }
|
||||
public decimal ExpensesPaid { get; set; }
|
||||
public decimal TotalPaid { get; set; }
|
||||
public bool NeedsForm { get; set; }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user