11using CommissionCalculator . DTO ;
2+ using CommissionCalculator . Internal ;
3+ using static CommissionCalculator . Internal . FastPath ;
24
35namespace CommissionCalculator ;
46
57public static class Commission
68{
7- private const decimal DecimalEpsilon = 1e-28M ; //smallest decimal value that is greater than zero
8-
9+ // Principal-based
910 public static decimal ComputeCommission ( decimal principalAmount , CommissionRule rule )
1011 {
11- decimal commission ;
12-
13- if ( rule . CalculationType == CalculationType . Proportional )
14- {
15- commission = CalculateProportionalCommission ( principalAmount , rule ) ;
16- return Math . Round ( commission , rule . DecimalPlace ) ;
17- }
18-
19- commission = CalculateAbsoluteCommission ( principalAmount , rule ) ;
20- return Math . Round ( commission , rule . DecimalPlace ) ;
21- }
22-
23- private static decimal CalculateAbsoluteCommission ( decimal principalAmount , CommissionRule rule )
24- {
25- rule = ConvertCommissionRanges ( rule ) ;
12+ var nr = CommissionRuleCache . Get ( rule ) ;
2613
27- var range = rule . CommissionRangeConfigs . FirstOrDefault ( r =>
28- principalAmount >= r . RangeStart && principalAmount < r . RangeEnd ) ;
14+ var commission = nr . CalcType == CalculationType . Proportional
15+ ? CalculateProportional ( principalAmount , nr )
16+ : CalculateAbsolute ( principalAmount , nr ) ;
2917
30- return ComputeRangeCommission ( range ! . Type ,
31- range . CommissionAmount ,
32- range . MinCommission ,
33- range . MaxCommission ,
34- principalAmount ) ;
18+ return Math . Round ( commission , nr . DecimalPlaces ) ;
3519 }
3620
21+ // Selector-based (selector chooses range; commission is applied to principal)
3722 public static decimal ComputeCommission ( decimal principalAmount , decimal selectorValue , CommissionRule rule )
3823 {
39- if ( rule . CalculationType == CalculationType . Proportional )
24+ var nr = CommissionRuleCache . Get ( rule ) ;
25+ if ( nr . CalcType == CalculationType . Proportional )
4026 {
4127 throw new InvalidOperationException (
4228 "Selector-based overload is incompatible with Proportional rules. Use Absolute." ) ;
4329 }
4430
45- var converted = ConvertCommissionRanges ( rule ) ;
31+ var idx = FindRangeIndex ( nr , selectorValue ) ;
32+ var r = nr . Ranges [ idx ] ;
4633
47- var range = converted . CommissionRangeConfigs . FirstOrDefault ( r =>
48- selectorValue >= r . RangeStart && selectorValue < r . RangeEnd ) ;
34+ var commission = ComputeRangeCommission ( r . Type , r . Amount , r . Min , r . Max , principalAmount ) ;
35+ return Math . Round ( commission , nr . DecimalPlaces ) ;
36+ }
4937
50- var commission = ComputeRangeCommission (
51- range ! . Type ,
52- range . CommissionAmount ,
53- range . MinCommission ,
54- range . MaxCommission ,
55- principalAmount
56- ) ;
38+ // ===== Fast paths using normalized rules =====
5739
58- return Math . Round ( commission , converted . DecimalPlace ) ;
40+ private static decimal CalculateAbsolute ( decimal principalAmount , NormalizedCommissionRule nr )
41+ {
42+ var idx = FindRangeIndex ( nr , principalAmount ) ;
43+ var r = nr . Ranges [ idx ] ;
44+ return ComputeRangeCommission ( r . Type , r . Amount , r . Min , r . Max , principalAmount ) ;
5945 }
6046
61- private static decimal CalculateProportionalCommission ( decimal principalAmount , CommissionRule rule )
47+ private static decimal CalculateProportional ( decimal principalAmount , NormalizedCommissionRule nr )
6248 {
63- rule = ConvertCommissionRanges ( rule ) ;
49+ // Find the current tier
50+ var idx = FindRangeIndex ( nr , principalAmount ) ;
51+ var r = nr . Ranges [ idx ] ;
6452
65- decimal commission = 0 ;
53+ // Sum of fully completed prior tiers
54+ var sum = nr . ProportionalPrefix . Length == 0 ? 0 : nr . ProportionalPrefix [ idx ] ;
6655
56+ // Partial of the current tier
57+ var portion = principalAmount - r . Start ;
58+ sum += ComputeRangeCommission ( r . Type , r . Amount , r . Min , r . Max , portion ) ;
6759
68- foreach ( var range in rule . CommissionRangeConfigs )
69- {
70- if ( principalAmount >= range . RangeStart && principalAmount < range . RangeEnd )
71- {
72- var portionOfPrincipal = principalAmount - range . RangeStart ;
73-
74- commission += ComputeRangeCommission ( range . Type ,
75- range . CommissionAmount ,
76- range . MinCommission ,
77- range . MaxCommission ,
78- portionOfPrincipal ) ;
79- }
80-
81- if ( principalAmount < range . RangeEnd )
82- {
83- continue ;
84- }
85-
86- {
87- var portionOfPrincipal = range . RangeEnd - range . RangeStart - DecimalEpsilon ;
88-
89- commission += ComputeRangeCommission ( range . Type ,
90- range . CommissionAmount ,
91- range . MinCommission ,
92- range . MaxCommission ,
93- portionOfPrincipal ) ;
94- }
95- }
96-
97- return commission ;
60+ return sum ;
9861 }
9962
100- private static decimal ComputeRangeCommission ( CommissionType commissionType ,
101- decimal commission ,
102- decimal minimum ,
103- decimal maximum ,
104- decimal principalAmount )
63+ // ===== Validation (public contract) =====
64+
65+ public static bool ValidateRule ( CommissionRule rule )
10566 {
106- if ( commissionType == CommissionType . FlatRate )
67+ try
10768 {
108- return commission ;
69+ ValidateCommissionRule ( rule ) ;
70+ return true ;
10971 }
110-
111- var computedCommission = principalAmount * commission ;
112- if ( computedCommission < minimum )
72+ catch
11373 {
114- return minimum ;
74+ return false ;
11575 }
116-
117- return computedCommission > maximum ? maximum : computedCommission ;
118- }
119-
120- private static CommissionRule ConvertCommissionRanges ( CommissionRule rule )
121- {
122- ValidateCommissionRule ( rule ) ;
123-
124- var convertedRanges = rule . CommissionRangeConfigs
125- . Select ( c => new CommissionRangeConfigs
126- {
127- RangeStart = c . RangeStart ,
128- RangeEnd = c . RangeEnd == 0 ? decimal . MaxValue : c . RangeEnd ,
129- Type = c . Type ,
130- CommissionAmount = c . CommissionAmount ,
131- MinCommission = c . MinCommission ,
132- MaxCommission = c . MaxCommission == 0 ? decimal . MaxValue : c . MaxCommission
133- } )
134- . ToList ( ) ;
135- return new CommissionRule
136- {
137- CalculationType = rule . CalculationType ,
138- DecimalPlace = rule . DecimalPlace ,
139- CommissionRangeConfigs = convertedRanges
140- } ;
14176 }
14277
143-
14478 private static void ValidateCommissionRule ( CommissionRule rule )
14579 {
14680 if ( rule == null || rule . CommissionRangeConfigs . Count == 0 )
@@ -157,21 +91,21 @@ private static void ValidateCommissionRule(CommissionRule rule)
15791
15892 if ( rule . CommissionRangeConfigs . Count == 1 )
15993 {
160- if ( rule . CommissionRangeConfigs [ 0 ] . RangeStart != 0 || rule . CommissionRangeConfigs [ 0 ] . RangeEnd != 0 )
94+ var only = rule . CommissionRangeConfigs [ 0 ] ;
95+ if ( only . RangeStart != 0 || only . RangeEnd != 0 )
16196 {
16297 throw new InvalidOperationException ( "In case of one range, both 'From' and 'To' should be 0." ) ;
16398 }
16499
165- if ( rule . CommissionRangeConfigs [ 0 ] . MaxCommission != 0 && rule . CommissionRangeConfigs [ 0 ] . MaxCommission <
166- rule . CommissionRangeConfigs [ 0 ] . MinCommission )
100+ if ( only . MaxCommission != 0 && only . MaxCommission < only . MinCommission )
167101 {
168102 throw new InvalidOperationException ( "MaxCommission should be greater than or equal to MinCommission." ) ;
169103 }
104+
105+ return ;
170106 }
171- else
172- {
173- ValidateEachRange ( rule ) ;
174- }
107+
108+ ValidateEachRange ( rule ) ;
175109 }
176110
177111 private static void ValidateEachRange ( CommissionRule rule )
@@ -188,7 +122,6 @@ private static void ValidateEachRange(CommissionRule rule)
188122 }
189123
190124 var verifiedRules = 1 ;
191-
192125 var lastTo = startRule . RangeEnd ;
193126
194127 while ( true )
@@ -215,7 +148,6 @@ private static void ValidateEachRange(CommissionRule rule)
215148 }
216149
217150 verifiedRules ++ ;
218-
219151 lastTo = nextRule ! . RangeEnd ;
220152 }
221153
@@ -224,17 +156,4 @@ private static void ValidateEachRange(CommissionRule rule)
224156 throw new InvalidOperationException ( "There is some nested or gap ranges in the rules." ) ;
225157 }
226158 }
227-
228- public static bool ValidateRule ( CommissionRule rule )
229- {
230- try
231- {
232- ValidateCommissionRule ( rule ) ;
233- return true ;
234- }
235- catch ( Exception )
236- {
237- return false ;
238- }
239- }
240159}
0 commit comments