source: trunk/grails-app/services/InventoryPurchaseService.groovy @ 605

Last change on this file since 605 was 605, checked in by gav, 14 years ago

Domain change, several changes to InventoryItemPurchase.
Update views and logic to suite.

File size: 18.1 KB
Line 
1class InventoryPurchaseService {
2
3    boolean transactional = false
4
5    def authService
6    def dateUtilService
7    def inventoryMovementService
8
9    /**
10    * Calulates the quantities for an inventoryItem and purchaseOrderNumber.
11    * @param order An inventory puchase that was the source of the order.
12    * @returns A result map containing the totalOrdered, totalReceived, totalRemaining, thisOrderRemaining,
13    *                 totalOrderedAmount, totalReceivedAmount, totalRemainingAmount, thisOrderRemainingAmount,
14    *                 totalPaymentApproved.
15    */
16    def calcQuantities(order) {
17
18        def result = [:]
19
20        result.totalOrdered = 0
21        result.totalOrderedAmount = 0
22        result.totalReceived = 0
23        result.totalReceivedAmount = 0
24        result.totalPaymentApproved = 0
25        InventoryItemPurchase.withCriteria {
26            eq("inventoryItem", order.inventoryItem)
27            eq("purchaseOrderNumber", order.purchaseOrderNumber)
28        }.each() {
29            if(it.inventoryItemPurchaseType.id == 1L) { // Orders.
30                result.totalOrdered += it.quantity
31                result.totalOrderedAmount += it.orderValueAmount
32            }
33            if(it.inventoryItemPurchaseType.id == 2L || it.inventoryItemPurchaseType.id == 3L) { // Received B/order and Complete.
34                result.totalReceived += it.quantity
35                result.totalReceivedAmount += it.orderValueAmount
36            }
37            if(it.inventoryItemPurchaseType.id == 4L) { // Approved.
38                result.totalPaymentApproved += it.orderValueAmount
39            }
40        }
41
42        result.totalRemaining
43        if(result.totalOrdered > result.totalReceived)
44            result.totalRemaining = result.totalOrdered - result.totalReceived
45        else
46            result.totalRemaining = 0
47
48        result.totalRemainingAmount
49        if(result.totalOrderedAmount > result.totalReceivedAmount)
50            result.totalRemainingAmount = result.totalOrderedAmount - result.totalReceivedAmount
51        else
52            result.totalRemainingAmount = 0
53
54        result.thisOrderRemaining
55        if(result.totalRemaining > order.quantity)
56            result.thisOrderRemaining = order.quantity
57        else
58            result.thisOrderRemaining = result.totalRemaining
59
60        result.thisOrderRemainingAmount
61        if(result.totalRemainingAmount > order.orderValueAmount)
62            result.thisOrderRemainingAmount = order.orderValueAmount
63        else
64            result.thisOrderRemainingAmount = result.totalRemainingAmount
65
66        return result
67    }
68
69    /**
70    * Get the original order for an inventoryItemPurchase and InventoryItem.
71    * @param inventoryItemPurchase An inventory puchase.
72    * @returns The order.
73    */
74    def getOriginalOrder(inventoryItemPurchase) {
75
76        def namedParams = [:]
77
78        namedParams.inventoryItem = inventoryItemPurchase.inventoryItem
79        namedParams.purchaseOrderNumber = inventoryItemPurchase.purchaseOrderNumber
80        namedParams.orderPlaced = InventoryItemPurchaseType.read(1)
81
82        def order = InventoryItemPurchase.find("from InventoryItemPurchase as p \
83                                                                                where( p.inventoryItem = :inventoryItem \
84                                                                                            and p.purchaseOrderNumber = :purchaseOrderNumber \
85                                                                                            and p.inventoryItemPurchaseType = :orderPlaced )",
86                                                                            namedParams)
87
88        return order
89    }
90
91    def delete(params) {
92        InventoryItemPurchase.withTransaction { status ->
93            def result = [:]
94            def fail = { Map m ->
95                status.setRollbackOnly()
96                if(result.inventoryItemPurchase && m.field)
97                    result.inventoryItemPurchase.errors.rejectValue(m.field, m.code)
98                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
99                return result
100            }
101
102            result.inventoryItemPurchaseInstance = InventoryItemPurchase.get(params.id)
103
104            if(!result.inventoryItemPurchaseInstance)
105                return fail(code:"default.not.found")
106
107            result.inventoryItemId = result.inventoryItemPurchaseInstance.inventoryItem.id
108            def purchaseTypeId = result.inventoryItemPurchaseInstance.inventoryItemPurchaseType.id
109
110            // Handle Invoice Payment Approved.
111            if(purchaseTypeId == 4) {
112                // Find and mark all orders as invoicePaymentApproved = false.
113                InventoryItemPurchase.withCriteria {
114                    eq("inventoryItem", result.inventoryItemPurchaseInstance.inventoryItem)
115                    eq("purchaseOrderNumber", result.inventoryItemPurchaseInstance.purchaseOrderNumber)
116                    inventoryItemPurchaseType {
117                            eq("id", 1L) // Order Placed.
118                    }
119                }.each() {
120                    it.invoicePaymentApproved = false
121                }
122                // Find and mark last orderReceived as invoicePaymentApproved = false.
123                InventoryItemPurchase.withCriteria {
124                    eq("inventoryItem", result.inventoryItemPurchaseInstance.inventoryItem)
125                    eq("purchaseOrderNumber", result.inventoryItemPurchaseInstance.purchaseOrderNumber)
126                    inventoryItemPurchaseType {
127                        or {
128                            eq("id", 2L) // Received B/order To Come
129                            eq("id", 3L) // Received Complete
130                        }
131                    }
132                }.last().invoicePaymentApproved = false
133            }
134
135            // Handle Received.
136            // Refuse to delete if payment approved.
137            // Find and reverse the matching movement.
138            // Find and mark all orders as receivedComplete = false.
139            if(purchaseTypeId == 2 || purchaseTypeId == 3) {
140
141                def paymentAlreadyApproved = InventoryItemPurchase.withCriteria {
142                    eq("inventoryItem", result.inventoryItemPurchaseInstance.inventoryItem)
143                    eq("purchaseOrderNumber", result.inventoryItemPurchaseInstance.purchaseOrderNumber)
144                    inventoryItemPurchaseType {
145                            eq("id", 4L) // Invoice Payment Approved.
146                    }
147                }
148
149                if(paymentAlreadyApproved)
150                    return fail(code:"inventoryItemPurchase.delete.failure.payment.approved")
151
152                def startOfDay = dateUtilService.getMidnight(result.inventoryItemPurchaseInstance.date)
153                def inventoryMovements = InventoryMovement.withCriteria {
154                    eq("inventoryItem", result.inventoryItemPurchaseInstance.inventoryItem )
155                    eq("quantity", result.inventoryItemPurchaseInstance.quantity)
156                    between("date", startOfDay, startOfDay+1)
157                    inventoryMovementType {
158                        eq("id", 3L) // purchaseReceived.
159                    }
160                    order('id', 'desc') // The newest one will be first.
161                }
162
163                def movementResult = inventoryMovementService.reverseMove(inventoryMovements[0])
164                if(movementResult.error)
165                    return fail(code:"inventoryMovement.quantity.insufficientItemsInStock")
166
167                InventoryItemPurchase.withCriteria {
168                    eq("inventoryItem", result.inventoryItemPurchaseInstance.inventoryItem)
169                    eq("purchaseOrderNumber", result.inventoryItemPurchaseInstance.purchaseOrderNumber)
170                    inventoryItemPurchaseType {
171                            eq("id", 1L) // Order Placed
172                    }
173                }.each() {
174                    it.receivedComplete = false
175                }
176            } // Handle Received.
177
178            // Handle Order Placed.
179            // Refuse to delete if we have received items.
180            // Deletion of received already requires payment approved to be deleted.
181            if(purchaseTypeId == 1) {
182                def calcQuantities = calcQuantities(result.inventoryItemPurchaseInstance)
183                if(calcQuantities.totalReceived > 0)
184                    return fail(code:"inventoryItemPurchase.delete.failure.received.exists")
185            }
186
187            // Success.
188            // By this stage everything should be handled and the delete call is allowed and expected to pass.
189            result.inventoryItemPurchaseInstance.delete()
190            return result
191
192        } // end withTransaction
193    }
194
195    def edit(params) {
196        def result = [:]
197        def fail = { Map m ->
198            result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
199            return result
200        }
201
202        result.inventoryItemPurchaseInstance = InventoryItemPurchase.get(params.id)
203
204        if(!result.inventoryItemPurchaseInstance)
205            return fail(code:"default.not.found")
206
207        // Success.
208        return result
209    }
210
211    def update(params) {
212        InventoryItemPurchase.withTransaction { status ->
213            def result = [:]
214
215            def fail = { Map m ->
216                status.setRollbackOnly()
217                if(result.inventoryItemPurchaseInstance && m.field)
218                    result.inventoryItemPurchaseInstance.errors.rejectValue(m.field, m.code)
219                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
220                return result
221            }
222
223            result.inventoryItemPurchaseInstance = InventoryItemPurchase.get(params.id)
224
225            if(!result.inventoryItemPurchaseInstance)
226                return fail(code:"default.not.found")
227
228            // Optimistic locking check.
229            if(params.version) {
230                if(result.inventoryItemPurchaseInstance.version > params.version.toLong())
231                    return fail(field:"version", code:"default.optimistic.locking.failure")
232            }
233
234            result.inventoryItemPurchaseInstance.properties = params
235            result.inventoryItemPurchaseInstance.purchaseOrderNumber = result.inventoryItemPurchaseInstance.purchaseOrderNumber.trim()
236            result.inventoryItemPurchaseInstance.lastUpdatedBy = authService.currentUser
237
238            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
239                return fail(code:"default.update.failure")
240
241            // Success.
242            return result
243
244        } //end withTransaction
245    }  // end update()
246
247    def save(params) {
248        InventoryItemPurchase.withTransaction { status ->
249            def result = [:]
250            def fail = { Map m ->
251                status.setRollbackOnly()
252                if(result.inventoryItemPurchase && m.field)
253                    result.inventoryItemPurchase.errors.rejectValue(m.field, m.code)
254                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
255                return result
256            }
257
258            result.inventoryItemPurchaseInstance = new InventoryItemPurchase(params)
259            result.inventoryItemPurchaseInstance.purchaseOrderNumber = result.inventoryItemPurchaseInstance.purchaseOrderNumber.trim()
260            result.inventoryItemPurchaseInstance.enteredBy = authService.currentUser
261            result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(1) // Order
262
263            // Fetch to prevent lazy initialization error.
264            result.inventoryItemPurchaseInstance.inventoryItem.unitOfMeasure
265
266            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
267                return fail(code:"default.create.failure")
268
269            result.inventoryItemId = result.inventoryItemPurchaseInstance.inventoryItem.id
270
271            // success
272            return result
273
274        } // end withTransaction
275    } // save()
276
277    def receiveSave(params) {
278        InventoryItemPurchase.withTransaction { status ->
279            def result = [:]
280
281            def fail = { Map m ->
282                status.setRollbackOnly()
283                if(result.inventoryItemPurchase && m.field)
284                    result.inventoryItemPurchase.errors.rejectValue(m.field, m.code)
285                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
286                return result
287            }
288
289            def order = InventoryItemPurchase.get(params.orderId)
290            if(!order)
291                return fail(code:"default.not.found")
292            result.orderId = order.id
293
294            result.inventoryItemPurchaseInstance = new InventoryItemPurchase(params)
295            result.inventoryItemPurchaseInstance.enteredBy = authService.currentUser
296            result.inventoryItemPurchaseInstance.purchaseOrderNumber = order.purchaseOrderNumber
297            result.inventoryItemPurchaseInstance.costCode = order.costCode
298            result.inventoryItemPurchaseInstance.orderValueCurrency = order.orderValueCurrency
299
300            def calcQuantities = calcQuantities(order)
301            if(result.inventoryItemPurchaseInstance.quantity)
302                calcQuantities.totalReceived += result.inventoryItemPurchaseInstance.quantity
303            if(result.inventoryItemPurchaseInstance.orderValueAmount)
304                calcQuantities.totalReceivedAmount += result.inventoryItemPurchaseInstance.orderValueAmount
305
306            if(calcQuantities.totalReceived >= calcQuantities.totalOrdered) {
307                order.receivedComplete = true
308                result.inventoryItemPurchaseInstance.receivedComplete = true
309                result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(3) // Received Complete.
310            }
311            else {
312                order.receivedComplete = false
313                result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(2) // Received B/oder to Come.
314            }
315
316            // Fetch to prevent lazy initialization error.
317            result.inventoryItemPurchaseInstance.inventoryItem.unitOfMeasure
318            result.inventoryItemPurchaseInstance.inventoryItem.inventoryLocation
319
320            if(order.hasErrors() || !order.save())
321                return fail(code:"default.create.failure")
322
323            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
324                return fail(code:"default.create.failure")
325
326            result.inventoryItemId = result.inventoryItemPurchaseInstance.inventoryItem.id
327
328            // Perform the inventory movement.
329            if(result.inventoryItemPurchaseInstance.quantity > 0) {
330                def p = [:]
331                p.inventoryItem = result.inventoryItemPurchaseInstance.inventoryItem
332                p.quantity = result.inventoryItemPurchaseInstance.quantity
333                p.inventoryMovementType = InventoryMovementType.read(3)
334                def movementResult = inventoryMovementService.move(p)
335                if(movementResult.error)
336                    return fail(code:"default.create.failure")
337            }
338
339            // success
340            return result
341
342        } // end withTransaction
343    } // receiveSave()
344
345    def approveInvoicePaymentSave(params) {
346        InventoryItemPurchase.withTransaction { status ->
347            def result = [:]
348
349            def fail = { Map m ->
350                status.setRollbackOnly()
351                if(result.inventoryItemPurchaseInstance && m.field)
352                    result.inventoryItemPurchaseInstance.errors.rejectValue(m.field, m.code)
353                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
354                return result
355            }
356
357            def received = InventoryItemPurchase.get(params.receivedId)
358            if(!received)
359                return fail(code:"default.not.found")
360            result.receivedId = received.id
361
362            def order = getOriginalOrder(received)
363            if(!order)
364                return fail(code:"default.not.found")
365            result.orderId = order.id
366
367            result.inventoryItemPurchaseInstance = new InventoryItemPurchase(params)
368            result.inventoryItemPurchaseInstance.enteredBy = authService.currentUser
369            result.inventoryItemPurchaseInstance.purchaseOrderNumber = order.purchaseOrderNumber
370            result.inventoryItemPurchaseInstance.costCode = order.costCode
371            result.inventoryItemPurchaseInstance.orderValueCurrency = order.orderValueCurrency
372            result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(4) // Approve.
373
374            received.invoicePaymentApproved = true
375            result.inventoryItemPurchaseInstance.invoicePaymentApproved = true
376
377            // Update orderValueAmount and invoicePaymentApproved if processing a receivedComplete.
378            if(received.inventoryItemPurchaseType.id == 3L) {
379                order.invoicePaymentApproved = true
380                result.inventoryItemPurchaseInstance.receivedComplete = true
381                def calcQuantities = calcQuantities(order)
382                if(result.inventoryItemPurchaseInstance.orderValueAmount)
383                    calcQuantities.totalPaymentApproved += result.inventoryItemPurchaseInstance.orderValueAmount
384                order.orderValueAmount = calcQuantities.totalPaymentApproved
385            }
386
387            // Fetch to prevent lazy initialization error.
388            result.inventoryItemPurchaseInstance.inventoryItem.unitOfMeasure
389
390            if(!result.inventoryItemPurchaseInstance.invoiceNumber)
391                return fail(field:"invoiceNumber", code:"inventoryItemPurchase.invoiceNumber.required")
392
393            if(order.hasErrors() || !order.save())
394                return fail(code:"default.create.failure")
395
396            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
397                return fail(code:"default.create.failure")
398
399            result.inventoryItemId = result.inventoryItemPurchaseInstance.inventoryItem.id
400
401            // Success..
402            return result
403
404        } // end withTransaction
405    } // approveInvoicePaymentSave()
406
407} // end class
Note: See TracBrowser for help on using the repository browser.