How to get GPS location in Acumatica Mobile App? - acumatica

Our customer has a request to record GPS location in Appointments screen when technician starts a drive to the site.
I added a new action to AppointmentEntry graph (basically it is a copy of StartAppointment action with some code removed) and added this action to mobile app. The problem is that fsAppointmentCopy.Mem_GPSLatitudeLongitude is null when my custom action invoked on mobile app, but it has a value when "Start Appointment" action is invoked. What am I missing?
public PXAction<PX.Objects.FS.FSAppointment> StartDrive;
[PXButton]
[PXUIField(DisplayName = "Start Drive", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
protected virtual IEnumerable startDrive(PXAdapter adapter)
{
List<FSAppointment> list = adapter.Get<FSAppointment>().ToList();
foreach (FSAppointment fsAppointmentRow in list)
{
Base.SaveBeforeApplyAction(Base.AppointmentRecords.Cache, fsAppointmentRow);
FSAppointment fsAppointmentCopy = (FSAppointment)Base.Caches[typeof(FSAppointment)].CreateCopy(fsAppointmentRow);
var fsAppointmentDetRows = Base.AppointmentDetServices.SelectWindowed(0, 1);
if (fsAppointmentDetRows.Count == 0)
{
throw new PXException(TX.Error.APPOINTMENT_START_VALIDATE_SERVICE, PXErrorLevel.Error);
}
using (var ts = new PXTransactionScope())
{
if (Base.IsMobile == true
&& Base.SetupRecord.Current != null
&& Base.SetupRecord.Current.TrackAppointmentLocation == true
&& string.IsNullOrEmpty(fsAppointmentCopy.Mem_GPSLatitudeLongitude) == false)
{
string[] parts = fsAppointmentCopy.Mem_GPSLatitudeLongitude.Split(':');
fsAppointmentCopy.GPSLatitudeStart = decimal.Parse(parts[0]);
fsAppointmentCopy.GPSLongitudeStart = decimal.Parse(parts[1]);
}
Base.ChangeStatusSave(fsAppointmentCopy);
ts.Complete();
}
}
return list;
}

Your sample "Start Drive" action worked for me (with slight modifications to make it work with most recent Acumatica version):
A couple of things worth checking:
Double check that mobile Acumatica app was granted access to location. (on iOS: Settings / Acumatica / Location)
Are you sure your code runs to the point when it reads coordinates from the field? It may fail during record save (some fields are not filled) or when validating that at least one detail line is present
Make sure that mobile screen mapping still contains definition for "OtherInformationSourceInfoLocation#GPSLatitudeLongitude" with special = GpsCoords

Related

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

How to add notification template for the email activity through Acumatica code

User requirement is to create a new action button to send email from Sales order screen, when user clicks on send email we are redirecting to email activity screen and for the body i need to pass a notification template through code also when i save this email activity then this activity should create on Contact screen under activities tab.
why contacts screen is in sales order we have a custom field as Contact and it is mandatory so user want to create this activity on contacts screen i had a code till to create activity for the contact but to add sales order details as notification template is not working when i add that template manually i see blank values in the body bcz this activity is creating under contacts so i am getting blanks for the order notification template, below is my code
public PXAction<SOOrder> createBSEmail;
[PXUIField(DisplayName = "Send Email", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
[PXButton]
public void CreateBSEmail()
{
ProcessBSEmail();
}
private void ProcessBSEmail()
{
if (Base.Document.Current != null)
{
SOOrderExt rowext = Base.Document.Current.GetExtension<SOOrderExt>();
if (rowext != null)
{
var targetGraph = PXGraph.CreateInstance<CREmailActivityMaint>();
var message = targetGraph.Message.Insert();
if (rowext.UsrKWContactID != null)
{
Contact con = PXSelectReadonly<Contact, Where<Contact.contactID, Equal<Required<Contact.contactID>>>>.Select(Base, rowext.UsrKWContactID);
if (con != null)
{
message.RefNoteID = con.NoteID;
message.BAccountID = con.BAccountID;
message.ContactID = con.ContactID;
//message.NoteID = con.NoteID;
message.IsIncome = false;
message.Subject = "Test Subjext";
message.MailTo = con.EMail != null ? con.EMail : string.Empty;
targetGraph.Message.Update(targetGraph.Message.Current);
throw new PXRedirectRequiredException(targetGraph, true, "Email") { Mode = PXBaseRedirectException.WindowMode.NewWindow };
}
}
}
}
}
Also, here if i pass salesorder noteid then i am able to get the values for the order notification template when i add it, but then the activity is creating for the order not to the contact.
So how i can resolve this i need to create the activity for the contact and through code i need to pass order details using notification template.
Thanks in advance.

How do I redirect to a dashboard now that GIScreenHelper is marked obsolete?

We have a dashboard that is accessed via the Inquiry menu on the Stocked Items page. Until our most recent minor upgrade on 2019 R2, the following code compiled without issue to allow opening the dashboard relevant to the current Inventory ID. It still compiles but with a warning that GIScreenHelper is obsolete and will be marked internal in the next upgrade. Hence my question... how do I redirect to a dashboard if I can't use GIScreenHelper to initialize the graph used in the PXRedirectRequiredException?
string screenID = "SS0010DB"; //DashboardID
PXSiteMapNode sm = GIScreenHelper.GetSiteMapNode(screenID);
PXGraph graph = GIScreenHelper.InstantiateGraph(screenID);
if (graph is LayoutMaint)
{
LayoutMaint copygraph = graph as LayoutMaint;
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters["InventoryID"] = item.InventoryCD;
copygraph.Filter.Current.Values = parameters;
throw new PXRedirectRequiredException(sm.Url, copygraph, PXBaseRedirectException.WindowMode.New, string.Empty);
}
I have tried initializing LayoutMaint directly, but I can't figure out what to set to specify which Screen ID to utilize and pass parameters.
I guess you have 2 options here:
Create the DashboardMaint graph instance which is the Dashboards page graph and provide the Name of the Dashboard and invoke the viewDashboard action of that graph.
Just take the code of the viewDashboard action of the DashboardMaint and redirect to your Dashboard directly:
[PXButton(ConfirmationType = PXConfirmationType.IfDirty, ConfirmationMessage = "Any unsaved changes will be discarded. Do you want to proceed?")]
[PXUIField(DisplayName = "View")]
public void viewDashboard()
{
throw new PXRedirectToUrlException(PXSiteMap.Provider.FindSiteMapNodeByScreenID(this.Dashboards.Current.ScreenID).Url, PXBaseRedirectException.WindowMode.Same, "View Dashboard");
}
UPDATED
Below is a code example how to open Dashboard with predefined value for Filter.
The example is written for Customer View dashboard.
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "CustomerView")]
protected virtual IEnumerable RedirectToCustomerViewDashboard(PXAdapter adapter)
{
string screenID = "DB000031"; //DashboardID
LayoutMaint graph;
using (new PXScreenIDScope(screenID))
{
graph = PXGraph.CreateInstance<LayoutMaint>(screenID);
}
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters["CustomerAccountID"] = "ABARTENDE";
graph.Filter.Current.Values = parameters;
throw new PXRedirectRequiredException(PXSiteMap.Provider.FindSiteMapNodeByScreenID(screenID).Url, graph, PXBaseRedirectException.WindowMode.New, string.Empty);
}
The key for the value is Name of the parameter from Dashboard definition

How can I set a custom field on Acumatica Production Detail Operation when the Production Order is first created?

I have a custom field on an Acumatica Production Detail Operation (UsrEligibleForRoboticFulfillment) that I have created an Action to set based on criteria on the component items in the Materials tab. (code below)
I would like to call this Action to set the field as soon as the Production Order is created, but the split nature of the Production Order is such that there are no events on the Production Detail raised that I can attach to and call the Action. I've tried Row Inserted as well as Persist delegate on the Production Detail graph.
I CAN attach to either the AMProdItem Row Inserted or Persist Delegate on the Production Maint graph, but at this point in time the Operations and Materials have not yet been created.
What's the best way to update this field when a new Production Order is created?
Action code:
public PXAction<AMProdItem> UpdateEligibleForRoboticFulfillment;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Update Robotic Eligibility", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
protected void updateEligibleForRoboticFulfillment()
{
AMProdItem prodDetail = Base.ProdItemRecords.Current;
AMProdOper prodOper = Base.ProdOperRecords.Current;
InventoryItem finishedProduct = PXSelect<InventoryItem,
Where<InventoryItem.inventoryID, Equal<Current<AMProdItem.inventoryID>>>>.Select(Base).FirstOrDefault();
//Only production orders are eligible for robotic fulfillment, not disassemblies
if (prodDetail.OrderType == "MO")
{
bool wasRoboticsEligible = (prodOper.GetExtension<AMProdOperExt>().UsrEligibleForRoboticFulfillment ?? false);
//Get the current branchID
int branchID = (int)Base.Accessinfo.BranchID;
//Get the default site/warehouse for this branch.
INSite site = INTranHelper.GetDefaultSiteForItemBranch(branchID);
//Get the flag indicating whether this site is active for robotics
bool activeRobotics = site.GetExtension<INSiteExt>().UsrActiveRobotics ?? false;
//Get the flags for manual process and component robotics compatible
bool requiresManualProcess = finishedProduct.GetExtension<InventoryItemExt>().UsrManualFinishRequired ?? false;
//Gotta be prepared for the possibility that more than one component is used
//Check for any components that are NOT robotics eligible that have qty required and haven't already been fully allocated
PXResultset<AMProdMatl> components = PXSelectJoin<AMProdMatl,
InnerJoin<InventoryItem, On<InventoryItem.inventoryID, Equal<AMProdMatl.inventoryID>,
And<InventoryItemExt.usrRoboticsCompatible, Equal<False>,
And<AMProdMatl.orderType, Equal<Current<AMProdOper.orderType>>,
And<AMProdMatl.prodOrdID, Equal<Current<AMProdOper.prodOrdID>>,
And<AMProdMatl.operationID, Equal<Current<AMProdOper.operationID>>,
And<AMProdMatl.qtyActual, Less<AMProdMatl.totalQtyRequired>,
And<AMProdMatl.qtyReq, Greater<decimal0>>>>>>>>>>
.Select(Base);
bool roboticsEligible = !requiresManualProcess && activeRobotics;
//If any component is not eligible, the whole operation is not eligible
if (components.Count > 0)
{
roboticsEligible = false;
}
//If the robotics eligible flag should have changed, change it
if (wasRoboticsEligible != roboticsEligible)
{
prodOper.GetExtension<AMProdOperExt>().UsrEligibleForRoboticFulfillment = roboticsEligible;
Base.ProdOperRecords.Update(prodOper);
}
}
}
Had to open a ticket with Acumatica; got a working solution! I had to enclose the persist delegate method in a transaction scope.
Override Persist() method of graph
Call base method first so that Operations and Materials on the Production Order Detail gets created
Enclosed in transaction scope
Something like this:
public delegate void PersistDelegate();
[PXOverride]
public void Persist(PersistDelegate baseMethod)
{
if (/**/)
{
using (var ts = new PXTransactionScope())
{
//Call base method to persist
baseMethod();
/*Custom Logic here*/
ts.Complete();
}
}
else
baseMethod();
}

Open custom Acumatica screen as popup from button on Bills and Adjustments screen

I have a completely custom screen with its own BLC and DACs, and I want to open it as a popup from a button placed on the Bills and Adjustments screen. I have coded it as follows:
public class APInvoiceEntryExt : PXGraphExtension<APInvoiceEntry>
{
public PXAction<APInvoice> LaunchOpenSource;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Open Source")]
protected void launchOpenSource()
{
APInvoice apinvoice = (APInvoice)Base.Document.Current;
if (apinvoice != null)
{
//var url = "http://localhost/AcumaticaDB2562/?ScreenId=AC302000&OpenSourceName=Bills+and+Adjustments&DataID=" + apinvoice.RefNbr;
OpenSourceDataMaint graph = PXGraph.CreateInstance<OpenSourceDataMaint>();
graph.OpenSourceDataHeader.Current = graph.OpenSourceDataHeader.Search<xTACOpenSourceHeader.openSourceName, xTACOpenSourceHeader.dataID>("Bills and Adjustments", apinvoice.RefNbr);
if (graph.OpenSourceDataHeader.Current != null)
{
throw new PXRedirectRequiredException(graph, "Open Source")
{
Mode = PXBaseRedirectException.WindowMode.NewWindow
};
}
}
}
}
I've included all the relevant DACs and BLC for my custom screen in the Class Library project I'm using to customize the 'Bills and Adjustments' screen where I'm adding the button.
The problem I'm having is that I get the following error message when launching the button:
I've set all the relevant permissions for the screen that uses the OpenSourceDataMaint BLC to 'Delete' in 'Access Right By Role', 'Access Rights By User', and 'Access Rights By Screen'. Nothing makes any difference.
Looks like DataSource is trying to find a node in SiteMap with GraphType equal to full name off your OpenSourceDataMaint class and fails:
public class PXBaseDataSource : DataSourceControl, IAttributeAccessor, INamingContainer, ICompositeControlDesignerAccessor, ICommandSource, IPXCallbackHandler, IPXScriptControl, IPXCallbackUpdatable, IPostBackDataHandler
{
...
private static string getFormUrl(Type graphType)
{
PXSiteMapNode node = getSiteMapNode(graphType);
if (node == null)
{
throw new PXException(string.Format(ErrorMessages.GetLocal(ErrorMessages.NotEnoughRightsToAccessObject), graphType.Name));
}
String url = node.Url;
//if (url.Contains("unum=")) url = PXUrl.IgnoreQueryParameter(url, "unum");
return PXUrl.TrimUrl(url);
}
...
}
Could you please check if TypeName is properly defined for PXDataSource inside your custom Aspx page? Also could you please check if your custom Aspx page also exists in Cst_Published folder and if values set for PXDataSource.TypeName property are identical inside Pages and Cst_Published folders?
One more thing to check, does the Site Map screen show the right GraphName for your custom screen? - would be beneficial if you can provide a screenshot for verification.
If possible, please provide your customization package, that can be published locally (even with compiled assembly) - this would greatly speed up the investigation process.
The solution, for me, was to put the code (shown below) in a customization window instead of a class library project in Visual Studio. Since the code needs to have a reference to another published customization, putting it inside an Acumatica code window takes care of this. There is no reference to the published custom screen customization in my class library project, and this obviously causes issues - and I'm not sure how to handle that.
public class APInvoiceEntryExt:PXGraphExtension<APInvoiceEntry>
{
public PXAction<APInvoice> LaunchOpenSource;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Open Source")]
protected void launchOpenSource()
{
APInvoice apinvoice = (APInvoice)Base.Document.Current;
if (apinvoice != null)
{
AssistantController.OpenSourceDataMaint graph = PXGraph.CreateInstance<AssistantController.OpenSourceDataMaint>();
graph.OpenSourceDataHeader.Current = graph.OpenSourceDataHeader.Search<AssistantController.xTACOpenSourceHeader.openSourceName
,AssistantController.xTACOpenSourceHeader.dataID>("Bills and Adjustments", apinvoice.RefNbr);
throw new PXRedirectRequiredException(graph, "Open Source")
{
Mode = PXBaseRedirectException.WindowMode.NewWindow
};
}
}
}

Resources