Acumatica - Fields do not load when item selected from PXSelector - customization

I'm having trouble getting my fields to load when a value is selected from a selector. First screen (Group-Categories) has a Group Selector that displays description in textfield and grid where users can input Categories and Cat Descriptions. This works perfectly.
// Group DAC
[Serializable]
public class INMerchandiseGroup : IBqlTable
{
#region GroupCD
[PXDBString(10, IsKey = true, BqlField = typeof(INMerchandiseGroup.groupCD))]
[PXUIField(DisplayName = "Group Code", Visibility=PXUIVisibility.SelectorVisible)]
[PXSelector(typeof(Search<INMerchandiseGroup.groupCD>),
typeof(INMerchandiseGroup.groupCD),
typeof(INMerchandiseGroup.description))]
public virtual string GroupCD { get; set; }
public abstract class groupCD : PX.Data.BQL.BqlString.Field<groupCD> { }
#endregion
#region Description
[PXDBString(256, IsUnicode = true, BqlField = typeof(INMerchandiseGroup.description))]
[PXUIField(DisplayName = "Description")]
public virtual string Description { get; set; }
public abstract class description : PX.Data.BQL.BqlString.Field<description> { }
#endregion
}
// Group Graph
public class BPGroupCategoryMaint : PXGraph<BPGroupCategoryMaint, INMerchandiseGroup>
{
// Setup for GroupCd in grid
#region Category GroupCD
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXDBDefault(typeof(INMerchandiseGroup.groupCD))]
protected virtual void INMerchandiseCategory_GroupCD_CacheAttached(PXCache cache)
{
}
#endregion
public PXSelect<INMerchandiseGroup> CurrentGroup;
public PXSave<INMerchandiseGroup> Save;
public PXCancel<INMerchandiseGroup> Cancel;
public PXSelect<INMerchandiseCategory,
Where<INMerchandiseCategory.groupCD, Equal<Current<INMerchandiseGroup.groupCD>>>>
GroupCategories;
}
I have same setup in Category-Dept screen. User selects Category from selector and the Cat Description and Group CD should populate text boxes, but category description and groupcd only populate the first time. After that, the values in cache are null. What am I doing incorrectly?
// Category DAC (this is used in the grid of Group screen, and as a record header in Category screen.
[Serializable]
public class INMerchandiseCategory : IBqlTable
{
#region CategoryCD
[PXDBString(10, IsKey = true, BqlField = typeof(INMerchandiseCategory.categoryCD, InputMask = ">CCCCCCCCCC")]
[PXUIField(DisplayName = "Category Code")]
public virtual string CategoryCD { get; set; }
public abstract class categoryCD : PX.Data.BQL.BqlString.Field<categoryCD> { }
#endregion
#region Description
[PXDBString(256, IsUnicode = true, BqlField = typeof(INMerchandiseCategory.categoryDescription))]
[PXUIField(DisplayName = "Category Description")]
public virtual string CategoryDescription { get; set; }
public abstract class categoryDescription : PX.Data.BQL.BqlString.Field<categoryDescription> { }
#endregion
#region GroupCD
[PXDBString(10, IsKey = true, BqlField = typeof(INMerchandiseCategory.groupCD) )]
[PXUIField(DisplayName = "Group Code")]
public virtual string GroupCD { get; set; }
public abstract class groupCD : PX.Data.BQL.BqlString.Field<groupCD> { }
#endregion
}
// Category Graph
public class BPCategoryDeptMaint : PXGraph<BPCategoryDeptMaint>
{
[PXMergeAttributes(Method = MergeMethod.Merge)]
[PXUIField(DisplayName = "Category Code", Visibility=PXUIVisibility.SelectorVisible)]
[PXSelector(typeof(Search2<INMerchandiseCategory.categoryCD,
InnerJoin<INMerchandiseGroup, On<INMerchandiseCategory.groupCD, Equal<INMerchandiseGroup.groupCD>>>>),
typeof(INMerchandiseCategory.categoryCD),
typeof(INMerchandiseCategory.categoryDescription),
typeof(INMerchandiseCategory.groupCD),
typeof(INMerchandiseGroup.description))]
protected virtual void INMerchandiseCategory_CategoryCD_CacheAttached(PXCache cache)
{
}
#region Dept GroupCD
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXDefault(typeof(INMerchandiseCategory.groupCD), PersistingCheck = PXPersistingCheck.Nothing)]
protected virtual void INMerchandiseDept_GroupCD_CacheAttached(PXCache cache)
{
}
#endregion
#region Dept CategoryCD
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXDefault(typeof(INMerchandiseCategory.categoryCD), PersistingCheck = PXPersistingCheck.Nothing)]
protected virtual void INMerchandiseDept_CategoryCD_CacheAttached(PXCache cache)
{
}
#endregion
public PXSelect<INMerchandiseCategory> CurrentCategory;
public PXSave<INMerchandiseCategory> Save;
public PXCancel<INMerchandiseCategory> Cancel;
public PXSelect<INMerchandiseDept,
Where<INMerchandiseDept.groupCD, Equal<Current<INMerchandiseCategory.groupCD>>,
And<INMerchandiseDept.categoryCD, Equal<Current<INMerchandiseCategory.categoryCD>>>>>
CategoryDepts;
}
}

Make sure the field in the ASPX has attribute commit=true. If flagged for commit, then the UI will send the update to the server when the control is changed.

Related

Add custom SQL column in register

environment: vs2017, .core net 2.1, default .core project with MVC
I try to add NickName column store in SQL DB, i had update DB and also add NickName column in the input model. it succeeds to show input the NickName option in the register page but fault to write into SQL DB after clicks register(and other work as usual).
after override IdentityUser it does not work.
add/chg code in Register.cshtml.cs:
public class CustomUserproperties : IdentityUser
{
public string NickName { get; set; }
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
//here is where i change.
var user = new CustomUserproperties { UserName = Input.UserName, Email = Input.Email, NickName = Input.NickName };
var result = await _userManager.CreateAsync(user, Input.Password);
what could i do else?
Here is a working demo, you could refer to create a new or check the difference between your old one.
1.Modify ApplicationDbContext and Add-migration , update-database
public class ApplicationDbContext : IdentityDbContext<CustomUserproperties>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
2. Scaffold the Identity with the above ApplicationDbContext , then you could get RegisterModel like below:
public class RegisterModel : PageModel
{
private readonly SignInManager<CustomUserproperties> _signInManager;
private readonly UserManager<CustomUserproperties> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
public RegisterModel(
UserManager<CustomUserproperties> userManager,
SignInManager<CustomUserproperties> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public class InputModel
{
[Required]
[Display(Name = "NickName")]
public string NickName { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public void OnGet(string returnUrl = null)
{
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var user = new CustomUserproperties { UserName = Input.Email, Email = Input.Email , NickName = Input.NickName };
var result = await _userManager.CreateAsync(user, Input.Password);
...
}
}
}
3.Change the ConfigureServices in StartUp.cs
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<CustomUserproperties>()
.AddEntityFrameworkStores<ApplicationDbContext>();
4._LoginPartial.cshtml
#inject SignInManager<CustomUserproperties> SignInManager
#inject UserManager<CustomUserproperties> UserManager
Here is my project .

Acumatica - How do I add a new column to InvoiceSplits Selector

I need to add UnitCost to the Add Invoice Details Selector screen that appears when Add Invoice is clicked in Sales Order screen when the invoice type is a credit or return. I am new to Acumatica and not sure how I need to do it as an extension. I added it to InvoiceSplits but when I put breakpoints in there, I never hit them.
I added this code to SOOrderEntry...am I on the right track?
namespace PX.Objects.SO
{
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
{
[System.SerializableAttribute()]
public class InvoiceSplits : IBqlTable
{
#region UnitCost
public abstract class unitCost : PX.Data.IBqlField
{
}
protected Decimal? _UnitCost;
[PXDBPriceCost()]
[PXUIField(DisplayName = "Unit Cost")]
public virtual Decimal? UnitCost
{
get
{
return this._UnitCost;
}
set
{
this._UnitCost = value;
}
}
}
#endregion
#region Event Handlers
public delegate InvoiceSplits CreateInvoiceSplitsDelegate(ARTran artran, SOLine line, SOSalesPerTran sptran, INTran tran, INTranSplit split);
// [PXOverride]
public InvoiceSplits CreateInvoiceSplits(ARTran artran, SOLine line, SOSalesPerTran sptran, INTran tran, INTranSplit split, CreateInvoiceSplitsDelegate baseMethod)
{
InvoiceSplits invSplit = new InvoiceSplits();
invSplit.UnitCost = artran.UnitCost;
if (tran != null)
{
invSplit.UnitCost = split.UnitCost ?? tran.UnitCost;
}
return invSplit;
// return baseMethod(artran,line,sptran,tran,split);
}
Instead of creating a separate InvoiceSplits class, you would want to make a PXCacheExtension of InvoiceSplits.
public sealed class InvoiceSplitsExtension : PXCacheExtension<InvoiceSplits>
{
#region UnitCost
public abstract class unitCost : IBqlField
{
}
[PXDBPriceCost]
[PXUIField(DisplayName = "Unit Cost")]
public decimal? UnitCost { get; set; }
#endregion
}
Then in the PXOverride of the CreateInvoiceSplits method you would get the CacheExtension and set the UnitCost on that.
public delegate InvoiceSplits CreateInvoiceSplitsDel(ARTran artran, SOLine line, SOSalesPerTran sptran, INTran tran, INTranSplit split);
[PXOverride]
public InvoiceSplits CreateInvoiceSplits(ARTran artran, SOLine line, SOSalesPerTran sptran, INTran tran, INTranSplit split, CreateInvoiceSplitsDel del)
{
InvoiceSplits invSplit = del?.Invoke(artran, line, sptran, tran, split);
InvoiceSplitsExtension invSplitExt = PXCache<InvoiceSplits>.GetExtension<InvoiceSplitsExtension>(invSplit);
invSplitExt.UnitCost = artran.UnitCost;
if (tran != null)
{
invSplitExt.UnitCost = split.UnitCost ?? tran.UnitCost;
}
return invSplit;
}
Lastly, you will need to add your UnitCost field to the UI. To do this you will need to go into a customization package, customize the SO301000 screen and navigate to Dialogs->Dialog: Add Invoice Details->Grid: invoiceSplits on the tree view. Then go to the "Add Data Fields" tab, select your UnitCost field and click create controls.

How to map DropList in Sitecore Glass.Mapper

I am mapping Sitecore Items using GlassMapper v5 in Sitecore.
We implemented the following classes with GlassMapper.
However, although the value of the field is acquired for the ItemTemplate item, the value of the Droplist field (CategoryTemplate) created in the ItemTemplate has been returned as NULL and it can not be acquired.
[SitecoreType(TemplateId = "9876...", AutoMap = true)]
public class ItemTemplate
{
[SitecoreParent]
public virtual Common Parent { get; set; }
[SitecoreField(FieldName = "Category", FieldType = SitecoreFieldType.Droplist)]
public virtual CategoryTemplate Category { get; set; }
}
[SitecoreType(TemplateId = "1234...", AutoMap = true, TemplateName = "CategoryTemplate")]
public class CategoryTemplate
{
[SitecoreField(FieldName = "Id")]
public virtual string CategoryId { get; set; }
[SitecoreField(FieldName = "Name")]
public virtual string CategoryName { get; set; }
}
Environment information:
- Sitecore 9.0.2
- GlassMapper 5.0.6.0
What am I missing, please?
Try SitecoreFieldType.DropLink. The DropList type stores string value. Your template need to change to droplink too.

Sitecore Glass Mapper ObjectToSwitchTo null reference in page editor

I have the following structure as Template in visual studio :
Under a page, i have one or more link root
[SitecoreType(TemplateId = "{4AAA9A10-36C2-484F-A648-2BEF349F0052}", AutoMap = true)]
public class LinkRoot : IBaseTemplate
{
[SitecoreChildren(InferType = true)]
public virtual IEnumerable<LinkItem> Children { get; set; }
[SitecoreInfo(SitecoreInfoType.TemplateId)]
public virtual Guid TemplateId { get; set; }
public Guid Id { get; set; }
public string Language { get; set; }
public ItemUri Uri { get; set; }
public int Version { get; private set; }
}
Under the link root i've LinkItems
[SitecoreType(AutoMap = true)]
public class LinkItem : IBaseTemplate
{
[SitecoreField("Link Name")]
public virtual string LinkName { get; set; }
[SitecoreField("Link")]
public virtual Link Link { get; set; }
public Guid Id { get; set; }
public string Language { get; set; }
public ItemUri Uri { get; set; }
public int Version { get; private set; }
}
I display those items in a view like that :
#foreach (var link in Model.Children.Where(o => o.TemplateId.Equals(TemplateIDs.LinksRoot.Guid)))
{
foreach (var linkChildren in link.Children)
{
using (BeginRenderLink(linkChildren, x => x.Link, isEditable: true))
{
#Editable(linkChildren, x => x.LinkName)
}
}
}
It works great, i can see my links with the good name etc, but when i go to the page editor i got this error :
Value cannot be null. Parameter name: objectToSwitchTo
at Sitecore.Diagnostics.Assert.ArgumentNotNull(Object argument, String argumentName)
at Sitecore.Common.Switcher2.Enter(TValue objectToSwitchTo)
at Sitecore.Data.Items.ContextItemSwitcher..ctor(Item item)
at Glass.Mapper.Sc.GlassHtml.MakeEditable[T](Expression1 field, Expression`1 standardOutput, T model, Object parameters, Context context, Database database, TextWriter writer)
Does someone already experienced that or have an idea why i have this error ?
Thanks
I think it means that Glass can't resolve the template of the LinkItem model.
Instead of:
[SitecoreType(AutoMap = true)]
public class LinkItem : IBaseTemplate
Try to explicitly define the template ID:
[SitecoreType(TemplateId = "{your-template-guid}", AutoMap = true)]
public class LinkItem : IBaseTemplate
I think this might be a bug, can you log it on github as an issue.

Asp.Net MVC 4.0 Model Localization With RegularExpression and ErrorMessage

How to create my custom RegularExpressionValidator that gets the RegularExpression and ErrorMessage from Resource file?
[RegularExpression(#"\d{5}(-\d{4})?",
ErrorMessageResourceType = typeof(Global), ErrorMessageResourceName = "regExpValforPostal_ErrorMessage")]
public string PostalCode { get; set; }
The resource file name is Global :
Global.resx, Global.zh.resx, Global.fr-ca.resx
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class LocalizedRegexAttribute : RegularExpressionAttribute
{
static LocalizedRegexAttribute()
{
// necessary to enable client side validation
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(LocalizedRegexAttribute), typeof(RegularExpressionAttributeAdapter));
}
public LocalizedRegexAttribute(string _RegularExpression, string _ErrorMessageResourceName, Type _ErrorMessageResourceType)
: base(LoadRegex(_RegularExpression))
{
ErrorMessageResourceType = _ErrorMessageResourceType;
ErrorMessageResourceName = _ErrorMessageResourceName;
}
private static string LoadRegex(string key)
{
var resourceManager = new ResourceManager(typeof(Water.Localization.Resources.Global));
return resourceManager.GetString(key);
}
In your model class you need to pass 3 parameters with the custom data
annotation as follows:
[LocalizedRegex("regExpValforPostal_ValidationExpression", "regExpValforPostal_ErrorMessage", typeof(Global))]
public string PostalCode { get; set; }
if your resources file called Validations.he.resx
and inside it you have both 'RegexExpression' and 'ErrorMessage' you should use this:
UPDATE #1: Option 2 added
Option 1:
public class LocalizedRegexAttribute :RegularExpressionAttribute
{
public LocalizedRegexAttribute () : base(Validations.RegexExpression)
{
}
public override string FormatErrorMessage(string name)
{
return base.FormatErrorMessage(ValidationStrings.ErrorMessage);
}
}
Option 2:
public class EmailAddressAttribute :ValidationAttribute {
public EmailAddressAttribute()
{
}
public override bool IsValid(object value)
{
Regex regex = new Regex(Validations.RegexExpression);
return regex.IsMatch(value.ToString());
}
public override string FormatErrorMessage(string name)
{
return base.FormatErrorMessage(ValidationStrings.ErrorMessage);
} }
than you will use it like this:
[LocalizedRegexAttribute]
public string PostalCode { get; set; }