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

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

Prevent reordering of obsolete or inactive inventory items.

File size: 18.5 KB
RevLine 
[441]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.
[596]12    * @returns A result map containing the totalOrdered, totalReceived, totalRemaining, thisOrderRemaining,
[597]13    *                 totalOrderedAmount, totalReceivedAmount, totalRemainingAmount, thisOrderRemainingAmount,
14    *                 totalPaymentApproved.
[441]15    */
16    def calcQuantities(order) {
17
18        def result = [:]
19
20        result.totalOrdered = 0
[596]21        result.totalOrderedAmount = 0
[441]22        result.totalReceived = 0
[596]23        result.totalReceivedAmount = 0
[597]24        result.totalPaymentApproved = 0
[441]25        InventoryItemPurchase.withCriteria {
26            eq("inventoryItem", order.inventoryItem)
27            eq("purchaseOrderNumber", order.purchaseOrderNumber)
[597]28        }.each() {
29            if(it.inventoryItemPurchaseType.id == 1L) { // Orders.
30                result.totalOrdered += it.quantity
31                result.totalOrderedAmount += it.orderValueAmount
[441]32            }
[597]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            }
[441]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
[596]48        result.totalRemainingAmount
49        if(result.totalOrderedAmount > result.totalReceivedAmount)
50            result.totalRemainingAmount = result.totalOrderedAmount - result.totalReceivedAmount
51        else
52            result.totalRemainingAmount = 0
53
[441]54        result.thisOrderRemaining
55        if(result.totalRemaining > order.quantity)
56            result.thisOrderRemaining = order.quantity
57        else
58            result.thisOrderRemaining = result.totalRemaining
59
[596]60        result.thisOrderRemainingAmount
61        if(result.totalRemainingAmount > order.orderValueAmount)
62            result.thisOrderRemainingAmount = order.orderValueAmount
63        else
64            result.thisOrderRemainingAmount = result.totalRemainingAmount
65
[441]66        return result
67    }
68
[597]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
[441]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) {
[597]112                // Find and mark all orders as invoicePaymentApproved = false.
[441]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                }
[597]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
[441]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
[605]152                def startOfDay = dateUtilService.getMidnight(result.inventoryItemPurchaseInstance.date)
[441]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 {
[597]158                        eq("id", 3L) // purchaseReceived.
[441]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
[600]235            result.inventoryItemPurchaseInstance.purchaseOrderNumber = result.inventoryItemPurchaseInstance.purchaseOrderNumber.trim()
[605]236            result.inventoryItemPurchaseInstance.lastUpdatedBy = authService.currentUser
[441]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)
[600]259            result.inventoryItemPurchaseInstance.purchaseOrderNumber = result.inventoryItemPurchaseInstance.purchaseOrderNumber.trim()
[441]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
[610]266            // Prevent ordering on obsolete or inactive inventoryItem.
267            def isObsolete = result.inventoryItemPurchaseInstance.inventoryItem?.isObsolete
268            def isActive = result.inventoryItemPurchaseInstance.inventoryItem?.isActive
269            if(isObsolete || !isActive)
270                return fail(code:"inventoryItemPurchase.operation.not.permitted.on.inactive.or.obsolete.item")
271
[441]272            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
273                return fail(code:"default.create.failure")
274
275            result.inventoryItemId = result.inventoryItemPurchaseInstance.inventoryItem.id
276
277            // success
278            return result
279
280        } // end withTransaction
281    } // save()
282
283    def receiveSave(params) {
284        InventoryItemPurchase.withTransaction { status ->
285            def result = [:]
286
287            def fail = { Map m ->
288                status.setRollbackOnly()
289                if(result.inventoryItemPurchase && m.field)
290                    result.inventoryItemPurchase.errors.rejectValue(m.field, m.code)
291                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
292                return result
293            }
294
295            def order = InventoryItemPurchase.get(params.orderId)
296            if(!order)
297                return fail(code:"default.not.found")
298            result.orderId = order.id
299
300            result.inventoryItemPurchaseInstance = new InventoryItemPurchase(params)
301            result.inventoryItemPurchaseInstance.enteredBy = authService.currentUser
302            result.inventoryItemPurchaseInstance.purchaseOrderNumber = order.purchaseOrderNumber
303            result.inventoryItemPurchaseInstance.costCode = order.costCode
304            result.inventoryItemPurchaseInstance.orderValueCurrency = order.orderValueCurrency
305
306            def calcQuantities = calcQuantities(order)
307            if(result.inventoryItemPurchaseInstance.quantity)
308                calcQuantities.totalReceived += result.inventoryItemPurchaseInstance.quantity
[596]309            if(result.inventoryItemPurchaseInstance.orderValueAmount)
310                calcQuantities.totalReceivedAmount += result.inventoryItemPurchaseInstance.orderValueAmount
[441]311
312            if(calcQuantities.totalReceived >= calcQuantities.totalOrdered) {
313                order.receivedComplete = true
314                result.inventoryItemPurchaseInstance.receivedComplete = true
315                result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(3) // Received Complete.
316            }
317            else {
318                order.receivedComplete = false
319                result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(2) // Received B/oder to Come.
320            }
321
322            // Fetch to prevent lazy initialization error.
323            result.inventoryItemPurchaseInstance.inventoryItem.unitOfMeasure
[605]324            result.inventoryItemPurchaseInstance.inventoryItem.inventoryLocation
[441]325
326            if(order.hasErrors() || !order.save())
327                return fail(code:"default.create.failure")
328
329            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
330                return fail(code:"default.create.failure")
331
332            result.inventoryItemId = result.inventoryItemPurchaseInstance.inventoryItem.id
333
334            // Perform the inventory movement.
335            if(result.inventoryItemPurchaseInstance.quantity > 0) {
336                def p = [:]
337                p.inventoryItem = result.inventoryItemPurchaseInstance.inventoryItem
338                p.quantity = result.inventoryItemPurchaseInstance.quantity
339                p.inventoryMovementType = InventoryMovementType.read(3)
340                def movementResult = inventoryMovementService.move(p)
341                if(movementResult.error)
342                    return fail(code:"default.create.failure")
343            }
344
345            // success
346            return result
347
348        } // end withTransaction
[605]349    } // receiveSave()
[441]350
351    def approveInvoicePaymentSave(params) {
352        InventoryItemPurchase.withTransaction { status ->
353            def result = [:]
354
355            def fail = { Map m ->
356                status.setRollbackOnly()
357                if(result.inventoryItemPurchaseInstance && m.field)
358                    result.inventoryItemPurchaseInstance.errors.rejectValue(m.field, m.code)
359                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
360                return result
361            }
362
[597]363            def received = InventoryItemPurchase.get(params.receivedId)
364            if(!received)
365                return fail(code:"default.not.found")
366            result.receivedId = received.id
367
368            def order = getOriginalOrder(received)
[441]369            if(!order)
370                return fail(code:"default.not.found")
371            result.orderId = order.id
372
373            result.inventoryItemPurchaseInstance = new InventoryItemPurchase(params)
374            result.inventoryItemPurchaseInstance.enteredBy = authService.currentUser
375            result.inventoryItemPurchaseInstance.purchaseOrderNumber = order.purchaseOrderNumber
376            result.inventoryItemPurchaseInstance.costCode = order.costCode
377            result.inventoryItemPurchaseInstance.orderValueCurrency = order.orderValueCurrency
378            result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(4) // Approve.
379
[597]380            received.invoicePaymentApproved = true
[441]381            result.inventoryItemPurchaseInstance.invoicePaymentApproved = true
382
[597]383            // Update orderValueAmount and invoicePaymentApproved if processing a receivedComplete.
384            if(received.inventoryItemPurchaseType.id == 3L) {
385                order.invoicePaymentApproved = true
386                result.inventoryItemPurchaseInstance.receivedComplete = true
387                def calcQuantities = calcQuantities(order)
388                if(result.inventoryItemPurchaseInstance.orderValueAmount)
389                    calcQuantities.totalPaymentApproved += result.inventoryItemPurchaseInstance.orderValueAmount
390                order.orderValueAmount = calcQuantities.totalPaymentApproved
391            }
392
[441]393            // Fetch to prevent lazy initialization error.
394            result.inventoryItemPurchaseInstance.inventoryItem.unitOfMeasure
395
396            if(!result.inventoryItemPurchaseInstance.invoiceNumber)
397                return fail(field:"invoiceNumber", code:"inventoryItemPurchase.invoiceNumber.required")
398
399            if(order.hasErrors() || !order.save())
400                return fail(code:"default.create.failure")
401
402            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
403                return fail(code:"default.create.failure")
404
405            result.inventoryItemId = result.inventoryItemPurchaseInstance.inventoryItem.id
406
407            // Success..
408            return result
409
410        } // end withTransaction
411    } // approveInvoicePaymentSave()
412
413} // end class
Note: See TracBrowser for help on using the repository browser.