function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Olga Kim 5Olga Kim 5 

create a parent and a child record in a single apex class

Dear Salesforce Professional

I am a beginner in Programming.
Please help me resolve this problem.

I have a requirement to create a child record (on Billing Line object) if parent records (on Billing object) exist I need to attach the child record (Billing Line) to the parent (Billing) but if Parent record doesn't exist I need to create parent and child records.

This trigger is supposed to fire when I check a checkbox on a TEST object.

For some reason, my code can't find parent records and can't create one.

I get this error message
"TriggerTestBillingLineCreate: execution of AfterUpdate caused by: System.DmlException: Insert failed. First exception on row 0; first error: REQUIRED_FIELD_MISSING, Required fields are missing: [AcctSeed__Customer__c]: [AcctSeed__Customer__c] Class.testBillingLineCreate.createBillingLine: line 48, column 1 Trigger.TriggerTestBillingLineCreate: line 5, column 1"

Basically, it is saying the required field is missing when I am trying to create a parent record. I don't understand why it is missing if I populate it there and in some cases why it needs to create a parent record if a parent exists in a database. It just needs to find it and link a child record to the parent.

I attached my code below. I appreciate any advice. Thank you for your help!


public class testBillingLineCreate {
    public static void createBillingLine (list<TEST__c> TestList, map<id, TEST__c> oldMap)
    {
     list<AcctSeed__Billing_Line__c> billingLineList=new List<AcctSeed__Billing_Line__c>();      
     list<AcctSeed__Billing__c> billingList= new list<AcctSeed__Billing__c>();
        
         for(TEST__c objtest:TestList)
        {
        if(objtest.Billed__c==true && oldMap.get(objtest.Id).Billed__c==false)

        {
          for(AcctSeed__Billing__c billings:[select id, name, AcctSeed__Customer__c, advAcctSeed__Matter__c from AcctSeed__Billing__c where AcctSeed__Status__c='Approved']){

          if(billings.AcctSeed__Customer__c==objtest.Account__c && billings.advAcctSeed__Matter__c==objtest.Matter__c){

            AcctSeed__Billing_Line__c bill=new AcctSeed__Billing_Line__c();
           bill.AcctSeed__Billing__c=billings.Id;
            bill.AcctSeed__Project__c=objtest.Project__c;
            bill.AcctSeed__Hours_Units__c=objtest.time__c;                  
            bill.AcctSeed__Rate__c=objtest.Rate__c; 

            billingLineList.add(bill);
        }




         else {
              AcctSeed__Billing__c billings2=new AcctSeed__Billing__c();
              billings2.AcctSeed__Customer__c= objtest.Account__c;
               billings2.advAcctSeed__Matter__c=objtest.matter__c;
            billings2.AcctSeed__Status__c='Approved';
             billings2.AcctSeed__Date__c=date.today();
             billingList.add(billings2);

               AcctSeed__Billing_Line__c bill1=new AcctSeed__Billing_Line__c();
           bill1.AcctSeed__Billing__c=billings2.Id;
            bill1.AcctSeed__Project__c=objtest.Project__c;
            bill1.AcctSeed__Hours_Units__c=objtest.time__c;                  
            bill1.AcctSeed__Rate__c=objtest.Rate__c; 

            billingLineList.add(bill1);

            }
    }

        }
        insert billingList;
        insert billingLineList;
        }}}
AbhishekAbhishek (Salesforce Developers) 
Hi Kim,

Your query is answered here check it once,

https://developer.salesforce.com/forums/?id=9060G000000Bf3XQAS

https://www.forcetalks.com/salesforce-topic/is-it-possible-to-insert-a-salesforce-parent-child-record-in-one-dml-operation/

For your reference,

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_dml_foreign_keys.htm


I hope you find the above solution helpful. If it does, please mark as Best Answer to help others too.

Thanks!
Andrew GAndrew G
@abhishek - I love a canned response.  If we hope to guide the new programmers, a little explaination will always help rather than just do it this way.

@Olga

Alright, to answer the questions,lets review the code and point some things out.

1.  You have a SOQL query inside a FOR loop.  This is an issue for bulkification.  There are plenty of posts regarding that issue and how to overcome it.

2.  Why are you getting the insert error?  I would guess to say that that the field is not set in the TEST__c object that you are using as a reference point.  If you wanted to find out if this was the cause, throw a debug in the code to test for the value in the field.  Or nest the creation of the record in an IF statement to test that the values exist in the TEST__c record.


3.  You will have future issues.  Which is why @abhishek posted the associated links.   Even if you get past the error you are experiencing, you will have issues on the insert of the billingLineList.  It may not error, but will be an issue.  Why?
Because of this line:
bill1.AcctSeed__Billing__c=billings2.Id;
The billings2 record has not been inserted.  The Id for the record will not exist prior to being inserted.  Therefore you will be setting the AcctSeed__Billing__c to null.  And so will not have the relationship you are trying to establish.

You could implement a simple piece of code below to prove that Ids do not exist until inserted.
Lead beforeLead = new Lead(LastName='Sample',
							FirstName ='A', 
							Company='ACME', 
							Status = 'Open - Not Contacted',
							Email = 'sample@acme.acme');
        System.assertEquals(null,beforeLead.Id);
		insert beforeLead;
		System.assertNotEquals(null,beforeLead.Id);

Hopefully that is clear. 

I would now recommend that you check the links that @abhishek posted and adjust your code.  Keep in mind points 1 & 2 above.

Regards
Andrew
 
MoonpieMoonpie
@Andrew: AMEN to your first paragraph!

@Olga: Read Andrew's post again, slowly, and do things in your code one step at a time.

Also, in the future, please paste your code in a code text box and not the main text box for your posts and replies - like Andrew did in his post.  Just click the <> button (highlighted below) at the top of your reply box
User-added image

Then paste your code inside the popup text box

User-added image

That will make your code much easier to read and follow, and it will give your code line numbers that anyone helping can use for reference.
Olga Kim 5Olga Kim 5
Thank you for your help @ Andrew, @ Moonpie, and @abhishek. It is big for me. As I said before I am new in coding and need a lot of guidance. 

@Andrew - 
1 As you suggested I took out my SOQL query from the for loop to avoid hitting governor limits. 
2 my TEST__c object contains fields that a Parent record (Billing__c) and a Child (Billing Line__c) should have. There are two fields: account and projects__c that should be the same for a parent and a child record. Both of these fields are on a TEST__c object.
3 Thank you for pointing out that I have to insert a parent record before creating a child. I fixed it. But still getting the error message that required field is missing.      

Even with these adjustments, I can't get my code work. I guess I need to change the whole logic in my helper class?

There are two tasks in my requirement. The first is to create a child record and linked it to an existing parent record based on two fields (account and matter__c). 

And the second requirement is in case there is no parent record I need to create one and create a child record.

I think I need to tackle the tasks one by one.

I will appreciate any ideas.
public class testBillingLineCreate {
    public static void createBillingLine (list<TEST__c> TestList, map<id, TEST__c> oldMap)
    {
     list<AcctSeed__Billing_Line__c> billingLineList=new List<AcctSeed__Billing_Line__c>();      
     list<AcctSeed__Billing__c> billingList= [select id, name, AcctSeed__Customer__c, advAcctSeed__Matter__c from AcctSeed__Billing__c where AcctSeed__Status__c='Approved'];
        
         for(TEST__c objtest:TestList)
        {
        if(objtest.Billed__c==true && oldMap.get(objtest.Id).Billed__c==false)

        {
          for(AcctSeed__Billing__c billings:billingList){

          if(billings.AcctSeed__Customer__c==objtest.Account__c && billings.advAcctSeed__Matter__c==objtest.Matter__c){

            AcctSeed__Billing_Line__c bill=new AcctSeed__Billing_Line__c();
           bill.AcctSeed__Billing__c=billings.Id;
            bill.AcctSeed__Project__c=objtest.Project__c;
            bill.AcctSeed__Hours_Units__c=objtest.time__c;                  
            bill.AcctSeed__Rate__c=objtest.Rate__c; 

            billingLineList.add(bill);
        }




         else {
              AcctSeed__Billing__c billings2=new AcctSeed__Billing__c();
              billings2.AcctSeed__Customer__c= objtest.Account__c;
               billings2.advAcctSeed__Matter__c=objtest.matter__c;
            billings2.AcctSeed__Status__c='Approved';
             billings2.AcctSeed__Date__c=date.today();
             insert billings2;

               AcctSeed__Billing_Line__c bill1=new AcctSeed__Billing_Line__c();
           bill1.AcctSeed__Billing__c=billings2.Id;
            bill1.AcctSeed__Project__c=objtest.Project__c;
            bill1.AcctSeed__Hours_Units__c=objtest.time__c;                  
            bill1.AcctSeed__Rate__c=objtest.Rate__c; 

            billingLineList.add(bill1);

            }
    }

        }
       
        insert billingLineList;
        }}}

 
Andrew GAndrew G
Ok, lets consider your code example.

First thing that jumps out is that you are looping within a loop.  This is not ideal.  If we consider a bulk insert of say 100 TEST__c records.  And lets say there are 1000 records returned in the SOQL for AcctSeed_Billing__c.   Therefore you will be doing 100,000 loops, of which not all would be needed.

Second, lets consider the logic in the loop on the billingList.  Lets assume 1 Test__c record is inserted, but the SOQL query returns 2 billing records.  Now lets assume that one (the firest) of those 2 matches the Test__c record.  On the first loop, it matches, so a billing line is attached to the first record.  On the second loop, a new Billing record is created and then a duplicate Billing Line will be created on that bill.
So my insert of one Test__c record attaches a line to the original Billing record and then creates a new Billing record and attaches the same Line.
Without knowing the exact business need, that does seem incorrect to me.

So to solve, i would go to a map.
Build a map of the billing records, keyed by account and matter.
Then loop the test records and do a containsKey
Action accordingly

So, how could that look:
public class testBillingLineCreate {
    public static void createBillingLine (list<TEST__c> TestList, map<id, TEST__c> oldMap)
    {
        list<AcctSeed__Billing_Line__c> billingLineList=new List<AcctSeed__Billing_Line__c>();
        list<AcctSeed__Billing__c> billingList= [select id, name, AcctSeed__Customer__c, advAcctSeed__Matter__c from AcctSeed__Billing__c where AcctSeed__Status__c='Approved'];

        Map<String, AcctSeed_Biling__c> billMap = new Map<String,AcctSeed_Billing__c>();
        for(AcctSeed__Billing__c bill : billingLineList){
            billMap.put(bill.AcctSeed__Customer__c+'~'+bill.advAcctSeed__Matter__c, bill);
        }
        //loop the test records
        for(TEST__c objtest:TestList) {
            //check if Billed field changes
            if(objtest.Billed__c==true && oldMap.get(objtest.Id).Billed__c==false) {
                //check if we find the bill record
                if(billMap.containsKey(objtest.Account__c+'~'+objtest.Matter__c)) {
                    AcctSeed__Billing_Line__c bill=new AcctSeed__Billing_Line__c();
                    bill.AcctSeed__Billing__c=billings.Id;
                    bill.AcctSeed__Project__c=objtest.Project__c;
                    bill.AcctSeed__Hours_Units__c=objtest.time__c;
                    bill.AcctSeed__Rate__c=objtest.Rate__c;

                    billingLineList.add(bill);
                } else {
                    //create a new bill record
                    AcctSeed__Billing__c billings2=new AcctSeed__Billing__c();
                    billings2.AcctSeed__Customer__c= objtest.Account__c;
                    billings2.advAcctSeed__Matter__c=objtest.matter__c;
                    billings2.AcctSeed__Status__c='Approved';
                    billings2.AcctSeed__Date__c=date.today();
                    insert billings2;

                    AcctSeed__Billing_Line__c bill1=new AcctSeed__Billing_Line__c();
                    bill1.AcctSeed__Billing__c=billings2.Id;
                    bill1.AcctSeed__Project__c=objtest.Project__c;
                    bill1.AcctSeed__Hours_Units__c=objtest.time__c;
                    bill1.AcctSeed__Rate__c=objtest.Rate__c;

                    billingLineList.add(bill1);
                }
            }

            insert billingLineList;
        }
    }
}

But, as I still look at it, two more things jump out.  

First I am duplicating code that is basically the same:
AcctSeed__Billing_Line__c bill1=new AcctSeed__Billing_Line__c();
                    bill1.AcctSeed__Billing__c=billings2.Id;
                    bill1.AcctSeed__Project__c=objtest.Project__c;
                    bill1.AcctSeed__Hours_Units__c=objtest.time__c;
                    bill1.AcctSeed__Rate__c=objtest.Rate__c;

                    billingLineList.add(bill1);
So segment that out to a method, and call the method rather that having the code writting twice.  

Second, I still run the risk of hitting limits because i'm doing individual inserts of the billing record potentially for each loop that executes:
insert billings2;
So I would actually break the logic by building a second list of those records where the Test record did not match the Billing record.

So in short
 
//declare a list to hold records where we will create both parent and child records together
    List<Test__c> createList = new List<Test__c>();
// other code goes here


//check if we find the bill record
                if(billMap.containsKey(objtest.Account__c+'~'+objtest.Matter__c)) {
                    AcctSeed__Billing_Line__c bill=new AcctSeed__Billing_Line__c();
                    bill.AcctSeed__Billing__c=billings.Id;
                    bill.AcctSeed__Project__c=objtest.Project__c;
                    bill.AcctSeed__Hours_Units__c=objtest.time__c;
                    bill.AcctSeed__Rate__c=objtest.Rate__c;

                    billingLineList.add(bill);
                } else {
                    //create a list for new bill record
                    createList.add(objTest);
                }

And then outside the loop implement another piece of code that loops the CreateList and generates both the parent and child record, using the links provided above by @abhishek


Regards
Andrew