Skip to content

Commit

Permalink
Use tax configurations stored inside receipts.
Browse files Browse the repository at this point in the history
  • Loading branch information
Utar94 committed Apr 20, 2024
1 parent 3d46281 commit 6e6ddf7
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 53 deletions.
32 changes: 14 additions & 18 deletions backend/src/Faktur.Domain/Receipts/ReceiptAggregate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,12 @@ private static IReadOnlyDictionary<string, ReceiptTaxUnit> InitializeTaxes(IEnum
return receiptTaxes;
}

public void Calculate(IEnumerable<TaxAggregate> taxes, ActorId actorId = default)
public void Calculate(IEnumerable<TaxAggregate> _, ActorId actorId = default)
{
Dictionary<string, decimal> taxableAmounts = [];
foreach (TaxAggregate tax in taxes)
foreach (KeyValuePair<string, ReceiptTaxUnit> tax in _taxes)
{
taxableAmounts[tax.Code.Value] = 0.0m;
taxableAmounts[tax.Key] = 0.00m;
}

decimal subTotal = 0m;
Expand All @@ -161,33 +161,29 @@ public void Calculate(IEnumerable<TaxAggregate> taxes, ActorId actorId = default

if (item.Flags != null)
{
foreach (TaxAggregate tax in taxes)
foreach (KeyValuePair<string, ReceiptTaxUnit> tax in _taxes)
{
if (item.IsTaxable(tax))
if (item.IsTaxable(tax.Value))
{
taxableAmounts[tax.Code.Value] += item.Price;
taxableAmounts[tax.Key] += item.Price;
}
}
}
}

decimal total = subTotal;
Dictionary<string, ReceiptTaxUnit> receiptTaxes = [];
foreach (TaxAggregate tax in taxes)
foreach (KeyValuePair<string, ReceiptTaxUnit> tax in _taxes)
{
if (tax.Flags != null)
{
decimal taxableAmount = taxableAmounts[tax.Code.Value];
if (taxableAmount > 0)
{
decimal amount = Math.Round((decimal)tax.Rate * taxableAmount, 2);
ReceiptTaxUnit receiptTax = new(tax.Flags, tax.Rate, taxableAmount, amount);
receiptTaxes[tax.Code.Value] = receiptTax;
total += receiptTax.Amount;
}
}
decimal taxableAmount = Math.Round(taxableAmounts[tax.Key], 2);
decimal amount = Math.Round((decimal)tax.Value.Rate * taxableAmount, 2);
ReceiptTaxUnit receiptTax = new(tax.Value.Flags, tax.Value.Rate, taxableAmount, amount);
receiptTaxes[tax.Key] = receiptTax;
total += receiptTax.Amount;
}

subTotal = Math.Round(subTotal, 2);
total = Math.Round(total, 2);
Raise(new ReceiptCalculatedEvent(subTotal, receiptTaxes, total), actorId);
}
protected virtual void Apply(ReceiptCalculatedEvent @event)
Expand Down
5 changes: 2 additions & 3 deletions backend/src/Faktur.Domain/Receipts/ReceiptItemUnit.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Faktur.Domain.Articles;
using Faktur.Domain.Products;
using Faktur.Domain.Stores;
using Faktur.Domain.Taxes;
using FluentValidation;
using Logitar.Identity.Domain.Shared;

Expand Down Expand Up @@ -40,9 +39,9 @@ public ReceiptItemUnit(GtinUnit? gtin, SkuUnit? sku, DisplayNameUnit label, Flag
new ReceiptItemValidator().ValidateAndThrow(this);
}

public bool IsTaxable(TaxAggregate tax)
public bool IsTaxable(ReceiptTaxUnit tax)
{
if (Flags != null && tax.Flags != null)
if (Flags != null)
{
foreach (char flag in Flags.Value)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@ namespace Faktur.Domain.Receipts;
[Trait(Traits.Category, Categories.Unit)]
public class ReceiptAggregateTests
{
private readonly ReceiptAggregate _receipt;
private readonly TaxAggregate _gst;
private readonly TaxAggregate _hst;
private readonly TaxAggregate _qst;

private readonly StoreAggregate _store;
private readonly ReceiptAggregate _receipt;

public ReceiptAggregateTests()
{
StoreAggregate store = new(new DisplayNameUnit("Maxi Drummondville"));
_receipt = new(store);

_gst = new(new TaxCodeUnit("GST"), rate: 0.05d)
{
Flags = new FlagsUnit("F")
Expand All @@ -31,32 +30,41 @@ public ReceiptAggregateTests()
{
Flags = new FlagsUnit("P")
};

_store = new(new DisplayNameUnit("Maxi Drummondville"));
_receipt = new(_store);
}

[Fact(DisplayName = "Calculate: it should calculate the correct taxes.")]
public void Calculate_it_should_calculate_the_correct_taxes()
{
_receipt.SetItem(1, new ReceiptItemUnit(new GtinUnit("06038385904"), sku: null, new DisplayNameUnit("PC POULET BBQ"), new FlagsUnit("FPMRJ"),
ReceiptAggregate receipt = new(_store, _receipt.IssuedOn, _receipt.Number, taxes: [_gst, _hst, _qst]);

receipt.SetItem(1, new ReceiptItemUnit(new GtinUnit("06038385904"), sku: null, new DisplayNameUnit("PC POULET BBQ"), new FlagsUnit("FPMRJ"),
quantity: 1.0d, unitPrice: 9.99m, price: 9.99m, departmentNumber: null, department: null));
_receipt.SetItem(2, new ReceiptItemUnit(new GtinUnit("05749601228"), sku: null, new DisplayNameUnit("KCALM LAUGH BIO"), new FlagsUnit("FRQ"),
receipt.SetItem(2, new ReceiptItemUnit(new GtinUnit("05749601228"), sku: null, new DisplayNameUnit("KCALM LAUGH BIO"), new FlagsUnit("FRQ"),
quantity: 1.0d, unitPrice: 10.29m, price: 9.29m, departmentNumber: null, department: null));
_receipt.SetItem(3, new ReceiptItemUnit(new GtinUnit("4011"), sku: null, new DisplayNameUnit("BANANES"), new FlagsUnit("MRJ"),
receipt.SetItem(3, new ReceiptItemUnit(new GtinUnit("4011"), sku: null, new DisplayNameUnit("BANANES"), new FlagsUnit("MRJ"),
quantity: 1.150d, unitPrice: 1.52m, price: 1.75m, departmentNumber: null, department: null));

_receipt.Calculate([_gst, _hst, _qst]);
Assert.Equal(21.03m, _receipt.SubTotal);
receipt.Calculate([_gst, _hst, _qst]);
Assert.Equal(21.03m, receipt.SubTotal);

Assert.Equal(3, receipt.Taxes.Count);

Assert.Equal(2, _receipt.Taxes.Count);
Assert.Equal(_gst.Rate, receipt.Taxes[_gst.Code.Value].Rate);
Assert.Equal(19.28m, receipt.Taxes[_gst.Code.Value].TaxableAmount);
Assert.Equal(0.96m, receipt.Taxes[_gst.Code.Value].Amount);

Assert.Equal(_gst.Rate, _receipt.Taxes[_gst.Code.Value].Rate);
Assert.Equal(19.28m, _receipt.Taxes[_gst.Code.Value].TaxableAmount);
Assert.Equal(0.96m, _receipt.Taxes[_gst.Code.Value].Amount);
Assert.Equal(_hst.Rate, receipt.Taxes[_hst.Code.Value].Rate);
Assert.Equal(0.00m, receipt.Taxes[_hst.Code.Value].TaxableAmount);
Assert.Equal(0.00m, receipt.Taxes[_hst.Code.Value].Amount);

Assert.Equal(_qst.Rate, _receipt.Taxes[_qst.Code.Value].Rate);
Assert.Equal(9.99m, _receipt.Taxes[_qst.Code.Value].TaxableAmount);
Assert.Equal(1.00m, _receipt.Taxes[_qst.Code.Value].Amount);
Assert.Equal(_qst.Rate, receipt.Taxes[_qst.Code.Value].Rate);
Assert.Equal(9.99m, receipt.Taxes[_qst.Code.Value].TaxableAmount);
Assert.Equal(1.00m, receipt.Taxes[_qst.Code.Value].Amount);

Assert.Equal(21.03m + 0.96m + 1.00m, _receipt.Total);
Assert.Equal(21.03m + 0.96m + 1.00m, receipt.Total);
}

[Fact(DisplayName = "Calculate: it should not calculate any tax when no item is taxable.")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Faktur.Domain.Articles;
using Faktur.Domain.Products;
using Faktur.Domain.Stores;
using Faktur.Domain.Taxes;
using FluentValidation.Results;
using Logitar.Identity.Domain.Shared;

Expand Down Expand Up @@ -52,11 +51,8 @@ public void IsTaxable_it_should_return_false_when_the_receipt_item_is_not_taxabl
ReceiptItemUnit itemWithoutFlags = new(gtin, sku: null, label, flags: null, quantity: 1.0d, unitPrice: 9.99m, price: 9.99m, departmentNumber: null, department: null);
ReceiptItemUnit itemWithFlags = new(gtin, sku: null, label, flags: new FlagsUnit("FPMRJ"), quantity: 1.0d, unitPrice: 9.99m, price: 9.99m, departmentNumber: null, department: null);

TaxAggregate taxWithoutFlags = new(new TaxCodeUnit("GST"), rate: 0.05);
TaxAggregate taxWithFlags = new(new TaxCodeUnit("QST"), rate: 0.09975)
{
Flags = new FlagsUnit("Q")
};
ReceiptTaxUnit taxWithFlags = new(new FlagsUnit("Q"), rate: 0.09975, taxableAmount: 0.00m, amount: 0.00m);
ReceiptTaxUnit taxWithoutFlags = new(new FlagsUnit("Z"), rate: 0.05, taxableAmount: 0.00m, amount: 0.00m);

Assert.False(itemWithoutFlags.IsTaxable(taxWithoutFlags));
Assert.False(itemWithoutFlags.IsTaxable(taxWithFlags));
Expand All @@ -69,10 +65,7 @@ public void IsTaxable_it_should_return_true_when_the_receipt_item_is_taxable()
{
ReceiptItemUnit item = new(new GtinUnit("06038385904"), sku: null, new DisplayNameUnit("PC POULET BBQ"),
new FlagsUnit("FPMRJ"), quantity: 1.0d, unitPrice: 9.99m, price: 9.99m, departmentNumber: null, department: null);
TaxAggregate tax = new(new TaxCodeUnit("QST"), rate: 0.09975)
{
Flags = new FlagsUnit("P")
};
ReceiptTaxUnit tax = new(new FlagsUnit("P"), rate: 0.09975, taxableAmount: 0.00m, amount: 0.00m);
Assert.True(item.IsTaxable(tax));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public CreateOrReplaceReceiptItemCommandTests() : base()
};
_chickenProduct.Update(ActorId);

_receipt = new(_store, actorId: ActorId);
_receipt = new(_store, taxes: [_gst, _qst], actorId: ActorId);
}

public override async Task InitializeAsync()
Expand Down Expand Up @@ -129,9 +129,22 @@ public async Task It_should_create_a_new_receipt_item()
Assert.Equal(Actor, item.Receipt.UpdatedBy);

Assert.Equal(1.75m, item.Receipt.SubTotal);
Assert.Empty(item.Receipt.Taxes);
Assert.Equal(1.75m, item.Receipt.Total);

Assert.Equal(2, item.Receipt.Taxes.Count);

ReceiptTax? gst = item.Receipt.Taxes.SingleOrDefault(t => t.Code == _gst.Code.Value);
Assert.NotNull(gst);
Assert.Equal(0.05d, gst.Rate);
Assert.Equal(0.00m, gst.TaxableAmount);
Assert.Equal(0.00m, gst.Amount);

ReceiptTax? qst = item.Receipt.Taxes.SingleOrDefault(t => t.Code == _qst.Code.Value);
Assert.NotNull(qst);
Assert.Equal(0.09975d, qst.Rate);
Assert.Equal(0.00m, qst.TaxableAmount);
Assert.Equal(0.00m, qst.Amount);

ReceiptItemEntity? entity = await FakturContext.ReceiptItems.AsNoTracking()
.Include(x => x.Product)
.Include(x => x.Receipt)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ public async Task It_should_import_a_new_empty_receipt()
Assert.Empty(receipt.Items);
Assert.Equal(0, receipt.SubTotal);
Assert.Equal(0, receipt.Total);
Assert.Empty(receipt.Taxes);
Assert.Equal(2, receipt.Taxes.Count);
Assert.Contains(receipt.Taxes, t => t.Code == _gst.Code.Value && t.Rate == _gst.Rate && t.TaxableAmount == 0.00m && t.Amount == 0.00m);
Assert.Contains(receipt.Taxes, t => t.Code == _qst.Code.Value && t.Rate == _qst.Rate && t.TaxableAmount == 0.00m && t.Amount == 0.00m);
Assert.False(receipt.HasBeenProcessed);
Assert.Null(receipt.ProcessedBy);
Assert.Null(receipt.ProcessedOn);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public UpdateReceiptItemCommandTests() : base()
};
_chickenProduct.Update(ActorId);

_receipt = new(_store, actorId: ActorId);
_receipt = new(_store, taxes: [_gst, _qst], actorId: ActorId);
_receipt.SetItem(ItemNumber, new ReceiptItemUnit(gtin: null, new SkuUnit("test"), new DisplayNameUnit("Test"), flags: null,
quantity: 1.0d, unitPrice: 0.99m, price: 0.99m, departmentNumber: null, department: null), ActorId);
}
Expand Down Expand Up @@ -235,9 +235,22 @@ public async Task It_should_update_an_existing_receipt_item_with_a_Sku()
Assert.Equal(Actor, item.Receipt.UpdatedBy);

Assert.Equal(1.75m, item.Receipt.SubTotal);
Assert.Empty(item.Receipt.Taxes);
Assert.Equal(1.75m, item.Receipt.Total);

Assert.Equal(2, item.Receipt.Taxes.Count);

ReceiptTax? gst = item.Receipt.Taxes.SingleOrDefault(t => t.Code == _gst.Code.Value);
Assert.NotNull(gst);
Assert.Equal(0.05d, gst.Rate);
Assert.Equal(0.00m, gst.TaxableAmount);
Assert.Equal(0.00m, gst.Amount);

ReceiptTax? qst = item.Receipt.Taxes.SingleOrDefault(t => t.Code == _qst.Code.Value);
Assert.NotNull(qst);
Assert.Equal(0.09975d, qst.Rate);
Assert.Equal(0.00m, qst.TaxableAmount);
Assert.Equal(0.00m, qst.Amount);

ReceiptItemEntity? entity = await FakturContext.ReceiptItems.AsNoTracking()
.Include(x => x.Product)
.Include(x => x.Receipt)
Expand Down

0 comments on commit 6e6ddf7

Please sign in to comment.