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

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

Improvements to purchasing, calculate remaining value amounts.

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