Rev 37312 | Blame | Compare with Previous | Last modification | View Log | RSS feed
package com.spice.profitmandi.dao.service;import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;import com.spice.profitmandi.dao.entity.catalog.Item;import com.spice.profitmandi.dao.entity.dtr.ScratchOffer;import com.spice.profitmandi.dao.entity.fofo.FofoOrder;import com.spice.profitmandi.dao.entity.fofo.FofoOrderItem;import com.spice.profitmandi.dao.entity.scratch.GiftEntity;import com.spice.profitmandi.dao.entity.scratch.OfferEntity;import com.spice.profitmandi.dao.entity.scratch.ScratchRequest;import com.spice.profitmandi.dao.repository.catalog.ItemRepository;import com.spice.profitmandi.dao.repository.dtr.ScratchOfferRepository;import com.spice.profitmandi.dao.repository.fofo.FofoOrderItemRepository;import com.spice.profitmandi.dao.repository.fofo.FofoOrderRepository;import com.spice.profitmandi.dao.repository.scratch.GiftRepository;import com.spice.profitmandi.dao.repository.scratch.SeasonalOfferRepository;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.util.*;import java.util.function.Function;import java.util.stream.Collectors;@Servicepublic class ScratchService {private static final Logger logger = LogManager.getLogger(ScratchService.class);@AutowiredSeasonalOfferRepository seasonalOfferRepository;@AutowiredGiftRepository giftRepository;@AutowiredScratchOfferRepository scratchOfferRepository;@AutowiredFofoOrderItemRepository fofoOrderItemRepository;@AutowiredItemRepository itemRepository;@AutowiredFofoOrderRepository fofoOrderRepository;public void processScratchOffer(int fofoOrderId, Set<Integer> fofoOrderItemIds) throws ProfitMandiBusinessException {List<Item> items = itemRepository.selectByIds(fofoOrderItemIds);boolean smartPhone = items.stream().anyMatch(Item::isSmartPhone);if (smartPhone) {this.processScratchOfferCoupon(fofoOrderId);}}public void processScratchOfferCoupon(int fofoOrderId) throws ProfitMandiBusinessException {List<OfferEntity> activeOffers = seasonalOfferRepository.getOffersByDate(LocalDate.now());if (activeOffers.isEmpty()) {return;}FofoOrder fofoOrder = fofoOrderRepository.selectByOrderId(fofoOrderId);// --- V2: offer selection and the per-offer availed check now happen inside processOfferV2 ---// OfferEntity eligibleOffer = findEligibleOffer(activeOffers, fofoOrder);// if (eligibleOffer == null) {// logger.info("No eligible offer for order {}", fofoOrderId);// return;// }//// ScratchOffer availedOffer = scratchOfferRepository.getScractchOfferByCustomerIdAndOfferId(fofoOrder.getCustomerId(), eligibleOffer.getId());// if (availedOffer != null) {// logger.info("Customer {} already availed offer {}", fofoOrder.getCustomerId(), eligibleOffer.getId());// return;// }//// this.processOffer(eligibleOffer, fofoOrder);this.processOfferV2(activeOffers, fofoOrder);}// ------------------------------------------------------------------------------------------------// V2 award flow. Order value picks the slab, a random in-stock gift wins, and the offer's default// gift is the floor. A gift carrying a partner list (fofo_store) is a partner-restricted special and// is awarded to those partners on bills above SPECIAL_GIFT_MIN_ORDER_VALUE; a gift with no partner// list belongs to the random pool.//// Deliberately bypasses the V1 gating: no per-day user limit, no Apple override, no item-category// filter, no payment-method filter, no classification switch. The one-gift-per-customer-per-offer// rule is kept, applied per offer across every active offer.//// Nothing below calls a V1 helper, so this whole block can be deleted to roll back.// ------------------------------------------------------------------------------------------------private static final double SPECIAL_GIFT_MIN_ORDER_VALUE = 5000d;private void processOfferV2(List<OfferEntity> activeOffers, FofoOrder fofoOrder) {if (fofoOrder == null) {logger.error("Order not found, skipping scratch award");return;}List<OfferEntity> eligibleOffers = filterOffersNotAvailed(activeOffers, fofoOrder.getCustomerId());if (eligibleOffers.isEmpty()) {logger.info("Customer {} has already availed every active offer", fofoOrder.getCustomerId());return;}Map<Integer, OfferEntity> offerById = new HashMap<>();List<GiftEntity> pool = new ArrayList<>();for (OfferEntity offer : eligibleOffers) {offerById.put(offer.getId(), offer);List<GiftEntity> gifts = giftRepository.findByOfferId(offer.getId());if (gifts != null) {pool.addAll(gifts);}}if (pool.isEmpty()) {logger.warn("No gifts configured on any active offer, order {} gets no scratch card", fofoOrder.getId());return;}double amount = fofoOrder.getTotalAmount();// 1. Partner-restricted special giftGiftEntity special = findSpecialPartnerGift(pool, fofoOrder.getFofoId(), amount);if (special != null && awardWithStock(fofoOrder, offerById.get(special.getOfferId()), special)) {return;}// 2. Random draw from the order-value slabList<GiftEntity> candidates = new ArrayList<>(findSlabGifts(pool, amount));Collections.shuffle(candidates);for (GiftEntity gift : candidates) {if (awardWithStock(fofoOrder, offerById.get(gift.getOfferId()), gift)) {return;}}// 3. Floor: the offer's default gift, always issued, never Better Luck Next TimeGiftEntity defaultGift = resolveDefaultGift(pool, eligibleOffers);if (defaultGift == null) {logger.error("No default gift on any active offer, order {} gets no scratch card", fofoOrder.getId());return;}awardUnconditional(fofoOrder, offerById.get(defaultGift.getOfferId()), defaultGift);}/** One gift per customer per offer: drop any offer this customer already holds a card for. */private List<OfferEntity> filterOffersNotAvailed(List<OfferEntity> offers, int customerId) {List<OfferEntity> eligible = new ArrayList<>();for (OfferEntity offer : offers) {if (scratchOfferRepository.getScractchOfferByCustomerIdAndOfferId(customerId, offer.getId()) == null) {eligible.add(offer);} else {logger.info("Customer {} already availed offer {}", customerId, offer.getId());}}return eligible;}private GiftEntity findSpecialPartnerGift(List<GiftEntity> pool, int fofoId, double amount) {if (amount <= SPECIAL_GIFT_MIN_ORDER_VALUE) {return null;}GiftEntity match = null;for (GiftEntity gift : pool) {if (!hasPartnerList(gift) || !hasStock(gift) || !csvContainsId(gift.getFofoStore(), fofoId)) {continue;}// deterministic when a partner is listed on more than one special giftif (match == null || gift.getId() < match.getId()) {match = gift;}}if (match != null) {logger.info("Special partner gift {} matched partner {} on amount {}", match.getId(), fofoId, amount);}return match;}/** Random-pool gifts whose cart-value band contains the order amount. Excludes specials and the default. */private List<GiftEntity> findSlabGifts(List<GiftEntity> pool, double amount) {List<GiftEntity> slab = new ArrayList<>();for (GiftEntity gift : pool) {if (hasPartnerList(gift) || isDefaultGift(gift) || !hasStock(gift)) {continue;}if (gift.getMinCartValue() == null && gift.getMaxCartValue() == null) {logger.warn("Gift {} has no cart-value bounds, excluding it from the slab pool", gift.getId());continue;}double min = gift.getMinCartValue() == null ? 0d : gift.getMinCartValue();double max = gift.getMaxCartValue() == null ? Double.MAX_VALUE : gift.getMaxCartValue();if (amount >= min && amount <= max) {slab.add(gift);}}return slab;}/*** The default gift of the first eligible offer that has one. Resolved from the already-fetched pool* rather than via getDefaultGift, whose getSingleResult throws when an offer has two defaults.*/private GiftEntity resolveDefaultGift(List<GiftEntity> pool, List<OfferEntity> offers) {for (OfferEntity offer : offers) {GiftEntity best = null;for (GiftEntity gift : pool) {if (gift.getOfferId() == null || gift.getOfferId() != offer.getId() || !isDefaultGift(gift)) {continue;}if (best == null || gift.getId() < best.getId()) {best = gift;}}if (best != null) {return best;}}return null;}/** Award only if the atomic decrement wins; false lets the caller try the next candidate. */private boolean awardWithStock(FofoOrder order, OfferEntity offer, GiftEntity gift) {if (offer == null || gift == null) {return false;}if (giftRepository.decrementMaxRedemptions(gift.getId()) == 0) {logger.warn("Gift {} exhausted by a concurrent order, trying the next candidate", gift.getId());return false;}persistScratchV2(order, offer, gift);return true;}/** Floor award: attempt the decrement for accounting, but issue the card either way. */private void awardUnconditional(FofoOrder order, OfferEntity offer, GiftEntity gift) {if (offer == null || gift == null) {logger.error("Cannot resolve the floor gift for order {}", order.getId());return;}if (giftRepository.decrementMaxRedemptions(gift.getId()) == 0) {logger.warn("Default gift {} is out of stock but is still being issued for order {}", gift.getId(), order.getId());}persistScratchV2(order, offer, gift);}private void persistScratchV2(FofoOrder order, OfferEntity offer, GiftEntity gift) {if (order.getInvoiceNumber() == null) {logger.warn("Order {} has no invoice number, persisting the scratch card anyway", order.getId());}ScratchOffer so = new ScratchOffer();so.setCustomerId(order.getCustomerId());so.setOfferId(offer.getId());so.setGiftId(gift.getId());so.setInvoiceNumber(order.getInvoiceNumber());so.setCreatedTimestamp(LocalDateTime.now());so.setOfferName(gift.getName());scratchOfferRepository.persist(so);logger.info("Scratch card created for order {} customer {} offer {} gift {} ({})",order.getId(), order.getCustomerId(), offer.getId(), gift.getId(), gift.getName());}/** A gift with a partner list is partner-restricted; one without belongs to the random pool. */private static boolean hasPartnerList(GiftEntity gift) {return gift.getFofoStore() != null && !gift.getFofoStore().trim().isEmpty();}private static boolean hasStock(GiftEntity gift) {return gift.getMaxRedemptions() != null && gift.getMaxRedemptions() > 0;}private static boolean isDefaultGift(GiftEntity gift) {return Boolean.TRUE.equals(gift.isDefault());}private static boolean csvContainsId(String csv, int id) {if (csv == null || csv.trim().isEmpty()) {return false;}for (String token : csv.split(",")) {String trimmed = token.trim();if (trimmed.isEmpty()) {continue;}try {if (Integer.parseInt(trimmed) == id) {return true;}} catch (NumberFormatException e) {// malformed entry, skip it}}return false;}// ------------------------------------------------------------------------------ end of V2 blockprivate OfferEntity findEligibleOffer(List<OfferEntity> activeOffers, FofoOrder fofoOrder) {int customerFofoId = fofoOrder.getFofoId();// Check PartnerWise offers firstfor (OfferEntity activeOffer : activeOffers) {if (!"PartnerWise".equals(activeOffer.getClassification())) {continue;}List<GiftEntity> gifts = giftRepository.findByOfferId(activeOffer.getId());for (GiftEntity gift : gifts) {if (gift.getFofoStore() == null || gift.getFofoStore().trim().isEmpty()) {continue;}boolean matches = Arrays.stream(gift.getFofoStore().split(",")).map(String::trim).filter(s -> !s.isEmpty()).anyMatch(store -> {try {return Integer.parseInt(store) == customerFofoId;} catch (NumberFormatException e) {return false;}});if (matches) {return activeOffer;}}}// Fallback to first non-PartnerWise offerreturn activeOffers.stream().filter(offer -> !"PartnerWise".equals(offer.getClassification())).findFirst().orElse(null);}private boolean checkGiftAvailability(LocalDate date, int perDayMaxQty, OfferEntity offer) {GiftEntity defaultGift = giftRepository.getDefaultGift(offer.getId());// No default gift configured -> no default-gift daily cap to enforce.// Return true so the caller skips the fallback and proceeds to the picker.if (defaultGift == null) return true;Integer giftCounts = scratchOfferRepository.countTodaysGiftDistribution(date.atStartOfDay(), date.atTime(LocalTime.MAX), defaultGift.getId());return giftCounts < perDayMaxQty;}private boolean isAppleItem(FofoOrder fofoOrder) {List<FofoOrderItem> fofoOrderItems = fofoOrderItemRepository.selectByOrderId(fofoOrder.getId());if (fofoOrderItems == null || fofoOrderItems.isEmpty()) {return false;}return fofoOrderItems.stream().anyMatch(item -> item != null && "apple".equalsIgnoreCase(item.getBrand()));}private boolean processOffer(OfferEntity offer, FofoOrder fofoOrder) {logger.info("Processing offer {} for order {}", offer.getId(), fofoOrder.getId());List<GiftEntity> allGifts = giftRepository.findNonDefaultGIft(offer.getId());GiftEntity defaultGift = giftRepository.getDefaultGift(offer.getId());if (allGifts.isEmpty()) {logger.warn("Offer {} has no gifts", offer.getId());return false;}// Compute cart amount + item categories up-front so both the fast-paths (Apple,// daily-limit) and the main picker can consult them when deciding fallbacks.List<FofoOrderItem> orderItems = fofoOrderItemRepository.selectByOrderId(fofoOrder.getId());Set<Integer> itemCategoryIds = new HashSet<>();for (FofoOrderItem orderItem : orderItems) {try {Item item = itemRepository.selectById(orderItem.getItemId());if (item != null) {itemCategoryIds.add(item.getCategoryId());}} catch (ProfitMandiBusinessException e) {logger.warn("Item {} not found", orderItem.getItemId());}}float totalAmount = fofoOrder.getTotalAmount();// Apple items always get the last giftif (isAppleItem(fofoOrder)) {GiftEntity lastGift = allGifts.get(allGifts.size() - 1);if (lastGift.getMaxRedemptions() > 0) {createScratchOffer(fofoOrder, offer, lastGift);} else {createBetterLuckNextTimeScratch(fofoOrder, offer);}return true;}// Check daily limit — if the default-gift daily cap is hit, we still need to// honour the default gift's own slab bounds (management rule: default EW must// not be awarded above its own max_cart_value, e.g. above 20K).if (!checkGiftAvailability(LocalDate.now(), offer.getUserLimit(), offer)) {awardDefaultOrBlnt(fofoOrder, offer, defaultGift, totalAmount, itemCategoryIds,"daily-limit-reached");return true;}// Find eligible gifts by cart value and categoryList<GiftEntity> allOfferGifts = giftRepository.findByOfferId(offer.getId());List<GiftEntity> eligibleGifts = allOfferGifts.stream().filter(gift -> gift.getMaxRedemptions() > 0&& totalAmount >= gift.getMinCartValue()&& totalAmount <= gift.getMaxCartValue()&& itemCategoryIds.stream().anyMatch(catId -> isItemCategoryEligible(gift.getProductCategory(), catId))).collect(Collectors.toList());if (eligibleGifts.isEmpty()) {awardDefaultOrBlnt(fofoOrder, offer, defaultGift, totalAmount, itemCategoryIds,"no-eligible-gifts");return true;}GiftEntity selectedGift = selectGiftByClassification(offer, eligibleGifts, fofoOrder);if (selectedGift == null || selectedGift.getMaxRedemptions() <= 0) {awardDefaultOrBlnt(fofoOrder, offer, defaultGift, totalAmount, itemCategoryIds,"picker-returned-null-or-exhausted");return true;}createScratchOffer(fofoOrder, offer, selectedGift);return true;}/*** Fallback awarder that respects the default gift's own slab bounds and category.* If the default gift is genuinely eligible for this cart (cart within its* min/max, category matches, stock left), award it. Otherwise award Better Luck* Next Time — this prevents the "EW awarded above 20K" management rule violation* that could happen when a slab gap or exhausted pool caused the picker to fall* through with no eligible gift.*/private void awardDefaultOrBlnt(FofoOrder order, OfferEntity offer, GiftEntity defaultGift,float totalAmount, Set<Integer> itemCategoryIds, String reason) {// 1) Default gift — only if truly eligible for this cart (respects its slab).boolean defaultEligible = defaultGift != null&& defaultGift.getMaxRedemptions() != null&& defaultGift.getMaxRedemptions() > 0&& defaultGift.getMinCartValue() != null&& defaultGift.getMaxCartValue() != null&& totalAmount >= defaultGift.getMinCartValue()&& totalAmount <= defaultGift.getMaxCartValue()&& itemCategoryIds.stream().anyMatch(catId ->isItemCategoryEligible(defaultGift.getProductCategory(), catId));if (defaultEligible) {logger.info("Fallback fired for order {} ({}) — default gift {} eligible, awarding it",order.getId(), reason, defaultGift.getId());createDefaultGiftScratch(order, offer, defaultGift);return;}// 2) Neckband safety net — management rule: when EW isn't allowed for this cart// (i.e. default fell through), award a Neckband from this offer's config.// Prefer a Neckband whose OWN slab covers this cart amount (so stock// decrements from the correct slab, matching per-slab quota intent).// Only if no slab-matching Neckband exists do we cross-slab pick.List<GiftEntity> allGifts = giftRepository.findByOfferId(offer.getId());List<GiftEntity> neckbandCandidates = allGifts.stream().filter(g -> g.getName() != null && "neckband".equalsIgnoreCase(g.getName().trim())).filter(g -> g.getMaxRedemptions() != null && g.getMaxRedemptions() > 0).filter(g -> itemCategoryIds.stream().anyMatch(catId ->isItemCategoryEligible(g.getProductCategory(), catId))).collect(Collectors.toList());// First pass — slab-matching Neckband (this cart's own slab).GiftEntity neckband = neckbandCandidates.stream().filter(g -> g.getMinCartValue() != null && g.getMaxCartValue() != null&& totalAmount >= g.getMinCartValue()&& totalAmount <= g.getMaxCartValue()).findFirst().orElse(null);// Second pass — cross-slab pick only if the correct slab has no Neckband left.if (neckband == null && !neckbandCandidates.isEmpty()) {neckband = neckbandCandidates.get(0);logger.info("Fallback for order {} ({}) — no slab-matching Neckband for cart={}; cross-slab picking row {}",order.getId(), reason, totalAmount, neckband.getId());}if (neckband != null) {logger.info("Fallback fired for order {} ({}) — awarding Neckband row {} (slab {}-{}) for cart={}",order.getId(), reason, neckband.getId(),neckband.getMinCartValue(), neckband.getMaxCartValue(), totalAmount);createScratchOffer(order, offer, neckband);return;}// 3) Absolute last resort — no default, no Neckband available anywhere.logger.warn("Fallback fired for order {} ({}) — no default gift AND no Neckband in stock; awarding Better Luck Next Time",order.getId(), reason);createBetterLuckNextTimeScratch(order, offer);}private boolean isItemCategoryEligible(String giftCategories, Integer itemCategoryId) {if (giftCategories == null || giftCategories.trim().isEmpty()) {return false;}return Arrays.stream(giftCategories.split(",")).map(String::trim).filter(s -> !s.isEmpty()).map(category -> {try {return Integer.parseInt(category);} catch (NumberFormatException e) {return -1;}}).anyMatch(itemCategoryId::equals);}private GiftEntity selectGiftByClassification(OfferEntity offer, List<GiftEntity> eligibleGifts, FofoOrder fofoOrder) {switch (offer.getClassification()) {case "Serial":return eligibleGifts.get(0);case "Random":List<GiftEntity> shuffled = new ArrayList<>(eligibleGifts);Collections.shuffle(shuffled);return shuffled.get(0);case "PartnerWise":int fofoId = fofoOrder.getFofoId();return eligibleGifts.stream().filter(gift -> gift.getFofoStore() != null&& Arrays.stream(gift.getFofoStore().split(",")).map(String::trim).anyMatch(s -> {try { return Integer.parseInt(s) == fofoId; }catch (NumberFormatException e) { return false; }})).findFirst().orElse(null);default:logger.warn("Unknown classification: {}", offer.getClassification());return null;}}public void createBetterLuckNextTimeScratch(FofoOrder order, OfferEntity offer) {ScratchOffer so = new ScratchOffer();so.setCustomerId(order.getCustomerId());so.setOfferId(offer.getId());so.setGiftId(1);so.setInvoiceNumber(order.getInvoiceNumber());so.setCreatedTimestamp(LocalDateTime.now());so.setOfferName(offer.getName());scratchOfferRepository.persist(so);}public void createScratchOffer(FofoOrder order, OfferEntity offer, GiftEntity gift) {int updated = giftRepository.decrementMaxRedemptions(gift.getId());if (updated == 0) {// Race: another concurrent scratch drained this gift's stock to 0 between// our eligibility read and our atomic decrement. We MUST still give the// customer a scratch record — either via the Neckband safety-net path or// (worst case) BLNT. Silent return here caused the "no scratch created"// bug that silently ate ~12+ orders on 2026-08-13.logger.warn("Gift {} decrement raced (stock hit 0); falling back to Neckband safety-net for order {}",gift.getId(), order.getId());try {GiftEntity defaultGift = giftRepository.getDefaultGift(offer.getId());float totalAmount = order.getTotalAmount();Set<Integer> itemCategoryIds = new HashSet<>();List<FofoOrderItem> orderItems = fofoOrderItemRepository.selectByOrderId(order.getId());for (FofoOrderItem oi : orderItems) {try {Item it = itemRepository.selectById(oi.getItemId());if (it != null) itemCategoryIds.add(it.getCategoryId());} catch (Exception ignore) {}}awardDefaultOrBlnt(order, offer, defaultGift, totalAmount, itemCategoryIds,"createScratchOffer-decrement-race");} catch (Exception e) {logger.error("Fallback after decrement race also failed for order {} — awarding BLNT",order.getId(), e);createBetterLuckNextTimeScratch(order, offer);}return;}ScratchOffer so = new ScratchOffer();so.setCustomerId(order.getCustomerId());so.setOfferId(offer.getId());so.setGiftId(gift.getId());so.setInvoiceNumber(order.getInvoiceNumber());so.setCreatedTimestamp(LocalDateTime.now());so.setOfferName(gift.getName());scratchOfferRepository.persist(so);logger.info("Created scratch offer for order {} gift {}", order.getId(), gift.getId());}public void createDefaultGiftScratch(FofoOrder order, OfferEntity offer, GiftEntity gift) {ScratchOffer so = new ScratchOffer();so.setCustomerId(order.getCustomerId());so.setOfferId(offer.getId());so.setGiftId(gift.getId());so.setInvoiceNumber(order.getInvoiceNumber());so.setCreatedTimestamp(LocalDateTime.now());so.setOfferName(gift.getName());scratchOfferRepository.persist(so);}public boolean createOffer(ScratchRequest request) {OfferEntity offer = mapToOfferEntity(request);seasonalOfferRepository.save(offer);List<GiftEntity> gifts = mapToGiftEntities(request.getGifts(), offer.getId());gifts.forEach(giftRepository::save);return offer.getId() > 0 && giftRepository.findByOfferId(offer.getId()).size() == request.getGifts().size();}public boolean updateOffer(Integer offerId, ScratchRequest request) {OfferEntity existingOffer = seasonalOfferRepository.findById(offerId);boolean res = false;if (existingOffer != null) {updateOfferEntity(existingOffer, request);updateGifts(existingOffer.getId(), request.getGifts());res = true;}return res;}//update offerprivate void updateOfferEntity(OfferEntity existingOffer, ScratchRequest request) {existingOffer.setName(request.getName());existingOffer.setDescription(request.getDescription());existingOffer.setStartDate(request.getStartDate());existingOffer.setEndDate(request.getEndDate());existingOffer.setTermsCondition(request.getTermsCondition());existingOffer.setUserLimit(request.getUserLimit());existingOffer.setScratchValidity(request.getScratchValidity());existingOffer.setProductCategory(request.getProductCategory());existingOffer.setPaymentMethod(request.getPaymentMethod());existingOffer.setClassification(request.getClassification());}//update giftprivate void updateGifts(Integer offerId, List<ScratchRequest.GiftData> giftsData) {Map<Integer, GiftEntity> existingGifts = giftRepository.findByOfferId(offerId).stream().collect(Collectors.toMap(GiftEntity::getId, Function.identity()));for (ScratchRequest.GiftData giftData : giftsData) {if (giftData.getId() != null && existingGifts.containsKey(giftData.getId())) {GiftEntity existingGift = existingGifts.get(giftData.getId());updateGiftEntity(existingGift, giftData);existingGifts.remove(giftData.getId());} else {GiftEntity gift = new GiftEntity();gift.setName(giftData.getName());gift.setThumbnailUrl(giftData.getThumbnailUrl());gift.setMinCartValue(giftData.getMinCartValue());gift.setMaxRedemptions(giftData.getMaxRedemptions());gift.setOfferId(offerId);gift.setMaxCartValue(giftData.getMaxCartValue());gift.setFofoStore(giftData.getFofoStore());gift.setProductCategory(giftData.getProductCategory());gift.setIsDefault(giftData.isDefault());giftRepository.save(gift);}}}private void updateGiftEntity(GiftEntity gift, ScratchRequest.GiftData data) {gift.setName(data.getName());gift.setThumbnailUrl(data.getThumbnailUrl());gift.setMinCartValue(data.getMinCartValue());gift.setMaxRedemptions(data.getMaxRedemptions());gift.setMaxCartValue(data.getMaxCartValue());gift.setFofoStore(data.getFofoStore());gift.setProductCategory(data.getProductCategory());gift.setIsDefault(data.isDefault());}//offerprivate OfferEntity mapToOfferEntity(ScratchRequest request) {OfferEntity offer = new OfferEntity();offer.setName(request.getName());offer.setDescription(request.getDescription());offer.setOfferImage(request.getOfferImage());offer.setOfferType(request.getOfferType());offer.setStartDate(request.getStartDate());offer.setEndDate(request.getEndDate());offer.setTermsCondition(request.getTermsCondition());offer.setUserLimit(request.getUserLimit());offer.setScratchValidity(request.getScratchValidity());offer.setProductCategory(request.getProductCategory());offer.setPaymentMethod(request.getPaymentMethod());offer.setClassification(request.getClassification());logger.info("active flag - " + request.isActive());offer.setActive(request.isActive());return offer;}//giftprivate List<GiftEntity> mapToGiftEntities(List<ScratchRequest.GiftData> giftsData, Integer offerId) {return giftsData.stream().map(giftData -> {logger.info("giftData: {}", giftData);GiftEntity gift = new GiftEntity();gift.setName(giftData.getName());gift.setThumbnailUrl(giftData.getThumbnailUrl());gift.setMinCartValue(giftData.getMinCartValue());gift.setMaxRedemptions(giftData.getMaxRedemptions());gift.setOfferId(offerId);gift.setMaxCartValue(giftData.getMaxCartValue());gift.setFofoStore(giftData.getFofoStore());gift.setProductCategory(giftData.getProductCategory());gift.setIsDefault(giftData.isDefault());return gift;}).collect(Collectors.toList());}}