ReferenceError: Offer is not defined - Hyperledger Composer IBM Blockchain - ibm-cloud

I'm following a tutorial for Hyperledger Composer on IBM Blockchain
I'm stuck in that error
This is the transaction:
{
"$class": "org.acme.vehicle.auction.Offer",
"bidPrice": 6000,
"listing": "resource:org.acme.vehicle.auction.VehicleListing#1304",
"member": "resource:org.acme.vehicle.auction.Member#alice#email.com"
}
1304 is an id of a VehicleListing and alice#mail.com is an user i've inserted before.
This is the transaction process function:
/**
* Make an Offer for a VehicleListing
* #param {org.acme.vehicle.auction.Offer} offer - the offer
* #transaction
*/
async function makeOffer(offer) {
let listing = offer.listing;
if (listing.state !== 'FOR_SALE') {
throw new Error('Listing is not FOR SALE');
}
if (!listing.offers) {
listing.offers = [];
}
listing.offers.push(offer);
const vehicleListingRegistry = await getAssetRegistry('org.acme.vehicle.auction.VehicleListing');
await vehicleListingRegistry.update(listing);
}
and this is the model:
asset VehicleListing identified by listingId {
o String listingId
o Double reservePrice
o String description
o ListingState state
o Offer[] offers optional
--> Vehicle vehicle
}
abstract participant User identified by email {
o String email
o String firstname
o String lastname
}
participant Member extends User {
o Double balance
}
transaction Offer {
o Double bidPrice
--> VehicleListing listing
--> Member member
}

Related

Loopback-next ReferenceMany Unable to find _id

I have user Model, which reference to ListedStocks,
#referencesMany(
() => ListedStocks,
{},
{
mongodb: {dataType: 'ObjectId'},
},
)
stockIds?: string[];
But when i try to Create a user and pass an Array of Stocks _id, it gives me an error.
Error: Entity not found: ListedStocks with id "constraint {\"_id\":[\"62eeb4b42b59f883f02f381b\"]}"
When i tried To Debug this, I saw this Query.
loopback:connector:mongodb all ListedStocks { where: { _id: [ '62eeb4b42b59f883f02f381b' ] } } null []
shouldn't where : {_id: []} be where: {_id: {$in: []}}
Or am i missing something?
The Issue was in the constraint created by the createReferencesManyAccessor
The constraint const constraint: any = {[primaryKey]: foreignKeyValue};
should have been constraint = {[primaryKey]: {inq:foreignKeyValue}};
Complete Fix is Below.
NOTE: This fix is specifically for the MongoDB, not sure if this breaks SQL Based Repos
export class TempReferencesManyRepository<
TargetEntity extends Entity,
TargetIds,
TargetRepository extends EntityCrudRepository<TargetEntity, TargetIds>,
> implements ReferencesManyRepository<TargetEntity>
{
/**
* Constructor of DefaultReferencesManyEntityCrudRepository
* #param getTargetRepository - the getter of the related target model repository instance
* #param constraint - the key value pair representing foreign key name to constrain
* #param foreignKeyValue - PrimaryKey for Constraint
* the target repository instance
*/
constructor(
public getTargetRepository: Getter<TargetRepository>,
public constraint: DataObject<TargetEntity>,
protected foreignKeyValue: any,
) {}
async get(options?: Options): Promise<TargetEntity> {
const targetRepo = await this.getTargetRepository();
const result = await targetRepo.find(
constrainFilter(undefined, this.constraint),
options,
);
let expectedLength = 1;
if(Array.isArray(this.foreignKeyValue)){
expectedLength = this.foreignKeyValue.length;
}
if (result.length !== expectedLength) {
// We don't have a direct access to the foreign key value here :(
const id = 'constraint ' + JSON.stringify(this.constraint);
throw new EntityNotFoundError(targetRepo.entityClass, id);
}
console.log("RESULT: ",result);
return result[0];
}
}
export function createReferencesManyAccessor<
Target extends Entity,
TargetIds,
Source extends Entity,
SourceId,
>(
referencesManyMetadata: ReferencesManyDefinition,
targetRepoGetter: Getter<EntityCrudRepository<Target, TargetIds>>,
sourceRepository: EntityCrudRepository<Source, SourceId>,
): ReferencesManyAccessor<Target, SourceId> {
const meta = resolveReferencesManyMetadata(referencesManyMetadata);
debug('Resolved ReferencesMany relation metadata: %o', meta);
const result: ReferencesManyAccessor<Target, SourceId> =
async function getTargetInstancesOfReferencesMany(sourceId: SourceId) {
const foreignKey = meta.keyFrom;
const primaryKey = meta.keyTo;
const sourceModel = await sourceRepository.findById(sourceId);
const foreignKeyValue = sourceModel[foreignKey as keyof Source];
// workaround to check referential integrity.
// should be removed once the memory connector ref integrity is done
// GH issue: https://github.com/loopbackio/loopback-next/issues/2333
if (!foreignKeyValue) {
return undefined as unknown as Target;
}
//FIXME: This is MONGODB Specific Fix, MIGHT BROKE in SQL.
// eslint-disable-next-line #typescript-eslint/no-explicit-any
let constraint: any = {[primaryKey]: foreignKeyValue};
if(Array.isArray(foreignKeyValue)){
constraint = {[primaryKey]: {inq: foreignKeyValue}};
}
const constrainedRepo = new TempReferencesManyRepository(
targetRepoGetter,
constraint as DataObject<Target>,
foreignKeyValue
);
return constrainedRepo.get();
};
result.inclusionResolver = createReferencesManyInclusionResolver(
meta,
targetRepoGetter,
);
return result;
}
export default class ReferenceManyCurdRepository<
T extends Entity,
ID,
Relations extends object = {}
> extends DefaultCrudRepository<T, ID, Relations>{
protected createReferencesManyAccessorFor<Target extends Entity, TargetId>(
relationName: string,
targetRepoGetter: Getter<EntityCrudRepository<Target, TargetId>>,
): ReferencesManyAccessor<Target, ID> {
const meta = this.entityClass.definition.relations[relationName];
return createReferencesManyAccessor<Target, TargetId, T, ID>(
meta as ReferencesManyDefinition,
targetRepoGetter,
this,
);
}
}

Multiple Roles causing issues in Blazor

I add roles via the following code in the server project
foreach (var userRole in userInfo.Roles)
{
claims.Add(new Claim(ClaimTypes.Role, userRole));
}
var token = new JwtSecurityToken(
_config["JWTSettings:validIssuer"],
_config["JWTSettings:validAudience"],
claims,
null,
expires: DateTime.Now.AddMinutes(20),
signingCredentials: credentials);
Now, If I only have one role then this works fine and the following code in the OnInitializedAsync method in a razor component
var t = await AuthState;
var role1= t.User.IsInRole("admin");
leads to role1 being true.
However if I have multiple roles then role1 is then false (as it is for all the roles I add to the user) despite clearly being there!
Now, if I do the following in OnInitializedAsync
var t= await AuthState;
var claimsList= t.User.Claims;
foreach(var item in claimsList)
{
var s1 = item.Type;
var s2 = item.Value;
string asasas = string.Empty;
}
I get a single claim that has a type of role and it has the following as its value
["admin","myrole2"]
on the server side I get a number of claims of type role, each with a single role as the value.
What on earth is going on?
You need to transform your claims:
public class CustomUserFactory : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
public CustomUserFactory(IAccessTokenProviderAccessor accessor)
: base(accessor)
{
}
public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
RemoteUserAccount account,
RemoteAuthenticationUserOptions options)
{
var user = await base.CreateUserAsync(account, options);
ClaimsIdentity claimsIdentity = (ClaimsIdentity)user.Identity;
if (account is not null) {
MapArrayClaimsToMultipleSeparateClaims(account, claimsIdentity);
}
return user;
}
private void MapArrayClaimsToMultipleSeparateClaims(RemoteUserAccount account, ClaimsIdentity claimsIdentity)
{
foreach (var keyValuePair in account.AdditionalProperties) {
var key = keyValuePair.Key;
var value = keyValuePair.Value;
if (value is not null &&
value is JsonElement element && element.ValueKind == JsonValueKind.Array) {
claimsIdentity.RemoveClaim(claimsIdentity.FindFirst(keyValuePair.Key));
var claims = element.EnumerateArray()
.Select(x => new Claim(keyValuePair.Key, x.ToString()));
claimsIdentity.AddClaims(claims);
}
}
}
}
Program.cs in your client.
services.AddApiAuthorization().AddAccountClaimsPrincipalFactory<CustomUserFactory>();

Salesforce Test class getting System.JSONException: Malformed JSON: Expected '[' at the beginning of List/Set Error

I have written a trigger and handler class (which uses a future method) and it works well. I am really struggling to write the test class for it. My first problem is that the code coverage is high (before tweaks it was 0% in the handler class). After tweaking, I am getting the following error:
System.JSONException: Malformed JSON: Expected '[' at the beginning of List/Set
I am not sure how to solve this and I am a pretty new dev so not sure what to do next . Can someone please help?
Here is my code:
Trigger:
trigger AccountTriggerKeepAcctTeam on Account (before update) {
List<AccountTeamMember> listAcc = [SELECT Id, AccountAccessLevel, AccountId, CaseAccessLevel, UserId, ContactAccessLevel, OpportunityAccessLevel, TeamMemberRole, PhotoUrl, Title FROM AccountTeamMember Where AccountId in : Trigger.new AND TeamMemberRole != 'Account Executive'];
for(Account acc: Trigger.new){
Account oldAccount = Trigger.oldMap.get(acc.Id);
if(acc.OwnerId != oldAccount.OwnerId){
system.debug('AccountTeamMember records: '+(JSON.serialize(listAcc)));
String str = JSON.serialize(listAcc);
//delete team member records if required
AccountTriggerKeepAcctTeamHandler.retainOldTeamMemberOnOwnerChange(str);
}
}
}
Handler:
public class AccountTriggerKeepAcctTeamHandler {
#future
public static void retainOldTeamMemberOnOwnerChange(String str){
system.debug('Future call '+str);
List<AccountTeamMember> newlistAcc = (List<AccountTeamMember>) JSON.deserialize(str,List<AccountTeamMember>.class);
for(AccountTeamMember objAccTeamMember : newlistAcc){
objAccTeamMember.Id= null;
}
system.debug('Account records to insert'+(JSON.serialize(newlistAcc)));
Upsert newlistAcc;
}
}
Test class:
#isTest
public class AccountTriggerKeepAcctTeamTest {
//#TestSetup
static testMethod void AcctOwnerChange(){
List<User> userList = TestDataFactory.createUser(true, 2);
Profile p = [SELECT Id FROM Profile WHERE Name='System Administrator'];
User u1 = new User(Alias = 'standt', Email='standarduser#testorg1.com',
EmailEncodingKey='UTF-8', LastName='Testing', LanguageLocaleKey='en_US',
LocaleSidKey='en_US', ProfileId = p.Id,
TimeZoneSidKey='America/Los_Angeles', UserName='standarduser#testorg01.com');
insert u1;
User u2 = new User(Alias = 'standt', Email='standarduser#testorg2.com',
EmailEncodingKey='UTF-8', LastName='Testing2', LanguageLocaleKey='en_US',
LocaleSidKey='en_US', ProfileId = p.Id,
TimeZoneSidKey='America/Los_Angeles', UserName='standarduser#testorg92.com');
insert u2;
System.runAs(u1){
Allowed_Account_Owner_Change__c setting = new Allowed_Account_Owner_Change__c();
setting.Allowed_to_change_Account_Owner__c = true;
insert setting;
fferpcore__ExchangeRateGroup__c exrg = new fferpcore__ExchangeRateGroup__c(CurrencyISOCode = 'USD', fferpcore__DefaultCurrency__c = 'USD - U.S. Dollar', Name = 'FF Shared Test Group', fferpcore__SelectedCurrencies__c = 'USD - U.S. Dollar');
insert exrg;
c2g__codaCompany__c company = new c2g__codaCompany__c();
company.Name = 'ApexTestCompany';
company.RecordTypeId = Schema.SObjectType.c2g__codaCompany__c.RecordTypeInfosByName.get('SUT').RecordTypeId;
insert company;
company.c2g__ExchangeRateGroup__c = exrg.Id;
update company;
Account acc = new Account(Name = 'Test Acc2', NumberOfEmployees = 500);//TestDataFactory.createAccountwithCurrencyMaster(true);
acc.OwnerId = userinfo.getUserId();
insert acc;
AccountTeamMember accTeam = new AccountTeamMember();
accTeam.UserId = acc.OwnerId;
accTeam.AccountAccessLevel = 'Read';
accTeam.AccountId = acc.Id;
insert accTeam;
System.debug('## User '+userinfo.getUserName());
//create opportunity
//}
// }
// static testMethod void testAcctOwnerChange(){
// User u = [Select id, LastName from User where LastName = 'Testing2'];
// Account acc = [Select id, OwnerId from Account Limit 1];
Test.startTest();
acc.OwnerId = u2.id;
update acc;
AccountTriggerKeepAcctTeamHandler.retainOldTeamMemberOnOwnerChange(JSON.serialize(acc));
//System.assertEquals(accTeam.UserId,u2.Id);
Test.stopTest();
//AccountTeamMember atm = [Select userId,AccountId, AccountAccessLevel from AccountTeamMember where AccountId =: acc.Id];
}
}
Can someone please help? I am a beginner apex dev
Trigger Attempt for Alin's test class:
trigger AccountTriggerKeepAcctTeam on Account (before update) {
Set<Id> accteammbrId = new Set<Id>();
List<AccountTeamMember> listAcc = [SELECT Id, AccountAccessLevel, AccountId, CaseAccessLevel, UserId, ContactAccessLevel, OpportunityAccessLevel, TeamMemberRole, PhotoUrl, Title FROM AccountTeamMember Where AccountId in : Trigger.new AND TeamMemberRole != 'Account Executive'];
for(Account acc: Trigger.new){
Account oldAccount = Trigger.oldMap.get(acc.Id);
if(acc.OwnerId != oldAccount.OwnerId){
//system.debug('AccountTeamMember records: '+(JSON.serialize(listAcc)));
//String str = JSON.serialize(listAcc);
for(AccountTeamMember actmbr : listAcc){
accteammbrId.add(actmbr.Id);
}
//delete team member records if required
AccountTriggerKeepAcctTeamHandler.retainOldTeamMemberOnOwnerChange(accteammbrId);
}
}
}
public class AccountTriggerKeepAcctTeamHandler {
#future
public static void retainOldTeamMemberOnOwnerChange(Set<Id> AccteambrId){
List<AccountTeamMember> newlistAcc = [SELECT Id, AccountAccessLevel, AccountId, CaseAccessLevel, UserId, ContactAccessLevel, OpportunityAccessLevel, TeamMemberRole, PhotoUrl, Title FROM AccountTeamMember Where AccountId in :AccteambrId];
//system.debug('Future call '+str);
//List<AccountTeamMember> newlistAcc = (List<AccountTeamMember>) JSON.deserialize(str,List<AccountTeamMember>.class);
for(AccountTeamMember objAccTeamMember : newlistAcc){
objAccTeamMember.Id= null;
}
//system.debug('Account records to insert'+(JSON.serialize(newlistAcc)));
system.debug('Account records to insert' + newlistAcc);
Upsert newlistAcc;
}
}
I don't understand why you have to serialize the list in the first place.
Change your retainOldTeamMemberOnOwnerChange to accept a List as parameter and don't use the JSON at all.
Then in your test class you create an account, assign an account team to it and change the owner. After that you will query the List of associated AccountTeamMember and assert it with regards to the AccountTeamMember before the ownership of the account was changed.
If it's not clear enough I can give you a code example, but I think the exercise will be good for you.
Edit: I see. My bad. This is what I use for test coverage. I can also provide code sample from the trigger if needed.
Trigger:
trigger AccountTrigger on Account (before update, After Update) {
if(trigger.isBefore){
if(trigger.isUpdate){
AccountTriggerHandler.retainAccountTeam(trigger.new, trigger.oldMap);
}
}
}
Handler:
public class AccountTriggerHandler {
public static void retainAccountTeam(List<Account> newList, Map<Id, Account> oldMap) {
Set<Id> accountsWithNewOwners = new Set<Id>();
for(Account acc : newList){
if(acc.OwnerId != oldMap.get(acc.Id).OwnerId){
accountsWithNewOwners.add(acc.Id);
}
}
if(accountsWithNewOwners.size() > 0){
List<AccountTeamMember> listAcc = [SELECT Id, AccountAccessLevel, AccountId, CaseAccessLevel, UserId, ContactAccessLevel, OpportunityAccessLevel, TeamMemberRole, PhotoUrl, Title FROM AccountTeamMember WHERE AccountId IN :accountsWithNewOwners];
system.debug('AccountTeamMember records: '+(JSON.serialize(listAcc)));
String str = JSON.serialize(listAcc);
// delete team member records if required
retainOldTeamMemberOnOwnerChange(str);
}
}
//Before Update calls the future method that basically reinserts the Account Team member records. You can also consider deleting the Account Team Member records before calling the future method.
#future
public static void retainOldTeamMemberOnOwnerChange(String str){
system.debug('Future call '+str);
List<AccountTeamMember> newlistAcc = (List<AccountTeamMember>) JSON.deserialize(str,List<AccountTeamMember>.class);
for(AccountTeamMember objAccTeamMember : newlistAcc){
objAccTeamMember.Id= null;
}
system.debug('Account records to insert'+(JSON.serialize(newlistAcc)));
Upsert newlistAcc;
}
}
Test method:
static testMethod void test_retainAccountTeam(){
Id mhbkRTId = Schema.SObjectType.Account.getRecordTypeInfosByName().get('MHBK').getRecordTypeId();
Profile p = [SELECT Id FROM Profile WHERE Name='System Administrator'];
User u = new User(
Alias = 'rivacon',
Email = 'rivaConnector#testorg.com',
EmailEncodingKey = 'UTF-8',
LastName = 'Connector',
FirstName = 'Riva',
LanguageLocaleKey = 'en_US',
LocaleSidKey = 'en_US',
ProfileId = p.Id,
TimeZoneSidKey = 'America/Los_Angeles',
UserName = 'rivaConnector#testorg.com');
insert u;
User u2 = new User(
Alias = 'updated',
Email = 'rivaConnector#testorg.com',
EmailEncodingKey = 'UTF-8',
LastName = 'Connector',
FirstName = 'Riva2',
LanguageLocaleKey = 'en_US',
LocaleSidKey = 'en_US',
ProfileId = p.Id,
TimeZoneSidKey = 'America/Los_Angeles',
UserName = 'rivaConnector2#testorg.com');
insert u2;
system.runAs(u){
Account acc = new Account(RecordTypeId = mhbkRTId,Name = 'test',Branch__c='London',Industry='Banking',Year_Founded__c='1900',BillingStreet = '1 Long Rd',BillingCity = 'City',CurrencyIsoCode = 'GBP');
insert acc;
AccountTeamMember atm = new AccountTeamMember();
atm.AccountId = acc.Id;
//atm.Name ='Test ATM';
atm.TeamMemberRole = 'Sales Assistant';
atm.UserId = u.Id;
insert atm;
Test.startTest();
acc.OwnerId = u2.Id;
update acc;
Test.stopTest();
System.debug('usr1 ' + u.id);
System.debug('usr2 ' + u2.id);
Account updatedAccount = [SELECT OwnerId FROM Account WHERE Id = :acc.Id];
system.assertEquals(u2.Id, updatedAccount.OwnerId);
List<AccountTeamMember> teamMembers = [SELECT Id, AccountAccessLevel, AccountId, UserId, TeamMemberRole, Title FROM AccountTeamMember WHERE AccountId = :acc.Id];
system.assertEquals(1, teamMembers.size());
system.assertEquals(teamMembers[0].TeamMemberRole, atm.TeamMemberRole);
}

Updating related Phone entities with custom tag helper

As my application currently sits, each AppUser may (or may not) have 3 phone numbers (UserPhones). One of each type (Mobile, Home, Other).
The following Tag Helper works great (Thanks #itminus).
Calling code from Razor Page:
<user-phones phones="#Model.UserPhones"
asp-for="#Model.UserPhones"
prop-name-to-edit="PhoneNumber"
types-to-edit="new EnumPhoneType[] { EnumPhoneType.Mobile,
EnumPhoneType.Other }" />
Code:
public class UserPhonesTagHelper : TagHelper
{
private readonly IHtmlGenerator _htmlGenerator;
private const string ForAttributeName = "asp-for";
[HtmlAttributeName("expression-filter")]
public Func<string, string> ExpressionFilter { get; set; } = e => e;
public List<UserPhones> Phones { get; set; }
public EnumPhoneType[] TypesToEdit { get; set; }
public string PropNameToEdit { get; set; }
[ViewContext]
public ViewContext ViewContext { set; get; }
[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; }
public UserPhonesTagHelper(IHtmlGenerator htmlGenerator)
{
_htmlGenerator = htmlGenerator;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.TagName = null; //DO NOT WANT AN OUTTER HTML ELEMENT
for (int i = 0; i < Phones.Count(); i++)
{
var props = typeof(UserPhones).GetProperties();
var pType = props.Single(z => z.Name == "Type");
var pTypeVal = pType.GetValue(Phones[i]);
EnumPhoneType eType = (EnumPhoneType) Enum.Parse(typeof(EnumPhoneType), pTypeVal.ToString());
string lVal = null;
switch (eType)
{
case EnumPhoneType.Home:
lVal = "Home Phone";
break;
case EnumPhoneType.Mobile:
lVal = "Mobile Phone";
break;
case EnumPhoneType.Other:
lVal = "Other Phone";
break;
default:
break;
}
//LOOP ALL PROPERTIES
foreach (var pi in props)
{
var v = pi.GetValue(Phones[i]);
var expression = this.ExpressionFilter(For.Name + $"[{i}].{pi.Name}");
var explorer = For.ModelExplorer.GetExplorerForExpression(typeof(IList<UserPhones>), o => v);
//IF REQUESTED TYPE AND PROPERTY SPECIFIED
if (pi.Name.NormalizeString() == PropNameToEdit.NormalizeString() && TypesToEdit.Contains(eType))
{
TagBuilder gridItem = new TagBuilder("div");
gridItem.Attributes.Add("class", "rvt-grid__item");
gridItem.InnerHtml.AppendHtml(BuildLabel(explorer, expression, lVal));
gridItem.InnerHtml.AppendHtml(BuildTextBox(explorer, expression, v.ToString()));
output.Content.AppendHtml(gridItem);
}
else //ADD HIDDEN FIELD SO BOUND PROPERLY
output.Content.AppendHtml(BuildHidden(explorer, expression, v.ToString()));
}
}
}
private TagBuilder BuildTextBox(ModelExplorer explorer, string expression, string v)
{
return _htmlGenerator.GenerateTextBox(ViewContext, explorer, expression, v, null, new { #class = "form-control" });
}
public TagBuilder BuildHidden(ModelExplorer explorer, string expression, string v)
{
return _htmlGenerator.GenerateHidden(ViewContext, explorer, expression, v, false, new { });
}
public TagBuilder BuildLabel(ModelExplorer explorer, string expression, string v)
{
return _htmlGenerator.GenerateLabel(ViewContext, explorer, expression, v, new { });
}
}
My Question:
Lets assume this AppUser only has one related Mobile phone number listed currently. So AppUser.UserPhones (count = 1 of type Mobile). So the code above, as-is, will only render an input for Mobile phone.
Since types-to-edit calls for both Mobile and Other, I want both inputs to be rendered to the screen. And IF the user adds a phone number to the Other input, then it would be saved to the related UserPhones entity on the Razor Pages OnPostAsync method. If the user does NOT provide a number for the "Other" input, then the related UserPhones record of type "Other" should NOT be created.
Can you help?
Thanks again!!!!
TagHelper
As my application currently sits, each AppUser may (or may not) have 3 phone numbers (UserPhones). One of each type (Mobile, Home, Other).
If I understand correctly, an AppUser might have 3 phone numbers and the count of each phone type for every user will be zero or one.
If that's the case, we can simply use PhoneType as an index, in other words, there's no need to use a custom index to iterate through the Phones property, and the ProcessAsync() method could be :
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.TagName = null; //DO NOT WANT AN OUTTER HTML ELEMENT
var props = typeof(UserPhones).GetProperties();
// display editable tags for phones
foreach (var pt in this.TypesToEdit) {
var phone = Phones.SingleOrDefault(p=>p.Type == pt);
var index = (int) pt;
foreach (var pi in props)
{
// if phone==null , then the pv should be null too
var pv = phone==null? null: pi.GetValue(phone);
var tag = GenerateFieldForProperty(pi.Name, pv, index, pt);
output.Content.AppendHtml(tag);
}
}
// generate hidden input tags for phones
var phones= Phones.Where(p => !this.TypesToEdit.Contains((p.Type)));
foreach (var p in phones) {
var index = (int)p.Type;
foreach (var pi in props) {
var pv = pi.GetValue(p);
var tag = GenerateFieldForProperty(pi.Name,pv,index,p.Type);
output.Content.AppendHtml(tag);
}
}
}
Here the GenerateFieldForProperty is a simply helper method to generate tag builder for particular property:
private TagBuilder GenerateFieldForProperty(string propName,object propValue,int index, EnumPhoneType eType )
{
// whether current UserPhone is editable (check the PhoneType)
var editable = TypesToEdit.Contains(eType);
var expression = this.ExpressionFilter(For.Name + $"[{index}].{propName}");
var explorer = For.ModelExplorer.GetExplorerForExpression(typeof(IList<UserPhones>), o => propValue);
//IF REQUESTED TYPE AND PROPERTY SPECIFIED
if (pi.Name.NormalizeString() == PropNameToEdit.NormalizeString() && editable)
{
TagBuilder gridItem = new TagBuilder("div");
gridItem.Attributes.Add("class", "rvt-grid__item");
var labelText = this.GetLabelTextByPhoneType(eType);
gridItem.InnerHtml.AppendHtml(BuildLabel(explorer, expression, labelText));
gridItem.InnerHtml.AppendHtml(BuildTextBox(explorer, expression, propValue?.ToString()));
return gridItem;
}
else //ADD HIDDEN FIELD SO BOUND PROPERLY
return BuildHidden(explorer, expression, propValue?.ToString());
}
private string GetLabelTextByPhoneType(EnumPhoneType eType) {
string lVal = null;
switch (eType)
{
case EnumPhoneType.Home:
lVal = "Home Phone";
break;
case EnumPhoneType.Mobile:
lVal = "Mobile Phone";
break;
case EnumPhoneType.Other:
lVal = "Other Phone";
break;
default:
break;
}
return lVal;
}
When posted to server, if someone doesn't input a phone number for the other PhoneType, the actual payload will be something like:
AppUser.UserPhones[0].UserPhoneId=....&AppUser.UserPhones[0].PhoneNumber=911&....
&AppUser.UserPhones[2].UserPhoneId=&AppUser.UserPhones[2].PhoneNumber=&AppUser.UserPhones[2].Type=&AppUser.UserPhones[2].AppUserId=&AppUser.UserPhones[2].AppUser=
&AppUser.UserPhones[1].UserPhoneId=...&AppUser.UserPhones[1].PhoneNumber=119&....
Since we use phone type as the index, we can conclude that the UserPhones[0] will be used as an Mobile phone and the UserPhones[2] will be treated as an Home phone.
page handler or action method
And the model binder on server side will create a empty string for each UserPhone.
To remove those empty inputs and prevent overposting attack, we could use Linq to filter UserPhones so that we can create or update UserPhone records without empty Phones:
var editables = new[] {
EnumPhoneType.Mobile,
EnumPhoneType.Other,
};
AppUser.UserPhones = AppUser.UserPhones
.Where(p => !string.IsNullOrEmpty(p.PhoneNumber)) // remove empty inputs
.Where(p => editables.Contains(p.Type) ) // remove not editable inputs
.ToList();
// now the `UserPhones` will be clean for later use
// ... create or update user phones as you like
Let's say you want to create phones :
public IActionResult OnPostCreate() {
var editables = new[] {
EnumPhoneType.Mobile,
EnumPhoneType.Other,
};
AppUser.UserPhones = AppUser.UserPhones
.Where(p => !string.IsNullOrEmpty(p.PhoneNumber))
.Where(p => editables.Contains(p.Type) )
.Select(p => { // construct relationship for inputs
p.AppUser = AppUser;
p.AppUserId = AppUser.Id;
return p;
})
.ToList();
this._dbContext.Set<UserPhones>().AddRange(AppUser.UserPhones);
this._dbContext.SaveChanges();
return Page();
}
Test Case :
<form method="post">
<div class="row">
<user-phones
phones="#Model.AppUser.UserPhones"
asp-for="#Model.AppUser.UserPhones"
prop-name-to-edit="PhoneNumber"
types-to-edit="new EnumPhoneType[] { EnumPhoneType.Mobile, EnumPhoneType.Other}"
>
</user-phones>
</div>
<button type="submit">submit</button>
</form>
User1 who has Mobile phone and Home phone number:
User2 who wants to create a new Mobile phone number :

Salesforce Trigger to populate a lookup field

I am trying to create salesforce trigger on Lead that auto-populates a look up field which links the current Lead to an existing Account if there exist an Account with the same name as the Lead's Company custom field.
This is my code:
trigger Link_Lead_To_Account on Lead (before insert ) {
Set<String> whatIDs = new Set<String>();
MAP<id,String> accountMap= new MAP<id,String>();
// save the leads that have been triggered
for (Lead l : Trigger.new) {
whatIDs.add(l.id);
}
List<Lead> leads = [SELECT Id,Company FROM Lead where ID=:whatIDs ];
// loop through the triggered leads, if the account.name == to lead.company then link the found account to the lead
for (Integer i = 0; i <Trigger.new.size(); i++)
{
// System.Debug('++++++++++++++'+Trigger.new[i].company+Trigger.new[i].id);
if(accountMap.get(Trigger.new[i].company)!=null)
{
for(Account ac :[Select name,id from Account])
{
if(Trigger.new[i].Company==ac.Name)
{
Trigger.new[i].Account__c= ac.id;
break;
}
}
}
// System.Debug('Trigger.new[i].Account__c::::'+Trigger.new[i].Account__c);
// System.Debug('Trigger.new[i].company:::::'+Trigger.new[i].company);
// System.Debug('Trigger.new[i].ID:::::'+Trigger.new[i].ID);
}
update leads;
}
But it doesn't work at all. It throws the following error:
Review all error messages below to correct your data.
Apex trigger Link_Lead_To_Account caused an unexpected exception, contact your administrator: Link_Lead_To_Account: execution of AfterInsert caused by: System.StringException: Invalid id: TestAccount2: External entry point
As it requires the Company field to be an ID, but when I write an ID it does't perform any changes.
I managed to fix it.
This is the working class where newLeads.Values() is being populated in the constructor with to the Trigger.new() values on the before insert event:
public void LinkLeadToAccount() {
Set<String> companies = new Set<String>();
for (Lead l: newLeads.values()) {
if (l.Company != null) companies.add(l.Company);
}
if (companies.size() > 0) {
// Pick most recent Account where more than one with same name
Map<String, Id> accountNameToId = new Map<String, Id>();
for (Account a : [
select Name, Id
from Account
where Name in :companies
order by CreatedDate
]) {
accountNameToId.put(a.Name, a.Id);
}
if (accountNameToId.size() > 0) {
Lead[] updates = new Lead[] {};
for (Lead l: newLeads.values()) {
if (l.Company != null) {
Id accountId = accountNameToId.get(l.Company);
if (accountId != null) {
updates.add(new Lead(Id = l.Id, Account__c = accountId));
}
}
}
System.debug(' leads_to_update : ' + updates.size() + ' leads_to_update : ' + updates);
update updates;
}
}
}