I have an Invoice table and a ClientPayments table in my database. I have code that confirms a payment has occurred and registers it against a selected invoice (the user selects an invoice in the view). I am trying to get the status of the Invoice to change to "Confirmed" once the total payment amount equals the invoice amount. There can be many payments against once invoice and if the payment amount is less than the invoice amount, the status changes to "Partly Paid". This is my code from the controller below:
public ActionResult Confirm(int id, long InvoiceAmount, string PaymentType, float? InvoiceCustomAmount)
{
var invoice = db.Invoice.Find(id);
//now validate that if the logged in user is authorized to select and confirm this invoice or not.
ClientPayments clientPayment = db.ClientPayments.FirstOrDefault(cp => cp.InvoiceNumberID == id);
clientPayment = new ClientPayments();
clientPayment.InvoiceNumberID = id;
var TotalPayments = (clientPayment.PaymentAmount + InvoiceAmount);
if (InvoiceAmount == 115)
{
clientPayment.PaymentAmount = (long)InvoiceCustomAmount;
}
else
{
clientPayment.PaymentAmount = InvoiceAmount;
}
clientPayment.PaymentType = PaymentType;
clientPayment.PaymentDate = DateTime.Now;
db.ClientPayments.Add(clientPayment);
if (TotalPayments != invoice.InvoiceAmount)
{
invoice.InvoiceStatus = "Partly Paid";
}
else
{
invoice.InvoiceStatus = "Confirmed";
}
// You donĀ“t need this, since "invoice" was retrieved earlier in the method the database context
// knows that changes have been made to this object when you call "SaveChanges".
// db.Entry(invoices).State = EntityState.Modified;
db.SaveChanges();
return View();
}
The problem I am having is that TotalPayments doesn't give my total payment amount - it doesn't add up the entries that have already be added to the database.
Thanks
Looks like your loading the clientPayment here
ClientPayments clientPayment = db.ClientPayments.FirstOrDefault(cp => cp.InvoiceNumberID == id);
Now you've set the reference to a new ClientPayments, no longer loaded
clientPayment = new ClientPayments();
Here your setting the InvoiceNumberID, but nothing else
clientPayment.InvoiceNumberID = id;
Now your setting TotalPayments to the InvoiceAmount (clientPayment.PaymentAmount is 0 or null)
var TotalPayments = (clientPayment.PaymentAmount + InvoiceAmount);
You then set the clientPayment.PaymentAmount later, but after you've already set the TotalPayment.
Am I missing something?
EDIT:
I think your close. I don't think you want FirstOrDefault when you get your payments from the database. I think you want a sum.
So, if your just getting the sum of payments, use something like this:
var totalPaid = ClientPayments.Where(cp => cp.InvoiceNumberID == id).Sum(cp => cp.PaymentAmount);
Now, you have your total from the database.
Also, what is this? InvoiceAmount == 115? 115 is what's referred to as a Magic Number. It doesn't mean anything to anyone reading your code. Consider using a Constant or an Enumeration.
For example,
const int totalInvoiceAmount = 115;
Then your code would read,
if (InvoiceAmount == totalAmountOwed){...}
Then I think you can go ahead and create your new ClientPayment as you've done. Then set the values accordingly.
It looks like you need to get the amount the user is paying and add that to the total we got earlier. You can then check this amount against the balance owed to determine if the invoice stauts should be 'Confirmed' or 'Paid in Full'
Related
I have the following entries in my database:
MeetingID AgendaItem LegistarID Title
48620 3 60710 Comment
48620 5 60615 Extending report date
48620 6 60714 Update on Additional meeting dates
48620 7 59909 Budget Rules & Procedures
48620 8 60703 Update Director name
That I need to update with these values:
MeetingID AgendaItem LegistarID Title
48620 3 60710 Public Comment
48620 5 60769 Briefing by Victor
48620 6 60615 Extending report dates
48620 7 60714 Update on Additional meeting dates
48620 8 60703 Update from Director on new processes
The way I am trying doing this in C#, is as follows:
if (ModelState.IsValid)
{
var errors = new List<string>();
var rowCounter = 1;
using (Entities db = new Entities())
{
foreach (var i in meeting)
{
if (i.MeetingID == 0)
{
// Let the user know this row is bad
errors.Add($"Row {rowCounter}: Missing Meeting ID value. " +
"Verify that the data you are trying to upload meets the required criteria, " +
"and then try to upload your file again." );
break;
}
// Check if LegistarID is missing
if (i.LegistarID == 0)
{
// Check if Agenda Item is present
if (i.AgendaItem == 0)
{
errors.Add($"Row {rowCounter}: Meeting has no LegistarID and no Agenda Item. Please check data.");
break;
}
else
{
i.LegistarID = i.AgendaItem;
}
}
var compositeKey = db.Meeting.Find(i.MeetingID, i.AgendaItem);
if (compositeKey == null)
{
// Add new
db.Meeting.Add(i);
}
else
{
// Serves as an update, or addition of a previously imported dataset
db.Entry(compositeKey).CurrentValues.SetValues(i.MeetingID);
db.Entry(compositeKey).CurrentValues.SetValues(i.AgendaItem);
db.Entry(compositeKey).CurrentValues.SetValues(i.LegistarID);
db.Entry(compositeKey).CurrentValues.SetValues(i.Title);
db.Entry(compositeKey).State = EntityState.Modified;
}
rowCounter++;
}
// If there are errors do not save and return error message
if (errors.Count > 0)
{
return new JsonResult { Data = new { status = false, message = string.Join("\n", errors) } };
}
db.SaveChanges();
status = true;
}
}
else
{
message = string.Format(#"Please, verify that the file you are trying to upload is correctly formatted,
and that the data it contains, meets the expected criteria,
then click the upload button again. \n Thank you!");
return new JsonResult { Data = new { status = status, message = message } };
}
The code for the Add part works well, but the part that updates the record if the composite key is found does not work, the update is not working.
I am not sure if I am doing this the best way, but if there is a better way I am open to change the code, or if I have an error on how I am doing the process, please let me know
Any help is appreciated.
Thank you,
Erasmo
Remove all your calls to SetValues and replace them with single one:
db.Entry(compositeKey).CurrentValues.SetValues(i);
SetValues which accepts object as parameter copies data to entity based on object properties names:
Any property on the object with a name that matches a property name in the entity type and can be read will be copied. Other properties will be ignored.
I am using ASP.NET MVC and EF to create a vehicle reservation app in which a user will be able to reserve multiple vehicles for one datetime, if they want. I created a stored procedure to prevent double booking of vehicles, but am having trouble figuring out how to add the results to a list.
Example: I want to reserve Vehicle#1 and Vehicle#2 for 12/18/2018 from 12:00 to 13:00....stored procedure goes to db to find out that Vehicle#1 is already reserved from 12:00 to 13:00, but Vehicle#2 is not reserved. Due to the foreach that runs, alreadyReservedVehicle comes back with the result of .Count() = 0 because it sees the last result, which is that Vehicle#2 is not reserved. It should be showing an error message to say double booking is not allowed, since Vehicle#1 is already reserved, but it isn't counting that reservation.
Is there a way to collect both results and tell the application that because one of those 2 vehicles are reserved, that neither vehicle can be reserved?
public ActionResult Create([Bind(Include = "ID,StartDate,EndDate,RequestorID,Destination,PurposeOfTrip,TransportStudentsFG,LastChangedBy,LastChangedDate,VehicleList,ThemeColor")] Reservations reservation)
{
if (ModelState.IsValid)
{
var selectedVehicles = reservation.VehicleList.Where(x => x.IsChecked == true).ToList();
List<usp_PreventDoubleBooking_Result> alreadyReservedVehicle = null;
// get each vehicle that was selected to be reserved then check db to make sure it is available
foreach (var selectedVehicle in selectedVehicles)
{
using (VehicleReservationEntities db = new VehicleReservationEntities())
{
alreadyReservedVehicle = db.usp_PreventDoubleBooking(selectedVehicle.ID, reservation.StartDate, reservation.EndDate).ToList();
}
}
if (alreadyReservedVehicle.Count() == 0) // create a new reservation if the vehicle is available at the selected date and time
{
db.Reservations.Add(reservation);
reservation.LastChangedDate = DateTime.Now;
db.SaveChanges();
}
else
{
//return error message on page if vehicle is already reserved
TempData["Error"] = "Double booking of vehicles is not allowed. Please choose another vehicle/time. Check the availability timeline before reserving to ensure there are no errors. Thank you.";
return RedirectToAction("Create");
}
}
}
Above is what I have so far and I know I am close, because what I have will work assuming a user is only trying to reserve one vehicle that is already booked. It's when they are trying to create a reservation for multiple vehicles where the code only counts the last vehicle in the list and uses that as the result that determines whether or not the reservation will be saved.
I thought about moving the conditional statement into the foreach, but I don't want the reservation to be saved for each vehicle that was selected...that wouldn't make any sense because there is only 1 reservation to be saved, it just has multiple vehicles that can be associated to it.
Here is the stored procedure that finds the reserved vehicles:
SELECT
v.ID, r.StartDate, r.EndDate
FROM
dbo.Reservations r
JOIN
dbo.ReservationToVehicle rtv ON r.id = rtv.ReservationID
JOIN
dbo.Vehicles v ON v.ID = rtv.VehicleID
WHERE
(v.ID = #VehicleID)
AND (r.StartDate <= #NewEndDate)
AND (r.EndDate >= #NewStartDate)
So how can I get the alreadyReservedVehicle List to add each result to the list so I can determine if the List actually has a Count of 0?
UPDATE:
Here is the model for the stored procedure
public partial class usp_PreventDoubleBooking_Result
{
public int ID { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
public ActionResult Create(/*Bind attribute omitted*/ Reservations reservation)
{
if (ModelState.IsValid)
{
// Is 'IsChecked' nullable? If not, "== true" is redundant.
var selectedVehicles = reservation.VehicleList.Where(x => x.IsChecked == true).ToList();
// get each vehicle that was selected to be reserved then check db to make sure it is available
using (VehicleReservationEntities db = new VehicleReservationEntities())
{
foreach (var selectedVehicle in selectedVehicles)
{
// 'alreadyReservedVehicle' can be declared here because you don't need to let it
// out of its cage, I mean the loop.
List<usp_PreventDoubleBooking_Result> alreadyReservedVehicle =
db.usp_PreventDoubleBooking(selectedVehicle.ID, reservation.StartDate, reservation.EndDate).ToList();
if (alreadyReservedVehicle.Count() > 0)
{
//return error message on page if vehicle is already reserved
TempData["Error"] = "Double booking of vehicles is not allowed. Please choose another vehicle/time. Check the availability timeline before reserving to ensure there are no errors. Thank you.";
return RedirectToAction("Create");
}
}
}
// create a new reservation if the vehicle is available at the selected date and time
db.Reservations.Add(reservation);
reservation.LastChangedDate = DateTime.Now;
db.SaveChanges();
}
}
You could do something like:
var preventDoubleBookingList= await context.Database.SqlQuery<usp_PreventDoubleBooking>("sproc_name", prams_here).ToListAsync();
You will have to create a model that matches the sproc.
I have two models: Machine and Devices.
The relation between them is: A Machine has a collection of Devices.
How the PostAction should work: When the user creates a new Machine, he will also declare the number of Devices that Machine has.
This way, if 3 devices are declared for a Machine, 3 registers must be saved on the Device model.
The Code:
[HttpPost, ActionName("CreateEdit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateEditPost(int? id,
[Bind("TypeID,BrandID,SupplierID,StoreID,MchName,NumDevices,FechaCompra,CostoMaq,MachineStatus")]Machine model,
[Bind("Id,DeviceName")]Device devicemodel)
{
if (id == null)
{
return NotFound();
}
if (ModelState.IsValid)
{
if (id == 0)
{
_context.Add(model);
for (var items = 0; items < model.NumDevices; items++)
{
var contador = items + 1;
string devicename = model.MchName + "-" + contador.ToString();
devicemodel.DeviceName = devicename;
_context.Add(devicemodel);
await _context.SaveChangesAsync();
}
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
}
return RedirectToAction("Index");
}
The problem:
When indicating, for example, 2 devices, here is what the debug is showing:
As shown, in the first attempt the DeviceID is 0. In the second attempt is 1006. This DeviceID is autogenerated.
At this point the application interrups claiming:
SqlException: Cannot insert explicit value for identity column in table 'Device' when IDENTITY_INSERT is set to OFF.
I believe this is happening because it's trying to write a zero on an Key field (DeviceID).
But also, it's saving one register on the database:
But it's saving a combination of the two attempts: (1) The DeviceName from attempt 1, (2) The DeviceID from attempt 2.
Can someone explain why in the first attempt the DeviceID is zero? How can this be fixed? And why is it saving the mix of both attempts?
Thanks in advance.
From what I can tell in your code, your loop is going through the number of devices that it thinks it has based off of the auto-bound number of devices in the Machine model, which I assume there is a hand-entered value for on your MVC form.
For each "Device" it has, you are literally trying to tell Entity Framework to add the same object (after it has its properties modified) and save it to the database. After the first "SaveChanges" call, the device's Id column will be updated to the ID that the database assigned to it. If you then try to add that to the DBContext again, it will try to create a NEW device with the SAME id, which is illegal unless, as it says, IDENTITY_INSERT is set to ON. Even it that setting was ON, it would be illegal because of the likely unique-ness constraint.
So, the first thing, is that it's a better practice to have DISCONNECTED models, and then a data layer which converts those model to actual entities and inserts those into the DB. But, barring that, something like this, which creates a new Device each time around, would work better:
if (id == 0)
{
_context.Add(model);
for (var items = 0; items < model.NumDevices; items++)
{
var contador = items + 1;
string devicename = model.MchName + "-" + contador.ToString();
var devNew = new Device();
devNew.DeviceName = devicename;
_context.Add(devNew);
await _context.SaveChangesAsync();
}
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
I have code to Create a Vendor Payment for aVendor Bill like this :
InitializeRecord ir = new InitializeRecord();
ir.type = InitializeType.vendorPayment;
InitializeRef iref = new InitializeRef();
iref.typeSpecified = true;
iref.type = InitializeRefType.vendorBill;
iref.internalId = vendorBillId;
ir.reference = iref;
Login();
ReadResponse getInitResp = _service.initialize(ir);
if (getInitResp.status.isSuccess)
{
Record rec = getInitResp.record;
((VendorPayment)rec).total = (double)amount; //I don't want to pall all, just pay a half or just an amount less than the total
((VendorPayment)rec).totalSpecified = true;
WriteResponse writeRes = _service.add(rec);
return writeRes.status;
}
That can create a payment but the total is not apply, the payment is pay all amount of vendor bill's total.
I don't know what I'm missing here.
While applying payments to bill you cannot change the body level amount field. you got to change the amount line level field on apply line item record. I am not sure on syntax in Suitetalk, but, that should work.
I need to know the best practice of creating an entity object and assigning the foreign key. Here is my scenario. I have a Product table with pid,name,unit_price etc.. I also have a Rating table with pid (foregin key),rate,votes etc... Currently i am doing the following to create the rating object:
var prod = entities.Product.First(p => p.product_id == pid);
prod.Rating.Load();
if (prod.Rating != null)
{
log.Info("Rating already exists!");
// set values and Calcuate the score
}
else
{
log.Info("New Rating!!!");
Rating rating = new Rating();
// set values and do inital calculation
prod.Rating = rating;
} entities.SaveChanges();
Even though this works fine, I would like to know the best practice in doing these kind of assignment.
Whenever you are always going to load an entity, just do one round trip, and include it in the query that will get generated by EF.
Product prod = entities
.Product.Include("Rating")
.First(p => p.product_id == pid);
if (prod.Rating != null)
{
log.Info("Rating already exists!");
// set values and Calcuate the score
}
else
{
log.Info("New Rating!!!");
Rating rating = new Rating();
// set values and do inital calculation
prod.Rating = rating;
}
entities.SaveChanges();