Rev 35271 | Go to most recent revision | 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.common.model.CustomPaymentOption;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 org.springframework.transaction.annotation.Transactional;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<CustomPaymentOption> paymentOptions, Set<Integer> fofoOrderItemIds) throws ProfitMandiBusinessException {FofoOrder fofoOrder = fofoOrderRepository.selectByOrderId(fofoOrderId);logger.info("Starting scratchOffer for order: {}", fofoOrder);LocalDate today = LocalDate.now();for (Integer orderItemId : fofoOrderItemIds) {logger.info("Processing order item: {}", orderItemId);Item item = this.getItemById(orderItemId);Integer itemCategoryId = item.getCategoryId();logger.info("Item category ID: {}", itemCategoryId);// Find active offerList<OfferEntity> activeOffers = seasonalOfferRepository.getOffersByDate(today);logger.info("Found Active offer: {}", activeOffers);OfferEntity eligibleOffer = FindEligibleOffer(activeOffers, fofoOrder);// Set<String> allowedPaymentMethods = Arrays.stream(eligibleOffer.getPaymentMethod().split(","))// .collect(Collectors.toSet());//// boolean isOtherPaymentUsed = paymentOptions.stream()// .map(option -> String.valueOf(option.getPaymentOptionId()))// .anyMatch(paymentId -> !allowedPaymentMethods.contains(paymentId));// // proceed with default because user payment method doest not match// if (isOtherPaymentUsed) {// logger.info("Customer {} has chosen a different mode of payment.", fofoOrder.getCustomerId());// return;// }if (eligibleOffer != null) {logger.info("Found active offer: {}", eligibleOffer.getId());Integer activeOfferId = eligibleOffer.getId();logger.info("Active offer ID: {}, Type: {}", activeOfferId, eligibleOffer.getName());ScratchOffer availedOffer = scratchOfferRepository.getScractchOfferByCustomerIdAndOfferId(fofoOrder.getCustomerId(), activeOfferId);if (availedOffer != null) {logger.info("Customer {} has already scratched offer ID {}. No new gift will be allotted.", fofoOrder.getCustomerId(), activeOfferId);return;}if (this.processOffer(eligibleOffer, fofoOrder, itemCategoryId)) {logger.info("Successfully processed offer for item: {}", orderItemId);break;} else {logger.info("Failed to process offer for item: {}", orderItemId);}} else {logger.info("No active offers found for today: {}", today);}logger.info("No eligible gifts found for any items in order: {}", fofoOrder.getId());return;}logger.info("No eligible gifts found for any items in order: {}", fofoOrder.getId());}private OfferEntity FindEligibleOffer(List<OfferEntity> activeOffers, FofoOrder fofoOrder) {int customerFofoId = fofoOrder.getFofoId();logger.info("Looking for eligible offer for customer fofoId: {}", customerFofoId);logger.info("Total active offers to check: {}", activeOffers.size());for (OfferEntity activeOffer : activeOffers) {logger.info("Checking offer ID: {} with classification: {}", activeOffer.getId(), activeOffer.getClassification());if ("PartnerWise".equals(activeOffer.getClassification())) {List<GiftEntity> gifts = giftRepository.findByOfferId(activeOffer.getId());logger.info("Found {} gifts for PartnerWise offer {}", gifts.size(), activeOffer.getId());if (gifts.isEmpty()) {logger.warn("No gifts found for PartnerWise offer {}", activeOffer.getId());continue;}for (GiftEntity gift : gifts) {logger.info("Checking gift ID: {} with fofoStore: '{}'", gift.getId(), gift.getFofoStore());if (gift.getFofoStore() == null || gift.getFofoStore().trim().isEmpty()) {logger.warn("Gift {} has null/empty fofoStore - skipping", gift.getId());continue;}String[] fofoStores = gift.getFofoStore().split(",");logger.info("Gift {} fofoStores array: {}", gift.getId(), Arrays.toString(fofoStores));boolean isEligible = Arrays.stream(fofoStores).filter(store -> !store.trim().isEmpty()).map(store -> {try {int storeId = Integer.parseInt(store.trim());logger.info("Parsed fofoStore: {} (comparing with customer fofoId: {})", storeId, customerFofoId);return storeId;} catch (NumberFormatException e) {logger.warn("Invalid fofoId format in gift {}: '{}' - skipping", gift.getId(), store);return -1;}}).anyMatch(fofoStore -> {boolean matches = fofoStore == customerFofoId;logger.info("FofoStore {} matches customer fofoId {}: {}", fofoStore, customerFofoId, matches);return matches;});logger.info("Gift {} is eligible for customer fofoId {}: {}", gift.getId(), customerFofoId, isEligible);if (isEligible) {logger.info("Found matching PartnerWise offer: {} for customer fofoId: {}", activeOffer.getId(), customerFofoId);return activeOffer;}}logger.info("No matching gifts found in PartnerWise offer {} for customer fofoId: {}", activeOffer.getId(), customerFofoId);} else {logger.info("Offer {} is not PartnerWise (classification: {})", activeOffer.getId(), activeOffer.getClassification());}}logger.info("No PartnerWise offers matched. Looking for non-PartnerWise offers...");List<OfferEntity> nonPartnerWiseOffers = activeOffers.stream().filter(offer -> !"PartnerWise".equals(offer.getClassification())).collect(Collectors.toList());logger.info("Found {} non-PartnerWise offers", nonPartnerWiseOffers.size());OfferEntity selectedOffer = nonPartnerWiseOffers.stream().findFirst().orElse(null);if (selectedOffer != null) {logger.info("Selected non-PartnerWise offer: {}", selectedOffer.getId());} else {logger.info("No eligible offers found for customer fofoId: {}", customerFofoId);}return selectedOffer;}private Item getItemById(Integer itemId) throws ProfitMandiBusinessException {logger.info("Getting item by ID: {}", itemId);Item item = itemRepository.selectById(itemId);if (item == null) {logger.warn("Item not found with ID: {}", itemId);}return item;}private boolean checkGiftAvailability(LocalDate date, int perDayMaxQty, OfferEntity offer) {int defaultGiftId = giftRepository.getDefaultGift(offer.getId()).getId();Integer giftCounts = scratchOfferRepository.countTodaysGiftDistribution(date.atStartOfDay(), date.atTime(LocalTime.MAX), defaultGiftId);return giftCounts < perDayMaxQty;}private boolean isAppleItem(FofoOrder fofoOrder) {List<FofoOrderItem> fofoOrderItems = fofoOrderItemRepository.selectByOrderId(fofoOrder.getId());if (fofoOrderItems == null || fofoOrderItems.isEmpty()) {return false;}for (FofoOrderItem item : fofoOrderItems) {if (item != null && item.getBrand() != null && item.getBrand().equalsIgnoreCase("apple")) {return true;}}return false;}private boolean processOffer(OfferEntity offer, FofoOrder fofoOrder, Integer itemCategoryId) {List<GiftEntity> allGifts = giftRepository.findNonDefaultGIft(offer.getId());logger.info("Processing offer {} for order {}", offer.getId(), fofoOrder.getId());if (isAppleItem(fofoOrder)) {GiftEntity lastGift = allGifts.get(allGifts.size() - 1);if (lastGift.getMaxRedemptions() > 0) {createScratchOffer(fofoOrder, offer, lastGift);logger.info("Apple item detected, assigned last gift: {}", lastGift.getName());} else {logger.info("Apple last gift exhausted, assigning Better Luck Next Time");createBetterLuckNextTimeScratch(fofoOrder, offer);}return true;}int perDayMaxQty = offer.getUserLimit();LocalDate today = LocalDate.now();boolean isMoreGiftAvailable = checkGiftAvailability(today, perDayMaxQty, offer);logger.info("Gift availability result: {}", isMoreGiftAvailable);if (allGifts.isEmpty()) {logger.warn("Offer {} has no gifts - cannot process scratch offer", offer.getId());return false;}if (!isMoreGiftAvailable) {logger.info("Daily limit reached - assigning default gift");createDefaultGiftScratch(fofoOrder, offer, giftRepository.getDefaultGift(offer.getId()));return true;}// Get eligible giftsList<GiftEntity> eligibleGifts = findEligibleGifts(offer, fofoOrder, itemCategoryId);if (eligibleGifts.isEmpty()) {logger.info("No eligible gifts found for offer: {}", offer.getId());createDefaultGiftScratch(fofoOrder, offer, giftRepository.getDefaultGift(offer.getId()));return true;}logger.info("Found {} eligible gifts for offer {}", eligibleGifts.size(), offer.getId());// Select gift based on classificationGiftEntity selectedGift = selectGiftByClassification(offer, eligibleGifts, fofoOrder);if (selectedGift == null || selectedGift.getMaxRedemptions() <= 0) {logger.info("No valid gift selected for offer: {}", offer.getId());createScratchOffer(fofoOrder, offer, allGifts.get(0));return true;}logger.info("Selected gift: {} for order: {}", selectedGift.getId(), fofoOrder.getId());// Create scratch offercreateScratchOffer(fofoOrder, offer, selectedGift);logger.info("Created scratch offer for order: {}", fofoOrder.getId());return true;}private List<GiftEntity> findEligibleGifts(OfferEntity offer, FofoOrder fofoOrder, Integer itemCategoryId) {logger.info("Finding eligible gifts for offer: {} and category: {}", offer.getId(), itemCategoryId);List<GiftEntity> activeOfferGifts = giftRepository.findByOfferId(offer.getId());logger.info("Total gifts for offer {}: {}", offer.getId(), activeOfferGifts.size());float totalAmount = fofoOrder.getTotalAmount();logger.info("Order total amount: {}", totalAmount);List<GiftEntity> eligibleGifts = activeOfferGifts.stream().filter(gift -> isGiftEligible(gift, totalAmount, itemCategoryId)).collect(Collectors.toList());logger.info("Filtered to {} eligible gifts", eligibleGifts.size());for (GiftEntity gift : activeOfferGifts) {boolean isEligible = isGiftEligible(gift, totalAmount, itemCategoryId);logger.info("Gift ID: {}, Name: {}, MinCart: {}, MaxCart: {}, Categories: {}, MaxRedemptions: {}, Eligible: {}",gift.getId(), gift.getName(), gift.getMinCartValue(), gift.getMaxCartValue(),gift.getProductCategory(), gift.getMaxRedemptions(), isEligible);}return eligibleGifts;}private boolean isGiftEligible(GiftEntity gift, float totalAmount, Integer itemCategoryId) {logger.info("Checking eligibility for gift: {}", gift.getId());// Check cart value conditionboolean cartValueCondition = totalAmount >= gift.getMinCartValue() && totalAmount <= gift.getMaxCartValue() && gift.getMaxRedemptions() > 0;logger.info("Gift {} cart value condition: {} (order amount: {}, min: {}, max: {}, max redemptions: {})",gift.getId(), cartValueCondition, totalAmount,gift.getMinCartValue(), gift.getMaxCartValue(),gift.getMaxRedemptions());// Check category conditionboolean categoryCondition = isItemCategoryEligible(gift.getProductCategory(), itemCategoryId);logger.info("Gift {} category condition: {} (item category: {}, gift categories: {})", gift.getId(), categoryCondition, itemCategoryId, gift.getProductCategory());boolean isEligible = cartValueCondition && categoryCondition;logger.info("Gift {} is eligible: {}", gift.getId(), isEligible);return isEligible;}private boolean isItemCategoryEligible(String giftCategories, Integer itemCategoryId) {logger.info("Checking if item category {} is in gift categories: {}", itemCategoryId, giftCategories);if (giftCategories == null || giftCategories.trim().isEmpty()) {return false;}return Arrays.stream(giftCategories.split(",")).filter(category -> !category.trim().isEmpty()) // Filter out empty strings.map(category -> {try {return Integer.parseInt(category.trim());} catch (NumberFormatException e) {logger.warn("Invalid category format in gift categories: {}", category);return -1;}}).anyMatch(itemCategoryId::equals);}private GiftEntity selectGiftByClassification(OfferEntity offer, List<GiftEntity> eligibleGifts, FofoOrder fofoOrder) {String classification = offer.getClassification();logger.info("Selecting gift by classification: {}", classification);GiftEntity selectedGift;switch (classification) {case "Serial":logger.info("Using Serial gift selection strategy");selectedGift = handleSerialGifts(eligibleGifts);break;case "Random":logger.info("Using Random gift selection strategy");selectedGift = handleRandomGifts(eligibleGifts);break;case "PartnerWise":logger.info("Using PartnerWise gift selection strategy");selectedGift = handlePartnerWiseGifts(eligibleGifts, fofoOrder);break;case "BrandSpecific":logger.info("Using BrandSpecific gift selection strategy");selectedGift = handleBrandSpecificGifts(eligibleGifts, fofoOrder);break;default:logger.warn("Unknown classification: {}", classification);selectedGift = null;}if (selectedGift != null) {logger.info("Selected gift: {}", selectedGift.getId());} else {logger.warn("No gift selected for classification: {}", classification);}return selectedGift;}private GiftEntity handleSerialGifts(List<GiftEntity> gifts) {logger.info("Handling Serial gifts selection from {} gifts", gifts.size());return gifts.get(0);}private GiftEntity handleRandomGifts(List<GiftEntity> gifts) {logger.info("Handling Random gifts selection from {} gifts", gifts.size());List<GiftEntity> giftPool = new ArrayList<>(gifts.subList(0, gifts.size()));Collections.shuffle(giftPool);return giftPool.get(0);}private GiftEntity handlePartnerWiseGifts(List<GiftEntity> gifts, FofoOrder order) {int fofoId = order.getFofoId();logger.info("Handling PartnerWise gifts selection from {} gifts for fofoId {}", gifts.size(), fofoId);return gifts.stream().filter(gift -> {String[] fofoStores = gift.getFofoStore().split(",");return Arrays.stream(fofoStores).map(storeId -> {try {return Integer.parseInt(storeId.trim());} catch (NumberFormatException e) {logger.warn("Invalid fofoId format in gift {}: {}", gift.getId(), storeId);return -1;}}).anyMatch(storeId -> storeId == fofoId);}).findFirst().orElse(null);}private GiftEntity handleBrandSpecificGifts(List<GiftEntity> gifts, FofoOrder order) {logger.info("Handling BrandSpecific gifts selection from {} gifts for order {}", gifts.size(), order.getId());// TODO: Add brand-specific logiclogger.warn("BrandSpecific gift selection not implemented yet");return null;}public void createBetterLuckNextTimeScratch(FofoOrder order, OfferEntity offer) {try {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);logger.info("Created Better Luck Next Time scratch offer: {}", so);} catch (Exception e) {logger.error("Error creating Better Luck Next Time scratch offer: {}", e.getMessage());}}public void createScratchOffer(FofoOrder order, OfferEntity offer, GiftEntity gift) {try {GiftEntity currentGift = giftRepository.findById(gift.getId());if (currentGift == null || currentGift.getMaxRedemptions() <= 0) {logger.warn("Gift {} has no remaining redemptions", gift.getId());return;}currentGift.setMaxRedemptions(currentGift.getMaxRedemptions() - 1);giftRepository.save(currentGift);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: {}", so);} catch (Exception e) {logger.error("Error creating scratch offer: {}", e.getMessage());}}public void createDefaultGiftScratch(FofoOrder order, OfferEntity offer, GiftEntity gift) {try {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 default (unlimited) scratch offer: {}", so);} catch (Exception e) {logger.error("Error creating default scratch offer: {}", e.getMessage());}}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());}}