1+ using System ;
2+ using System . Linq ;
3+ using System . Text . Json ;
4+ using System . Collections . Generic ;
5+
6+ namespace PayrollEngine . Client . Model ;
7+
8+ /// <summary>Extension methods for lookup sets</summary>
9+ /// <remarks>Code duplicated in PayrollEngine.Domain.Model</remarks>
10+ public static class LookupSetExtensions
11+ {
12+ /// <summary>Apply range value</summary>
13+ /// <param name="lookup">The lookup</param>
14+ /// <param name="rangeValue">The range value</param>
15+ /// <param name="valueFieldName">Value field name</param>
16+ /// <returns>Lookup range value</returns>
17+ public static decimal ApplyRangeValue ( this ILookupSet lookup , decimal rangeValue , string valueFieldName = null )
18+ {
19+ if ( rangeValue == 0 )
20+ {
21+ return 0 ;
22+ }
23+ switch ( lookup . RangeMode )
24+ {
25+ case LookupRangeMode . Threshold :
26+ return ApplyThresholdRangeValue ( lookup , rangeValue , valueFieldName ) ;
27+ case LookupRangeMode . Progressive :
28+ return ApplyProgressiveRangeValue ( lookup , rangeValue , valueFieldName ) ;
29+ }
30+ return 0 ;
31+ }
32+
33+ #region Threshold
34+
35+ /// <summary>Apply progressive factor value</summary>
36+ /// <param name="lookup">The lookup</param>
37+ /// <param name="rangeValue">The range value</param>
38+ /// <param name="valueFieldName">Value field name</param>
39+ /// <remarks>The first lookup range value must be zero.</remarks>
40+ /// <returns>Summary of all lookup ranges</returns>
41+ public static decimal ApplyThresholdRangeValue ( this ILookupSet lookup , decimal rangeValue , string valueFieldName = null )
42+ {
43+ // ranges
44+ var ranges = GetLookupRanges ( lookup , valueFieldName ) ;
45+
46+ // select threshold range
47+ var result = ranges ? . FirstOrDefault ( x => x . IsThreshold ( rangeValue ) ) ;
48+ var factor = result ? . Factor ?? 0 ;
49+ return rangeValue * factor ;
50+ }
51+
52+ #endregion
53+
54+ #region Progressive
55+
56+ /// <summary>Apply progressive factor value</summary>
57+ /// <param name="lookup">The lookup</param>
58+ /// <param name="rangeValue">The range value</param>
59+ /// <param name="valueFieldName">Value field name</param>
60+ /// <remarks>The first lookup range value must be zero.</remarks>
61+ /// <returns>Summary of all lookup ranges</returns>
62+ public static decimal ApplyProgressiveRangeValue ( this ILookupSet lookup , decimal rangeValue , string valueFieldName = null )
63+ {
64+ // ranges
65+ var ranges = GetLookupRanges ( lookup , valueFieldName ) ;
66+
67+ // calculate factor for each lookup range
68+ var result = ranges . Select ( x => x . GetRangeFactorValue ( rangeValue ) ) . Sum ( ) ;
69+ return result ;
70+ }
71+
72+ #endregion
73+
74+ #region Lookup Range
75+
76+ private sealed class LookupRange
77+ {
78+ internal decimal Factor { get ; }
79+ private decimal Start { get ; }
80+ private decimal ? End { get ; set ; }
81+
82+ internal LookupRange ( decimal factor , decimal start )
83+ {
84+ Factor = factor ;
85+ Start = start ;
86+ }
87+
88+ internal void SetEnd ( decimal end ) => End = end ;
89+ internal void SetEndByOffset ( decimal offset ) =>
90+ End = Start + offset ;
91+
92+ internal bool IsThreshold ( decimal rangeValue ) =>
93+ rangeValue >= Start &&
94+ ( ! End . HasValue || rangeValue < End . Value ) ;
95+
96+ internal decimal GetRangeFactorValue ( decimal rangeValue )
97+ {
98+ // undefined
99+ if ( ( End . HasValue && End <= Start ) || Factor == 0 || rangeValue == 0 )
100+ {
101+ return 0 ;
102+ }
103+ // outside (ignore start is equals rangeValue)
104+ if ( Start >= rangeValue || ( End . HasValue && End < 0 ) )
105+ {
106+ return 0 ;
107+ }
108+
109+ // lookup range intersection with the range value (0...range value)
110+ var rangeStart = Math . Max ( Start , 0 ) ;
111+ var rangeEnd = End . HasValue ? Math . Min ( End . Value , rangeValue ) : rangeValue ;
112+ var rangeSize = rangeEnd - rangeStart ;
113+ return rangeSize > 0 ? rangeSize * Factor : 0 ;
114+ }
115+
116+ public override string ToString ( ) =>
117+ $ "{ Start } - { End } ({ Factor } )";
118+ }
119+
120+ private static List < LookupRange > GetLookupRanges ( ILookupSet lookup , string valueFieldName = null )
121+ {
122+ if ( ! lookup . Values . Any ( ) )
123+ {
124+ return new ( ) ;
125+ }
126+
127+ // ranges
128+ var ranges = new List < LookupRange > ( ) ;
129+ for ( var i = 0 ; i < lookup . Values . Count ; i ++ )
130+ {
131+ var lookupValue = lookup . Values [ i ] ;
132+
133+ // ignore lookup values without range and lookup value
134+ if ( lookupValue . RangeValue == null || string . IsNullOrWhiteSpace ( lookupValue . Value ) )
135+ {
136+ continue ;
137+ }
138+
139+ // first value need to be zero
140+ if ( i == 0 && lookupValue . RangeValue . Value != 0 )
141+ {
142+ throw new PayrollException (
143+ $ "Get range factor requires a start range value of zero ({ lookupValue . RangeValue . Value } ).") ;
144+ }
145+
146+ // factor
147+ decimal ? factor = null ;
148+ if ( string . IsNullOrWhiteSpace ( valueFieldName ) )
149+ {
150+ // decimal factor from lookup value
151+ factor = JsonSerializer . Deserialize < decimal ? > ( lookupValue . Value ) ;
152+ }
153+ else
154+ {
155+ // decimal factor from JSON object filed
156+ var values = JsonSerializer . Deserialize < Dictionary < string , object > > ( lookupValue . Value ) ;
157+ if ( values != null && values [ valueFieldName ] is JsonElement jsonElement )
158+ {
159+ factor = jsonElement . GetDecimal ( ) ;
160+ }
161+ }
162+
163+ if ( ! factor . HasValue )
164+ {
165+ continue ;
166+ }
167+
168+ // update previous end
169+ if ( i > 0 )
170+ {
171+ ranges [ i - 1 ] . SetEnd ( lookupValue . RangeValue . Value ) ;
172+ }
173+
174+ // add new range
175+ ranges . Add ( new LookupRange (
176+ factor : factor . Value ,
177+ start : lookupValue . RangeValue . Value ) ) ;
178+ }
179+
180+ // last range
181+ if ( lookup . RangeSize . HasValue )
182+ {
183+ var last = ranges . Last ( ) ;
184+ last . SetEndByOffset ( lookup . RangeSize . Value ) ;
185+ }
186+
187+ return ranges ;
188+ }
189+
190+ #endregion
191+
192+ }
0 commit comments