| Line 220... |
Line 220... |
| 220 |
createDefaultGiftScratch(order, offer, defaultGift);
|
220 |
createDefaultGiftScratch(order, offer, defaultGift);
|
| 221 |
return;
|
221 |
return;
|
| 222 |
}
|
222 |
}
|
| 223 |
|
223 |
|
| 224 |
// 2) Neckband safety net — management rule: when EW isn't allowed for this cart
|
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
|
225 |
// (i.e. default fell through), award a Neckband from this offer's config.
|
| 226 |
// from this offer's config, even if its own slab bounds don't cover this cart.
|
226 |
// Prefer a Neckband whose OWN slab covers this cart amount (so stock
|
| 227 |
// Team keeps Neckband quantity topped up so BLNT never fires under normal ops.
|
227 |
// decrements from the correct slab, matching per-slab quota intent).
|
| - |
|
228 |
// Only if no slab-matching Neckband exists do we cross-slab pick.
|
| 228 |
List<GiftEntity> allGifts = giftRepository.findByOfferId(offer.getId());
|
229 |
List<GiftEntity> allGifts = giftRepository.findByOfferId(offer.getId());
|
| 229 |
GiftEntity neckband = allGifts.stream()
|
230 |
List<GiftEntity> neckbandCandidates = allGifts.stream()
|
| 230 |
.filter(g -> g.getName() != null && "neckband".equalsIgnoreCase(g.getName().trim()))
|
231 |
.filter(g -> g.getName() != null && "neckband".equalsIgnoreCase(g.getName().trim()))
|
| 231 |
.filter(g -> g.getMaxRedemptions() != null && g.getMaxRedemptions() > 0)
|
232 |
.filter(g -> g.getMaxRedemptions() != null && g.getMaxRedemptions() > 0)
|
| 232 |
.filter(g -> itemCategoryIds.stream().anyMatch(catId ->
|
233 |
.filter(g -> itemCategoryIds.stream().anyMatch(catId ->
|
| 233 |
isItemCategoryEligible(g.getProductCategory(), catId)))
|
234 |
isItemCategoryEligible(g.getProductCategory(), catId)))
|
| - |
|
235 |
.collect(Collectors.toList());
|
| - |
|
236 |
|
| - |
|
237 |
// First pass — slab-matching Neckband (this cart's own slab).
|
| - |
|
238 |
GiftEntity neckband = neckbandCandidates.stream()
|
| - |
|
239 |
.filter(g -> g.getMinCartValue() != null && g.getMaxCartValue() != null
|
| - |
|
240 |
&& totalAmount >= g.getMinCartValue()
|
| - |
|
241 |
&& totalAmount <= g.getMaxCartValue())
|
| 234 |
.findFirst()
|
242 |
.findFirst()
|
| 235 |
.orElse(null);
|
243 |
.orElse(null);
|
| - |
|
244 |
|
| - |
|
245 |
// Second pass — cross-slab pick only if the correct slab has no Neckband left.
|
| - |
|
246 |
if (neckband == null && !neckbandCandidates.isEmpty()) {
|
| - |
|
247 |
neckband = neckbandCandidates.get(0);
|
| - |
|
248 |
logger.info("Fallback for order {} ({}) — no slab-matching Neckband for cart={}; cross-slab picking row {}",
|
| - |
|
249 |
order.getId(), reason, totalAmount, neckband.getId());
|
| - |
|
250 |
}
|
| - |
|
251 |
|
| 236 |
if (neckband != null) {
|
252 |
if (neckband != null) {
|
| 237 |
logger.info("Fallback fired for order {} ({}) — default not eligible; awarding Neckband row {} as safety net",
|
253 |
logger.info("Fallback fired for order {} ({}) — awarding Neckband row {} (slab {}-{}) for cart={}",
|
| 238 |
order.getId(), reason, neckband.getId());
|
254 |
order.getId(), reason, neckband.getId(),
|
| - |
|
255 |
neckband.getMinCartValue(), neckband.getMaxCartValue(), totalAmount);
|
| 239 |
createScratchOffer(order, offer, neckband);
|
256 |
createScratchOffer(order, offer, neckband);
|
| 240 |
return;
|
257 |
return;
|
| 241 |
}
|
258 |
}
|
| 242 |
|
259 |
|
| 243 |
// 3) Absolute last resort — no default, no Neckband available anywhere.
|
260 |
// 3) Absolute last resort — no default, no Neckband available anywhere.
|