Search code examples
salesforceapexapex-trigger

Update Parent Object Field in Apex Trigger


I have three objects 1)Truck__c 2)Booking__C 3)Payment___C. Truck and Booking has master detail relationship where Truckk is master and Booking is detail. Booking and payment has lookup relationship where Booking is parent and payment is child.

I want to update a field present isBooked(checkbox) in Truck based on the value present in a field remainingAmount in payment object. I have added trigger on Payment object like below

trigger TestTrigger on Payment__c (after insert) {
 if(Trigger.isAfter && Trigger.isUpdate){
    for (Payment__c pay:Trigger.New)
    {
        payId.add(pay.id);
        paidAmount =Integer.valueOf(pay.Paid_Amount__c);
    }
    Map <id,Booking__c> bookingMap = new Map <id,Booking__c> ();
    for (Booking__c obj: [Select Id, Booking_ID__c from Booking__c where Booking_ID__c in:payId])
    {
        bookingMap.put(obj.Booking_ID__c, obj);
    }
    
    for(Booking__c objBooking:matchingIdsMap){
     Truck__c truckObj = [Select id,Price__c from Truck__c where Truck__c.id = objBooking.Truck__c.id];
     //Integer paidAmount = Payment__c.Paid_Amount__c;
     Integer totalAmount = Integer.valueOf(truckObj.Price__c);
     Integer remainingAmount = totalAmount-paidAmount;
        If(remainingAmount == 0){
            truckObj.Booked__c = true
        }
     update truckObj;
 } 
}

}

Here first I am getting payment ID's and based on that I am fetching Booking objects which is lookup parent of payment. After this I am trying to fetch the truck object which is master of Booking. But I don't know how to query on this as where clause in query giving error

Truck__c truckObj = [Select id,Price__c from Truck__c where Truck__c.id = objBooking.Truck__c.id];

Please note there is no direct relationship between Truck and Payment

How can I fetch truck object Thanks in Advance


Solution

  • Short answer: while referring to parent fields, you should use the relationship name, so Truck__r instead of Truck__c.
    Anyway it is not the only problem with that code.


    Long answer:

    • your trigger is in after insert, but you check for an after update event: if(Trigger.isAfter && Trigger.isUpdate). Probably you want to run this trigger in both after insert and after update.
    • You never declared payId nor paidAmount which you use in your first for-loop. Anyway paidAmount would just hold the last value, which probably you won't need.
    • [Select Id, Booking_ID__c from Booking__c where Booking_ID__c in:payId] this query should return an empty list because in payId you've stored the ids of Payment__c, that is a child of Booking__c, while in the first loop you should have stored the ids of parents Booking__c
    • [Select id,Price__c from Truck__c where Truck__c.id = objBooking.Truck__c.id] Here there is no reason to write where Truck__c.id It should be just WHERE Id = :objBooking.Truck__c.
    • Beware: putting a SOQL in a loop you will easily hit the Governor Limit about SOQL, which will raise a System.LimitException: Too many SOQL queries: 101. The same goes by putting a DML in a loop.

    I'm going to assume that the API Name of the lookup fields is the same of the parent object, so that a Booking__c field exists on Payment__c object and a Truck__c exists on Booking__c object.
    If I got the logic right about how setting the flag on Truck object, this should be the trigger code.

    trigger TestTrigger on Payment__c (after insert, after update) {
        if(Trigger.isAfter && (Trigger.isInsert || Trigger.isUpdate)) {
            Map<Id, List<Payment__c>> mapBookingIdPaymentList = new Map<Id, List<Payment__c>>();
            for (Payment__c pay : Trigger.New) {
                List<Payment__c> paymentList = mapBookingIdPaymentList.get(pay.Booking__c);
                if (paymentList == null) {
                    paymentList = new List<Payment__c>();
                    mapBookingIdPaymentList.put(pay.Booking__c, paymentList);
                }
                paymentList.add(pay);
            }
            
            Map<Id, Decimal> mapTruckPrice = new Map<Id, Decimal>();
            Map<Id, Integer> mapTruckRemainingAmount = new Map<Id, Integer>();
            for (Booking__c booking: [SELECT Id, Truck__c, Truck__r.Price__c FROM Booking__c WHERE Id IN :mapBookingIdPaymentList.keySet()]) {
                mapTruckPrice.put(booking.Truck__c, booking.Truck__r.Price__c);
                
                Integer sumOfRemainingAmount = mapTruckRemainingAmount.containsKey(booking.Truck__c) ? mapTruckRemainingAmount.get(booking.Truck__c) : 0;
                for (Payment__c pay : mapBookingIdPaymentList.get(booking.Id)) {
                    sumOfRemainingAmount += pay.Paid_Amount__c != null ? pay.Paid_Amount__c.intValue() : 0;
                }
                mapTruckRemainingAmount.put(booking.Truck__c, sumOfRemainingAmount);
            }
    
            List<Truck__c> trucksToUpdate = new List<Truck__c>();
            for (Id truckId : mapTruckPrice.keySet()) {
                // There is no need to query a record just to update it if you already have its Id.
                Truck__c truck = new Truck__c(Id = truckId);
                truck.Booked__c = mapTruckPrice.get(truckId) - mapTruckRemainingAmount.get(truckId) == 0;
                trucksToUpdate.add(truck);
            }
            update trucksToUpdate; // dml outside the loop
        }
    }
    

    By the way, you should move the logic in an handler class, following the best practices.