Apex Trigger Between Two Child Objects - triggers

I am in the early stages of learning Apex and trying to write some code for a particular scenario. I have two custom objects called Outstanding and Transaction that are both children of an Account. I want to write a trigger that updates the end date of the Outstanding record that has a start date >= the start date on the Transaction and an end date < the end date on the Transaction. I want the end date on the correct Outstanding record to change to the end date on the Transaction.
Example:
Transaction: Start_Date__c (is changed to)= 1/2/2019
End_Date__c=1/31/2020
Outstanding Records:
Start_Date__c 1/4/2018 End_Date__c 1/4/2019
Start_Date__c 1/4/2019 End_Date__c 1/4/2020
Start_Date__c 1/4/2020 End_Date__c 1/4/2021
When the Transaction's start date is changed to 1/2/2019, I want the trigger to find the relevant Outstanding record 2 and update the end date to 12/31/2019.
Hopefully, that makes sense. If it doesn't, please let me know and I'll try to explain it again. Any help would be appreciated. Thanks.

You have to write a trigger on your Transaction Object on after update context. Please try the below codes.
**Trigger** :
`trigger TransactionTrigger on Transaction__c (after update) {
Map<Id,Transaction__c> accTraMap = new Map<Id,Transaction__c>();
for(Transaction__c transac : trigger.new){
accTraMap.put(transac.Account__c, transac);
}
if(!accTraMap.isEmpty()){
TransactionTriggerHandler.afterUpdate(accTraMap);
}
}`
**Class :**
public class TransactionTriggerHandler {
public static void afterUpdate(Map<Id,Transaction__c> accTransMap){
list<Outstanding__c> outstanding = [SELECT Id, Name, StartDate__c, EndDate__c, Account__c
FROM Outstanding__c where Account__c in:accTransMap.keyset()];
list<Outstanding__c> outstandingToUpdate = new list<Outstanding__c>();
for(Outstanding__c o : outstanding){
if(accTransMap.containsKey(o.Account__c)){
if(o.StartDate__c >= accTransMap.get(o.Account__c).Start_Date__c && o.EndDate__c < accTransMap.get(o.Account__c).End_Date__c){
o.EndDate__c = o.EndDate__c.toStartOfMonth().addDays(-1);
outstandingToUpdate.add(o);
}
}
}
system.debug('\n updated outstanding '+outstandingToUpdate);
if(!outstandingToUpdate.isEmpty()){
update outstandingToUpdate;
}
}
}

Related

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

Trying to update Values in Standard Object from metaData

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...

Salesforce trigger - passing values from trigger record

Objective here – to extract the account__r.id from the record in the trigger and add the value to a list with each for-loop…
CODE SAMPLE ONE
if (trigger.isinsert) {
for (loan__c oloan : trigger.new){
system.debug(oloan);
Loan__c loanintrigger = new loan__c();
loanintrigger = [ SELECT loan__c.name,account__r.id from loan__c WHERE loan__c.id = : oloan.id];
Account a = new account ();
a.id = loanintrigger.account__r.id;
// debug to test insertion of value directly from trigger object.....
system.debug(a.id);
CODE SAMPLE TWO
if (trigger.isinsert) {
for (loan__c oloan : trigger.new){
system.debug(oloan);
Account a = new account ();
a.id = oloan.account__r.id;
// debug to test insertion of value directly from trigger object.....
system.debug(a.id);
So… trigger one runs and completes its assignment. Trigger two fails due to “missing argument.” I.e. – system.debug(a.id) shows that the id in the account object is null.
1, of course, requires a query for every record in the trigger. No style points.
This foreshadows another important and vexing question….
In the context of an after delete trigger, not even #1 works because you cannot query DB for data that has been deleted!
Can anybody suggest alternate approach? What am I missing?

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();

Firing Maximo workflow event from code

In our Maximo workflow we have a few schemas in which work order reaches a Condition node with a check on a startdate. If current date is less than it's startdate then work order goes to a Wait node with "maximo.workorder.update" condition. So when the scheduled date for WO comes people need to go to WO tracking and save this WO manually. Only then it continues it's way through the workflow. Otherwise WO will sit on that Wait node till the end of time.
What I want to do is to trigger this update event by crontask everyday so when the right date comes WO will wake up by itself.
I inspected source code for a Save button in WO tracking application and found that no matter what there's MboRemoteSet.save() method call. I assumed that you need to get some changes done and then call save() on the right MboSet. Also I know that in DB there's table called EVENTRESPONSE that keeps track of WOs sitting on the Wait nodes in workflow.
My crontask class contains this code:
MXServer mxServer = MXServer.getMXServer();
UserInfo userInfo = mxServer.getUserInfo("maxadmin");
woSet = mxServer.getMboSet("WORKORDER", userInfo);
...
String query = "select sourceid as WORKORDERID from EVENTRESPONSE"
+ " where eventname = 'maximo.workorder.update'"
+ " and sourcetable = 'WORKORDER'";
SqlFormat sqf = new SqlFormat("workorderid IN (" + query + ")");
woSet.setWhere(sqf.format());
MboRemote wo;
Date currentDate = new Date();
for (int i = 0; (wo = woSet.getMbo(i)) != null; i++) {
System.err.println(wo.getString("description"));
wo.setValue("CHANGEDATE", currentDate);
}
woSet.save();
workorder.changedate successfully refreshes but "maximo.workorder.update" event doesn't proc and WO stays on the Wait node.
So, how should I fire maximo.workorder.update?
This response comes a year late, I understand, but it may help others.
It is possible to use an "Escalation" to identify all work orders that have had their time to come and use an action on the escalation to update something on the work order. This will result in Maximo saving the change, thereby triggering the wait node of the workflow, all without any code, just configurations.
I have done something similar in the past and usually I end up flipping a YORN field that I had created for this purpose.