Trying to update Values in Standard Object from metaData - triggers

I am using Trigger isBefore
In System.debug(opp.get(metaData.get(0).Opportunity_Field_Name__c), it is showing correct Values but not Updating in Opportunity Object
Below is Trigger and its Apex Class Trigger
Trigger
trigger MetadataObjectFieldMapping on Opportunity (before insert, before update)
{
if(Trigger.isInsert || Trigger.isUpdate )
{
MetadataObjectFieldMappingHandler oppHandler = new MetadataObjectFieldMappingHandler();
oppHandler.Show(Trigger.new);
}
}
And Apex Class
public class MetadataObjectFieldMappingHandler {
List<String> strAccField = new List<String>();
//Getting List of MetaData Values
List<Object_Field_Mapping__mdt> metaData = new List<Object_Field_Mapping__mdt>
([SELECT Account_Field_Name__c,
Opportunity_Field_Name__c
FROM Object_Field_Mapping__mdt]);
//Function to check if Field Name Exists in Object or not
public Boolean hello(String objName, String fieldName)
{
Boolean temp = False;
//Creating Schema to get all fields from Account and Opportunity Object
Map<String, Schema.SObjectField> accFields = Schema.getGlobalDescribe().get(objName).getDescribe().fields.getMap();
for(Schema.SObjectField field : accFields.values())
{
strAccField.add(field+'');
}
//Calling Account and Opportunity Object in fieldName
if(strAccField.contains(fieldName)){
System.debug('PASS '+fieldName);
temp = true;
}
return temp;
}
public void Show(List<opportunity> newOppList)
{
Boolean test1 = hello('Account',metaData.get(0).Account_Field_Name__c);
Boolean test2 = hello('Opportunity',metaData.get(0).Opportunity_Field_Name__c);
//If both Field Value exists
if(test1 && test2){
//Getting value from Opp using dynamic Query
String query = 'Select Account.'+metaData.get(0).Account_Field_Name__c+', '+metaData.get(0).Opportunity_Field_Name__c+' from Opportunity where Id IN : newOppList ';
List<Opportunity> oppList =database.query(query);
for(Opportunity opp : oppList){
opp.put(
metaData.get(0).Opportunity_Field_Name__c,
opp.Account.get(metaData.get(0).Account_Field_Name__c)
);
System.debug(opp.get(metaData.get(0).Opportunity_Field_Name__c));
}
}
}
Can you please tell me, why Value is not Updating in Opportunity Object while it showing in Debug Logs..?

Multiple fails here I think.
Apex is case-insensitive when you do if('a' == 'A'). But when comparing Strings in collections (Lists, Sets, Map keys) it suddenly becomes case-sensitive.
List<String> fields = new List<String>{'Id', 'Name'};
System.debug(fields.contains('name')); // false
(this should have been a Set<String> by the way, for performance and logical readability). So I suspect something's fishy there, in case. You didn't show your metadata but check this one (you have only 1 row now, right? If you have more than one - we'll your metaData.get(0) essentially returns a random row).
I don't like the cast from Schema.SObjectField to String either.
Next: String query = 'Select Account.'+metaData.get(0).Account_Field_Name__c+', '+metaData.get(0).Opportunity_Field_Name__c+' from Opportunity where Id IN : newOppList ';
This has a chance of working in before update. But for sure not in before insert. Nothing's in database yet, your query will return zero results. You have to loop through trigger.new, collec AccountIds, query Accounts (directly, not via Opportunity table) and then make final loop that writes data.
You passed newOppList to Show(). If you want to get the save to database for free - you should modify values on the original, on newOppList. Instead you modify the in-memory results of query (oppList). Nothing will happen to them, they'll be discarded. If you want to save them, you'd have to do it manually (but then you risk entering a loop of update triggers and SF will stop you).
You sure this has to be code? Sounds like a job for workflow or process builder. Or make them formula fields so you always display fresh value instead of such copying... When something changes on Account it won't automatically cascade down to all opps unless you make next trigger/process builder...

Related

Salesforce trigger-Not able to understand

Below is the code written by my collegue who doesnt work in the firm anymore. I am inserting records in object with data loader and I can see success message but I do not see any records in my object. I am not able to understand what below trigger is doing.Please someone help me understand as I am new to salesforce.
trigger DataLoggingTrigger on QMBDataLogging__c (after insert) {
Map<string,Schema.RecordTypeInfo> recordTypeInfo = Schema.SObjectType.QMB_Initial_Letter__c.getRecordTypeInfosByName();
List<QMBDataLogging__c> logList = (List<QMBDataLogging__c>)Trigger.new;
List<Sobject> sobjList = (List<Sobject>)Type.forName('List<'+'QMB_Initial_Letter__c'+'>').newInstance();
Map<string, QMBLetteTypeToVfPage__c> QMBLetteTypeToVfPage = QMBLetteTypeToVfPage__c.getAll();
Map<String,QMBLetteTypeToVfPage__c> mapofLetterTypeRec = new Map<String,QMBLetteTypeToVfPage__c>();
set<Id>processdIds = new set<Id>();
for(string key : QMBLetteTypeToVfPage.keyset())
{
if(!mapofLetterTypeRec.containsKey(key)) mapofLetterTypeRec.put(QMBLetteTypeToVfPage.get(Key).Letter_Type__c, QMBLetteTypeToVfPage.get(Key));
}
for(QMBDataLogging__c log : logList)
{
Sobject logRecord = (sobject)log;
Sobject QMBLetterRecord = new QMB_Initial_Letter__c();
if(mapofLetterTypeRec.containskey(log.Field1__c))
{
string recordTypeId = recordTypeInfo.get(mapofLetterTypeRec.get(log.Field1__c).RecordType__c).isAvailable() ? recordTypeInfo.get(mapofLetterTypeRec.get(log.Field1__c).RecordType__c).getRecordTypeId() : recordTypeInfo.get('Master').getRecordTypeId();
string fieldApiNames = mapofLetterTypeRec.containskey(log.Field1__c) ? mapofLetterTypeRec.get(log.Field1__c).FieldAPINames__c : '';
//QMBLetterRecord.put('Letter_Type__c',log.Name);
QMBLetterRecord.put('RecordTypeId',tgh);
processdIds.add(log.Id);
if(string.isNotBlank(fieldApiNames) && fieldApiNames.contains(','))
{
Integer i = 1;
for(string fieldApiName : fieldApiNames.split(','))
{
string logFieldApiName = 'Field'+i+'__c';
fieldApiName = fieldApiName.trim();
system.debug('fieldApiName=='+fieldApiName);
Schema.DisplayType fielddataType = getFieldType('QMB_Initial_Letter__c',fieldApiName);
if(fielddataType == Schema.DisplayType.Date)
{
Date dateValue = Date.parse(string.valueof(logRecord.get(logFieldApiName)));
QMBLetterRecord.put(fieldApiName,dateValue);
}
else if(fielddataType == Schema.DisplayType.DOUBLE)
{
string value = (string)logRecord.get(logFieldApiName);
Double dec = Double.valueOf(value.replace(',',''));
QMBLetterRecord.put(fieldApiName,dec);
}
else if(fielddataType == Schema.DisplayType.CURRENCY)
{
Decimal decimalValue = Decimal.valueOf((string)logRecord.get(logFieldApiName));
QMBLetterRecord.put(fieldApiName,decimalValue);
}
else if(fielddataType == Schema.DisplayType.INTEGER)
{
string value = (string)logRecord.get(logFieldApiName);
Integer integerValue = Integer.valueOf(value.replace(',',''));
QMBLetterRecord.put(fieldApiName,integerValue);
}
else if(fielddataType == Schema.DisplayType.DATETIME)
{
DateTime dateTimeValue = DateTime.valueOf(logRecord.get(logFieldApiName));
QMBLetterRecord.put(fieldApiName,dateTimeValue);
}
else
{
QMBLetterRecord.put(fieldApiName,logRecord.get(logFieldApiName));
}
i++;
}
}
}
sobjList.add(QMBLetterRecord);
}
if(!sobjList.isEmpty())
{
insert sobjList;
if(!processdIds.isEmpty()) DeleteDoAsLoggingRecords.deleteTheProcessRecords(processdIds);
}
Public static Schema.DisplayType getFieldType(string objectName,string fieldName)
{
SObjectType r = ((SObject)(Type.forName('Schema.'+objectName).newInstance())).getSObjectType();
DescribeSObjectResult d = r.getDescribe();
return(d.fields.getMap().get(fieldName).getDescribe().getType());
}
}
You might be looking in the wrong place. Check if there's an unit test written for this thing (there should be one, especially if it's deployed to production), it should help you understand how it's supposed to be used.
You're inserting records of QMBDataLogging__c but then it seems they're immediately deleted in DeleteDoAsLoggingRecords.deleteTheProcessRecords(processdIds). Whether whatever this thing was supposed to do succeeds or not.
This seems to be some poor man's CSV parser or generic "upload anything"... that takes data stored in QMBDataLogging__c and creates QMB_Initial_Letter__c out of it.
QMBLetteTypeToVfPage__c.getAll() suggests you could go to Setup -> Custom Settings, try to find this thing and examine. Maybe it has some values in production but in your sandbox it's empty and that's why essentially nothing works? Or maybe some values that are there are outdated?
There's some comparison if what you upload into Field1__c can be matched to what's in that custom setting. I guess you load some kind of subtype of your QMB_Initial_Letter__c in there. Record Type name and list of fields to read from your log record is also fetched from custom setting based on that match.
Then this thing takes what you pasted, looks at the list of fields in from the custom setting and parses it.
Let's say the custom setting contains something like
Name = XYZ, FieldAPINames__c = 'Name,SomePicklist__c,SomeDate__c,IsActive__c'
This thing will look at first record you inserted, let's say you have the CSV like that
Field1__c,Field2__c,Field3__c,Field4__c
XYZ,Closed,2022-09-15,true
This thing will try to parse and map it so eventually you create record that a "normal" apex code would express as
new QMB_Initial_Letter__c(
Name = 'XYZ',
SomePicklist__c = 'Closed',
SomeDate__c = Date.parse('2022-09-15'),
IsActive__c = true
);
It's pretty fragile, as you probably already know. And because parsing CSV is an art - I expect it to absolutely crash and burn when text with commas in it shows up (some text,"text, with commas in it, should be quoted",more text).
In theory admin can change mapping in setup - but then they'd need to add new field anyway to the loaded file. Overcomplicated. I guess somebody did it to solve issue with Record Type Ids - but there are better ways to achieve that and still have normal CSV file with normal columns and strong type matching, not just chucking everything in as strings.
In theory this lets you have "jagged" csv files (row 1 having 5 fields, row 2 having different record type and 17 fields? no problem)
Your call whether it's salvageable or you'd rather ditch it and try normal loading of QMB_Initial_Letter__c records. (get back to your business people and ask for requirements?) If you do have variable number of columns at source - you'd need to standardise it or group the data so only 1 "type" of records (well, whatever's in that "Field1__c") goes into each file.

How to get rid of lots of soql queries using maps

Im finding it really hard to understand maps, I have read lots of examples but they always seem not to relate to what I need to do - I've just about understood how to use oldmap.trigger in a very simple use case.
So anyways I have a trigger on a object Data_ProtectionIA , its parent is Data Agreement, and then its parent is Data request.
I need to update Data agreement and Data request when DPIA gets updated, I have the below so far which I know is a very long winded way to do it, but now I also have to add further updates for when DPIA gets updated to something else and for me to do this like I have done would mean doubling this code again when it is already unnecesarily long.
If someone could please simplify this code into maps then I think I would be able to understand maps properly, I just cant find examples of what I need to do. Many thanks in advance
(So at the moment I am getting the grandchild record and then creating a list of parent records to loop through and then getting a list of all their parent records and then looping through that. - even if you could just show me from child to parent (not even, child parent to grandparent that would be very helpful)
public static void setDRStatus(List<Data_Protection_IA__c> dpia, Map<ID,Data_Protection_IA__c> oldMap ){
List<ID> childDAID = new List<ID>();
for(Data_Protection_IA__c pDA: dpia){
if(pDA.DPIA_Status__c == 'Fully Approved' && oldMap.get(pDA.Id).DPIA_Status__c != 'Fully Approved'){
childDAID.add(pDA.DataProcessingAgreement__c);
}
}
List<Data_Agreement__c> childDA = new List<Data_Agreement__c>();
ChildDA = [Select id, Data_Sharing_Status__c from Data_Agreement__c where id in:childDAID];
List<Data_Agreement__c> listToUpdate = new List <Data_Agreement__c>();
List<ID> dAId = new List <ID>();
system.debug('ChildDA'+ childDA);
for(Data_Agreement__c cda : ChildDA){
cda.Data_Sharing_Status__c = 'DPIA Fully Approved';
listToUpdate.add(cda);
dAId.add(cda.id);
}
update listToUpdate;
List<Data_Request__c> dRList = [SELECT id, Data_Sharing_Status__c,Existing_Data_Agreement__c
FROM Data_Request__c where Existing_Data_Agreement__c in:dAId];
List<Data_Request__c> listToUpdatedr = new List <Data_Request__c>();
system.debug('DRList'+ dRList);
for(Data_Request__c dr :dRList){
dr.Data_Sharing_Status__c = 'DPIA Approved';
listToUpdatedr.add(dr);
}
update listToUpdatedr;
}
----------------------------------------
Here is an example I had previously tried
So I started following an example and got to here
Set<Id> daIds = new Set<Id> {};
for (Data_Protection_IA__c p : dpia)
{
daIds.add(p.DataProcessingAgreement__c );
}
Map<ID, Data_Agreement__c> m = new Map<ID, Data_Agreement__c>(
[select id, from Data_Agreement__c where id in : daIds]);
list<Data_Agreement__c> dAupdates = new List <Data_Agreement__c>();
list<Contact> contactupdates = new List <Contact>();
for (Data_Protection_IA__c p1 :dpia)
{
m.get(p1.Data_Agreement__c) = 'DPIA Fully Approved');
}
but at the last line I think this map is tying to get the field Data_agreement from dpia object, but there is no data agreement field on dpia object. as dpia is the child. there is only a field for dpia on the data agreement object

How to fix FirstOrDefault returning Null in Linq

My Linq Query keeps returning the null error on FirstOrDefault
The cast to value type 'System.Int32' failed because the materialized value is null
because it can't find any records to match on the ClinicalAssetID form the ClinicalReading Table, fair enough!
But I want the fields in my details page just to appear blank if the table does not have matching entry.
But how can I handle the null issue when using the order by function ?
Current Code:
var ClinicalASSPATINCVM = (from s in db.ClinicalAssets
join cp in db.ClinicalPATs on s.ClinicalAssetID equals cp.ClinicalAssetID into AP
from subASSPAT in AP.DefaultIfEmpty()
join ci in db.ClinicalINSs on s.ClinicalAssetID equals ci.ClinicalAssetID into AI
from subASSINC in AI.DefaultIfEmpty()
join co in db.ClinicalReadings on s.ClinicalAssetID equals co.ClinicalAssetID into AR
let subASSRED = AR.OrderByDescending(subASSRED => subASSRED.MeterReadingDone).FirstOrDefault()
select new ClinicalASSPATINCVM
{
ClinicalAssetID = s.ClinicalAssetID,
AssetTypeName = s.AssetTypeName,
ProductName = s.ProductName,
ModelName = s.ModelName,
SupplierName = s.SupplierName,
ManufacturerName = s.ManufacturerName,
SerialNo = s.SerialNo,
PurchaseDate = s.PurchaseDate,
PoNo = s.PoNo,
Costing = s.Costing,
TeamName = s.TeamName,
StaffName = s.StaffName,
WarrantyEndDate = subASSPAT.WarrantyEndDate,
InspectionDate = subASSPAT.InspectionDate,
InspectionOutcomeResult = subASSPAT.InspectionOutcomeResult,
InspectionDocumnets = subASSPAT.InspectionDocumnets,
LastTypeofInspection = subASSINC.LastTypeofInspection,
NextInspectionDate = subASSINC.NextInspectionDate,
NextInspectionType = subASSINC.NextInspectionType,
MeterReadingDone = subASSRED.MeterReadingDone,
MeterReadingDue = subASSRED.MeterReadingDue,
MeterReading = subASSRED.MeterReading,
MeterUnitsUsed = subASSRED.MeterUnitsUsed,
FilterReplaced = subASSRED.FilterReplaced
}).FirstOrDefault(x => x.ClinicalAssetID == id);
Tried this but doesn't work
.DefaultIfEmpty(new ClinicalASSPATINCVM())
.FirstOrDefault()
Error was:
CS1929 'IOrderedEnumerable<ClinicalReading>' does not contain a definition for 'DefaultIfEmpty' and the best extension method overload 'Queryable.DefaultIfEmpty<ClinicalASSPATINCVM>(IQueryable<ClinicalASSPATINCVM>, ClinicalASSPATINCVM)' requires a receiver of type 'IQueryable<ClinicalASSPATINCVM>'
Feel a little closer with this but still errors
let subASSRED = AR.OrderByDescending(subASSRED => (subASSRED.MeterReadingDone != null) ? subASSRED.MeterReadingDone : String.Empty).FirstOrDefault()
Error:
CS0173 Type of conditional expression cannot be determined because there is no implicit conversion between 'System.DateTime?' and 'string'
The original error means that some of the following properties of the ClinicalASSPATINCVM class - MeterReadingDone, MeterReadingDue, MeterReading, MeterUnitsUsed, or FilterReplaced is of type int.
Remember that subASSRED here
let subASSRED = AR.OrderByDescending(subASSRED => subASSRED.MeterReadingDone).FirstOrDefault()
might be null (no corresponding record).
Now look at this part of the projection:
MeterReadingDone = subASSRED.MeterReadingDone,
MeterReadingDue = subASSRED.MeterReadingDue,
MeterReading = subASSRED.MeterReading,
MeterUnitsUsed = subASSRED.MeterUnitsUsed,
FilterReplaced = subASSRED.FilterReplaced
If that was LINQ to Objects, all these would generate NRE (Null Reference Exception) at runtime. In LINQ to Entities this is converted and executed as SQL. SQL has no issues with expression like subASSRED.SomeProperty because SQL supports NULL naturally even if SomeProperty normally does not allow NULL. So the SQL query executes normally, but now EF must materialize the result into objects, and the C# object property is not nullable, hence the error in question.
To solve it, find the int property(es) and use the following pattern inside query:
SomeIntProperty = (int?)subASSRED.SomeIntProperty ?? 0 // or other meaningful default
or change receiving object property type to int? and leave the original query as is.
Do the same for any non nullable type property, e.g. DateTime, double, decimal, Guid etc.
You're problem is because your DefaultIfEmpty is executed AsQueryable. Perform it AsEnumerable and it will work:
// create the default element only once!
static readonly ClinicalAssPatInVcm defaultElement = new ClinicalAssPatInVcm ();
var result = <my big linq query>
.Where(x => x.ClinicalAssetID == id)
.AsEnumerable()
.DefaultIfEmpty(defaultElement)
.FirstOrDefault();
This won't lead to a performance penalty!
Database management systems are extremely optimized for selecting data. One of the slower parts of a database query is the transport of the selected data to your local process. Hence it is wise to let the DBMS do most of the selecting, and only after you know that you only have the data that you really plan to use, move the data to your local process.
In your case, you need at utmost one element from your DBMS, and if there is nothing, you want to use a default object instead.
AsQueryable will move the selected data to your local process in a smart way, probably per "page" of selected data.
The page size is a good compromise: not too small, so you don't have to ask for the next page too often; not too large, so that you don't transfer much more items than you actually use.
Besides, because of the Where statement you expect at utmost one element anyway. So that a full "page" is fetched is no problem, the page will contain only one element.
After the page is fetched, DefaultIfEmpty checks if the page is empty, and if so, returns a sequence containing the defaultElement. If not, it returns the complete page.
After the DefaultIfEmpty you only take the first element, which is what you want.

What does this string of code: gr.sys_id[key] = current.getValue(glideElement.getName());

I'm trying to copy (duplicate) a record in ServiceNow table of incidents, but can not make this string work: gr.sys_id[key] = current.getValue(glideElement.getName());
The goal is to copy all fields values except sys_id.
Take a look at the UI Action Insert & Stay which is kind of a Duplicate Script.
You can use the same functionality in your Business rule or any other server side script:
doInsertAndStay();
function doInsertAndStay() {
var saveMe = current;
if (typeof current.number != 'undefined' && current.number){
current.number = ""; // generate a new number
}
current.insert();
action.setRedirectURL(saveMe);
}
The GlideRecord function insert() duplicates a record and of course a new sys_id is used for the new record. As far as I know you are not able to define the sys_id by your self.

Trigger works but test doesn't cover 75% of the code

I have a trigger which works in the sandbox. The workflow checks the field in the campaign level and compares it with the custom setting. If it matches, then it returns the target to the DS Multiplier field. The trigger looks as follows
trigger PopulateTarget on Campaign (before insert, before update)
{
for(Campaign campaign : Trigger.new)
{
if (String.isNotBlank(campaign.Apex_Calculator__c) == true)
{
DSTargets__c targetInstance = DSTargets__c.getInstance(campaign.Apex_Calculator__c);
{
String target = targetInstance .Target__c;
campaign.DS_Target_Multiplier__c = Target;
}
}
}
}
However, I had problems to write a proper test to this and asked for the help on the internet. I received the test
#isTest
private class testPopulateTarget{
static testMethod void testMethod1(){
// Load the Custom Settings
DSTargets__c testSetting = new DSTargets__c(Name='Africa - 10 Weeks; CW 10',Target__c='0.1538', SetupOwnerId = apexCalculatorUserId);
insert testSetting;
// Create Campaign. Since it would execute trigger, put it in start and stoptests
Test.startTest();
Campaign testCamp = new Campaign();
// populate all reqd. fields.
testCamp.Name = 'test DS campaign';
testCamp.RecordTypeId = '012200000001b3v';
testCamp.Started_Campaign_weeks_before_Event__c = '12 Weeks';
testCamp.ParentId= '701g0000000EZRk';
insert testCamp;
Test.stopTest();
testCamp = [Select ID,Apex_Calculator__c,DS_Target_Multiplier__c from Campaign where Id = :testCamp.Id];
system.assertEquals(testCamp.DS_Target_Multiplier__c,testSetting.Target__c);// assert that target is populated right
}
}
Such test returns the error "Compile Error: Variable does not exist: apexCalculatorUserId at line 6 column 122". If I remove that ApexCalculator part System.assertEquals then the test passes. However it covers 4/6 part of the code (which is 66%)
Could anyone help me how should I amend the code to make the coverage of 75%?
Yes, apexCalculatorUserId has not been defined. The code you were given appears to be incomplete. You'll need to look at the constructor DSTargets__c and see what kind of ID it is expecting there.
At a guess, you could try UserInfo.getUserId() to get the ID of the current user, but that may not be the ID that's expected in the constructor. It would be worth trying it to see if the test coverage improves.
1) Replace apexCalculatorUserId with UserInfo.getUserId()
2) I'm not sure what kind of field is Apex_Calculator__c on campaign. If its not a formula you want to insert a new line before "insert testCamp". Something like:
testCamp.Apex_Calculator__c = UserInfo.getUserId();