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

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

Trim inventory purchase order number.
Allow editing of any purchase's flags.
Display description, location and suppliers part number when receiving.

File size: 17.9 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.dateEntered)
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
237            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
238                return fail(code:"default.update.failure")
239
240            // Success.
241            return result
242
243        } //end withTransaction
244    }  // end update()
245
246    def save(params) {
247        InventoryItemPurchase.withTransaction { status ->
248            def result = [:]
249            def fail = { Map m ->
250                status.setRollbackOnly()
251                if(result.inventoryItemPurchase && m.field)
252                    result.inventoryItemPurchase.errors.rejectValue(m.field, m.code)
253                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
254                return result
255            }
256
257            result.inventoryItemPurchaseInstance = new InventoryItemPurchase(params)
258            result.inventoryItemPurchaseInstance.purchaseOrderNumber = result.inventoryItemPurchaseInstance.purchaseOrderNumber.trim()
259            result.inventoryItemPurchaseInstance.enteredBy = authService.currentUser
260            result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(1) // Order
261
262            // Fetch to prevent lazy initialization error.
263            result.inventoryItemPurchaseInstance.inventoryItem.unitOfMeasure
264
265            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
266                return fail(code:"default.create.failure")
267
268            result.inventoryItemId = result.inventoryItemPurchaseInstance.inventoryItem.id
269
270            // success
271            return result
272
273        } // end withTransaction
274    } // save()
275
276    def receiveSave(params) {
277        InventoryItemPurchase.withTransaction { status ->
278            def result = [:]
279
280            def fail = { Map m ->
281                status.setRollbackOnly()
282                if(result.inventoryItemPurchase && m.field)
283                    result.inventoryItemPurchase.errors.rejectValue(m.field, m.code)
284                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
285                return result
286            }
287
288            def order = InventoryItemPurchase.get(params.orderId)
289            if(!order)
290                return fail(code:"default.not.found")
291            result.orderId = order.id
292
293            result.inventoryItemPurchaseInstance = new InventoryItemPurchase(params)
294            result.inventoryItemPurchaseInstance.enteredBy = authService.currentUser
295            result.inventoryItemPurchaseInstance.purchaseOrderNumber = order.purchaseOrderNumber
296            result.inventoryItemPurchaseInstance.costCode = order.costCode
297            result.inventoryItemPurchaseInstance.orderValueCurrency = order.orderValueCurrency
298
299            def calcQuantities = calcQuantities(order)
300            if(result.inventoryItemPurchaseInstance.quantity)
301                calcQuantities.totalReceived += result.inventoryItemPurchaseInstance.quantity
302            if(result.inventoryItemPurchaseInstance.orderValueAmount)
303                calcQuantities.totalReceivedAmount += result.inventoryItemPurchaseInstance.orderValueAmount
304
305            if(calcQuantities.totalReceived >= calcQuantities.totalOrdered) {
306                order.receivedComplete = true
307                result.inventoryItemPurchaseInstance.receivedComplete = true
308                result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(3) // Received Complete.
309            }
310            else {
311                order.receivedComplete = false
312                result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(2) // Received B/oder to Come.
313            }
314
315            // Fetch to prevent lazy initialization error.
316            result.inventoryItemPurchaseInstance.inventoryItem.unitOfMeasure
317
318            if(order.hasErrors() || !order.save())
319                return fail(code:"default.create.failure")
320
321            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
322                return fail(code:"default.create.failure")
323
324            result.inventoryItemId = result.inventoryItemPurchaseInstance.inventoryItem.id
325
326            // Perform the inventory movement.
327            if(result.inventoryItemPurchaseInstance.quantity > 0) {
328                def p = [:]
329                p.inventoryItem = result.inventoryItemPurchaseInstance.inventoryItem
330                p.quantity = result.inventoryItemPurchaseInstance.quantity
331                p.inventoryMovementType = InventoryMovementType.read(3)
332                def movementResult = inventoryMovementService.move(p)
333                if(movementResult.error)
334                    return fail(code:"default.create.failure")
335            }
336
337            // success
338            return result
339
340        } // end withTransaction
341    } // save()
342
343    def approveInvoicePaymentSave(params) {
344        InventoryItemPurchase.withTransaction { status ->
345            def result = [:]
346
347            def fail = { Map m ->
348                status.setRollbackOnly()
349                if(result.inventoryItemPurchaseInstance && m.field)
350                    result.inventoryItemPurchaseInstance.errors.rejectValue(m.field, m.code)
351                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
352                return result
353            }
354
355            def received = InventoryItemPurchase.get(params.receivedId)
356            if(!received)
357                return fail(code:"default.not.found")
358            result.receivedId = received.id
359
360            def order = getOriginalOrder(received)
361            if(!order)
362                return fail(code:"default.not.found")
363            result.orderId = order.id
364
365            result.inventoryItemPurchaseInstance = new InventoryItemPurchase(params)
366            result.inventoryItemPurchaseInstance.enteredBy = authService.currentUser
367            result.inventoryItemPurchaseInstance.purchaseOrderNumber = order.purchaseOrderNumber
368            result.inventoryItemPurchaseInstance.costCode = order.costCode
369            result.inventoryItemPurchaseInstance.orderValueCurrency = order.orderValueCurrency
370            result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(4) // Approve.
371
372            received.invoicePaymentApproved = true
373            result.inventoryItemPurchaseInstance.invoicePaymentApproved = true
374
375            // Update orderValueAmount and invoicePaymentApproved if processing a receivedComplete.
376            if(received.inventoryItemPurchaseType.id == 3L) {
377                order.invoicePaymentApproved = true
378                result.inventoryItemPurchaseInstance.receivedComplete = true
379                def calcQuantities = calcQuantities(order)
380                if(result.inventoryItemPurchaseInstance.orderValueAmount)
381                    calcQuantities.totalPaymentApproved += result.inventoryItemPurchaseInstance.orderValueAmount
382                order.orderValueAmount = calcQuantities.totalPaymentApproved
383            }
384
385            // Fetch to prevent lazy initialization error.
386            result.inventoryItemPurchaseInstance.inventoryItem.unitOfMeasure
387
388            if(!result.inventoryItemPurchaseInstance.invoiceNumber)
389                return fail(field:"invoiceNumber", code:"inventoryItemPurchase.invoiceNumber.required")
390
391            if(order.hasErrors() || !order.save())
392                return fail(code:"default.create.failure")
393
394            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
395                return fail(code:"default.create.failure")
396
397            result.inventoryItemId = result.inventoryItemPurchaseInstance.inventoryItem.id
398
399            // Success..
400            return result
401
402        } // end withTransaction
403    } // approveInvoicePaymentSave()
404
405} // end class
Note: See TracBrowser for help on using the repository browser.