Retrieve outstanding invoices with currency conversion complexity in LINQ - c#

In my Entity Framework application, I have an Entity called Invoice.cs, it has various properties, but here are the ones we're concerned about for the question:
public class Invoice : IEntity
{
public int Id { get; set; }
public decimal Amount { get; set; }
public DateTime Date { get; set; }
public int OrderId { get; set; }
public virtual ICollection<Payment> Payments { get; set; }
}
I am attempting to query the database to get a list of all outstanding invoices. An outstanding invoice is the following:
If the total of the payments made against the Invoice are less than the Invoice Amount, then the invoice is Outstanding.
I'm stuck on working out if an invoice is outstanding or not in my LINQ query, which currently looks like this:
var outstandingInvoices = from inv in _context.Invoices
where !inv.IsDeleted && inv.Date >= startDate && inv.Date <= endDate
select inv;
startDate and endDate are parameters passed in to filter the result.
Another complexity with this is to do with how payments are made. The payments can be made in RMB (Chinese currency) or GBP (UK currency), and in the report I'm generating, I want all to be displayed as GBP.
So I must somehow add this logic too:
// loop through payments for each invoice, and add payment
// amount to a local variable called total
if (payment.Currency == Currency.Gbp)
{
total += payment.Amount;
} else
{
total += payment.Amount / (decimal)payment.ConversionRate;
}
The Payment.cs entity has these two properties that are of concern:
public class PaymentViewModel
{
public int Id { get; set; }
[Required]
public decimal Amount { get; set; }
[Required]
[Display(Name = "Payment Currency")]
public Currency Currency { get; set; }
[Display(Name = "Conversion Rate")]
public float ConversionRate { get; set; }
}

You are going to have an issue because of the float/decimal differences. Depending on your database provider, it might allow you to force the cast from decimal to float (or it might not). Of course, then you'll have issues of payments that are really close, but not quite the same. What if the amount / CoversionRate is 0.999999999999999999999999999 GBP when the amount was for 1 GBP? Technically it's not fully paid.
Ultimately, the conversion rate should also be a decimal, not float, but determining the precision depends on your source. Is it accurate to 5 decimal places or 7?
var outstanding = _context.Invoices
.Where(x=>x.Date >= startDate)
.Where(x=>x.Date <= endDate)
.Where(x=>!x.IsDeleted)
.Where(x=>x.Payments.Sum(p=>(float)p.Amount / p.ConversionRate) < x.Amount);
Alternatively if the total paid is within 1 GBP:
var outstanding = _context.Invoices
.Where(x=>x.Date >= startDate)
.Where(x=>x.Date <= endDate)
.Where(x=>!x.IsDeleted)
.Where(x=>x.Payments.Sum(p=>(float)p.Amount / p.ConversionRate) - x.Amount < 1);

You can use ternary operator to adjust payment amount in case it is not in GBP:
var outstandingInvoices =
from inv in _context.Invoices
let totalPayment = inv.Payments.Sum(p =>
p.Currency == Currency.Gbp ? p.Amount : p.Amount / p.ConversionRate)
where !inv.IsDeleted
&& inv.Date >= startDate && inv.Date <= endDate
&& totalPayment < inv.Amount
select inv;

Related

Aggregate and create results list using EF

I'm having a list of budget units each one containing the following properties:
DateTime Month,
int IdCurrency,
decimal Planned,
int sign, //denotes whether we have income (1) or cost (0)
etc...
Based on given year, I'd like to return a list of objects of the following structure:
public class BudgetBalances
{
public DateTime Month { get; set; }
public int IdCurrency { get; set; }
public decimal Incomes { get; set; }
public decimal Costs { get; set; }
public decimal Balance { get; set; }
}
The first part is easy - I'm getting all budget units for given day from the database, but now I do not know how to make an EF query to:
Get all incomes (sign==1) in currencies within one month, sum them and store it Incomes property
Get all costs (sign==0) and do the same as above
Substract Cost from Income and store it under Balance property
As the result I will have
Jan2022, USD, 3000, 1000, 2000
Jan2022, EUR, 5000, 2000, 3000
etc..
I can always make three level foreach structure, but that is not an effective way to do so. Could you please give me hint how to do it proper way?
That is what I got so far:
public List<BudgetBalances>GetYearlyBudget(int IdOwner, int year)
{
var budgets = _context.Budgets
.Where(_ => _.Month.Year == year && _.IdOwner == IdOwner);
List<BudgetBalances> list = budgets.GroupBy(a => a.Month)
.Select(ls => new BudgetBalances
{
Incomes = ls.Where(_ => _.IsIncome == 1).Sum(_ => _.Planned),
Costs = ls.Where(_ => _.IsIncome == 0).Sum(_ => _.Planned)
}).ToList();
return list;
}
And it calculates each month budget taking into account incomes and costs, but it does not take currencies into consideration. Also I do not know how should I obtain balance value.
Balance = Income - Costs
does not work
Reference this
code sample
using (var context = new MyContext())
{
var result = context.BudgetBalances
.Where(b => b.IdCurrency == 1);
}
Thanks, finally I got what I wanted, here's my code:
public List<BudgetBalances>GetYearlyBudget(int IdOwner, int year)
{
var budgets = _context.Budgets
.Where(_ => _.Month.Year == year && _.IdOwner == IdOwner);
List<BudgetBalances> list = budgets.GroupBy(a => new { a.Month, a.IdCurrency})
.Select(ls => new BudgetBalances
{
IdCurrency = ls.Key.IdCurrency,
CurrencySymbol = _context.Currencies.Where(_=>_.IdCurrency==ls.Key.IdCurrency).FirstOrDefault().CurrencySymbol,
Month = ls.Key.Month,
Incomes = ls.Where(_ => _.IsIncome == 1).Sum(_ => _.Planned),
Costs = ls.Where(_ => _.IsIncome == 0).Sum(_ => _.Planned),
})
.OrderBy(_=>_.Month)
.ToList();
foreach(BudgetBalances ls in list)
{
ls.Balance = ls.Incomes - ls.Costs;
ls.month = ls.Month.ToString("MM/yyyy");
}
return list;
}

Calculating price ranges with LINQ and corresponding sale numbers

to start things off I have a class which basically represents all the transactions for specific items, it's usually 100 items for which I have transactions. The class looks as following:
public class RawTransactions
{
public string SellerName { get; set; }
public int FeedBackScore { get; set; }
public int QuantityPurchased { get; set; }
public DateTime TransactionDate { get; set; }
public string ProductTitle { get; set; }
public double CurrentPrice { get; set; }
public double SalePrice { get; set; }
public string ItemID { get; set; }
public string Date { get; set; }
}
Based on this class and the transactions inside a list of this type I've created a function which basically takes smallest and largest sale price of any transaction that is present in the list and creates a 7 stage price range
private List<double> GetRangeForElements(double minPrice, double maxPrice)
{
double step = (maxPrice - minPrice) / 7.00d;
return Enumerable.Range(0, 8).Select(i => minPrice + i * step).ToList();
}
So for example if I pass $0 (smallest sale price) and $10 (largest sale price) it will create a list of 7 price range like following:
0
1.5
3
4.5
6
7.5
9
10
This can be interpreted as:
0 - 1.5 price range
1.5 - 3 price range
3 - 4.5 price range
4.5 - 6 price range
6 - 7.5 price range
// and so on...
The usage is as following:
var ranges = GetRangeForElements(0,10); // Here I will have the ranges now
Now based on these ranges that were just created and the existing transactions that I have I need to determine following parameters:
Price range
How many sales specific ItemID has sales for a specific range
How many sellers (based on SellerName property) had sales for a specific price range
How many sellers (again based on SellerName propert) DIDN'T had sales for a specific price range
I'm not really sure how can I now combine all this data to get these parameters using LINQ? Can someone help me out with this?
P.S. guys the transactions of all items are stored in a List like following:
var allItemsTransactions = new List<ProductResearchRawTransactions>();
P.S. guys this is the existing solution that I have, but it's giving me completely wrong results:
var priceRanges = ranges.Select(r => new PriceRangeGraph
{
Price = Math.Round(r, 2),
Sales = allItemsTransactions.Where(x => ranges.FirstOrDefault(y => y >= x.SalePrice) == r).Sum(x => x.QuantityPurchased),
SuccessfulSellers = allItemsTransactions.Where(x => ranges.FirstOrDefault(y => y >= x.SalePrice) == r).GroupBy(x => new { x.SellerName, x.QuantityPurchased }).Where(x => x.Key.QuantityPurchased > 0).Select(x => x.Key.SellerName).Count(),
UnSuccessfulSellers = allItemsTransactions.Where(x => ranges.FirstOrDefault(y => y >= x.SalePrice) == r).GroupBy(x => new { x.SellerName, x.QuantityPurchased }).Where(x => x.Key.QuantityPurchased == 0).Select(x => x.Key.SellerName).Count(),
}).ToList();

C# Linq how to add each missing year and month to data

I have a list of the following class:
public class Data
{
public string Sector { get; set; }
public string Company { get; set; }
public string Branch { get; set; }
public string Category { get; set; }
public string Item { get; set; }
public string Currency { get; set; }
public int Year { get; set; }
public int Quarter { get; set; }
public int Month { get; set; }
public double Amount { get; set; }
}
With the date range given by the user, I am bringing data on year and month basis. However there is no data for some year and month in the given date range.
var dateRange = new List<DateTime>();
for (DateTime date = Report.Parameter.BeginDate; date <= Report.Parameter.EndDate; date.AddMonths(1))
{
dateRange.Add(new DateTime(date.Year, date.Month, 1));
}
For each year and month in the given date range, I want the Data object to come with a Amount column of 0 (zero).
This data should be added once for each Sector, Company, Branch and Category columns.
Example:
How do I do this with linq?
If you really need to use Linq, it would look like this:
public void MyFunction(List<Data> userData)
{
var beginDate = DateTime.Today;
var endDate = DateTime.Today.AddYears(2);
var monthsApart = 12 * (beginDate.Year - endDate.Year) + beginDate.Month - endDate.Month;
var dateRange = Enumerable.Range(0, monthsApart).Select(offset => beginDate.AddMonths(offset));
var difference = dateRange.Where(item => !(userData.Any(uData => uData.Year == item.Year && uData.Month == item.Month)));
userData.AddRange(difference.Select(date => new Data() {Year = date.Year, Month = date.Month, Amount = 0}));
}
This is just an example. You need to add your logic, where it is needed.
You mentioned This data should be added once for each Sector, Company, Branch and Category columns
If you want to do this, you have to call
userData.AddRange(difference.Select(date => new Data() {Year = date.Year, Month = date.Month, Amount = 0}));
as many times as you have combinations of Sector, Company, Branch and Category.
I hope this helps to understand and my explanation is not too complex.

Multiple condition with Entity Framework to get desired record

I am trying to get single record from statistics table with following logic:
1st preference : Flag = 0
2nd preference : Flag = 1
3rd preference : Flag = 2 (only if we dont have records with flag=0 and 1)
Table: Statistics
Id Zoneid Emergency Flag Date
1 100 0 1 2016-6-01 13:10:05.360
2 100 2 2 2016-6-01 14:10:05.360
3 100 0 2 2016-6-01 15:10:05.360
4 100 2 2 2016-6-01 16:10:05.360
5 100 2 0 2016-6-01 14:10:05.360
6 100 1 2 2016-6-01 13:10:05.360
The logic I am trying to implement is like below:
If(Flag == 0) then
take records with highest emergency(order by emergency desc) but if multiple records found then take latest record order by date desc(only 1 ).
else if(flag==1)
Take records with highest emergency(order by emergency desc) but if multiple records found then take latest record order by date desc(only 1).
else if (no records with flag==0 and flag==1 found)
Take records with highest emergency(order by emergency desc) but if multiple records found then take latest record order by date desc(only 1).
Data model:
public partial class Zone
{
public int Id { get; set; }
public string Area { get; set; }
public virtual ICollection<Statistics> Statistics { get; set; }
}
public partial class Statistics
{
public int Id { get; set; }
public int ZoneId { get; set; }
public int Emergency { get; set; }
public int Flag { get; set; }
public System.DateTime Date { get; set; }
public virtual Zone Zone { get; set; }
}
My query:
var statistics= (from z in db.Zone
select new
{
ZoneName = z.Area,
//Not getting how t implement multiple conditions in EF
StatisticsId = z.Statistics.Where(t => t.ZoneId == 100 &&)
.Select(t => t.Id).FirstOrDefault()
}
So here I am not getting how to implement all those conditions and get desired statistic record.
The logic you are describing sounds like simple priority order - first by Flag ascending, then (for equal Flag) by Emergency descending, then (for equal Flag and Emergency) by Date descending, and taking the first record in that order:
StatisticsId = (from s in z.Statistics
where s.ZoneId == 100
orderby s.Flag, s.Emergency descending, s.Date descending
select (int?)s.Id).FirstOrDefault() ?? 0
Have a shot with this (warning: untested, don't have the time to re-create the class and all):
var statistics= (from z in db.Zone
select new
{
ZoneName = z.Area,
StatisticsId = z.Statistics.OrderBy(t=>t.Flag)
.Where(t => t.ZoneId == 100).Select(t => t.Id).FirstOrDefault()
});

Excluding rows from collection from Linq Query in MVC .net application

This is a follow up question to the one answered here: Excluding dates from Linq Query in MVC .net application - which I'm very grateful for.
I'm hoping that someone can check my syntax in my Linq query below - to confirm if it's the best way to build the query up, or if my use of the syntax is inefficient.
public class Room
{
public int RoomId { get; set; }
[Display(Name = "Room Name")]
public string Name { get; set; }
public bool Disabled { get; set; }
public virtual ICollection<Client> Clients { get; set; }
}
public class Client
{
public int ClientId { get; set; }
public int RoomId { get; set; }
public string ClientName { get; set; }
public DateTime Arrival { get; set; }
public DateTime Departure { get; set; }
public virtual Room Room { get; set; }
}
Clients lists a row for each client who has a particuar room booked. I have 3 rooms, Room 1, Room 2, and Room 3. So entries in the client table could be:
Client 1, Room 1, Mr Smith, Arr: 2012-07-08, Dep: 2012-07-10
Client 2, Room 1, Mr Jones, Arr: 2012-07-14, Dep: 2012-07-20
Client 3, Room 2, Mr Alas, Arr: 2012-07-12, Dep: 2012-07-15
Given an arrival and departure date, I'm trying to take my whole list of rooms, and take away any that have a client staying where the arrival or departure dates overlap. So using the data above, if I had an arrival date of 2012-07-12 and a departure date of 2012-07-13, then Room 2 would not be available, however, Room 1, does not have any bookings spanning that date - so Room 1 I want to leave in my result set.
So my Linq query (I'm new to Linq, so please point out where I may be going wrong) is:
var dteFrom = DateTime.Parse("2012-07-12");
var dteTo = DateTime.Parse("2012-07-13");
var rooms = (from r in Rooms
where !r.Clients.Any(
client =>
( dteFrom >= client.Arrival && dteFrom <= client.Departure )
||
( dteTo >= client.Arrival && dteFrom <= client.Departure )
||
( dteFrom <= client.Arrival && dteTo >= client.Departure )
)
select r);
Given that I'm looking to include ALL rooms, EXCEPT any that meet the criteria, can anyone confirm that my use of .Any and ! and || are correct, as far as LINQ goes?
Is there any better way within the syntax, of excluding records from the Rooms list?
Thank you again for any help,
Mark
Looks fine to me - one thing that may help readability would be to compose your query in two steps:
var prebookedRooms = rooms
.Where(room => room.Clients.Any(client =>
(dteFrom >= client.Arrival && dteFrom <= client.Departure) ||
(dteTo >= client.Arrival && dteFrom <= client.Departure) ||
(dteFrom <= client.Arrival && dteTo >= client.Departure)));
var freeRooms = rooms.Except(prebookedRooms);
Remembering that the query is only executed, when the results are enumerated - so there's no performance cost to doing this in two steps.

Categories