Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
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
}