Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions scu-gr/src/fiskaltrust.Middleware.SCU.GR.MyData/AADEFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1227,20 +1227,22 @@ private static long GetInvoiceMark(ReceiptResponse receiptResponse)

private static List<PaymentMethodDetailType> GetPayments(ReceiptRequest receiptRequest)
{
// what is payitemcase 99?
return receiptRequest.cbPayItems.Where(x => !(x.ftPayItemCase.IsCase(PayItemCase.Grant) && x.ftPayItemCase.IsFlag(PayItemCaseFlags.Tip)) && !(x.ftPayItemCase.IsCase(PayItemCase.DebitCardPayment) && x.ftPayItemCase.IsFlag(PayItemCaseFlags.Tip))).Select(x =>
return receiptRequest.GetGroupedPayItems().Select(group =>
{
var x = group.payItem;
var payment = new PaymentMethodDetailType
{
type = AADEMappings.GetPaymentType(x),
amount = receiptRequest.ftReceiptCase.IsFlag(ReceiptCaseFlags.Refund) ? -x.Amount : x.Amount,
paymentMethodInfo = x.Description,
};
var tipPayment = receiptRequest.cbPayItems.FirstOrDefault(x => x.ftPayItemCase.IsFlag(PayItemCaseFlags.Tip));
if (tipPayment != null)
if (group.tip != null)
{
payment.tipAmount = tipPayment.Amount;
payment.tipAmount = Math.Abs(group.tip.Amount); // TipAmount should always be positive
payment.tipAmountSpecified = true;
// in case of using a tip we need to correct the amount being sent to AADE, as AADE expects the total amount including the tip, while in our model the tip is a separate PayItem. So we need to add the tip amount to the main payment amount. In case of refunds we assume that the tip is also refunded and we apply the same negative sign as for the main payment.
var combinedAmount = x.Amount + group.tip.Amount;
payment.amount = receiptRequest.ftReceiptCase.IsFlag(ReceiptCaseFlags.Refund) ? -combinedAmount : combinedAmount;
}

if (x.ftPayItemCaseData != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,39 @@ public static bool TryDeserializeftChargeItemCaseData<T>(this ChargeItem chargeI
return data;
}

public static List<(PayItem payItem, PayItem? tip)> GetGroupedPayItems(this ReceiptRequest receiptRequest)
{
var data = new List<(PayItem payItem, PayItem? tip)>();
foreach (var payItem in receiptRequest.cbPayItems)
{
if (payItem.ftPayItemCase.IsFlag(PayItemCaseFlags.Tip))
{
var last = data.Count > 0 ? data[^1] : default;
if (last == default)
{
throw new ArgumentException($"Tip pay item at position {payItem.Position} ('{payItem.Description}') has no preceding payment.");
}
else if (last.tip != null)
{
throw new ArgumentException($"Tip pay item at position {payItem.Position} ('{payItem.Description}') cannot be attached because the preceding payment at position {last.payItem.Position} already has a tip assigned.");
}
else if (last.payItem.ftPayItemCase.Case() != payItem.ftPayItemCase.Case())
{
throw new ArgumentException($"Tip pay item at position {payItem.Position} ('{payItem.Description}') has a payment case that does not match the preceding payment at position {last.payItem.Position}.");
}
else
{
data[^1] = (last.payItem, payItem);
}
}
else
{
data.Add((payItem, null));
}
}
return data;
}

public static bool ContainsCustomerInfo(this ReceiptRequest receiptRequest)
{
if (receiptRequest.cbCustomer != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,4 @@ public void GetIncomeClassificationCategoryType_WithDifferentServiceTypes_Return
// Assert
result.Should().Be(expectedCategory);
}

[Theory]
[InlineData(ChargeItemCaseTypeOfService.Tip)]
[InlineData(ChargeItemCaseTypeOfService.Voucher)]
[InlineData(ChargeItemCaseTypeOfService.Grant)]
[InlineData(ChargeItemCaseTypeOfService.Receivable)]
[InlineData(ChargeItemCaseTypeOfService.CashTransfer)]
public void GetIncomeClassificationCategoryType_WithNonSupportedType_ReturnsException(ChargeItemCaseTypeOfService serviceType)
{
// Arrange
var receiptRequest = CreateReceiptRequest();
var chargeItem = CreateChargeItem(serviceType);


var action = () => AADEMappings.GetIncomeClassificationCategoryType(receiptRequest, chargeItem);
action.Should().Throw<Exception>()
.WithMessage($"The ChargeItem type {chargeItem.ftChargeItemCase.TypeOfService()} is not supported for IncomeClassificationCategoryType.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
using System;
using System.Collections.Generic;
using System.Linq;
using fiskaltrust.ifPOS.v2;
using fiskaltrust.ifPOS.v2.Cases;
using fiskaltrust.Middleware.SCU.GR.MyData.Helpers;
using FluentAssertions;
using Xunit;

namespace fiskaltrust.Middleware.SCU.GR.MyData.UnitTest;

public class GetGroupedPayItemsTests
{
private static PayItemCase GR(PayItemCase c) => ((PayItemCase) 0x4752_2000_0000_0000).WithCase(c);

private static ReceiptRequest BuildRequest(List<PayItem> payItems) =>
new ReceiptRequest
{
cbTerminalID = "1",
Currency = Currency.EUR,
cbReceiptMoment = System.DateTime.UtcNow,
cbReceiptReference = System.Guid.NewGuid().ToString(),
ftPosSystemId = System.Guid.NewGuid(),
cbChargeItems = new List<ChargeItem>(),
cbPayItems = payItems,
ftReceiptCase = ((ReceiptCase) 0x4752_2000_0000_0000).WithCase(ReceiptCase.Pay0x3005)
};

[Fact]
public void SinglePayment_NoTip_ShouldReturnOneGroupWithNullTip()
{
var request = BuildRequest(new List<PayItem>
{
new PayItem { Position = 1, Amount = 10m, Description = "Card", ftPayItemCase = GR(PayItemCase.CreditCardPayment) }
});

var result = request.GetGroupedPayItems();

result.Should().HaveCount(1);
result[0].payItem.Description.Should().Be("Card");
result[0].tip.Should().BeNull();
}

[Fact]
public void SinglePayment_FollowedByMatchingTip_ShouldGroup()
{
var request = BuildRequest(new List<PayItem>
{
new PayItem { Position = 1, Amount = 10m, Description = "Card", ftPayItemCase = GR(PayItemCase.CreditCardPayment) },
new PayItem { Position = 2, Amount = -1.5m, Description = "Tip", ftPayItemCase = GR(PayItemCase.CreditCardPayment).WithFlag(PayItemCaseFlags.Tip) }
});

var result = request.GetGroupedPayItems();

result.Should().HaveCount(1);
result[0].payItem.Description.Should().Be("Card");
result[0].tip.Should().NotBeNull();
result[0].tip!.Amount.Should().Be(-1.5m);
}

[Fact]
public void SinglePayment_FollowedByTipWithDifferentCase_ShouldThrow()
{
var request = BuildRequest(new List<PayItem>
{
new PayItem { Position = 1, Amount = 10m, Description = "Cash", ftPayItemCase = GR(PayItemCase.CashPayment) },
new PayItem { Position = 2, Amount = -1m, Description = "Tip", ftPayItemCase = GR(PayItemCase.CreditCardPayment).WithFlag(PayItemCaseFlags.Tip) }
});

var act = () => request.GetGroupedPayItems();

act.Should().Throw<ArgumentException>().Which.Message.Should().Contain("does not match");
}

[Fact]
public void TwoPayments_EachFollowedByTip_ShouldGroupCorrectly()
{
var request = BuildRequest(new List<PayItem>
{
new PayItem { Position = 1, Amount = 10m, Description = "Card 1", ftPayItemCase = GR(PayItemCase.CreditCardPayment) },
new PayItem { Position = 2, Amount = -1.5m, Description = "Tip 1", ftPayItemCase = GR(PayItemCase.CreditCardPayment).WithFlag(PayItemCaseFlags.Tip) },
new PayItem { Position = 3, Amount = 20m, Description = "Card 2", ftPayItemCase = GR(PayItemCase.CreditCardPayment) },
new PayItem { Position = 4, Amount = -3m, Description = "Tip 2", ftPayItemCase = GR(PayItemCase.CreditCardPayment).WithFlag(PayItemCaseFlags.Tip) }
});

var result = request.GetGroupedPayItems();

result.Should().HaveCount(2);
result[0].payItem.Description.Should().Be("Card 1");
result[0].tip!.Description.Should().Be("Tip 1");
result[1].payItem.Description.Should().Be("Card 2");
result[1].tip!.Description.Should().Be("Tip 2");
}

[Fact]
public void TwoPayments_OnlyFirstFollowedByTip_SecondShouldHaveNoTip()
{
var request = BuildRequest(new List<PayItem>
{
new PayItem { Position = 1, Amount = 10m, Description = "Card 1", ftPayItemCase = GR(PayItemCase.CreditCardPayment) },
new PayItem { Position = 2, Amount = -2m, Description = "Tip", ftPayItemCase = GR(PayItemCase.CreditCardPayment).WithFlag(PayItemCaseFlags.Tip) },
new PayItem { Position = 3, Amount = 15m, Description = "Card 2", ftPayItemCase = GR(PayItemCase.CreditCardPayment) }
});

var result = request.GetGroupedPayItems();

result.Should().HaveCount(2);
result[0].tip!.Description.Should().Be("Tip");
result[1].tip.Should().BeNull("no tip follows the second card");
}

[Fact]
public void TipNotDirectlyAfterPayment_ShouldThrow()
{
var request = BuildRequest(new List<PayItem>
{
new PayItem { Position = 1, Amount = 10m, Description = "Card", ftPayItemCase = GR(PayItemCase.CreditCardPayment) },
new PayItem { Position = 2, Amount = 5m, Description = "Cash", ftPayItemCase = GR(PayItemCase.CashPayment) },
new PayItem { Position = 3, Amount = -1m, Description = "Tip for Card", ftPayItemCase = GR(PayItemCase.CreditCardPayment).WithFlag(PayItemCaseFlags.Tip) }
});

var act = () => request.GetGroupedPayItems();

act.Should().Throw<ArgumentException>().Which.Message.Should().Contain("does not match");
}

[Fact]
public void PaymentAlreadyHasTip_SecondTipShouldThrow()
{
var request = BuildRequest(new List<PayItem>
{
new PayItem { Position = 1, Amount = 10m, Description = "Card", ftPayItemCase = GR(PayItemCase.CreditCardPayment) },
new PayItem { Position = 2, Amount = -1m, Description = "Tip 1", ftPayItemCase = GR(PayItemCase.CreditCardPayment).WithFlag(PayItemCaseFlags.Tip) },
new PayItem { Position = 3, Amount = -0.5m, Description = "Tip 2", ftPayItemCase = GR(PayItemCase.CreditCardPayment).WithFlag(PayItemCaseFlags.Tip) }
});

var act = () => request.GetGroupedPayItems();

act.Should().Throw<ArgumentException>().Which.Message.Should().Contain("already has a tip");
}

[Fact]
public void EmptyPayItems_ShouldReturnEmptyList()
{
var request = BuildRequest(new List<PayItem>());

var result = request.GetGroupedPayItems();

result.Should().BeEmpty();
}

[Fact]
public void OnlyTipItems_ShouldThrow()
{
var request = BuildRequest(new List<PayItem>
{
new PayItem { Position = 1, Amount = -1m, Description = "Tip", ftPayItemCase = GR(PayItemCase.CreditCardPayment).WithFlag(PayItemCaseFlags.Tip) }
});

var act = () => request.GetGroupedPayItems();

act.Should().Throw<ArgumentException>().Which.Message.Should().Contain("no preceding payment");
}

[Fact]
public void MixedCases_TipOnlyMatchesImmediatelyPrecedingSameCase()
{
var request = BuildRequest(new List<PayItem>
{
new PayItem { Position = 1, Amount = 10m, Description = "Card", ftPayItemCase = GR(PayItemCase.CreditCardPayment) },
new PayItem { Position = 2, Amount = -1m, Description = "Card Tip", ftPayItemCase = GR(PayItemCase.CreditCardPayment).WithFlag(PayItemCaseFlags.Tip) },
new PayItem { Position = 3, Amount = 5m, Description = "Cash", ftPayItemCase = GR(PayItemCase.CashPayment) },
new PayItem { Position = 4, Amount = -0.5m, Description = "Cash Tip", ftPayItemCase = GR(PayItemCase.CashPayment).WithFlag(PayItemCaseFlags.Tip) }
});

var result = request.GetGroupedPayItems();

result.Should().HaveCount(2);
result[0].payItem.Description.Should().Be("Card");
result[0].tip!.Description.Should().Be("Card Tip");
result[1].payItem.Description.Should().Be("Cash");
result[1].tip!.Description.Should().Be("Cash Tip");
}
}
Loading
Loading