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
Leadhas 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
StageNameonly if theCloseDateis today and theStageNameis not already "Closed Won."
// 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
Accounttype is "Customer," ensure the relatedContacthas aPhonenumber. If not, prevent saving theAccountand 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
Accountrecords.Update the
Total_Opportunities__con theAccountwhen an Opportunity is inserted, updated, or deleted.
Solution:
- OpportunityTrigger
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
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
Accounthas any relatedOpportunitiesin "Negotiation" or "Proposal" stage, throw an error: "Cannot delete an Account with active Opportunities."
Solution:
- AccountTrigger
if(Trigger.isDelete) {
if (Trigger.isBefore) {
accountTriggerHandler.preventDeletionOfAccountWithActiveOpportunities(Trigger.Old);
}
}
- AccountTriggerHandler
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
StageNameis updated to "Closed Won" and update theEmailfield of the relatedContact.Ensure the email update happens only if the
Contact'sEmailfield is not already set to the predefined value.
Solution:
- OpportunityTriggerHandler
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
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
Accountname exists within the sameIndustry, prevent theAccountfrom being created and display an error: "An Account with the same name already exists in this Industry."
Solution:
- AccountTriggerHandler
// 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
Amountchanges, or an opportunity is deleted.
// 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
// 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
// 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
CloseDatePriority: "Medium"
Requirements:
Trigger should run after update on
Opportunity.Create the task only when the
StageNameis updated to specific stages (e.g., "Negotiation," "Proposal").Ensure the task is assigned to the
Opportunity Owner.
OpportunityTrigger
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
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
// 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
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
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
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
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
// 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
// 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
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
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)
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)
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)
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');
}
}
}
}