# Apex Trigger Practice Problem

### Problem 1: Prevent Lead Conversion If No Email

**Scenario:** Create a trigger on the `Lead` object that prevents conversion if the `Email` field is blank.

**Requirements:**

* Trigger should run before update on `Lead`.
    
* If the `Lead` has no email address and a user tries to convert it, display an error message: "Lead must have an email address before conversion."
    

---

### Problem 2: Automatically Create a Task on Account Creation

**Scenario:** Create a trigger on the `Account` object that automatically creates a `Task` when a new `Account` record is created. The task should have the following properties:

* **Subject**: "Welcome Call"
    
* **Due Date**: 3 days from today
    
* **Priority**: "High"
    
* **Owner**: Same as the `Account Owner`
    

**Requirements:**

* Trigger should run after insert.
    
* Ensure the task is created only once per `Account`.
    
* Use the `Account`'s owner as the task owner.
    

---

### Problem 3: Update Opportunity Stage Based on Close Date

**Scenario:** Create a trigger on the `Opportunity` object that automatically sets the `StageName` to "Closed Won" when the `CloseDate` is set to today’s date.

**Requirements:**

* Trigger should run before update.
    
* The trigger should update the `StageName` only if the `CloseDate` is today and the `StageName` is not already "Closed Won."
    

```plaintext
// update opportunity stage based on close date
    public void updateOpportunityStage(List<Opportunity> opportunityList, Map<Id, Opportunity> oldOpportunityMap) {
        for (Opportunity opportunity : opportunityList) {
            Date oldOpportunityDate = oldOpportunityMap.get(opportunity.Id).CloseDate;
            String oldOpportunityStageName = oldOpportunityMap.get(opportunity.Id).StageName;
            if (opportunity.CloseDate != oldOpportunityDate && oldOpportunityStageName != 'Closed Won' && opportunity.CloseDate == System.today()) {
                opportunity.StageName = 'Closed Won';
            }
        }
    }
```

---

### Problem 4: Enforce Required Contact Field for Account Type

**Scenario:** Create a trigger on the `Account` object that ensures if the `Account` type is "Customer," the related `Contact` record must have a `Phone` number provided.

**Requirements:**

* Trigger should run before insert and before update on `Account`.
    
* If the `Account` type is "Customer," ensure the related `Contact` has a `Phone` number. If not, prevent saving the `Account` and display an error: "Customer accounts must have a phone number on the associated contact."
    

---

### Problem 5: Roll-Up Summary of Opportunities to Account

**Scenario:** Create an **Apex trigger on the Opportunity object** to maintain a custom field on the related **Account** object. The custom field, `Total_Opportunities__c`, will hold the **total number of Opportunities** related to the Account that are in the **"Closed Won"** stage.

**Requirements:**

* Trigger should run after insert, after update, and after delete.
    
* Perform bulk processing and use a map to efficiently update `Account` records.
    
* Update the `Total_Opportunities__c` on the `Account` when an Opportunity is inserted, updated, or deleted.
    

### Solution:

* **OpportunityTrigger**
    

```plaintext
trigger OpportunityTrigger on Opportunity (after update, before update, after insert, after delete) {
    OpportunityTriggerHandler opportunityTriggerHandler = new OpportunityTriggerHandler();
    
    if (Trigger.isUpdate) {        
        if (Trigger.isAfter) {
            opportunityTriggerHandler.updateTotalNumberOfOpportunities(Trigger.New);
        }
    }

     if (Trigger.isInsert) {
        if (Trigger.isAfter) {
            opportunityTriggerHandler.updateTotalNumberOfOpportunities(Trigger.New);
        }
    }


    if (Trigger.isDelete) {
        if (Trigger.isAfter) {
            opportunityTriggerHandler.updateTotalNumberOfOpportunities(Trigger.New);
        }
    }
}
```

* **OpportunityTriggerHandler**
    

```plaintext
public class OpportunityTriggerHandler {
    public void updateTotalNumberOfOpportunities(List<Opportunity> opportunityList) {
        Set<Id> accountIds = new Set<Id>();
        for (Opportunity opportunity : opportunityList) {
            accountIds.add(opportunity.AccountId);
        }

        List<Account> accountList = new List<Account>();
        for (Account account : [SELECT Id, (SELECT Id FROM Opportunities WHERE StageName = 'Closed Won') FROM Account WHERE Id IN :accountIds]) {
            Account currentAccount = new Account();
            currentAccount.Id = account.Id;
            currentAccount.Total_Opportunities__c = account.Opportunities.size();

            accountList.add(currentAccount);
        }

        try {
            update accountList;
        } catch (Exception e) {
            System.debug(e.getMessage());
        }
    }
}
```

### Problem 6: Prevent Deletion of Accounts with Active Opportunities

**Scenario:** Create a trigger on the `Account` object that prevents the deletion of an `Account` if there are any related `Opportunities` with a `StageName` of "Perception Analysis" or "Proposal/Price Quote."

**Requirements:**

* Trigger should run before delete on `Account`.
    
* If the `Account` has any related `Opportunities` in "Negotiation" or "Proposal" stage, throw an error: "Cannot delete an Account with active Opportunities."
    

### Solution:

* **AccountTrigger**
    

```plaintext
if(Trigger.isDelete) {
        if (Trigger.isBefore) {
            accountTriggerHandler.preventDeletionOfAccountWithActiveOpportunities(Trigger.Old);
        }
    }
```

* **AccountTriggerHandler**
    

```plaintext
public void preventDeletionOfAccountWithActiveOpportunities(List<Account> accountList) {
        System.debug('Prevent delete function called');
        
        Set<Id> accountIds = new Set<Id>();
        for (Account account : accountList) {
            accountIds.add(account.Id);
        }

        System.debug('Account Ids : ' + accountIds);

        List<String> stages = new List<String>{'Perception Analysis', 'Proposal/Price Quote'};
        Set<Id> accountIdWithRelatedOpportunity = new Set<Id>();
        for (Opportunity opportunity : [SELECT AccountId FROM Opportunity WHERE StageName IN :stages AND AccountId IN :accountIds]) {
            accountIdWithRelatedOpportunity.add(opportunity.AccountId);
        }

        System.debug('Account Ids with related opportunity : ' + accountIdWithRelatedOpportunity);

        for (Account account : accountList) {
            if (accountIdWithRelatedOpportunity.contains(account.Id)) {
                account.addError('Account cannot be deleted as it has related opportunity with Perception Analysis or Proposal/Price Quote ');
                System.debug('Inside loop');
            }
        }
    }
```

---

### Problem 7: Update Contact’s Email on Opportunity Stage Change

**Scenario:** Create a trigger on the `Opportunity` object. When the `StageName` is changed to "Closed Won," update the `Email` field of the related `Contact` to a predefined value (e.g., "won@opportunity.com").

**Requirements:**

* Trigger should run after update on `Opportunity`.
    
* The trigger should check if the `StageName` is updated to "Closed Won" and update the `Email` field of the related `Contact`.
    
* Ensure the email update happens only if the `Contact`'s `Email` field is not already set to the predefined value.
    

### Solution:

* **OpportunityTriggerHandler**
    

```plaintext
public void updateContactEmailField(List<Opportunity> opportunityList, Map<Id, Opportunity> oldOpportunityMap) {
        Set<Id> contactIds = new Set<Id>();

        
        for (Opportunity opportunity : opportunityList) {
            if (opportunity.StageName != oldOpportunityMap.get(opportunity.Id).StageName && opportunity.StageName == 'Closed Won') {
                contactIds.add(opportunity.ContactId);
            }
        }
        System.debug('Contact Ids : ' + contactIds);

        List<Contact> contactList = new List<Contact>();
        for (Contact contact : [SELECT Id, Email FROM Contact WHERE Id IN :contactIds]) {
            contact.email = 'won@opportunity.com';
            contactList.add(contact);
        }
        System.debug('Contacts : ' + contactList);

        try {
            if (!contactList.isEmpty()) {
                update contactList;
                System.debug('Contacts updated');
            }
        } catch (Exception e) {
            System.debug('Exception : ' + e.getMessage());
        }
    }
```

* **OpportunityTrigger**
    

```plaintext
trigger OpportunityTrigger on Opportunity (after update, before update, after insert, after delete) {
    OpportunityTriggerHandler opportunityTriggerHandler = new OpportunityTriggerHandler();
    
    if (Trigger.isUpdate) {
        if (Trigger.isAfter) {
            // Update Contact’s Email on Opportunity Stage Change
            opportunityTriggerHandler.updateContactEmailField(Trigger.New, Trigger.OldMap); 
        }
    }
}
```

### Problem 8: Prevent Account Creation with Duplicate Account Name

**Scenario:** Create a trigger on the `Account` object that prevents the creation of a new `Account` if an `Account` with the same `Name` already exists within the same `Industry`.

**Requirements:**

* Trigger should run before insert on `Account`.
    
* If a duplicate `Account` name exists within the same `Industry`, prevent the `Account` from being created and display an error: "An Account with the same name already exists in this Industry."
    

### Solution:

* **AccountTriggerHandler**
    

```plaintext
// trigger runs before account is inserted
public void preventDuplicateAccountWithinSameIndustry(List<Account> accountList) {
        // store all account name
        List<String> accountNames = new List<String>();
        for (Account account : accountList) {
            accountNames.add(account.Name);
        }

        // get all matching account and store inm map
        Map<String, Account> accountsFromObject = new Map<String, Account>();
        for (Account account : [SELECT Id, Name, Industry FROM Account WHERE Name IN :accountNames]) {
            accountsFromObject.put(account.Name, account);
        }

        // check if account name already exist's within the same industry
        for (Account account : accountList) {
            if (accountsFromObject.containsKey(account.Name) && accountsFromObject.get(account.Name).Industry == account.Industry) {
                account.addError('Account Name : ' + account.Name + ' exist with ' + account.Industry);
            }
        }
    }
```

---

### Problem 9: Calculate Total Opportunity Value for Account

**Scenario:** Create a trigger on the `Opportunity` object that updates a custom field `Total_Opportunity_Value__c` on the related `Account` with the sum of the `Amount` field of all related opportunities that are "Closed Won."

**Requirements:**

* Trigger should run after insert, after update, and after delete.
    
* Perform bulk processing to handle updates on multiple records efficiently.
    
* Ensure the sum is recalculated when an opportunity’s `Amount` changes, or an opportunity is deleted.
    

```plaintext
// calculate total opportunity value for account after update
    public void calculateTotalOpportunityValueAfterUpdate(List<Opportunity> opportunityList, Map<Id, Opportunity> oldOpportunityMap) {
        Set<Id> accountIds = new Set<Id>();
        for (Opportunity opportunity : opportunityList) {
            Decimal oldOpportunityAmount = oldOpportunityMap.get(opportunity.Id).Amount;
            String oldStageName = oldOpportunityMap.get(opportunity.Id).StageName;

            if (opportunity.Amount != oldOpportunityAmount || ((opportunity.StageName != oldStageName) && opportunity.StageName == 'Closed Won')) {
                accountIds.add(opportunity.AccountId);
            }
        }

        List<Account> accountList = new List<Account>();
        for (Account account : [SELECT Id, (SELECT Amount FROM Opportunities WHERE StageName = 'Closed Won') FROM Account WHERE Id IN :accountIds]) {
            
            // calculate total amount
            Decimal totalAmount = 0;
            for (Opportunity opp : account.Opportunities) {
                totalAmount += opp.Amount;
            }

            // update total opportunity value field
            account.Total_Opportunity_Value__c = totalAmount;
            accountList.add(account);
        }

        System.debug('Account list : ' + accountList);

        if (!accountList.isEmpty()) {
            update accountList;
            System.debug('Account updated');
        }
    }
```

### Problem 10: Calculate Total Opportunity Value for Account After Insert

```plaintext
// calculate total opportunity value for account after insert and update
    public void calculateTotalOpportunityValue(List<Opportunity> opportunityList) {
        Set<Id> accountIds = new Set<Id>();
        for (Opportunity opportunity : opportunityList) {
            accountIds.add(opportunity.AccountId);
        }

        List<Account> accountList = new List<Account>();
        for (Account account : [SELECT Id, (SELECT Amount FROM Opportunities WHERE StageName = 'Closed Won') FROM Account WHERE Id IN :accountIds]) {
            
            // calculate total amount
            Decimal totalAmount = 0;
            for (Opportunity opp : account.Opportunities) {
                totalAmount += opp.Amount;
            }

            // update total opportunity value field
            account.Total_Opportunity_Value__c = totalAmount;
            accountList.add(account);
        }

        System.debug('Account list : ' + accountList);

        if (!accountList.isEmpty()) {
            update accountList;
            System.debug('Account updated');
        }
    }
```

### Problem 11: Calculate Total Opportunity Value for Account After Delete

```plaintext
// calculate total opportunity value for account after insert and update
    public void calculateTotalOpportunityValue(List<Opportunity> opportunityList) {
        Set<Id> accountIds = new Set<Id>();
        for (Opportunity opportunity : opportunityList) {
            accountIds.add(opportunity.AccountId);
        }

        List<Account> accountList = new List<Account>();
        for (Account account : [SELECT Id, (SELECT Amount FROM Opportunities WHERE StageName = 'Closed Won') FROM Account WHERE Id IN :accountIds]) {
            
            // calculate total amount
            Decimal totalAmount = 0;
            for (Opportunity opp : account.Opportunities) {
                totalAmount += opp.Amount;
            }

            // update total opportunity value field
            account.Total_Opportunity_Value__c = totalAmount;
            accountList.add(account);
        }

        System.debug('Account list : ' + accountList);

        if (!accountList.isEmpty()) {
            update accountList;
            System.debug('Account updated');
        }
    }
```

### Problem 12: Auto-Assign Tasks Based on Opportunity Stage

**Scenario:** Create a trigger on the `Opportunity` object that automatically assigns a task to the Opportunity owner based on the `StageName`. The task should have the following:

* **Subject**: "Follow-up on Opportunity"
    
* **Due Date**: 7 days from the `CloseDate`
    
* **Priority**: "Medium"
    

**Requirements:**

* Trigger should run after update on `Opportunity`.
    
* Create the task only when the `StageName` is updated to specific stages (e.g., "Negotiation," "Proposal").
    
* Ensure the task is assigned to the `Opportunity Owner`.
    

**OpportunityTrigger**

```plaintext
trigger OpportunityTrigger on Opportunity (after update, before update, after insert, after delete) {
    
    if (Trigger.isUpdate) {
        if (Trigger.isAfter) {
            // create task for opportunity owner when StageName is updated to 'Closed Won'
            opportunityTriggerHandler.autoAssignTaskBasedOnOpportunityStage(Trigger.New, Trigger.OldMap);
        }
    }
}
```

**OpportunityTriggerHandler**

```plaintext
public void autoAssignTaskBasedOnOpportunityStage(List<Opportunity> opportunityList, Map<Id, Opportunity> opportunityOldMap) {
        List<Task> taskList = new List<Task>();

        for (Opportunity opportunity : opportunityList) {
            String oldStageName = opportunityOldMap.get(opportunity.Id).StageName;
            if (opportunity.StageName != oldStageName && (opportunity.StageName == 'Closed Won')) {
                Task task = new Task();
                task.OwnerId = opportunity.OwnerId;
                task.WhatId = opportunity.Id;
                task.ActivityDate = System.today() + 7;
                task.Priority = 'Medium';
                task.subject = 'Follow-up on Opportunity';

                taskList.add(task);
            }
        }

        System.debug('Task list : ' + taskList);

        if (!taskList.isEmpty()) {
            insert taskList;
            System.debug('Task created');
        }
    }
```

### Problem 13: Prevent duplicate Account name

```plaintext
// This handler function run's : BEFORE INSERT

public void preventDuplicateAccountandIndustry(List<Account> accountList) {
        // store all account name
        List<String> accountNames = new List<String>();
        for (Account account : accountList) {
            accountNames.add(account.Name);
        }

        // get all matching account names
        List<String> accountNamesFromObject = new List<String>();
        for (Account account : [SELECT Id, Name FROM Account WHERE Name IN :accountNames]) {
            accountNamesFromObject.add(account.Name);
        }

        // check if account name already exist's
        for (Account account : accountList) {
            if (accountNamesFromObject.contains(account.Name)) {
                account.addError('Account with ' + account.Name + ' already exists');
            }
        }
   }
```

### Problem 14 : Prevent Duplicate Account name with same Industry

```plaintext
public void preventDuplicateAccountWithinSameIndustry(List<Account> accountList) {
        // store all account name
        List<String> accountNames = new List<String>();
        for (Account account : accountList) {
            accountNames.add(account.Name);
        }

        System.debug('Account Names : ' + accountNames);

        Map<String, Account> accountsFromObject = new Map<String, Account>();
        for (Account account : [SELECT Id, Name, Industry FROM Account WHERE Name IN :accountNames]) {
            accountsFromObject.put(account.Name, account);
            System.debug('Added to map');
        }

        System.debug('Accounts From Object ' + accountsFromObject);

        for (Account account : accountList) {
            if (accountsFromObject.containsKey(account.Name) && accountsFromObject.get(account.Name).Industry == account.Industry) {
                account.addError('Account Name : ' + account.Name + ' exist with ' + account.Industry);
            }
        }
    }
```

### Problem 15 : Update related Contacts Phone field when Account Phone field is updated

* Update related contacts of the account whose phone field is updated
    

```plaintext
public void updateRelatedContacts(List<Account> accountList, Map<Id, Account> accountOldMap) {
        Map<Id, String> accountWithPhoneList = new Map<Id, String>();

        for (Account account : accountList) {
            String oldAccountPhone = accountOldMap.get(account.Id).Phone;
            if (account.Phone != oldAccountPhone) {
                accountWithPhoneList.put(account.Id, account.Phone);
            }
        }

        System.debug('Accounts with phone list '+ accountWithPhoneList);

        List<Contact> contactList = new List<Contact>();
        for (Contact contact : [SELECT Id, AccountId, Phone FROM Contact WHERE AccountId IN :accountWithPhoneList.keySet()]) {
            contact.Phone = accountWithPhoneList.get(contact.AccountId);
            contactList.add(contact);
        }

        System.debug('Contacts ' + contactList);

        if (!contactList.isEmpty()) {
            update contactList;
            System.debug('Contact List updated');
        }
    }
```

### Problem 16: Check Primary Contact on Insert

```plaintext
public void checkPrimaryContact(List<Contact> contactList) {
        Map<Id, Contact> mp = new Map<Id, Contact>();

        for (Contact contact : contactList) {
            mp.put(contact.AccountId, contact);
        }

        for (Account account : [SELECT Id, (SELECT Id , AccountId, Primary_Contact__c FROM Contacts) FROM Account WHERE Id IN :mp.keySet()]) {
            System.debug('Current Account ' + account);

            // check if primary contact exist
            for (Contact contact : account.Contacts) {
                if (contact.Primary_Contact__c && mp.get(contact.AccountId).Primary_Contact__c == true) {
                    mp.get(contact.AccountId).addError('Primary contact Exist');
                }
            }
        }
    }
```

### Problem 17: Check Primary contact on update

```plaintext
public void checkPrimaryContactAfterUpdate(Map<Id, Contact> contactMap, Map<Id, Contact> oldContactMap) {
        Map<Id, Contact> contactsWithAccountId = new Map<Id, Contact>();

        for (Contact contact : contactMap.values()) {
            if (contact.Primary_Contact__c != oldContactMap.get(contact.Id).Primary_Contact__c) {
                contactsWithAccountId.put(contact.AccountId, contact);
            }
        }

        for (Account account : [SELECT Id, (SELECT Id , AccountId, Primary_Contact__c FROM Contacts) FROM Account WHERE Id IN :contactsWithAccountId.keySet()]) {
            System.debug('Current Account ' + account);

            // check if primary contact exist
            for (Contact contact : account.Contacts) {
                if (contact.Primary_Contact__c && contactsWithAccountId.get(contact.AccountId).Primary_Contact__c == true) {
                    contactsWithAccountId.get(contact.AccountId).addError('Primary contact Exist');
                }
            }
        }
    }
```

### Problem 18: Create Contact according to total contact field

```plaintext
// after insert trigger
public void createContactsAccordingToTotalContactField(List<Account> accountList) {
        System.debug('createContactsAccordingToTotalContactField');
        List<Contact> contactList = new List<Contact>();
        for (Account account : accountList) {
            Integer totalContacts = (Integer) account.Total_Number_of_Contacts__c;

            // create Contacts
            for (Integer i = 0; i < totalContacts; i++) {
                Contact contact = new Contact(LastName=account.Name);
                contact.AccountId = account.Id;
                contactList.add(contact);
            }
        }

        System.debug('Contact list ' + contactList);

        if (!contactList.isEmpty()) {
            insert contactList;
            System.debug('Contact inserted');
        }
    }
```

### Problem 19: Create Account before insertion of Contact and link Contact with that created Account

```plaintext
// before insert trigger on contact
public void createAccountBeforeCreatingContact(List<Contact> contactList) {
        
        List<Account> accountList = new List<Account>();

        for (Contact contact : contactList) {
            Account account = new Account(Name=contact.LastName);
            accountList.add(account);
        }

        if (!accountList.isEmpty()) {
            insert accountList;
            System.debug('Account created');

            for (Integer i = 0; i < contactList.size(); i++) {
                contactList[i].AccountId = accountList[i].Id;
            }
        }
    }
```

### Problem 20: Prevent user from deactivation if there are active cases for that user

```plaintext
public void preventDeactivationOfUserForActiveCases(Map<Id, User> userMap, Map<Id, User> oldUserMap) {

        Set<Id> userIds = new Set<Id>();
        for (User user : userMap.values()) {
            if (user.IsActive != oldUserMap.get(user.Id).IsActive && user.IsActive == false) {
                userIds.add(user.Id);
            }
        }

        System.debug('Users' + userIds);

        for (Case currentCase : [SELECT Id, OwnerId FROM Case WHERE Status != 'Closed' AND OwnerId IN :userIds]) {
            if (currentCase != null) {
                userMap.get(currentCase.OwnerId).addError('Cannot deactive user cases are active on it');
                System.debug('Cannot deactivate user');
            }
        }
    }
```

### Problem 21: Update Related Contacts Optimized

```plaintext
public void updateRelatedContactsOptimized(Map<Id, Account> newAccountMap, Map<Id, Account> oldAccountMap) {
        Set<Id> accountIds = new Set<Id>();
        for (Account account : newAccountMap.values()) {
            if (account.Phone != oldAccountMap.get(account.Id).Phone) {
                accountIds.add(account.Id);
            }
        }

        List<Contact> contactList = new List<Contact>();
        for (Contact contact : [SELECT Id, AccountId, Phone FROM Contact WHERE AccountId IN :accountIds]) {
            contact.Phone = newAccountMap.get(contact.AccountId).Phone;
            contactList.add(contact);
        }

        if (!contactList.isEmpty()) {
            update contactList;
            System.debug('Contacts updated');
        }
    }
```

### Problem 22 : No conflict should be there while inserting timetable (Project Trigger)

```plaintext
public void handleTimetableBeforeInsert(List<Timetable__c> timetableList) {
        Map<Id, Timetable__c> timetableWithCourseId = new Map<Id, Timetable__c>();
        Map<Id, Timetable__c> timetableWithFactultyId = new Map<Id, Timetable__c>();
        List<Date> scheduledDate = new List<Date>();
        List<String> timeSlotList = new List<String>();
        
        for (Timetable__c timetable : timetableList) {
            timetableWithCourseId.put(timetable.Course__c, timetable);
			timetableWithFactultyId.put(timetable.Faculties__c, timetable);
        	scheduledDate.add(timetable.Scheduled_Date__c);
        	timeSlotList.add(timetable.Subject_Time_Slot__c);
        }
        
        // check if faculty is already assined to this timetable
        for (Timetable__c timetable : [SELECT Id, Course__c FROM Timetable__c WHERE Course__c IN :timetableWithCourseId.keySet()
                                   		AND Scheduled_Date__c IN :scheduledDate 
                                    	AND Subject_Time_Slot__c IN :timeSlotList]) 
        {
            timetableWithCourseId.get(timetable.Course__c).addError('Timetable for this course is already set');
        }
        
        // check if faculty is been allocated to other course with same time
        for (Timetable__c timetable : [SELECT Id, Faculties__c FROM Timetable__c WHERE Faculties__c IN :timetableWithFactultyId.keySet()
                                   		AND Scheduled_Date__c IN :scheduledDate 
                                    	AND Subject_Time_Slot__c IN :timeSlotList]) 
        {
            timetableWithFactultyId.get(timetable.Faculties__c).addError('Timetable for this faculty is already set');
        }        
    } 
```

### Problem 23 : Check if valid subject belongs to selected course while adding timetable (Project Trigger)

```plaintext
public void handleSubjectsForCourse(List<Timetable__c> timetableList) {
        List<Id> timetableWithCourseId = new List<Id>();
        List<Id> timetableWithFacultyId = new List<Id>();
        
        for (Timetable__c timetable : timetableList) {
            timetableWithCourseId.add(timetable.Course__c);
            timetableWithFacultyId.add(timetable.Faculties__c);
        }
        
        Map<Id, String> subjectWithFacultyId = new Map<Id, String>();
        for (Faculties__c faculty : [SELECT Id, Subject__c FROM Faculties__c WHERE Id IN :timetableWithFacultyId]) {
            subjectWithFacultyId.put(faculty.Id, faculty.Subject__c);
        }
        
        Map<Id, Set<Id>> subjectsWithCourseId = new Map<Id, Set<Id>>();
        for (Subject_Course__c subject : [SELECT Course__c, Subject__c FROM Subject_Course__c WHERE Course__c IN :timetableWithCourseId]) {
            if (subjectsWithCourseId.containsKey(subject.Course__c) && subjectsWithCourseId.get(subject.Course__c) != null) {
                subjectsWithCourseId.get(subject.Course__c).add(subject.Subject__c);
            } 
            else {
                subjectsWithCourseId.put(subject.Course__c, new Set<Id>{subject.Subject__c});
            }
        }
        
        for (Timetable__c timetable : timetableList) {
            String subjectId = subjectWithFacultyId.get(timetable.Faculties__c);
            if (!subjectsWithCourseId.get(timetable.Course__c).contains(subjectId)) {
                timetable.addError('Subject is not listed in course');
            }
        }
    }
```

### Problem 24 : Handle student enrollment in course, throw error if student is already enrolled in any course (Project Trigger)

```plaintext
public void handleEnrollment(List<Enrollment__c> enrollmentList) {
        Map<Id, Enrollment__c> enrollmentsByStudentId = new Map<Id, Enrollment__c>();
        Map<Id, Enrollment__c> enrollmentsByCourseId = new Map<Id, Enrollment__c>();
        
        for (Enrollment__c enrollment : enrollmentList) {
            enrollmentsByStudentId.put(enrollment.Student__c, enrollment);
            enrollmentsByCourseId.put(enrollment.Course__c, enrollment);
        }
        
        // check if course is finished already
        for (Course__c course : [SELECT Id, End_Date__c FROM Course__c WHERE Id IN: enrollmentsByCourseId.keySet()]) {
            if (enrollmentsByCourseId.containsKey(course.Id) && course.End_Date__c < System.today()) {
                enrollmentsByCourseId.get(course.Id).addError('Course has been completed');
            }
        }
        
        for (Enrollment__c enrollment : [SELECT Student__r.Id, Course__r.Id, Course__r.End_Date__c FROM Enrollment__c WHERE Student__c IN :enrollmentsByStudentId.keySet()]) {                    
            if (enrollmentsByStudentId.containsKey(enrollment.Student__r.Id)) {
                
                // check for same course
                if (enrollmentsByStudentId.get(enrollment.Student__r.Id).Course__c == enrollment.Course__r.Id) {
                    enrollmentsByStudentId.get(enrollment.Student__r.Id).addError('Student already enrolled in this course');
                }
                
                // check if student is already enrolled in other course and that course is not finished
                if (enrollmentsByStudentId.get(enrollment.Student__r.Id).Course__c != enrollment.Course__r.Id && enrollment.Course__r.End_Date__c >= System.today()) {
					enrollmentsByStudentId.get(enrollment.Student__r.Id).addError('Student already enrolled in other course');     
				}
            }
        }
    } 
```
