Skip to content

Commit 7030e04

Browse files
authored
OBPIH-6332 Dashboard indicator for number of backdated shipments (#4628)
* OBPIH-6332 Add translations for backdated outbound shipments chart * OBPIH-6332 Add chart config * OBPIH-6332 Add URL mapping * OBPIH-6332 Add controller method for backdated outbound shipments * OBPIH-6332 Make the time limit on charts more configurable * OBPIH-6332 Add getting data for backdated outbound shipments * OBPIH-6332 Add translations for backdated inbound shipments * OBPIH-6332 Add url mapping for indicator data * OBPIH-6332 Add new config properties * OBPIH-6332 Add backdated inbound shipments indicator * OBPIH-6332 Fixes after reviews * OBPIH-6332 Add string interpolation to query * OBPIH-6332 Rename config properties
1 parent 618908d commit 7030e04

7 files changed

Lines changed: 135 additions & 11 deletions

File tree

grails-app/conf/runtime.groovy

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ openboxes.client.notification.autohide.delay = 8000
7373
// Autosave configuration (OBPIH-5493)
7474
openboxes.client.autosave.enabled = false
7575

76+
// Backdata configuration (OBPIH-6332)
77+
openboxes.dashboard.backdatedShipments.daysOffset = 1
78+
openboxes.dashboard.backdatedShipments.monthsLimit = 6
79+
7680
// Merge Products (OBPIH-5453)
7781
openboxes.products.merge.enabled = false
7882

@@ -1105,6 +1109,28 @@ openboxes {
11051109
type = 'number'
11061110
endpoint = "/api/dashboard/openPurchaseOrdersCount"
11071111
}
1112+
backdatedOutboundShipments {
1113+
enabled = true
1114+
title = "react.dashboard.backdatedOutboundShipments.title.label"
1115+
info = "react.dashboard.backdatedOutboundShipments.info.label"
1116+
graphType = "bar"
1117+
type = 'graph'
1118+
endpoint = "/api/dashboard/backdatedOutboundShipments"
1119+
timeFilter = true
1120+
timeLimit = 6
1121+
datalabel = true
1122+
}
1123+
backdatedInboundShipments {
1124+
enabled = true
1125+
title = "react.dashboard.backdatedInboundShipments.title.label"
1126+
info = "react.dashboard.backdatedInboundShipments.info.label"
1127+
graphType = "bar"
1128+
type = 'graph'
1129+
endpoint = "/api/dashboard/backdatedInboundShipments"
1130+
timeFilter = true
1131+
timeLimit = 6
1132+
datalabel = true
1133+
}
11081134
}
11091135
}
11101136
}

grails-app/controllers/org/pih/warehouse/UrlMappings.groovy

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,16 @@ class UrlMappings {
746746
action = [GET: "getOpenPurchaseOrdersCount"]
747747
}
748748

749+
"/api/dashboard/backdatedOutboundShipments" {
750+
controller = { "dashboardApi" }
751+
action = [GET: "getBackdatedOutboundShipments"]
752+
}
753+
754+
"/api/dashboard/backdatedInboundShipments" {
755+
controller = { "dashboardApi" }
756+
action = [GET: "getBackdatedInboundShipments"]
757+
}
758+
749759
/**
750760
* Purchase Orders API endpoints
751761
*/

grails-app/controllers/org/pih/warehouse/api/DashboardApiController.groovy

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ package org.pih.warehouse.api
22

33
import grails.converters.JSON
44
import grails.core.GrailsApplication
5+
import grails.util.Holders
56
import org.pih.warehouse.auth.AuthService
67
import org.pih.warehouse.core.Location
78
import grails.gorm.transactions.Transactional
89
import org.pih.warehouse.core.User
10+
import org.pih.warehouse.dashboard.IndicatorDataService
911
import org.pih.warehouse.dashboard.NumberData
1012

1113
@Transactional
1214
class DashboardApiController {
1315

1416
def numberDataService
15-
def indicatorDataService
17+
IndicatorDataService indicatorDataService
1618
def userService
1719
def messageService
1820
GrailsApplication grailsApplication
@@ -214,4 +216,24 @@ class DashboardApiController {
214216
NumberData numberData = numberDataService.getOpenPurchaseOrdersCount(params)
215217
render(numberData as JSON)
216218
}
219+
220+
def getBackdatedOutboundShipments() {
221+
Integer defaultMonthsLimit = Holders.config.openboxes.dashboard.backdatedShipments.monthsLimit
222+
Integer monthsLimit = params.int('querySize', defaultMonthsLimit)
223+
Map backdatedShipments = indicatorDataService.getBackdatedOutboundShipmentsData(
224+
params.locationId,
225+
monthsLimit > defaultMonthsLimit ? defaultMonthsLimit : monthsLimit
226+
)
227+
render(backdatedShipments as JSON)
228+
}
229+
230+
def getBackdatedInboundShipments() {
231+
Integer defaultMonthsLimit = Holders.config.openboxes.dashboard.backdatedShipments.monthsLimit
232+
Integer monthsLimit = params.int('querySize', defaultMonthsLimit)
233+
Map backdatedShipments = indicatorDataService.getBackdatedInboundShipmentsData(
234+
params.locationId,
235+
monthsLimit > defaultMonthsLimit ? defaultMonthsLimit : monthsLimit
236+
)
237+
render(backdatedShipments as JSON)
238+
}
217239
}

grails-app/i18n/messages.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3691,7 +3691,11 @@ react.dashboard.inventorySummaryData.info.label=Number of products in this wareh
36913691
react.dashboard.sentStockMovements.title.label=Stock Movements Sent by Month
36923692
react.dashboard.sentStockMovements.info.label=Graph of all shipments shipped from this warehouse by origin and month shipped
36933693
react.dashboard.receivedStockData.title.label=Stock Movements Received by Month
3694+
react.dashboard.backdatedOutboundShipments.title.label=Backdated Outbound Shipments
3695+
react.dashboard.backdatedInboundShipments.title.label=Backdated Inbound Shipments
36943696
react.dashboard.receivedStockData.info.label=Graph of all shipments received in this warehouse by origin and month received
3697+
react.dashboard.backdatedOutboundShipments.info.label=Graph of outbound shipments recorded in OpenBoxes several days after they happened in the warehouse, by number of days backdated
3698+
react.dashboard.backdatedInboundShipments.info.label=Graph of inbound shipments recorded in OpenBoxes several days after they happened in the warehouse, by number of days backdated
36953699
react.dashboard.outgoingStock.title.label=Outgoing Stock Movements in Progress
36963700
react.dashboard.outgoingStock.info.label=Number of outgoing shipments created but not issued, broken down by how long they have been in progress
36973701
react.dashboard.incomingStock.title.label=Incoming Stock Movements By Status

grails-app/services/org/pih/warehouse/dashboard/IndicatorDataService.groovy

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import grails.compiler.GrailsTypeChecked
44
import grails.core.GrailsApplication
55
import grails.gorm.transactions.Transactional
66
import grails.plugin.cache.Cacheable
7+
import grails.util.Holders
78
import groovy.transform.TypeCheckingMode
89
import org.grails.plugins.web.taglib.ApplicationTagLib
910
import org.grails.web.json.JSONObject
1011
import org.joda.time.LocalDate
1112
import org.pih.warehouse.LocalizationUtil
13+
import org.pih.warehouse.api.StockMovementDirection
1214
import org.pih.warehouse.core.Location
1315
import org.pih.warehouse.inventory.InventorySnapshot
1416
import org.pih.warehouse.inventory.TransactionCode
@@ -1111,6 +1113,53 @@ class IndicatorDataService {
11111113
return graphData.toJson()
11121114
}
11131115

1116+
Map getBackdatedShipmentsChart(String locationId, Integer monthsLimit, StockMovementDirection direction) {
1117+
Map<String, Integer> data = Constants.backdataAxes
1118+
Date timeLimit = LocalDate.now().minusMonths(monthsLimit).toDate()
1119+
String transactionProperty = direction == StockMovementDirection.OUTBOUND ? 'outgoing_shipment_id' : 'incoming_shipment_id'
1120+
String shipmentLocationProperty = direction == StockMovementDirection.OUTBOUND ? 'origin_id' : 'destination_id'
1121+
String query = """
1122+
SELECT (
1123+
CASE
1124+
WHEN DATEDIFF(t.date_created, t.transaction_date) > 7 THEN '7+ days'
1125+
ELSE CONCAT(DATEDIFF(t.date_created, t.transaction_date), ' days')
1126+
END
1127+
) as days_backdated, COUNT(t.id) as shipments FROM shipment s
1128+
INNER JOIN transaction t ON t.${transactionProperty} = s.id
1129+
WHERE s.${shipmentLocationProperty} = :locationId
1130+
AND t.date_created > :timeLimit
1131+
GROUP BY days_backdated
1132+
HAVING days_backdated > :daysOffset
1133+
"""
1134+
List queryData = dataService.executeQuery(query, [
1135+
locationId: locationId,
1136+
timeLimit: timeLimit,
1137+
daysOffset: Holders.config.openboxes.dashboard.backdatedShipments.daysOffset
1138+
])
1139+
1140+
queryData.each {
1141+
data[it[0]] = it[1]
1142+
}
1143+
1144+
List<IndicatorDatasets> datasets = [
1145+
new IndicatorDatasets('Backdated shipments', data*.value.toList())
1146+
]
1147+
IndicatorData indicatorData = new IndicatorData(datasets, data*.key.toList())
1148+
GraphData graphData = new GraphData(indicatorData)
1149+
1150+
return graphData.toJson()
1151+
}
1152+
1153+
@Cacheable(value = "dashboardCache", key = { "getBackdatedOutboundShipments-${locationId}${monthsLimit}" })
1154+
Map getBackdatedOutboundShipmentsData(String locationId, Integer monthsLimit) {
1155+
return getBackdatedShipmentsChart(locationId, monthsLimit, StockMovementDirection.OUTBOUND)
1156+
}
1157+
1158+
@Cacheable(value = "dashboardCache", key = { "getBackdatedInboundShipments-${locationId}${monthsLimit}" })
1159+
Map getBackdatedInboundShipmentsData(String locationId, Integer monthsLimit) {
1160+
return getBackdatedShipmentsChart(locationId, monthsLimit, StockMovementDirection.INBOUND)
1161+
}
1162+
11141163
private List fillLabels(int querySize) {
11151164
Date today = new Date()
11161165
today.clearTime()

src/js/components/dashboard/GraphCard.jsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,18 @@ class FilterComponent extends Component {
124124
},
125125
)}
126126
</option>
127-
<option value="12">
128-
{this.props.translate(
129-
this.props.label[0],
130-
this.props.label[1],
131-
{
132-
number: '',
133-
timeUnit: this.props.translate('react.dashboard.timeFilter.year.label', 'Year'),
134-
},
135-
)}
136-
</option>
127+
{
128+
(this.props.timeLimit >= 12 || !this.props.timeLimit) && <option value="12">
129+
{this.props.translate(
130+
this.props.label[0],
131+
this.props.label[1],
132+
{
133+
number: '',
134+
timeUnit: this.props.translate('react.dashboard.timeFilter.year.label', 'Year'),
135+
},
136+
)}
137+
</option>
138+
}
137139
{
138140
this.props.timeLimit === 24 &&
139141
<option value="24">

src/main/groovy/org/pih/warehouse/core/Constants.groovy

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,15 @@ class Constants {
156156
static final String ADJUSTMENT = "Adjustment"
157157

158158
static final String UOM_EACH_ID = "EA"
159+
160+
// Dashboard indicator axes
161+
static final Map<String, Integer> backdataAxes = [
162+
'2 days': 0,
163+
'3 days': 0,
164+
'4 days': 0,
165+
'5 days': 0,
166+
'6 days': 0,
167+
'7 days': 0,
168+
'7+ days': 0,
169+
]
159170
}

0 commit comments

Comments
 (0)