Acumatica : is it possible and how customize the grid in AR521000 screen? - acumatica

First of all, sorry for my English !!!
I need to customize the grid (DunningLetterList) in the screen AR521000.
In this grid, we have the BAccountid. In the database, each BAccountId (customer) has one (and only one) salesperson checked by default (checkbox "Default" in the "Salespersons" tab in the CustomerMaint).
I need to add the name of this default salesperson in the DunningLetterList, for each customer that appears on the list.
Is it possible, knowing that it's a process screen ? And if it's possible, how can I do that ?
Thanks in advance !!

I am afraid there is no easy solution without affecting performance. That's because the rows from grid are not inserted in cache as usual, but generated inside the data view delegate and added with cache.Hold(row);
Therefore the only solution that I found is to add the sales person's name on RowSelected.
(!) Beware this is not recommended by Acumatica customization best practices (although you might find this kind of selects here and there). Just to let you know, the SQL generated query is SELECT TOP (1).
The customization of business logic:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using PX.Data;
using PX.Objects.CM;
using PX.Objects.CS;
using PX.Objects.GL;
using PX.SM;
using static PX.Objects.Common.Extensions.CollectionExtensions;
using PX.Objects;
using PX.Objects.AR;
using ARDunningLetterList = PX.Objects.AR.ARDunningLetterProcess.ARDunningLetterList;
namespace PX.Objects.AR
{
public class ARDunningLetterListSP : PXCacheExtension<ARDunningLetterList>
{
#region SalesPerson
public abstract class salesPerson : PX.Data.BQL.BqlString.Field<salesPerson> { }
[PXString(60)]
[PXUIField(DisplayName = "Sales Person")]
public virtual string SalesPerson { get; set; }
#endregion
}
public class ARDunningLetterProcess_Extension : PXGraphExtension<ARDunningLetterProcess>
{
public virtual void ARDunningLetterList_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
ARDunningLetterList row = e.Row as ARDunningLetterList ;
if (row == null) return;
SalesPerson sp = PXSelectJoin<SalesPerson,
InnerJoin<CustSalesPeople, On<SalesPerson.salesPersonID, Equal<CustSalesPeople.salesPersonID>>>,
Where<CustSalesPeople.bAccountID, Equal<Required<CustSalesPeople.bAccountID>>, And<CustSalesPeople.isDefault, Equal<True>>>>.SelectSingleBound(this.Base, null, row.BAccountID);
var rowExt = row.GetExtension<ARDunningLetterListSP>();
rowExt.SalesPerson = sp?.Descr;
}
}
}
Then you add the custom field in the grid.
Note: I've tried other solutions as well (like overriding the data view (adding joined tables), the delegate, or using attributes to calculate the value, but I had no luck).

I knew it wouldn't be easy !!
Thank you for your answer and your researches !!
It works perfectly !!
Do you know why I can't filter or sort this column ? I added the "AllowFilter" and "AllowSort" properties, but it doesn't work.

Related

Custom Action - Selecting the PXCache to act upon

TL;DR - I am creating a time clock feature on the Employee Time Activities Screen (EP307000). How does one select/use the default view already built into a screen (or graph)?
I have two custom actions - Stop_Timer and Pause_Timer.
I could not figure out how to act upon the "Activity" view already built into the Graph (PX.Objects.EP.EmployeeActivitiesEntry).
To solve the problem, I wrote my own PXSelect statement (seen below).
But, by writing my own PXSelect statement, I broke the filter already present on the screen. Additionally, I seem to have lost the ability to use the "DependonGrid" property of the "PXDSCallbackCommand".
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using PX.Common;
using PX.Data.EP;
using PX.Objects.CR;
using PX.Objects.CS;
using PX.Objects.CT;
using PX.Objects.IN;
using PX.Objects.PM;
using PX.SM;
using PX.Data;
using PX.TM;
using OwnedFilter = PX.Objects.CR.OwnedFilter;
using PX.Api;
using PX.Objects;
namespace PX.Objects.EP
{
public class EmployeeActivitiesEntry_Extension : PXGraphExtension<EmployeeActivitiesEntry>
{
#region Select
public PXSelectJoin<EPActivityApprove,
InnerJoin<PMTask,
On<PMTask.taskID, Equal<EPActivityApprove.projectTaskID>>,
LeftJoin<PMTimeActivity,
On<PMTimeActivity.noteID, Equal<EPActivityApprove.origNoteID>>>>,
Where<EPActivityApprove.ownerID, Equal<Current<EmployeeActivitiesEntry.PMTimeActivityFilter.ownerID>>,
And<EPActivityApprove.trackTime, Equal<True>>>> Activity;
#endregion
#region Actions
public PXAction<PX.Objects.EP.EmployeeActivitiesEntry.PMTimeActivityFilter> Stop_Timer;
[PXButton]
[PXUIField(DisplayName = "Stop")]
protected void stop_Timer()
{
EPActivityApprove row = Activity.Current;
PMTimeActivityExt pMTimeActivityExt = PXCache<PMTimeActivity>.GetExtension<PMTimeActivityExt>(row);
throw new PXException("You pressed Stop Timer for the row containing Date = " + (DateTime)row.Date);
// All of my actions
Base.Caches[typeof(PMTimeActivity)].Update(pMTimeActivityExt);
Base.Caches[typeof(EPActivityApprove)].Update(row);
}
public PXAction<PX.Objects.EP.EmployeeActivitiesEntry.PMTimeActivityFilter> Pause_Timer;
[PXButton]
[PXUIField(DisplayName = "Pause/Play")]
protected void pause_Timer()
{
EPActivityApprove row = Activity.Current;
PMTimeActivityExt pMTimeActivityExt = PXCache<PMTimeActivity>.GetExtension<PMTimeActivityExt>(row);
throw new PXException("You pressed Pause Timer for the row containing Date = " + (DateTime)row.Date);
// All of my actions
Base.Caches[typeof(PMTimeActivity)].Update(pMTimeActivityExt);
Base.Caches[typeof(EPActivityApprove)].Update(row);
}
#endregion
}
}
see customization package stored on Github - for laughter at my poor coding skills
If you are happy with the view in the base graph, just refer to it as Base.Activity in your graph extension. Accessing views and actions work similarly to how you did Base.Caches at the bottom of your code.
The current Activity record => Base.Activity.Current
EPActivityApprove row = Base.Activity.Current;
EPActivityApproveExt rowExt = row.GetExtension<>EPActivityApproveExt>();
rowExt.MyField = "My Value";
Base.Activity.Cache.Update(rowExt);
If you need to press the save button at some point...
Base.Save.Press();

Including default Vendor Inventory ID on Multiple Grids

I've been tasked with adding the default vendor inventory ID to grids in multiple screens in Acumatica. The field does not need to be bound and is disabled. I've gotten it as far as showing the field correctly, but only on saved records. Adding a new line and even refreshing the grid will not display the ID, I have to close the screen or switch to another record and come back before the vendor ID will display, even clicking the save button and refreshing will not cause it show. The client is using this field as a reference point so it's important it shows as soon as they select an item.
Below is the code I have for the Kit Specification screen, I need to figure out a way to make it a little more reactive, at least show properly on a refresh. I have tried using Current<> in the where statement, but that just breaks it entirely and always shows nothing.
public class INKitSpecStkDetExt : PXCacheExtension<PX.Objects.IN.INKitSpecStkDet>
{
#region VendorInventoryCode
public abstract class vendorInventoryCode: IBqlField { }
[PXDBScalar(typeof(Search2<PO.POVendorInventory.vendorInventoryID,
InnerJoin<IN.InventoryItem,
On<PO.POVendorInventory.vendorID, Equal<IN.InventoryItem.preferredVendorID>, And<PO.POVendorInventory.inventoryID, Equal<IN.InventoryItem.inventoryID>>>>,
Where<IN.InventoryItem.inventoryID,Equal<IN.INKitSpecStkDet.compInventoryID>>,
OrderBy<Desc<PO.POVendorInventory.vendorInventoryID>>>))]
[PXString(40, IsUnicode = true)]
[PXUIField(DisplayName = "Vendor Inventory Code", Enabled=false)]
public string VendorInventoryCode{ get; set; }
#endregion
}
Once I have the code nailed down it will be used in several other places. Help very much appreciated! Frustrating to have it so close and not be able to cross the finish line...
Follow up based on feedback from HB_Acumatica, working code is below for reference:
public void INKitSpecStkDet_VendorInventoryCode_FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
var row = e.Row as INKitSpecStkDet;
if (e.Row != null)
{
PO.POVendorInventory vendorInventory = PXSelectReadonly2<PO.POVendorInventory,
InnerJoin<IN.InventoryItem,
On<PO.POVendorInventory.vendorID, Equal<IN.InventoryItem.preferredVendorID>, And<PO.POVendorInventory.inventoryID, Equal<IN.InventoryItem.inventoryID>>>>,
Where<IN.InventoryItem.inventoryID, Equal<Required<IN.INKitSpecStkDet.compInventoryID>>>,
OrderBy<Desc<PO.POVendorInventory.vendorInventoryID>>>.Select(Base, row.CompInventoryID);
e.ReturnValue = vendorInventory != null ? vendorInventory.VendorInventoryID : null;
}
}
PXDBScalar attribute doesn't refresh by itself. Maybe explicitly calling RaiseFieldDefaulting method will refresh it:
object newValue = null;
base.Caches[typeof(INKitSpecStkDet)].RaiseFieldDefaulting<INKitSpecStkDetExt.vendorInventoryCode>(rowINKitSpecStkDet, out newValue);
Using PXFormula instead of PXDBScalar if possible will yield better automatic refresh behavior but has it's own set of limitation as well.
If you're looking for the simplest way that works in most contexts (except when no graph is used like in reports and generic inquiry) that would be the FieldSelecting event.
You can execute BQL and return any desired value from the event. It will be called each time the field is referenced so it should update by itself.
public void INKitSpecStkDet_VendorInventoryCode_FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
PO.POVendorInventory vendorInventory = PXSelectReadonly2<Po.POVendorInventory […]>.Select(Base);
e.ReturnValue = vendorInventory != null ? vendorInventory.VendorInventoryCode : null;
}

Create Button Under Actions To Redirect To Report In Acumatica CA304000

I am trying to add an option under Actions in Acumatica on the Transactions screen CA304000. See below what I am trying to achieve:
using System;
using System.Collections;
using System.Collections.Generic;
using PX.Data;
using PX.Objects.Common;
using PX.Objects.AP;
using PX.Objects.CM;
using PX.Objects.CS;
using PX.Objects.GL;
using PX.Objects.IN;
using PX.Objects.TX;
using PX.Objects.EP;
using PX.Objects.CR;
using Avalara.AvaTax.Adapter;
using Avalara.AvaTax.Adapter.TaxService;
using AvaAddress = Avalara.AvaTax.Adapter.AddressService;
using AvaMessage = Avalara.AvaTax.Adapter.Message;
using CRLocation = PX.Objects.CR.Standalone.Location;
using PX.Objects;
using PX.Objects.CA;
namespace PX.Objects.CA
{
public class CATranEntry_Extension:PXGraphExtension<CATranEntry>
{
#region Event Handlers
public override void Initialize()
{
Base.action.AddMenuAction(ShowURL);
}
public PXAction<CAAdj> ShowURL;
[PXUIField(DisplayName = "Phieu Thu")]
[PXButton]
protected virtual void showURL()
{
CAAdj doc = Base.Document.Current;
if (doc.RefNbr != null)
{
throw new PXReportRequiredException(doc, "TNCA6401", null);
}
}
#endregion
}
}
This is however telling me that there is ('PX.Objects.CA.CATranEntry' does not contain a definition for 'Document' and no extension method 'Document' accepting a first argument of type 'PX.Objects.CA.CATranEntry' could be found) twice.
this TNCA6401 only have one paramenter Reference Number. Please be specific (Image is the best). I'm Noob. Thanks you.
You should be working with CAAdjRecords data view not Document.
CAAdj doc = Base.Document.Current;
should be CAAdj doc = Base.CAAdjRecords.Current
And Reference Nbr. is tied to AdjRefNbr field.
You could use Customization -> Inspect Element to identify screen is working with which Graph, Data View, DAC and DAC field.

Adding additional filter field on process shipments

I have added a new custom field to SOShipment and SOShipmentFilter. I am trying to use it to filter the grid for Process Shipments and I am having problems with the code. I have done other customizations where I have extended code but I was able to call the baseHandler first and then execute my snippet when it returned. However when I override the delegate function, it just sets up a template with a return to baseMethod. When I put my code in to include the new filter field, I get compile errors for undefined fields/references. Do I need to copy all of the original code from the original delegate and include it in my override function? Below is my current override code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using PX.Data;
using PX.Objects.CS;
using PX.Objects.IN;
using PX.Objects.AR;
using PX.Objects.CM;
using POReceipt = PX.Objects.PO.POReceipt;
using POReceiptLine = PX.Objects.PO.POReceiptLine;
using POLineType = PX.Objects.PO.POLineType;
using PX.Objects;
using PX.Objects.SO;
namespace PX.Objects.SO
{
public class SOInvoiceShipment_Extension:PXGraphExtension<SOInvoiceShipment>
{
#region Event Handlers
public delegate IEnumerable ordersDelegate();
[PXOverride]
public IEnumerable orders(ordersDelegate baseMethod)
{
if (filter.usrTruckNbr != null)
{
((PXSelectBase<SOShipment>)cmd).WhereAnd<Where<SOShipment.usrTruckNbr, GreaterEqual<Current<SOShipmentFilter.usrTruckNbr>>>>();
}
return baseMethod();
}
protected virtual void SOShipmentFilter_UsrTruckNbr_CacheAttached(PXCache cache)
{
}
#endregion
}
}
cmd is a location variable and you cannot access it from your extension. I see two ways you could achieve the result you want:
Copy the whole delegate function to your extension. This is not ideal because it will have to be verified and updated with every new version of Acumatica.
Filter data on the client side after it is returned from the original delegate but before it is displayed on screen. This is not as efficient as filtering on SQL but at least would remove the need to copy too much code to your extension. Here's a complete sample which will filter the list of shipments to return only documents where shipment quantity is even:
public class SOInvoiceShipment_Extension : PXGraphExtension<SOInvoiceShipment>
{
[PXFilterable]
public PXFilteredProcessing<SOShipment, SOShipmentFilter> Orders;
protected IEnumerable orders()
{
// Filter the list of shipments to return only documents where shipment quantity is even
foreach (SOShipment shipment in Base.Orders.Select())
{
if(shipment.ShipmentQty % 2 == 0)
{
yield return shipment;
}
}
}
public void SOShipmentFilter_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
if (e.Row == null) return;
SOShipmentFilter filter = e.Row as SOShipmentFilter;
if (filter != null && !string.IsNullOrEmpty(filter.Action))
{
Dictionary<string, object> parameters = Base.Filter.Cache.ToDictionary(filter);
Orders.SetProcessTarget(null, null, null, filter.Action, parameters);
}
}
}
In all cases, you should not use PXOverride to override the view delegate; the customization tools unfortunately generate the wrong method signature when you try to override a delegate, and this will be improved. You should refer to the T300 training available here for more information on this type of customization (look for "Declaring or Altering a BLC Data View Delegate).

Reusable generic LightSwitch screen with WCF RIA Services

I'm new to WCF RIA Services, and have been working with LightSwitch for 4 or so months now.
I created a generic screen to be used for editing lookup tables all over my LightSwitch application, mostly to learn how to create a generic screen that can be used with different entity sets on a dynamic basis.
The screen is pretty simple:
Opened with arguments similar to this:
Application.ShowLookupTypesList("StatusTypes", "StatusTypeId"); which correspond to the entity set for the lookup table in the database.
Here's my WCF RIA service code:
using System.Data.Objects.DataClasses;
using System.Diagnostics;
using System.Reflection;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq;
using System.ServiceModel.DomainServices.EntityFramework;
using System.ServiceModel.DomainServices.Server;
namespace WCF_RIA_Project
{
public class LookupType
{
[Key]
public int TypeId { get; set; }
public string Name { get; set; }
}
public static class EntityInfo
{
public static Type Type;
public static PropertyInfo Key;
public static PropertyInfo Set;
}
public class WCF_RIA_Service : LinqToEntitiesDomainService<WCSEntities>
{
public IQueryable<LookupType> GetLookupTypesByEntitySet(string EntitySetName, string KeyName)
{
EntityInfo.Set = ObjectContext.GetType().GetProperty(EntitySetName);
EntityInfo.Type = EntityInfo.Set.PropertyType.GetGenericArguments().First();
EntityInfo.Key = EntityInfo.Type.GetProperty(KeyName);
return GetTypes();
}
[Query(IsDefault = true)]
public IQueryable<LookupType> GetTypes()
{
var set = (IEnumerable<EntityObject>)EntityInfo.Set.GetValue(ObjectContext, null);
var types = from e in set
select new LookupType
{
TypeId = (int)EntityInfo.Key.GetValue(e, null),
Name = (string)EntityInfo.Type.GetProperty("Name").GetValue(e, null)
};
return types.AsQueryable();
}
public void InsertLookupType(LookupType lookupType)
{
dynamic e = Activator.CreateInstance(EntityInfo.Type);
EntityInfo.Key.SetValue(e, lookupType.TypeId, null);
e.Name = lookupType.Name;
dynamic set = EntityInfo.Set.GetValue(ObjectContext, null);
set.AddObject(e);
}
public void UpdateLookupType(LookupType currentLookupType)
{
var set = (IEnumerable<EntityObject>)EntityInfo.Set.GetValue(ObjectContext, null);
dynamic modified = set.FirstOrDefault(t => (int)EntityInfo.Key.GetValue(t, null) == currentLookupType.TypeId);
modified.Name = currentLookupType.Name;
}
public void DeleteLookupType(LookupType lookupType)
{
var set = (IEnumerable<EntityObject>)EntityInfo.Set.GetValue(ObjectContext, null);
var e = set.FirstOrDefault(t => (int)EntityInfo.Key.GetValue(t, null) == lookupType.TypeId);
Debug.Assert(e.EntityState != EntityState.Detached, "Entity was in a detached state.");
ObjectContext.ObjectStateManager.ChangeObjectState(e, EntityState.Deleted);
}
}
}
When I add an item to the list from the running screen, save it, then edit it and resave, I receive data conflict "Another user has deleted this record."
I can workaround this by reloading the query after save, but it's awkward.
If I remove, save, then readd and save an item with the same name I get unable to save data, "The context is already tracking a different entity with the same resource Uri."
Both of these problems only affect my generic screen using WCF RIA Services. When I build a ListDetail screen for a specific database entity there are no problems. It seems I'm missing some logic, any ideas?
I've learned that this the wrong approach to using LightSwitch.
There are several behind-the-scenes things this generic screen won't fully emulate and may not be do-able without quite a bit of work. The errors I've received are just one example. LightSwitch's built-in conflict resolution will also fail.
LS's RAD design means just creating a bunch of similar screens is the way to go, with some shared methods. If the actual layout needs changed across many identical screens at once, you can always find & replace the .lsml files if you're careful and make backups first. Note that modifying these files directly isn't supported.
I got that error recently. In my case I create a unique ID in my WCF RIA service, but in my screen behind code I must explicitly set a unique ID when I create the object that will later be passed to the WCF RIA Service insert method (this value will then be overwritten with the unique counter ID in the table of the underlying database).
See the sample code for this project:
http://lightswitchhelpwebsite.com/Blog/tabid/61/EntryId/157/A-Visual-Studio-LightSwitch-Picture-File-Manager.aspx

Resources