When upgrading from Acumatica ERP 5.3 to 17.2, I ran into an issue because the ActiveProject* attribute classes no longer exist. Does anyone have any suggestions for adapting our customized code using these attributes (e.g. ActiveProjectForModuleAttribute) on a project id to work in R2? Is there a new preferred way to accomplish what this class used to?
What I usually do is compare the code file from the previous version with the same code file from the newer build. This is quite straightforward because the entire source code of PX.Objects.dll is stored inside Acumatica website's \App_Data\CodeRepository\PX.Objects folder.
In your case, you only need to install locally one Acuamtica ERP 5.3 website and one Acuamtica ERP 2017 R2 website, then search in Visual Studio for ActiveProjectForModuleAttribute inside your 5.3 website's \App_Data\CodeRepository\PX.Objects folder and compare the search results with 2017 R2 code base.
Just to give an example, in ver 5.3 the ActiveProjectForModuleAttribute was used on the ProjectID field of the POLine:
[System.SerializableAttribute()]
[PXCacheName(Messages.POLineShort)]
public partial class POLine : PX.Data.IBqlTable, IAPTranSource, IItemPlanMaster, ISortOrder
{
...
#region ProjectID
public abstract class projectID : PX.Data.IBqlField
{
}
protected Int32? _ProjectID;
[POProjectDefault(typeof(POLine.lineType), AccountType = typeof(POLine.expenseAcctID))]
[ActiveProjectForModule(BatchModule.PO, null, false, false, true)]
public virtual Int32? ProjectID
{
get
{
return this._ProjectID;
}
set
{
this._ProjectID = value;
}
}
#endregion
...
}
In 2017 R2 ActiveProjectForModuleAttribute was replaced with the ProjectBaseAttribute and 2 PXRestrictor attributes:
[System.SerializableAttribute()]
[PXCacheName(Messages.POLineShort)]
public partial class POLine : PX.Data.IBqlTable, IAPTranSource, IItemPlanMaster, ISortOrder
{
...
#region ProjectID
public abstract class projectID : PX.Data.IBqlField
{
}
protected Int32? _ProjectID;
[POProjectDefault(typeof(POLine.lineType), AccountType = typeof(POLine.expenseAcctID))]
[PXRestrictor(typeof(Where<PMProject.isCancelled, Equal<False>>),
PM.Messages.CancelledContract, typeof(PMProject.contractCD))]
[PXRestrictor(typeof(Where<PMProject.visibleInPO, Equal<True>,
Or<PMProject.nonProject, Equal<True>>>),
PM.Messages.ProjectInvisibleInModule, typeof(PMProject.contractCD))]
[ProjectBaseAttribute()]
public virtual Int32? ProjectID
{
get
{
return this._ProjectID;
}
set
{
this._ProjectID = value;
}
}
#endregion
...
}
Related
We have a graph extension that manages the tab we added to a screen. The tab displays data from our own DAC. We do not use a DAC extension, not going to go into the reasons right now.
When a user opens the main screen I want to create a record of our data if it does not exist, with some defaulting business logic.
I added a RowSelected event handler on the main DAC and it fires as I expect it to. When I add the code to create our missing record in the event handler Acuminator gives me error "PX1044 Changes to PXCache cannot be performed in event handlers."
I understand the error that Acuminator is raising, but I'm not sure where else to create our record. I cannot remember a section of the university specifically addressing this scenario.
Can anyone tell me how I would handle this scenario? And if possible, point me at the learning material that covers this scenario for broader information.
Unfortunatly there are only a few OK ways to do this and make Acuminator happy. You will risk data inconsistency.
https://github.com/Acumatica/Acuminator/blob/master/docs/diagnostics/PX1044.md
I would reccomend putting your data insertion code in Row Updated and hope the user updates something
I got a reply from Acumatica support, you can do this in a view select delegate.
Here's some example code, I haven't tested it though:
public class InventoryItemMaintExtension : PXGraphExtension<InventoryItemMaint>
{
public SelectFrom<MyCustomTable>.
Where<MyCustomTable.inventoryID.IsEqual<
InventoryItem.inventoryID.FromCurrent>>.
View AdditionalItemData;
public static bool IsActive() { return true; }
public IEnumerable additionalItemData()
{
var data = AdditionalItemData.SelectSingle();
if (data is null)
{
data = new MyCustomTable();
AdditionalItemData.Insert(data);
}
return AdditionalItemData.Select();
}
}
[PXCacheName("Additional Item Data")]
[Serializable]
public class MyCustomTable : IBqlTable
{
#region CashAccountID
[Inventory]
[PXDefault]
public virtual int? InventoryID { get; set; }
public abstract class inventoryID : PX.Data.BQL.BqlInt.Field<inventoryID> { }
#endregion
#region Required
[PXDBBool()]
[PXDefault(false)]
[PXUIField(DisplayName = "Required")]
public virtual bool? Required { get; set; }
public abstract class required : PX.Data.BQL.BqlBool.Field<required> { }
#endregion
}
I have this issue with some Win controls. There is a Date DropDowns that I want to access, however both start and end Date identical (todays date), so replay goes to first one -start Date- everytime, both for Start Date and End Date comboboxes.
My question is related to this old post and I see issue in this post still not fixed / answered
CodedUI : PropertyNames.ControlName doesn't work
When I spy over comboboxes I see ControlNames are unique so I tried to use control names for the controls , through UIMap.uitest I added ControlName to SearchProperties collection and write the values however now it can not find.
public WinControl UIItem17Ocak2019PerşemDropDown
{
get
{
if ((this.mUIItem17Ocak2019PerşemDropDown == null))
{
this.mUIItem17Ocak2019PerşemDropDown = new WinControl(this);
#region Search Criteria
this.mUIItem17Ocak2019PerşemDropDown.SearchProperties[UITestControl.PropertyNames.ControlType] = "DropDown";
this.mUIItem17Ocak2019PerşemDropDown.SearchProperties[UITestControl.PropertyNames.Name] = "17 Ocak 2019 Perşembe";
this.mUIItem17Ocak2019PerşemDropDown.SearchProperties["ControlName"] = "bBasT";
this.mUIItem17Ocak2019PerşemDropDown.WindowTitles.Add("Filtre");
#endregion
}
return this.mUIItem17Ocak2019PerşemDropDown;
}
}
here is exception I am getting
Message: Test method
CodedUITestProject2.KayitTablolari_HurdaListesi.HurdaListesiTabloKontrol threw exception: Microsoft.VisualStudio.TestTools.UITest.Extension.UITestControlNotFoundException: The playback failed to find the control with the given search properties. Additional Details: TechnologyName: 'MSAA'ControlType: 'DropDown' Name: '17 Ocak 2019 Perşembe' ControlName: 'bBasT' ---System.Runtime.InteropServices.COMException: Bir COM bileşenine yapılan çağrıdan HRESULT E_FAIL hatası döndürüldü.
Or is there a way for order of controls in the window? such as "not click first but click second combobox in the window."
I found a solution, as described in below page, controlName is not for individual controls but for windowed controls, such as WinWindow.
https://blogs.msdn.microsoft.com/vstsqualitytools/2010/01/15/understanding-the-window-search-and-windowed-properties/
Following on Rasim Avci's anwser, below code illustrates generated code from a UIMap. The program under test was a windows Forms project containing a Form with a ComboBox on it.
[GeneratedCode("Coded UITest Builder", "15.0.26208.0")]
public class UIForm1Window : WinWindow
{
public UIForm1Window()
{
#region Search Criteria
this.SearchProperties[WinWindow.PropertyNames.Name] = "Form1";
this.SearchProperties.Add(new PropertyExpression(WinWindow.PropertyNames.ClassName, "WindowsForms10.Window", PropertyExpressionOperator.Contains));
this.WindowTitles.Add("Form1");
#endregion
}
#region Properties
public UICbStartDateWindow UICbStartDateWindow
{
get
{
if ((this.mUICbStartDateWindow == null))
{
this.mUICbStartDateWindow = new UICbStartDateWindow(this);
}
return this.mUICbStartDateWindow;
}
}
public UICbEndDateWindow UICbEndDateWindow
{
get
{
if ((this.mUICbEndDateWindow == null))
{
this.mUICbEndDateWindow = new UICbEndDateWindow(this);
}
return this.mUICbEndDateWindow;
}
}
#endregion
#region Fields
private UICbStartDateWindow mUICbStartDateWindow;
private UICbEndDateWindow mUICbEndDateWindow;
#endregion
}
[GeneratedCode("Coded UITest Builder", "15.0.26208.0")]
public class UICbStartDateWindow : WinWindow
{
public UICbStartDateWindow(UITestControl searchLimitContainer) :
base(searchLimitContainer)
{
#region Search Criteria
this.SearchProperties[WinWindow.PropertyNames.ControlName] = "cbStartDate";
this.WindowTitles.Add("Form1");
#endregion
}
#region Properties
public WinComboBox UICbStartDateComboBox
{
get
{
if ((this.mUICbStartDateComboBox == null))
{
this.mUICbStartDateComboBox = new WinComboBox(this);
#region Search Criteria
this.mUICbStartDateComboBox.SearchProperties[WinComboBox.PropertyNames.Name] = "cbStartDate";
this.mUICbStartDateComboBox.WindowTitles.Add("Form1");
#endregion
}
return this.mUICbStartDateComboBox;
}
}
#endregion
#region Fields
private WinComboBox mUICbStartDateComboBox;
#endregion
}
In bellow image, the control hierarchy is illustrated. It clearly shows the UICbStartDateWindow as parent for the ComboBox.
As you can see, the generated code should follow what is described in the link from Rasim Avci's answer.
I'm extending the AccountByPeriodEnq logic, I just want to add a button on top of my screen to modify the selected GL records but it just doesnt want to show up and I can't figure out why.
Here's my code :
namespace PX.Objects.GL
{
class AccountByPeriodEnqExtensions : PXGraphExtension<AccountByPeriodEnq>
{
#region Actions
public PXAction<AccountByPeriodFilter> Letter;
[PXUIField(Visible = true, DisplayName = "Lettrer")]
[PXButton(CommitChanges = true)]
protected virtual IEnumerable letter(PXAdapter adapter)
{
IReadOnlyCollection<GLTranR> selectedTrans = GetSelectedTrans();
if (selectedTrans.Any())
{
PXLongOperation.StartOperation(this, delegate ()
{
foreach(GLTranR line in selectedTrans)
{
// UpdateSomeFieldsAndPersists
}
});
}
else
{
throw new PXException("Error");
}
return Base.Filter.Select();
}
#endregion
#region Utility
private IReadOnlyCollection<GLTranR> GetSelectedTrans()
{
return Base.GLTranEnq.Cache.Updated
.Cast<GLTranR>()
.Where(tran => tran.Selected == true)
.ToArray();
}
#endregion
}
}
Is there anything I'm missing here ?
Regards,
Edit:
To clarify i'm trying to customize the GL404000, Account Details. And using the inspector I saw the Business logic is in the AccountByPeriodEnq Graph
Using Acumatica Inspect Element feature notice that the 'Account by Period' screen (GL402000) is not using the 'AccountByPeriodEnq' graph.
It is using the 'AccountHistoryByYearEnq' graph instead so that's the graph you want to target:
You also need to declare Actions on the primary DAC of that Graph.
The one for 'AccountHistoryByYearEnq' is a little bit harder to find than usual.
You can use Acumatica Source Code page and search for 'PXPrimaryGraph(typeof(AccountHistoryByYearEnq)':
In this case it is AccountByYearFilter DAC that you should use:
[System.SerializableAttribute()]
[PXCacheName(Messages.Account)]
[PXPrimaryGraph(typeof(AccountHistoryByYearEnq), Filter = typeof(AccountByYearFilter))]
public partial class Account : PX.Data.IBqlTable, PX.SM.IIncludable
{
[…]
}
I think this is a special case for Filter, when there's no filter Account would have been the DAC to use for the Actions.
Now that you have identified the Primary Graph of the Screen (AccountHistoryByYearEnq) and the Primary DAC of the Graph (AccountByYearFilter) it should work as expected:
public class AccountByPeriodEnq_Extension : PXGraphExtension<AccountHistoryByYearEnq>
{
public PXAction<AccountByYearFilter> letter;
[PXUIField(DisplayName = "Letter")]
[PXButton]
protected virtual IEnumerable Letter(PXAdapter adapter)
{
return adapter.Get();
}
}
UI:
EDIT:
For Account detail page (GL404000), use the same code with different DAC and Graph:
using PX.Data;
using System.Collections;
namespace PX.Objects.GL
{
public class AccountByPeriodEnq_Extension : PXGraphExtension<AccountByPeriodEnq>
{
#region Actions
public PXAction<AccountByPeriodFilter> letter;
[PXUIField(DisplayName = "Letter")]
[PXButton]
protected virtual IEnumerable Letter(PXAdapter adapter)
{
return adapter.Get();
}
#endregion
}
}
With a base Acumatica install that's all that is needed for the Action to appear:
Note that you can specify explicit state and view rights for the button control, though I don't think your issue is related to access rights if you're working in a developer instance:
[PXUIField(DisplayName = "Letter",
MapEnableRights = PXCacheRights.Select,
MapViewRights = PXCacheRights.Select)]
I finally found my problem, I missed the public member for my class.
namespace PX.Objects.GL
{
public class AccountByPeriodEnqExtensions : PXGraphExtension<AccountByPeriodEnq>
{
#region Actions
public PXAction<AccountByPeriodFilter> Letter;
Thanks for your answer HB, i'll reuse it.
I am trying to create a custom Parts selector to search all records if there is no vendor selected, if a vendor is selected I want to filter the selector's results to only include Parts with the vendor selected.
Using PXCustomSelectorAttribute:
SQL Error: Incorrect syntax near the keyword 'AND'
Incorrect syntax near the keyword 'ORDER'
[PXNonInstantiatedExtension]
public class SO_SOLine_ExistingColumn : PXCacheExtension<PX.Objects.SO.SOLine>
{
#region InventoryID
[PXMergeAttributes(Method = MergeMethod.Replace)] //was append
[PXUIField(DisplayName = "Part #")]
[PartSelector(typeof(SOLineExt.usrCusVendor))]
public int? InventoryID { get; set; }
#endregion
}
public class PartSelector : PXCustomSelectorAttribute
{
[Serializable]
[PXProjection(typeof(
Select2<atcVendorItem,
LeftJoin<InventoryItem,
On<InventoryItem.inventoryCD, Equal<atcVendorItem.inventoryCD>, And<InventoryItem.inventoryID, Equal<atcVendorItem.inventoryID>>>>>), Persistent = false)]
public class atcPartView : IBqlTable
{
// DAC W/Inventory Item Table mapped and joined with Vendor Items
}
//Selected table
private Type _MfgField;
////way to have multiple description fields
public PartSelector(Type MfgField) : base(typeof(atcPartView.inventoryID))
{
_MfgField = MfgField;
}
protected virtual IEnumerable GetRecords()
{
var cache = this._Graph.Caches[BqlCommand.GetItemType(_MfgField)];
var cbs = (BAccount)cache.Current;
// make mfgfield
if (cbs != null)
{
foreach (atcPartView p in PXSelect<atcPartView, Where<atcPartView.vendorID, Equal<Required<SOLineExt.usrMfg>>>>.Select(_Graph, cbs.BAccountID))
{
yield return p;
}
}
else
{
foreach (atcPartView p in PXSelect<atcPartView, Where<atcPartView.vendorID, IsNotNull>>.Select(_Graph))
{
yield return p;
}
}
}
}
I was able to find another exception while debugging in visual studio,
my issue was because I was trying to cast the field as BAccount when referencing the SOLine Extension. Changing the cast fixed my issue
I am writing a custom module that retrieves and pushes data directly from the Orchard DB using an injected IRepository.
This works fine until i need to update a content part. I add an update in my migrations class and the update runs through (DB schema updated with default values), however I can't update any of the new values through IRepository. I have to drop down into the NHibernate.ISession to flush the changes through.
This all works fine on a newly created recipe, it's only when i alter a part. Here are the key code snippets:
public class TranslationsPartRecord : ContentPartRecord
{
internal const string DefaultProductName = "Product";
public TranslationsPartRecord()
{
ProductName = DefaultProductName;
}
public virtual string ProductName { get; set; }
}
public class TranslationsPart : ContentPart<TranslationsPartRecord>
{
public string ProductName
{
get { return Record.ProductName; }
set { Record.ProductName = value; }
}
}
public class TranslationsHandler : ContentHandler
{
public TranslationsHandler(IRepository<TranslationsPartRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
}
public class Migrations : DataMigrationImpl
{
public int Create()
{
SchemaBuilder.CreateTable("TranslationsPartRecord", table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column("ProductName", DbType.String, column => column.NotNull().WithDefault(TranslationsPartRecord.DefaultProductName))
);
return 1;
}
public int UpdateFrom1()
{
SchemaBuilder.AlterTable("TranslationsPartRecord", table => table.AddColumn("ProductDescription", DbType.String, column => column.NotNull().WithDefault(TranslationsPartRecord.DefaultProductDescription)));
return 2;
}
}
When i add the second property "ProductDescription" in this example, after the update is run the columns appear in the DB but i cannot update them until i recreate the Orchard recipe (blat App_Data and start again).
here's how I am trying to update:
// ctor
public AdminController(IRepository<TranslationsPartRecord> translationsRepository)
{
_translationsRepository = translationsRepository;
}
[HttpPost]
public ActionResult Translations(TranslationsViewModel translationsViewModel)
{
var translations = _translationsRepository.Table.SingleOrDefault();
translations.ProductName = translationsViewModel.ProductName;
translations.ProductDescription = translationsViewModel.ProductDescription;
_translationsRepository.Update(translations);
_translationsRepository.Flush();
}
and here's the NHibernate "fix":
var session = _sessionLocator.For(typeof(TranslationsPartRecord));
var translations = _translationsRepository.Table.SingleOrDefault();
// is translations.Id always 1?
var dbTranslations = session.Get<TranslationsPartRecord>(translations.Id);
dbTranslations.ProductName = translationsViewModel.ProductName;
dbTranslations.ProductDescription = translationsViewModel.ProductDescription;
session.Update(dbTranslations);
session.Flush();
which seems a bit kludgey...
Cheers.
ps i'm still running Orchard 1.3.9
pps after more testing, the NHibernate fix has stopped working now, so perhaps my initial findings were a red herring. It seems as though new properties on the content part are totally ignored by NHibernate when updating/retrieving - as though the object definition is cached somewhere...
If your mappings aren't being updated that is strange. You can try to force it by deleting the mappings.bin in the app_data folder, and restarting the application. Orchard should recreate the nhibernate mappings and save as mappings.bin.
I have ran into the same issue, and the only way around it that I can find is to delete mappings.bin (I don't need to disable and re-enable the module). In fact, this is the answer that I got from Bertrand when I asked why this was happening.
I have logged this as an issue at http://orchard.codeplex.com/workitem/19306. If you could vote this up, then we may get it looked at quicker.
This seems like a similar issue to what I am seeing... I am seeing that when you enable a module, it runs the NHibernate mappings BEFORE running the Migrations..
https://orchard.codeplex.com/workitem/19603
Josh
Update the hash value in the ComputingHash method in the PersistenceConfiguration Class,
updating the hash value may recreate the mappings.bin file.
public class PersistenceConfiguration : ISessionConfigurationEvents
{
public void Created(FluentConfiguration cfg, AutoPersistenceModel defaultModel)
{
DoModelMapping(cfg, defaultModel);
}
public void ComputingHash(Hash hash)
{
hash.AddString("Some_strings_to_update_hash");
}
private void DoModelMapping(FluentConfiguration cfg, AutoPersistenceModel defaultModel)
{
// mappings here....
}
public void Prepared(FluentConfiguration cfg) { }
public void Building(Configuration cfg) { }
public void Finished(Configuration cfg) { }
}