In the first part of this post I outlined the issues involved with creating a trigger to do roll-up summaries with a lookup relationship. To solve the problem I did what you should always try to do first: let the Force.com platform do the heavy lifting for you. Don't try to code around the platform when the declarative interface will do the trick 99% of the time. You'll be amazed with the things you can do with the platform when you let it. The rule of thumb is to use the point-n-click Builder first and write code only as a last resort.
So to solve my problem I added another custom object called Shipment Item and created a Master-Detail(Shipment) field that would automatically provide the roll-up summaries. However, since users only interact with the Inventory Items on the shipment they never actually see or use this Shipment Items so there's really no interaction with them; it's simply for the roll-up summaries. I needed a way to allow users to manage the Inventory Items on the shipment but in the background add/move/remove Shipment Items to keep the number of records in synch with the Inventory Items. Here is the trigger that I wrote for the Inventory Items to implement this functionality:
Trigger ShipmentItemProcess.cls
/**************************************************************************************
* Keeps inventory items and shipment items in sync. If an inventory item is on a
* shipment, then it ensures that there is a corresponding shipment item record. When
* an inventory item is removed from a shipment, it deletes the shipment item record.
**************************************************************************************/
trigger ShipmentItemProcess on Inventory_Item__c ( after update , after insert ) {
if ( Trigger . isUpdate ) {
List < inventory_Item__c > invItemsToInsert = new List < inventory_Item__c > ();
Map < id , ID > mapInvItemShipment = new Map < id , ID > ();
Set < id > invItemIdsToUpdate = new Set < id > ();
Set < id > invItemIdsToDelete = new Set < id > ();
// figure out which ones need to be inserted, updated or deleted
for ( Integer i = 0 ; i < trigger . new . size (); i ++ ) {
// if the old is null and the new is NOT null, they are assigning to a shipment - INSERT RECORD
if ( Trigger . old [ i ]. Shipment__c == null & Trigger . new [ i ]. Shipment__c != null ) {
invItemsToInsert . add ( Trigger . new [ i ]);
// if old is NOT null and new is null, then they are removing from the shipment - DELETE RECORD
} else if ( Trigger . old [ i ]. Shipment__c != null & Trigger . new [ i ]. Shipment__c == null ) {
invItemIdsToDelete . add ( Trigger . new [ i ]. id );
// if they are both NOT null the we need to switch the new one to another shipment
} else if ( Trigger . old [ i ]. Shipment__c != null & Trigger . new [ i ]. Shipment__c != null ) {
if ( Trigger . old [ i ]. Shipment__c != Trigger . new [ i ]. Shipment__c ) {
mapInvItemShipment . put ( Trigger . new [ i ]. id , Trigger . new [ i ]. Shipment__c );
invItemIdsToUpdate . add ( Trigger . new [ i ]. id );
}
}
}
// insert the new shipment records
if ( invItemsToInsert . size () > 0 ) {
List < shipment_Item__c > shipmentItemsToInsert = new List < shipment_Item__c > ();
for ( Inventory_Item__c item : invItemsToInsert ) {
Shipment_Item__c shipItem = new Shipment_Item__c ();
shipItem . Name = item . Name ;
shipItem . Inventory_Item__c = item . Id ;
shipItem . Shipment__c = item . Shipment__c ;
shipmentItemsToInsert . add ( shipItem );
}
insert shipmentItemsToInsert ;
}
// delete the corresponding shipment items that were removed
if ( invItemIdsToDelete . size () > 0 )
delete [ select id from Shipment_Item__c where Inventory_Item__c IN : invItemIdsToDelete ];
// update ones that have been moved to another shipment
if ( invItemIdsToUpdate . size () > 0 ) {
List < shipment_Item__c > itemsToUpdate = [ select Id , Inventory_Item__c , Shipment__c from Shipment_Item__c where Inventory_Item__c IN : invItemIdsToUpdate ];
for ( Shipment_Item__c item : itemsToUpdate )
item . Shipment__c = mapInvItemShipment . get ( item . Inventory_Item__c );
update itemsToUpdate ;
}
} else {
List < shipment_Item__c > shipmentItemsToInsert = new List < shipment_Item__c > ();
for ( Inventory_Item__c item : Trigger . new ) {
if ( item . Shipment__c != null ) {
Shipment_Item__c shipItem = new Shipment_Item__c ();
shipItem . Name = item . Name ;
shipItem . Inventory_Item__c = item . Id ;
shipItem . Shipment__c = item . Shipment__c ;
shipmentItemsToInsert . add ( shipItem );
}
}
if ( shipmentItemsToInsert . size () > 0 )
insert shipmentItemsToInsert ;
}
}
My test class looks like:
@ isTest
private class Test_ShipmentItemProcess {
static ID createShipment ( String shipmentName ) {
Shipment__c shipment = new Shipment__c (
name = shipmentName
);
insert shipment ;
return shipment . id ;
}
static Inventory_Item__c createInventoryItem ( String itemName ) {
Inventory_Item__c item = new Inventory_Item__c (
name = itemName
);
insert item ;
return item ;
}
static testMethod void testInsertAndDelete () {
ID ship1Id = createShipment ( ' Shipment 1 ' );
Inventory_Item__c item1 = createInventoryItem ( ' LP100 ' );
// update the item with the new shipment number
item1 . Shipment__c = ship1Id ;
update item1 ;
// fetch the newly created shipment item and make sure it was created correctly
Shipment_Item__c shipItem1 = [ select Id , Shipment__c , Inventory_Item__c from Shipment_Item__c where Inventory_Item__c = : item1 . id ];
System . assertEquals ( item1 . id , shipItem1 . Inventory_Item__c );
System . assertEquals ( item1 . Shipment__c , shipItem1 . Shipment__c );
// now delete the shipment
item1 . Shipment__c = null ;
update item1 ;
// make sure there are no records
System . assertEquals ( 0 ,[ select count () from Shipment_Item__c where Inventory_Item__c = : item1 . id ]);
}
static testMethod void testChangeShipment () {
ID ship1Id = createShipment ( ' Shipment 1 ' );
Inventory_Item__c item = new Inventory_Item__c (
name = ' My LP ' ,
Shipment__c = ship1Id
);
insert item ;
Shipment_Item__c si = new Shipment_Item__c ();
si . Inventory_Item__c = item . id ;
si . Name = ' My LP ' ;
si . Shipment__c = ship1Id ;
insert si ;
ID ship2Id = createShipment ( ' Shipment 2 ' );
item . Shipment__c = ship2Id ;
update item ;
// fetch the newly created shipment item and make sure it was created correctly
Shipment_Item__c shipItem2 = [ select Id , Shipment__c , Inventory_Item__c from Shipment_Item__c where Inventory_Item__c = : item . id ];
System . assertEquals ( ship2Id , shipItem2 . Shipment__c );
}
}