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

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

Add CostCode and InventoryItemPurchase domain classes with import features.
Includes some fixes to inventory imports, where manufacturer and supplier were crossed.

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