Shipping Invoice Example Code

These triggers and test class make up the shipping invoice example application:

Calculate Trigger

1trigger calculate on Item__c (after insert, after update, after delete) {
2
3// Use a map because it doesn't allow duplicate values
4
5Map<ID, Shipping_Invoice__C> updateMap = new Map<ID, Shipping_Invoice__C>();
6
7// Set this integer to -1 if we are deleting
8Integer subtract ;
9
10// Populate the list of items based on trigger type
11List<Item__c> itemList;
12    if(trigger.isInsert || trigger.isUpdate){
13        itemList = Trigger.new;
14        subtract = 1;
15    }
16    else if(trigger.isDelete)
17    {
18        // Note -- there is no trigger.new in delete
19        itemList = trigger.old;
20        subtract = -1;
21    }
22
23// Access all the information we need in a single query 
24// rather than querying when we need it.
25// This is a best practice for bulkifying requests
26
27set<Id> AllItems = new set<id>();
28
29for(item__c i :itemList){
30// Assert numbers are not negative.  
31// None of the fields would make sense with a negative value
32
33Assert.isTrue(i.quantity__c > 0, 'Quantity must be positive');
34Assert.isTrue(i.weight__c >= 0, 'Weight must be non-negative');
35Assert.isTrue(i.price__c >= 0, 'Price must be non-negative');
36
37// If there is a duplicate Id, it won't get added to a set
38AllItems.add(i.Shipping_Invoice__C);
39}
40
41// Accessing all shipping invoices associated with the items in the trigger
42List<Shipping_Invoice__C> AllShippingInvoices = [SELECT Id, ShippingDiscount__c, 
43                   SubTotal__c, TotalWeight__c, Tax__c, GrandTotal__c 
44                   FROM Shipping_Invoice__C WHERE Id IN :AllItems WITH USER_MODE];
45                   
46// Take the list we just populated and put it into a Map.  
47// This will make it easier to look up a shipping invoice
48// because you must iterate a list, but you can use lookup for a map, 
49Map<ID, Shipping_Invoice__C> SIMap = new Map<ID, Shipping_Invoice__C>();
50
51for(Shipping_Invoice__C sc : AllShippingInvoices)
52{
53    SIMap.put(sc.id, sc);
54}
55
56// Process the list of items
57    if(Trigger.isUpdate)
58    {
59        // Treat updates like a removal of the old item and addition of the         
60        // revised item rather than figuring out the differences of each field 
61        // and acting accordingly.
62        // Note updates have both trigger.new and trigger.old
63        for(Integer x = 0; x < Trigger.old.size(); x++)
64        {
65            Shipping_Invoice__C myOrder;
66            myOrder = SIMap.get(trigger.old[x].Shipping_Invoice__C);
67
68            // Decrement the previous value from the subtotal and weight.
69            myOrder.SubTotal__c -= (trigger.old[x].price__c * 
70                                    trigger.old[x].quantity__c);
71            myOrder.TotalWeight__c -= (trigger.old[x].weight__c * 
72                                       trigger.old[x].quantity__c);
73                
74            // Increment the new subtotal and weight.
75            myOrder.SubTotal__c += (trigger.new[x].price__c * 
76                                    trigger.new[x].quantity__c);
77            myOrder.TotalWeight__c += (trigger.new[x].weight__c * 
78                                       trigger.new[x].quantity__c);
79        }
80        
81        for(Shipping_Invoice__C myOrder : AllShippingInvoices)
82        {
83            
84            // Set tax rate to 9.25%  Please note, this is a simple example.  
85            // Generally, you would never hard code values.
86            // Leveraging Custom Settings for tax rates is a best practice.  
87            // See Custom Settings in the Apex Developer Guide 
88            // for more information.
89            myOrder.Tax__c = myOrder.Subtotal__c * .0925;
90            
91            // Reset the shipping discount
92            myOrder.ShippingDiscount__c = 0;
93    
94            // Set shipping rate to 75 cents per pound.  
95            // Generally, you would never hard code values.
96            // Leveraging Custom Settings for the shipping rate is a best practice.
97            // See Custom Settings in the Apex Developer Guide 
98            // for more information.
99            myOrder.Shipping__c = (myOrder.totalWeight__c * .75);
100            myOrder.GrandTotal__c = myOrder.SubTotal__c + myOrder.tax__c + 
101                                    myOrder.Shipping__c;
102            updateMap.put(myOrder.id, myOrder);
103         }
104    }
105    else
106    { 
107        for(Item__c itemToProcess : itemList)
108        {
109            Shipping_Invoice__C myOrder;
110    
111            // Look up the correct shipping invoice from the ones we got earlier
112            myOrder = SIMap.get(itemToProcess.Shipping_Invoice__C);
113            myOrder.SubTotal__c += (itemToProcess.price__c * 
114                                    itemToProcess.quantity__c * subtract);
115            myOrder.TotalWeight__c += (itemToProcess.weight__c * 
116                                       itemToProcess.quantity__c * subtract);
117        }
118        
119        for(Shipping_Invoice__C myOrder : AllShippingInvoices)
120        {
121            
122             // Set tax rate to 9.25%  Please note, this is a simple example.  
123             // Generally, you would never hard code values.
124             // Leveraging Custom Settings for tax rates is a best practice.  
125             // See Custom Settings in the Apex Developer Guide 
126             // for more information.
127             myOrder.Tax__c = myOrder.Subtotal__c * .0925;
128             
129             // Reset shipping discount
130             myOrder.ShippingDiscount__c = 0;
131    
132            // Set shipping rate to 75 cents per pound.  
133            // Generally, you would never hard code values.
134            // Leveraging Custom Settings for the shipping rate is a best practice.
135            // See Custom Settings in the Apex Developer Guide 
136            // for more information.
137            myOrder.Shipping__c = (myOrder.totalWeight__c * .75);
138            myOrder.GrandTotal__c = myOrder.SubTotal__c + myOrder.tax__c + 
139                                    myOrder.Shipping__c;
140                                       
141            updateMap.put(myOrder.id, myOrder);
142    
143         }
144     }    
145     
146     // Only use one DML update at the end.
147     // This minimizes the number of DML requests generated from this trigger.
148     update as user updateMap.values();
149}

ShippingDiscount Trigger

1trigger ShippingDiscount on Shipping_Invoice__C (before update) {
2    // Free shipping on all orders greater than $100
3    
4    for(Shipping_Invoice__C myShippingInvoice : Trigger.new)
5    {
6        if((myShippingInvoice.subtotal__c >= 100.00) && 
7           (myShippingInvoice.ShippingDiscount__c == 0))
8        {
9            myShippingInvoice.ShippingDiscount__c = 
10                         myShippingInvoice.Shipping__c * -1;
11            myShippingInvoice.GrandTotal__c += myShippingInvoice.ShippingDiscount__c;
12        }
13    }
14}

Shipping Invoice Test

1@IsTest
2private with sharing class TestShippingInvoice{
3
4    // Test for inserting three items at once
5    @IsTest
6    public static void testBulkItemInsert(){
7        // Create the shipping invoice. It's a best practice to either use defaults
8        // or to explicitly set all values to zero so as to avoid having
9        // extraneous data in your test.
10        Shipping_Invoice__C order1 = new Shipping_Invoice__C(subtotal__c = 0, 
11                          totalweight__c = 0, grandtotal__c = 0, 
12                          ShippingDiscount__c = 0, Shipping__c = 0, tax__c = 0);
13
14        // Insert the order and populate with items
15        insert Order1;
16        List<Item__c> list1 = new List<Item__c>();
17        Item__c item1 = new Item__C(Price__c = 10, weight__c = 1, quantity__c = 1, 
18                                    Shipping_Invoice__C = order1.id);
19        Item__c item2 = new Item__C(Price__c = 25, weight__c = 2, quantity__c = 1, 
20                                    Shipping_Invoice__C = order1.id);
21        Item__c item3 = new Item__C(Price__c = 40, weight__c = 3, quantity__c = 1, 
22                                    Shipping_Invoice__C = order1.id);
23        list1.add(item1);
24        list1.add(item2);
25        list1.add(item3);
26        insert list1;
27        
28        // Retrieve the order, then do assertions
29        order1 = [SELECT id, subtotal__c, tax__c, shipping__c, totalweight__c, 
30                  grandtotal__c, shippingdiscount__c 
31                  FROM Shipping_Invoice__C 
32                  WHERE id = :order1.id WITH USER_MODE];
33        
34        Assert.areEqual(75, order1.subtotal__c, 
35                'Order subtotal was not $75, but was '+ order1.subtotal__c);
36        Assert.areEqual(6.9375, order1.tax__c, 
37                'Order tax was not $6.9375, but was ' + order1.tax__c);
38        Assert.areEqual(4.50, order1.shipping__c, 
39                'Order shipping was not $4.50, but was ' + order1.shipping__c);
40        Assert.areEqual(6.00, order1.totalweight__c, 
41                'Order weight was not 6 but was ' + order1.totalweight__c);
42        Assert.areEqual(86.4375, order1.grandtotal__c, 
43                'Order grand total was not $86.4375 but was ' 
44                 + order1.grandtotal__c);
45        Assert.areEqual(0, order1.shippingdiscount__c, 
46                'Order shipping discount was not $0 but was ' 
47                + order1.shippingdiscount__c);
48    }
49    
50    // Test for updating three items at once
51    @IsTest
52    public static void testBulkItemUpdate(){
53
54        // Create the shipping invoice. It's a best practice to either use defaults
55        // or to explicitly set all values to zero so as to avoid having
56        // extraneous data in your test.
57        Shipping_Invoice__C order1 = new Shipping_Invoice__C(subtotal__c = 0, 
58                          totalweight__c = 0, grandtotal__c = 0, 
59                          ShippingDiscount__c = 0, Shipping__c = 0, tax__c = 0);
60
61        // Insert the order and populate with items.
62        insert as user Order1;
63        List<Item__c> list1 = new List<Item__c>();
64        Item__c item1 = new Item__C(Price__c = 1, weight__c = 1, quantity__c = 1, 
65                                    Shipping_Invoice__C = order1.id);
66        Item__c item2 = new Item__C(Price__c = 2, weight__c = 2, quantity__c = 1, 
67                                    Shipping_Invoice__C = order1.id);
68        Item__c item3 = new Item__C(Price__c = 4, weight__c = 3, quantity__c = 1, 
69                                    Shipping_Invoice__C = order1.id);
70        list1.add(item1);
71        list1.add(item2);
72        list1.add(item3);
73        insert as system list1;
74        
75        // Update the prices on the 3 items
76        list1[0].price__c = 10;
77        list1[1].price__c = 25;
78        list1[2].price__c = 40;
79        update as system list1;
80        
81        // Access the order and assert items updated
82        order1 = [SELECT id, subtotal__c, tax__c, shipping__c, totalweight__c, 
83                  grandtotal__c, shippingdiscount__c 
84                  FROM Shipping_Invoice__C 
85                  WHERE Id = :order1.Id WITH USER_MODE];
86
87        Assert.areEqual(75, order1.subtotal__c, 
88                       'Order subtotal was not $75, but was '+ order1.subtotal__c);
89        Assert.areEqual(6.9375, order1.tax__c, 
90                       'Order tax was not $6.9375, but was ' + order1.tax__c);
91        Assert.areEqual(4.50, order1.shipping__c, 
92                       'Order shipping was not $4.50, but was ' 
93                       + order1.shipping__c);
94        Assert.areEqual(6.00, order1.totalweight__c, 
95                       'Order weight was not 6 but was ' + order1.totalweight__c);
96        Assert.areEqual(86.4375, order1.grandtotal__c, 
97                       'Order grand total was not $86.4375 but was ' 
98                       + order1.grandtotal__c);
99        Assert.areEqual(0, order1.shippingdiscount__c, 
100                       'Order shipping discount was not $0 but was ' 
101                       + order1.shippingdiscount__c);
102    
103    }
104    
105    // Test for deleting items
106    @IsTest
107    public static void testBulkItemDelete(){
108
109        // Create the shipping invoice. It's a best practice to either use defaults
110        // or to explicitly set all values to zero so as to avoid having
111        // extraneous data in your test.
112        Shipping_Invoice__C order1 = new Shipping_Invoice__C(subtotal__c = 0, 
113                          totalweight__c = 0, grandtotal__c = 0, 
114                          ShippingDiscount__c = 0, Shipping__c = 0, tax__c = 0);
115
116        // Insert the order and populate with items
117        insert Order1;
118        List<Item__c> list1 = new List<Item__c>();
119        Item__c item1 = new Item__C(Price__c = 10, weight__c = 1, quantity__c = 1, 
120                                    Shipping_Invoice__C = order1.id);
121        Item__c item2 = new Item__C(Price__c = 25, weight__c = 2, quantity__c = 1, 
122                                    Shipping_Invoice__C = order1.id);
123        Item__c item3 = new Item__C(Price__c = 40, weight__c = 3, quantity__c = 1, 
124                                    Shipping_Invoice__C = order1.id);
125        Item__c itemA = new Item__C(Price__c = 1, weight__c = 3, quantity__c = 1, 
126                                    Shipping_Invoice__C = order1.id);
127        Item__c itemB = new Item__C(Price__c = 1, weight__c = 3, quantity__c = 1, 
128                                    Shipping_Invoice__C = order1.id);
129        Item__c itemC = new Item__C(Price__c = 1, weight__c = 3, quantity__c = 1, 
130                                    Shipping_Invoice__C = order1.id);
131        Item__c itemD = new Item__C(Price__c = 1, weight__c = 3, quantity__c = 1, 
132                                    Shipping_Invoice__C = order1.id);
133        list1.add(item1);
134        list1.add(item2);
135        list1.add(item3);
136        list1.add(itemA);
137        list1.add(itemB);
138        list1.add(itemC);
139        list1.add(itemD);
140        insert list1;
141        
142        // Seven items are now in the shipping invoice. 
143       // The following deletes four of them.
144        List<Item__c> list2 = new List<Item__c>();
145        list2.add(itemA);
146        list2.add(itemB);
147        list2.add(itemC);
148        list2.add(itemD);
149        delete list2;
150        
151        // Retrieve the order and verify the deletion
152        order1 = [SELECT id, subtotal__c, tax__c, shipping__c, totalweight__c, 
153                  grandtotal__c, shippingdiscount__c 
154                  FROM Shipping_Invoice__C 
155                  WHERE Id = :order1.Id WITH USER_MODE];
156        
157        Assert.areEqual(75, order1.subtotal__c, 
158                      'Order subtotal was not $75, but was '+ order1.subtotal__c);
159        Assert.areEqual(6.9375, order1.tax__c, 
160                      'Order tax was not $6.9375, but was ' + order1.tax__c);
161        Assert.areEqual(4.50, order1.shipping__c, 
162                      'Order shipping was not $4.50, but was ' + order1.shipping__c);
163        Assert.areEqual(6.00, order1.totalweight__c, 
164                      'Order weight was not 6 but was ' + order1.totalweight__c);
165        Assert.areEqual(86.4375, order1.grandtotal__c, 
166                      'Order grand total was not $86.4375 but was ' 
167                      + order1.grandtotal__c);
168        Assert.areEqual(0, order1.shippingdiscount__c, 
169                      'Order shipping discount was not $0 but was ' 
170                      + order1.shippingdiscount__c);
171    }
172    // Testing free shipping
173    @IsTest
174    public static void testFreeShipping(){
175
176        // Create the shipping invoice. It's a best practice to either use defaults
177        // or to explicitly set all values to zero so as to avoid having
178        // extraneous data in your test.
179        Shipping_Invoice__C order1 = new Shipping_Invoice__C(subtotal__c = 0, 
180                          totalweight__c = 0, grandtotal__c = 0, 
181                          ShippingDiscount__c = 0, Shipping__c = 0, tax__c = 0);
182
183        // Insert the order and populate with items.
184        insert as user Order1;
185        List<Item__c> list1 = new List<Item__c>();
186        Item__c item1 = new Item__C(Price__c = 10, weight__c = 1, 
187                                 quantity__c = 1, Shipping_Invoice__C = order1.id);
188        Item__c item2 = new Item__C(Price__c = 25, weight__c = 2, 
189                                 quantity__c = 1, Shipping_Invoice__C = order1.id);
190        Item__c item3 = new Item__C(Price__c = 40, weight__c = 3, 
191                                 quantity__c = 1, Shipping_Invoice__C = order1.id);
192        list1.add(item1);
193        list1.add(item2);
194        list1.add(item3);
195        insert as user list1;
196        
197        // Retrieve the order and verify free shipping not applicable
198        order1 = [SELECT id, subtotal__c, tax__c, shipping__c, totalweight__c, 
199                  grandtotal__c, shippingdiscount__c 
200                  FROM Shipping_Invoice__C 
201                  WHERE Id = :order1.Id WITH USER_MODE];
202        
203        // Free shipping not available on $75 orders
204        Assert.areEqual(75, order1.subtotal__c, 
205                      'Order subtotal was not $75, but was '+ order1.subtotal__c);
206        Assert.areEqual(6.9375, order1.tax__c, 
207                      'Order tax was not $6.9375, but was ' + order1.tax__c);
208        Assert.areEqual(4.50, order1.shipping__c, 
209                      'Order shipping was not $4.50, but was ' + order1.shipping__c);
210        Assert.areEqual(6.00, order1.totalweight__c, 
211                      'Order weight was not 6 but was ' + order1.totalweight__c);
212        Assert.areEqual(86.4375, order1.grandtotal__c, 
213                      'Order grand total was not $86.4375 but was ' 
214                      + order1.grandtotal__c);
215        Assert.areEqual(0, order1.shippingdiscount__c, 
216                      'Order shipping discount was not $0 but was ' 
217                      + order1.shippingdiscount__c);
218        
219        // Add items to increase subtotal
220        item1 = new Item__C(Price__c = 25, weight__c = 20, quantity__c = 1, 
221                            Shipping_Invoice__C = order1.id);       
222        insert as user item1;
223
224        // Retrieve the order and verify free shipping is applicable
225        order1 = [SELECT id, subtotal__c, tax__c, shipping__c, totalweight__c, 
226                  grandtotal__c, shippingdiscount__c 
227                  FROM Shipping_Invoice__C 
228                  WHERE Id = :order1.Id WITH USER_MODE];
229        
230        // Order total is now at $100, so free shipping should be enabled
231        Assert.areEqual(100, order1.subtotal__c, 
232                      'Order subtotal was not $100, but was '+ order1.subtotal__c);
233        Assert.areEqual(9.25, order1.tax__c, 
234                      'Order tax was not $9.25, but was ' + order1.tax__c);
235        Assert.areEqual(19.50, order1.shipping__c, 
236                      'Order shipping was not $19.50, but was ' 
237                      + order1.shipping__c);
238        Assert.areEqual(26.00, order1.totalweight__c, 
239                      'Order weight was not 26 but was ' + order1.totalweight__c);
240        Assert.areEqual(109.25, order1.grandtotal__c, 
241                      'Order grand total was not $86.4375 but was ' 
242                      + order1.grandtotal__c);
243        Assert.areEqual(-19.50, order1.shippingdiscount__c, 
244                      'Order shipping discount was not -$19.50 but was ' 
245                      + order1.shippingdiscount__c);
246    }
247    
248     // Negative testing for inserting bad input
249    @IsTest
250    public static void testNegativeTests(){
251
252        // Create the shipping invoice. It's a best practice to either use defaults
253        // or to explicitly set all values to zero so as to avoid having
254        // extraneous data in your test.
255        Shipping_Invoice__C order1 = new Shipping_Invoice__C(subtotal__c = 0, 
256                          totalweight__c = 0, grandtotal__c = 0, 
257                          ShippingDiscount__c = 0, Shipping__c = 0, tax__c = 0);
258
259        // Insert the order and populate with items. 
260        insert Order1;
261        Item__c item1 = new Item__C(Price__c = -10, weight__c = 1, quantity__c = 1, 
262                                    Shipping_Invoice__C = order1.id);
263        Item__c item2 = new Item__C(Price__c = 25, weight__c = -2, quantity__c = 1, 
264                                    Shipping_Invoice__C = order1.id);
265        Item__c item3 = new Item__C(Price__c = 40, weight__c = 3, quantity__c = -1, 
266                                    Shipping_Invoice__C = order1.id);
267        Item__c item4 = new Item__C(Price__c = 40, weight__c = 3, quantity__c = 0, 
268                                    Shipping_Invoice__C = order1.id);
269
270        try{
271            insert item1;
272        }
273        catch(Exception e)
274        {
275            Assert.isTrue(e.getMessage().contains('Price must be non-negative'), 
276                         'Price was negative but was not caught');
277        }
278        
279        try{
280            insert item2;
281        }
282        catch(Exception e)
283        {
284            Assert.isTrue(e.getMessage().contains('Weight must be non-negative'), 
285                         'Weight was negative but was not caught');
286        }
287
288        try{
289            insert item3;
290        }
291        catch(Exception e)
292        {
293            Assert.isTrue(e.getMessage().contains('Quantity must be positive'), 
294                         'Quantity was negative but was not caught');
295        }
296        
297        try{
298            insert item4;
299        }
300        catch(Exception e)
301        {
302            Assert.isTrue(e.getMessage().contains('Quantity must be positive'), 
303                         'Quantity was zero but was not caught');
304        }
305    }
306}