source: branches/features/purchaseOrders/grails-app/services/InventoryPurchaseService.groovy @ 954

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

Formatting only, no code change.

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