From d19488bb44538f345a713b01e4ee6cabf6d7a73a Mon Sep 17 00:00:00 2001 From: jmiranda Date: Tue, 4 Aug 2015 22:39:49 -0500 Subject: [PATCH 01/12] reverted inline liquibase plugin change to deal with bamboo build error --- application.properties | 1 + grails-app/conf/BuildConfig.groovy | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/application.properties b/application.properties index 7eee4bee48f..237536ee630 100644 --- a/application.properties +++ b/application.properties @@ -28,6 +28,7 @@ plugins.joda-time=1.4 plugins.jquery=1.11.1 plugins.jquery-ui=1.8.7 plugins.ldap=0.8.2 +plugins.liquibase=1.9.3.6 plugins.pretty-time=0.3 plugins.profile-template=0.1 plugins.resources=1.1.6 diff --git a/grails-app/conf/BuildConfig.groovy b/grails-app/conf/BuildConfig.groovy index ef591e268ea..2b0069385ba 100644 --- a/grails-app/conf/BuildConfig.groovy +++ b/grails-app/conf/BuildConfig.groovy @@ -16,7 +16,7 @@ grails.project.test.reports.dir = "target/test-reports" //grails.project.war.file = "target/${appName}-${appVersion}.war" grails.plugin.location.spock='spock/' -grails.plugin.location.liquibase='liquibase/' +//grails.plugin.location.liquibase='liquibase/' // Development configuration property used to enable xrebel features //grails.tomcat.jvmArgs = ["-javaagent:/home/jmiranda/Desktop/xrebel/xrebel.jar"] From de2b45122d96f93f335ae6a78619e2e4d25b90d6 Mon Sep 17 00:00:00 2001 From: jmiranda Date: Tue, 4 Aug 2015 22:47:03 -0500 Subject: [PATCH 02/12] changed the cron expression for the inventory snapshot background process --- .../jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grails-app/jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy b/grails-app/jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy index f2b9487b93f..6e1b9168569 100755 --- a/grails-app/jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy +++ b/grails-app/jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy @@ -15,7 +15,7 @@ class CalculateQuantityJob { static triggers = { //simple startDelay: 300000, repeatInterval: 1000l * 60 * 10 * 1 * 60 * 24; // startDelay: 5 minutes, repeatInterval: every 24 hours - cron name:'cronTrigger', cronExpression: '0 0 3 * * ?' // cronExpression: at 4am + cron name:'cronTrigger', cronExpression: '0 15 4 * * ?' // cronExpression: at 4am //cron name:'cronTrigger', startDelay:300000, cronExpression: '0/6 * 15 * * ?' // cronExpression: } From 27b9231ce82b89692d633e67c466d6e27efd514b Mon Sep 17 00:00:00 2001 From: jmiranda Date: Wed, 5 Aug 2015 14:30:39 -0500 Subject: [PATCH 03/12] more dependency gymnastics to fix bamboo build (Error executing script TestApp: java.lang.ClassNotFoundException: grails.plugin.spock.test.GrailsSpecTestType) --- application.properties | 2 +- grails-app/conf/BuildConfig.groovy | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/application.properties b/application.properties index 237536ee630..27b52ce670b 100644 --- a/application.properties +++ b/application.properties @@ -28,7 +28,7 @@ plugins.joda-time=1.4 plugins.jquery=1.11.1 plugins.jquery-ui=1.8.7 plugins.ldap=0.8.2 -plugins.liquibase=1.9.3.6 +#plugins.liquibase=1.9.3.6 plugins.pretty-time=0.3 plugins.profile-template=0.1 plugins.resources=1.1.6 diff --git a/grails-app/conf/BuildConfig.groovy b/grails-app/conf/BuildConfig.groovy index 2b0069385ba..000f2b8b9d5 100644 --- a/grails-app/conf/BuildConfig.groovy +++ b/grails-app/conf/BuildConfig.groovy @@ -16,7 +16,7 @@ grails.project.test.reports.dir = "target/test-reports" //grails.project.war.file = "target/${appName}-${appVersion}.war" grails.plugin.location.spock='spock/' -//grails.plugin.location.liquibase='liquibase/' +grails.plugin.location.liquibase='liquibase/' // Development configuration property used to enable xrebel features //grails.tomcat.jvmArgs = ["-javaagent:/home/jmiranda/Desktop/xrebel/xrebel.jar"] @@ -73,6 +73,8 @@ grails.project.dependency.resolution = { test 'org.seleniumhq.selenium:selenium-ie-driver:2.25.0' test 'org.seleniumhq.selenium:selenium-support:2.25.0' test 'dumbster:dumbster:1.6' + test "org.spockframework:spock-grails-support:0.6-groovy-1.7" + } plugins { @@ -90,12 +92,19 @@ grails.project.dependency.resolution = { test(name:'spock', version:'0.6') */ + + + //runtime(":liquibase:1.9.3.6") { excludes 'data-source' } runtime(':mail:1.0.6') { excludes 'mail', 'spring-test' } runtime(':excel-import:0.3') { excludes 'poi-contrib', 'poi-scratchpad' } runtime(':hibernate:1.3.7') { excludes 'antlr' } runtime(':external-config-reload:1.4.0') { exclude 'spock-grails-support' } runtime(':quartz2:2.1.6.2') - test (name:'geb', version:'0.6.3') + + //test(":spock:0.6") { + // exclude "spock-grails-support" + //} + test(name:'geb', version:'0.6.3') { } // Dependencies that we want to use but cannot due to errors From 762b7ae7eb16c5405dddb169bc17ff3b2ee0a8f9 Mon Sep 17 00:00:00 2001 From: jmiranda Date: Wed, 5 Aug 2015 14:32:02 -0500 Subject: [PATCH 04/12] changed the cron expression (for testing purposes) --- .../jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grails-app/jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy b/grails-app/jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy index 6e1b9168569..22c4b459f95 100755 --- a/grails-app/jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy +++ b/grails-app/jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy @@ -15,7 +15,7 @@ class CalculateQuantityJob { static triggers = { //simple startDelay: 300000, repeatInterval: 1000l * 60 * 10 * 1 * 60 * 24; // startDelay: 5 minutes, repeatInterval: every 24 hours - cron name:'cronTrigger', cronExpression: '0 15 4 * * ?' // cronExpression: at 4am + cron name:'cronTrigger', cronExpression: '0 0 20 * * ?' // cronExpression: at 4am //cron name:'cronTrigger', startDelay:300000, cronExpression: '0/6 * 15 * * ?' // cronExpression: } From 4a2707d14483cc586dedd9c781eb85c1dc176265 Mon Sep 17 00:00:00 2001 From: jmiranda Date: Wed, 5 Aug 2015 20:36:51 -0500 Subject: [PATCH 05/12] added controller for administering the quartz jobs and triggers --- .../pih/warehouse/jobs/JobsController.groovy | 133 ++++++++++++++++++ grails-app/views/jobs/list.gsp | 91 ++++++++++++ grails-app/views/jobs/show.gsp | 96 +++++++++++++ 3 files changed, 320 insertions(+) create mode 100644 grails-app/controllers/org/pih/warehouse/jobs/JobsController.groovy create mode 100644 grails-app/views/jobs/list.gsp create mode 100644 grails-app/views/jobs/show.gsp diff --git a/grails-app/controllers/org/pih/warehouse/jobs/JobsController.groovy b/grails-app/controllers/org/pih/warehouse/jobs/JobsController.groovy new file mode 100644 index 00000000000..1deb3001d35 --- /dev/null +++ b/grails-app/controllers/org/pih/warehouse/jobs/JobsController.groovy @@ -0,0 +1,133 @@ +/** +* Copyright (c) 2012 Partners In Health. All rights reserved. +* The use and distribution terms for this software are covered by the +* Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +* which can be found in the file epl-v10.html at the root of this distribution. +* By using this software in any fashion, you are agreeing to be bound by +* the terms of this license. +* You must not remove this notice, or any other, from this software. +**/ +package org.pih.warehouse.jobs + +import grails.converters.JSON +import grails.plugin.quartz2.TriggerHelper +import org.quartz.CronTrigger +import org.quartz.JobDataMap +import org.quartz.JobDetail +import org.quartz.JobKey +import org.quartz.Scheduler +import org.quartz.Trigger +import org.quartz.TriggerBuilder +import org.quartz.TriggerKey +import org.quartz.core.QuartzScheduler +import org.quartz.impl.StdScheduler +import org.quartz.impl.StdSchedulerFactory + +import java.text.ParseException + +class JobsController { + + StdScheduler quartzScheduler + + def index = { + redirect(action: "list", params: params) + } + + def list = { + log.info "Jobs" + //Scheduler quartzScheduler = ctx.getBean('quartzScheduler') + //scheduler.start(); + //scheduler.scheduleJob(job, trigger); + + Set jobKeys = quartzScheduler.getJobKeys() + + + //JobKey jobKey = JobKey.jobKey("org.pih.warehouse.jobs.CalculateQuantityJob") + //JobDetail jobDetail = quartzScheduler.getJobDetail(jobKey) + + //def triggers = quartzScheduler.getTriggersOfJob(jobKey) + +// TriggerBuilder.newTrigger().forJob(jobKey). +// CronTrigger trigger = new CronTrigger( +// triggerName, triggerGroupName, +// jobName, jobGroupName, +// cronString); +// +// JobDataMap jobDataMap = new JobDataMap(); +// jobDetail.setJobDataMap(jobDataMap); +// +// quartzScheduler.scheduleJob(jobDetail, trigger); + + [jobKeys: jobKeys] + } + + def show = { + JobKey jobKey = JobKey.jobKey(params.id) + JobDetail jobDetail = quartzScheduler.getJobDetail(jobKey) + + log.info(jobKey); + log.info(jobDetail); + def triggers = quartzScheduler.getTriggersOfJob(jobKey) + + [jobDetail:jobDetail, jobKey: jobKey, triggers:triggers] + } + + + + def unscheduleJob = { + // find jobKey of job + JobKey jobKey = JobKey.jobKey(params.id) + if (jobKey) { + JobDetail jobDetail = quartzScheduler.getJobDetail(jobKey) + + // get list of existing triggers + def triggersList = quartzScheduler.getTriggersOfJob(jobKey) + triggersList.each { + log.info "Unscheduling trigger " + it + // remove all existing triggers + quartzScheduler.unscheduleJob(it.key) + } + } + else { + flash.message = "Unable to find job with jobKey = ${params.id}" + } + redirect(action: "show", id: params.id) + } + + def unscheduleTrigger = { + // find jobKey of job + + TriggerKey triggerKey = TriggerKey.triggerKey(params.id) + Trigger trigger = quartzScheduler.getTrigger(triggerKey) + JobKey jobKey = trigger.jobKey + if (trigger) { + quartzScheduler.unscheduleJob(triggerKey) + } + else { + flash.message = "Unable to unschedule trigger with trigger key ${params.id}" + } + redirect(action: "show", id: jobKey.name) + } + + def scheduleJob = { + JobKey jobKey = JobKey.jobKey(params.id) + if (jobKey) { + //JobDetail jobDetail = quartzScheduler.getJobDetail(jobKey) + // cronExpression 0 0 22 * * ? + try { + Trigger trigger = TriggerHelper.cronTrigger(jobKey, params.cronExpression, [:]) + def date = quartzScheduler.scheduleJob(trigger) + flash.message = "Job ${jobKey} scheduled " + date + } catch (ParseException e) { + flash.message = "Unable to schedule job with cron expression ${params.cronExpression} due to the following error: " + e.message + } + } + else { + flash.message = "Unable to find job with jobKey = ${params.id}" + } + + redirect(action: "show", id: params.id) + + } + +} diff --git a/grails-app/views/jobs/list.gsp b/grails-app/views/jobs/list.gsp new file mode 100644 index 00000000000..8a69fc1c044 --- /dev/null +++ b/grails-app/views/jobs/list.gsp @@ -0,0 +1,91 @@ + + + + + <warehouse:message code="users.label" /> + + +
+ +
${flash.message}
+
+ + +
+ + +
+ +
+
+
+

+ +
    +
  • +

    + +

    + +
  • +
  • +

    + +

    + + +
  • +
  • +
    +
  • +
  • + +
  • +
+
+
+ + +
+
+ +
+

(${jobKeys.size()} results)

+ + + + + + + + + + + + + + + + + + + + +
job key
${jobKey.name}${jobKey.properties}
+

+ +

+
+
+
+ +
+
+
+
+ + + diff --git a/grails-app/views/jobs/show.gsp b/grails-app/views/jobs/show.gsp new file mode 100644 index 00000000000..06a7870edfe --- /dev/null +++ b/grails-app/views/jobs/show.gsp @@ -0,0 +1,96 @@ +<%@ page import="org.quartz.impl.triggers.CronTriggerImpl" %> + + + + + <warehouse:message code="users.label" /> + + +
+ +
${flash.message}
+
+ + +
+ + +
+ +
+
+ + +
+

${jobDetail.name}

+ + + + + + + + + + + +
${property.key}${property.value}
Triggers + + + + + + + + + + + + + + + + + + + + + + + +
IDSummaryPrevious fire timeNext fire timeActions
${trigger.key} + + ${trigger?.cronExpression} + (${trigger?.expressionSummary}) + + + + ${trigger.properties} + + + ${trigger.previousFireTime}${trigger.nextFireTime} + Delete + +
+ There are no triggers for job ${jobDetail} +
+ +
+ + + + + + +
+ +
+ +
+ +
+
+
+ + + From cce713b4f0f0b097f43b933fda874ba0b1b3e60c Mon Sep 17 00:00:00 2001 From: jmiranda Date: Fri, 7 Aug 2015 15:14:11 -0500 Subject: [PATCH 06/12] downgraded to jquery 1.7.2 due to issue with deprecated live() function (see http://stackoverflow.com/questions/14354040/jquery-1-9-live-is-not-a-function) --- application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application.properties b/application.properties index 27b52ce670b..3ab9ec3d7be 100644 --- a/application.properties +++ b/application.properties @@ -25,7 +25,7 @@ plugins.google-visualization=0.6.2 plugins.grails-ui=1.2.3 plugins.image-builder=0.2 plugins.joda-time=1.4 -plugins.jquery=1.11.1 +plugins.jquery=1.7.2 plugins.jquery-ui=1.8.7 plugins.ldap=0.8.2 #plugins.liquibase=1.9.3.6 From 8de03f1a93b1b9a1dc7bdbd72156e4594962de01 Mon Sep 17 00:00:00 2001 From: jmiranda Date: Mon, 10 Aug 2015 08:13:06 -0500 Subject: [PATCH 07/12] changed the cron expression for the inventory item snapshot job --- .../jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/grails-app/jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy b/grails-app/jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy index 22c4b459f95..5fb6a1f6660 100755 --- a/grails-app/jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy +++ b/grails-app/jobs/org/pih/warehouse/jobs/CalculateQuantityJob.groovy @@ -14,9 +14,9 @@ class CalculateQuantityJob { def mailService static triggers = { - //simple startDelay: 300000, repeatInterval: 1000l * 60 * 10 * 1 * 60 * 24; // startDelay: 5 minutes, repeatInterval: every 24 hours - cron name:'cronTrigger', cronExpression: '0 0 20 * * ?' // cronExpression: at 4am - //cron name:'cronTrigger', startDelay:300000, cronExpression: '0/6 * 15 * * ?' // cronExpression: + // cron job needs to be triggered after the staging deployment + cron name:'cronTrigger', cronExpression: '0 30 7 * * ?' // cronExpression: at 4am + } def execute(context) { From 3a61d59344c07592a049b80258d92a67895c32ed Mon Sep 17 00:00:00 2001 From: jmiranda Date: Thu, 13 Aug 2015 15:08:29 -0500 Subject: [PATCH 08/12] Improved performance of the expired and expiring reports linked from the dashboard (when inventory item snapshot records are available) --- .../inventory/InventoryController.groovy | 22 +++--- grails-app/i18n/messages.properties | 2 +- .../inventory/InventoryService.groovy | 73 +++++++++++++++---- .../views/inventory/_actionsExpiredStock.gsp | 4 +- .../views/inventory/_actionsExpiringStock.gsp | 2 +- .../views/inventory/listExpiredStock.gsp | 3 +- 6 files changed, 76 insertions(+), 30 deletions(-) diff --git a/grails-app/controllers/org/pih/warehouse/inventory/InventoryController.groovy b/grails-app/controllers/org/pih/warehouse/inventory/InventoryController.groovy index 23ce4599d46..a40266685d2 100755 --- a/grails-app/controllers/org/pih/warehouse/inventory/InventoryController.groovy +++ b/grails-app/controllers/org/pih/warehouse/inventory/InventoryController.groovy @@ -663,13 +663,13 @@ class InventoryController { def listExpiredStock = { - def warehouse = Location.get(session.warehouse.id) + def location = Location.get(session.warehouse.id) def categorySelected = (params.category) ? Category.get(params.category) : null; - def expiredStock = inventoryService.getExpiredStock(categorySelected, warehouse); - def categories = expiredStock?.collect { it.product.category }?.unique() - def quantityMap = inventoryService.getQuantityForInventory(warehouse.inventory) + def inventoryItems = inventoryService.getExpiredStock(categorySelected, location); + def categories = inventoryItems?.collect { it.product.category }?.unique() + def quantityMap = inventoryService.getQuantityByLocation(location) def expiredStockMap = [:] - expiredStock.each { inventoryItem -> + inventoryItems.each { inventoryItem -> expiredStockMap[inventoryItem] = quantityMap[inventoryItem] } if (params.format == "csv") { @@ -679,7 +679,7 @@ class InventoryController { return; } - [inventoryItems:expiredStock, quantityMap:quantityMap, categories:categories, categorySelected:categorySelected] + [inventoryItems:inventoryItems, quantityMap:quantityMap, categories:categories, categorySelected:categorySelected] } @@ -687,11 +687,11 @@ class InventoryController { def threshold = (params.threshold) ? params.threshold as int : 0; def category = (params.category) ? Category.get(params.category) : null; def location = Location.get(session.warehouse.id) - def expiringStock = inventoryService.getExpiringStock(category, location, threshold) - def categories = expiringStock?.collect { it?.product?.category }?.unique().sort { it.name } ; - def quantityMap = inventoryService.getQuantityForInventory(location.inventory) + def inventoryItems = inventoryService.getExpiringStock(category, location, threshold) + def categories = inventoryItems?.collect { it?.product?.category }?.unique().sort { it.name } ; + def quantityMap = inventoryService.getQuantityByLocation(location) def expiringStockMap = [:] - expiringStock.each { inventoryItem -> + inventoryItems.each { inventoryItem -> expiringStockMap[inventoryItem] = quantityMap[inventoryItem] } @@ -702,7 +702,7 @@ class InventoryController { return; } - [inventoryItems:expiringStock, quantityMap:quantityMap, categories:categories, + [inventoryItems:inventoryItems, quantityMap:quantityMap, categories:categories, categorySelected:category, thresholdSelected:threshold ] } diff --git a/grails-app/i18n/messages.properties b/grails-app/i18n/messages.properties index 2a7eae9578c..63ae5a47509 100755 --- a/grails-app/i18n/messages.properties +++ b/grails-app/i18n/messages.properties @@ -789,7 +789,7 @@ inventory.listReorderStock.label = Below reorder inventory.listOverStock.label = Above maximum inventory.listReconditionedStock.label = Recondition inventory.listExpiredStock.label = Expired -inventory.listExpiringStock.label = Expiring within {0} days +inventory.listExpiringStock.label = Expiring within {0} days diff --git a/grails-app/services/org/pih/warehouse/inventory/InventoryService.groovy b/grails-app/services/org/pih/warehouse/inventory/InventoryService.groovy index 889fb7a768e..e51ccc0ea6f 100755 --- a/grails-app/services/org/pih/warehouse/inventory/InventoryService.groovy +++ b/grails-app/services/org/pih/warehouse/inventory/InventoryService.groovy @@ -654,6 +654,19 @@ class InventoryService implements ApplicationContextAware { } + def getInventoryItemSnapshot(Location location, Integer daysToExpiry) { + def results = InventoryItemSnapshot.executeQuery(""" + SELECT a.inventoryItem, a.quantityOnHand, DATEDIFF(a.inventoryItem.expirationDate, current_date) as daysToExpiry + FROM InventoryItemSnapshot a + WHERE a.date = (select max(b.date) from InventoryItemSnapshot b) + AND a.location = :location + AND a.quantityOnHand > 0 + AND DATEDIFF(a.inventoryItem.expirationDate, current_date) <= :daysToExpiry + ORDER BY a.inventoryItem.expirationDate ASC + """, [location:location, daysToExpiry:daysToExpiry]) + return results + } + /** * Get all expired inventory items for the given category and location. @@ -665,22 +678,15 @@ class InventoryService implements ApplicationContextAware { List getExpiredStock(Category category, Location location) { long startTime = System.currentTimeMillis() - def today = new Date(); - // Stock that has already expired - def expiredStock = InventoryItem.findAllByExpirationDateLessThan(today, [sort: 'expirationDate', order: 'desc']); + def expiredStock = InventoryItem.findAllByExpirationDateLessThan(new Date(), [sort: 'expirationDate', order: 'desc']); log.debug expiredStock - def quantityMap = getQuantityForInventory(location.inventory) + Map quantityMap = getQuantityByLocation(location) expiredStock = expiredStock.findAll { quantityMap[it] > 0 } - // Get the set of categories BEFORE we filter - //def categories = [] as Set - //categories.addAll(expiredStock.collect { it.product.category }) - //categories = categories.findAll { it != null } - // FIXME poor man's filter if (category) { expiredStock = expiredStock.findAll { item -> item?.product?.category == category } @@ -705,7 +711,7 @@ class InventoryService implements ApplicationContextAware { // Get all stock expiring ever (we'll filter later) def expiringStock = InventoryItem.findAllByExpirationDateGreaterThan(today + 1, [sort: 'expirationDate', order: 'asc']); - def quantityMap = getQuantityForInventory(location.inventory) + def quantityMap = getQuantityByLocation(location) expiringStock = expiringStock.findAll { quantityMap[it] > 0 } if (category) { expiringStock = expiringStock.findAll { item -> item?.product?.category == category } @@ -1643,8 +1649,23 @@ class InventoryService implements ApplicationContextAware { return quantityOnHand; } + /** + * Get the most recent quantity on hand from inventory item snapshot table. If there are no + * records in the inventory item snapshot table then we calculate the QoH from transactions. + * + * @param location + * @return + */ + Map getQuantityByLocation(Location location) { + Map quantityMap = getMostRecentInventoryItemSnapshot(location) + if (!quantityMap) { + quantityMap = getQuantityForInventory(location?.inventory) + } + return quantityMap; + } + /** - * Get a map of quantity values for all available inventory items in the given inventory. + * Calculate quantity on hand values for all available inventory items at the given inventory. * * FIXME Use sparingly - this is very expensive because it calculates the QoH over an entire inventory. * @@ -1657,7 +1678,7 @@ class InventoryService implements ApplicationContextAware { } /** - * Get a map of quantity values for all available inventory items in the given inventory. + * Calculate quantity on hand values for all available inventory items in the given inventory. * * @param inventory * @param products @@ -1669,6 +1690,25 @@ class InventoryService implements ApplicationContextAware { } + Map getMostRecentInventoryItemSnapshot(Location location) { + Map quantityMap = [:] + def results = InventoryItemSnapshot.executeQuery(""" + SELECT a.inventoryItem, a.quantityOnHand, DATEDIFF(a.inventoryItem.expirationDate, current_date) as daysToExpiry + FROM InventoryItemSnapshot a + WHERE a.date = (select max(b.date) from InventoryItemSnapshot b) + AND a.location = :location + AND a.quantityOnHand > 0 + ORDER BY a.inventoryItem.expirationDate ASC + """, [location:location]) + + + results.each { + quantityMap[it[0]] = it[1] + } + + return quantityMap + } + /** * Fetches and populates a StockCard Command object * @@ -3877,10 +3917,17 @@ class InventoryService implements ApplicationContextAware { calculateQuantityOnHand(product, location) } return quantityMap + } + def calculateQuantityOnHand(InventoryItem inventoryItem, Location location) { + throw new UnsupportedOperationException("Method has not been implemented yet") } + + def calculateQuantityOnHand(Product product, Location location) { + long startTime = System.currentTimeMillis() + def quantityOnHand = 0 def stockCountDate = null def mostRecentStockCount = getMostRecentQuantityOnHand(product, location) @@ -3898,7 +3945,7 @@ class InventoryService implements ApplicationContextAware { println "quantityDebit: " + quantityDebit println "quantityCredit: " + quantityCredit - + log.info ("Time to calculate quantity on hand: " + (System.currentTimeMillis() - startTime) + " ms") return quantityOnHand - quantityDebit + quantityCredit } diff --git a/grails-app/views/inventory/_actionsExpiredStock.gsp b/grails-app/views/inventory/_actionsExpiredStock.gsp index de93fe1f8c2..bb75f3a9b9a 100644 --- a/grails-app/views/inventory/_actionsExpiredStock.gsp +++ b/grails-app/views/inventory/_actionsExpiredStock.gsp @@ -1,6 +1,6 @@
@@ -26,7 +26,7 @@
- +