Rev 34474 | Rev 35095 | 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.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;@Service@Transactional(rollbackFor = Throwable.class)public class ScratchService {private static final Logger logger = LogManager.getLogger(ScratchService.class);@AutowiredSeasonalOfferRepository seasonalOfferRepository;@AutowiredGiftRepository giftRepository;@AutowiredScratchOfferRepository scratchOfferRepository;@AutowiredFofoOrderItemRepository fofoOrderItemRepository;@AutowiredItemRepository itemRepository;@AutowiredFofoOrderRepository fofoOrderRepository;// @Transactional(propagation = Propagation.REQUIRES_NEW)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())) // Replace with the correct getter// .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());// Get the customer's scratch list and check if this offer is already thereInteger 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; // Customer already has this offer}// Proceed with processing the offer since customer hasn't scratched it yetif (this.processOffer(eligibleOffer, fofoOrder, itemCategoryId)) {logger.info("Successfully processed offer for item: {}", orderItemId);return; // Successfully processed an offer} 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; // No eligible gifts found for any items}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();for (OfferEntity activeOffer : activeOffers) {if ("PartnerWise".equals(activeOffer.getClassification())) {List<GiftEntity> gifts = giftRepository.findByOfferId(activeOffer.getId());for (GiftEntity gift : gifts) {String[] fofoStores = gift.getFofoStore().split(",");boolean isEligible = Arrays.stream(fofoStores).map(Integer::parseInt) // Convert string to int.anyMatch(fofoStore -> fofoStore == customerFofoId);if (isEligible) {return activeOffer; // Return the matching PARTNERWISE offer}}}}// If no PARTNERWISE offer matches, return the first non-PARTNERWISE offer (if any)return activeOffers.stream().filter(offer -> !"PartnerWise".equals(offer.getClassification())).findFirst().orElse(null);}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) {Integer giftCounts = scratchOfferRepository.countTodaysGiftDistribution(date.atStartOfDay(), date.atTime(LocalTime.MAX));return giftCounts < perDayMaxQty;}private boolean processOffer(OfferEntity offer, FofoOrder fofoOrder, Integer itemCategoryId) {List<GiftEntity> allGifts = giftRepository.findByOfferId(offer.getId());logger.info("Processing offer {} for order {}", offer.getId(), fofoOrder.getId());//Is more gift availableint perDayMaxQty = offer.getUserLimit();LocalDate today = LocalDate.now();boolean isMoreGiftAvailable = checkGiftAvailability(today, perDayMaxQty);logger.info("Gift availability result: {}", isMoreGiftAvailable);if (!isMoreGiftAvailable) {logger.info("Daily limit reached - assigning default gift");createScratchOffer(fofoOrder, offer, allGifts.get(0));return true;}// Get eligible giftsList<GiftEntity> eligibleGifts = findEligibleGifts(offer, fofoOrder, itemCategoryId);if (eligibleGifts.isEmpty()) {logger.info("No eligible gifts found for offer: {}", offer.getId());createScratchOffer(fofoOrder, offer, allGifts.get(0));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);// Filter eligible giftsList<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);// Handle null or empty categoriesif (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; // Will not match any real category ID}}).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());// Create a new list for remaining gifts to avoid modifying the original listList<GiftEntity> giftPool = new ArrayList<>(gifts.subList(0, gifts.size()));// Shuffle the gift pool to increase randomnessCollections.shuffle(giftPool);// Find a valid gift from the shuffled poolreturn 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);// Stream approach - consistent with FindEligibleOffer methodreturn 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; // Invalid ID that won't match}}).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;}private void createScratchOffer(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());gift.setMaxRedemptions(gift.getMaxRedemptions() - 1);scratchOfferRepository.persist(so);logger.info("Created scratch offer: {}", 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());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());}//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());return gift;}).collect(Collectors.toList());}}