| 37330 |
amit |
1 |
package com.smartdukaan.cron.offercircular;
|
|
|
2 |
|
|
|
3 |
import java.math.BigDecimal;
|
|
|
4 |
import java.util.ArrayList;
|
|
|
5 |
import java.util.Arrays;
|
|
|
6 |
import java.util.LinkedHashMap;
|
|
|
7 |
import java.util.LinkedHashSet;
|
|
|
8 |
import java.util.List;
|
|
|
9 |
import java.util.Map;
|
|
|
10 |
import java.util.Set;
|
|
|
11 |
import java.util.regex.Matcher;
|
|
|
12 |
import java.util.regex.Pattern;
|
|
|
13 |
|
|
|
14 |
/**
|
|
|
15 |
* Parses the "Offer Description" cell into a value per payment mode.
|
|
|
16 |
*
|
|
|
17 |
* One circular row routinely carries two different values, and even two different
|
|
|
18 |
* calculation types:
|
|
|
19 |
* "10% cashback up to 2500 on CC EMI & Rs 1500 on CC Full Swipe"
|
|
|
20 |
* -> CC_EMI = PERCENT 10% capped at 2500
|
|
|
21 |
* CC_FULL_SWIPE = FLAT 1500
|
|
|
22 |
* which is exactly why the amount belongs on (offer, mode) and never on the offer.
|
|
|
23 |
*/
|
|
|
24 |
public final class BenefitParser {
|
|
|
25 |
|
|
|
26 |
public static final String CALC_FLAT = "FLAT";
|
|
|
27 |
public static final String CALC_PERCENT = "PERCENT";
|
|
|
28 |
|
|
|
29 |
/** Order matters: the compound forms must be tested before the simple ones. */
|
|
|
30 |
private static final Object[][] MODE_PATTERNS = {
|
|
|
31 |
{"CC\\s*/\\s*DC\\s*FULL\\s*SWIPE", new String[]{"CC_FULL_SWIPE", "DC_FULL_SWIPE"}},
|
|
|
32 |
{"CC\\s*/\\s*DC\\s*EMI", new String[]{"CC_EMI", "DC_EMI"}},
|
|
|
33 |
{"DC\\s*EMI", new String[]{"DC_EMI"}},
|
|
|
34 |
{"CC\\s*EMI", new String[]{"CC_EMI"}},
|
|
|
35 |
{"CCEMI", new String[]{"CC_EMI"}},
|
|
|
36 |
{"DC\\s*FULL\\s*SWIPE", new String[]{"DC_FULL_SWIPE"}},
|
|
|
37 |
{"CC\\s*FULL\\s*SWIPE", new String[]{"CC_FULL_SWIPE"}},
|
|
|
38 |
{"CC\\s*REG", new String[]{"CC_FULL_SWIPE"}},
|
|
|
39 |
{"FULL\\s*SWIPE", new String[]{"CC_FULL_SWIPE"}},
|
|
|
40 |
{"\\bFS\\b", new String[]{"CC_FULL_SWIPE"}},
|
|
|
41 |
{"\\bUPI\\b", new String[]{"UPI"}},
|
|
|
42 |
{"CARDLESS", new String[]{"CARDLESS_EMI"}},
|
|
|
43 |
};
|
|
|
44 |
|
|
|
45 |
private static final List<Pattern> MODE_REGEX = new ArrayList<>();
|
|
|
46 |
static {
|
|
|
47 |
for (Object[] row : MODE_PATTERNS) {
|
|
|
48 |
MODE_REGEX.add(Pattern.compile((String) row[0], Pattern.CASE_INSENSITIVE));
|
|
|
49 |
}
|
|
|
50 |
}
|
|
|
51 |
|
|
|
52 |
private static final Pattern PERCENT = Pattern.compile("(\\d+(?:\\.\\d+)?)\\s*%");
|
|
|
53 |
private static final Pattern CAP =
|
|
|
54 |
Pattern.compile("(?:up\\s*to|upto)\\s*(?:rs\\.?\\s*)?(\\d[\\d,]*)", Pattern.CASE_INSENSITIVE);
|
|
|
55 |
private static final Pattern FLAT =
|
|
|
56 |
Pattern.compile("(?:rs\\.?\\s*)?(\\d[\\d,]{2,})", Pattern.CASE_INSENSITIVE);
|
|
|
57 |
private static final Pattern EMI_ONLY =
|
|
|
58 |
Pattern.compile("only\\s*nc\\s*emi", Pattern.CASE_INSENSITIVE);
|
|
|
59 |
|
|
|
60 |
public static final class Benefit {
|
|
|
61 |
private final String calcType;
|
|
|
62 |
private final BigDecimal flatAmount;
|
|
|
63 |
private final BigDecimal percent;
|
|
|
64 |
private final BigDecimal maxAmount;
|
|
|
65 |
Benefit(String calcType, BigDecimal flatAmount, BigDecimal percent, BigDecimal maxAmount) {
|
|
|
66 |
this.calcType = calcType;
|
|
|
67 |
this.flatAmount = flatAmount;
|
|
|
68 |
this.percent = percent;
|
|
|
69 |
this.maxAmount = maxAmount;
|
|
|
70 |
}
|
|
|
71 |
public String getCalcType() { return calcType; }
|
|
|
72 |
public BigDecimal getFlatAmount() { return flatAmount; }
|
|
|
73 |
public BigDecimal getPercent() { return percent; }
|
|
|
74 |
public BigDecimal getMaxAmount() { return maxAmount; }
|
|
|
75 |
@Override public String toString() {
|
|
|
76 |
return CALC_PERCENT.equals(calcType)
|
|
|
77 |
? percent + "%" + (maxAmount == null ? "" : " cap " + maxAmount)
|
|
|
78 |
: String.valueOf(flatAmount);
|
|
|
79 |
}
|
|
|
80 |
}
|
|
|
81 |
|
|
|
82 |
public static final class Result {
|
|
|
83 |
/** payment mode -> value; insertion-ordered */
|
|
|
84 |
private final Map<String, Benefit> benefits = new LinkedHashMap<>();
|
|
|
85 |
private String warning;
|
|
|
86 |
public Map<String, Benefit> getBenefits() { return benefits; }
|
|
|
87 |
public String getWarning() { return warning; }
|
|
|
88 |
}
|
|
|
89 |
|
|
|
90 |
private BenefitParser() { }
|
|
|
91 |
|
|
|
92 |
private static List<String> findModes(String text) {
|
|
|
93 |
Set<String> ordered = new LinkedHashSet<>();
|
|
|
94 |
for (int i = 0; i < MODE_REGEX.size(); i++) {
|
|
|
95 |
if (MODE_REGEX.get(i).matcher(text).find()) {
|
|
|
96 |
ordered.addAll(Arrays.asList((String[]) MODE_PATTERNS[i][1]));
|
|
|
97 |
}
|
|
|
98 |
}
|
|
|
99 |
return new ArrayList<>(ordered);
|
|
|
100 |
}
|
|
|
101 |
|
|
|
102 |
private static BigDecimal num(String raw) {
|
|
|
103 |
return new BigDecimal(raw.replace(",", ""));
|
|
|
104 |
}
|
|
|
105 |
|
|
|
106 |
private static Benefit findValue(String text) {
|
|
|
107 |
Matcher pct = PERCENT.matcher(text);
|
|
|
108 |
if (pct.find()) {
|
|
|
109 |
Matcher cap = CAP.matcher(text);
|
|
|
110 |
return new Benefit(CALC_PERCENT, null, new BigDecimal(pct.group(1)),
|
|
|
111 |
cap.find() ? num(cap.group(1)) : null);
|
|
|
112 |
}
|
|
|
113 |
Matcher flat = FLAT.matcher(text);
|
|
|
114 |
if (flat.find()) {
|
|
|
115 |
return new Benefit(CALC_FLAT, num(flat.group(1)), null, null);
|
|
|
116 |
}
|
|
|
117 |
return null;
|
|
|
118 |
}
|
|
|
119 |
|
|
|
120 |
public static Result parse(String cell) {
|
|
|
121 |
Result result = new Result();
|
|
|
122 |
if (cell == null) {
|
|
|
123 |
result.warning = "blank";
|
|
|
124 |
return result;
|
|
|
125 |
}
|
|
|
126 |
String text = cell.trim();
|
|
|
127 |
if (text.isEmpty() || "-".equals(text) || "NA".equals(text) || "N/A".equals(text)) {
|
|
|
128 |
result.warning = "blank";
|
|
|
129 |
return result;
|
|
|
130 |
}
|
|
|
131 |
if (EMI_ONLY.matcher(text).find()) {
|
|
|
132 |
result.warning = "emi-only-no-cashback";
|
|
|
133 |
return result;
|
|
|
134 |
}
|
|
|
135 |
|
|
|
136 |
// '&' separates the per-mode clauses: "Rs.4000 on CC EMI & Rs.3000 CC Full Swipe"
|
|
|
137 |
List<Benefit> values = new ArrayList<>();
|
|
|
138 |
List<List<String>> modesPerSegment = new ArrayList<>();
|
|
|
139 |
Benefit carried = null;
|
|
|
140 |
for (String segment : text.split("\\s*&\\s*")) {
|
|
|
141 |
if (segment.trim().isEmpty()) {
|
|
|
142 |
continue;
|
|
|
143 |
}
|
|
|
144 |
Benefit value = findValue(segment);
|
|
|
145 |
if (value == null) {
|
|
|
146 |
value = carried; // "…& UPI" inherits the preceding amount
|
|
|
147 |
} else {
|
|
|
148 |
carried = value;
|
|
|
149 |
}
|
|
|
150 |
if (value == null) {
|
|
|
151 |
continue;
|
|
|
152 |
}
|
|
|
153 |
List<String> modes = findModes(segment);
|
|
|
154 |
values.add(value);
|
|
|
155 |
modesPerSegment.add(modes.isEmpty() ? null : modes);
|
|
|
156 |
}
|
|
|
157 |
if (values.isEmpty()) {
|
|
|
158 |
result.warning = "no-value-found";
|
|
|
159 |
return result;
|
|
|
160 |
}
|
|
|
161 |
|
|
|
162 |
// A clause naming no mode of its own applies to whatever modes the whole cell
|
|
|
163 |
// mentions and no other clause has claimed; ANY when the cell names none.
|
|
|
164 |
List<String> allModes = findModes(text);
|
|
|
165 |
if (allModes.isEmpty()) {
|
|
|
166 |
allModes = new ArrayList<>(Arrays.asList("ANY"));
|
|
|
167 |
}
|
|
|
168 |
Set<String> claimed = new LinkedHashSet<>();
|
|
|
169 |
for (List<String> modes : modesPerSegment) {
|
|
|
170 |
if (modes != null) {
|
|
|
171 |
claimed.addAll(modes);
|
|
|
172 |
}
|
|
|
173 |
}
|
|
|
174 |
for (int i = 0; i < values.size(); i++) {
|
|
|
175 |
List<String> targets = modesPerSegment.get(i);
|
|
|
176 |
if (targets == null) {
|
|
|
177 |
targets = new ArrayList<>();
|
|
|
178 |
for (String mode : allModes) {
|
|
|
179 |
if (!claimed.contains(mode)) {
|
|
|
180 |
targets.add(mode);
|
|
|
181 |
}
|
|
|
182 |
}
|
|
|
183 |
if (targets.isEmpty()) {
|
|
|
184 |
targets = Arrays.asList("ANY");
|
|
|
185 |
}
|
|
|
186 |
}
|
|
|
187 |
for (String mode : targets) {
|
|
|
188 |
// first clause wins for a given mode
|
|
|
189 |
result.benefits.putIfAbsent(mode, values.get(i));
|
|
|
190 |
}
|
|
|
191 |
}
|
|
|
192 |
return result;
|
|
|
193 |
}
|
|
|
194 |
}
|