1
- /* global moment:false */
2
- import config from './config/config.json'
3
-
4
- // TODO: replace with Array#find ponyfill
5
- const find = ( list , match ) => {
6
- for ( let i = 0 ; i < list . length ; i ++ ) {
7
- if ( match ( list [ i ] ) ) {
8
- return list [ i ]
9
- }
10
- }
11
- return undefined
12
- }
13
-
14
- const isVersion = ( date , version ) => {
15
- const momentDate = moment ( ) . isoWeekYear ( date . year ) . isoWeek ( date . week ) . isoWeekday ( 1 ) . startOf ( 'day' )
16
- const momentVersionStartDate = moment ( version . date , config . versionDateFormat ) . startOf ( 'isoWeek' ) . startOf ( 'day' )
17
- return momentDate . isSameOrAfter ( momentVersionStartDate )
18
- }
19
-
20
- const isId = ( id , item ) => item . _id === id
21
-
22
- const getFactor = ( versions , date ) => {
23
- const reverseVersions = versions . slice ( 0 ) . reverse ( )
24
- const factor = find ( reverseVersions , isVersion . bind ( null , date ) )
25
- // If the doc is too old to have a matching version, default to the oldest one
26
- if ( ! factor ) {
27
- return versions [ 0 ]
28
- }
29
- return factor
30
- }
31
-
32
- const getFactors = ( stockCount , location ) => {
33
- // centralized for whenever we implement #16
34
- const somethingIsWrong = ( ) => undefined
35
-
36
- const getWeeklyLevels = ( ) => {
37
- if ( ! ( location . allocations && location . allocations . length ) ) {
38
- somethingIsWrong ( )
39
- }
40
-
41
- const allocations = getFactor ( location . allocations , stockCount . date )
42
- return allocations && allocations . weeklyLevels
43
- }
44
-
45
- const getWeeksOfStock = ( ) => {
46
- if ( ! ( location . plans && location . plans . length ) ) {
47
- somethingIsWrong ( )
48
- }
49
-
50
- const plans = getFactor ( location . plans , stockCount . date )
51
- return plans && plans . weeksOfStock
52
- }
53
-
54
- const getMonthlyTargetPopulations = ( ) => {
55
- let monthlyTargetPopulations
56
- if ( location . targetPopulations ) {
57
- if ( ! location . targetPopulations . length ) {
58
- somethingIsWrong ( )
59
- }
60
-
61
- const targetPopulations = getFactor ( location . targetPopulations , stockCount . date )
62
- monthlyTargetPopulations = targetPopulations && targetPopulations . monthlyTargetPopulations
63
- } else {
64
- // For backwards compatibility with the old style location docs,
65
- // since we have no control about when the dashboards are going
66
- // to replicate the new location docs
67
- if ( ! ( location . targetPopulation && location . targetPopulation . length ) ) {
68
- somethingIsWrong ( )
69
- }
70
- monthlyTargetPopulations = location . targetPopulation
71
- }
72
- return monthlyTargetPopulations
73
- }
74
-
75
- return {
76
- weeksOfStock : getWeeksOfStock ( ) ,
77
- weeklyLevels : getWeeklyLevels ( ) ,
78
- targetPopulations : getMonthlyTargetPopulations ( )
79
- }
80
- }
1
+ import defaultCoefficients from './config/coefficients.json'
2
+ import { find , somethingIsWrong } from './utils.js'
3
+ import getFactors from './factor-extractor.js'
81
4
82
5
class ThresholdsService {
83
6
constructor ( $q , smartId , lgasService , statesService ) {
@@ -90,38 +13,53 @@ class ThresholdsService {
90
13
// For zones the thresholds are based on the state store required allocation for
91
14
// the week, that information is passed as an optional param (`requiredStateStoresAllocation`).
92
15
// That param is only used for zones.
93
- calculateThresholds ( location , stockCount , products , requiredStateStoresAllocation = { } ) {
94
- if ( ! ( stockCount && stockCount . date ) ) {
95
- return
16
+ //
17
+ // Passing the coefficientVersions as a param so that it can be adapted later to use the database doc
18
+ calculateThresholds ( location , stockCount , products , requiredStateStoresAllocation = { } , productCoefficients = defaultCoefficients ) {
19
+ if ( ! stockCount ) {
20
+ const locationId = location && location . _id ? location . _id : 'with unknown id'
21
+ return somethingIsWrong ( `missing mandatory param stock count for location ${ locationId } ` )
22
+ }
23
+ if ( ! stockCount . date ) {
24
+ return somethingIsWrong ( `missing date on stock count ${ stockCount . _id } ` )
96
25
}
97
26
98
- if ( ! ( location && location . level ) ) {
99
- return
27
+ if ( ! location ) {
28
+ const stockCountId = stockCount && stockCount . _id ? stockCount . _id : 'with unknown id'
29
+ return somethingIsWrong ( `missing mandatory param location for stock count ${ stockCountId } ` )
30
+ }
31
+ if ( ! location . level ) {
32
+ return somethingIsWrong ( `missing level on location ${ location . _id } ` )
100
33
}
101
34
102
35
if ( ! ( products && products . length ) ) {
103
- return
36
+ return somethingIsWrong ( 'missing mandatory param products' )
104
37
}
105
38
106
- const { weeklyLevels, weeksOfStock, targetPopulations } = getFactors ( stockCount , location )
107
-
108
- if ( ! ( weeklyLevels && weeksOfStock ) ) {
39
+ let locationFactors
40
+ try {
41
+ locationFactors = getFactors ( location , productCoefficients , stockCount . date )
42
+ } catch ( e ) {
43
+ somethingIsWrong ( e . message )
109
44
return
110
45
}
111
46
112
- return Object . keys ( weeklyLevels ) . reduce ( ( index , productId ) => {
113
- index [ productId ] = Object . keys ( weeksOfStock ) . reduce ( ( productThresholds , threshold ) => {
114
- const level = weeklyLevels [ productId ] * weeksOfStock [ threshold ]
115
- const product = find ( products , isId . bind ( null , productId ) )
47
+ const { weeksOfStock, weeklyLevels, monthlyTargetPopulations } = locationFactors
116
48
117
- // Default rounding used in VSPMD and highest possible presentation
118
- let presentation = 20
49
+ return products . reduce ( ( index , product ) => {
50
+ const productId = product . _id
51
+ const weeklyLevel = weeklyLevels [ productId ]
119
52
120
- if ( product && product . presentation ) {
121
- // TODO: product presentations should be ints, not strings
122
- presentation = parseInt ( product . presentation , 10 )
123
- }
53
+ // Default rounding used in VSPMD and highest possible presentation
54
+ let presentation = 20
124
55
56
+ if ( product && product . presentation ) {
57
+ // TODO: product presentations should be ints, not strings
58
+ presentation = parseInt ( product . presentation , 10 )
59
+ }
60
+
61
+ index [ productId ] = Object . keys ( weeksOfStock ) . reduce ( ( productThresholds , threshold ) => {
62
+ const level = weeklyLevel * weeksOfStock [ threshold ]
125
63
const roundedLevel = Math . ceil ( level / presentation ) * presentation
126
64
productThresholds [ threshold ] = roundedLevel
127
65
@@ -132,15 +70,19 @@ class ThresholdsService {
132
70
return productThresholds
133
71
} , { } )
134
72
135
- if ( targetPopulations ) { // old (and new?) zone docs have no target population doc
136
- index [ productId ] . targetPopulation = targetPopulations [ productId ]
73
+ index [ productId ] . weeklyLevel = weeklyLevel
74
+
75
+ if ( monthlyTargetPopulations ) { // old zone docs have no target population
76
+ index [ productId ] . targetPopulation = monthlyTargetPopulations [ productId ]
137
77
}
138
78
139
79
return index
140
80
} , { } )
141
81
}
142
82
143
- getThresholdsFor ( stockCounts , products ) {
83
+ getThresholdsFor ( stockCounts , products , productCoefficients = defaultCoefficients ) {
84
+ const isId = ( id , item ) => item . _id === id
85
+
144
86
// TODO: make it work for zones too.
145
87
// For making it work with zones, we need to take into account the amount of stock
146
88
// to be allocated to the zone state stores in a particular week
@@ -176,7 +118,7 @@ class ThresholdsService {
176
118
Object . keys ( index ) . forEach ( ( key ) => {
177
119
const item = index [ key ]
178
120
const location = find ( promisesRes [ item . type ] , isId . bind ( null , key ) )
179
- item . thresholds = this . calculateThresholds ( location , item , products )
121
+ item . thresholds = this . calculateThresholds ( location , item , products , null , productCoefficients )
180
122
delete item . type
181
123
} )
182
124
0 commit comments