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

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

Improvements to purchasing, update order amount with total invoice payment amounts and set flags.

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