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}