I am getting the list of Invoice details by Invoice Id.
Now i want to update AmountDue by respective Invoice Id.
i tried by below code:
ByInvoiceId.AmountDue = Convert.ToDecimal(100.00);
public_app_api.Invoices.Update(ByInvoiceId);
but..Error as
"A validation exception occurred"
What's the reason behind and How to solve this?
There are a number of ways to change the amount due on an invoice, however changing the value of the property directly is not one of them.
The amount due on an invoice is driven by the totals of the line items on the invoice minus the total of payments and allocated credit. One way to change the amount due is to change the values of your line items or add/remove some line items. You won't be able to change the invoice if it has been paid/partially paid.
Another way you could change the amount due on an invoice is to add a payment against the invoice or allocate credit from a credit note, prepayment, or overpayment to the invoice
In addition to MJMortimer's answer.
You can't change the line amounts on an AUTHORISED invoice via the c# API. You have to VOID the invoice and create a new one. You can however update DRAFT and SUBMITTED ones by updating the line items.
EDIT: Here is some code to help you. This is create invoice code, but amending one is essentially the same.
public XeroTransferResult CreateInvoices(IEnumerable<InvoiceModel> invoices, string user, string status)
{
_user = XeroApiHelper.User(user);
var invoicestatus = InvoiceStatus.Draft;
if (status == "SUBMITTED")
{
invoicestatus = InvoiceStatus.Submitted;
}
else if (status == "AUTHORISED")
{
invoicestatus = InvoiceStatus.Authorised;
}
var api = XeroApiHelper.CoreApi();
var xinvs = new List<Invoice>();
foreach (var inv in invoices)
{
var items = new List<LineItem>();
foreach (var line in inv.Lines)
{
decimal discount = 0;
if (line.PriceBeforeDiscount != line.Price)
{
discount = (decimal)(1 - line.Price / line.PriceBeforeDiscount) * 100;
}
items.Add(new LineItem
{
AccountCode = line.AccountCode,
Description = line.PublicationName != "N/A" ? line.PublicationName + " - " + line.Description : line.Description,
TaxAmount = (decimal)line.TaxAmount,
Quantity = 1,
UnitAmount = (decimal)line.PriceBeforeDiscount,
DiscountRate = discount,
TaxType = line.XeroCode,
ItemCode = line.ItemCode
});
}
var person = inv.Company.People.FirstOrDefault(p => p.IsAccountContact);
if (person == null)
{
person = inv.Company.People.FirstOrDefault(p => p.IsPrimaryContact);
}
var ninv = new Invoice
{
Number = inv.ClientInvoiceId,
Type = InvoiceType.AccountsReceivable,
Status = invoicestatus,
Reference = inv.Reference,
Contact = new Contact
{
Name = inv.Company.OrganisationName,
//ContactNumber = "MM" + inv.Company.CompanyId.ToString(),
FirstName = person.FirstName,
LastName = person.LastName,
EmailAddress = person.Email,
Phones = new List<Phone>()
{
new Phone {PhoneNumber = person.Telephone, PhoneType = PhoneType.Default},
new Phone {PhoneNumber = person.Mobile, PhoneType = PhoneType.Mobile}
},
Addresses = new List<Address>
{ new Address
{
//AttentionTo = inv.Company.People.FirstOrDefault(p => p.IsAccountContact) == null
//? inv.Company.People.FirstOrDefault(p=> p.IsPrimaryContact).FullName
//: inv.Company.People.FirstOrDefault(p => p.IsAccountContact).FullName,
//AddressLine1 = inv.Company.OrganisationName,
AddressLine1 = inv.Company.Address.Address1,
AddressLine2 = inv.Company.Address.Address2 ?? "",
AddressLine3 = inv.Company.Address.Address3 ?? "",
Region = inv.Company.Address.CountyState,
City = inv.Company.Address.TownCity,
PostalCode = inv.Company.Address.PostCode,
}
}
},
AmountDue = (decimal)inv.TotalAmount,
Date = inv.InvoiceDate,
DueDate = inv.DueDate,
LineItems = items,
LineAmountTypes = LineAmountType.Exclusive
};
if (SessionContext.TransferContactDetailsToXero == false)
{
ninv.Contact = new Contact
{
Id = inv.Company.XeroId ?? Guid.Empty,
Name = inv.Company.OrganisationName
};
}
xinvs.Add(ninv);
}
var success = true;
var xinvresult = new List<Invoice>();
try
{
api.SummarizeErrors(false);
xinvresult = api.Invoices.Create(xinvs).ToList();
}
catch (ValidationException ex)
{
// Something's gone wrong
}
foreach (var inv in xinvresult)
{
var mminvoice = invoices.FirstOrDefault(i => i.ClientInvoiceId == inv.Number);
if (inv.Errors != null && inv.Errors.Count > 0)
{
success = false;
if (mminvoice != null)
{
var errors = new List<XeroError>();
foreach (var err in inv.Errors)
{
errors.Add(new XeroError { ErrorDescription = err.Message });
}
mminvoice.XeroErrors = errors;
}
}
else
{
mminvoice.XeroTransferDate = DateTime.Now;
mminvoice.XeroId = inv.Id;
mminvoice.XeroErrors = new List<XeroError>();
}
}
return new XeroTransferResult
{
Invoices = invoices,
Success = success
};
}
Related
I've managed to create a payment using the C# .NET SDK, however it keeps showing up as 'unapplied' when I check in QBO.
I am providing the Invoice ID and tried to follow their developer API documentation but I been at this so long now that maybe I am missing something?
The following code creates the payment but doesn't 'receive' the payment towards the invoice, is there something I missed or need to do in order for the two to be linked together?
Payment payment = new Payment
{
ProcessPayment = false,
CustomerRef = new ReferenceType { name = customer.DisplayName, Value = customer.Id },
CurrencyRef = new ReferenceType { type = "Currency", Value = "CAD" },
TotalAmt = amount,
TotalAmtSpecified = true
};
if (method == PaymentTypes.Cash)
{
var paymentType = paymentMethods.FirstOrDefault(o => o.Id == "1");
if (paymentType != null)
{
payment.PaymentMethodRef = new ReferenceType()
{name = paymentType.Name, Value = paymentType.Id};
}
}
if (method == PaymentTypes.DebitCard)
{
var paymentType = paymentMethods.FirstOrDefault(o => o.Id == "9");
if (paymentType != null)
{
payment.PaymentMethodRef = new ReferenceType()
{ name = paymentType.Name, Value = paymentType.Id };
}
}
if (method == PaymentTypes.CreditCard)
{
var paymentType = paymentMethods.FirstOrDefault(o => o.Id == "8");
if (paymentType != null)
{
payment.PaymentMethodRef = new ReferenceType()
{ name = paymentType.Name, Value = paymentType.Id };
}
}
List<LinkedTxn> linkedTxns = new List<LinkedTxn>
{
new LinkedTxn()
{
TxnId = invoice.Id,
TxnType = TxnTypeEnum.Invoice.ToString()
},
};
foreach (Line line in invoice.Line)
{
//line.Amount = amount;
//line.AmountSpecified = true;
line.Received = amount;
line.ReceivedSpecified = true;
line.DetailType = LineDetailTypeEnum.PaymentLineDetail;
line.DetailTypeSpecified = true;
line.LinkedTxn = linkedTxns.ToArray();
}
payment.DepositToAccountRef = new ReferenceType() { Value = "5" };
payment.Line = invoice.Line;
payment.PaymentRefNum = reference;
DataService dataService = new DataService(serviceContext);
dataService.Add<Payment>(payment);
This is not an answer. However there's too much to add to the comments. I'm hoping this will be a helpful starting point (if not I'll remove it later).
Firstly I'd suggest refactoring your code and parameterise your variables. Doing so you should be able to preform repeatable testing in isolation.
When you preform the dataService.Add<Payment>(payment), store the response object as it may offer clues on how the request was processed. Alternatively if this is suppressing error messages, you might want to try an use Postman to send HTTP requests. This may help determine what's parameters are missing/ incorrect.
Avoid creating objects that are partially assigned it makes it a lot easier to read 7 work out which properties need to be assigned.
Also if you have a look at Full update a payment section on https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/payment
the json payload has an additional Line with the property TxnType set to CreditMemo. I would assume you'll need to add something like ReceivePayment or CreditCardPayment?
To refactor your code consider:
// TODO - Set variables for testing purposes.
// This can be added as params to a method.
decimal amount = 100;
string reference = "";
string invoiceId = ""; // invoice.Id
string customerDisplayName = ""; //customer.DisplayName
string customerId = ""; //customer.Id
string paymentName = "Cash"; // paymentType.Name
string paymentId = "1"; // paymentType.Id
List<Line> lines = new List<Line>(); // invoice.Line
if(lines.Count() == 0)
{
// TODO: You might want to check there are lines?
throw new ArgumentException("No invoice.");
}
Line[] invoiceLines = lines.Select(m => new Line()
{
AnyIntuitObject = m.AnyIntuitObject,
Amount = m.Amount,
AmountSpecified = m.AmountSpecified,
CustomField = m.CustomField,
Id = m.Id,
LineNum = m.LineNum,
Description = m.Description,
DetailType = LineDetailTypeEnum.PaymentLineDetail, //m.DetailType,
DetailTypeSpecified = true, //m.DetailTypeSpecified,
LinkedTxn = new List<LinkedTxn>
{
new LinkedTxn()
{
TxnId = invoiceId,
TxnType = TxnTypeEnum.Invoice.ToString() // TODO: Should this be ReceivePayment?
},
}.ToArray(), //m.LinkedTxn,
LineEx = m.LineEx,
Received = amount, //m.Received,
ReceivedSpecified = true // m.ReceivedSpecified
}).ToArray();
Payment payment = new Payment
{
ProcessPayment = false,
CustomerRef = new ReferenceType { name = customerDisplayName, Value = customerId },
CurrencyRef = new ReferenceType { type = "Currency", Value = "CAD" },
TotalAmt = amount,
TotalAmtSpecified = true,
DepositToAccountRef = new ReferenceType() { Value = "5" },
Line = invoiceLines, // Variable is for debugging purposes - it should be inline or call a method.
PaymentRefNum = reference,
PaymentMethodRef = new ReferenceType()
{
name = paymentName,
Value = paymentId,
}
};
DataService dataService = new DataService(serviceContext);
Payment results = dataService.Add<Payment>(payment);
var json = JsonConvert.SerializeObject(results);
Console.Write(json); // TODO - Use break point/ or write response somewhere for clues.
I'm trying to add data to my database and I keep getting this error:
Cannot insert explicit value for identity column in table "Terms" when IDENTITY_INSERT is set to OFF
The exception gets thrown on SaveChanges(). I've already tried using [DatabaseGenerated(DatabaseGeneratedOption.Identity)] above
public int TermId { get; set; }
and all the other ideas I found on here, but nothing worked. I don't know what to try anymore.
This is part of the OnPost method:
try
{
int? termId;
while ((termId = ReadTermId(ligne, worksheet)) != null)
{
var term = worksheet.Cell(ligne, 2).Value.ToString();
var definition = worksheet.Cell(ligne, 3).Value.ToString();
var listExamplesEntities = new List<Example>();
foreach (var cell in worksheet.Cell(ligne, 4).ToString().Split("\n"))
{
listExamplesEntities.Add(new Example {TermId = termId.Value, LocalizationId = language, Text = cell});
}
var notes = worksheet.Cell(ligne, 6).Value.ToString();
var occurence = worksheet.Cell(ligne, 7).Value.ToString();
var roots = worksheet.Cell(ligne, 8).Value.ToString();
var rootsEntities = new List<Root>();
if (!string.IsNullOrWhiteSpace(roots))
{
var rawRoots = roots.Trim().Split("\n");
for (int i = 0; i < rawRoots.Length; i += 2)
{
var description = "";
if (i < rawRoots.Length - 1)
description = rawRoots[i + 1];
rootsEntities.Add(new Root { TermId = termId.Value, LocalizationId = language,
Definition = description, Word = rawRoots[0]});
if (!string.IsNullOrWhiteSpace(description))
i++;
}
}
// Create and add term
var termEntity = new Term
{
TermId = termId.Value
};
if (db.Terms.Find(termId.Value) == null)
{
db.Terms.Add(termEntity);
}
else
{
db.Terms.Update(termEntity);
}
// Create and add termLocalization
TermLocalization termLocalizationEntity = new TermLocalization
{
TermId = termId.Value,
Term = db.Terms.Find(language),
LocalizationId = language,
Localization = db.Localizations.Find(language),
Roots = rootsEntities,
Examples = listExamplesEntities,
Word = term,
Definition = definition,
Note = notes,
FirstOccurence = occurence,
LastUpdateDate = DateTime.Today
};
if(db.TermLocalizations.SingleOrDefaultAsync(x => x.TermId == termId && x.LocalizationId == language).Result == null)
{
db.TermLocalizations.Add(termLocalizationEntity);
}
else
{
db.TermLocalizations.Update(termLocalizationEntity);
}
db.SaveChanges();
ligne++;
}
Confirmation = "Le fichier a été téléchargé avec succès";
}
catch (Exception ex)
{
Confirmation = "Fichier invalide à la ligne " + ligne;
}
And this is my Term entity:
namespace ENAP.Domain.Entities
{
public class Term
{
public int TermId { get; set; }
}
}
I found the solution to my problem. It may not be the cleanest code, but it worked just fine.
//Create and add term
var transaction = await db.Database.BeginTransactionAsync();
var termEntity = new Term
{
TermId = termId.Value
};
if (db.Terms.Find(termId.Value) == null)
{
db.Terms.Add(termEntity);
}
else
{
db.Terms.Update(termEntity);
}
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Terms ON;");
await db.SaveChangesAsync();
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Terms OFF");
//Create and add termLocalization
TermLocalization termLocalizationEntity = new TermLocalization
{
TermId = termId.Value,
LocalizationId = language,
Roots = rootsEntities,
Examples = listExamplesEntities,
Word = term,
Definition = definition,
Note = notes,
FirstOccurence = occurence,
LastUpdateDate = DateTime.Today
};
if (db.TermLocalizations.SingleOrDefaultAsync(x => x.TermId == termId && x.LocalizationId == language).Result == null)
{
db.TermLocalizations.Add(termLocalizationEntity);
}
else
{
db.TermLocalizations.Update(termLocalizationEntity);
}
await db.SaveChangesAsync();
await transaction.CommitAsync();
ligne++;
remove the "set" (Id) (Model: Term)
public int TermId { get; set; }
public int TermId { get;}
"allocation is handled by the DB"
I'm trying to set up PayPal to accept payment from my site which calculate fees based on the number of photos they uploaded. Followed a tutorial, but I wanted to pass the price which I calculated in my other controller.
My PayPal controller:
public ActionResult PaymentWithPaypal()
{
APIContext apiContext = PayPalConfig.GetAPIContext();
try
{
string payerId = Request.Params["PayerID"];
if (string.IsNullOrEmpty(payerId))
{
string baseURI = Request.Url.Scheme + "://" + Request.Url.Authority + "/Paypal/PaymentWithPayPal?";
var guid = Convert.ToString((new Random()).Next(100000));
var createdPayment = this.CreatePayment(apiContext, baseURI + "guid=" + guid);
var links = createdPayment.links.GetEnumerator();
string paypalRedirectUrl = null;
while (links.MoveNext())
{
Links lnk = links.Current;
if (lnk.rel.ToLower().Trim().Equals("approval_url"))
{
paypalRedirectUrl = lnk.href;
}
}
Session.Add(guid, createdPayment.id);
return Redirect(paypalRedirectUrl);
}
else
{
var guid = Request.Params["guid"];
var executedPayment = ExecutePayment(apiContext, payerId, Session[guid] as string);
if (executedPayment.state.ToLower() != "approved")
{
return View("FailureView");
}
}
}
catch (Exception ex)
{
Logger.Log("Error" + ex.Message);
return View("FailureView");
}
return View("SuccessView");
}
private PayPal.Api.Payment payment;
private PayPal.Api.Payment ExecutePayment(APIContext apiContext, string payerId, string paymentId)
{
var paymentExecution = new PaymentExecution() { payer_id = payerId };
this.payment = new PayPal.Api.Payment() { id = paymentId };
return this.payment.Execute(apiContext, paymentExecution);
}
private PayPal.Api.Payment CreatePayment(APIContext apiContext, string redirectUrl)
{
var itemList = new ItemList() { items = new List<Item>() };
itemList.items.Add(new Item()
{
name = "Participation Fee",
currency = "USD",
price = "5",
quantity = "1",
sku = "sku"
});
var payer = new Payer() { payment_method = "paypal" };
var redirUrls = new RedirectUrls()
{
cancel_url = redirectUrl,
return_url = redirectUrl
};
var details = new Details()
{
tax = "1",
shipping = "1",
subtotal = "5"
};
var amount = new Amount()
{
currency = "USD",
total = "7",
details = details
};
var transactionList = new List<Transaction>();
transactionList.Add(new Transaction()
{
description = "Transaction description.",
invoice_number = "your invoice number",
amount = amount,
item_list = itemList
});
this.payment = new PayPal.Api.Payment()
{
intent = "sale",
payer = payer,
transactions = transactionList,
redirect_urls = redirUrls
};
return this.payment.Create(apiContext);
}
Controller where my price is calculated:
int Asection;
int Bsection;
int Csection;
int Dsection;
if (viewPhotos.GetA1.Any() || viewPhotos.GetA2.Any() || viewPhotos.GetA3.Any() || viewPhotos.GetA4.Any())
{
Asection = 1;
}
else
{
Asection = 0;
}
if (viewPhotos.GetB1.Any() || viewPhotos.GetB2.Any() || viewPhotos.GetB3.Any() || viewPhotos.GetB4.Any())
{
Bsection = 1;
}
else
{
Bsection = 0;
}
if (viewPhotos.GetC1.Any() || viewPhotos.GetC2.Any() || viewPhotos.GetC3.Any() || viewPhotos.GetC4.Any())
{
Csection = 1;
}
else
{
Csection = 0;
}
if (viewPhotos.GetD1.Any() || viewPhotos.GetD2.Any() || viewPhotos.GetD3.Any() || viewPhotos.GetD4.Any())
{
Dsection = 1;
}
else
{
Dsection = 0;
}
int TotalSection = Asection + Bsection + Csection + Dsection;
viewPhotos.MoneyValue = TotalSection;
int RequiredMoney;
if (TotalSection == 1)
{
RequiredMoney = 20;
}
else if (TotalSection == 2)
{
RequiredMoney = 25;
}
else if (TotalSection == 3)
{
RequiredMoney = 30;
}
else
{
RequiredMoney = 36;
}
viewPhotos.RequiredMoney = RequiredMoney;
return View(viewPhotos);
My view where price was shown to user:
<p>You will need to pay participation fees USD #Model.RequiredMoney.</p>
<h3>Total: USD #Model.RequiredMoney</h3>
#Html.ActionLink("Make Payment with PayPal", "PaymentWithPaypal", "Paypal")
So far the above codes work with the default testing item price and detail on the site. Would appreciate if anyone could help to show how do I set the amount to be collected by PayPal to my calculated price, without any shipping or tax. Thanks in advance.
After searching and reviewing the log for a while, here's how I solved my problem:
At the controller where I calculated my price, I used TempData to store my price:
TempData["ParticipationFee"] = RequiredMoney;
Then at the PaypalController, under CreatePayment function,
var itemList = new ItemList() { items = new List<Item>() };
string FeeAmount = TempData["ParticipationFee"].ToString();
itemList.items.Add(new Item()
{
name = "Participation Fee",
currency = "USD",
price = FeeAmount,
quantity = "1",
sku = "sku"
});
Hit F5 and got a successful response from Paypal Sandbox. Woot!
If your adding several related entities at once, should you call SaveChanges after each, or can it be done once? Im getting an error such as "Violation of PRIMARY KEY constraint".
Orders has a navigation property to an entity Records.
I want to add Records to an order, then call savechanges.
When I do, I get an error complaining about the primary key on Records.
I want to do something like this
Order o = new Order{.......}
Record r1 = new Record{...}
Record r2 = new Record{....}
o.Records.Add(r1)
o.Records.Add(r2)
Context.Orders.Add(o);
Context.SaveChanges()
Isnt this the right approach?
...
//populate order entity
Order o = new Order { AccountKey = iAccnt, OfficeKey = iOffice, UserKey = iUser, CreatedDate = dtCreateDate };
//save
ctx.Orders.Add(o);
//locations
Location origin = new Location
{
Order = o,
LocationTypeKey = ctx.LocationTypes.Where(a => a.TypeName == "Origin").First().LocationTypeId,
Address1 = " test address 1",
Address2 = " test Address 2",
City = oi.OriginCity,
StateKey = ctx.States.Where(a => a.StateName == oi.OriginState).First().StateId,
CountryKey = ctx.Countries.Where(a => a.CountryName == oi.OriginCountry).First().CountryId
};
Location destn = new Location
{
Order = o,
LocationTypeKey = ctx.LocationTypes.Where(a => a.TypeName == "Destination").First().LocationTypeId,
Address1 = " test address 1",
Address2 = " test Address 2",
City = oi.OriginCity,
StateKey = ctx.States.Where(a => a.StateName == oi.DestinationState).First().StateId,
CountryKey = ctx.Countries.Where(a => a.CountryName == oi.DestinationCountry).First().CountryId
};
try
{
int rows = ctx.SaveChanges();
if (rows == 0)
{
retval = false;
}
}
catch (DbEntityValidationException ex)
{
string exst = ex.ToString();
}
I'm trying to specify a Product/Service list item for invoice line items of invoices that I am importing to QuickBooks Online (QBO) company file and I am getting an error.
Error I receive is:
Intuit.Ipp.Exception.IdsException: InternalServerError ---> Intuit.Ipp.Exception.EndpointNotFoundException: Ids service endpoint was not found.
The exception doesn't give any further breakdown as to if what I'm doing is valid or not.
My unit test method:
[TestMethod()]
public void CreateTest()
{
Entities.Invoice invoice = new Entities.Invoice();
invoice.ReferenceId = Guid.NewGuid().ToString("N").Substring(0, 10);
invoice.CreatedDate = DateTime.Now;
invoice.CustomerId = 1;
invoice.LineItems.Add(new InvoiceLine() { ItemName = "Initial Funding", Description = "Initial Funding", Amount = 5500 });
invoice.LineItems.Add(new InvoiceLine() { ItemName = "Lien Fee", Description = "Lien Fee", Amount = 100 });
IPPRestProfile restProfile = new IPPRestProfile(realmId, accessToken, accessTokenSecret, Intuit.Ipp.Core.IntuitServicesType.QBO, consumerKey, consumerSecret);
IPP.Invoices target = new IPP.Invoices(restProfile);
Intuit.Ipp.Data.Invoice actual = target.Create(invoice);
if (actual != null)
{
Console.WriteLine("QB Invoice ID: {0}", actual.Id);
Console.WriteLine("QB Sync Token: {0}", actual.SyncToken);
Console.WriteLine("================================================");
ObjectDumper.Write(actual, 4);
}
}
The method that the unit test calls:
public Intuit.Ipp.Data.Invoice Create(Entities.Invoice invoice)
{
// Check pre-conditions
if (invoice == null) { throw new ArgumentException("Invoice object is required.", "invoice"); }
var qboInvoice = new Intuit.Ipp.Data.Invoice();
BuildInvoiceEntity(qboInvoice, invoice);
return _Service.Add(qboInvoice) as Intuit.Ipp.Data.Invoice;
}
And finally the build invoice method:
private void BuildInvoiceEntity(Intuit.Ipp.Data.Invoice qboInvoice, Entities.Invoice invoice)
{
if (qboInvoice != null && invoice != null)
{
IQuickBooksHeader header = invoice as IQuickBooksHeader;
if (String.IsNullOrEmpty(header.Id))
{
qboInvoice.DocNumber = invoice.ReferenceId;
qboInvoice.TxnDate = invoice.CreatedDate;
qboInvoice.TxnDateSpecified = true;
// Customer
qboInvoice.CustomerRef = new ReferenceType()
{
type = objectNameEnumType.Customer.ToString(),
Value = invoice.CustomerId.ToString()
};
// AR Account
qboInvoice.ARAccountRef = new ReferenceType()
{
type = objectNameEnumType.Account.ToString(),
name = "Accounts Receivable"
};
}
if (invoice.LineItems.Count > 0)
{
Intuit.Ipp.Data.Line[] invoiceLineCollection = new Intuit.Ipp.Data.Line[invoice.LineItems.Count];
for (int i = 0; i < invoice.LineItems.Count; i++)
{
var line = invoice.LineItems[i];
var qboInvoiceLine = new Intuit.Ipp.Data.Line()
{
Amount = line.Amount,
AmountSpecified = true,
Description = line.Description,
DetailType = LineDetailTypeEnum.SalesItemLineDetail,
DetailTypeSpecified = true,
AnyIntuitObject = new SalesItemLineDetail()
{
ItemRef = new ReferenceType()
{
name = line.ItemName,
},
ItemElementName = ItemChoiceType.UnitPrice,
AnyIntuitObject = line.Amount
}
};
invoiceLineCollection[i] = qboInvoiceLine;
}
qboInvoice.Line = invoiceLineCollection;
}
}
}
If I remove this piece of code from my build method:
ItemRef = new ReferenceType()
{
name = line.ItemName,
},
the invoice is successfully added with the default "Services" list item for the Product/Service of the invoice line items.
The online documentation for the IPP .NET SDK V3 is vague on what to specify for ReferenceType. What is wrong about just specifying the name of the list item? If I'm wrong about how I'm trying to specify a Product/Service list item for invoice line items, what is the correct way?
After days of researching, I never did find an answer as to why I can't just use the name like I wanted to, even though it works that way when specifying an AccountRef. But I digress, here is my solution:
// Hold a collection of QBO items
private ReadOnlyCollection<Item> _Items;
// I set the collection in the constructor only once
public Invoices(Entities.IPPRestProfile restProfile)
{
if (restProfile == null)
throw new ArgumentException("IPPRestProfile object is required.", "restProfile");
OAuthRequestValidator oAuthValidator = new OAuthRequestValidator(restProfile.OAuthAccessToken, restProfile.OAuthAccessTokenSecret,
restProfile.ConsumerKey, restProfile.ConsumerSecret);
ServiceContext context = new ServiceContext(restProfile.RealmId, restProfile.DataSource, oAuthValidator);
_Service = new DataService(context);
_Items = (new QueryService<Item>(context)).ExecuteIdsQuery("SELECT * FROM Item", QueryOperationType.query);
}
Whenever I build my invoice I query the collection for the Id of the item by name:
private void BuildInvoiceEntity(Intuit.Ipp.Data.Invoice qboInvoice, Entities.Invoice invoice)
{
...
// Get the Id value of the item by name
string itemTypeId = _Items.Where(o => o.Name == line.ItemName).FirstOrDefault().Id;
// Specify the Id value in the item reference of the SalesItemLineDetail
var qboInvoiceLine = new Intuit.Ipp.Data.Line()
{
Amount = (decimal)amount,
AmountSpecified = true,
Description = line.Description,
DetailType = LineDetailTypeEnum.SalesItemLineDetail,
DetailTypeSpecified = true,
AnyIntuitObject = new SalesItemLineDetail()
{
ItemRef = new ReferenceType() { Value = itemTypeId },
AnyIntuitObject = (decimal)line.Rate,
ItemElementName = ItemChoiceType.UnitPrice
}
};
...
}
Hopefully this helps point someone in the right direction to a possible better solution.