Subversion Repositories SmartDukaan

Rev

Rev 35980 | Rev 37301 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 35980 Rev 37299
Line 103... Line 103...
103
    }
103
    }
104
 
104
 
105
 
105
 
106
    private boolean checkGiftAvailability(LocalDate date, int perDayMaxQty, OfferEntity offer) {
106
    private boolean checkGiftAvailability(LocalDate date, int perDayMaxQty, OfferEntity offer) {
107
        GiftEntity defaultGift = giftRepository.getDefaultGift(offer.getId());
107
        GiftEntity defaultGift = giftRepository.getDefaultGift(offer.getId());
-
 
108
        // No default gift configured -> no default-gift daily cap to enforce.
-
 
109
        // Return true so the caller skips the fallback and proceeds to the picker.
108
        if (defaultGift == null) return false;
110
        if (defaultGift == null) return true;
109
        Integer giftCounts = scratchOfferRepository.countTodaysGiftDistribution(date.atStartOfDay(), date.atTime(LocalTime.MAX), defaultGift.getId());
111
        Integer giftCounts = scratchOfferRepository.countTodaysGiftDistribution(date.atStartOfDay(), date.atTime(LocalTime.MAX), defaultGift.getId());
110
        return giftCounts < perDayMaxQty;
112
        return giftCounts < perDayMaxQty;
111
    }
113
    }
112
 
114
 
113
    private boolean isAppleItem(FofoOrder fofoOrder) {
115
    private boolean isAppleItem(FofoOrder fofoOrder) {
Line 128... Line 130...
128
        if (allGifts.isEmpty()) {
130
        if (allGifts.isEmpty()) {
129
            logger.warn("Offer {} has no gifts", offer.getId());
131
            logger.warn("Offer {} has no gifts", offer.getId());
130
            return false;
132
            return false;
131
        }
133
        }
132
 
134
 
-
 
135
        // Compute cart amount + item categories up-front so both the fast-paths (Apple,
-
 
136
        // daily-limit) and the main picker can consult them when deciding fallbacks.
-
 
137
        List<FofoOrderItem> orderItems = fofoOrderItemRepository.selectByOrderId(fofoOrder.getId());
-
 
138
        Set<Integer> itemCategoryIds = new HashSet<>();
-
 
139
        for (FofoOrderItem orderItem : orderItems) {
-
 
140
            try {
-
 
141
                Item item = itemRepository.selectById(orderItem.getItemId());
-
 
142
                if (item != null) {
-
 
143
                    itemCategoryIds.add(item.getCategoryId());
-
 
144
                }
-
 
145
            } catch (ProfitMandiBusinessException e) {
-
 
146
                logger.warn("Item {} not found", orderItem.getItemId());
-
 
147
            }
-
 
148
        }
-
 
149
        float totalAmount = fofoOrder.getTotalAmount();
-
 
150
 
133
        // Apple items always get the last gift
151
        // Apple items always get the last gift
134
        if (isAppleItem(fofoOrder)) {
152
        if (isAppleItem(fofoOrder)) {
135
            GiftEntity lastGift = allGifts.get(allGifts.size() - 1);
153
            GiftEntity lastGift = allGifts.get(allGifts.size() - 1);
136
            if (lastGift.getMaxRedemptions() > 0) {
154
            if (lastGift.getMaxRedemptions() > 0) {
137
                createScratchOffer(fofoOrder, offer, lastGift);
155
                createScratchOffer(fofoOrder, offer, lastGift);
Line 139... Line 157...
139
                createBetterLuckNextTimeScratch(fofoOrder, offer);
157
                createBetterLuckNextTimeScratch(fofoOrder, offer);
140
            }
158
            }
141
            return true;
159
            return true;
142
        }
160
        }
143
 
161
 
144
        // Check daily limit
162
        // Check daily limit — if the default-gift daily cap is hit, we still need to
-
 
163
        // honour the default gift's own slab bounds (management rule: default EW must
-
 
164
        // not be awarded above its own max_cart_value, e.g. above 20K).
145
        if (!checkGiftAvailability(LocalDate.now(), offer.getUserLimit(), offer)) {
165
        if (!checkGiftAvailability(LocalDate.now(), offer.getUserLimit(), offer)) {
146
            createDefaultGiftScratch(fofoOrder, offer, defaultGift);
166
            awardDefaultOrBlnt(fofoOrder, offer, defaultGift, totalAmount, itemCategoryIds,
-
 
167
                    "daily-limit-reached");
147
            return true;
168
            return true;
148
        }
169
        }
149
 
170
 
150
        // Find eligible gifts by cart value and category
171
        // Find eligible gifts by cart value and category
151
        List<FofoOrderItem> orderItems = fofoOrderItemRepository.selectByOrderId(fofoOrder.getId());
-
 
152
        Set<Integer> itemCategoryIds = new HashSet<>();
-
 
153
        for (FofoOrderItem orderItem : orderItems) {
-
 
154
            try {
-
 
155
                Item item = itemRepository.selectById(orderItem.getItemId());
-
 
156
                if (item != null) {
-
 
157
                    itemCategoryIds.add(item.getCategoryId());
-
 
158
                }
-
 
159
            } catch (ProfitMandiBusinessException e) {
-
 
160
                logger.warn("Item {} not found", orderItem.getItemId());
-
 
161
            }
-
 
162
        }
-
 
163
 
-
 
164
        float totalAmount = fofoOrder.getTotalAmount();
-
 
165
        List<GiftEntity> allOfferGifts = giftRepository.findByOfferId(offer.getId());
172
        List<GiftEntity> allOfferGifts = giftRepository.findByOfferId(offer.getId());
166
        List<GiftEntity> eligibleGifts = allOfferGifts.stream()
173
        List<GiftEntity> eligibleGifts = allOfferGifts.stream()
167
                .filter(gift -> gift.getMaxRedemptions() > 0
174
                .filter(gift -> gift.getMaxRedemptions() > 0
168
                        && totalAmount >= gift.getMinCartValue()
175
                        && totalAmount >= gift.getMinCartValue()
169
                        && totalAmount <= gift.getMaxCartValue()
176
                        && totalAmount <= gift.getMaxCartValue()
170
                        && itemCategoryIds.stream().anyMatch(catId -> isItemCategoryEligible(gift.getProductCategory(), catId)))
177
                        && itemCategoryIds.stream().anyMatch(catId -> isItemCategoryEligible(gift.getProductCategory(), catId)))
171
                .collect(Collectors.toList());
178
                .collect(Collectors.toList());
172
 
179
 
173
        if (eligibleGifts.isEmpty()) {
180
        if (eligibleGifts.isEmpty()) {
174
            createDefaultGiftScratch(fofoOrder, offer, defaultGift);
181
            awardDefaultOrBlnt(fofoOrder, offer, defaultGift, totalAmount, itemCategoryIds,
-
 
182
                    "no-eligible-gifts");
175
            return true;
183
            return true;
176
        }
184
        }
177
 
185
 
178
        GiftEntity selectedGift = selectGiftByClassification(offer, eligibleGifts, fofoOrder);
186
        GiftEntity selectedGift = selectGiftByClassification(offer, eligibleGifts, fofoOrder);
179
        if (selectedGift == null || selectedGift.getMaxRedemptions() <= 0) {
187
        if (selectedGift == null || selectedGift.getMaxRedemptions() <= 0) {
180
            createDefaultGiftScratch(fofoOrder, offer, defaultGift);
188
            awardDefaultOrBlnt(fofoOrder, offer, defaultGift, totalAmount, itemCategoryIds,
-
 
189
                    "picker-returned-null-or-exhausted");
181
            return true;
190
            return true;
182
        }
191
        }
183
 
192
 
184
        createScratchOffer(fofoOrder, offer, selectedGift);
193
        createScratchOffer(fofoOrder, offer, selectedGift);
185
        return true;
194
        return true;
186
    }
195
    }
187
 
196
 
-
 
197
    /**
-
 
198
     * Fallback awarder that respects the default gift's own slab bounds and category.
-
 
199
     * If the default gift is genuinely eligible for this cart (cart within its
-
 
200
     * min/max, category matches, stock left), award it. Otherwise award Better Luck
-
 
201
     * Next Time — this prevents the "EW awarded above 20K" management rule violation
-
 
202
     * that could happen when a slab gap or exhausted pool caused the picker to fall
-
 
203
     * through with no eligible gift.
-
 
204
     */
-
 
205
    private void awardDefaultOrBlnt(FofoOrder order, OfferEntity offer, GiftEntity defaultGift,
-
 
206
                                    float totalAmount, Set<Integer> itemCategoryIds, String reason) {
-
 
207
        // 1) Default gift — only if truly eligible for this cart (respects its slab).
-
 
208
        boolean defaultEligible = defaultGift != null
-
 
209
                && defaultGift.getMaxRedemptions() != null
-
 
210
                && defaultGift.getMaxRedemptions() > 0
-
 
211
                && defaultGift.getMinCartValue() != null
-
 
212
                && defaultGift.getMaxCartValue() != null
-
 
213
                && totalAmount >= defaultGift.getMinCartValue()
-
 
214
                && totalAmount <= defaultGift.getMaxCartValue()
-
 
215
                && itemCategoryIds.stream().anyMatch(catId ->
-
 
216
                isItemCategoryEligible(defaultGift.getProductCategory(), catId));
-
 
217
        if (defaultEligible) {
-
 
218
            logger.info("Fallback fired for order {} ({}) — default gift {} eligible, awarding it",
-
 
219
                    order.getId(), reason, defaultGift.getId());
-
 
220
            createDefaultGiftScratch(order, offer, defaultGift);
-
 
221
            return;
-
 
222
        }
-
 
223
 
-
 
224
        // 2) Neckband safety net — management rule: when EW isn't allowed for this cart
-
 
225
        //    (i.e. default fell through), award any in-stock, category-matching Neckband
-
 
226
        //    from this offer's config, even if its own slab bounds don't cover this cart.
-
 
227
        //    Team keeps Neckband quantity topped up so BLNT never fires under normal ops.
-
 
228
        List<GiftEntity> allGifts = giftRepository.findByOfferId(offer.getId());
-
 
229
        GiftEntity neckband = allGifts.stream()
-
 
230
                .filter(g -> g.getName() != null && "neckband".equalsIgnoreCase(g.getName().trim()))
-
 
231
                .filter(g -> g.getMaxRedemptions() != null && g.getMaxRedemptions() > 0)
-
 
232
                .filter(g -> itemCategoryIds.stream().anyMatch(catId ->
-
 
233
                        isItemCategoryEligible(g.getProductCategory(), catId)))
-
 
234
                .findFirst()
-
 
235
                .orElse(null);
-
 
236
        if (neckband != null) {
-
 
237
            logger.info("Fallback fired for order {} ({}) — default not eligible; awarding Neckband row {} as safety net",
-
 
238
                    order.getId(), reason, neckband.getId());
-
 
239
            createScratchOffer(order, offer, neckband);
-
 
240
            return;
-
 
241
        }
-
 
242
 
-
 
243
        // 3) Absolute last resort — no default, no Neckband available anywhere.
-
 
244
        logger.warn("Fallback fired for order {} ({}) — no default gift AND no Neckband in stock; awarding Better Luck Next Time",
-
 
245
                order.getId(), reason);
-
 
246
        createBetterLuckNextTimeScratch(order, offer);
-
 
247
    }
-
 
248
 
188
 
249
 
189
    private boolean isItemCategoryEligible(String giftCategories, Integer itemCategoryId) {
250
    private boolean isItemCategoryEligible(String giftCategories, Integer itemCategoryId) {
190
        if (giftCategories == null || giftCategories.trim().isEmpty()) {
251
        if (giftCategories == null || giftCategories.trim().isEmpty()) {
191
            return false;
252
            return false;
192
        }
253
        }