Rev 35925 | Go to most recent revision | View as "text/plain" | Blame | Compare with Previous | Last modification | View Log | RSS feed
package com.spice.profitmandi.web.controller;import com.google.gson.Gson;import com.jcraft.jsch.*;import com.spice.profitmandi.common.enumuration.MessageType;import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;import com.spice.profitmandi.common.model.CustomRetailer;import com.spice.profitmandi.common.model.ProfitMandiConstants;import com.spice.profitmandi.common.model.SendNotificationModel;import com.spice.profitmandi.common.util.FormattingUtils;import com.spice.profitmandi.common.web.util.ResponseSender;import com.spice.profitmandi.dao.entity.catalog.BrandCatalog;import com.spice.profitmandi.dao.entity.catalog.Offer;import com.spice.profitmandi.dao.entity.fofo.PartnerType;import com.spice.profitmandi.dao.enumuration.catalog.ItemCriteriaType;import com.spice.profitmandi.dao.enumuration.catalog.OfferSchemeType;import com.spice.profitmandi.dao.model.CreateOfferRequest;import com.spice.profitmandi.dao.model.ItemCriteriaPayout;import com.spice.profitmandi.dao.model.TodayOfferModel;import com.spice.profitmandi.dao.repository.catalog.CatalogRepository;import com.spice.profitmandi.dao.repository.catalog.ItemRepository;import com.spice.profitmandi.dao.repository.catalog.OfferMarginRepository;import com.spice.profitmandi.dao.repository.catalog.OfferRepository;import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;import com.spice.profitmandi.dao.repository.dtr.UserAccountRepository;import com.spice.profitmandi.service.NotificationService;import com.spice.profitmandi.service.authentication.RoleManager;import com.spice.profitmandi.service.catalog.BrandsService;import com.spice.profitmandi.service.offers.ItemCriteria;import com.spice.profitmandi.service.offers.OfferService;import com.spice.profitmandi.service.offers.PartnerCriteria;import com.spice.profitmandi.service.offers.TodayOfferService;import com.spice.profitmandi.service.user.RetailerService;import com.spice.profitmandi.web.model.LoginDetails;import com.spice.profitmandi.web.util.CookiesProcessor;import com.spice.profitmandi.web.util.MVCResponseSender;import org.apache.commons.io.FileUtils;import org.apache.commons.io.output.ByteArrayOutputStream;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.cache.CacheManager;import org.springframework.cache.interceptor.SimpleKey;import org.springframework.core.io.InputStreamResource;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.mock.web.MockHttpServletRequest;import org.springframework.mock.web.MockHttpServletResponse;import org.springframework.stereotype.Controller;import org.springframework.transaction.annotation.Transactional;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;import org.springframework.web.servlet.View;import org.springframework.web.servlet.ViewResolver;import org.xhtmlrenderer.swing.Java2DRenderer;import javax.imageio.ImageIO;import javax.servlet.http.HttpServletRequest;import java.awt.*;import java.awt.image.BufferedImage;import java.io.ByteArrayInputStream;import java.io.File;import java.io.FileNotFoundException;import java.io.InputStream;import java.time.Instant;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.YearMonth;import java.util.*;import java.util.List;import java.util.stream.Collectors;@Controller@Transactional(rollbackFor = Throwable.class)public class OfferController {private static final Logger LOGGER = LogManager.getLogger(OfferController.class);private static final String IMAGE_REMOTE_DIR = "/var/www/dtrdashboard/uploads/campaigns/";private static final String IMAGE_STATIC_SERVER_URL = "https://images.smartdukaan.com/uploads/campaigns";@AutowiredUserAccountRepository userAccountRepository;@AutowiredRoleManager roleManager;@Autowiredprivate OfferRepository offerRepository;@Autowiredprivate OfferMarginRepository offerMarginRepository;@Autowiredprivate FofoStoreRepository fofoStoreRepository;@Autowiredprivate ResponseSender responseSender;@Autowiredprivate ViewResolver viewResolver;@Autowiredprivate ItemRepository itemRepository;@Autowiredprivate MVCResponseSender mvcResponseSender;@Autowiredprivate RetailerService retailerService;@Autowiredprivate NotificationService notificationService;@Autowiredprivate CookiesProcessor cookiesProcessor;@Autowiredprivate OfferService offerService;@Autowiredprivate CacheManager oneDayCacheManager;@Autowiredprivate CacheManager redisCacheManager;@Autowiredprivate CacheManager redisShortCacheManager;@Autowiredprivate CacheManager thirtyMinsTimeOutCacheManager;@Autowiredprivate Gson gson;@AutowiredBrandsService brandsService;@Autowiredprivate CatalogRepository catalogRepository;@AutowiredTodayOfferService todayOfferService;@RequestMapping(value = "/getCreateOffer", method = RequestMethod.GET)public String getCreateOffer(HttpServletRequest request, Model model) throws ProfitMandiBusinessException {LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);List<Integer> fofoIds = fofoStoreRepository.selectActiveStores().stream().map(x -> x.getId()).collect(Collectors.toList());Set<String> brands = brandsService.getBrandsToDisplay(3).stream().map(x -> x.getName()).collect(Collectors.toSet());brands.addAll(itemRepository.selectAllBrands(ProfitMandiConstants.LED_CATEGORY_ID));brands.addAll(itemRepository.selectAllBrands(ProfitMandiConstants.SMART_WATCH_CATEGORY_ID));//Lets allow demobrands.add("Live Demo");Map<Integer, CustomRetailer> customRetailerMap = retailerService.getAllFofoRetailers();Map<Integer, CustomRetailer> customRetailersMap = fofoIds.stream().map(x -> customRetailerMap.get(x)).filter(x -> x != null).collect(Collectors.toList()).stream().collect(Collectors.toMap(x -> x.getPartnerId(), x -> x));model.addAttribute("customRetailersMap", customRetailersMap);model.addAttribute("itemCriteriaType", ItemCriteriaType.values());model.addAttribute("brands", brands);model.addAttribute("partnerCategories", PartnerType.values());model.addAttribute("warehouseRegion", ProfitMandiConstants.WAREHOUSE_MAP);return "scheme_offer";}@RequestMapping(value = "/createOffer", method = RequestMethod.POST)public String createOffer(HttpServletRequest request, @RequestBody CreateOfferRequest createOfferRequest,Model model) throws Exception {LOGGER.info("createOfferRequest [{}]", createOfferRequest);offerService.addOfferService(createOfferRequest);oneDayCacheManager.getCache("allOffers").evict(YearMonth.from(createOfferRequest.getStartDate()));model.addAttribute("response1", mvcResponseSender.createResponseString(true));return "response";}@RequestMapping(value = "/offers/published", method = RequestMethod.GET)public String getPublishedOffers(HttpServletRequest request, @RequestParam int fofoId, Model model)throws Exception {LOGGER.info("Published");offerService.getPublishedOffers(fofoId, YearMonth.from(LocalDateTime.now()));return "scheme_offer/published";}@Value("${prod}")private boolean isProd;@RequestMapping(value = "/offer/active/{offerId}", method = RequestMethod.GET)public String activateOffer(HttpServletRequest request, @PathVariable(name = "offerId") String offerIdsString,Model model, @RequestParam(defaultValue = "true") boolean active)throws ProfitMandiBusinessException, Exception {List<Integer> offerIds = Arrays.stream(offerIdsString.split(",")).map(x -> Integer.parseInt(x)).collect(Collectors.toList());List<Offer> offers = offerRepository.selectAllByIds(offerIds);//Consider only offers that have opposite statusoffers = offers.stream().filter(x -> x.isActive() != active).collect(Collectors.toList());Set<YearMonth> yearMonthsToEvict = new HashSet<>();for (Offer offer : offers) {offer.setActive(active);yearMonthsToEvict.add(YearMonth.from(offer.getStartDate()));}// Evict partner-to-offer mapping (which partners see which offers)for (YearMonth ymToEvict : yearMonthsToEvict) {redisCacheManager.getCache("catalog.published_yearmonth").evict(ymToEvict);oneDayCacheManager.getCache("allOffers").evict(ymToEvict);}// Evict partner's published offer list with achievement dataoneDayCacheManager.getCache("publishedOffersWithAchievement").clear();redisShortCacheManager.getCache("todayOffers").clear();// Evict all partner offer caches (price circular)thirtyMinsTimeOutCacheManager.getCache("partnerOffers").clear();if (active) {for (Offer offer : offers) {this.sendNotification(offer);}}model.addAttribute("response1", mvcResponseSender.createResponseString(true));return "response";}@RequestMapping(value = "/offers/publishAll", method = RequestMethod.POST)public ResponseEntity<?> publishAllUnpublished(@RequestParam YearMonth yearMonth)throws ProfitMandiBusinessException, Exception {List<Offer> published = offerService.publishAllUnpublished(yearMonth);if (!published.isEmpty()) {// Evict partner-to-offer mapping and offer listingredisCacheManager.getCache("catalog.published_yearmonth").evict(yearMonth);oneDayCacheManager.getCache("allOffers").evict(yearMonth);oneDayCacheManager.getCache("publishedOffersWithAchievement").clear();redisShortCacheManager.getCache("todayOffers").clear();// Evict all partner offer caches (price circular)thirtyMinsTimeOutCacheManager.getCache("partnerOffers").clear();for (Offer offer : published) {this.sendNotification(offer);}}return responseSender.ok(published.size() + " offers published");}@RequestMapping(value = "/offer/delete/{offerId}", method = RequestMethod.DELETE)public ResponseEntity<?> deleteOffer(@PathVariable int offerId) throws ProfitMandiBusinessException {offerService.deleteOffer(offerId);return responseSender.ok(true);}@RequestMapping(value = "/offer/testimage/{offerId}", method = RequestMethod.GET)public String testOffer(HttpServletRequest request, @PathVariable int offerId, Model model,@RequestParam(defaultValue = "true") boolean active) throws ProfitMandiBusinessException, Exception {Offer offer = offerRepository.selectById(offerId);// model.addAttribute("response1", mvcResponseSender.createResponseString(true));// return "response";CreateOfferRequest createOfferRequest = offerService.getCreateOfferRequest(offer);Map<String, Object> model1 = new HashMap<>();model1.put("offer", createOfferRequest);model1.put("lessThan", "<");String htmlContent = this.getContentFromTemplate("offer_margin_detail_notify", model1);model.addAttribute("response1", htmlContent);return "response";}private void sendNotification(Offer offer) throws Exception {if (!YearMonth.from(offer.getStartDate()).equals(YearMonth.now())) {return;}String fileName = "offer-" + offer.getId() + ".png";//String htmlFileName = fileName.replace("png", "html");CreateOfferRequest createOfferRequest = offerService.getCreateOfferRequest(offer);boolean isLiveDemo = createOfferRequest.getTargetSlabs().stream().map(x -> x.getItemCriteriaPayouts()).flatMap(List::stream).map(ItemCriteriaPayout::getItemCriteria).map(ItemCriteria::getCatalogIds).flatMap(List::stream).anyMatch(catalogId -> catalogRepository.selectCatalogById(catalogId).getBrand().equals("Live Demo"));if (!isLiveDemo) {SendNotificationModel sendNotificationModel = new SendNotificationModel();sendNotificationModel.setCampaignName("SchemeOffer");sendNotificationModel.setTitle(offer.getName());sendNotificationModel.setMessage(createOfferRequest.getSchemeType().name() + " of select models, "+ FormattingUtils.formatDateMonth(offer.getStartDate()) + " to "+ FormattingUtils.formatDateMonth(offer.getEndDate()));sendNotificationModel.setType("url");String imageUrl = IMAGE_STATIC_SERVER_URL + "/" + "image" + LocalDate.now() + "/" + fileName;sendNotificationModel.setImageUrl(imageUrl);sendNotificationModel.setUrl("https://app.smartdukaan.com/pages/home/notifications");sendNotificationModel.setExpiresat(LocalDateTime.now().plusDays(1));sendNotificationModel.setMessageType(MessageType.scheme);//Map<Integer, List<Offer>> offersMap = offerRepository.selectAllPublishedMapByPartner(YearMonth.now());Map<String, InputStream> fileStreamsMap = new HashMap<>();Map<String, Object> model = new HashMap<>();model.put("offer", createOfferRequest);String htmlContent = this.getContentFromTemplate("offer_margin_detail_notify", model);LOGGER.info("this.getContentFromTemplate {}", htmlContent);fileStreamsMap.put(fileName, this.getImageBuffer(htmlContent));// fileStreamsMap.put(htmlFileName, new// ByteArrayInputStream(htmlContent.getBytes()));List<Integer> fofoIds = null;if (isProd) {this.uploadFile(fileStreamsMap);}List<Integer> fofoIdSet = new ArrayList<>(offerRepository.getEligibleFofoIds(offer));//LOGGER.info(fofoIdSet);List<Integer> userIds = userAccountRepository.selectUserIdsByRetailerIds(new ArrayList<>(fofoIdSet));sendNotificationModel.setUserIds(userIds);notificationService.sendNotification(sendNotificationModel);sendWhatsapp(offer, fofoIds, imageUrl);}}private void sendWhatsapp(Offer offer, List<Integer> fofoIds, String imageUrl) throws Exception {offerService.sendWhatsapp(offer, fofoIds, imageUrl);}private InputStream asInputStream(BufferedImage bi) throws Exception {ByteArrayOutputStream baos = new ByteArrayOutputStream();ImageIO.write(bi, "png", baos);return new ByteArrayInputStream(baos.toByteArray());}private ChannelSftp setupJsch() throws JSchException {JSch jsch = new JSch();Session jschSession = jsch.getSession("root", "172.105.58.16");// Session jschSession = jsch.getSession("root", "173.255.254.24");LOGGER.info("getClass().getResource(\"id_rsa\") {}",getClass().getClassLoader().getResource("id_rsa").getPath());jsch.addIdentity(getClass().getClassLoader().getResource("id_rsa").getPath());// jschSession.setPassword("spic@2015static0");jschSession.setConfig("StrictHostKeyChecking", "no");jschSession.connect();return (ChannelSftp) jschSession.openChannel("sftp");}private void fileUpload(ChannelSftp channelSftp, Map<String, InputStream> streamsFileMap, String destinationPath)throws SftpException, FileNotFoundException {channelSftp.cd(destinationPath);String folderName = "image" + LocalDate.now();channelSftp.cd(destinationPath);SftpATTRS attrs = null;// check if the directory is already existingtry {attrs = channelSftp.stat(folderName);} catch (Exception e) {System.out.println(destinationPath + "/" + folderName + " not found");}// else create a directoryif (attrs == null) {channelSftp.mkdir(folderName);channelSftp.chmod(0755, ".");}channelSftp.cd(folderName);for (Map.Entry<String, InputStream> streamsFileEntry : streamsFileMap.entrySet()) {channelSftp.put(streamsFileEntry.getValue(), streamsFileEntry.getKey(), ChannelSftp.OVERWRITE);}}private void uploadFile(Map<String, InputStream> fileStreamsMap) throws Exception {ChannelSftp channelSftp = setupJsch();channelSftp.connect();this.fileUpload(channelSftp, fileStreamsMap, IMAGE_REMOTE_DIR + "");channelSftp.exit();}private InputStream getImageBuffer(String html) throws Exception {// Sanitize HTML to valid XHTML for Flying Saucer (Java2DRenderer requires well-formed XML)org.jsoup.nodes.Document doc = org.jsoup.Jsoup.parse(html);doc.outputSettings().syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml);doc.outputSettings().escapeMode(org.jsoup.nodes.Entities.EscapeMode.xhtml);doc.outputSettings().charset("UTF-8");html = doc.html();String fileName = "/tmp/" + Instant.now().toEpochMilli();FileUtils.writeStringToFile(new File(fileName), html, "UTF-8");String address = "file:" + fileName;Java2DRenderer renderer = new Java2DRenderer(address, 400);RenderingHints hints = new RenderingHints(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_QUALITY);hints.add(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));hints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));hints.add(new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC));renderer.setRenderingHints(hints);BufferedImage img = renderer.getImage();ByteArrayOutputStream os = new ByteArrayOutputStream();ImageIO.write(img, "png", os);return new ByteArrayInputStream(os.toByteArray());}private String getContentFromTemplate(String template, Map<String, Object> model) throws Exception {View resolvedView = viewResolver.resolveViewName(template, Locale.US);MockHttpServletResponse mockResp = new MockHttpServletResponse();MockHttpServletRequest req = new MockHttpServletRequest();LOGGER.info("Resolved view -> {}, {}, {}, {}", resolvedView, model, req, mockResp);resolvedView.render(model, req, mockResp);return mockResp.getContentAsString();}@RequestMapping(value = "/offerHistory", method = RequestMethod.GET)public String getPaginatedOffers(HttpServletRequest request, @RequestParam YearMonth yearMonth, Model model)throws ProfitMandiBusinessException {List<CreateOfferRequest> publishedOffers = offerService.getAllOffers(yearMonth).values().stream().sorted(Comparator.comparing(CreateOfferRequest::getId).reversed()).collect(Collectors.toList());model.addAttribute("offers", publishedOffers);model.addAttribute("yearMonth", yearMonth);model.addAttribute("currentMonth", yearMonth.equals(YearMonth.now()));return "offer_history";}@RequestMapping(value = "/offer-details", method = RequestMethod.GET)public String schemeDetails(HttpServletRequest request, @RequestParam int offerId, Model model)throws ProfitMandiBusinessException {CreateOfferRequest createOfferRequest = offerService.getOffer(0, offerId);model.addAttribute("offer", createOfferRequest);return "offer-details";}@RequestMapping(value = "/offer/process/{offerId}", method = RequestMethod.GET)public ResponseEntity<?> processOfferRequest(HttpServletRequest request, @PathVariable int offerId, Model model)throws Exception {CreateOfferRequest createOfferRequest = offerService.getOffer(0, offerId);if (!createOfferRequest.isActive()) {throw new ProfitMandiBusinessException("Offer not active", "Offer not active", "Offer not active");}if (createOfferRequest.getSchemeType().equals(OfferSchemeType.SELLIN)) {offerService.processSellin(createOfferRequest);} else if (createOfferRequest.getSchemeType().equals(OfferSchemeType.ACTIVATION)) {offerService.processActivationtOffer(createOfferRequest);}return responseSender.ok(true);}@RequestMapping(value = "/offerDownload", method = RequestMethod.GET)public ResponseEntity<?> dowloadOfferSummary(HttpServletRequest request, @RequestParam int offerId, Model model)throws Exception {final HttpHeaders headers = new HttpHeaders();headers.set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");headers.set("Content-disposition", "inline; filename=offer-" + offerId + ".csv");CreateOfferRequest createOfferRequest = offerService.getOffer(0, offerId);ByteArrayOutputStream baos = offerService.createCSVOfferReport(createOfferRequest);final InputStream inputStream = new ByteArrayInputStream(baos.toByteArray());final InputStreamResource inputStreamResource = new InputStreamResource(inputStream);return new ResponseEntity<>(inputStreamResource, headers, HttpStatus.OK);}@RequestMapping(value = "/offerById", method = RequestMethod.GET)public String offerById(HttpServletRequest request, int offerId, Model model) throws ProfitMandiBusinessException {Offer offer = offerRepository.selectById(offerId);model.addAttribute("offer", offer);return "offer-edit";}@RequestMapping(value = "/published-offers", method = RequestMethod.GET)public String publishedOffersOnMonthBefore(HttpServletRequest request, @RequestParam int yearMonth, @RequestParam(required = false, defaultValue = "") String brandFilter, Model model)throws ProfitMandiBusinessException {LOGGER.info("publishedOffersCalled");LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);int fofoId = loginDetails.getFofoId();List<CreateOfferRequest> createOffers = offerService.getPublishedOffers(fofoId,YearMonth.from(LocalDate.now()).minusMonths(yearMonth));List<CreateOfferRequest> publishedOffers = null;if (!brandFilter.isEmpty()) {publishedOffers = createOffers.stream().filter(createOffer -> createOffer.getTargetSlabs().stream().map(x -> x.getItemCriteriaPayouts()).flatMap(List::stream).map(ItemCriteriaPayout::getItemCriteria).map(ItemCriteria::getBrands).flatMap(List::stream).anyMatch(brand -> brand.equals(brandFilter))).collect(Collectors.toList());} else {publishedOffers = createOffers.stream().filter(createOffer -> createOffer.getTargetSlabs().stream().map(x -> x.getItemCriteriaPayouts()).flatMap(List::stream).map(ItemCriteriaPayout::getItemCriteria).map(ItemCriteria::getCatalogIds).flatMap(List::stream).noneMatch(catalogId -> catalogRepository.selectCatalogById(catalogId).getBrand().equals("Live Demo"))).collect(Collectors.toList());}model.addAttribute("publishedOffers", publishedOffers);return "published-offers";}@PostMapping(value = "/offers/upload")public String uploadOffers(HttpServletRequest request, @RequestPart("file") MultipartFile targetFile, Model model)throws Exception {offerService.createOffers(targetFile.getInputStream());model.addAttribute("response1", true);return "response";}@RequestMapping(value = "/getOfferMargins", method = RequestMethod.GET)public String getOfferMargins(HttpServletRequest request,@RequestParam(name = "offerId", defaultValue = "0") int offerId, Model model) throws Exception {LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);boolean isAdmin = roleManager.isAdmin(loginDetails.getRoleIds());CreateOfferRequest createOfferRequest = offerService.getOffer(isAdmin ? 0 : loginDetails.getFofoId(), offerId);model.addAttribute("offer", createOfferRequest);model.addAttribute("isAdmin", isAdmin);return "offer_margin_detail_partner";}@RequestMapping(value = "/todayOffer")public String todayOffer(HttpServletRequest request, Model model, @RequestParam(name = "fofoId", defaultValue = "0") int fofoId) throws ProfitMandiBusinessException {LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);if (fofoId == 0) {fofoId = loginDetails.getFofoId();}List<BrandCatalog> allBrands = brandsService.getBrandsToDisplay(3);// 1. IDs to exclude entirelyList<Integer> excludedIds = Arrays.asList(132, 133, 28, 17, 125);// 2. Brands that must come first (in this specific order)List<String> priorityOrder = Arrays.asList("Samsung", "Oppo", "Vivo", "Xiaomi", "Realme");List<BrandCatalog> sortedBrands = allBrands.stream().filter(brand -> !excludedIds.contains(brand.getId())) // Remove excluded.sorted((b1, b2) -> {// Get the index of the brand name in our priority listint index1 = priorityOrder.indexOf(b1.getName());int index2 = priorityOrder.indexOf(b2.getName());// If brand is NOT in priority list, give it a high index (move to bottom)int p1 = (index1 != -1) ? index1 : Integer.MAX_VALUE;int p2 = (index2 != -1) ? index2 : Integer.MAX_VALUE;if (p1 != p2) {return Integer.compare(p1, p2); // Sort by priority first}// If both are "Others", sort them alphabeticallyreturn b1.getName().compareToIgnoreCase(b2.getName());}).collect(Collectors.toList());model.addAttribute("brands", sortedBrands);model.addAttribute("fofoId", fofoId);model.addAttribute("date", FormattingUtils.format(LocalDateTime.now()));return "today-offer";}@RequestMapping(value = "/todayOfferList")public String todayOfferList(HttpServletRequest request, Model model, @RequestParam String brand, @RequestParam(defaultValue = "0", required = false) int fofoId) throws ProfitMandiBusinessException {LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);if (fofoId == 0) {fofoId = loginDetails.getFofoId();}List<String> brands = brandsService.getBrandsToDisplay(3).stream().map(x -> x.getName()).collect(Collectors.toList());List<TodayOfferModel> todayOfferModels = todayOfferService.findAllTodayOffer(brand, fofoId);List<TodayOfferModel> groupedOffers = todayOfferService.groupSameOffers(todayOfferModels);model.addAttribute("brands", brands);model.addAttribute("fofoId", fofoId);model.addAttribute("todayOfferModels", todayOfferModels);model.addAttribute("groupedOffers", groupedOffers);return "today-offer-list";}@RequestMapping(value = "/todayFofoOffer")public String todayFofoOffer(HttpServletRequest request, Model model, @RequestParam(name = "fofoId", defaultValue = "0") int fofoId) throws ProfitMandiBusinessException {LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);if (fofoId == 0) {fofoId = loginDetails.getFofoId();}List<BrandCatalog> allBrands = brandsService.getBrandsToDisplay(3);// 1. IDs to exclude entirelyList<Integer> excludedIds = Arrays.asList(132, 133, 28, 17, 125);// 2. Brands that must come first (in this specific order)List<String> priorityOrder = Arrays.asList("Samsung", "Oppo", "Vivo", "Xiaomi", "Realme");List<BrandCatalog> sortedBrands = allBrands.stream().filter(brand -> !excludedIds.contains(brand.getId())) // Remove excluded.sorted((b1, b2) -> {// Get the index of the brand name in our priority listint index1 = priorityOrder.indexOf(b1.getName());int index2 = priorityOrder.indexOf(b2.getName());// If brand is NOT in priority list, give it a high index (move to bottom)int p1 = (index1 != -1) ? index1 : Integer.MAX_VALUE;int p2 = (index2 != -1) ? index2 : Integer.MAX_VALUE;if (p1 != p2) {return Integer.compare(p1, p2); // Sort by priority first}// If both are "Others", sort them alphabeticallyreturn b1.getName().compareToIgnoreCase(b2.getName());}).collect(Collectors.toList());model.addAttribute("brands", sortedBrands);model.addAttribute("fofoId", fofoId);model.addAttribute("date", FormattingUtils.format(LocalDateTime.now()));return "today-fofo-offer";}// ===== Offer Partner & Target Management (Admin Only) =====@RequestMapping(value = "/offer/partners", method = RequestMethod.GET)public String getOfferPartners(HttpServletRequest request, @RequestParam int offerId, Model model) throws Exception {LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);if (!roleManager.isAdmin(loginDetails.getRoleIds())) {throw new ProfitMandiBusinessException("Unauthorized", "Unauthorized", "");}CreateOfferRequest offer = offerService.getOffer(0, offerId);Map<Integer, CustomRetailer> customRetailerMap = retailerService.getAllFofoRetailers();// Partners are stored in partner_criteria JSON, not in offer_partners tableList<Integer> partnerFofoIds = offer.getPartnerCriteria() != null? offer.getPartnerCriteria().getFofoIds() : new ArrayList<>();if (partnerFofoIds == null) partnerFofoIds = new ArrayList<>();List<Integer> allFofoIds = fofoStoreRepository.selectActiveStores().stream().map(x -> x.getId()).collect(Collectors.toList());Map<Integer, CustomRetailer> allRetailersMap = allFofoIds.stream().map(id -> customRetailerMap.get(id)).filter(x -> x != null).collect(Collectors.toMap(CustomRetailer::getPartnerId, x -> x));model.addAttribute("offer", offer);model.addAttribute("offerId", offerId);model.addAttribute("partnerFofoIds", partnerFofoIds);model.addAttribute("customRetailerMap", customRetailerMap);model.addAttribute("allRetailersMap", allRetailersMap);return "offer_partners";}@RequestMapping(value = "/offer/removePartners", method = RequestMethod.POST)public ResponseEntity<?> removePartnersFromOffer(HttpServletRequest request,@RequestParam int offerId, @RequestParam List<Integer> fofoIds,@RequestParam(required = false, defaultValue = "false") boolean createNewOffer,@RequestParam(required = false) List<Integer> targets) throws Exception {LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);if (!roleManager.isAdmin(loginDetails.getRoleIds())) {throw new ProfitMandiBusinessException("Unauthorized", "Unauthorized", "");}Offer offer = offerRepository.selectById(offerId);YearMonth ym = YearMonth.from(offer.getStartDate());offerService.removePartnersFromOffer(offerId, fofoIds);Integer newOfferId = null;String message;if (createNewOffer && targets != null && !targets.isEmpty()) {newOfferId = offerService.cloneOfferForPartners(offerId, fofoIds, targets);message = "Partner(s) removed from Offer #" + offerId + ". New Offer #" + newOfferId + " created (Unpublished).";} else {message = "Partner(s) removed from Offer #" + offerId + ".";}// Evict partner-to-offer mapping (removed partners no longer see this offer)oneDayCacheManager.getCache("allOffers").evict(ym);redisCacheManager.getCache("catalog.published_yearmonth").evict(ym);oneDayCacheManager.getCache("publishedOffersWithAchievement").clear();redisShortCacheManager.getCache("todayOffers").clear();thirtyMinsTimeOutCacheManager.getCache("partnerOffers").clear();Map<String, Object> response = new HashMap<>();response.put("message", message);response.put("newOfferId", newOfferId);response.put("yearMonth", ym.toString());return responseSender.ok(response);}@RequestMapping(value = "/offer/addPartners", method = RequestMethod.POST)public ResponseEntity<?> addPartnersToOffer(HttpServletRequest request,@RequestParam int offerId, @RequestParam List<Integer> fofoIds) throws Exception {LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);if (!roleManager.isAdmin(loginDetails.getRoleIds())) {throw new ProfitMandiBusinessException("Unauthorized", "Unauthorized", "");}offerService.addPartnersToOffer(offerId, fofoIds);Offer offer = offerRepository.selectById(offerId);YearMonth ym = YearMonth.from(offer.getStartDate());// Evict partner-to-offer mapping (added partners now see this offer)oneDayCacheManager.getCache("allOffers").evict(ym);redisCacheManager.getCache("catalog.published_yearmonth").evict(ym);oneDayCacheManager.getCache("publishedOffersWithAchievement").clear();redisShortCacheManager.getCache("todayOffers").clear();thirtyMinsTimeOutCacheManager.getCache("partnerOffers").clear();Map<String, Object> response = new HashMap<>();response.put("message", "Partner(s) added to Offer #" + offerId + ".");response.put("yearMonth", ym.toString());return responseSender.ok(response);}@RequestMapping(value = "/offer/updateTargets", method = RequestMethod.POST)public ResponseEntity<?> updateOfferTargets(HttpServletRequest request,@RequestParam int offerId, @RequestParam List<Integer> targets) throws Exception {LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);if (!roleManager.isAdmin(loginDetails.getRoleIds())) {throw new ProfitMandiBusinessException("Unauthorized", "Unauthorized", "");}offerService.updateOfferTargets(offerId, targets);evictOfferCaches(offerId);return responseSender.ok("Targets updated for Offer #" + offerId);}@RequestMapping(value = "/offer/updateSlabs", method = RequestMethod.POST, consumes = "application/json")public ResponseEntity<?> updateOfferSlabs(HttpServletRequest request,@RequestBody com.spice.profitmandi.dao.model.UpdateOfferSlabsRequest updateRequest) throws Exception {LoginDetails loginDetails = cookiesProcessor.getCookiesObject(request);if (!roleManager.isAdmin(loginDetails.getRoleIds())) {throw new ProfitMandiBusinessException("Unauthorized", "Unauthorized", "");}offerService.updateOfferSlabs(updateRequest);evictOfferCaches(updateRequest.getOfferId());return responseSender.ok("Slabs updated for Offer #" + updateRequest.getOfferId());}private void evictOfferCaches(int offerId) {Offer offer = offerRepository.selectById(offerId);YearMonth ym = YearMonth.from(offer.getStartDate());oneDayCacheManager.getCache("allOffers").evict(ym);redisCacheManager.getCache("catalog.published_yearmonth").evict(ym);oneDayCacheManager.getCache("publishedOffersWithAchievement").clear();redisShortCacheManager.getCache("todayOffers").clear();thirtyMinsTimeOutCacheManager.getCache("partnerOffers").clear();}}