using Books.Api.Domain; using Books.Api.Domain.JournalEntryDrafts; using AwesomeAssertions; namespace Books.Api.Tests.Domain; /// /// Unit tests for VatCalculationService. /// Tests Danish VAT calculation logic for SKAT compliance. /// [Trait("Category", "Unit")] public class VatCalculationServiceTests { private readonly VatCalculationService _sut = new(); private const string InputVatAccount = "5610"; // Købsmoms (indgående moms) private const string OutputVatAccount = "5611"; // Salgsmoms (udgående moms) #region Sales (U25) Tests [Fact] public void CalculateVat_SalesWithU25_Exclusive_CalculatesCorrectVat() { // Arrange - Sale of 1000 kr excl. VAT var lines = new List { new(1, "1000", 0m, 1000m, "Salg af varer", "U25") // Credit = sales revenue }; // Act var result = _sut.CalculateVat(lines, VatCalculationMode.Exclusive, InputVatAccount, OutputVatAccount); // Assert result.VatLines.Should().HaveCount(1); var vatLine = result.VatLines[0]; vatLine.CreditAmount.Should().Be(250m); // 25% of 1000 vatLine.DebitAmount.Should().Be(0m); vatLine.AccountId.Should().Be(OutputVatAccount); vatLine.VatCode.Should().Be("U25"); result.VatSummary.Should().HaveCount(1); result.VatSummary[0].BaseAmount.Should().Be(1000m); result.VatSummary[0].VatAmount.Should().Be(250m); result.VatSummary[0].IsInputVat.Should().BeFalse(); } [Fact] public void CalculateVat_SalesWithU25_Inclusive_ExtractsCorrectVat() { // Arrange - Sale of 1250 kr incl. VAT (1000 + 250 VAT) var lines = new List { new(1, "1000", 0m, 1250m, "Salg inkl moms", "U25") }; // Act var result = _sut.CalculateVat(lines, VatCalculationMode.Inclusive, InputVatAccount, OutputVatAccount); // Assert var vatLine = result.VatLines[0]; vatLine.CreditAmount.Should().Be(250m); // 1250 * 0.25 / 1.25 = 250 vatLine.DebitAmount.Should().Be(0m); result.VatSummary[0].BaseAmount.Should().Be(1000m); // 1250 - 250 result.VatSummary[0].VatAmount.Should().Be(250m); } #endregion #region Purchases (I25) Tests [Fact] public void CalculateVat_PurchaseWithI25_Exclusive_CalculatesCorrectVat() { // Arrange - Purchase of 1000 kr excl. VAT var lines = new List { new(1, "2000", 1000m, 0m, "Køb af varer", "I25") // Debit = expense }; // Act var result = _sut.CalculateVat(lines, VatCalculationMode.Exclusive, InputVatAccount, OutputVatAccount); // Assert result.VatLines.Should().HaveCount(1); var vatLine = result.VatLines[0]; vatLine.DebitAmount.Should().Be(250m); // 25% of 1000 - debit because it's an asset (receivable) vatLine.CreditAmount.Should().Be(0m); vatLine.AccountId.Should().Be(InputVatAccount); vatLine.VatCode.Should().Be("I25"); result.VatSummary[0].IsInputVat.Should().BeTrue(); } [Fact] public void CalculateVat_PurchaseWithI25_Inclusive_ExtractsCorrectVat() { // Arrange - Purchase of 1250 kr incl. VAT var lines = new List { new(1, "2000", 1250m, 0m, "Køb inkl moms", "I25") }; // Act var result = _sut.CalculateVat(lines, VatCalculationMode.Inclusive, InputVatAccount, OutputVatAccount); // Assert var vatLine = result.VatLines[0]; vatLine.DebitAmount.Should().Be(250m); vatLine.CreditAmount.Should().Be(0m); result.VatSummary[0].BaseAmount.Should().Be(1000m); result.VatSummary[0].VatAmount.Should().Be(250m); } #endregion #region No VAT Tests [Fact] public void CalculateVat_NoVatCode_ReturnsNoVatLines() { // Arrange var lines = new List { new(1, "1000", 0m, 1000m, "Momsfrit salg") // No VAT code }; // Act var result = _sut.CalculateVat(lines, VatCalculationMode.Exclusive, InputVatAccount, OutputVatAccount); // Assert result.VatLines.Should().BeEmpty(); result.VatSummary.Should().BeEmpty(); result.TotalVatAmount.Should().Be(0m); } [Fact] public void CalculateVat_WithINGEN_ReturnsNoVatLines() { // Arrange var lines = new List { new(1, "1000", 0m, 1000m, "Momsfrit", "INGEN") }; // Act var result = _sut.CalculateVat(lines, VatCalculationMode.Exclusive, InputVatAccount, OutputVatAccount); // Assert result.VatLines.Should().BeEmpty(); } [Fact] public void CalculateVat_WithUEU_ReturnsNoVatLines() { // Arrange - EU sale (no VAT charged) var lines = new List { new(1, "1000", 0m, 10000m, "EU-salg af varer", "UEU") }; // Act var result = _sut.CalculateVat(lines, VatCalculationMode.Exclusive, InputVatAccount, OutputVatAccount); // Assert - UEU has 0% rate, so no VAT line generated result.VatLines.Should().BeEmpty(); } #endregion #region Multiple Lines Tests [Fact] public void CalculateVat_MultipleLines_AggregatesByVatCode() { // Arrange var lines = new List { new(1, "1000", 0m, 1000m, "Salg 1", "U25"), new(2, "1000", 0m, 2000m, "Salg 2", "U25"), new(3, "2000", 500m, 0m, "Køb 1", "I25") }; // Act var result = _sut.CalculateVat(lines, VatCalculationMode.Exclusive, InputVatAccount, OutputVatAccount); // Assert result.VatLines.Should().HaveCount(3); // Check VAT summary aggregates correctly result.VatSummary.Should().HaveCount(2); // U25 and I25 var u25Summary = result.VatSummary.First(s => s.VatCode == "U25"); u25Summary.BaseAmount.Should().Be(3000m); // 1000 + 2000 u25Summary.VatAmount.Should().Be(750m); // 25% of 3000 var i25Summary = result.VatSummary.First(s => s.VatCode == "I25"); i25Summary.BaseAmount.Should().Be(500m); i25Summary.VatAmount.Should().Be(125m); // 25% of 500 } #endregion #region Rounding Tests [Fact] public void CalculateVat_OddAmount_RoundsCorrectly() { // Arrange - Amount that results in rounding var lines = new List { new(1, "1000", 0m, 100.01m, "Salg med øreafrunding", "U25") }; // Act var result = _sut.CalculateVat(lines, VatCalculationMode.Exclusive, InputVatAccount, OutputVatAccount); // Assert // 100.01 * 0.25 = 25.0025, rounded to 25.00 result.VatLines[0].CreditAmount.Should().Be(25.00m); } [Fact] public void CalculateVat_InclusiveOddAmount_RoundsCorrectly() { // Arrange - 125.01 incl VAT var lines = new List { new(1, "1000", 0m, 125.01m, "Salg inkl moms", "U25") }; // Act var result = _sut.CalculateVat(lines, VatCalculationMode.Inclusive, InputVatAccount, OutputVatAccount); // Assert // 125.01 * 0.25 / 1.25 = 25.002, rounded to 25.00 result.VatLines[0].CreditAmount.Should().Be(25.00m); result.VatSummary[0].BaseAmount.Should().Be(100.01m); // 125.01 - 25.00 } #endregion #region Total VAT Tests [Fact] public void CalculateVat_MixedInputOutput_CalculatesTotalCorrectly() { // Arrange - Sales and purchases var lines = new List { new(1, "1000", 0m, 1000m, "Salg", "U25"), // Output VAT: 250 credit new(2, "2000", 400m, 0m, "Køb", "I25") // Input VAT: 100 debit }; // Act var result = _sut.CalculateVat(lines, VatCalculationMode.Exclusive, InputVatAccount, OutputVatAccount); // Assert // Total = debit (input VAT asset) - credit (output VAT liability) // = 100 - 250 = -150 (negative means net VAT payable to SKAT) result.TotalVatAmount.Should().Be(-150m); } #endregion }