Using the Dynamics GP Web Service API on GP 2013, how do I apply payments to a Sales Invoice? I receive the error in the subject line. Here is my code:
static void CreateInvoice()
{
CompanyKey companyKey;
Context context;
SalesInvoice salesInvoice;
SalesDocumentTypeKey salesInvoiceType;
CustomerKey customerKey;
BatchKey batchKey;
SalesInvoiceLine salesInvoiceLine;
ItemKey invoiceItem;
Quantity invoiceCount;
Policy salesInvoiceCreatePolicy;
// Create an instance of the service
DynamicsGP wsDynamicsGP = new DynamicsGP();
// Be sure the default credentials are used
wsDynamicsGP.UseDefaultCredentials = true;
// Create a context with which to call the service
context = new Context();
// Specify which company to use (sample company)
companyKey = new CompanyKey();
companyKey.Id = (-1);
// Set up the context object
context.OrganizationKey = (OrganizationKey)companyKey;
// Create a sales invoice object
salesInvoice = new SalesInvoice();
// Create a sales document type key for the sales invoice
salesInvoiceType = new SalesDocumentTypeKey();
salesInvoiceType.Type = SalesDocumentType.Invoice;
// Populate the document type key for the sales invoice
salesInvoice.DocumentTypeKey = salesInvoiceType;
// Create a customer key
customerKey = new CustomerKey();
customerKey.Id = "ADMINISTRATORGL";
// Set the customer key property of the sales invoice
salesInvoice.CustomerKey = customerKey;
// Create a batch key
batchKey = new BatchKey();
batchKey.Id = "SALES INVOICES";
// Set the batch key property of the sales invoice object
salesInvoice.BatchKey = batchKey;
// Create a sales invoice line to specify the invoiced item
salesInvoiceLine = new SalesInvoiceLine();
// Create an item key
invoiceItem = new ItemKey();
invoiceItem.Id = "ADI123";
// Set the item key property of the sales invoice line object
salesInvoiceLine.ItemKey = invoiceItem;
// Create a sales invoice quatity object
invoiceCount = new Quantity();
invoiceCount.Value = 2;
// Set the quantity of the sales invoice line object
salesInvoiceLine.Quantity = invoiceCount;
salesInvoiceLine.WarehouseKey = new WarehouseKey { Id = "NORTH" };
// Create an array of sales invoice lines
// Initialize the array with the sales invoice line object
SalesInvoiceLine[] invoiceLines = { salesInvoiceLine };
// Add the sales invoice line array to the sales line object
salesInvoice.Lines = invoiceLines;
//salesInvoice.LineTotalAmount = new MoneyAmount { Value = 399.96M };
salesInvoice.DepositAmount = new MoneyAmount { Value = 399.96M };
salesInvoice.PaymentAmount = new MoneyAmount { Value = 399.96M };
// Get the create policy for the sales invoice object
salesInvoiceCreatePolicy = wsDynamicsGP.GetPolicyByOperation("CreateSalesInvoice", context);
// Create the sales invoice
wsDynamicsGP.CreateSalesInvoice(salesInvoice, context, salesInvoiceCreatePolicy);
}
I had to create a customer and set the credit limit to unlimited.
//assign unlimited class
CustomerCreditLimit cc = new CustomerCreditLimit();
cc.Item = CreditLimitSpecialAmount.UnlimitedCredit;
customer.CreditLimit = cc;
My blog post has more details on the code:
http://www.jmawebtechnologies.com/company-blog/may-2013/appying-payments-to-sales-invoices-and-sales-order
Related
I'm trying add this transaction named placeOrder i want to add a Customer participant
before creating Order asset and map its relationship with the Order asset while processing this transaction. But I'm getting customer not defined error. Can anybody help? Thanks.
My models
namespace org.isn.customer
participant Customer identified by email {
o String firstName
o String lastName
o String email
o String password
}
enum Status{
o ACTIVE
o OFF_THE_ROAD
}
asset Vehicle identified by serial {
o String brand
o String model
o String color
o Status status
o Double price
o String serial
}
asset Order identified by orderId{
o String orderId
o Vehicle item
--> Customer customer
}
transaction PlaceOrder {
o String orderId
--> Vehicle item
o Customer customer
}
script.js
/**
* #param {org.isn.shop.PlaceOrder}orderRequest
* #transaction
*/
async function placeOrder(orderRequest){
const factory = getFactory();
const customerRegistry = await getParticipantRegistry("org.isn.customer.Customer");
const customerExists = await customerRegistry.exists(orderRequest.customer.email);
if(!customerExists){
const customer = factory.newResource("org.isn.customer","Customer",orderRequest.customer.email);
customer.firstName = orderRequest.customer.firstName;
customer.lastName = orderRequest.customer.lastName;
customer.email = orderRequest.customer.email;
customer.password = orderRequest.customer.password;
await customerRegistry.add(customer);
}else{
const customer = await customerRegistry.get(orderRequest.customer.email);
}
const order = await factory.newResource("org.isn.shop","Order",orderRequest.orderId);
order.customer = customer.getIdentifier();
order.item = orderRequest.item;
const orderRegistry = await getAssetRegistry("org.isn.shop.Order");
await orderRegistry.add(order);
const PlaceOrderEvent = factory.newEvent("org.isn.shop","PlaceOrderEvent");
placeOrderEvent.order = order;
emit(placeOrderEvent);
}
o Customer customer needs to become --> Customer customer then you will be able to reference orderRequest.customer.firstName etc also not sure you need order.customer = customer.getIdentifier(); might need to become order.customer.email = customer.getIdentifier();
The following version of the Model and the JS will work for a new and existing customer:
namespace org.isn.customer
participant Customer identified by email {
o String firstName
o String lastName
o String email
o String password
}
enum Status{
o ACTIVE
o OFF_THE_ROAD
}
asset Vehicle identified by serial {
o String brand
o String model
o String color
o Status status
o Double price
o String serial
}
asset Order identified by orderId{
o String orderId
--> Vehicle item
--> Customer customer
}
transaction PlaceOrder {
o String orderId
--> Vehicle item
o Customer customer
}
/**
* #param {org.isn.customer.PlaceOrder}orderRequest
* #transaction
*/
async function placeOrder(orderRequest){
const factory = getFactory();
const customerRegistry = await getParticipantRegistry("org.isn.customer.Customer");
const customerExists = await customerRegistry.exists(orderRequest.customer.email);
if(!customerExists){
var customer = factory.newResource("org.isn.customer","Customer",orderRequest.customer.email);
customer.firstName = orderRequest.customer.firstName;
customer.lastName = orderRequest.customer.lastName;
customer.email = orderRequest.customer.email;
customer.password = orderRequest.customer.password;
await customerRegistry.add(customer);
}else{
var customer = await customerRegistry.get(orderRequest.customer.email);
}
const order = await factory.newResource("org.isn.customer","Order",orderRequest.orderId);
order.customer = factory.newRelationship("org.isn.customer","Customer",customer.getIdentifier());
order.item = orderRequest.item;
const orderRegistry = await getAssetRegistry("org.isn.customer.Order");
await orderRegistry.add(order);
}
Please note the following:
In the Order asset I have changed the o Vehicle to be --> Vehicle item to match up with the Transaction.
I have also used var customer in both the if and else code.
(To make mine work I had to use the same namespace, but perhaps you had separate .cto files)
I am trying to create and confirm shipment programmatically for a single order. But I am stuck on how to select all the lines that are displayed on ADD Order popup for adding SOLines on Shipments screen. Any help on this?
string operation = SOOperation.Issue;
SOShipmentEntry shipmentGraph = PXGraph.CreateInstance<SOShipmentEntry>();
BAccountR customer = null;
INSite warehouse = null;
PXResultset<SOOrder> objSOOrder = PXSelect<SOOrder, Where<SOOrder.customerRefNbr, Equal<Required<SOImportFilter.referenceID>>>>.Select(this, currentFilter.ReferenceID);
foreach(SOOrder order in objSOOrder)
{
shipmentGraph.Clear();
var shipment = shipmentGraph.Document.Insert();
customer = (BAccountR)PXSelect<BAccountR,
Where<BAccountR.bAccountID, Equal<Required<BAccountR.bAccountID>>>>
.SelectSingleBound(shipmentGraph, new object[] { }, order.CustomerID);
shipment.CustomerID = customer.BAccountID;
shipment = shipmentGraph.Document.Update(shipment);
warehouse = (INSite)PXSelect<INSite,
Where<INSite.siteID, Equal<Required<INSite.siteID>>>>
.SelectSingleBound(shipmentGraph, new object[] { }, "159");
shipment.SiteID = warehouse.SiteID;
shipment = shipmentGraph.Document.Update(shipment);
var addorder = shipmentGraph.addsofilter.Insert();
addorder.Operation = operation;
addorder = shipmentGraph.addsofilter.Update(addorder);
addorder.OrderType = order.OrderType;
addorder = shipmentGraph.addsofilter.Update(addorder);
addorder.OrderNbr = order.OrderNbr;
addorder = shipmentGraph.addsofilter.Update(addorder);
foreach (PXResult<SOShipmentPlan, SOLineSplit, SOLine> plan in
shipmentGraph.soshipmentplan.Select())
{
SOShipmentPlan shipmentPlan = (SOShipmentPlan)plan;
shipmentPlan.Selected = true;
shipmentGraph.soshipmentplan.Update(plan);
shipmentGraph.Actions.PressSave();
}
shipmentGraph.Actions.PressSave();
}
I have difficulty understanding the feature you are trying to implement from the description. Usually you would automate CreateShipment and ConfirmShipment actions to do this.
Perhaps you have to handle a special case, if all that is blocking you is selecting the data from the grid inside the "Add Order" smart panel:
Using Inspect Element feature, determine the name of the DataView you target by clicking on the grid:
Use View Business Logic Source to look up the DataView source code:
From the source code for that DataView, we see that it returns 3 DAC (SOShipmentPlan, SOLineSplit and SOLine):
PXSelectJoinOrderBy<SOShipmentPlan,
InnerJoin<SOLineSplit, On<SOLineSplit.planID, Equal<SOShipmentPlan.planID>>,
InnerJoin<SOLine, On<SOLine.orderType, Equal<SOLineSplit.orderType>, And<SOLine.orderNbr, Equal<SOLineSplit.orderNbr>, And<SOLine.lineNbr, Equal<SOLineSplit.lineNbr>>>>>>,
OrderBy<Asc<SOLine.sortOrder, Asc<SOLine.lineNbr, Asc<SOLineSplit.lineNbr>>>>> soshipmentplan;
With that information we can now iterate the DataView using the Select method:
foreach (PXResult<SOShipmentPlan, SOLineSplit, SOLine> plan in Base.soshipmentplan.Select())
{
SOShipmentPlan shipmentPlan = (SOShipmentPlan)plan;
SOLineSplit lineSplit = (SOLineSplit)plan;
SOLine line = (SOLine)plan;
}
I used Base member to reference SOShipmentEntry graph in order to get the DataView. This should be used when you are in the context of a SOShipmentEntry graph extension:
Base.soshipmentplan.Select()
If you have a direct reference to SOShipmentEntry graph instead you can use that directly:
SOShipmentEntry shipmentEntry = PXGraph.CreateInstance<SOShipmentEntry>();
shipmentEntry.soshipmentplan.Select()
EDIT
Code for automating the Add Order dialog:
shipmentEntry.addsofilter.Current.OrderType = SOOrderTypeConstants.SalesOrder;
shipmentEntry.addsofilter.Current.OrderNbr = "000001";
shipmentEntry.addsofilter.Update(shipmentEntry.addsofilter.Current);
foreach (SOShipmentPlan line in shipmentEntry.soshipmentplan.Select())
{
line.Selected = true;
shipmentEntry.soshipmentplan.Update(line);
}
shipmentEntry.addSO.Press();
I'm attempting to process creit cards through PayPal and keep getting this weird error:
{"name":"VALIDATION_ERROR","details":[{"field":"transactions[0].amount","issue":"Transaction
amount details (subtotal, tax, shipping) must add up to specified
amount total"}],"message":"Invalid request - see
details","information_link":"https://developer.paypal.com/webapps/developer/docs/api/#VALIDATION_ERROR","debug_id":"6e2cef39d556a"}
Here's the method I'm using:
public ActionResult PaymentWithCreditCard(PayWithCCViewModel model)
{
var cart = ShoppingCart.GetCart(this.HttpContext);
var cartItems = cart.GetCartItems();
List<Item> orderItems = new List<Item>();
foreach (var cartItem in cartItems)
{
var item = new Item();
item.name = cartItem.Product.ProductName;
item.currency = "USD";
item.price = cartItem.ProductPrice.ToString();
item.quantity = cartItem.ProductQuantity.ToString();
orderItems.Add(item);
}
ItemList itemList = new ItemList();
itemList.items = orderItems;
//Address for the payment
Address billingAddress = new Address();
billingAddress.city = model.BillingCity;
billingAddress.country_code = "US";
billingAddress.line1 = model.BillingAddressLine1;
billingAddress.line2 = model.BillingAddressLine2;
billingAddress.postal_code = model.BillingPostalCode;
billingAddress.state = model.BillingState;
//Now Create an object of credit card and add above details to it
CreditCard card = new CreditCard();
card.billing_address = billingAddress;
card.cvv2 = model.CVV2;
card.expire_month = model.ExpirationMonth;
card.expire_year = model.ExpirationYear;
card.first_name = model.FirstName;
card.last_name = model.LastName;
card.number = model.Number;
card.type = model.CardType.ToString();
var subTotal = cart.GetTotal();
var tax = Convert.ToDouble(subTotal) * Convert.ToDouble(.076);
var total = Convert.ToDouble(subTotal) + tax;
// Specify details of your payment amount.
Details details = new Details();
details.shipping = "3";
details.subtotal = subTotal.ToString();
details.tax = tax.ToString();
// Specify your total payment amount and assign the details object
Amount amount = new Amount();
amount.currency = "USD";
// Total = shipping tax + subtotal.
amount.total = total.ToString();
amount.details = details;
// Now make a trasaction object and assign the Amount object
Transaction transaction = new Transaction();
transaction.amount = amount;
transaction.description = "Payment to AccessorizeForLess.net";
transaction.item_list = itemList;
transaction.invoice_number = cart.GetCartId(this.HttpContext);
// Now, we have to make a list of trasaction and add the trasactions object
// to this list. You can create one or more object as per your requirements
List<Transaction> transactions = new List<Transaction>();
transactions.Add(transaction);
// Now we need to specify the FundingInstrument of the Payer
// for credit card payments, set the CreditCard which we made above
FundingInstrument fundInstrument = new FundingInstrument();
fundInstrument.credit_card = card;
// The Payment creation API requires a list of FundingIntrument
List<FundingInstrument> fundingInstrumentList = new List<FundingInstrument>();
fundingInstrumentList.Add(fundInstrument);
// Now create Payer object and assign the fundinginstrument list to the object
Payer payr = new Payer();
payr.funding_instruments = fundingInstrumentList;
payr.payment_method = "credit_card";
// finally create the payment object and assign the payer object & transaction list to it
Payment pymnt = new Payment();
pymnt.intent = "sale";
pymnt.payer = payr;
pymnt.transactions = transactions;
try
{
//getting context from the paypal, basically we are sending the clientID and clientSecret key in this function
//to the get the context from the paypal API to make the payment for which we have created the object above.
// Basically, apiContext has a accesstoken which is sent by the paypal to authenticate the payment to facilitator account. An access token could be an alphanumeric string
APIContext context = PayPalModel.GetAPIContext();
// Create is a Payment class function which actually sends the payment details to the paypal API for the payment. The function is passed with the ApiContext which we received above.
Payment payment = pymnt.Create(context);
//if the createdPayment.State is "approved" it means the payment was successfull else not
if (payment.state.ToLower() != "approved")
{
return View("FailureView");
}
}
catch (PayPalException ex)
{
Logger.Log("Error: " + ex.Message);
return View("FailureView");
}
return View("SuccessView");
}
I've stepped through the code and the total, tax and shipping seem to be correct, anyone have issues when dealing with PayPal before that could led me t a solution?
I forgot to add shipping to the total, now I get an approval
As I mentioned in my previous question( How to customize the sales order process to trigger an automatic "adding contract" process when sales order is successfully completed), I need to automatically add a contract for each of some particular products that are in a sales order after this sales order is added.
I have learned adding contract part in previous questions,thanks to #Gabriel's response, and now I need to know how to get those order information such as inventory id in order items, customer id and location id in a sales order business logic (screen SO301000). Would anybody please kindly provide me some sample code?
Thanks.
Now I seem to be able to get customer id and location id from code:
SOOrder SalesOrder = (SOOrder)Base.Caches[typeof(SOOrder)].Current;
int customer_id = SalesOrder.CustomerID;
int Location ID = SalesOrder.CustomerLocationID;
....
but I still need to find out how to iterate through product list (SOLine item) in the order...the code I found as below (it was an example for implementing a SO release operation) in T200 training PDF seems too old and not helpful to me:
public static void ReleaseOrder(SalesOrder order)
{
SalesOrderEntry graph = PXGraph.CreateInstance<SalesOrderEntry>();
graph.Orders.Current = order;
foreach (OrderLine line in graph.OrderDetails.Select())
{
ProductQty productQty = new ProductQty();
productQty.ProductID = line.ProductID;
productQty.AvailQty = -line.OrderQty;
graph.Stock.Insert(productQty);
}
order.ShippedDate = graph.Accessinfo.BusinessDate;
order.Status = OrderStatus.Completed;
graph.Orders.Update(order);
graph.Persist();
}
This is how you have to loop through the lines while you override persist in your extension
foreach (SOLine line in this.Base.Transactions.Select())
{
}
What you are doing here is you are looping through the valid records in the cache by executing select method. For this you have to find the view definition (Transactions) related to the DAC(SOLine) from the Base BLC definitions.
I figured out how to do it and the below is the code what I have and it's working for me so far - you can see I use "SOLine_RowPersisted" instead of customizing "Persist()" as I did before.
protected virtual void SOLine_RowPersisted(PXCache sender,PXRowPersistedEventArgs e)
{
if (e.TranStatus == PXTranStatus.Completed)
{
if ((e.Operation & PXDBOperation.Command) == PXDBOperation.Insert)
{
SOOrder SalesOrder = (SOOrder)Base.Caches[typeof(SOOrder)].Current;
SOLine line = (SOLine)e.Row;
// Lookup inventory
InventoryItem template = PXSelect<InventoryItem,
Where<InventoryItem.inventoryID, Equal<Required<InventoryItem.inventoryID>>>>
.Select(Base, line.InventoryID);
if (template.InventoryCD == null)
{
throw new PXException("Inventory CD can not be blank.");
}
if (template.InventoryCD.StartsWith("AAABBB"))
{
ContractMaint contractMaint = PXGraph.CreateInstance<ContractMaint>();
CTBillEngine engine = PXGraph.CreateInstance<CTBillEngine>();
DateTime StartDate = DateTime.Now;
........
string contractCD = ......
Contract contract = SetupActivateContract(contractMaint, contractCD, StartDate , line.CustomerID, SalesOrder.CustomerLocationID, engine);
}
} else if ((e.Operation & PXDBOperation.Command) == PXDBOperation.Delete)
{
.....
} else if ((e.Operation & PXDBOperation.Command) == PXDBOperation.Update)
{
....
}
}
}
I have an appointment entity which has an association with contact entity. I am trying to remove all association from this appointment entity without explicitly providing contact_id. Here is my code snippet with create appointment entity and then associate with an existing contact.
Entity activity = new Entity("appointment");
activity["scheduledstart"] = DateTime.Now;
activity["scheduledend"] = DateTime.Now.AddMinutes(30);
activity["subject"] = "Test Meeting";
activity["description"] = "Test Description";
activity["owneridname"] = "test_user";
activity["location"] = "Dallas";
EntityCollection attendees = new EntityCollection();
Entity attendee1 = new Entity("activityparty");
attendee1["addressused"] = "test.test#acmegroup.com";
attendees.Entities.Add(attendee1);
activity["requiredattendees"] = attendees;
Guid id = _service.Create(activity);
Console.WriteLine("id: " + id);
AssociateRequest associateRequest = new AssociateRequest();
associateRequest.Relationship = new Relationship("new_appointment_contact");
associateRequest.Target = new Microsoft.Xrm.Sdk.EntityReference("appointment", id);
EntityReferenceCollection referenceCollection = new EntityReferenceCollection();
Microsoft.Xrm.Sdk.EntityReference entityReference = new Microsoft.Xrm.Sdk.EntityReference("contact", new Guid("e6e71e53-b44b-e211-a81e-0050568b36bf"));
referenceCollection.Add(entityReference);
associateRequest.RelatedEntities = referenceCollection;
// Execute the request.
_service.Execute(associateRequest);
I am aware of using DisassociateRequest to remove this association but I don't want to explicitly provide contact_id. I just need something like .Clear() which can
remove all contact association.
DisassociateRequest disassociateRequest = new DisassociateRequest();
disassociateRequest.Relationship = new Relationship("new_appointment_contact");
disassociateRequest.Target = new Microsoft.Xrm.Sdk.EntityReference("appointment", id);
EntityReferenceCollection referenceCollection2 = new EntityReferenceCollection();
Microsoft.Xrm.Sdk.EntityReference entityReference2 = new Microsoft.Xrm.Sdk.EntityReference("contact", new Guid("e6e71e53-b44b-e211-a81e-0050568b36bf"));
referenceCollection2.Add(entityReference2);
disassociateRequest.RelatedEntities = referenceCollection2;
// Execute the request.
_service.Execute(disassociateRequest);
I used GetRelatedEntities method to pull all related entities into collection and then use this collection to disassociate all related entities:
DisassociateRequest disassociateRequest = new DisassociateRequest();
disassociateRequest.Relationship = new Relationship("new_appointment_contact");
disassociateRequest.Target = new Microsoft.Xrm.Sdk.EntityReference("appointment", id);
EntityReferenceCollection referenceCollection2 = new EntityReferenceCollection();
Entity existingAppointment = _service.Retrieve("appointment", id, new ColumnSet(true));
foreach (Entity item in existingAppointment.GetRelatedEntities(orgContext, "new_appointment_contact"))
{
Guid contactId = new Guid(item["contactid"].ToString());
Microsoft.Xrm.Sdk.EntityReference entityReference2 = new Microsoft.Xrm.Sdk.EntityReference("contact", contactId);
referenceCollection2.Add(entityReference2);
}
disassociateRequest.RelatedEntities = referenceCollection2;
_service.Execute(disassociateRequest);