b7eb95056d
Adds I1099FormService and Form1099FormService: an IRIS/accountant filing-data CSV (one row per reportable recipient) and a plain-paper recipient Copy B 1099-NEC PDF rendered via the DevExpress RichEdit/Office API (mirroring CheckPrintService). Includes a CSV-export unit test over a stub report service. Service lives in namespace ROLAC.API.Services (not ...Services.Form1099) to avoid shadowing the ROLAC.API.Entities.Form1099 constants class. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
74 lines
3.0 KiB
C#
74 lines
3.0 KiB
C#
using System.Globalization;
|
|
using System.Text;
|
|
using ROLAC.API.DTOs.Finance;
|
|
using ROLAC.API.Services;
|
|
using Xunit;
|
|
|
|
namespace ROLAC.API.Tests.Services;
|
|
|
|
public class Form1099FormServiceTests
|
|
{
|
|
/// <summary>Stub report service: only GetAnnualSummaryAsync is exercised by the CSV export.</summary>
|
|
private sealed class StubReportService : IForm1099ReportService
|
|
{
|
|
private readonly Form1099SummaryDto _summary;
|
|
public StubReportService(Form1099SummaryDto summary) => _summary = summary;
|
|
|
|
public Task<Form1099SummaryDto> GetAnnualSummaryAsync(int taxYear) => Task.FromResult(_summary);
|
|
public Task<List<Form1099BoxDto>> GetBoxesAsync() => throw new NotImplementedException();
|
|
public Task<Form1099RecipientDetailDto?> GetRecipientDetailAsync(int payeeId, int taxYear)
|
|
=> throw new NotImplementedException();
|
|
}
|
|
|
|
private static Form1099FormService BuildService(Form1099SummaryDto summary) =>
|
|
// IPayee1099Service and AppDbContext are only used by RenderCopyBAsync, not by the CSV path.
|
|
new Form1099FormService(new StubReportService(summary), payees: null!, db: null!);
|
|
|
|
[Fact]
|
|
public async Task ExportFilingCsvAsync_WritesHeaderRowPerRecipientAndInvariantNumbers()
|
|
{
|
|
var summary = new Form1099SummaryDto
|
|
{
|
|
TaxYear = 2026,
|
|
Rows =
|
|
{
|
|
new Form1099RecipientRowDto
|
|
{
|
|
PayeeId = 1, LegalName = "Acme, LLC", TinLast4 = "1234", W9Status = "OnFile",
|
|
NecTotal = 1234.50m, RentsTotal = 0m, GrandTotal = 1234.50m, MeetsThreshold = true
|
|
},
|
|
new Form1099RecipientRowDto
|
|
{
|
|
PayeeId = 2, LegalName = "Bob Smith", TinLast4 = "9876", W9Status = "Missing",
|
|
NecTotal = 100m, RentsTotal = 50m, GrandTotal = 150m, MeetsThreshold = false
|
|
},
|
|
}
|
|
};
|
|
|
|
var service = BuildService(summary);
|
|
var (stream, contentType, fileName) = await service.ExportFilingCsvAsync(2026);
|
|
|
|
Assert.Equal("text/csv", contentType);
|
|
Assert.Equal("1099-filing-2026.csv", fileName);
|
|
|
|
using var reader = new StreamReader(stream, Encoding.UTF8);
|
|
var text = await reader.ReadToEndAsync();
|
|
var lines = text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
// Header + one data line per row.
|
|
Assert.Equal(3, lines.Length);
|
|
Assert.Equal("LegalName,TinLast4,W9Status,Box1_NEC,Box1_Rents,Total,MeetsThreshold", lines[0]);
|
|
|
|
// A value containing a comma is quoted.
|
|
Assert.StartsWith("\"Acme, LLC\",1234,OnFile,", lines[1]);
|
|
|
|
// Invariant numeric formatting (period decimal separator) and Y/N threshold flag.
|
|
Assert.Contains("1234.50", lines[1]);
|
|
Assert.EndsWith(",Y", lines[1]);
|
|
Assert.EndsWith(",N", lines[2]);
|
|
|
|
// Sanity: the period really is the invariant separator regardless of current culture.
|
|
Assert.Equal("1234.50", 1234.50m.ToString(CultureInfo.InvariantCulture));
|
|
}
|
|
}
|