Subversion Repositories SmartDukaan

Rev

Rev 37409 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
25822 amit.gupta 1
package com.spice.profitmandi.service;
2
 
3
import com.google.gson.Gson;
30025 amit.gupta 4
import com.spice.profitmandi.common.enumuration.MessageType;
37508 ranu 5
import com.spice.profitmandi.common.enumuration.NotificationFormat;
6
import com.spice.profitmandi.common.enumuration.ScheduleStatus;
29198 manish 7
import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
37409 vikas 8
import com.spice.profitmandi.common.model.CustomRetailer;
37508 ranu 9
import com.spice.profitmandi.common.model.NotificationTargetCriteria;
29927 amit.gupta 10
import com.spice.profitmandi.common.model.ProfitMandiConstants;
25822 amit.gupta 11
import com.spice.profitmandi.common.model.SendNotificationModel;
29927 amit.gupta 12
import com.spice.profitmandi.dao.entity.auth.AuthUser;
32405 jai.hind 13
import com.spice.profitmandi.dao.entity.dtr.*;
36064 aman 14
import com.spice.profitmandi.dao.entity.fofo.Customer;
25822 amit.gupta 15
import com.spice.profitmandi.dao.entity.user.Device;
32405 jai.hind 16
import com.spice.profitmandi.dao.entity.whatsapp.WhatsappMessage;
25822 amit.gupta 17
import com.spice.profitmandi.dao.model.SimpleCampaignParams;
18
import com.spice.profitmandi.dao.repository.catalog.DeviceRepository;
29927 amit.gupta 19
import com.spice.profitmandi.dao.repository.cs.CsService;
35207 amit 20
import com.spice.profitmandi.dao.repository.cs.PartnerRegionRepository;
32405 jai.hind 21
import com.spice.profitmandi.dao.repository.dtr.*;
36064 aman 22
import com.spice.profitmandi.dao.repository.fofo.CustomerRepository;
32405 jai.hind 23
import com.spice.profitmandi.dao.repository.whatsapp.WhatsappMessageRepository;
29198 manish 24
import com.spice.profitmandi.service.user.RetailerService;
37508 ranu 25
import com.spice.profitmandi.service.whatsapp.*;
32405 jai.hind 26
import org.apache.logging.log4j.LogManager;
27
import org.apache.logging.log4j.Logger;
28
import org.springframework.beans.factory.annotation.Autowired;
34522 ranu 29
import org.springframework.beans.factory.annotation.Value;
32405 jai.hind 30
import org.springframework.stereotype.Component;
37508 ranu 31
import org.springframework.transaction.annotation.Propagation;
32
import org.springframework.transaction.annotation.Transactional;
25822 amit.gupta 33
 
34545 vikas.jang 34
import java.nio.charset.StandardCharsets;
32508 amit.gupta 35
import java.time.LocalDate;
32405 jai.hind 36
import java.time.LocalDateTime;
37
import java.util.*;
38
import java.util.stream.Collectors;
39
 
25835 amit.gupta 40
@Component
25822 amit.gupta 41
public class NotificationServiceImpl implements NotificationService {
42
 
35236 amit 43
    private static final Logger LOGGER = LogManager.getLogger(NotificationServiceImpl.class);
37051 amit 44
 
45
    // Region id that represents "all partners". Schemes meant for everyone are tagged to this region;
46
    // partner_region is not populated for it, so it must be expanded to all partners here.
47
    private static final int ALL_PARTNERS_REGION = 5;
48
 
32253 amit.gupta 49
    @Autowired
50
    UserCampaignRepository userCampaignRepository;
51
    @Autowired
52
    UserRepository dtrUserRepository;
53
    @Autowired
54
    UserAccountRepository userAccountRepository;
55
    @Autowired
56
    NotificationCampaignRepository notificationCampaignRepository;
57
    @Autowired
58
    DeviceRepository deviceRepository;
35207 amit 59
 
32253 amit.gupta 60
    @Autowired
35207 amit 61
    PartnerRegionRepository partnerRegionRepository;
62
 
63
 
64
    @Autowired
32253 amit.gupta 65
    FofoStoreRepository fofoStoreRepository;
66
    @Autowired
67
    CsService csService;
68
    @Autowired
69
    RetailerService retailerService;
70
    @Autowired
36064 aman 71
    CustomerRepository customerRepository;
72
    @Autowired
32253 amit.gupta 73
    PushNotificationRepository pushNotificationRepository;
74
    @Autowired
37508 ranu 75
    NotificationScheduleRepository notificationScheduleRepository;
76
    @Autowired
32253 amit.gupta 77
    private Gson gson;
34522 ranu 78
    @Value("${prod}")
79
    private boolean isProd;
25822 amit.gupta 80
 
32405 jai.hind 81
    @Autowired
32417 amit.gupta 82
    private WhatsappMessageRepository whatsappMessageRepository;
32405 jai.hind 83
 
84
    @Autowired
85
    private WhatsappMessageService whatsappMessageService;
86
 
36962 vikas 87
    @Autowired
37409 vikas 88
    private BotPenguinWhatsappService botPenguin;
36962 vikas 89
 
32253 amit.gupta 90
    @Override
91
    public void sendNotification(SendNotificationModel sendNotificationModel) throws ProfitMandiBusinessException {
29927 amit.gupta 92
 
37508 ranu 93
        NotificationTargetCriteria criteria = new NotificationTargetCriteria(
94
                sendNotificationModel.getUserIds(),
95
                sendNotificationModel.getFofoIds(),
96
                sendNotificationModel.getStateIds(),
97
                sendNotificationModel.getRegionIds());
98
 
99
        NotificationCampaign nc = buildAndPersistCampaign(sendNotificationModel, criteria);
100
 
101
        List<LocalDateTime> schedule = sendNotificationModel.getScheduledTimestamps();
102
        if (schedule != null && !schedule.isEmpty()) {
103
            LocalDateTime now = LocalDateTime.now();
104
            for (LocalDateTime ts : schedule) {
105
                NotificationSchedule ns = new NotificationSchedule();
106
                ns.setCampaignId(nc.getId());
107
                ns.setScheduledTimestamp(ts);
108
                ns.setStatus(ScheduleStatus.SCHEDULED);
109
                ns.setCreatedTimestamp(now);
110
                notificationScheduleRepository.persist(ns);
111
            }
112
            return;
113
        }
114
 
115
        Set<Integer> userIds = resolveTargetUserIds(criteria);
116
        if (userIds.isEmpty()) {
117
            LOGGER.info("Failed to send notification to any retailer with this model - {}", sendNotificationModel);
118
            return;
119
        }
120
        fanOutToUsers(nc.getId(), userIds, nc.getFormat());
121
    }
122
 
123
    private NotificationCampaign buildAndPersistCampaign(SendNotificationModel model, NotificationTargetCriteria criteria) {
32253 amit.gupta 124
        SimpleCampaignParams scp = new SimpleCampaignParams();
37508 ranu 125
        scp.setMessage(model.getMessage());
126
        scp.setTitle(model.getTitle());
127
        scp.setImageUrl(model.getImageUrl());
128
        scp.setType(model.getType());
129
        scp.setUrl(model.getUrl());
130
        scp.setShowImage(model.getShowImage());
131
        scp.setExpireTimestamp(model.getExpiresat());
30025 amit.gupta 132
 
37508 ranu 133
        // Pop-up / Story fields — silently no-op for PUSH since model getters return null.
134
        scp.setButtonLabel(model.getButtonLabel());
135
        scp.setShowCta(model.getShowCta());
136
        scp.setShowClose(model.getShowClose());
137
        scp.setShowOnPage(model.getShowOnPage());
138
        scp.setAutoCloseSeconds(model.getAutoCloseSeconds());
139
        scp.setFrequencyCap(model.getFrequencyCap());
140
        scp.setLiveFrom(model.getLiveFrom());
141
        scp.setLiveUntil(model.getLiveUntil());
142
        scp.setStoryCards(model.getStoryCards());
143
        scp.setPostInCategory(model.getPostInCategory());
144
        scp.setPlayDurationSeconds(model.getPlayDurationSeconds());
145
        scp.setExpiresAfterPolicy(model.getExpiresAfterPolicy());
146
        scp.setImages(model.getImages());
147
        scp.setLinkedEntities(model.getLinkedEntities());
148
 
32253 amit.gupta 149
        NotificationCampaign nc = new NotificationCampaign();
37508 ranu 150
        nc.setName(model.getCampaignName());
32253 amit.gupta 151
        nc.setImplementationType("SimpleCampaign");
152
        nc.setImplementationParams(gson.toJson(scp));
37508 ranu 153
        nc.setMessageType(model.getMessageType());
154
        nc.setDocumentId(model.getDocumentId());
32253 amit.gupta 155
        nc.setCreatedTimestamp(LocalDateTime.now());
37508 ranu 156
        nc.setTargetCriteria(gson.toJson(criteria));
157
        nc.setFormat(model.getFormat() != null ? model.getFormat() : NotificationFormat.PUSH);
158
        nc.setCategory(model.getCategory());
159
        nc.setCampaignGroupId(model.getCampaignGroupId());
32253 amit.gupta 160
        notificationCampaignRepository.persist(nc);
37508 ranu 161
        return nc;
162
    }
25822 amit.gupta 163
 
37508 ranu 164
    @Override
165
    public Set<Integer> resolveTargetUserIds(NotificationTargetCriteria criteria) throws ProfitMandiBusinessException {
32253 amit.gupta 166
        Set<Integer> userIds = new HashSet<>();
37508 ranu 167
        if (criteria.getUserIds() != null && !criteria.getUserIds().isEmpty()) {
168
            userIds.addAll(criteria.getUserIds());
32253 amit.gupta 169
        }
37508 ranu 170
        if (criteria.getStateIds() != null && !criteria.getStateIds().isEmpty()) {
171
            List<Integer> fofoIds = fofoStoreRepository.selectByWarehouseIds(criteria.getStateIds()).stream()
33025 amit.gupta 172
                    .map(x -> x.getId()).collect(Collectors.toList());
37508 ranu 173
            if (!fofoIds.isEmpty()) {
33025 amit.gupta 174
                userIds.addAll(userAccountRepository.selectUserIdsByRetailerIds(fofoIds));
175
            }
32253 amit.gupta 176
        }
37508 ranu 177
        if (criteria.getRegionIds() != null && !criteria.getRegionIds().isEmpty()) {
178
            if (criteria.getRegionIds().contains(ALL_PARTNERS_REGION)) {
37051 amit 179
                // "ALL partners" region: partner_region only holds a sentinel, so expand to every partner.
180
                userIds.addAll(fofoStoreRepository.selectAllDtrUserIds());
181
            } else {
37508 ranu 182
                List<Integer> fofoIds = partnerRegionRepository.selectAllByRegionIds(criteria.getRegionIds()).stream()
183
                        .map(x -> x.getFofoId()).collect(Collectors.toList());
184
                if (!fofoIds.isEmpty()) {
37051 amit 185
                    userIds.addAll(userAccountRepository.selectUserIdsByRetailerIds(fofoIds));
186
                }
35207 amit 187
            }
188
        }
37508 ranu 189
        return userIds;
190
    }
29927 amit.gupta 191
 
37508 ranu 192
    private void fanOutToUsers(int campaignId, Set<Integer> userIds, NotificationFormat format) {
193
        LocalDateTime now = LocalDateTime.now();
194
        for (Integer userId : userIds) {
195
            UserCampaign uc = new UserCampaign();
196
            uc.setCampaignId(campaignId);
197
            uc.setUserId(userId);
198
            uc.setPushTimestamp(now);
199
            userCampaignRepository.persist(uc);
200
        }
201
        // FCM/APNs delivery only for PUSH. Pop-ups and Stories are pull-based (mobile fetches on app open),
202
        // so we still write user_campaign rows above but skip pushnotifications.
203
        if (format == null || format == NotificationFormat.PUSH) {
33025 amit.gupta 204
            List<Device> devices = deviceRepository.selectByUserIdAndModifiedTimestamp(new ArrayList<>(userIds),
37508 ranu 205
                    now.minusMonths(1), now);
206
            pushNotification(campaignId, devices);
32253 amit.gupta 207
        }
37508 ranu 208
    }
25822 amit.gupta 209
 
37508 ranu 210
    @Override
211
    public void dispatchScheduled(int scheduleId) throws ProfitMandiBusinessException {
212
        NotificationSchedule ns = notificationScheduleRepository.selectById(scheduleId);
213
        if (ns == null) {
214
            LOGGER.warn("dispatchScheduled: schedule id {} not found", scheduleId);
215
            return;
216
        }
217
        if (ns.getStatus() != ScheduleStatus.SCHEDULED) {
218
            LOGGER.debug("dispatchScheduled: skip id {} in status {}", scheduleId, ns.getStatus());
219
            return;
220
        }
221
        // Atomic claim: only one worker transitions SCHEDULED -> DISPATCHED; others get 0 rows.
222
        int claimed = notificationScheduleRepository.claimForDispatch(scheduleId, LocalDateTime.now());
223
        if (claimed == 0) {
224
            LOGGER.debug("dispatchScheduled: id {} already claimed by another worker", scheduleId);
225
            return;
226
        }
227
        try {
228
            NotificationCampaign campaign = notificationCampaignRepository.selectById(ns.getCampaignId());
229
            if (campaign == null) {
230
                LOGGER.error("dispatchScheduled: campaign {} missing for schedule {}", ns.getCampaignId(), scheduleId);
231
                notificationScheduleRepository.markFailed(scheduleId);
232
                return;
233
            }
234
            NotificationTargetCriteria criteria = campaign.getTargetCriteria() == null
235
                    ? new NotificationTargetCriteria()
236
                    : gson.fromJson(campaign.getTargetCriteria(), NotificationTargetCriteria.class);
237
            Set<Integer> userIds = resolveTargetUserIds(criteria);
238
            if (userIds.isEmpty()) {
239
                LOGGER.info("dispatchScheduled: no users resolved for schedule {} (campaign {})",
240
                        scheduleId, campaign.getId());
241
                return;
242
            }
243
            fanOutToUsers(campaign.getId(), userIds, campaign.getFormat());
244
        } catch (RuntimeException e) {
245
            // Swallow: markFailed must commit, and cron shouldn't abort the batch on one bad row.
246
            LOGGER.error("dispatchScheduled: fan-out failed for schedule {}", scheduleId, e);
247
            notificationScheduleRepository.markFailed(scheduleId);
248
        }
32253 amit.gupta 249
    }
25822 amit.gupta 250
 
32253 amit.gupta 251
    @Override
37508 ranu 252
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Throwable.class)
253
    public int dispatchDueScheduled(int batchSize) throws ProfitMandiBusinessException {
254
        List<Integer> due = notificationScheduleRepository.selectDueScheduledIds(LocalDateTime.now(), batchSize);
255
        if (due.isEmpty()) {
256
            return 0;
257
        }
258
        LOGGER.info("dispatchDueScheduled: dispatching {} scheduled notification(s)", due.size());
259
        for (Integer id : due) {
260
            dispatchScheduled(id);
261
        }
262
        return due.size();
263
    }
264
 
265
    @Override
266
    public boolean cancelSchedule(int scheduleId) {
267
        return notificationScheduleRepository.cancelIfScheduled(scheduleId) == 1;
268
    }
269
 
270
    @Override
271
    public int resolveAudienceSize(NotificationTargetCriteria criteria) throws ProfitMandiBusinessException {
272
        if (criteria == null) return 0;
273
        return resolveTargetUserIds(criteria).size();
274
    }
275
 
276
    @Override
277
    public int addScheduleForCampaign(int campaignId, LocalDateTime scheduledAt) throws ProfitMandiBusinessException {
278
        NotificationCampaign nc = notificationCampaignRepository.selectById(campaignId);
279
        if (nc == null) {
280
            throw new ProfitMandiBusinessException("Campaign not found: ", campaignId, "- campaignId");
281
        }
282
        NotificationSchedule ns = new NotificationSchedule();
283
        ns.setCampaignId(campaignId);
284
        ns.setScheduledTimestamp(scheduledAt);
285
        ns.setStatus(ScheduleStatus.SCHEDULED);
286
        ns.setCreatedTimestamp(LocalDateTime.now());
287
        notificationScheduleRepository.persist(ns);
288
        LOGGER.info("addScheduleForCampaign: campaignId={} scheduledAt={} newScheduleId={}",
289
                campaignId, scheduledAt, ns.getId());
290
        return ns.getId();
291
    }
292
 
293
    @Override
32253 amit.gupta 294
    public void sendNotificationToAll(SendNotificationModel sendNotificationModel) throws ProfitMandiBusinessException {
295
        sendNotificationModel.setUserIds(fofoStoreRepository.selectAllDtrUserIds());
296
        Set<AuthUser> authUsers = new HashSet<>(
297
                csService.getAuthUserByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_RBM));
298
        authUsers.addAll(csService.getAuthUserByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_CATEGORY));
299
        authUsers.addAll(csService.getAuthUserByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_SALES));
34903 ranu 300
        authUsers.addAll(csService.getAuthUserByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_ABM));
32253 amit.gupta 301
        List<String> emailIds = authUsers.stream().map(x -> x.getEmailId()).collect(Collectors.toList());
302
        emailIds.add("devkinandan.lal@smartdukaan.com");
303
        List<User> systemUsers = dtrUserRepository.selectAllByEmailIds(emailIds);
32405 jai.hind 304
        sendNotificationModel.getUserIds()
305
                .addAll(systemUsers.stream().map(x -> x.getId()).collect(Collectors.toList()));
32253 amit.gupta 306
        this.sendNotification(sendNotificationModel);
307
    }
25822 amit.gupta 308
 
32253 amit.gupta 309
    @Override
34550 vikas.jang 310
    public void sendNotificationToSystemUsers(SendNotificationModel sendNotificationModel) throws ProfitMandiBusinessException {
311
        Set<AuthUser> authUsers = new HashSet<>(csService.getAuthUserByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_RBM));
312
        authUsers.addAll(csService.getAuthUserByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_CATEGORY));
313
        authUsers.addAll(csService.getAuthUserByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_SALES));
34903 ranu 314
        authUsers.addAll(csService.getAuthUserByCategoryId(ProfitMandiConstants.TICKET_CATEGORY_ABM));
34550 vikas.jang 315
        List<String> emailIds = authUsers.stream().map(x -> x.getEmailId()).collect(Collectors.toList());
316
        emailIds.add("devkinandan.lal@smartdukaan.com");
317
        List<User> systemUsers = dtrUserRepository.selectAllByEmailIds(emailIds);
318
        sendNotificationModel.setUserIds(systemUsers.stream().map(x -> x.getId()).collect(Collectors.toList()));
319
        this.sendNotification(sendNotificationModel);
320
    }
321
 
322
    @Override
32405 jai.hind 323
    public void sendNotification(int fofoId, String campaignName, MessageType messageType, String title, String message)
324
            throws ProfitMandiBusinessException {
32253 amit.gupta 325
        SendNotificationModel sendNotificationModel = this.getDefaultNotificationModel();
326
        sendNotificationModel.setCampaignName(campaignName);
327
        sendNotificationModel.setMessageType(messageType);
328
        sendNotificationModel.setTitle(title);
329
        sendNotificationModel.setMessage(message);
330
        int userId = userAccountRepository.selectUserIdByRetailerId(fofoId);
331
        sendNotificationModel.setUserIds(Arrays.asList(userId));
332
        sendNotificationModel.setMessageType(MessageType.wallet);
333
        this.sendNotification(sendNotificationModel);
334
    }
25822 amit.gupta 335
 
32253 amit.gupta 336
    public SendNotificationModel getDefaultNotificationModel() {
337
        SendNotificationModel sendNotificationModel = new SendNotificationModel();
338
        sendNotificationModel.setType("url");
35840 vikas 339
        sendNotificationModel.setUrl("https://app.smartdukaan.com/pages/home/notifications");
32253 amit.gupta 340
        sendNotificationModel.setExpiresat(LocalDateTime.now().plusDays(1));
341
        sendNotificationModel.setMessageType(MessageType.notification);
342
        return sendNotificationModel;
343
    }
25822 amit.gupta 344
 
32253 amit.gupta 345
    public void pushNotification(int cid, List<Device> devices) {
29198 manish 346
 
32253 amit.gupta 347
        for (Device device : devices) {
348
            PushNotifications pn = new PushNotifications();
349
            pn.setNotificationCampaignid(cid);
350
            pn.setDeviceId(device.getId());
351
            pn.setUserId(device.getUser_id());
352
            pushNotificationRepository.persist(pn);
353
        }
29198 manish 354
 
32253 amit.gupta 355
    }
29198 manish 356
 
32253 amit.gupta 357
    @Override
34545 vikas.jang 358
    public void sendNotification(int fofoId, String campaignName, MessageType messageType, String title, String message, String url) throws ProfitMandiBusinessException {
32253 amit.gupta 359
        SendNotificationModel sendNotificationModel = this.getDefaultNotificationModel();
360
        sendNotificationModel.setCampaignName(campaignName);
361
        sendNotificationModel.setMessageType(messageType);
362
        sendNotificationModel.setTitle(title);
363
        sendNotificationModel.setMessage(message);
364
        int userId = userAccountRepository.selectUserIdByRetailerId(fofoId);
365
        sendNotificationModel.setUserIds(Arrays.asList(userId));
366
        sendNotificationModel.setMessageType(messageType);
367
        sendNotificationModel.setUrl(url);
368
        this.sendNotification(sendNotificationModel);
369
    }
29198 manish 370
 
37409 vikas 371
    /**
372
     * Invoice PDF, sent as the template's document header.
373
     *
374
     * <p>The upsell wording that used to be built here now lives in the approved template; only the
375
     * recipient's name and the PDF link cross the wire.</p>
376
     */
32253 amit.gupta 377
    @Override
37409 vikas 378
    public boolean sendWhatsappInvoice(int customerId, int fofoId, String invoiceNumber, String whatsAppNo) throws Exception {
379
        if (!shouldSendWhatsappMessage(whatsAppNo)) {
380
            return false;
32874 amit.gupta 381
        }
36064 aman 382
        Customer customer = customerRepository.selectById(customerId);
383
        String mobileNumber = (whatsAppNo != null && !whatsAppNo.isEmpty()) ? whatsAppNo : customer.getMobileNumber();
384
 
385
        String mediaUrl = "https://partners.smartdukaan.com/wa-invoice-send/"
386
                + Base64.getMimeEncoder().encodeToString(invoiceNumber.getBytes(StandardCharsets.UTF_8)) + ".pdf";
387
        String fileName = "INV-" + invoiceNumber.replace("/", "-") + ".pdf";
388
 
389
        LOGGER.info("sendWhatsappInvoice: mobile={} mediaUrl={}", mobileNumber, mediaUrl);
37409 vikas 390
        return botPenguin.send(WhatsappFlow.INVOICE, mobileNumber,
391
                Arrays.asList(customerName(customer), storeName(fofoId)), mediaUrl, fileName,
392
                WhatsappMessageType.DOCUMENT);
36064 aman 393
    }
394
 
395
    @Override
37409 vikas 396
    public boolean sendPaymentLink(String mobile, String paymentLink) throws Exception {
397
        return botPenguin.send(WhatsappFlow.PAYMENT_LINK, mobile,
398
                Collections.singletonList(paymentLink), null, null, null);
32253 amit.gupta 399
    }
25822 amit.gupta 400
 
32253 amit.gupta 401
    @Override
37409 vikas 402
    public boolean sendOfferAnnouncement(String mobile, String offerName, String schemeType, String startDate,
403
                                         String endDate, String imageUrl) throws Exception {
404
        return botPenguin.send(WhatsappFlow.OFFER, mobile,
405
                Arrays.asList(offerName, schemeType, startDate, endDate), imageUrl, null,
406
                WhatsappMessageType.IMAGE);
32253 amit.gupta 407
    }
29927 amit.gupta 408
 
37409 vikas 409
    @Override
410
    public boolean sendBiddingLive(String mobile, List<String> lots, String mediaUrl) throws Exception {
411
        int slots = ProfitMandiConstants.WHATSAPP_TEMPLATE.BIDDING_LIVE.getBodyParamCount();
412
        if (lots.size() > slots) {
413
            LOGGER.warn("sendBiddingLive: {} lots available but the template holds {}; dropping {}",
414
                    lots.size(), slots, lots.subList(slots, lots.size()));
415
        }
416
        List<String> params = new ArrayList<>(lots.subList(0, Math.min(lots.size(), slots)));
417
        // Meta rejects an empty parameter, so unused slots carry a dash rather than "".
418
        while (params.size() < slots) {
419
            params.add("-");
420
        }
421
        return botPenguin.send(WhatsappFlow.BIDDING_LIVE, mobile, params, mediaUrl, null,
422
                WhatsappMessageType.IMAGE);
32253 amit.gupta 423
    }
30025 amit.gupta 424
 
32405 jai.hind 425
    @Override
37409 vikas 426
    public boolean sendLoanDefaultAlert(String mobile, String name, String outstandingAmount) throws Exception {
427
        LOGGER.info("Is Prod - {}", isProd);
428
        if (!isProd) {
429
            return false;
430
        }
431
        return botPenguin.send(WhatsappFlow.LOAN_ALERT, mobile,
432
                Arrays.asList(name, outstandingAmount), null, null, null);
433
    }
434
 
435
    @Override
436
    public boolean sendOrderDelivered(String mobile, String airwayBillNumber) throws Exception {
437
        LOGGER.info("Is Prod - {}", isProd);
438
        if (!isProd) {
439
            return false;
440
        }
441
        return botPenguin.send(WhatsappFlow.ORDER_DELIVERED, mobile,
442
                Collections.singletonList(airwayBillNumber), null, null, null);
443
    }
444
 
445
    @Override
446
    public boolean sendADLDWhatsappMessage(String mobile, String invoiceNumber, String mediaUrl,
447
                                           String fileName) throws Exception {
448
        LOGGER.info("Is Prod - {}", isProd);
449
        if (!isProd) {
450
            return false;
451
        }
452
        return botPenguin.send(WhatsappFlow.ADLD, mobile, Collections.singletonList(invoiceNumber),
453
                mediaUrl, fileName, WhatsappMessageType.DOCUMENT);
454
    }
455
 
456
    @Override
33415 amit.gupta 457
    public boolean shouldSendWhatsappMessage(String mobile) {
37409 vikas 458
        // Normalised rather than concatenated: BotPenguin stores the normalised MSISDN, so for anything
459
        // that is not already a bare ten digits the old "91" + mobile key could never match a stored row
460
        // and the throttle quietly never fired. Identical result for clean input.
37388 vikas 461
        String destAddr = WhatsappNumbers.toMsisdn(mobile);
462
        if (destAddr == null) {
463
            return true;
464
        }
33415 amit.gupta 465
        boolean shouldSend = true;
32508 amit.gupta 466
        List<WhatsappMessage> whatsappMessages = whatsappMessageRepository.selectByDestAddr(destAddr, LocalDate.now());
32405 jai.hind 467
        if (!whatsappMessages.isEmpty()) {
32508 amit.gupta 468
            long failedCount = whatsappMessages.stream().filter(x -> x.getFailed() != null && x.getFailed().equals("FAILED")).collect(Collectors.counting());
32405 jai.hind 469
            if (failedCount >= 2) {
33415 amit.gupta 470
                shouldSend = false;
32405 jai.hind 471
            }
472
        }
33415 amit.gupta 473
        return shouldSend;
32405 jai.hind 474
    }
33715 ranu 475
 
37409 vikas 476
    /**
477
     * The store the purchase was made at, for the invoice template's second variable. Falls back to the
478
     * brand name: an unknown partner id must not stop an invoice going out.
479
     */
480
    private String storeName(int fofoId) {
481
        try {
482
            CustomRetailer retailer = retailerService.getFofoRetailer(fofoId);
483
            String businessName = retailer == null ? null : retailer.getBusinessName();
484
            if (businessName != null && !businessName.trim().isEmpty()) {
485
                return businessName.trim();
486
            }
487
        } catch (Exception e) {
488
            LOGGER.warn("storeName: could not resolve fofoId {}", fofoId, e);
489
        }
490
        return "SmartDukaan";
491
    }
33715 ranu 492
 
37409 vikas 493
    /** Meta greets by name, so an empty first name would render "Hello ,". */
494
    private static String customerName(Customer customer) {
495
        String firstName = customer == null ? null : customer.getFirstName();
496
        return (firstName == null || firstName.trim().isEmpty()) ? "Customer" : firstName.trim();
33715 ranu 497
    }
34545 vikas.jang 498
 
25822 amit.gupta 499
}