My use case is that when making a bill, the client wants to be able to specify a portion of the bill amount to which no discount is applied. (eg the total bill is $125, and the customer gets a 10% discount for paying in cash on delivery, but the discount does not apply to shipping costs. So, they want to say: "$25 of this is not discountable")
Then, I want to override the discount amount field based on this custom field.
I have added a data field to my APInvoice DAC via the Customization editor, and then activated it in my C# code as an extension:
namespace AcmeCorp.DAC
{
[PXCacheName("APInvoice")]
public class APInvoiceExt : PXCacheExtension<APInvoice>
{
public static bool IsActive() { return true; }
// Auto-Generated from ERP Customization Editor
#region UsrAmountExcludedFromDiscount
[PXDBDouble()]
[PXUIField(DisplayName = "Amt Excluded Discount")]
public virtual double? UsrAmountExcludedFromDiscount { get; set; }
public abstract class usrAmountExcludedFromDiscount : PX.Data.BQL.BqlDouble.Field<usrAmountExcludedFromDiscount> { }
#endregion
}
}
Now I want to check the value in that field to know how to adjust the discount field. How do I get this value?
[EDIT]
This allows me to get the extension, but the value is null, even though I have checked the record, and there is a value present there.
I set up a using statement first:
using APInvoiceExtension = Acme.DAC.APInvoiceExt;
And then I can use that in the GetExtension() method:
protected virtual void _(Events.RowSelected<APInvoice> e)
{
if (e.Row == null) return;
double? Amt2Exclude = null;
APInvoice apInvoice = e.Row as APInvoice;
var chkRef = apInvoice.RefNbr;
var ext = PXCache<APInvoice>.GetExtension<APInvoiceExtension>(apInvoice);
if (ext != null)
{
Amt2Exclude = ext.UsrAmountExcludedFromDiscount;
}
}
The RefNbr has a value just fine. But, as I said the value for UsrAmountExcludedFromDiscount is null.
[EDIT]
I tried Rick's suggestion and got the same error:
There's a few things I'm kind of confused about, like your DAC has the namespace AcmeCorp.DAC in the code example but you have a using statement that uses a different namespace. I would also probably use a decimal over a double for your data type in this instance, and in any instance where you are working with currencies.
For retrieving an extension, you can simply do the following.
protected virtual void _(Events.RowSelected<APInvoice> eventHandler)
{
APInvoice row = eventHandler.Row;
if (row is null) return;
APInvoiceExtension rowExt = row.GetExtension<APInvoiceExtension>();
if (rowExt != null)
{
excludedAmount = rowExt.UsrAmountExcludedFromDiscount;
}
}
I would probably avoid renaming the extension from APInvoiceExt to APInvoiceExtension on your using statement, but the above code should work to retrieve the extension. If you still have a null value in your extension, I would say that's most likely because a value never gets assigned to it, and you may need to post more of the code for us to evaluate what's actually going on here.
how about
APInvoiceExt ext = PXCache<APInvoice>.GetExtension<APInvoiceExt>(apInvoice);
EDIT:
namespace PX.Objects.AP
{
public class APRegisterExt : PXCacheExtension<PX.Objects.AP.APRegister>
{
#region UsrAmountExcludedFromDiscount
[PXDBDecimal]
[PXUIField(DisplayName="Amt Excluded Discount")]
public virtual Decimal? UsrAmountExcludedFromDiscount { get; set; }
public abstract class usrAmountExcludedFromDiscount : PX.Data.BQL.BqlDecimal.Field<usrAmountExcludedFromDiscount> { }
#endregion
}
public class APInvoiceEntry_Extension : PXGraphExtension<APInvoiceEntry>
{
#region Event Handlers
protected void APInvoice_RowSelecting(PXCache cache, PXRowSelectingEventArgs e)
{
if (e.Row == null) return;
decimal? Amt2Exclude = null;
APRegister apInvoice = e.Row as APRegister;
var chkRef = apInvoice.RefNbr;
var ext = PXCache<APRegister>.GetExtension<APRegisterExt>(apInvoice);
if (ext != null)
{
Amt2Exclude = ext.UsrAmountExcludedFromDiscount;
}
PXTrace.WriteInformation($"REF : {apInvoice.RefNbr}");
}
#endregion
}
}
Related
I made a custom selector that only displays the customers of the current user but when I select a customer I get the error: 'Customer' Cannot be found in the system.
The code for the Custom selector and how I implemented it on the DAC:
[PXNonInstantiatedExtension]
public class SO_SOOrder_ExistingColumn : PXCacheExtension<PX.Objects.SO.SOOrder>
{
#region CustomerID
[PXMergeAttributes(Method = MergeMethod.Merge)]
[PXForeignReference(typeof(Field<SOOrder.customerID>.IsRelatedTo<BAccount.bAccountID>))]
[SalesRepCustomer]
public int? CustomerID { get; set; }
#endregion
}
public class SalesRepCustomer : PXCustomSelectorAttribute
{
public SalesRepCustomer() : base(typeof(Customer.acctCD))
{
this.DescriptionField = typeof(Customer.acctCD);
}
protected virtual IEnumerable GetRecords()
{
foreach (Customer pc in PXSelect<Customer>.Select(this._Graph))
{
//Getting Current UserID
var cache1 = _Graph.Caches[BqlCommand.GetItemType(typeof(AccessInfo.userName))];
AccessInfo currentCacheObjecta = (AccessInfo)cache1.Current;
var userName = currentCacheObjecta.UserName;
SalesPerson person = PXSelect<SalesPerson, Where<SalesPerson.descr, Equal<Required<AccessInfo.userName>>>>.Select(_Graph, userName);
if (person != null)
{
CustSalesPeople custSalesPeople = PXSelect<CustSalesPeople, Where<CustSalesPeople.salesPersonID, Equal<Required<SalesPerson.salesPersonID>>, And<CustSalesPeople.bAccountID, Equal<Required<CustSalesPeople.bAccountID>>>>>.Select(_Graph, person.SalesPersonID, pc.BAccountID);
//return all customers related to this SalesPersonID in the CustSalesPeople table
if (!(custSalesPeople is null))
{
yield return pc;
}
}
else
{
//current user is not a sales person
//return all of the customers
yield return pc;
}
}
}
}
Screenshot of the selector on the Sales Order Screen:
Any help on this will be appreciated
You should have the constructor look something like
public SalesRepCustomer() : base(typeof(Customer.bAccountID))
{
this.DescriptionField = typeof(Customer.acctName);
this.SubstituteKey = typeof(Customer.acctCD);
}
The first type that you pass to the base constructor is the type of the value that will be used for the field.
In this case you want the customer id(an int) and you are currently using the AcctCD(a string) field. The description field would typically be the name of the customer account and the substitute key will make it so that the users see the AcctCD instead of the the customer ID(the actual value) which is just an integer.
I have a datagrid in my xamarin form app and it got a editable column. The values in the column are from MySql database and user can change the value and store to db. I used IPropertyChanged interface to allow user make the changes to the value. There is one condition when editing the value. The new value must be equal or bigger than the original value. My problem is whenever I enter a value bigger than the original, I cannot edit the value again to previous value. For example, the original value is 10. The new value I enter is 30. If I want to change the value again and this time I set it to 20, it is not allowing me because now the original value is 30 not 10 and 20 is less than 30. How can I retain the original value and compare with it?
public int ActualReading
{
get
{
return _ActualReading;
}
set
{
if (value >= _ActualReading)
{
_ActualReading = value;
RaisePropertyChanged("ActualReading");
}
else
{
UserDialogs.Instance.Alert("Meter readings should not be smaller than previous value.","Error","Ok");
}
}
}
private void RaisePropertyChanged(String Name)
{
if (PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(Name));
}
You have to store the original value. I'm using the following pattern.
Assuming you have a model like this
public class Model
{
public int ActualReading {get; set;}
}
and a viewmodel like this (I removed the INotifyPropertyChanged part for better reading)
public class ViewModel
{
private readonly Model MyModel;
private int _actualReading;
public int ActualReading
{
get { return _actualReading; }
set { _actualReading = value; }
}
public ViewModel(Model model)
{
MyModel = model;
ActualReading = model.ActualReading;
}
public Model GetModel()
{
MyModel.ActualReading = ActualReading;
return MyModel;
}
}
When you create the ViewModel instances you initialize it with the coresponding Model instance. When you have implemented this you can add your check in an easy way like this.
private int _actualReading;
public int ActualReading
{
get { return _actualReading; }
set
{
if (value >= MyModel.ActualReading)
{
_actualReading = value;
}
else
{
UserDialogs.Instance.Alert("Meter readings should not be smaller than previous value.", "Error", "Ok");
}
}
}
I have already create a document using type = 'check' in screen Checks and Payments (AP302000) of Acumatica ERP and released it. Please refer to this screenshot below.
and then I will set off this payment using bill. Please refer to this screenshot below.
I want to pass Vendor Ref of detail transaction in Checks and Payments's screen to a new additional field in Applications tab Menu of Bill and Adjustments screen when I create bill using the same vendor.
Should I create new additional field ? and then should I create APPaymentEntryExtension in Release action to passing this Vendor Ref of APAdjust to new additional field in Application Tab Menu of Bill's screen ?
Or maybe is there another way without create new additional field ?
Thanks,
Looks like it's enough to declare custom unbound field for the APAdjust DAC and populate it inside RowSelecting (for records with 0 Amount Paid) and RowInserting (for records with Amount Paid greater then 0) handlers within the APInvoiceEntry BLC extension:
public class APAdjustExt : PXCacheExtension<APAdjust>
{
public abstract class invoiceNbr : IBqlField
{ }
[PXString]
[PXUIField(DisplayName = "Vendor Ref.")]
public string InvoiceNbr { get; set; }
}
public class APInvoiceEntryExt : PXGraphExtension<APInvoiceEntry>
{
protected void APAdjust_RowSelecting(PXCache sender, PXRowSelectingEventArgs e)
{
APAdjust row = e.Row as APAdjust;
if (row == null) return;
using (var connScope = new PXConnectionScope())
{
row.GetExtension<APAdjustExt>().InvoiceNbr = GetInvoiceNbr(row);
}
}
protected void APAdjust_RowInserting(PXCache sender, PXRowInsertingEventArgs e)
{
APAdjust row = e.Row as APAdjust;
if (row != null)
{
row.GetExtension<APAdjustExt>().InvoiceNbr = GetInvoiceNbr(row);
}
}
private string GetInvoiceNbr(APAdjust adjustment)
{
string invoiceNbr = null;
var doc = (APInvoice)PXSelect<APInvoice,
Where<APInvoice.docType, Equal<Required<APInvoice.docType>>,
And<APInvoice.refNbr, Equal<Required<APInvoice.refNbr>>>>>
.Select(Base, adjustment.AdjgDocType, adjustment.AdjgRefNbr);
if (doc != null)
{
invoiceNbr = doc.InvoiceNbr;
}
return invoiceNbr;
}
}
Currently I'm implementing a Screen indicating wheater a module is not existing or still in development.
The Back Button has the following code:
regionNavigationService.Journal.GoBack();
This is working as expected. But the user is not coming from the Home Screen. So I need to access the View Name from the last Entry in Navigation Journal.
Example: User is coming from Settings Screen => The text should display "Back to Settings Screen"
Assuming the view name you are looking for is when you do new Uri("Main", UriKind.Relative) that you would want the word Main as the view name.
The forward and backward stacks in the RegionNavigationJournal are private. You could use reflection to get access to it.
var journal = regionNavigationService.Journal as RegionNavigationJournal;
if (journal != null)
{
var stack =
(Stack<IRegionNavigationJournalEntry>)
typeof (RegionNavigationJournal).GetField("backStack",
BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(journal);
var name = stack.Peek().Uri.OriginalString;
}
Or a better way is to implement your own IRegionNavigationJournal that is a wrapper around it. This is using Unity to constructor inject the default RegionNavigationJournal if using MEF you might need to put the ImportingConstructorAttribute on it.
public class RegionNavigationJournalWrapper : IRegionNavigationJournal
{
private readonly IRegionNavigationJournal _regionNavigationJournal;
private readonly Stack<Uri> _backStack = new Stack<Uri>();
// Constructor inject prism default RegionNavigationJournal to wrap
public RegionNavigationJournalWrapper(RegionNavigationJournal regionNavigationJournal)
{
_regionNavigationJournal = regionNavigationJournal;
}
public string PreviousViewName
{
get
{
if (_backStack.Count > 0)
{
return _backStack.Peek().OriginalString;
}
return String.Empty;
}
}
public bool CanGoBack
{
get { return _regionNavigationJournal.CanGoBack; }
}
public bool CanGoForward
{
get { return _regionNavigationJournal.CanGoForward; }
}
public void Clear()
{
_backStack.Clear();
_regionNavigationJournal.Clear();
}
public IRegionNavigationJournalEntry CurrentEntry
{
get { return _regionNavigationJournal.CurrentEntry; }
}
public void GoBack()
{
// Save current entry
var currentEntry = CurrentEntry;
// try and go back
_regionNavigationJournal.GoBack();
// if currententry isn't equal to previous entry then we moved back
if (CurrentEntry != currentEntry)
{
_backStack.Pop();
}
}
public void GoForward()
{
// Save current entry
var currentEntry = CurrentEntry;
// try and go forward
_regionNavigationJournal.GoForward();
// if currententry isn't equal to previous entry then we moved forward
if (currentEntry != null && CurrentEntry != currentEntry)
{
_backStack.Push(currentEntry.Uri);
}
}
public INavigateAsync NavigationTarget
{
get { return _regionNavigationJournal.NavigationTarget; }
set { _regionNavigationJournal.NavigationTarget = value; }
}
public void RecordNavigation(IRegionNavigationJournalEntry entry)
{
var currentEntry = CurrentEntry;
_regionNavigationJournal.RecordNavigation(entry);
// if currententry isn't equal to previous entry then we moved forward
if (currentEntry != null && CurrentEntry == entry)
{
_backStack.Push(currentEntry.Uri);
}
}
}
If using unity in your Prism Bootstrapper you will need to replace the default registration of the IRegionNavigationJournal
protected override void ConfigureContainer()
{
this.RegisterTypeIfMissing(typeof(IRegionNavigationJournal), typeof(RegionNavigationJournalWrapper), false);
base.ConfigureContainer();
}
If using MEF you will need to put the ExportAttribute on top of the RegionNavigationJournalWrapper
[Export(typeof(IRegionNavigationJournal))]
You can see http://msdn.microsoft.com/en-us/library/gg430866%28v=pandp.40%29.aspx for more information on replacing their default implementation with your own. Once you have the wrapper you will still need to cast it as RegionNavigationJournalWrapper to get access to the PreviousViewName so still not perfect or create an interface that RegionNavigationJournalWrapper also implements to cast to that to get you access to the PreviousViewName
I have a multiple layered application I'm rewriting using Entity Framework 4 w/ Code First. The important things:
In the data layer, on my context, I have:
public DbSet<MobileSerialContainer> Mobiles { get; set; }
This context has a static instance. I know, I know, terrible practice. There are reasons which aren't relevant to this post as to why I'm doing this.
MobileSerialContainer consists of the following:
[Table("Mobiles")]
public sealed class MobileSerialContainer
{
[Key]
public long Serial { get; set; }
[StringLength(32)]
public string Name { get; set; }
public MobileSerialContainer() { }
public MobileSerialContainer(Mobile mobile)
{
Mobile = mobile;
LeContext.Instance.Mobiles.Add(this);
}
[StringLength(1024)]
public string FullClassName
{
get { return Mobile == null ? "" : Mobile.GetType().AssemblyQualifiedName; }
set
{
if (string.IsNullOrEmpty(value) || value == FullClassName)
return;
Mobile = null;
var type = Type.GetType(value);
if (type == null)
return;
if (!type.IsSubclassOf(typeof(Mobile))
&& type != typeof(Mobile))
return;
var constructor = type.GetConstructor(new [] { GetType() });
// The problem here is that Person ( which extends mobile ) does not have a constructor that takes a MobileSerialContainer.
// This is a problem of course, because I want to make this entire layer transparent to the system, so that each derivative
// of Mobile does not have to implement this second constructor. Blasphemy!
if (constructor == null)
return;
Mobile = (Mobile)constructor.Invoke(new object[] { this });
}
}
public string SerializedString
{
get
{
return Mobile == null ? "" : Mobile.Serialize();
}
set
{
if (Mobile == null)
return;
if (string.IsNullOrEmpty(value))
return;
Mobile.Deserialize(value);
}
}
[NotMapped]
public Mobile Mobile { get; set; }
public void Delete()
{
LeContext.Instance.Mobiles.Remove(this);
}
}
Now... I know this is a long post bear with me. Mobile is this:
public class Mobile
{
public long Serial { get { return Container.Serial; } }
public string Name { get { return Container.Name; } set { Container.Name = value; } }
public Mobile()
{
Container = new MobileSerialContainer(this);
}
public Mobile(MobileSerialContainer container)
{
Container = container;
}
public void Delete()
{
Container.Delete();
}
private MobileSerialContainer Container { get; set; }
protected static string MakeSafeString(string value)
{
if (string.IsNullOrEmpty(value))
return value;
return value.Replace("&", "&")
.Replace(",", ",")
.Replace("=", "&eq;");
}
protected static string MakeUnsafeString(string value)
{
if (string.IsNullOrEmpty(value))
return value;
return value.Replace("&eq;", "=")
.Replace(",", ",")
.Replace("&", "&");
}
public virtual string Serialize()
{
string result = "";
var properties = PersistentProperties;
foreach (var property in properties)
{
string name = MakeSafeString(property.Name);
var value = property.GetValue(this, null);
string unsafeValueString = (string)Convert.ChangeType(value, typeof(string));
string valueString = MakeSafeString(unsafeValueString);
result += name + "=" + valueString + ",";
}
return result;
}
public virtual void Deserialize(string serialized)
{
var properties = PersistentProperties.ToList();
var entries = serialized.Split(',');
foreach (var entry in entries)
{
if (string.IsNullOrEmpty(entry))
continue;
var keyPair = entry.Split('=');
if (keyPair.Length != 2)
continue;
string name = MakeUnsafeString(keyPair[0]);
string value = MakeUnsafeString(keyPair[1]);
var property = properties.FirstOrDefault(p => p.Name == name);
if (property == null)
continue;
object rawValue = Convert.ChangeType(value, property.PropertyType);
property.SetValue(this, rawValue, null);
}
}
protected IEnumerable<PropertyInfo> PersistentProperties
{
get
{
var type = GetType();
var properties = type.GetProperties().Where(p => p.GetCustomAttributes(typeof(PersistAttribute), true).Any());
return properties;
}
}
}
Several layers above this, I have the System layer, in which I have the class Person:
public class Person : Mobile
{
[Persist]
public string LastName { get; set; }
}
The basic idea is this: I want the System layer to have almost no knowledge of the Data layer. It creates anything that extends "Mobile", which is automatically saved to the database. I don't want to have a table for Person, hence the weird serialization stuff, because there are literally hundreds of classes that extend Mobile. I don't want hundreds of tables. All of this serialization stuff works perfectly, the SerializedString bit, saving everything, reloading, etc etc. The only thing I haven't come up with a solution for is:
I don't want to have to implement the two constructors to Person:
public Person() : base() { }
public Person(MobileSerialContainer container)
: base(container) { }
as that requires the System layer to have more knowledge of the Data layer.
The weird serialization string thing stays. The reflection business stays. I know it's slow, but database writes and reads are very rare, and asynchronous anyway.
Besides that, I'm looking for any cool ideas about how to resolve this. Thanks!
[edit]
Changed a miswritten line of code in the MobileSerialContainer class pasted here.
If you are rewriting your application, you could reconsider all the design of your system to keep your domain layer (System layer) independent from your Data Access layer using :
A repository pattern to handle access to your database (dataContext)
A domain layer for your business objects (mobile and stuff)
Inversion Of Control pattern (IOC) to keep your layers loosely coupled
The inheritance stuff is definitively not the good way to go to keep a system loosely coupled.
What you want is type.GetConstructors() not type.GetConstructor(), this will let you get the base constructors and pass the type you are looking for.