Subversion Repositories SmartDukaan

Rev

Rev 36975 | Rev 36979 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
34306 ranu 1
package com.smartdukaan.cron.scheduled;
2
 
3
import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
34619 ranu 4
import com.spice.profitmandi.common.model.BrandStockPrice;
34606 ranu 5
import com.spice.profitmandi.common.model.CustomRetailer;
36973 ranu 6
import com.spice.profitmandi.common.model.IdAmountModel;
34321 ranu 7
import com.spice.profitmandi.common.model.ProfitMandiConstants;
34619 ranu 8
import com.spice.profitmandi.common.util.FormattingUtils;
9
import com.spice.profitmandi.common.util.Utils;
34450 ranu 10
import com.spice.profitmandi.dao.cart.SmartCartService;
34321 ranu 11
import com.spice.profitmandi.dao.entity.auth.AuthUser;
34758 ranu 12
import com.spice.profitmandi.dao.entity.catalog.TagListing;
34606 ranu 13
import com.spice.profitmandi.dao.entity.fofo.*;
14
import com.spice.profitmandi.dao.entity.logistics.AST;
15
import com.spice.profitmandi.dao.entity.logistics.ASTRepository;
34619 ranu 16
import com.spice.profitmandi.dao.entity.transaction.*;
34321 ranu 17
import com.spice.profitmandi.dao.entity.user.User;
18
import com.spice.profitmandi.dao.enumuration.cs.EscalationType;
34619 ranu 19
import com.spice.profitmandi.dao.enumuration.transaction.LoanReferenceType;
34641 ranu 20
import com.spice.profitmandi.dao.model.*;
34606 ranu 21
import com.spice.profitmandi.dao.repository.auth.AuthRepository;
34758 ranu 22
import com.spice.profitmandi.dao.repository.catalog.TagListingRepository;
34321 ranu 23
import com.spice.profitmandi.dao.repository.cs.CsService;
24
import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;
34606 ranu 25
import com.spice.profitmandi.dao.repository.fofo.*;
26
import com.spice.profitmandi.dao.repository.inventory.StateRepository;
34619 ranu 27
import com.spice.profitmandi.dao.repository.transaction.*;
34321 ranu 28
import com.spice.profitmandi.dao.repository.user.UserRepository;
35358 ranu 29
import com.spice.profitmandi.dao.service.solr.FofoSolr;
34655 ranu 30
import com.spice.profitmandi.service.PartnerStatsService;
34641 ranu 31
import com.spice.profitmandi.service.RbmTargetService;
34758 ranu 32
import com.spice.profitmandi.service.inventory.*;
34308 ranu 33
import com.spice.profitmandi.service.transaction.SDCreditService;
34606 ranu 34
import com.spice.profitmandi.service.user.RetailerService;
34619 ranu 35
import com.spice.profitmandi.service.wallet.WalletService;
36
import in.shop2020.model.v1.order.WalletReferenceType;
37
import org.apache.commons.io.output.ByteArrayOutputStream;
34306 ranu 38
import org.apache.logging.log4j.LogManager;
39
import org.apache.logging.log4j.Logger;
34715 ranu 40
import org.apache.poi.common.usermodel.HyperlinkType;
34641 ranu 41
import org.apache.poi.ss.usermodel.*;
34619 ranu 42
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
34306 ranu 43
import org.springframework.beans.factory.annotation.Autowired;
34619 ranu 44
import org.springframework.beans.factory.annotation.Qualifier;
45
import org.springframework.core.io.ByteArrayResource;
34321 ranu 46
import org.springframework.mail.javamail.JavaMailSender;
47
import org.springframework.mail.javamail.MimeMessageHelper;
34306 ranu 48
import org.springframework.stereotype.Component;
49
import org.springframework.transaction.annotation.Transactional;
50
 
34321 ranu 51
import javax.mail.MessagingException;
52
import javax.mail.internet.InternetAddress;
53
import javax.mail.internet.MimeMessage;
34619 ranu 54
import java.io.*;
55
import java.math.BigDecimal;
34641 ranu 56
import java.time.*;
34749 ranu 57
import java.time.format.DateTimeFormatter;
34306 ranu 58
import java.time.temporal.ChronoUnit;
36113 ranu 59
import java.time.temporal.TemporalAdjusters;
60
import java.time.DayOfWeek;
34306 ranu 61
import java.util.*;
34321 ranu 62
import java.util.stream.Collectors;
34306 ranu 63
 
34939 ranu 64
import static java.util.stream.Collectors.toList;
65
 
34306 ranu 66
@Component
67
@Transactional(rollbackFor = {Throwable.class, ProfitMandiBusinessException.class})
68
public class ScheduledTasksTest {
69
 
70
    private static final Logger LOGGER = LogManager.getLogger(ScheduledTasksTest.class);
71
 
72
    @Autowired
73
    TransactionRepository transactionRepository;
74
 
75
    @Autowired
36976 ranu 76
    @Qualifier(value = "gmailRelaySender")
34619 ranu 77
    private JavaMailSender googleMailSender;
78
 
79
    @Autowired
34306 ranu 80
    LoanRepository loanRepository;
81
 
34308 ranu 82
    @Autowired
83
    SDCreditService sdCreditService;
84
 
34321 ranu 85
    @Autowired
34939 ranu 86
    SmartCartSuggestionRepository smartCartSuggestionRepository;
87
 
88
    @Autowired
34321 ranu 89
    UserRepository userRepository;
90
 
91
    @Autowired
92
    CsService csService;
93
 
94
    @Autowired
95
    RbmRatingRepository rbmRatingRepository;
96
 
97
    @Autowired
36402 amit 98
    private JavaMailSender gmailRelaySender;
34321 ranu 99
 
100
    @Autowired
101
    SalesRatingRepository salesRatingRepository;
102
 
103
    @Autowired
104
    FofoStoreRepository fofoStoreRepository;
105
 
34450 ranu 106
    @Autowired
107
    SmartCartService smartCartService;
108
 
34606 ranu 109
    @Autowired
110
    RetailerService retailerService;
111
 
112
    @Autowired
113
    ASTRepository astRepository;
114
 
115
    @Autowired
116
    AuthRepository authRepository;
117
 
118
    @Autowired
119
    StateRepository stateRepository;
120
 
121
    @Autowired
122
    MonthlyTargetRepository monthlyTargetRepository;
123
 
124
    @Autowired
125
    PartnerTypeChangeService partnerTypeChangeService;
126
 
127
    @Autowired
128
    ReturnOrderInfoRepository returnOrderInfoRepository;
129
 
130
    @Autowired
131
    OrderRepository orderRepository;
132
 
34619 ranu 133
    @Autowired
134
    FofoOrderItemRepository fofoOrderItemRepository;
135
 
136
    @Autowired
137
    InventoryService inventoryService;
138
 
139
    @Autowired
140
    UserWalletRepository userWalletRepository;
141
 
142
    @Autowired
143
    LoanStatementRepository loanStatementRepository;
144
 
145
    @Autowired
34641 ranu 146
    ActivatedImeiRepository activatedImeiRepository;
147
 
148
    @Autowired
149
    PartnerDailyInvestmentRepository partnerDailyInvestmentRepository;
150
 
151
    @Autowired
34758 ranu 152
    SaholicInventoryService saholicInventoryService;
153
 
154
    @Autowired
34619 ranu 155
    WalletService walletService;
156
 
34641 ranu 157
    @Autowired
158
    RbmTargetService rbmTargetService;
159
 
34655 ranu 160
    @Autowired
161
    PartnerStatsService partnerStatsService;
162
 
34715 ranu 163
    @Autowired
34758 ranu 164
    AgeingService ageingService;
165
 
166
    @Autowired
167
    TagListingRepository tagListingRepository;
168
 
169
    @Autowired
34715 ranu 170
    UserWalletHistoryRepository userWalletHistoryRepository;
171
 
35358 ranu 172
    @Autowired
173
    FofoSolr fofoSolr;
174
 
34321 ranu 175
    public void test() throws Exception {
35358 ranu 176
 
177
        fofoSolr.populateTagItems();
178
 
34366 ranu 179
        System.out.println("test end");
34306 ranu 180
 
181
    }
182
 
34648 ranu 183
    public void generateBiReport() throws Exception {
34912 ranu 184
        this.generateBiReportExcel();
34648 ranu 185
    }
186
 
34308 ranu 187
    public void createLoanForBillingByTransactionIdAndInvoiceNumber(int transactionId, double invoiceAmount, String invoiceNumber) throws Exception {
188
        sdCreditService.createLoanForBilling(transactionId, invoiceAmount, invoiceNumber);
34306 ranu 189
 
34308 ranu 190
    }
34306 ranu 191
 
34619 ranu 192
    public void loanSettle() throws Exception {
193
        List<Integer> refrences = Arrays.asList(25807,36003,38938,39506,42219,45084);
194
        for(Integer ref : refrences){
195
            List<LoanStatement> loanStatements = loanStatementRepository.selectByLoanId(ref);
196
            double amountSum = loanStatements.stream().map(LoanStatement::getAmount).mapToDouble(BigDecimal::doubleValue).sum();
197
            if(amountSum > 0){
198
                walletService.addAmountToWallet(loanStatements.get(0).getFofoId(),ref, WalletReferenceType.CREDIT_LIMIT,"Amount reversal against credit limit deduction",(float) amountSum,LocalDateTime.now());
34308 ranu 199
 
34619 ranu 200
//                Loan statement entry
201
                    BigDecimal adjustAmount = BigDecimal.valueOf(amountSum).negate(); // or multiply by -1
202
                    LoanStatement loanStatement = new LoanStatement();
203
                    loanStatement.setAmount(adjustAmount);
204
                    loanStatement.setFofoId(loanStatements.get(0).getFofoId());
205
                    loanStatement.setLoanReferenceType(LoanReferenceType.PRINCIPAL);
206
                    loanStatement.setCreatedAt(LocalDateTime.now());
207
                    loanStatement.setDescription("Amount reversal due to access debit against limit");
208
                    loanStatement.setLoanId(ref);
209
                    loanStatement.setBusinessDate(LocalDateTime.now());
210
                    loanStatementRepository.persist(loanStatement);
211
 
212
                    Loan loan = loanRepository.selectByLoanId(ref);
213
                    loan.setPendingAmount(BigDecimal.valueOf(0));
214
                    loan.setSettledOn(LocalDateTime.now());
215
                }
216
 
217
 
218
        }
219
    }
220
 
221
 
222
 
34321 ranu 223
    private void sendMailHtmlFormat(String email[], String body, String cc[], String bcc[], String subject)
224
            throws MessagingException, ProfitMandiBusinessException, IOException {
36402 amit 225
        MimeMessage message = gmailRelaySender.createMimeMessage();
34321 ranu 226
        MimeMessageHelper helper = new MimeMessageHelper(message);
227
        helper.setSubject(subject);
228
        helper.setText(body, true);
229
        helper.setTo(email);
230
        if (cc != null) {
231
            helper.setCc(cc);
232
        }
233
        if (bcc != null) {
234
            helper.setBcc(bcc);
34308 ranu 235
 
34321 ranu 236
        }
237
 
238
        InternetAddress senderAddress = new InternetAddress("noreply@smartdukaan.com", "Smart Dukaan");
239
        helper.setFrom(senderAddress);
36402 amit 240
        gmailRelaySender.send(message);
34321 ranu 241
    }
242
 
34307 ranu 243
    public Map<Integer,Integer> findLoanTransactionMapingAccordingLoan(List<Integer> loanIds) throws ProfitMandiBusinessException {
34306 ranu 244
 
245
        Map<Integer, Integer> transactionLoanMap = new HashMap<>();
246
 
247
        for(int loanId : loanIds){
248
            Transaction transaction = null;
249
            Loan loan = loanRepository.selectByLoanId(loanId);
250
            List<Transaction> transactions = transactionRepository.selectByRetailerId(loan.getFofoId());
251
 
252
            LocalDateTime nearestDateTime = transactions.stream().map(x -> x.getCreateTimestamp())
253
                    .min(Comparator.comparingLong(x -> Math.abs(ChronoUnit.MILLIS.between(x, loan.getCreatedOn()))))
254
                    .orElse(null);
255
 
256
            if (nearestDateTime != null && loan.getCreatedOn().plusMinutes(2).isAfter(nearestDateTime) &&
257
                    loan.getCreatedOn().minusMinutes(1).isBefore(nearestDateTime)) {
258
                // Here transaction is still null
259
                transaction = transactions.stream()
260
                        .filter(x -> x.getCreateTimestamp().equals(nearestDateTime))
261
                        .findFirst().get();
262
                transactionLoanMap.put(transaction.getId(), loanId);
263
            }
264
 
265
        }
266
        LOGGER.info("transactionLoanMap {}",transactionLoanMap);
267
        return transactionLoanMap;
268
    }
34321 ranu 269
 
270
 
271
 
272
    public void sendRbmFeedbackSummaryEmail() throws MessagingException, ProfitMandiBusinessException, IOException {
36113 ranu 273
        // Weekly date range: Previous Monday to Sunday
274
        LocalDate today = LocalDate.now();
275
        LocalDate previousMonday = today.with(TemporalAdjusters.previous(DayOfWeek.MONDAY));
276
        LocalDate previousSunday = previousMonday.plusDays(6);
277
        LocalDateTime startOfWeek = previousMonday.atStartOfDay();
278
        LocalDateTime endOfWeek = previousSunday.atTime(23, 59, 59);
279
 
34323 ranu 280
        String[] bcc = {"tarun.verma@smartdukaan.com"};
36113 ranu 281
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy");
34321 ranu 282
 
283
        // Get all RBM users
284
        List<AuthUser> authUsers = csService.getAuthUserIds(
285
                ProfitMandiConstants.TICKET_CATEGORY_RBM,
286
                Arrays.asList(EscalationType.L1)
287
        );
288
 
289
        if (authUsers.isEmpty()) {
290
            LOGGER.info("No RBMs found.");
291
            return;
292
        }
293
 
294
        List<Integer> rbmIds = authUsers.stream().map(AuthUser::getId).collect(Collectors.toList());
295
 
36113 ranu 296
        // Fetch ratings for all RBMs for the week
297
        List<RbmRating> feedbackList = rbmRatingRepository.selectByRbmIdsAndDateRange(rbmIds, startOfWeek, endOfWeek);
34321 ranu 298
 
299
        if (feedbackList.isEmpty()) {
36113 ranu 300
            LOGGER.info("No feedback entries found for RBMs for the week.");
34321 ranu 301
            return;
302
        }
303
 
304
        // Sort feedback by createTimeStamp DESC
305
        feedbackList.sort((a, b) -> b.getCreateTimeStamp().compareTo(a.getCreateTimeStamp()));
306
 
307
        // Fetch and map FOFO (partner) names
308
        Map<Integer, String> fofoNameMap = new HashMap<>();
309
        for (RbmRating rating : feedbackList) {
310
            int fofoId = rating.getFofoId();
311
            if (!fofoNameMap.containsKey(fofoId)) {
312
                User fofoUser = userRepository.selectById(fofoId);
313
                FofoStore fofoStore = fofoStoreRepository.selectByRetailerId(fofoId);
314
 
315
                String partnerName = fofoUser != null ? fofoUser.getName() : "Unknown Partner";
316
                String storeCode = fofoStore != null ? fofoStore.getCode() : "Unknown Code";
317
 
318
                String displayName = partnerName + " (" + storeCode + ")";
319
                fofoNameMap.put(fofoId, displayName);
320
            }
321
        }
322
 
323
        // Map RBM ID to name for quick lookup
324
        Map<Integer, String> rbmNameMap = authUsers.stream()
325
                .collect(Collectors.toMap(AuthUser::getId, AuthUser::getFullName));
326
 
36113 ranu 327
        // Calculate RBM statistics: average rating and unique partner count
328
        Map<Integer, List<RbmRating>> feedbackByRbm = feedbackList.stream()
329
                .collect(Collectors.groupingBy(RbmRating::getRbmId));
330
 
331
        List<RbmWeeklyStats> rbmStatsList = new ArrayList<>();
332
        for (Map.Entry<Integer, List<RbmRating>> entry : feedbackByRbm.entrySet()) {
333
            int rbmId = entry.getKey();
334
            List<RbmRating> ratings = entry.getValue();
335
 
336
            double avgRating = ratings.stream()
337
                    .mapToInt(RbmRating::getRating)
338
                    .average()
339
                    .orElse(0.0);
340
 
341
            long uniquePartnerCount = ratings.stream()
342
                    .map(RbmRating::getFofoId)
343
                    .distinct()
344
                    .count();
345
 
346
            String rbmName = rbmNameMap.getOrDefault(rbmId, "Unknown RBM");
347
            rbmStatsList.add(new RbmWeeklyStats(rbmId, rbmName, avgRating, (int) uniquePartnerCount));
348
        }
349
 
350
        // Find max partner count for normalization
351
        int maxPartnerCount = rbmStatsList.stream()
352
                .mapToInt(RbmWeeklyStats::getPartnerCount)
353
                .max()
354
                .orElse(1);
355
 
356
        // Calculate combined score: 65% partner count + 35% rating
357
        // Score = (0.65 × partnerCount/maxPartnerCount) + (0.35 × avgRating/5)
358
        for (RbmWeeklyStats stats : rbmStatsList) {
359
            double partnerScore = (double) stats.getPartnerCount() / maxPartnerCount;
360
            double ratingScore = stats.getAvgRating() / 5.0;
361
            double combinedScore = (0.65 * partnerScore) + (0.35 * ratingScore);
362
            stats.setCombinedScore(combinedScore);
363
        }
364
 
365
        // Sort by combined score DESC and assign rank
366
        rbmStatsList.sort((a, b) -> Double.compare(b.getCombinedScore(), a.getCombinedScore()));
367
        for (int i = 0; i < rbmStatsList.size(); i++) {
368
            rbmStatsList.get(i).setRank(i + 1);
369
        }
370
 
371
        // Create map for quick lookup of stats by RBM ID
372
        Map<Integer, RbmWeeklyStats> rbmStatsMap = rbmStatsList.stream()
373
                .collect(Collectors.toMap(RbmWeeklyStats::getRbmId, s -> s));
374
 
34321 ranu 375
        // Generate HTML content
376
        StringBuilder emailContent = new StringBuilder();
377
        emailContent.append("<html><body>");
378
        emailContent.append("<p>Dear Team,</p>");
36113 ranu 379
        emailContent.append("<p>Here is the <b>Weekly RBM Rating and Feedback Summary</b> for the week: <b>")
380
                .append(previousMonday.format(dateFormatter))
381
                .append(" to ")
382
                .append(previousSunday.format(dateFormatter))
383
                .append("</b></p>");
34321 ranu 384
 
36113 ranu 385
        // RBM Ranking Summary Table
386
        emailContent.append("<h3>RBM Weekly Rankings</h3>");
387
        emailContent.append("<p style='font-size: 12px; color: #666;'>Rank = 65% Partner Count + 35% Avg Rating</p>");
388
        emailContent.append("<table border='1' cellspacing='0' cellpadding='5' style='border-collapse: collapse;'>");
389
        emailContent.append("<tr style='background-color: #4CAF50; color: white;'>")
390
                .append("<th>Rank</th>")
34321 ranu 391
                .append("<th>RBM Name</th>")
36113 ranu 392
                .append("<th>Partner Count</th>")
393
                .append("<th>Avg Rating</th>")
394
                .append("<th>Score</th>")
395
                .append("</tr>");
396
 
397
        // Already sorted by rank (combined score DESC)
398
        for (RbmWeeklyStats stats : rbmStatsList) {
399
            emailContent.append("<tr>")
400
                    .append("<td style='text-align: center; font-weight: bold;'>").append(stats.getRank()).append("</td>")
401
                    .append("<td>").append(stats.getRbmName()).append("</td>")
402
                    .append("<td style='text-align: center;'>").append(stats.getPartnerCount()).append("</td>")
403
                    .append("<td style='text-align: center;'>").append(String.format("%.2f", stats.getAvgRating())).append("</td>")
404
                    .append("<td style='text-align: center;'>").append(String.format("%.2f", stats.getCombinedScore())).append("</td>")
405
                    .append("</tr>");
406
        }
407
        emailContent.append("</table>");
408
 
409
        // Detailed Feedback Table
410
        emailContent.append("<br><h3>Detailed Feedback</h3>");
411
        emailContent.append("<table border='1' cellspacing='0' cellpadding='5' style='border-collapse: collapse;'>");
412
        emailContent.append("<tr style='background-color: #2196F3; color: white;'>")
413
                .append("<th>RBM Name</th>")
414
                .append("<th>Rank</th>")
34321 ranu 415
                .append("<th>Partner Name</th>")
416
                .append("<th>Rating</th>")
417
                .append("<th>Comment</th>")
418
                .append("<th>Date</th>")
419
                .append("</tr>");
420
 
421
        for (RbmRating rating : feedbackList) {
36113 ranu 422
            int rbmId = rating.getRbmId();
423
            String rbmName = rbmNameMap.getOrDefault(rbmId, "Unknown RBM");
34321 ranu 424
            String partnerName = fofoNameMap.getOrDefault(rating.getFofoId(), "Unknown Partner");
36113 ranu 425
            RbmWeeklyStats stats = rbmStatsMap.get(rbmId);
426
 
34321 ranu 427
            emailContent.append("<tr>")
428
                    .append("<td>").append(rbmName).append("</td>")
36113 ranu 429
                    .append("<td style='text-align: center; font-weight: bold;'>").append(stats != null ? stats.getRank() : "-").append("</td>")
34321 ranu 430
                    .append("<td>").append(partnerName).append("</td>")
36113 ranu 431
                    .append("<td style='text-align: center;'>").append(rating.getRating()).append("</td>")
34321 ranu 432
                    .append("<td>").append(rating.getComment() != null ? rating.getComment() : "-").append("</td>")
433
                    .append("<td>").append(rating.getCreateTimeStamp().toLocalDate()).append("</td>")
434
                    .append("</tr>");
435
        }
436
 
437
        emailContent.append("</table>");
438
        emailContent.append("<br><p>Regards,<br>Smart Dukaan Team</p>");
439
        emailContent.append("</body></html>");
440
 
36113 ranu 441
        String subject = "Weekly RBM Feedback Summary - " + previousMonday.format(dateFormatter) + " to " + previousSunday.format(dateFormatter);
34321 ranu 442
 
443
        List<String> sendTo = new ArrayList<>();
36113 ranu 444
        sendTo.add("sm@smartdukaan.com");
445
        sendTo.add("chiranjib.sarkar@smartdukaan.com");
446
        sendTo.add("kamini.sharma@smartdukaan.com");
34321 ranu 447
 
448
        String[] emailRecipients = sendTo.toArray(new String[0]);
449
 
450
        this.sendMailHtmlFormat(emailRecipients, emailContent.toString(), null, bcc, subject);
451
 
36113 ranu 452
        LOGGER.info("Weekly RBM feedback summary email sent for week: {} to {}", previousMonday, previousSunday);
34321 ranu 453
    }
454
 
36113 ranu 455
    // Inner class to hold RBM weekly statistics
456
    private static class RbmWeeklyStats {
457
        private final int rbmId;
458
        private final String rbmName;
459
        private final double avgRating;
460
        private final int partnerCount;
461
        private double combinedScore;
462
        private int rank;
34321 ranu 463
 
36113 ranu 464
        public RbmWeeklyStats(int rbmId, String rbmName, double avgRating, int partnerCount) {
465
            this.rbmId = rbmId;
466
            this.rbmName = rbmName;
467
            this.avgRating = avgRating;
468
            this.partnerCount = partnerCount;
469
        }
470
 
471
        public int getRbmId() { return rbmId; }
472
        public String getRbmName() { return rbmName; }
473
        public double getAvgRating() { return avgRating; }
474
        public int getPartnerCount() { return partnerCount; }
475
        public double getCombinedScore() { return combinedScore; }
476
        public void setCombinedScore(double combinedScore) { this.combinedScore = combinedScore; }
477
        public int getRank() { return rank; }
478
        public void setRank(int rank) { this.rank = rank; }
479
    }
480
 
481
 
34321 ranu 482
    public void sendSalesFeedbackSummaryEmail() throws MessagingException, ProfitMandiBusinessException, IOException {
36113 ranu 483
        // Weekly date range: Previous Monday to Sunday
484
        LocalDate today = LocalDate.now();
485
        LocalDate previousMonday = today.with(TemporalAdjusters.previous(DayOfWeek.MONDAY));
486
        LocalDate previousSunday = previousMonday.plusDays(6);
487
        LocalDateTime startOfWeek = previousMonday.atStartOfDay();
488
        LocalDateTime endOfWeek = previousSunday.atTime(23, 59, 59);
489
 
34323 ranu 490
        String[] bcc = {"tarun.verma@smartdukaan.com"};
36113 ranu 491
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy");
34321 ranu 492
 
34903 ranu 493
        // Get all Sales users
34321 ranu 494
        List<AuthUser> authUsers = csService.getAuthUserIds(
495
                ProfitMandiConstants.TICKET_CATEGORY_SALES,
496
                Arrays.asList(EscalationType.L1)
497
        );
498
 
499
        if (authUsers.isEmpty()) {
500
            LOGGER.info("No sales person found.");
501
            return;
502
        }
503
 
504
        List<Integer> salesL1Ids = authUsers.stream().map(AuthUser::getId).collect(Collectors.toList());
505
 
36113 ranu 506
        // Fetch ratings for all Sales L1 for the week
507
        List<SalesRating> feedbackList = salesRatingRepository.selectBySalesL1IdsAndDateRange(salesL1Ids, startOfWeek, endOfWeek);
34321 ranu 508
 
509
        if (feedbackList.isEmpty()) {
36113 ranu 510
            LOGGER.info("No feedback entries found for Sales for the week.");
34321 ranu 511
            return;
512
        }
513
 
514
        // Sort feedback by createTimeStamp DESC
515
        feedbackList.sort((a, b) -> b.getCreateTimeStamp().compareTo(a.getCreateTimeStamp()));
516
 
517
        // Fetch and map FOFO (partner) names
518
        Map<Integer, String> fofoNameMap = new HashMap<>();
519
        for (SalesRating rating : feedbackList) {
520
            int fofoId = rating.getFofoId();
521
            if (!fofoNameMap.containsKey(fofoId)) {
522
                User fofoUser = userRepository.selectById(fofoId);
523
                FofoStore fofoStore = fofoStoreRepository.selectByRetailerId(fofoId);
524
 
525
                String partnerName = fofoUser != null ? fofoUser.getName() : "Unknown Partner";
526
                String storeCode = fofoStore != null ? fofoStore.getCode() : "Unknown Code";
527
 
528
                String displayName = partnerName + " (" + storeCode + ")";
529
                fofoNameMap.put(fofoId, displayName);
530
            }
531
        }
532
 
36113 ranu 533
        // Map Sales L1 ID to name for quick lookup
34321 ranu 534
        Map<Integer, String> salesL1NameMap = authUsers.stream()
535
                .collect(Collectors.toMap(AuthUser::getId, AuthUser::getFullName));
536
 
36113 ranu 537
        // Calculate Sales L1 statistics: average rating and unique partner count
538
        Map<Integer, List<SalesRating>> feedbackBySales = feedbackList.stream()
539
                .collect(Collectors.groupingBy(SalesRating::getSalesL1Id));
540
 
541
        List<SalesWeeklyStats> salesStatsList = new ArrayList<>();
542
        for (Map.Entry<Integer, List<SalesRating>> entry : feedbackBySales.entrySet()) {
543
            int salesL1Id = entry.getKey();
544
            List<SalesRating> ratings = entry.getValue();
545
 
546
            double avgRating = ratings.stream()
547
                    .mapToInt(SalesRating::getRating)
548
                    .average()
549
                    .orElse(0.0);
550
 
551
            long uniquePartnerCount = ratings.stream()
552
                    .map(SalesRating::getFofoId)
553
                    .distinct()
554
                    .count();
555
 
556
            String salesL1Name = salesL1NameMap.getOrDefault(salesL1Id, "Unknown Sales Person");
557
            salesStatsList.add(new SalesWeeklyStats(salesL1Id, salesL1Name, avgRating, (int) uniquePartnerCount));
558
        }
559
 
560
        // Find max partner count for normalization
561
        int maxPartnerCount = salesStatsList.stream()
562
                .mapToInt(SalesWeeklyStats::getPartnerCount)
563
                .max()
564
                .orElse(1);
565
 
566
        // Calculate combined score: 65% partner count + 35% rating
567
        // Score = (0.65 × partnerCount/maxPartnerCount) + (0.35 × avgRating/5)
568
        for (SalesWeeklyStats stats : salesStatsList) {
569
            double partnerScore = (double) stats.getPartnerCount() / maxPartnerCount;
570
            double ratingScore = stats.getAvgRating() / 5.0;
571
            double combinedScore = (0.65 * partnerScore) + (0.35 * ratingScore);
572
            stats.setCombinedScore(combinedScore);
573
        }
574
 
575
        // Sort by combined score DESC and assign rank
576
        salesStatsList.sort((a, b) -> Double.compare(b.getCombinedScore(), a.getCombinedScore()));
577
        for (int i = 0; i < salesStatsList.size(); i++) {
578
            salesStatsList.get(i).setRank(i + 1);
579
        }
580
 
581
        // Create map for quick lookup of stats by Sales L1 ID
582
        Map<Integer, SalesWeeklyStats> salesStatsMap = salesStatsList.stream()
583
                .collect(Collectors.toMap(SalesWeeklyStats::getSalesL1Id, s -> s));
584
 
34321 ranu 585
        // Generate HTML content
586
        StringBuilder emailContent = new StringBuilder();
587
        emailContent.append("<html><body>");
588
        emailContent.append("<p>Dear Team,</p>");
36113 ranu 589
        emailContent.append("<p>Here is the <b>Weekly Sales L1 Rating and Feedback Summary</b> for the week: <b>")
590
                .append(previousMonday.format(dateFormatter))
591
                .append(" to ")
592
                .append(previousSunday.format(dateFormatter))
593
                .append("</b></p>");
34321 ranu 594
 
36113 ranu 595
        // Sales L1 Ranking Summary Table
596
        emailContent.append("<h3>Sales L1 Weekly Rankings</h3>");
597
        emailContent.append("<p style='font-size: 12px; color: #666;'>Rank = 65% Partner Count + 35% Avg Rating</p>");
598
        emailContent.append("<table border='1' cellspacing='0' cellpadding='5' style='border-collapse: collapse;'>");
599
        emailContent.append("<tr style='background-color: #4CAF50; color: white;'>")
600
                .append("<th>Rank</th>")
34321 ranu 601
                .append("<th>Sales L1 Name</th>")
36113 ranu 602
                .append("<th>Partner Count</th>")
603
                .append("<th>Avg Rating</th>")
604
                .append("<th>Score</th>")
605
                .append("</tr>");
606
 
607
        // Already sorted by rank (combined score DESC)
608
        for (SalesWeeklyStats stats : salesStatsList) {
609
            emailContent.append("<tr>")
610
                    .append("<td style='text-align: center; font-weight: bold;'>").append(stats.getRank()).append("</td>")
611
                    .append("<td>").append(stats.getSalesL1Name()).append("</td>")
612
                    .append("<td style='text-align: center;'>").append(stats.getPartnerCount()).append("</td>")
613
                    .append("<td style='text-align: center;'>").append(String.format("%.2f", stats.getAvgRating())).append("</td>")
614
                    .append("<td style='text-align: center;'>").append(String.format("%.2f", stats.getCombinedScore())).append("</td>")
615
                    .append("</tr>");
616
        }
617
        emailContent.append("</table>");
618
 
619
        // Detailed Feedback Table
620
        emailContent.append("<br><h3>Detailed Feedback</h3>");
621
        emailContent.append("<table border='1' cellspacing='0' cellpadding='5' style='border-collapse: collapse;'>");
622
        emailContent.append("<tr style='background-color: #2196F3; color: white;'>")
623
                .append("<th>Sales L1 Name</th>")
624
                .append("<th>Rank</th>")
34321 ranu 625
                .append("<th>Partner Name</th>")
34411 tejus.loha 626
                .append("<th>Partner Category</th>")
34321 ranu 627
                .append("<th>Rating</th>")
628
                .append("<th>Comment</th>")
629
                .append("<th>Date</th>")
630
                .append("</tr>");
631
 
632
        for (SalesRating rating : feedbackList) {
36113 ranu 633
            int salesL1Id = rating.getSalesL1Id();
634
            String salesL1Name = salesL1NameMap.getOrDefault(salesL1Id, "Unknown Sales Person");
34321 ranu 635
            String partnerName = fofoNameMap.getOrDefault(rating.getFofoId(), "Unknown Partner");
36113 ranu 636
            SalesWeeklyStats stats = salesStatsMap.get(salesL1Id);
34411 tejus.loha 637
            PartnerType partnerType = partnerTypeChangeService.getTypeOnDate(rating.getFofoId(), LocalDate.now());
36113 ranu 638
 
34321 ranu 639
            emailContent.append("<tr>")
36113 ranu 640
                    .append("<td>").append(salesL1Name).append("</td>")
641
                    .append("<td style='text-align: center; font-weight: bold;'>").append(stats != null ? stats.getRank() : "-").append("</td>")
34321 ranu 642
                    .append("<td>").append(partnerName).append("</td>")
34411 tejus.loha 643
                    .append("<td>").append(partnerType).append("</td>")
36113 ranu 644
                    .append("<td style='text-align: center;'>").append(rating.getRating()).append("</td>")
34321 ranu 645
                    .append("<td>").append(rating.getComment() != null ? rating.getComment() : "-").append("</td>")
646
                    .append("<td>").append(rating.getCreateTimeStamp().toLocalDate()).append("</td>")
647
                    .append("</tr>");
648
        }
649
 
650
        emailContent.append("</table>");
36113 ranu 651
        emailContent.append("<br><p>Regards,<br>Smart Dukaan Team</p>");
34321 ranu 652
        emailContent.append("</body></html>");
653
 
36113 ranu 654
        String subject = "Weekly Sales L1 Feedback Summary - " + previousMonday.format(dateFormatter) + " to " + previousSunday.format(dateFormatter);
34321 ranu 655
 
656
        List<String> sendTo = new ArrayList<>();
36113 ranu 657
        sendTo.add("sm@smartdukaan.com");
658
        sendTo.add("kamini.sharma@smartdukaan.com");
34321 ranu 659
 
660
        String[] emailRecipients = sendTo.toArray(new String[0]);
661
 
662
        this.sendMailHtmlFormat(emailRecipients, emailContent.toString(), null, bcc, subject);
663
 
36113 ranu 664
        LOGGER.info("Weekly Sales L1 feedback summary email sent for week: {} to {}", previousMonday, previousSunday);
34321 ranu 665
    }
666
 
36113 ranu 667
    // Inner class to hold Sales L1 weekly statistics
668
    private static class SalesWeeklyStats {
669
        private final int salesL1Id;
670
        private final String salesL1Name;
671
        private final double avgRating;
672
        private final int partnerCount;
673
        private double combinedScore;
674
        private int rank;
675
 
676
        public SalesWeeklyStats(int salesL1Id, String salesL1Name, double avgRating, int partnerCount) {
677
            this.salesL1Id = salesL1Id;
678
            this.salesL1Name = salesL1Name;
679
            this.avgRating = avgRating;
680
            this.partnerCount = partnerCount;
681
        }
682
 
683
        public int getSalesL1Id() { return salesL1Id; }
684
        public String getSalesL1Name() { return salesL1Name; }
685
        public double getAvgRating() { return avgRating; }
686
        public int getPartnerCount() { return partnerCount; }
687
        public double getCombinedScore() { return combinedScore; }
688
        public void setCombinedScore(double combinedScore) { this.combinedScore = combinedScore; }
689
        public int getRank() { return rank; }
690
        public void setRank(int rank) { this.rank = rank; }
691
    }
692
 
34912 ranu 693
    public Map<String, Set<Integer>> generateBiReportHierarchyWise() throws Exception{
694
        List<Integer> categoryIds = Arrays.asList(ProfitMandiConstants.TICKET_CATEGORY_RBM, ProfitMandiConstants.TICKET_CATEGORY_SALES,ProfitMandiConstants.TICKET_CATEGORY_ABM,ProfitMandiConstants.TICKET_CATEGORY_BUSINESSINTELLIGENT);
695
        Map<String, Set<Integer>> storeGuyEntry = csService.getAuthUserPartnerIdMappingByCategoryIds(categoryIds, false);
696
        return storeGuyEntry;
34911 ranu 697
    }
698
 
699
 
700
 
34912 ranu 701
    public void generateBiReportExcel() throws Exception {
34911 ranu 702
 
36975 ranu 703
        long __biReportStartMs = System.currentTimeMillis();
704
        LOGGER.info("[BI_REPORT] START batch-optimized generateBiReportExcel at {}", LocalDateTime.now());
705
 
34741 ranu 706
        LocalDateTime startOfToday;
707
        LocalDateTime previousDay;
34321 ranu 708
 
34741 ranu 709
        if (LocalDate.now().getDayOfMonth() == 1) {
710
            startOfToday = LocalDate.now().minusDays(1).atStartOfDay();
711
            previousDay = startOfToday.with(LocalTime.MAX);
712
        } else {
713
            startOfToday = LocalDate.now().atStartOfDay();
714
            previousDay = startOfToday.with(LocalTime.MAX).minusDays(1);
715
        }
716
 
35239 ranu 717
        Map<Integer, CustomRetailer> customRetailers = retailerService.getAllFofoRetailersInternalFalse();
34912 ranu 718
 
35239 ranu 719
        List<Integer> retailerIds = customRetailers.values().stream()
34903 ranu 720
                .filter(retailer -> {
36973 ranu 721
                    String storeCode = retailer.getCode();
34903 ranu 722
                    return !storeCode.equalsIgnoreCase("UPGBN640") && !storeCode.equalsIgnoreCase("HRYN039");
723
                })
724
                .map(CustomRetailer::getPartnerId)
35239 ranu 725
                .collect(Collectors.toList());
34606 ranu 726
 
36973 ranu 727
        Set<Integer> retailerIdSet = new HashSet<>(retailerIds);
34903 ranu 728
 
34641 ranu 729
        //partner daily investment
34729 amit.gupta 730
        List<Loan> defaultLoans = sdCreditService.getDefaultLoans();
34641 ranu 731
        Map<Integer,List<Loan>> defaultLoanMap = defaultLoans.stream().collect(Collectors.groupingBy(Loan::getFofoId));
34619 ranu 732
 
34641 ranu 733
        Map<Integer, PartnerDailyInvestment> partnerDailyInvestmentMap = new HashMap<>();
734
        List<PartnerDailyInvestment> partnerDailyInvestments = partnerDailyInvestmentRepository
34741 ranu 735
                .selectAll(new ArrayList<>(retailerIds), previousDay.toLocalDate());
34641 ranu 736
        if (!partnerDailyInvestments.isEmpty()) {
737
            partnerDailyInvestmentMap = partnerDailyInvestments.stream()
36973 ranu 738
                    .collect(Collectors.toMap(PartnerDailyInvestment::getFofoId, x -> x));
34641 ranu 739
        }
740
 
34741 ranu 741
        YearMonth currentMonth;
742
        LocalDateTime currentMonthStartDate;
743
        LocalDateTime currentMonthEndDate;
744
 
745
        if (LocalDate.now().getDayOfMonth() == 1) {
746
            currentMonth = YearMonth.now().minusMonths(1);
747
            currentMonthStartDate = currentMonth.atDay(1).atStartOfDay();
748
            currentMonthEndDate = currentMonth.atEndOfMonth().atTime(23, 59, 59);
749
        } else {
750
            currentMonth = YearMonth.now();
751
            currentMonthStartDate = currentMonth.atDay(1).atStartOfDay();
752
            currentMonthEndDate = LocalDate.now().minusDays(1).atTime(23, 59, 59);
753
        }
754
 
34619 ranu 755
        String currentMonthStringValue = String.valueOf(currentMonth);
34606 ranu 756
 
36973 ranu 757
        YearMonth lastMonth = currentMonth.minusMonths(1);
758
        String lastMonthStringValue = String.valueOf(lastMonth);
759
        LocalDateTime lastMontStartDate = lastMonth.atDay(1).atStartOfDay();
760
        LocalDateTime lastMonthEndDate = lastMonth.atEndOfMonth().atTime(23, 59, 59);
34606 ranu 761
 
36973 ranu 762
        YearMonth twoMonthsAgo = currentMonth.minusMonths(2);
763
        String twoMonthAgoStringValue = String.valueOf(twoMonthsAgo);
764
        LocalDateTime twoMonthsAgoStartDate = twoMonthsAgo.atDay(1).atStartOfDay();
765
        LocalDateTime twoMonthsAgoEndDate = twoMonthsAgo.atEndOfMonth().atTime(23, 59, 59);
34606 ranu 766
 
36973 ranu 767
        LocalDateTime dayBeforeStart = previousDay.toLocalDate().atStartOfDay().minusDays(1);
768
        LocalDateTime dayBeforeEnd = previousDay.minusDays(1);
769
        LocalDateTime yesterdayStart = previousDay.toLocalDate().atStartOfDay();
34749 ranu 770
 
36973 ranu 771
        // Returns / RTO — already batched
772
        Map<Integer, Long> currentMonthPartnerReturnOrderInfoModelMap = returnOrderInfoRepository.selectAllByBetweenDate(currentMonthStartDate, currentMonthEndDate)
773
                .stream().collect(Collectors.groupingBy(ReturnOrderInfoModel::getRetailerId, Collectors.summingLong(x -> Math.round(x.getRefundAmount()))));
774
        Map<Integer, Long> currentMonthRtoRefundOrderMap = orderRepository.selectAllRefundOrderDatesBetween(currentMonthStartDate, currentMonthEndDate)
775
                .stream().collect(Collectors.groupingBy(Order::getRetailerId, Collectors.summingLong(x -> Math.round(x.getTotalAmount()))));
776
        Map<Integer, Long> yesterdayReturnOrderInfoModelMap = returnOrderInfoRepository.selectAllByBetweenDate(yesterdayStart, previousDay)
777
                .stream().collect(Collectors.groupingBy(ReturnOrderInfoModel::getRetailerId, Collectors.summingLong(x -> Math.round(x.getRefundAmount()))));
778
        Map<Integer, Long> yesterdayRtoRefundOrderMap = orderRepository.selectAllRefundOrderDatesBetween(yesterdayStart, previousDay)
779
                .stream().collect(Collectors.groupingBy(Order::getRetailerId, Collectors.summingLong(x -> Math.round(x.getTotalAmount()))));
780
        Map<Integer, Long> dayBeforeYesterdayReturnOrderInfoModelMap = returnOrderInfoRepository.selectAllByBetweenDate(dayBeforeStart, dayBeforeEnd)
781
                .stream().collect(Collectors.groupingBy(ReturnOrderInfoModel::getRetailerId, Collectors.summingLong(x -> Math.round(x.getRefundAmount()))));
782
        Map<Integer, Long> dayBeforeYesterdayRtoRefundOrderMap = orderRepository.selectAllRefundOrderDatesBetween(dayBeforeStart, dayBeforeEnd)
783
                .stream().collect(Collectors.groupingBy(Order::getRetailerId, Collectors.summingLong(x -> Math.round(x.getTotalAmount()))));
784
        Map<Integer, Long> lastMonthPartnerReturnOrderInfoModelMap = returnOrderInfoRepository.selectAllByBetweenDate(lastMontStartDate, lastMonthEndDate)
785
                .stream().collect(Collectors.groupingBy(ReturnOrderInfoModel::getRetailerId, Collectors.summingLong(x -> Math.round(x.getRefundAmount()))));
786
        Map<Integer, Long> lastMonthRtoRefundOrderMap = orderRepository.selectAllRefundOrderDatesBetween(lastMontStartDate, lastMonthEndDate)
787
                .stream().collect(Collectors.groupingBy(Order::getRetailerId, Collectors.summingLong(x -> Math.round(x.getTotalAmount()))));
788
        Map<Integer, Long> twoMonthAgoPartnerReturnOrderInfoModelMap = returnOrderInfoRepository.selectAllByBetweenDate(twoMonthsAgoStartDate, twoMonthsAgoEndDate)
789
                .stream().collect(Collectors.groupingBy(ReturnOrderInfoModel::getRetailerId, Collectors.summingLong(x -> Math.round(x.getRefundAmount()))));
790
        Map<Integer, Long> twoMonthAgoRtoRefundOrderMap = orderRepository.selectAllRefundOrderDatesBetween(twoMonthsAgoStartDate, twoMonthsAgoEndDate)
791
                .stream().collect(Collectors.groupingBy(Order::getRetailerId, Collectors.summingLong(x -> Math.round(x.getTotalAmount()))));
34749 ranu 792
 
36973 ranu 793
        // ---- Batch pre-fetch everything else that was previously per-fofo ----
34749 ranu 794
 
36973 ranu 795
        // Auth hierarchy (BM / SalesManager / RBM L1+L2 fallback / ABM L1+L2+L3 fallback)
796
        Map<Integer, Integer> rbmL1Map = csService.getAuthUserIdsWithoutTicketAssigneeByFofoIds(ProfitMandiConstants.TICKET_CATEGORY_RBM, EscalationType.L1, retailerIdSet);
797
        Map<Integer, Integer> rbmL2Map = csService.getAuthUserIdsWithoutTicketAssigneeByFofoIds(ProfitMandiConstants.TICKET_CATEGORY_RBM, EscalationType.L2, retailerIdSet);
798
        Map<Integer, Integer> bmMap = csService.getAuthUserIdsWithoutTicketAssigneeByFofoIds(ProfitMandiConstants.TICKET_CATEGORY_SALES, EscalationType.L2, retailerIdSet);
799
        Map<Integer, Integer> salesManagerMap = csService.getAuthUserIdsWithoutTicketAssigneeByFofoIds(ProfitMandiConstants.TICKET_CATEGORY_SALES, EscalationType.L1, retailerIdSet);
800
        Map<Integer, Integer> abmL1Map = csService.getAuthUserIdsWithoutTicketAssigneeByFofoIds(ProfitMandiConstants.TICKET_CATEGORY_ABM, EscalationType.L1, retailerIdSet);
801
        Map<Integer, Integer> abmL2Map = csService.getAuthUserIdsWithoutTicketAssigneeByFofoIds(ProfitMandiConstants.TICKET_CATEGORY_ABM, EscalationType.L2, retailerIdSet);
802
        Map<Integer, Integer> abmL3Map = csService.getAuthUserIdsWithoutTicketAssigneeByFofoIds(ProfitMandiConstants.TICKET_CATEGORY_ABM, EscalationType.L3, retailerIdSet);
34749 ranu 803
 
36973 ranu 804
        Set<Integer> allAuthUserIds = new HashSet<>();
805
        for (Map<Integer, Integer> m : Arrays.asList(rbmL1Map, rbmL2Map, bmMap, salesManagerMap, abmL1Map, abmL2Map, abmL3Map)) {
806
            m.values().stream().filter(v -> v != null && v != 0).forEach(allAuthUserIds::add);
807
        }
808
        Map<Integer, AuthUser> authUserMap = allAuthUserIds.isEmpty() ? new HashMap<>()
809
                : authRepository.selectByIds(new ArrayList<>(allAuthUserIds)).stream()
810
                    .collect(Collectors.toMap(AuthUser::getId, u -> u, (a, b) -> a));
34749 ranu 811
 
36973 ranu 812
        // AST batch
813
        Set<Integer> astIds = customRetailers.values().stream()
814
                .map(CustomRetailer::getAstId).filter(id -> id != null && id != 0).collect(Collectors.toSet());
815
        Map<Integer, AST> astMap = astIds.isEmpty() ? new HashMap<>()
816
                : astRepository.selectByIds(new ArrayList<>(astIds)).stream()
817
                    .collect(Collectors.toMap(AST::getId, a -> a, (a, b) -> a));
34749 ranu 818
 
36973 ranu 819
        // FofoStore batch (replaces the two duplicate per-fofo calls)
820
        Map<Integer, FofoStore> fofoStoreMap = fofoStoreRepository.selectByRetailerIds(retailerIds).stream()
821
                .collect(Collectors.toMap(FofoStore::getId, s -> s, (a, b) -> a));
34749 ranu 822
 
36973 ranu 823
        // Users + wallet-creation dates
824
        Map<Integer, User> userMap = userRepository.selectByIds(retailerIds).stream()
825
                .collect(Collectors.toMap(User::getId, u -> u, (a, b) -> a));
826
        Map<Integer, LocalDateTime> walletFirstCreatedMap = userWalletHistoryRepository.selectFirstCreatedDatesForFofoIds(retailerIds);
34606 ranu 827
 
36973 ranu 828
        // Partner type for today
829
        Map<Integer, PartnerType> partnerTypeMap = partnerTypeChangeService.getTypesForFofoIds(retailerIds, LocalDate.now());
34606 ranu 830
 
36973 ranu 831
        // Monthly targets
832
        Map<Integer, Double> currentTargetMap = monthlyTargetRepository.selectByDateAndFofoIds(currentMonth, retailerIds).stream()
833
                .collect(Collectors.toMap(MonthlyTarget::getFofoId, MonthlyTarget::getPurchaseTarget, (a, b) -> a));
834
        Map<Integer, Double> lastTargetMap = monthlyTargetRepository.selectByDateAndFofoIds(lastMonth, retailerIds).stream()
835
                .collect(Collectors.toMap(MonthlyTarget::getFofoId, MonthlyTarget::getPurchaseTarget, (a, b) -> a));
836
        Map<Integer, Double> twoMonthAgoTargetMap = monthlyTargetRepository.selectByDateAndFofoIds(twoMonthsAgo, retailerIds).stream()
837
                .collect(Collectors.toMap(MonthlyTarget::getFofoId, MonthlyTarget::getPurchaseTarget, (a, b) -> a));
34606 ranu 838
 
36973 ranu 839
        // DRR target: RbmTargetService.calculateFofoIdTodayTarget always uses YearMonth.now() internally, regardless of
840
        // the date arg. On the 1st this differs from currentMonth (previous month) — preserve original semantics.
841
        Map<Integer, Double> drrTargetMap = YearMonth.now().equals(currentMonth) ? currentTargetMap
842
                : monthlyTargetRepository.selectByDateAndFofoIds(YearMonth.now(), retailerIds).stream()
843
                    .collect(Collectors.toMap(MonthlyTarget::getFofoId, MonthlyTarget::getPurchaseTarget, (a, b) -> a));
34606 ranu 844
 
36973 ranu 845
        // Monthly secondary (order value) — batched
846
        Map<Integer, Double> mtdSecondaryMap = orderRepository.selectOrderValueBetweenBillingDatesGroupByFofoId(retailerIds,
847
                startOfToday.withDayOfMonth(1), previousDay).stream()
848
                .collect(Collectors.toMap(IdAmountModel::getId, IdAmountModel::getAmount, (a, b) -> a));
849
        Map<Integer, Double> yesterDaySecondaryMap = orderRepository.selectOrderValueBetweenBillingDatesGroupByFofoId(retailerIds,
850
                yesterdayStart, previousDay).stream()
851
                .collect(Collectors.toMap(IdAmountModel::getId, IdAmountModel::getAmount, (a, b) -> a));
852
        Map<Integer, Double> dayBeforeSecondaryMap = orderRepository.selectOrderValueBetweenBillingDatesGroupByFofoId(retailerIds,
853
                dayBeforeStart, dayBeforeEnd).stream()
854
                .collect(Collectors.toMap(IdAmountModel::getId, IdAmountModel::getAmount, (a, b) -> a));
855
        Map<Integer, Double> lastMonthSecondaryMap = orderRepository.selectOrderValueBetweenBillingDatesGroupByFofoId(retailerIds,
856
                lastMontStartDate, lastMonthEndDate).stream()
857
                .collect(Collectors.toMap(IdAmountModel::getId, IdAmountModel::getAmount, (a, b) -> a));
858
        Map<Integer, Double> twoMonthAgoSecondaryMap = orderRepository.selectOrderValueBetweenBillingDatesGroupByFofoId(retailerIds,
859
                twoMonthsAgoStartDate, twoMonthsAgoEndDate).stream()
860
                .collect(Collectors.toMap(IdAmountModel::getId, IdAmountModel::getAmount, (a, b) -> a));
34606 ranu 861
 
36973 ranu 862
        // Tertiary MTD / last-month / two-months-ago (all-partners scan via fofoId=0)
863
        LocalDateTime now = LocalDateTime.now();
864
        Map<Integer, Double> mtdSaleTillYesterdayMap = fofoOrderItemRepository.selectSumMopGroupByRetailer(startOfToday.withDayOfMonth(1), previousDay, 0, false);
865
        Map<Integer, Double> lastMonthSaleMap = fofoOrderItemRepository.selectSumMopGroupByRetailer(lastMontStartDate, lastMonthEndDate, 0, false);
866
        Map<Integer, Double> twoMonthAgoSaleMap = fofoOrderItemRepository.selectSumMopGroupByRetailer(twoMonthsAgoStartDate, twoMonthsAgoEndDate, 0, false);
34749 ranu 867
 
36973 ranu 868
        // Monthly activated-but-not-billed
869
        Map<Integer, Map<YearMonth, PartnerWiseActivatedNotBilledTotal>> activatedNotBilledByFofo = new HashMap<>();
870
        for (PartnerWiseActivatedNotBilledTotal t : activatedImeiRepository.getTotalMonthlyActivatedNotBilledForFofoIds(retailerIds, twoMonthsAgoStartDate)) {
871
            activatedNotBilledByFofo.computeIfAbsent(t.getFofoId(), k -> new HashMap<>())
872
                    .put(YearMonth.parse(t.getYearMonth()), t);
873
        }
34606 ranu 874
 
36973 ranu 875
        // Brand-wise tertiary MTD (per fofo → brand → amount)
876
        Map<Integer, Map<String, Double>> brandTertiaryByFofo = fofoOrderItemRepository.selectSumAmountGroupByBrandForFofoIds(currentMonthStartDate, currentMonthEndDate, retailerIds);
34749 ranu 877
 
36973 ranu 878
        // Brand-wise secondary billed (MTD)
879
        Map<Integer, Map<String, Long>> brandBilledByFofo = new HashMap<>();
880
        for (BrandWiseModel m : orderRepository.selectAllBilledByCategoryOrderGroupByBrandFofoIds(retailerIds, currentMonthStartDate, currentMonthEndDate, Arrays.asList(10006, 10001))) {
881
            brandBilledByFofo.computeIfAbsent(m.getFofoId(), k -> new HashMap<>()).merge(m.getBrand(), m.getAmount(), Long::sum);
882
        }
883
        // Brand-wise returns + RTO returns (MTD)
884
        Map<Integer, Map<String, Double>> brandReturnByFofo = new HashMap<>();
885
        for (BrandWiseReturnInfo r : returnOrderInfoRepository.selectAllBrandWiseByBetweenDateForFofoIds(currentMonthStartDate, currentMonthEndDate.plusDays(1), retailerIds)) {
886
            brandReturnByFofo.computeIfAbsent(r.getRetailerId(), k -> new HashMap<>()).merge(r.getBrand(), r.getReturnAmount(), Double::sum);
887
        }
888
        Map<Integer, Map<String, Double>> brandRtoReturnByFofo = new HashMap<>();
889
        for (BrandWiseReturnInfo r : returnOrderInfoRepository.selectAllBrandWiseRTORefundByBetweenDateForFofoIds(currentMonthStartDate, currentMonthEndDate.plusDays(1), retailerIds)) {
890
            brandRtoReturnByFofo.computeIfAbsent(r.getRetailerId(), k -> new HashMap<>()).merge(r.getBrand(), r.getReturnAmount(), Double::sum);
891
        }
892
 
893
        // Active loans grouped per fofo, plus batched loanId → sum.
894
        // The single-fofo selectAllActiveLoan(fofoId) additionally filters pendingAmount > 0 — must preserve.
895
        Map<Integer, List<Loan>> activeLoansByFofo = loanRepository.selectAllActiveLoan().stream()
896
                .filter(l -> retailerIdSet.contains(l.getFofoId())
897
                        && l.getPendingAmount() != null
898
                        && l.getPendingAmount().doubleValue() > 0)
899
                .collect(Collectors.groupingBy(Loan::getFofoId));
900
        Set<Integer> loanIdsForSum = new HashSet<>();
901
        activeLoansByFofo.values().forEach(list -> list.forEach(l -> loanIdsForSum.add(l.getId())));
902
        defaultLoans.forEach(l -> loanIdsForSum.add(l.getId()));
903
        Map<Integer, Double> loanStatementSumByLoanId = loanStatementRepository.sumAmountByLoanIds(new ArrayList<>(loanIdsForSum));
904
 
905
        // Last order per fofo (batched)
906
        Map<Integer, Integer> lastOrderIdByFofo = orderRepository.getLastOrderByFofoIds(retailerIds);
907
        Set<Integer> lastOrderIds = lastOrderIdByFofo.values().stream().filter(id -> id != null && id != 0).collect(Collectors.toSet());
908
        Map<Integer, Order> lastOrderById = new HashMap<>();
909
        for (Integer oid : lastOrderIds) {
910
            Order o = orderRepository.selectById(oid);
911
            if (o != null) lastOrderById.put(oid, o);
912
        }
913
 
914
        // DRR precomputation — the two dates we ever call with
915
        long day1RemainingDays = rbmTargetService.getRemainingDaysInMonth(currentMonth.atDay(1));
916
        long todayRemainingDays = rbmTargetService.getRemainingDaysInMonth(startOfToday.toLocalDate());
917
 
36975 ranu 918
        LOGGER.info("[BI_REPORT] batch pre-fetch complete in {}ms; retailers={}, entering per-fofo loop",
919
                System.currentTimeMillis() - __biReportStartMs, retailerIds.size());
920
        long __biReportLoopStartMs = System.currentTimeMillis();
921
 
34648 ranu 922
        Map<Integer , String> assessmentMap = new HashMap<>();
923
        Map<Integer , String> zeroBillingMap = new HashMap<>();
924
        Map<Integer , Float> billingNeededMap = new HashMap<>();
925
        Map<Integer , Integer> countAMap = new HashMap<>();
34619 ranu 926
        Map<Integer , BIRetailerModel> biRetailerModelMap = new HashMap<>();
34641 ranu 927
        Map<Integer , FofoInvestmentModel> biInvestmentModelMap = new HashMap<>();
34619 ranu 928
        Map<Integer, Map<YearMonth, BiSecondaryModel>> allRetailerMonthlyData = new HashMap<>();
929
        Map<Integer,Double> fofoTotalStockPriceMap = new HashMap<>();
930
        Map<Integer,Map<String, BrandStockPrice>> fofoBrandStockPriceMap = new HashMap<>();
34641 ranu 931
        Map<Integer,Long> fofoTotalMtdSecondaryMap = new HashMap<>();
34749 ranu 932
        Map<Integer,Long> fofoYesterdaySecondaryMap = new HashMap<>();
933
        Map<Integer,Long> fofoDayBeforeYesterdaySecondaryMap = new HashMap<>();
34641 ranu 934
        Map<Integer,Map<String, Long>> fofoBrandWiseMtdSecondaryMap = new HashMap<>();
935
        Map<Integer,Double> fofoTotalMtdTertiaryMap = new HashMap<>();
936
        Map<Integer,Map<String, Double>> fofoBrandMtdTertiaryMap = new HashMap<>();
937
 
36973 ranu 938
        for (Integer fofoId : retailerIds) {
939
            // resolve auth-user names from batched maps
34619 ranu 940
            String rbmName = "";
36973 ranu 941
            int rbmL1 = rbmL1Map.getOrDefault(fofoId, 0);
942
            if (rbmL1 != 0 && authUserMap.get(rbmL1) != null) {
943
                rbmName = authUserMap.get(rbmL1).getFullName();
944
            } else {
945
                int rbmL2 = rbmL2Map.getOrDefault(fofoId, 0);
946
                if (rbmL2 != 0 && authUserMap.get(rbmL2) != null) {
947
                    rbmName = authUserMap.get(rbmL2).getFullName();
34677 ranu 948
                }
34619 ranu 949
            }
36973 ranu 950
            String bmName = "";
951
            int bmId = bmMap.getOrDefault(fofoId, 0);
952
            if (bmId != 0 && authUserMap.get(bmId) != null) {
953
                bmName = authUserMap.get(bmId).getFullName();
34619 ranu 954
            }
36973 ranu 955
            int managerId = salesManagerMap.getOrDefault(fofoId, 0);
956
            String managerName;
957
            if (managerId != 0 && authUserMap.get(managerId) != null) {
958
                managerName = authUserMap.get(managerId).getFullName();
959
            } else {
34606 ranu 960
                managerName = bmName;
961
            }
36973 ranu 962
            String abmName = "";
963
            int abmId = abmL1Map.getOrDefault(fofoId, 0);
964
            int abmL2Id = abmL2Map.getOrDefault(fofoId, 0);
965
            int abmL3Id = abmL3Map.getOrDefault(fofoId, 0);
966
            if (abmId != 0 && authUserMap.get(abmId) != null) {
967
                abmName = authUserMap.get(abmId).getFullName();
968
            } else if (abmL2Id != 0 && authUserMap.get(abmL2Id) != null) {
969
                abmName = authUserMap.get(abmL2Id).getFullName();
970
            } else if (abmL3Id != 0 && authUserMap.get(abmL3Id) != null) {
971
                abmName = authUserMap.get(abmL3Id).getFullName();
34915 ranu 972
            }
34606 ranu 973
 
36973 ranu 974
            AST ast = astMap.get(customRetailers.get(fofoId).getAstId());
975
            PartnerType partnerTypeThisMonth = partnerTypeMap.get(fofoId);
34606 ranu 976
 
977
            BIRetailerModel biRetailerModel = new BIRetailerModel();
978
            biRetailerModel.setBmName(bmName);
979
            biRetailerModel.setCode(customRetailers.get(fofoId).getCode());
36973 ranu 980
            biRetailerModel.setArea(ast != null ? ast.getArea() : "-");
981
 
982
            FofoStore fofoStore = fofoStoreMap.get(fofoId);
983
            String retailerStatus;
984
            if (fofoStore == null) {
985
                retailerStatus = "-";
986
            } else if (!fofoStore.isActive()) {
34738 ranu 987
                retailerStatus = "INACTIVE";
36973 ranu 988
            } else {
989
                retailerStatus = String.valueOf(fofoStore.getActivationType());
34738 ranu 990
            }
34606 ranu 991
            biRetailerModel.setCity(customRetailers.get(fofoId).getAddress().getCity());
992
            biRetailerModel.setStoreName(customRetailers.get(fofoId).getBusinessName());
34738 ranu 993
            biRetailerModel.setStatus(retailerStatus);
34606 ranu 994
            biRetailerModel.setCategory(String.valueOf(partnerTypeThisMonth));
995
            biRetailerModel.setSalesManager(managerName);
996
            biRetailerModel.setRbm(rbmName);
34915 ranu 997
            biRetailerModel.setAbm(abmName);
36973 ranu 998
            biRetailerModelMap.put(fofoId, biRetailerModel);
34606 ranu 999
 
36973 ranu 1000
            Map<YearMonth, PartnerWiseActivatedNotBilledTotal> partnerWiseActivatedNotBilledTotalMap =
1001
                    activatedNotBilledByFofo.getOrDefault(fofoId, new HashMap<>());
34619 ranu 1002
 
36973 ranu 1003
            // Current-month secondary
1004
            double currentSecondaryTarget = currentTargetMap.getOrDefault(fofoId, 0d);
1005
            long currentMonthReturn = currentMonthPartnerReturnOrderInfoModelMap.getOrDefault(fofoId, 0L)
1006
                    + currentMonthRtoRefundOrderMap.getOrDefault(fofoId, 0L);
34641 ranu 1007
 
36973 ranu 1008
            double dayBeforeYesterdayAfterReturnSecondary = dayBeforeSecondaryMap.getOrDefault(fofoId, 0d)
1009
                    - (dayBeforeYesterdayReturnOrderInfoModelMap.getOrDefault(fofoId, 0L) + dayBeforeYesterdayRtoRefundOrderMap.getOrDefault(fofoId, 0L));
34749 ranu 1010
            fofoDayBeforeYesterdaySecondaryMap.put(fofoId, (long) dayBeforeYesterdayAfterReturnSecondary);
1011
 
36973 ranu 1012
            double yesterDayAfterReturnSecondary = yesterDaySecondaryMap.getOrDefault(fofoId, 0d)
1013
                    - (yesterdayReturnOrderInfoModelMap.getOrDefault(fofoId, 0L) + yesterdayRtoRefundOrderMap.getOrDefault(fofoId, 0L));
34749 ranu 1014
            fofoYesterdaySecondaryMap.put(fofoId, (long) yesterDayAfterReturnSecondary);
1015
 
36973 ranu 1016
            double secondaryAchievedMtd = mtdSecondaryMap.getOrDefault(fofoId, 0.0);
34619 ranu 1017
            double currentMonthNetSecondary = secondaryAchievedMtd - currentMonthReturn;
36973 ranu 1018
            double currentMonthSecondaryPercent = currentSecondaryTarget == 0 ? 0.0
1019
                    : Math.round(Math.abs((secondaryAchievedMtd / currentSecondaryTarget) * 100));
1020
            double currentMonthUnbilled = partnerWiseActivatedNotBilledTotalMap.get(currentMonth) != null
1021
                    ? partnerWiseActivatedNotBilledTotalMap.get(currentMonth).getTotalUnbilledAmount() : 0d;
1022
            double mtdSale = mtdSaleTillYesterdayMap.getOrDefault(fofoId, 0d);
34606 ranu 1023
 
36973 ranu 1024
            // Last month secondary
1025
            double lastMonthSecondaryTarget = lastTargetMap.getOrDefault(fofoId, 0d);
1026
            long lastMonthReturn = lastMonthPartnerReturnOrderInfoModelMap.getOrDefault(fofoId, 0L)
1027
                    + lastMonthRtoRefundOrderMap.getOrDefault(fofoId, 0L);
1028
            double lastMonthSecondaryAchieved = lastMonthSecondaryMap.getOrDefault(fofoId, 0.0);
34619 ranu 1029
            double lastMonthNetSecondary = lastMonthSecondaryAchieved - lastMonthReturn;
36973 ranu 1030
            double lastMonthSecondaryPercent = lastMonthSecondaryTarget == 0 ? 0.0
1031
                    : Math.round(Math.abs((lastMonthSecondaryAchieved / lastMonthSecondaryTarget) * 100));
1032
            double lastMonthUnbilled = partnerWiseActivatedNotBilledTotalMap.get(lastMonth) != null
1033
                    ? partnerWiseActivatedNotBilledTotalMap.get(lastMonth).getTotalUnbilledAmount() : 0d;
1034
            double lastMonthSale = lastMonthSaleMap.getOrDefault(fofoId, 0d);
34606 ranu 1035
 
36973 ranu 1036
            // Two months ago secondary
1037
            double twoMonthAgoSecondaryTarget = twoMonthAgoTargetMap.getOrDefault(fofoId, 0d);
1038
            long twoMonthAgoReturn = twoMonthAgoPartnerReturnOrderInfoModelMap.getOrDefault(fofoId, 0L)
1039
                    + twoMonthAgoRtoRefundOrderMap.getOrDefault(fofoId, 0L);
1040
            double twoMonthAgoSecondaryAchieved = twoMonthAgoSecondaryMap.getOrDefault(fofoId, 0.0);
34619 ranu 1041
            double twoMonthAgoNetSecondary = twoMonthAgoSecondaryAchieved - twoMonthAgoReturn;
36973 ranu 1042
            double twoMonthAgoSecondaryPercent = twoMonthAgoSecondaryTarget == 0 ? 0.0
1043
                    : Math.round(Math.abs((twoMonthAgoSecondaryAchieved / twoMonthAgoSecondaryTarget) * 100));
1044
            double twoMonthAgoUnbilled = partnerWiseActivatedNotBilledTotalMap.get(twoMonthsAgo) != null
1045
                    ? partnerWiseActivatedNotBilledTotalMap.get(twoMonthsAgo).getTotalUnbilledAmount() : 0d;
1046
            double twoMonthAgoSale = twoMonthAgoSaleMap.getOrDefault(fofoId, 0d);
34606 ranu 1047
 
34619 ranu 1048
            Map<YearMonth, BiSecondaryModel> monthlySecondaryModels = new HashMap<>();
36973 ranu 1049
            monthlySecondaryModels.put(currentMonth, new BiSecondaryModel(
1050
                    currentSecondaryTarget, secondaryAchievedMtd, currentMonthReturn,
1051
                    currentMonthNetSecondary, currentMonthSecondaryPercent, mtdSale, currentMonthUnbilled));
1052
            monthlySecondaryModels.put(lastMonth, new BiSecondaryModel(
1053
                    lastMonthSecondaryTarget, lastMonthSecondaryAchieved, lastMonthReturn,
1054
                    lastMonthNetSecondary, lastMonthSecondaryPercent, lastMonthSale, lastMonthUnbilled));
1055
            monthlySecondaryModels.put(twoMonthsAgo, new BiSecondaryModel(
1056
                    twoMonthAgoSecondaryTarget, twoMonthAgoSecondaryAchieved, twoMonthAgoReturn,
1057
                    twoMonthAgoNetSecondary, twoMonthAgoSecondaryPercent, twoMonthAgoSale, twoMonthAgoUnbilled));
34619 ranu 1058
            allRetailerMonthlyData.put(fofoId, monthlySecondaryModels);
1059
 
36973 ranu 1060
            // Brand-wise stock value — still per-fofo (per-fofo pricing service, not batchable trivially)
34619 ranu 1061
            Map<String, BrandStockPrice> brandStockPriceMap = inventoryService.getBrandWiseStockValue(fofoId);
36973 ranu 1062
            fofoBrandStockPriceMap.put(fofoId, brandStockPriceMap);
1063
            fofoTotalStockPriceMap.put(fofoId, brandStockPriceMap.values().stream().mapToDouble(BrandStockPrice::getTotalValue).sum());
34619 ranu 1064
 
36973 ranu 1065
            Map<String, Double> brandMtdTertiaryAmount = brandTertiaryByFofo.getOrDefault(fofoId, new HashMap<>());
1066
            fofoBrandMtdTertiaryMap.put(fofoId, brandMtdTertiaryAmount);
1067
            fofoTotalMtdTertiaryMap.put(fofoId, brandMtdTertiaryAmount.values().stream().mapToDouble(Double::doubleValue).sum());
34619 ranu 1068
 
36973 ranu 1069
            Map<String, Long> brandWiseMtdSecondaryMap = brandBilledByFofo.getOrDefault(fofoId, new HashMap<>());
1070
            Map<String, Double> brandWiseReturnInfoMap = brandReturnByFofo.getOrDefault(fofoId, new HashMap<>());
1071
            Map<String, Double> brandWiseRTOReturnInfoMap = brandRtoReturnByFofo.getOrDefault(fofoId, new HashMap<>());
34619 ranu 1072
 
34730 ranu 1073
            Set<String> allBrands = new HashSet<>();
1074
            allBrands.addAll(brandWiseMtdSecondaryMap.keySet());
1075
            allBrands.addAll(brandWiseReturnInfoMap.keySet());
1076
            allBrands.addAll(brandWiseRTOReturnInfoMap.keySet());
1077
            Map<String, Long> brandWiseMtdNetSecondaryMap = new HashMap<>();
1078
            for (String brand : allBrands) {
36973 ranu 1079
                long billedAmount = brandWiseMtdSecondaryMap.getOrDefault(brand, 0L);
1080
                double returnAmount = brandWiseReturnInfoMap.getOrDefault(brand, 0d);
1081
                double rtoReturnAmount = brandWiseRTOReturnInfoMap.getOrDefault(brand, 0d);
1082
                brandWiseMtdNetSecondaryMap.put(brand, Math.round(billedAmount - (returnAmount + rtoReturnAmount)));
34730 ranu 1083
            }
36973 ranu 1084
            fofoBrandWiseMtdSecondaryMap.put(fofoId, brandWiseMtdNetSecondaryMap);
1085
            fofoTotalMtdSecondaryMap.put(fofoId, brandWiseMtdNetSecondaryMap.values().stream().mapToLong(Long::longValue).sum());
34730 ranu 1086
 
36973 ranu 1087
            // Investment info
1088
            PartnerDailyInvestment pdi = partnerDailyInvestmentMap.get(fofoId);
1089
            float shortInvestment = pdi != null ? pdi.getShortInvestment() : 0f;
1090
            float agreedInvestment = pdi != null ? pdi.getMinInvestment() : 0f;
1091
            float investmentLevel = pdi != null ? Math.abs(((shortInvestment - agreedInvestment) / agreedInvestment) * 100) : 0f;
34730 ranu 1092
 
36973 ranu 1093
            List<Loan> fofoDefaultLoans = defaultLoanMap.get(fofoId);
34641 ranu 1094
            float defaultLoanAmount = 0f;
36973 ranu 1095
            if (fofoDefaultLoans != null) {
1096
                for (Loan entry : fofoDefaultLoans) {
1097
                    double amount = loanStatementSumByLoanId.getOrDefault(entry.getId(), 0d);
1098
                    defaultLoanAmount += amount;
34641 ranu 1099
                }
1100
            }
36973 ranu 1101
            List<Loan> activeLoans = activeLoansByFofo.getOrDefault(fofoId, Collections.emptyList());
34743 ranu 1102
            float activeLoan = 0f;
1103
            for (Loan entry : activeLoans) {
36973 ranu 1104
                double pendingAmount = loanStatementSumByLoanId.getOrDefault(entry.getId(), 0d);
34743 ranu 1105
                activeLoan += pendingAmount;
1106
            }
1107
 
36973 ranu 1108
            float poValue = pdi != null ? pdi.getUnbilledAmount() : 0f;
34719 ranu 1109
            float poAndBilledValue = (float) (currentMonthNetSecondary + poValue);
34641 ranu 1110
 
36973 ranu 1111
            // DRR — inlined equivalent of RbmTargetService.calculateFofoIdTodayTarget.
1112
            // Note: original always looks up YearMonth.now() target (not currentMonth). On the 1st these differ.
1113
            double drrTarget = drrTargetMap.getOrDefault(fofoId, 0d);
1114
            double monthDay1Drr = 0d;
1115
            if (drrTarget != 0d) {
1116
                double remainingTarget = drrTarget;
1117
                monthDay1Drr = day1RemainingDays == 0 ? remainingTarget : (int) Math.ceil(remainingTarget / day1RemainingDays);
34897 ranu 1118
            }
36973 ranu 1119
            double todayRequiredDrr = 0d;
1120
            if (monthDay1Drr != 0d) {
1121
                double remainingTarget = drrTarget - currentMonthNetSecondary;
1122
                todayRequiredDrr = todayRemainingDays == 0 ? remainingTarget : (int) Math.ceil(remainingTarget / todayRemainingDays);
1123
            }
1124
            double gotDrrPercent = monthDay1Drr == 0 ? 0 : (todayRequiredDrr / monthDay1Drr) * 100;
34701 ranu 1125
            long drrPercentDisplay = Math.round(Math.abs(gotDrrPercent));
1126
 
36973 ranu 1127
            int orderId = lastOrderIdByFofo.getOrDefault(fofoId, 0);
34644 ranu 1128
            String alertLevel = "-";
1129
            int lastPurchaseDays = 0;
34641 ranu 1130
            if (orderId != 0) {
36973 ranu 1131
                Order order = lastOrderById.get(orderId);
1132
                if (order != null) {
1133
                    lastPurchaseDays = (int) Duration.between(order.getCreateTimestamp().plusDays(1), LocalDateTime.now()).toDays();
1134
                    if (lastPurchaseDays >= 11) alertLevel = "Alert for Management";
1135
                    else if (lastPurchaseDays >= 10) alertLevel = " Alert for RSM/SH";
1136
                    else if (lastPurchaseDays >= 7) alertLevel = "Must be Billed";
1137
                    else alertLevel = "OK";
34641 ranu 1138
                }
34644 ranu 1139
            }
34641 ranu 1140
 
34644 ranu 1141
            FofoInvestmentModel fofoInvestmentModel = new FofoInvestmentModel();
36973 ranu 1142
            fofoInvestmentModel.setCounterPotential(fofoStore != null ? fofoStore.getCounterPotential() : 0);
34644 ranu 1143
            fofoInvestmentModel.setShortInvestment(shortInvestment);
1144
            fofoInvestmentModel.setDefaultLoan(defaultLoanAmount);
1145
            fofoInvestmentModel.setInvestmentLevel(investmentLevel);
1146
            fofoInvestmentModel.setActiveLoan(activeLoan);
1147
            fofoInvestmentModel.setPoValue(poValue);
1148
            fofoInvestmentModel.setPoAndBilled(poAndBilledValue);
1149
            fofoInvestmentModel.setAgreedInvestment(agreedInvestment);
36973 ranu 1150
            fofoInvestmentModel.setWallet(pdi != null ? pdi.getWalletAmount() : 0);
34644 ranu 1151
            fofoInvestmentModel.setMonthBeginingDrr(monthDay1Drr);
1152
            fofoInvestmentModel.setRequiredDrr(todayRequiredDrr);
34701 ranu 1153
            fofoInvestmentModel.setDrrPercent(drrPercentDisplay);
34644 ranu 1154
            fofoInvestmentModel.setLastBillingDone(lastPurchaseDays);
1155
            fofoInvestmentModel.setSlab(alertLevel);
1156
            biInvestmentModelMap.put(fofoId, fofoInvestmentModel);
34641 ranu 1157
 
36973 ranu 1158
            String assessment;
1159
            if (defaultLoanAmount < 0) assessment = "Loan Default";
1160
            else if (investmentLevel <= 75 && defaultLoanAmount >= 0) assessment = "Low Invest";
1161
            else assessment = "-";
1162
            assessmentMap.put(fofoId, assessment);
1163
            zeroBillingMap.put(fofoId, currentMonthNetSecondary <= 100000 ? "Zero Billing" : "-");
1164
            billingNeededMap.put(fofoId, drrPercentDisplay >= 110 && todayRequiredDrr > 0 ? (float) todayRequiredDrr : 0f);
1165
            countAMap.put(fofoId, (defaultLoanAmount > 0 || investmentLevel <= 75 || currentMonthNetSecondary <= 100000 || drrPercentDisplay >= 110) ? 1 : 0);
34606 ranu 1166
        }
1167
 
34619 ranu 1168
        LOGGER.info("Total BI Retailers processed: {}", biRetailerModelMap.size());
36975 ranu 1169
        LOGGER.info("[BI_REPORT] per-fofo loop finished in {}ms",
1170
                System.currentTimeMillis() - __biReportLoopStartMs);
34606 ranu 1171
 
34619 ranu 1172
        //generate excel and sent to mail
1173
        List<List<String>> headerGroup = new ArrayList<>();
34606 ranu 1174
 
34619 ranu 1175
        List<String> headers1 = Arrays.asList(
34641 ranu 1176
                "","","","",
34916 ranu 1177
                "Retailer Detail", "","", "", "", "", "", "", "", "","","","","",
34677 ranu 1178
 
1179
                twoMonthAgoStringValue, "", "", "", "", "", "",
1180
                lastMonthStringValue, "", "", "", "", "", "",
34619 ranu 1181
                currentMonthStringValue, "", "", "", "", "", "",
34641 ranu 1182
 
1183
                "","", "", "", "", "", "", "", "", "", "", "", "", "",
1184
 
1185
                "", "", "", "", "", "", "", "", "", "", "", "", "",
34749 ranu 1186
                "", "", "", "", "", "", "", "", "", "", "", "", "","",""
34641 ranu 1187
 
34619 ranu 1188
        );
34606 ranu 1189
 
34619 ranu 1190
        List<String> headers2 = Arrays.asList(
34641 ranu 1191
                "Assessment","Zero billing","Billing needed","Counta",
34916 ranu 1192
                "BM","Partner Id","Link","Wallet Date","Creation Date","Code","Area",  "City", "Store Name", "Status","Category","Sales Manager", "RBM","ABM",
34619 ranu 1193
                "Secondary Target", "Secondary Achieved", "Returns", "Net Secondary", "Secondary %",
1194
                "Tertiary Sale", "Unbilled",
1195
                "Secondary Target", "Secondary Achieved", "Returns", "Net Secondary", "Secondary %",
1196
                "Tertiary Sale", "Unbilled",
1197
                "Secondary Target", "Secondary Achieved", "Returns", "Net Secondary", "Secondary %",
1198
                "Tertiary Sale", "Unbilled",
34641 ranu 1199
                "Counter Potential", "Short investment", "Default", "INVESTMENT LEVEL", "Loan", "PO value", "Agreed investment",
1200
                "Wallet", "po+bill", "MONTH BEGINNING DRR", "REQ DRR", "Drr %", "Last billing Done", "Slab",
34606 ranu 1201
 
36193 ranu 1202
              "Total Stock",  "Apple","Xiaomi", "Vivo", "Tecno","Motorola", "Samsung", "Realme", "Oppo", "OnePlus", "POCO", "Lava", "Itel", "Almost New",
1203
              "Total Secondary", "Apple", "Xiaomi", "Vivo", "Tecno","Motorola", "Samsung", "Realme", "Oppo", "OnePlus", "POCO", "Lava", "Itel", "Almost New",
1204
              "Total Tertiary",  "Apple", "Xiaomi", "Vivo", "Tecno","Motorola", "Samsung", "Realme", "Oppo", "OnePlus", "POCO", "Lava", "Itel", "Almost New",
34749 ranu 1205
                "YesterDay Seconday","Day Before Yesterday Secondary"
34619 ranu 1206
        );
1207
 
1208
        headerGroup.add(headers1);
1209
        headerGroup.add(headers2);
1210
 
1211
 
1212
        List<List<?>> rows = new ArrayList<>();
36973 ranu 1213
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
34619 ranu 1214
        for (Map.Entry<Integer, BIRetailerModel> entry : biRetailerModelMap.entrySet()) {
1215
            Integer fofoId = entry.getKey();
36973 ranu 1216
            User user = userMap.get(fofoId);
1217
            LocalDateTime walletCreationDate = walletFirstCreatedMap.get(fofoId);
1218
            if (walletCreationDate == null && user != null) {
34758 ranu 1219
                walletCreationDate = user.getCreateTimestamp();
1220
            }
34619 ranu 1221
            BIRetailerModel retailer = entry.getValue();
34641 ranu 1222
 
34619 ranu 1223
            Map<YearMonth, BiSecondaryModel> monthlyData = allRetailerMonthlyData.get(fofoId);
1224
 
34741 ranu 1225
            BiSecondaryModel current = monthlyData.getOrDefault(currentMonth, new BiSecondaryModel(0,0,0,0,0,0,0));
1226
            BiSecondaryModel last = monthlyData.getOrDefault(currentMonth.minusMonths(1), new BiSecondaryModel(0,0,0,0,0,0,0));
1227
            BiSecondaryModel twoAgo = monthlyData.getOrDefault(currentMonth.minusMonths(2), new BiSecondaryModel(0,0,0,0,0,0,0));
34619 ranu 1228
 
1229
            List<Object> row = new ArrayList<>();
34758 ranu 1230
            LOGGER.info("fofoId-11 {}",fofoId);
1231
 
34619 ranu 1232
            row.addAll(Arrays.asList(
34758 ranu 1233
                    assessmentMap.get(fofoId),
1234
                    zeroBillingMap.get(fofoId),
1235
                    billingNeededMap.get(fofoId),
1236
                    countAMap.get(fofoId),
1237
                    retailer.getBmName(),
1238
                    fofoId ,
1239
                    "https://partners.smartdukaan.com/partnerPerformance?fofoId="+fofoId,
36973 ranu 1240
                    walletCreationDate != null ? walletCreationDate.format(formatter) : "-",
1241
                    (user != null && user.getCreateTimestamp() != null) ? user.getCreateTimestamp().format(formatter) : "-",
34758 ranu 1242
                    retailer.getCode(),
1243
                    retailer.getArea(),
1244
                    retailer.getCity(),
1245
                    retailer.getStoreName(),
1246
                    retailer.getStatus(),
1247
                    retailer.getCategory(),
1248
                    retailer.getSalesManager(),
34915 ranu 1249
                    retailer.getRbm(),
1250
                    retailer.getAbm()
34677 ranu 1251
 
34619 ranu 1252
            ));
1253
 
34677 ranu 1254
 
1255
            // Two Months Ago
34619 ranu 1256
            row.addAll(Arrays.asList(
34677 ranu 1257
                    twoAgo.getSecondaryTarget(),
1258
                    twoAgo.getSecondaryAchieved(),
1259
                    twoAgo.getSecondaryReturn(),
1260
                    twoAgo.getNetSecondary(),
34704 ranu 1261
                    twoAgo.getSecondaryAchievedPercent()+"%",
34677 ranu 1262
                    twoAgo.getTertiary(),
1263
                    twoAgo.getTertiaryUnBilled()
34619 ranu 1264
            ));
1265
 
1266
            // Last Month
1267
            row.addAll(Arrays.asList(
1268
                    last.getSecondaryTarget(),
1269
                    last.getSecondaryAchieved(),
1270
                    last.getSecondaryReturn(),
1271
                    last.getNetSecondary(),
34704 ranu 1272
                    last.getSecondaryAchievedPercent()+"%",
34619 ranu 1273
                    last.getTertiary(),
1274
                    last.getTertiaryUnBilled()
1275
            ));
1276
 
34677 ranu 1277
            // Current Month
34619 ranu 1278
            row.addAll(Arrays.asList(
34677 ranu 1279
                    current.getSecondaryTarget(),
1280
                    current.getSecondaryAchieved(),
1281
                    current.getSecondaryReturn(),
1282
                    current.getNetSecondary(),
34704 ranu 1283
                    current.getSecondaryAchievedPercent()+"%",
34677 ranu 1284
                    current.getTertiary(),
1285
                    current.getTertiaryUnBilled()
34619 ranu 1286
            ));
34677 ranu 1287
 
1288
 
1289
 
34641 ranu 1290
            FofoInvestmentModel fofoInvestmentModelValue = biInvestmentModelMap.get(fofoId);
1291
            if(fofoInvestmentModelValue != null){
1292
                row.addAll(Arrays.asList(
1293
                        fofoInvestmentModelValue.getCounterPotential(),
1294
                        fofoInvestmentModelValue.getShortInvestment(),
1295
                        fofoInvestmentModelValue.getDefaultLoan(),
34730 ranu 1296
                        fofoInvestmentModelValue.getInvestmentLevel() +"%",
34743 ranu 1297
                        fofoInvestmentModelValue.getActiveLoan(),
34641 ranu 1298
                        fofoInvestmentModelValue.getPoValue(),
1299
                        fofoInvestmentModelValue.getAgreedInvestment(),
1300
                        fofoInvestmentModelValue.getWallet(),
1301
                        fofoInvestmentModelValue.getPoAndBilled(),
1302
                        fofoInvestmentModelValue.getMonthBeginingDrr(),
1303
                        fofoInvestmentModelValue.getRequiredDrr(),
34704 ranu 1304
                        fofoInvestmentModelValue.getDrrPercent()+"%",
34641 ranu 1305
                        fofoInvestmentModelValue.getLastBillingDone(),
1306
                        fofoInvestmentModelValue.getSlab()
1307
                ));
1308
            }else {
1309
                row.addAll(Arrays.asList(
1310
                        "-","-","-","-","-","-","-","-","-","-","-",""
1311
                ));
1312
            }
1313
 
1314
            Map<String, BrandStockPrice> brandStockMap = fofoBrandStockPriceMap.get(fofoId);
34619 ranu 1315
            row.addAll(Arrays.asList(
1316
                    fofoTotalStockPriceMap.getOrDefault(fofoId, 0.0),
34641 ranu 1317
                    brandStockMap.get("Apple") != null ? brandStockMap.get("Apple").getTotalValue() : 0.0,
1318
                    brandStockMap.get("Xiaomi") != null ? brandStockMap.get("Xiaomi").getTotalValue() : 0.0,
1319
                    brandStockMap.get("Vivo") != null ? brandStockMap.get("Vivo").getTotalValue() : 0.0,
1320
                    brandStockMap.get("Tecno") != null ? brandStockMap.get("Tecno").getTotalValue() : 0.0,
36193 ranu 1321
                    brandStockMap.get("Motorola") != null ? brandStockMap.get("Motorola").getTotalValue() : 0.0,
34641 ranu 1322
                    brandStockMap.get("Samsung") != null ? brandStockMap.get("Samsung").getTotalValue() : 0.0,
1323
                    brandStockMap.get("Realme") != null ? brandStockMap.get("Realme").getTotalValue() : 0.0,
1324
                    brandStockMap.get("Oppo") != null ? brandStockMap.get("Oppo").getTotalValue() : 0.0,
1325
                    brandStockMap.get("OnePlus") != null ? brandStockMap.get("OnePlus").getTotalValue() : 0.0,
34721 ranu 1326
                    brandStockMap.get("POCO") != null ? brandStockMap.get("POCO").getTotalValue() : 0.0,
34641 ranu 1327
                    brandStockMap.get("Lava") != null ? brandStockMap.get("Lava").getTotalValue() : 0.0,
1328
                    brandStockMap.get("Itel") != null ? brandStockMap.get("Itel").getTotalValue() : 0.0,
1329
                    brandStockMap.get("Almost New") != null ? brandStockMap.get("Almost New").getTotalValue() : 0.0
34619 ranu 1330
            ));
1331
 
34641 ranu 1332
            Map<String, Long> brandSecondaryMap = fofoBrandWiseMtdSecondaryMap.get(fofoId);
1333
            row.addAll(Arrays.asList(
34648 ranu 1334
                    fofoTotalMtdSecondaryMap.get(fofoId),
34641 ranu 1335
                    brandSecondaryMap.getOrDefault("Apple", 0L),
1336
                    brandSecondaryMap.getOrDefault("Xiaomi", 0L),
1337
                    brandSecondaryMap.getOrDefault("Vivo", 0L),
1338
                    brandSecondaryMap.getOrDefault("Tecno", 0L),
36193 ranu 1339
                    brandSecondaryMap.getOrDefault("Motorola", 0L),
34641 ranu 1340
                    brandSecondaryMap.getOrDefault("Samsung", 0L),
1341
                    brandSecondaryMap.getOrDefault("Realme", 0L),
1342
                    brandSecondaryMap.getOrDefault("Oppo", 0L),
1343
                    brandSecondaryMap.getOrDefault("OnePlus", 0L),
34721 ranu 1344
                    brandSecondaryMap.getOrDefault("POCO", 0L),
34641 ranu 1345
                    brandSecondaryMap.getOrDefault("Lava", 0L),
1346
                    brandSecondaryMap.getOrDefault("Itel", 0L),
1347
                    brandSecondaryMap.getOrDefault("Almost New", 0L)
1348
            ));
1349
 
1350
            Map<String, Double> brandTertiaryMap = fofoBrandMtdTertiaryMap.get(fofoId);
1351
            row.addAll(Arrays.asList(
34648 ranu 1352
                    fofoTotalMtdTertiaryMap.get(fofoId),
34641 ranu 1353
                    brandTertiaryMap.getOrDefault("Apple", 0d),
1354
                    brandTertiaryMap.getOrDefault("Xiaomi", 0d),
1355
                    brandTertiaryMap.getOrDefault("Vivo", 0d),
1356
                    brandTertiaryMap.getOrDefault("Tecno", 0d),
36193 ranu 1357
                    brandTertiaryMap.getOrDefault("Motorola", 0d),
34641 ranu 1358
                    brandTertiaryMap.getOrDefault("Samsung", 0d),
1359
                    brandTertiaryMap.getOrDefault("Realme", 0d),
1360
                    brandTertiaryMap.getOrDefault("Oppo", 0d),
1361
                    brandTertiaryMap.getOrDefault("OnePlus", 0d),
34721 ranu 1362
                    brandTertiaryMap.getOrDefault("POCO", 0d),
34641 ranu 1363
                    brandTertiaryMap.getOrDefault("Lava", 0d),
1364
                    brandTertiaryMap.getOrDefault("Itel", 0d),
1365
                    brandTertiaryMap.getOrDefault("Almost New", 0d)
1366
            ));
34749 ranu 1367
 
1368
            row.addAll(Arrays.asList(
1369
                    fofoYesterdaySecondaryMap.get(fofoId),
1370
                    fofoDayBeforeYesterdaySecondaryMap.get(fofoId)
1371
            ));
34641 ranu 1372
            rows.add(row);
34619 ranu 1373
        }
1374
 
34912 ranu 1375
        Map<String, Set<Integer>> storeGuyMap = this.generateBiReportHierarchyWise();
34619 ranu 1376
 
35239 ranu 1377
        for (Map.Entry<String, Set<Integer>> storeGuyEntry : storeGuyMap.entrySet()) {
34912 ranu 1378
            String storeGuyEmail = storeGuyEntry.getKey();
1379
            Set<Integer> fofoIds = storeGuyEntry.getValue();
1380
            String[] sendToArray = new String[]{storeGuyEmail};
34911 ranu 1381
 
34912 ranu 1382
            List<List<?>> filteredRows = rows.stream()
1383
                    .filter(row -> row.size() > 5 && fofoIds.contains((Integer) row.get(5)))
1384
                    .collect(Collectors.toList());
1385
            this.sendMailToUser(headerGroup,filteredRows,sendToArray);
35239 ranu 1386
        }
34912 ranu 1387
 
36193 ranu 1388
        this.sendMailToUser(
1389
                headerGroup,
1390
                rows,
1391
                new String[]{
1392
                        "ranu.rajput@smartdukaan.com",
1393
                        "niranjan.kala@smartdukaan.com",
36216 ranu 1394
                        "nivesh.mathur@smartdukaan.com",
36193 ranu 1395
                        "deena.nath@smartdukaan.com",
1396
                        "santosh.giri@smartdukaan.com"
1397
                }
1398
        );
34912 ranu 1399
 
36975 ranu 1400
        LOGGER.info("[BI_REPORT] DONE batch-optimized generateBiReportExcel; totalMs={}, retailers={}, rows={}",
1401
                System.currentTimeMillis() - __biReportStartMs, retailerIds.size(), rows.size());
34912 ranu 1402
 
36975 ranu 1403
 
34911 ranu 1404
    }
1405
 
1406
    private  void sendMailToUser(List<List<String>> headerGroup,List<List<?>> rows, String[] sendToArray ) throws Exception {
34641 ranu 1407
        // Send to email
1408
//        ByteArrayOutputStream csvStream = FileUtil.getCSVByteStreamWithMultiHeaders(headerGroup, rows);
1409
        ByteArrayOutputStream csvStream = getExcelStreamWithMultiHeaders(headerGroup, rows);
1410
        String fileName = "BI-Retailer-Monthly-Report-" + FormattingUtils.formatDate(LocalDateTime.now()) + ".xlsx";
34619 ranu 1411
        Utils.sendMailWithAttachment(googleMailSender, sendToArray, new String[]{}, "BI Retailer Monthly Report", "Please find attached the BI retailer secondary/tertiary monthly report.", fileName, new ByteArrayResource(csvStream.toByteArray()));
34911 ranu 1412
    }
34619 ranu 1413
 
1414
 
34641 ranu 1415
    public static ByteArrayOutputStream getExcelStreamWithMultiHeaders(List<List<String>> headerGroup, List<List<?>> rows) {
1416
        Workbook workbook = new XSSFWorkbook();
1417
        Sheet sheet = workbook.createSheet("BI Report");
34715 ranu 1418
        CreationHelper creationHelper = workbook.getCreationHelper();
34641 ranu 1419
        int rowIndex = 0;
34606 ranu 1420
 
34641 ranu 1421
        CellStyle centeredStyle = workbook.createCellStyle();
1422
        centeredStyle.setAlignment(HorizontalAlignment.CENTER); // Center horizontally
1423
        centeredStyle.setVerticalAlignment(VerticalAlignment.CENTER); // Center vertically
34606 ranu 1424
 
34641 ranu 1425
    // Optional: bold font
1426
        Font font1 = workbook.createFont();
1427
        font1.setBold(true);
1428
        centeredStyle.setFont(font1);
34606 ranu 1429
 
34619 ranu 1430
 
34641 ranu 1431
 
1432
        // Create styles
1433
        Map<String, CellStyle> headerStyles = new HashMap<>();
1434
 
1435
        // fontPurpleStyle
1436
        CellStyle purpleStyle = workbook.createCellStyle();
1437
        purpleStyle.setFillForegroundColor(IndexedColors.ROSE.getIndex());
1438
        purpleStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
1439
        purpleStyle.setFont(font1);
1440
        headerStyles.put("Assessment", purpleStyle);
1441
        headerStyles.put("Zero billing", purpleStyle);
1442
        headerStyles.put("Billing needed", purpleStyle);
1443
        headerStyles.put("Counta", purpleStyle);
1444
        headerStyles.put("MONTH BEGINNING DRR", purpleStyle);
1445
        headerStyles.put("REQ DRR", purpleStyle);
1446
        headerStyles.put("Drr %", purpleStyle);
1447
 
1448
        // Light Blue
1449
        CellStyle blueStyle = workbook.createCellStyle();
1450
        blueStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex());
1451
        blueStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
1452
        blueStyle.setFont(font1);
1453
        headerStyles.put("Code", blueStyle);
1454
        headerStyles.put("Store Name", blueStyle);
1455
        headerStyles.put("City", blueStyle);
1456
        headerStyles.put("Area", blueStyle);
1457
        headerStyles.put("BM", blueStyle);
1458
        headerStyles.put("RBM", blueStyle);
1459
        headerStyles.put("Sales Manager", blueStyle);
1460
        headerStyles.put("Status", blueStyle);
1461
        headerStyles.put("Category", blueStyle);
34715 ranu 1462
        headerStyles.put("Wallet Date", blueStyle);
1463
        headerStyles.put("Creation Date", blueStyle);
1464
        headerStyles.put("Partner Id", blueStyle);
34641 ranu 1465
 
34715 ranu 1466
        //for link
1467
        // Create hyperlink style
1468
        CellStyle hyperlinkStyle = workbook.createCellStyle();
1469
        Font hlinkFont = workbook.createFont();
1470
        hlinkFont.setUnderline(Font.U_SINGLE);
1471
        hlinkFont.setColor(IndexedColors.BLUE.getIndex());
1472
        hyperlinkStyle.setFont(hlinkFont);
1473
 
1474
 
34641 ranu 1475
        // Light Yellow
1476
        CellStyle yellowStyle = workbook.createCellStyle();
1477
        yellowStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex());
1478
        yellowStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
1479
        yellowStyle.setFont(font1);
1480
        headerStyles.put("Last billing Done", yellowStyle);
1481
        headerStyles.put("Total Stock", yellowStyle);
1482
 
1483
        // Light Orange
1484
        CellStyle orangeStyle = workbook.createCellStyle();
1485
        orangeStyle.setFillForegroundColor(IndexedColors.LIGHT_ORANGE.getIndex());
1486
        orangeStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
1487
        orangeStyle.setFont(font1);
1488
        headerStyles.put("Total Tertiary", orangeStyle);
1489
        headerStyles.put("Total Secondary", orangeStyle);
1490
        headerStyles.put("Default", orangeStyle);
1491
 
1492
 
1493
        // Light green
1494
        CellStyle lightGreenStyle = workbook.createCellStyle();
1495
        lightGreenStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
1496
        lightGreenStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
1497
        lightGreenStyle.setFont(font1);
1498
        headerStyles.put("Short investment", lightGreenStyle);
1499
        headerStyles.put("INVESTMENT LEVEL", lightGreenStyle);
1500
        headerStyles.put("Loan", lightGreenStyle);
1501
        headerStyles.put("PO value", lightGreenStyle);
1502
        headerStyles.put("Agreed investment", lightGreenStyle);
1503
        headerStyles.put("Wallet", lightGreenStyle);
1504
        headerStyles.put("po+bill", lightGreenStyle);
1505
 
1506
        // Light Green
1507
        CellStyle secondary1 = createStyle(workbook, IndexedColors.LIGHT_GREEN);
1508
        CellStyle secondary2 = createStyle(workbook, IndexedColors.LIGHT_YELLOW);
1509
        CellStyle secondary3 = createStyle(workbook, IndexedColors.LIGHT_ORANGE);
1510
 
1511
        Map<String, CellStyle> brandStyles = new HashMap<>();
1512
        brandStyles.put("Apple", createStyle(workbook, IndexedColors.GREY_25_PERCENT));
1513
        brandStyles.put("Xiaomi", createStyle(workbook, IndexedColors.ORANGE));
1514
        brandStyles.put("Vivo", createStyle(workbook, IndexedColors.SKY_BLUE));
1515
        brandStyles.put("Tecno", createStyle(workbook, IndexedColors.LIGHT_BLUE));
36193 ranu 1516
        brandStyles.put("Motorola", createStyle(workbook, IndexedColors.LIGHT_GREEN));
34641 ranu 1517
        brandStyles.put("Samsung", createStyle(workbook, IndexedColors.ROYAL_BLUE));
1518
        brandStyles.put("Realme", createStyle(workbook, IndexedColors.YELLOW));
1519
        brandStyles.put("Oppo", createStyle(workbook, IndexedColors.LIGHT_GREEN));
1520
        brandStyles.put("OnePlus", createStyle(workbook, IndexedColors.RED));
34721 ranu 1521
        brandStyles.put("POCO", createStyle(workbook, IndexedColors.ORANGE));
34641 ranu 1522
        brandStyles.put("Lava", createStyle(workbook, IndexedColors.LIGHT_YELLOW));
1523
        brandStyles.put("Itel", createStyle(workbook, IndexedColors.LIGHT_YELLOW));
1524
        brandStyles.put("Almost New", createStyle(workbook, IndexedColors.WHITE));
1525
 
1526
 
1527
        CellStyle defaultHeaderStyle = workbook.createCellStyle();
1528
        defaultHeaderStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
1529
        defaultHeaderStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
1530
        defaultHeaderStyle.setFont(font1);
1531
 
34749 ranu 1532
        CellStyle numberStyle = workbook.createCellStyle();
1533
        DataFormat format = workbook.createDataFormat();
1534
        numberStyle.setDataFormat(format.getFormat("#,##0")); // or "#,##0.00" for two decimals
34641 ranu 1535
 
34749 ranu 1536
 
1537
 
34641 ranu 1538
        Map<String, Integer> headerCount = new HashMap<>();
1539
 
1540
        for (int headerRowIndex = 0; headerRowIndex < headerGroup.size(); headerRowIndex++) {
1541
            List<String> headerRow = headerGroup.get(headerRowIndex);
1542
            Row row = sheet.createRow(rowIndex++);
1543
 
1544
            for (int i = 0; i < headerRow.size(); i++) {
1545
                String headerText = headerRow.get(i);
1546
                sheet.setColumnWidth(i, 25 * 256);
1547
                row.setHeightInPoints(20); // 25-point height
1548
                Cell cell = row.createCell(i);
1549
                cell.setCellValue(headerText);
1550
                cell.setCellStyle(centeredStyle);
1551
                // Count how many times this header has appeared
1552
                int count = headerCount.getOrDefault(headerText, 0) + 1;
1553
                headerCount.put(headerText, count);
1554
                // Apply special style for repeated headers
1555
                if (headerText.equals("Secondary Target") || headerText.equals("Secondary Achieved") || headerText.equals("Returns") || headerText.equals("Net Secondary") || headerText.equals("Secondary %") || headerText.equals("Tertiary Sale") || headerText.equals("Unbilled")) {
1556
                    if (count == 1) {
1557
                        cell.setCellStyle(secondary1);
1558
                    } else if (count == 2) {
1559
                        cell.setCellStyle(secondary2);
1560
                    } else if (count == 3) {
1561
                        cell.setCellStyle(secondary3);
1562
                    }
1563
                }
1564
                // Brand header styling (apply only for the 2nd row of headers)
1565
                else if (headerRowIndex == 1 && brandStyles.containsKey(headerText)) {
1566
                    cell.setCellStyle(brandStyles.get(headerText));
1567
                }else if (headerStyles.containsKey(headerText)) {
1568
                    cell.setCellStyle(headerStyles.get(headerText));
1569
                } else {
1570
                    cell.setCellStyle(defaultHeaderStyle); // default style for others
1571
                }
1572
            }
1573
        }
1574
 
1575
        // Write data rows
1576
        for (List<?> dataRow : rows) {
1577
            Row row = sheet.createRow(rowIndex++);
1578
            for (int i = 0; i < dataRow.size(); i++) {
1579
                Cell cell = row.createCell(i);
1580
                Object value = dataRow.get(i);
34715 ranu 1581
 
1582
                if (i == 6 && value != null) { // Assuming column 6 is "Link"
1583
                    Hyperlink hyperlink = creationHelper.createHyperlink(HyperlinkType.URL);
1584
                    hyperlink.setAddress(value.toString());
34719 ranu 1585
                    cell.setCellValue("View Link"); // Display text
34715 ranu 1586
                    cell.setHyperlink(hyperlink);
1587
                    cell.setCellStyle(hyperlinkStyle);
34719 ranu 1588
                } else if (value instanceof Number) {
34749 ranu 1589
                    double numeric = ((Number) value).doubleValue();
1590
                    cell.setCellValue(Math.round(numeric));
1591
                    cell.setCellStyle(numberStyle);
34715 ranu 1592
                } else {
1593
                    cell.setCellValue(value != null ? value.toString() : "");
1594
                }
34641 ranu 1595
            }
34719 ranu 1596
 
34641 ranu 1597
        }
1598
 
1599
        // Auto-size columns
1600
        if (!rows.isEmpty()) {
1601
            for (int i = 0; i < rows.get(0).size(); i++) {
1602
                sheet.autoSizeColumn(i);
1603
            }
1604
        }
1605
 
1606
        // Output as ByteArray
1607
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
1608
            workbook.write(outputStream);
1609
            workbook.close();
1610
            return outputStream;
1611
        } catch (IOException e) {
1612
            throw new RuntimeException("Failed to generate Excel file", e);
1613
        }
1614
    }
1615
 
1616
 
1617
    private static CellStyle createStyle(Workbook workbook, IndexedColors color) {
1618
        CellStyle style = workbook.createCellStyle();
1619
        style.setFillForegroundColor(color.getIndex());
1620
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
1621
        Font font = workbook.createFont();
1622
        font.setBold(true);
1623
        style.setFont(font);
1624
        return style;
1625
    }
1626
 
1627
 
34758 ranu 1628
    public void stockAlertMailToRetailer() throws Exception {
1629
 
1630
        Map<Integer, CustomRetailer> customRetailers = retailerService.getFofoRetailers(true);
1631
 
1632
        List<Integer> retailerIds = customRetailers.values().stream().map(CustomRetailer::getPartnerId).collect(Collectors.toList());
1633
 
1634
        for(Integer fofoId : retailerIds){
1635
            List<String> statusOrder = Arrays.asList("HID", "FASTMOVING", "RUNNING", "SLOWMOVING", "OTHER");
1636
            FofoStore fofoStore = fofoStoreRepository.selectByRetailerId(fofoId);
1637
            List<PartnerWarehouseStockSummaryModel> partnerWarehouseStockSummaryModels = saholicInventoryService.getSaholicAndPartnerStock(fofoId, fofoStore.getWarehouseId());
1638
 
1639
            List<PartnerWarehouseStockAgingSummaryModel> partnerWarehouseStockAgingSummaryModelList = new ArrayList<>();
1640
 
1641
            Set<Integer> catalogIds = partnerWarehouseStockSummaryModels.stream().map(x -> x.getCatalogId()).collect(Collectors.toSet());
1642
 
1643
            List<Integer> catalogsList = new ArrayList<>(catalogIds);
1644
 
1645
            Map<Integer, TagListing> tagListingsMap = tagListingRepository.selectAllByCatalogIds(catalogsList);
1646
 
1647
            List<CatalogAgingModel> catalogAgingModels = ageingService.getCatalogsAgingByWarehouse(catalogIds, fofoStore.getWarehouseId());
1648
 
1649
            Map<Integer, CatalogAgingModel> catalogAgingModelMap = catalogAgingModels.stream().collect(Collectors.toMap(x -> x.getCatalogId(), x -> x));
1650
 
1651
            for (PartnerWarehouseStockSummaryModel stockSummary : partnerWarehouseStockSummaryModels) {
1652
 
1653
                PartnerWarehouseStockAgingSummaryModel partnerWarehouseStockAgingSummaryModel = new PartnerWarehouseStockAgingSummaryModel();
1654
                partnerWarehouseStockAgingSummaryModel.setCatalogId(stockSummary.getCatalogId());
1655
                partnerWarehouseStockAgingSummaryModel.setBrand(stockSummary.getBrand());
1656
                partnerWarehouseStockAgingSummaryModel.setModelNumber(stockSummary.getModelNumber());
1657
                partnerWarehouseStockAgingSummaryModel.setNetAvailability(stockSummary.getShaholicNetAvailability());
1658
                partnerWarehouseStockAgingSummaryModel.setPartnerStockAvailability(stockSummary.getPartnerFullFilledQty());
1659
                partnerWarehouseStockAgingSummaryModel.setPartnerCurrentAvailability(stockSummary.getPartnerCurrentQty());
1660
                partnerWarehouseStockAgingSummaryModel.setPartnerShortageStock(stockSummary.getPartnerShortageQty());
1661
                if (catalogAgingModelMap.get(stockSummary.getCatalogId()) != null) {
1662
                    partnerWarehouseStockAgingSummaryModel.setExceedDays(catalogAgingModelMap.get(stockSummary.getCatalogId()).getExceedDays());
1663
                } else {
1664
                    partnerWarehouseStockAgingSummaryModel.setExceedDays(0);
1665
 
1666
                }
1667
                partnerWarehouseStockAgingSummaryModel.setStatus(stockSummary.getStatus());
1668
 
1669
                partnerWarehouseStockAgingSummaryModelList.add(partnerWarehouseStockAgingSummaryModel);
1670
            }
1671
 
1672
            Set<Integer> existingCatalogIdsInAgingSummaryList = partnerWarehouseStockAgingSummaryModelList.stream()
1673
                    .map(PartnerWarehouseStockAgingSummaryModel::getCatalogId)
1674
                    .collect(Collectors.toSet());
1675
        }
1676
 
1677
    }
1678
 
34939 ranu 1679
    public void createFofoSmartCartSuggestion(){
34758 ranu 1680
 
34939 ranu 1681
        List<Integer> fofoIds = fofoStoreRepository.selectActiveStores().stream().map(x->x.getId()).collect(toList());
1682
        LocalDateTime todayDate = LocalDate.now().atStartOfDay();
1683
        LocalDateTime fortyFiveAgoDate = todayDate.minusDays(45).with(LocalTime.MAX);
1684
        for(Integer fofoId :fofoIds){
1685
            smartCartSuggestionRepository.deleteByFofoId(fofoId);
1686
            List<SoldAllCatalogitemQtyByPartnerModel> soldAllCatalogitemQtyByPartnerModels = smartCartService.getAllSoldCatalogItemByPartner(fofoId,fortyFiveAgoDate,todayDate);
1687
            for(SoldAllCatalogitemQtyByPartnerModel soldAllCatalogitemQtyByPartnerModel : soldAllCatalogitemQtyByPartnerModels){
1688
               SmartCartSuggestion smartCartSuggestion = new SmartCartSuggestion();
34941 ranu 1689
 
1690
                // weekly average = total sold qty / 6 weeks
1691
                long avgWeeklyQty = Math.round((float) soldAllCatalogitemQtyByPartnerModel.getSoldQty() / 6);
1692
 
1693
                // ensure minimum 2
1694
                long suggestedQty = Math.max(1, avgWeeklyQty);
1695
 
34939 ranu 1696
               smartCartSuggestion.setCatalogId(soldAllCatalogitemQtyByPartnerModel.getCatalogId());
1697
               smartCartSuggestion.setFofoId(fofoId);
1698
               smartCartSuggestion.setSoldQty(soldAllCatalogitemQtyByPartnerModel.getSoldQty());
34941 ranu 1699
               smartCartSuggestion.setSuggestedQty(suggestedQty);
34939 ranu 1700
               smartCartSuggestion.setCreationDate(LocalDate.now());
1701
               smartCartSuggestionRepository.persist(smartCartSuggestion);
1702
            }
1703
        }
1704
 
1705
    }
1706
 
1707
 
34306 ranu 1708
}