Create SO via REST Web Service Endpoint does not apply Discounts - acumatica

When a Sales Order is created via a REST API call applicable Discounts are not applied automatically like they are when creating the same SO via the standard Acumatica screen. The Discounts should apply and are not "Manual" -- running the "Recalculate Prices" action after being created via the API will apply the Discounts.
Do the Discount Codes have to be specified in the API call or is there some way to get them to be automatically applied like on the screen? Orders received via the API are given a unique Order Type so we could reasonably do a customization to the SOOrderEntry graph for these particular order types that when the SO is initially created to run the "Recalculate Prices" but I couldn't find a way to successfully trigger the Base Action either.

Currently, discounts are completely skipped for all Sales Orders and AR Invoices created or updated via Web API or Import Scenarios. To launch discounts calculation through Web API, you should proceed as follows:
Create a hidden action in the SOOrderEntry BLC extension to recalculate discounts for the current order:
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> RecalculateDiscountsFromImport;
[PXButton]
[PXUIField(DisplayName = "Recalculate Discounts",
MapEnableRights = PXCacheRights.Select,
MapViewRights = PXCacheRights.Select,
Visible = false)]
public void recalculateDiscountsFromImport()
{
foreach (SOLine line in Base.Transactions.Select())
{
SOLine oldLine = Base.Transactions.Cache.CreateCopy(line) as SOLine;
DiscountEngine<SOLine>.RecalculateDiscountsLine<SOOrderDiscountDetail>(
Base.Transactions.Cache,
Base.Transactions,
line,
line,
Base.DiscountDetails,
Base.Document.Current.BranchID,
Base.Document.Current.CustomerLocationID,
Base.Document.Current.OrderDate.Value);
TX.TaxAttribute.Calculate<SOLine.taxCategoryID>(
Base.Transactions.Cache,
new PXRowUpdatedEventArgs(line, oldLine, false));
}
Base.Transactions.Cache.IsDirty = true;
}
}
Add mapping for the custom action above in an extended endpoint and invoke the action via the Contract API in a separate request after Sales Order was created and saved in Acumatica.

Based on RuslanDev's answer and a couple changes this was the action implemented and then executed in a subsequent API call:
public PXAction<SOOrder> RecalculateDiscountsFromImport;
[PXButton]
[PXUIField(DisplayName = "Recalculate Discounts",
MapEnableRights = PXCacheRights.Select,
MapViewRights = PXCacheRights.Select,
Visible = false)]
public void recalculateDiscountsFromImport()
{
foreach (SOLine line in Base.Transactions.Select())
{
DiscountEngine<SOLine>.RecalculateDiscountsLine<SOOrderDiscountDetail>(
Base.Transactions.Cache,
Base.Transactions,
line,
line,
Base.DiscountDetails,
Base.Document.Current.BranchID,
Base.Document.Current.CustomerLocationID,
Base.Document.Current.OrderDate.Value);
Base.Transactions.Update(line);
Base.Transactions.SetValueExt<SOLine.manualDisc>(line, false);
}
Base.Actions.PressSave();
}

Related

Passing parameters to GraphAction in Acumatica from toolbar button

(Edited to better reflect the current state after help from the comments)
I have a graph extension to which I have added a PXAction and PXButton etc. The method (now) accepts parameters.
The button appears on the screen as expected an I can debug the action code in Visual Studio when the button is clicked.
// test params action.
public PXAction<InventoryItem> testParams;
[PXUIField(DisplayName = "Test Params", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
[PXButton(CommitChanges = true)]
public virtual IEnumerable TestParams(PXAdapter adapter, [PXInt] int? siteID, [PXString] string testParamStr)
{
IEnumerable ret = adapter.Get();
if (adapter.Parameters != null)
{
int a = 0;
a++;
}
return adapter.Get();
}
After adding the parameters to my C# method and re publishing etc, the parameters appeared as options in the Parameter column below and I have been able to configure parameters to send through when I press the button on my stock item screen:
However... when I debug and look at the adapter, the method arguments (siteId and testParamsStr) are null as is adapter.Parameters
and adapter.CommandArguments is an empty string.
As per Hugues Beauséjour's comment, the documentation for amending an existing action describes just this but how do we get at the values in the C# code? Looking at existing actions in Acumatica I don't think I'm missing any markup attributes etc.
Ideally I'd like a zero-code way to do what I'm trying to do but I'm not sure that's possible so I'm trying very short action code that passes values to a central class we will write to allow users to set up their own parameters that the central class will process so that they don't have to do any lengthy coding in the action code (the rest of the story is longer but that's the bit that's relevant to this question!).
Thanks!
This is how to pass parameters using code only. I'm posting it to help other people who might need it. For configuring the wizard parameters, this will not help much unfortunately.
// Graph Action
public PXAction<MyPrimaryDAC> MyAction;
[PXButton]
[PXUIField(DisplayName = "My Action")]
public virtual IEnumerable myAction(PXAdapter adapter)
{
// String value coming from JavaScript is read in adapter.CommandArguments
string payload = adapter.CommandArguments;
}
// JavaScript calls the graph action with a string parameter in payload variable
var ds = px_alls['ds'];
var actionName = 'MyAction';
var payload = 'string value passed to server';
ds.executeCallback(actionName, payload);

Acumatica - Copy custom field contents from SO to IN

I have a similar type of issue as in
Acumatica refer custom field to another custom field on different screen
except that I am using custom source fields.
I created and added 2 fields to the SO Line to capture EDI data needed for invoicing. I created 2 new fields on the invoice line with the same name (different data class of course) on the SO Invoice form. Below is the code for 1 field on each of the forms:
SO301000 (Sales Orders):
[PXDBString(3)]
[PXUIField(DisplayName="Cust.Invoice Line Nbr.")]
[PXFormula(typeof(Selector<SOLineExt.usrCInvLine, ARTranExt.usrCInvLine>))]
SO303000 (Invoices):
[PXDBString(3)]
[PXUIField(DisplayName="Cust.Invoice Line Nbr.")]
It compiles but the data is not being copied to the invoice when created from shipment. I also added 1 of the fields to the shipment form for testing purposes but it does nit capture that value either.
Do I have this backwards?
After reviewing it into more details, you could override the Prepare Invoice method on SOOrderEntry graph (and also anywhere else is needed).
By overriding the PrepareInvoice Method of SOOrderEntry graph you could add a Handler(RowInserting) to SOInvoiceEntry graph to populate your new Custom field with the value on SOOrder.
See sample below where I'm doing similar action copying value from SOOrder(Header) to ARTran(details).
You can use this sample to achieve your goal: (In your case you could use ARInvoice instead of ARTran)
SOOrderEntry:
public class SOOrderEntry_Extension:PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> prepareInvoice;
[PXUIField(DisplayName = "Prepare Invoice", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select, Visible = false)]
[PXButton]
public virtual IEnumerable PrepareInvoice(PXAdapter adapter)
{
PXGraph.InstanceCreated.AddHandler<SOInvoiceEntry>((graph) =>
{
graph.RowInserting.AddHandler<ARTran>((sender, e) =>
{
//Custom logic goes here
var row = (ARTran)e.Row;
if (row == null)
return;
SOOrder order = Base.Document.Current;
if (order != null)
{
var tranExt = PXCache<ARTran>.GetExtension<ARTranExt>(row);
//Below to be change to your needs
//Here you can look for the SOLine of the SOOrder instead
var orderExt = PXCache<SOOrder>.GetExtension<SOOrderExt>(order);
if (orderExt != null && tranExt != null)
{
tranExt.UsrContactID = orderExt.UsrContactID;
}
//END
}
});
});
return Base.prepareInvoice.Press(adapter);
}
}
You could also find more information and instructions provided by my colleague on the stackoverflow link below:
Acumatica custom field SOLine transferred to ARTran

BusinessAccountMaint field required for Customer

I am trying to make the PriceClassID required for Business Accounts when they are created. I initially did this by editing the DAC. This caused an issue where whenever an Employee was created, an error was displayed making creating an employee impossible.
Error: 'CPriceClassID' cannot be empty
I went back to the drawing board and decided to edit the attributes on the Graph which allowed me to create Employee records. However now when editing existing Vendors via the Business Accounts screen I get the same error. I can create and edit Vendors from the Vendors screen because it uses a different graph but I would still like to implement a more elegant solution
[PXDBString(10, IsUnicode = true)]
[PXSelector(typeof(AR.ARPriceClass.priceClassID))]
[PXUIField(DisplayName = "Price Class", Visibility = PXUIVisibility.Visible)]
[PXDefault()]
protected virtual void Location_CPriceClassID_CacheAttached(PXCache sender)
{
}
What is the best method to make the CPriceClassID field required on the Business Accounts screen that will still allow me to create Employees and Vendors without any errors?
You can use PXUIRequiredAttribute for achieving what you need.
Below is an example of how you can use it for making the field required only on the specific screen:
public class LocationExt : PXCacheExtension<PX.Objects.CR.Location>
{
public class BusinessAccountMaintScreen :Constant<string>
{
//"CR.30.30.00" is the page id of the Business Accounts screen
public BusinessAccountMaintScreen():base("CR.30.30.00")
{
}
}
#region UsrCustomField
[PXDBString(10, IsUnicode = true)]
[PXSelector(typeof(AR.ARPriceClass.priceClassID))]
[PXUIField(DisplayName = "Price Class", Visibility = PXUIVisibility.Visible)]
[PXDefault]
// Here we add checking for the current page so that this field will be required only on the Business Accounts screen
[PXUIRequired(typeof(Where<Current<AccessInfo.screenID>, Equal<BusinessAccountMaintScreen>>))]
public virtual String CPriceClassID {get;set;}
#endregion
}

How do I edit a BLC extension that is part of out-of-the-box Acumatica, specifically SM_CRCaseMaint.cs?

I need to add some logic to the CreateServiceOrder action that is shown on the CRCaseMaint screen. I've discovered that the logic actually exists in the file called SM_CRCaseMaint.cs in a class that is an extension of CRCaseMaint. This file is part of base Acumatica, so it is already an extension but cannot be edited directly without risk of losing the changes when the instance is updated. When I attempt to create a graph extension:
I get an error:
Is there any way I can edit this page?
According to Brendan's answer here, as of Acumatica version 2018R1 Update 4 (18.104.0023) you can override or redefine the content of the graph extension shipped with the product.
I did a test with CreateServiceOrder action in version 2018R2 and it worked. The debugger did break into the redefined action when I invoked Create Service Order action from a case:
using PX.Data;
using PX.Objects.FS;
namespace PX.Objects.CR
{
public class CRCaseMaint_Extension : PXGraphExtension<CRCaseMaint>
{
[PXCopyPasteHiddenView]
public PXFilter<FSCreateServiceOrderOnCaseFilter> CreateServiceOrderFilter;
public PXAction<CRCase> CreateServiceOrder;
[PXButton]
[PXUIField(DisplayName = "Create Service Order", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
public virtual void createServiceOrder()
{
CRCase crCaseRow = Base.Case.Current;
FSxCRCase fsxCRCaseRow = Base.Case.Cache.GetExtension<FSxCRCase>(crCaseRow);
if (CreateServiceOrderFilter.AskExt() == WebDialogResult.OK)
{
Base.Case.SetValueExt<FSxCRCase.sDEnabled>(crCaseRow, true);
Base.Case.SetValueExt<FSxCRCase.branchLocationID>(crCaseRow, CreateServiceOrderFilter.Current.BranchLocationID);
Base.Case.SetValueExt<FSxCRCase.srvOrdType>(crCaseRow, CreateServiceOrderFilter.Current.SrvOrdType);
Base.Case.SetValueExt<FSxCRCase.assignedEmpID>(crCaseRow, CreateServiceOrderFilter.Current.AssignedEmpID);
Base.Case.SetValueExt<FSxCRCase.problemID>(crCaseRow, CreateServiceOrderFilter.Current.ProblemID);
Base.Case.Cache.SetStatus(crCaseRow, PXEntryStatus.Updated);
Base.Save.Press();
}
}
}
}

Acumatica Warning Message To Only Affect Shipment Screen SO302000

I'm trying to display a warning every time the ShippedQty field is changed to a value < OrigOrderQty on the "Shipment - SO302000" screen, but I only want the code to be active for that specific screen/form.
I added the code below to extend the SOShipmentEntry graph, which accomplishes my original goal, but the issue is that now the code I added is also being used by the "Create Shipment" action in "Sales Orders - SO301000" screen/form.
Create Shipment Action Discussed
namespace PX.Objects.SO
{
public class SOShipmentEntry_Extension : PXGraphExtension<SOShipmentEntry>
{
#region Event Handlers
protected void SOShipLine_ShippedQty_FieldUpdated(PXCache cache,PXFieldUpdatedEventArgs e)
{
var myrow = (SOShipLine)e.Row;
if (myrow == null) return;
if (myrow.ShippedQty >= myrow.OrigOrderQty)
{
}
else
{
throw new PXSetPropertyException("The difference between the shipped-qty and the ordered-qty will be placed on a back-order", PXErrorLevel.Warning);
}
}
#endregion
}
}
While the warning allows the user to save changes to a shipment on the Shipment Screen/form SO302000 (Because the exception is set up as a warning and not an error), I get the following error when I create a shipment using the "Create Shipment" button on the "Sales Orders - SO301000" screen.
Warning works fine for form-mode
Warning becomes error when processed in background by action button
Any ideas to accomplish this? It is my understanding that if I want to make global changes to a field I must make them in the DAC, but if I want to make changes that only affect screens/forms where a graph is used, then I have to make those changes in the graph code itself. I'm guessing the "Create Shipment" action button in the Sales Orders screen is creating an instance of the graph where I added the code, so I'm wondering what are my options here.
Best regards,
-An Acumatica newbie
If you want your event logic to execute only when CreateShipment is invoked from the Shipment screen you can override the other calls to CreateShipment to dynamically remove your event handler.
The event that invokes CreateShipment action from the SalesOrderEntry graph is Action:
public PXAction<SOOrder> action;
[PXUIField(DisplayName = "Actions", MapEnableRights = PXCacheRights.Select)]
[PXButton]
protected virtual IEnumerable Action(PXAdapter adapter,
[PXInt]
[PXIntList(new int[] { 1, 2, 3, 4, 5 }, new string[] { "Create Shipment", "Apply Assignment Rules", "Create Invoice", "Post Invoice to IN", "Create Purchase Order" })]
int? actionID,
[PXDate]
DateTime? shipDate,
[PXSelector(typeof(INSite.siteCD))]
string siteCD,
[SOOperation.List]
string operation,
[PXString()]
string ActionName
)
In that method it creates a SOShipmentEntry graph to create the shipment. You can override Action and remove your handler from that graph instance:
SOShipmentEntry docgraph = PXGraph.CreateInstance<SOShipmentEntry>();
// >> Remove event handler
SOShipmentEntry_Extension docgraphExt = docgraph.GetExtension<SOShipmentEntry_Extension>();
docgraph.FieldUpdated.RemoveHandler<SOShipLine.shippedQuantity>(docgrapExt.SOShipLine_ShippedQty_FieldUpdated);
// << Remove event handler
docgraph.CreateShipment(order, SiteID, filter.ShipDate, adapter.MassProcess, operation, created);
Note that in order to reference SOShipLine_ShippedQty_FieldUpdated method from another graph you'll have to make it public:
public void SOShipLine_ShippedQty_FieldUpdated(PXCache cache,PXFieldUpdatedEventArgs e)
I have described how to do this in that answer too:
Updating custom field is ending into infinite loop
If you want your event logic to execute only when it is modified in the UI or by web service.
You can use the ExternalCall Boolean property of the PXFieldUpdatedEventArgs parameter.
This property value will be true only when the sender field is modified in the UI or by web service.
Usage example:
protected void SOShipLine_ShippedQty_FieldUpdated(PXCache cache,PXFieldUpdatedEventArgs e)
{
// If ShippedQty was updated in the UI or by a web service call
if (e.ExternalCall)
{
// The logic here won't be executed when CreateShipment is invoked
}
}
ExternalCall Property (PXFieldUpdatedEventArgs)
Gets the value specifying whether the new value of the current DAC field has been changed in the UI or through the Web Service API.

Resources