I currently have a recalc function that resets the shipping cost field if a line item has changed, but I really only need to do that if the item has a weight. How do I retrieve the weight of the changed line item?
Here's what I currently have:
function recalc(){
nlapiSetFieldValue('shippingcost', '0.00');
}
recalc is fired only when a change to a line item affects the Total of the transaction, and as such, may not be a reliable event for what you want to accomplish.
I would recommend against using validateLine as that event should be used to determine whether the new value for a field is valid.
I would advise you to use fieldChanged for responding to a field value that has changed. Something like:
function fieldChanged(type, name, linenum) {
if (type == 'item') {
if (name == 'item') {
handleItemChange(linenum);
}
}
}
function handleItemChange(linenum) {
var itemWeight = parseFloat(nlapiGetFieldValue('item', 'weight', linenum)) || 0;
if (itemWeight > 0) {
nlapiSetFieldValue('shippingcost', 0);
}
}
You may also want to consider the postSourcing event instead of fieldChanged, depending on which fields should actually trigger this logic.
Small segue, recalc doesn't give you a way to get the current line of the sublist, you'd need to loop through the whole sublist anytime a single line was changed.
Try validateLine, something like:
function validateLine(listType){
//To get the item weight, you could create a
//custom transaction column field that sourced the item weight.
if(nlapiGetCurrentLineItemValue(listType,'custcolitemWeight') > 0){
nlapiSetFieldValue('shippingcost','0.00')
}
//or you could source directly from the item record using nlapiLookupField
// Depending on your use case either could be appropriate
if(nlapiLookupField('item',nlapiGetCurrentLineItemValue(listType,'item'),'weight')){
nlapiSetFieldValue('shippingcost','0.00')
}
//you *need* to return true with the validate* event functions.
return true;
}
This (untested) example only handles line additions. If users are allowed to remove items, you'll need to implement a similar validateDelete that also reverts your changes.
Related
I have a Document/Transaction (FormGrid) structure to a custom screen. I need to validate the values in the Transaction level when the Hold box is unchecked in the header. Fundamentally, my challenge seems to come from order of operation for event handlers. (FieldVerifying -> FieldUpdating -> Field Updated -> RowUpdating -> RowUpdated) combined with order of views processed (Seems to be the primary view before the others).
What has me completely confused right now is that if I leave the field in the transaction (grid) section of my form, CommitChanges fires, and everything is ok. However, if I set my field (OrderQty) to 0 and then immediately go uncheck Hold in the header without "leaving" the OrderQty field first, the OrderQty value is not committed until AFTER the Hold checkbox is processed. That means that I cannot validate that OrderQty is greater than 0. Literally, I cannot see my OrderQty value in the Cache or any form of the view because the hold box in the header is being processed before the OrderQty in the grid.
I have tried simple means to do the tests, and this is a snippit of something a bit more complex to try to grab the data. Caches[].Updated holds the new values AFTER the hold checkbox is processed, not before. AddWithoutDuplicates is a method to simply make sure I didn't have the record in the list already (allows looking at the updated cache values rather than the old values not yet committed)
List<SSRQLine> list = new List<SSRQLine>();
foreach (SSRQLine line in Caches[typeof(SSRQLine)].Updated)
{
AddWithoutDuplicates(list, line);
}
foreach (SSRQLine line in Caches[typeof(SSRQLine)].Inserted)
{
AddWithoutDuplicates(list, line);
}
foreach (SSRQLine line in Lines.Select())
{
AddWithoutDuplicates(list, line);
}
foreach (SSRQLine line in list)
...
Is there way to validate the grid data from the hold checkbox reliably during entry? Or must I handle the validation in a RowPersisting event later? Or any other suggestions on how to validate the user entry in the grid from Hold_FieldVerifying?
The parent DAC is SSRQRequisition, and the child DAC is SSRQLine.
As the goal was to monitor taking the request off of hold, the original effort was attached to the header DAC's hold field. The problem is that it required validating child DAC values in a One to Many relationship in which the child DAC had not processed its Field Events. To warn the user but not prevent saving the request, create the following event handler:
#region SSRQLine_OrderQty_FieldVerifying
protected virtual void _(Events.FieldVerifying<SSRQLine.orderQty> e)
{
SSRQLine row = (SSRQLine) e.Row;
if((decimal?)e.NewValue == 0)
{
e.Cache.RaiseExceptionHandling<SSRQLine.orderQty>(e.Row, row.OrderQty, new PXSetPropertyException(Messages.InvalidQuantity, PXErrorLevel.Warning));
}
}
#endregion
To re-validate the entire request before allowing the user to save it upon taking the request off of hold, perform the validation in the header DAC's RowPersisting event handler. Note that validation should not be performed if the request is being deleted, so the operation is tested as well. Additionally, the test is only needed if the user is taking the request off of hold, so no need to validate when placing back on hold.
#region SSRQRequisition_RowPersisting
protected void _(Events.RowPersisting<SSRQRequisition> e)
{
SSRQRequisition row = e.Row;
if (e.Operation != PXDBOperation.Delete && row?.Hold == false)
{
ValidateConsignment();
}
}
#endregion
The ValidateConsignment method has been created to simplify reading the code, but is called by the SSRQRequisition RowPersisting event. By raising exception handling as an ERROR this time and throwing an exception, the user is alerted that the warning is now what prevents saving the record without it being on hold.
#region ValidateConsignment
protected virtual void ValidateConsignment()
{
int lineCounter = 0;
SSRQRequisition req = Requisitions.Current;
List<SSRQLine> list = new List<SSRQLine>();
foreach (SSRQLine line in Caches[typeof(SSRQLine)].Updated)
{
AddWithoutDuplicates(list, line);
}
foreach (SSRQLine line in Caches[typeof(SSRQLine)].Inserted)
{
AddWithoutDuplicates(list, line);
}
foreach (SSRQLine line in Lines.Select())
{
AddWithoutDuplicates(list, line);
}
foreach (SSRQLine line in list)
{
lineCounter++;
if (line.SiteID == null)
{
throw new PXSetPropertyException(Messages.NoWhse, PXErrorLevel.Error);
}
if (line.OrderQty == null || line.OrderQty == 0)
{
this.Lines.Cache.RaiseExceptionHandling<SSRQLine.orderQty>(line, line.OrderQty, new PXSetPropertyException(Messages.InvalidQuantity, PXErrorLevel.Error));
throw new PXSetPropertyException(Messages.InvalidQuantity, PXErrorLevel.Error);
}
}
if (lineCounter == 0)
{
this.Lines.Cache.RaiseExceptionHandling<SSRQRequisition.hold>(req, req.Hold, new PXSetPropertyException(Messages.NoLines, PXErrorLevel.Error));
throw new PXSetPropertyException(Messages.NoLines, PXErrorLevel.Error);
}
}
#endregion
The ValidateConsignment method currently cycles through the Inserted and Updated copies of the cache, but I may be able to remove these since it is no longer being tested before the updates are applied to the cache. As this is now called from the RowPersisting method, all field and row event handlers for inserting and updating records in the cache should be applied already. However, I'll be testing further before removing them. Initial testing confirms that the code provided achieves the goal. To provide the desired end user experience, the code simply needed to be spread across 3 different events rather than cramming it all into the event on the Hold checkbox that originally intended to initiate the checks. The behavior ends up being slightly different than originally intended, but the result is more user friendly and achieves the same goal.
I have a modification that facilitates an internal business process utilizing PO and SO. The screen provides the ability to purchase an MRO spare part outside of the normal replenishment process. The item may or may not be maintained in inventory, so it may be ordered for replenishment or as an SO to be processed as Mark for PO.
In creating the SO, I am able to store the SO reference information to the DAC for my customization. When creating the PO directly, I also am able to capture the PO reference information. However, when creating the PO from the SO using the standard Acumatica menu action for Create Purchase Order, I have been unable to capture the right event to enable storing the PO reference being assigned in SOLineSplit3 to my custom DAC. (Worth noting that I also need to be able to override the default curyunitcost value on the PO line using the value stored on my custom DAC as these purchases do not carry a fixed price per buy. This is done by tracing the SOLineSplit back to my custom DAC and overriding POLine_CuryUnitCost_FieldDefaulting.)
The action invoked on the Sales Order Entry screen (Action - Create Purchase Order) calls the method CreatePOOrders in POCreate.cs which in turn creates an instance of the POOrderEntry graph to create the actual purchase order.
Eventually, the following code is reached, which appears to attach the PO reference information to SOLineSplit3 as soline:
soline.POType = line.OrderType;
soline.PONbr = line.OrderNbr;
soline.POLineNbr = line.LineNbr;
soline.RefNoteID = docgraph.Document.Current.NoteID;
docgraph.UpdateSOLine(soline, docgraph.Document.Current.VendorID, true);
docgraph.FixedDemand.Cache.SetStatus(soline, PXEntryStatus.Updated);
I am not yet familiar with Cache.SetStatus, but the pop-up description seems to indicate that this is using the FixedDemand select in POOrderEntry to find and set (or insert) the SOLineSplit3 record. The call to UpdateSOLine above it is a new internal method that was not in my previous version of POCrete.cs, as this entire method seems to have had some significant rework recently. In trying to capture events on SOLineSplit3 in both POCreate and POOrderEntry, it appears that Cache.SetStatus does not raise any events that I can capture... or I am just completely lost on what event to capture/override.
Immediately following this section, the following appears to update a Replenishment record and save the entire POOrderEntry graph.
if (docgraph.Transactions.Cache.IsInsertedUpdatedDeleted)
{
using (PXTransactionScope scope = new PXTransactionScope())
{
docgraph.Save.Press();
if (demand.PlanType == INPlanConstants.Plan90)
{
docgraph.Replenihment.Current = docgraph.Replenihment.Search<INReplenishmentOrder.noteID>(demand.RefNoteID);
if (docgraph.Replenihment.Current != null)
{
INReplenishmentLine rLine =
PXCache<INReplenishmentLine>.CreateCopy(docgraph.ReplenishmentLines.Insert(new INReplenishmentLine()));
rLine.InventoryID = line.InventoryID;
...
rLine.PlanID = demand.PlanID;
docgraph.ReplenishmentLines.Update(rLine);
docgraph.Caches[typeof(INItemPlan)].Delete(demand);
docgraph.Save.Press();
}
}
scope.Complete();
}
...
}
Basically, I need to insert my code right between the assignment of the PO information to "soline" and the docgraph.Save.Press(); without copying dozens of lines of code to modify this method. I have managed cloning the base method and inserting my code successfully, but I'd prefer to use an event handler and eliminate modifying the standard code. But the question... What event in which graph will let me grab the PO information and follow the breadcumbs back through SOLineSplit to my custom DAC?
Acumatica Build 18.212.0033
Extend POCreate graph because it is the one instanciating the POOrderEntry you are interested in.
Setup a hook on any POOrderEntry graph created by POCreate and subscribe your events on the intercepted graph. I tested this solution, with a Sales Order that has allocations lines in allocation window it will catch the SOLineSplit3 events:
public class POCreate_Extension : PXGraphExtension<POCreate>
{
public override void Initialize()
{
PXGraph.InstanceCreated.AddHandler<POOrderEntry>((graph) =>
{
graph.RowInserting.AddHandler<POOrder>((sender, e) =>
{
PXTrace.WriteInformation("POOrderEntry_POOrder_RowInserting");
});
graph.RowInserting.AddHandler<POOrderEntry.SOLineSplit3>((sender, e) =>
{
PXTrace.WriteInformation("POOrderEntry_SOLineSplit3_RowInserting");
});
graph.RowUpdating.AddHandler<POOrderEntry.SOLineSplit3>((sender, e) =>
{
PXTrace.WriteInformation("POOrderEntry_SOLineSplit3_RowUpdating");
});
graph.RowPersisting.AddHandler<POOrderEntry.SOLineSplit3>((sender, e) =>
{
PXTrace.WriteInformation("POOrderEntry_SOLineSplit3_RowPersisting");
});
});
}
}
Is it possible to compare the properties listItemId with a field in a new list? I have an event receiver, I want to update a new list once an item in updated. I don't want to use GetItemById, I want to compare the first list auto generated ID with a field in the new list.
foreach(ListItem UpdateItem in destListItems) {
if (properties.ListItem["ID"] == UpdateItem["NewId"]); {
//Do something
}
}
The script does not update the item in the destination list. Any suggestions?
Try this:
if (properties.ListItem["ID"].ToString() == UpdateItem["NewId"].ToString()); {
UpdateItem["Field"]="123";
UpdateItem.Update();
}
In CiviCRM webform, you can 'enable tag and groups'. Configuring those allows you to create option elements in the webform.
This creates one 'widget', one dropdown or set of checkboxes. I have two field instances where I want the user to select a group - say for example
which mailing lists do you want to receive (a,b,c)
what food are you interested in (d,e,f)
a,b,c,d,e and f are all groups. I can not change that.
How could I do that ?
A technical suggestion below, but first, I'd suggest that your real solution is to not use groups for the second question. Groups are set up nicely to handle mailing lists, but if it's to track interests, you'd be better off setting those up as custom fields. It'll solve this immediate issue, and it'll make it easier to deal with tandem searches and so forth (on list b and likes food d).
Now if you must have them as groups, you can create a fake field and move checkboxes into it using jQuery. Create the fake field with one option that you don't care about, but label it "What food are you interested in", or equivalent. Then, edit the Groups field that CiviCRM generated: label it more specifically as "which mailing lists...", and choose Static Options so it doesn't start offering up just any group for someone to choose.
Now, add the following javascript:
// first remove the dummy checkboxes in your fake field
$('#yourdummyfield .form-item').each( function() { $(this).remove(); });
// now move things into the dummy field
$('#yourdummyfield').append( $('#groupsfield .form-item-d');
$('#yourdummyfield').append( $('#groupsfield .form-item-e');
$('#yourdummyfield').append( $('#groupsfield .form-item-f');
From the form processing perspective, they'll all be evaluated as the "groups" field. However, they'll look separate. For better or worse, this will have to be adjusted as you add new groups fields.
After using Andrew Hunts suggestion for a while, I finally solved this on the server side, in a custom module, using webform logic as described here
http://www.webomelette.com/drupal-webform-submission-presave-hook
Basicly, on presave, I look for 2 custom fields containing group ids (mailing and food in the example). Then I add these to the CiviCRM groups field.
I'll add the code below, which has some more logic:
to make it flexible, I use one hidden field to contain the fieldkey
of the civicrm groups selector to add the other fields in. that
field is called 'the_groups_element' (but its not the groups element, it contains the key of the groups element)
there is only one foods group allowed, so before it adds you to a food group, it removes all other food groups from the groups selector.
You could probably make it even more generic, but since I had different logic for the different groups, this was suitable for me.
function getFoodGroups() {
// return foodgroups
}
function getMailGroups() {
// return mailgroups
}
function MYMODULE_webform_submission_presave($node, &$submission) {
$groupselm = '';
$groups_cid = false;
$foods_cid = false;
$mailings_cid = false;
// http://www.webomelette.com/drupal-webform-submission-presave-hook
foreach($node->webform['components'] as $cid=>$comp) {
if ($comp['form_key']=='the_groups_element') {
$groupselm = $comp['value'];
break;
}
}
if ($groupselm) {
foreach($node->webform['components'] as $cid=>$comp) {
if ($comp['form_key']==$groupselm) $groups_cid = $comp['cid'];
if ($comp['form_key']=='the_foods') $foods_cid = $comp['cid'];
if ($comp['form_key']=='the_mailings') $mailings_cid = $comp['cid'];
}
$group_gids = $submission->data[$groups_cid];
if (!$group_gids) $group_gids=array();
if ($foods_cid!==false && $submission->data[$foods_cid]) {
// remove all current foods
foreach ($group_gids as $gidx=>$group_gid) {
foreach (getFoodGroups() as $foodgroup) {
if ($group_gid==$foodgroup['gid']) {
if ($debug) drupal_set_message('removing foodgroup '.$foodgroup['gid']);
unset($group_gids[$gidx]);
}
}
}
// validate and add submitted regions
$foodsgids = $submission->data[$foods_cid];
if (!is_array($foodsgids)) $foodsgids = array($foodsgids);
foreach ($foodsgids as $foodsgid) {
foreach (getFoodGroups() as $foodgroup) {
if ($foodsgid==$foodgroup['gid']) {
$group_gids[]=$foodsgid;
break; // only one food allowed
}
}
}
}
if ($mailings_cid!==false && $submission->data[$mailings_cid]) {
// just add submitted mailings, dont remove any
$mailinggids = $submission->data[$mailings_cid];
if (!is_array($mailinggids)) $mailinggids = array($mailinggids);
foreach ($mailinggids as $mailinggid) {
foreach (getMailGroups() as $mailing) {
if ($mailinggid==$mailing['gid']) {
if ($debug) drupal_set_message('adding mailing '.$mailing['gid']);
$group_gids[]=$mailinggid;
}
}
}
}
$submission->data[$groups_cid] = array_unique($group_gids);
}
We Have Contact Entities in contact Entitie one lookup filed company Name in that lookup having two values 1.Account and 2.Contact . When we are selecting contact show the address filed when we select account hide the address filed we needs to write the plugin to Execute that works. Kindly any one help me on the same.
Thanks!!
Rajesh Singh
First, if you need to make change on a form, you can't use plug-ins. Plug-ins are made for bussinees logics, like Update another record when the first one is created, make complex logic, etc...
What you need it is a javascript that executes on the OnLoad of the form and OnChange event in that OptionSet.
The lines you are looking for are:
function ShowField()
{
// The field is present on the form so we can access to its methods
var optionSet = Xrm.Page.getAttribute("custom_attribute");
if (optionSet == undefined)
{
return;
}
// The control is present on the form so we can access to its methods
var controlAddress = Xrm.Page.getControl("custom_address");
if (controlAddress == undefined)
{
return;
}
var valueOptionSet = optionSet.getValue(); // This is the value you set creating the OptionSet
switch (valueOptionSet)
{
case 0: // Your account value, show
controlAddress.setVisible(true);
case 1: // Your contact value, hide
controlAddress.setVisible(false);
default:
return;
}
}
You need to register a web resource and register the event, if you need more information about the code or why this stuff is here and why this not just tell me.