Subversion Repositories SmartDukaan

Rev

Blame | Last modification | View Log | RSS feed

package com.spice.profitmandi.service.user;

import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
import com.spice.profitmandi.common.model.CustomRetailer;
import com.spice.profitmandi.common.model.ProfitMandiConstants;
import com.spice.profitmandi.common.util.Utils;
import com.spice.profitmandi.dao.entity.auth.AuthUser;
import com.spice.profitmandi.dao.entity.cs.Position;
import com.spice.profitmandi.dao.entity.dtr.Document;
import com.spice.profitmandi.dao.entity.fofo.FofoStore;
import com.spice.profitmandi.dao.entity.fofo.StoreClosure;
import com.spice.profitmandi.dao.enumuration.cs.EscalationType;
import com.spice.profitmandi.dao.enumuration.fofo.StoreClosureReason;
import com.spice.profitmandi.dao.repository.auth.AuthRepository;
import com.spice.profitmandi.dao.repository.cs.CsService;
import com.spice.profitmandi.dao.repository.cs.PositionRepository;
import com.spice.profitmandi.dao.repository.dtr.DocumentRepository;
import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;
import com.spice.profitmandi.dao.repository.fofo.StoreClosureRepository;
import com.spice.profitmandi.service.mail.MailOutboxService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Service;

import java.io.File;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Permanent partner store closure. A closure needs a reason, a remark and the approval mail
 * (uploaded via /document-upload), and queues a closure report with the approval attached to the partner's
 * Sales L2, the top three Sales levels, the top Accounts level and the closer.
 */
@Service
public class StoreClosureService {

        private static final Logger LOGGER = LogManager.getLogger(StoreClosureService.class);


        private static final DateTimeFormatter REPORT_TIME_FORMAT = DateTimeFormatter.ofPattern("dd-MMM-yyyy hh:mm a");

        @Autowired
        private FofoStoreRepository fofoStoreRepository;

        @Autowired
        private StoreClosureRepository storeClosureRepository;

        @Autowired
        private DocumentRepository documentRepository;

        @Autowired
        private PositionRepository positionRepository;

        @Autowired
        private AuthRepository authRepository;

        @Autowired
        private CsService csService;

        @Autowired
        private RetailerService retailerService;

        @Autowired
        private MailOutboxService mailOutboxService;

        public void closeStore(int fofoId, String storeCode, StoreClosureReason reason, String remark,
                                                   int approvalDocumentId, String closedBy) throws ProfitMandiBusinessException {
                StoreAccess.check(StoreAccess.CLOSE, closedBy, "close store");
                FofoStore fofoStore = fofoStoreRepository.selectByRetailerId(fofoId);
                if (fofoStore.isClosed()) {
                        throw new ProfitMandiBusinessException("Store Closed", "Store Already Closed",
                                        "This store is already permanently closed");
                }
                if (storeCode == null || !storeCode.equals(fofoStore.getCode())) {
                        throw new ProfitMandiBusinessException("Invalid Store Code", "Store Code Mismatch",
                                        "Please type the correct store code to confirm closure");
                }
                if (reason == null) {
                        throw new ProfitMandiBusinessException("Closure Reason", "Reason Missing",
                                        "Please select the reason for closing the store");
                }
                if (remark == null || remark.trim().isEmpty()) {
                        throw new ProfitMandiBusinessException("Closure Remark", "Remark Missing",
                                        "Please describe why the store is being closed");
                }
                if (remark.trim().length() > 1000) {
                        throw new ProfitMandiBusinessException("Closure Remark", "Remark Too Long",
                                        "Please keep the remark within 1000 characters");
                }
                Document approvalDocument = approvalDocumentId > 0 ? documentRepository.selectById(approvalDocumentId) : null;
                if (approvalDocument == null) {
                        throw new ProfitMandiBusinessException("Approval Mail", "Approval Missing",
                                        "Please attach the approval mail for this closure");
                }
                File approvalFile = new File(approvalDocument.getPath(), approvalDocument.getName());
                if (!approvalFile.isFile()) {
                        throw new ProfitMandiBusinessException("Approval Mail", "Approval File Missing",
                                        "The uploaded approval mail could not be found, please upload it again");
                }

                LocalDateTime now = LocalDateTime.now();
                fofoStore.setActive(false);
                fofoStore.setClosed(true);
                fofoStore.setClosedTimestamp(now);

                StoreClosure storeClosure = new StoreClosure();
                storeClosure.setFofoId(fofoId);
                storeClosure.setReason(reason);
                storeClosure.setRemark(remark.trim());
                storeClosure.setApprovalDocumentId(approvalDocumentId);
                storeClosure.setClosedBy(closedBy);
                storeClosure.setCreateTimestamp(now);
                storeClosureRepository.persist(storeClosure);
                LOGGER.info("Store {} closed permanently by {} - {}", fofoStore.getCode(), closedBy, storeClosure);

                sendClosureReport(fofoStore, storeClosure, approvalDocument, approvalFile);
        }

        private void sendClosureReport(FofoStore fofoStore, StoreClosure storeClosure, Document approvalDocument,
                                                                   File approvalFile) throws ProfitMandiBusinessException {
                CustomRetailer retailer = retailerService.getFofoRetailer(fofoStore.getId());

                List<AuthUser> salesL2 = csService.getAuthUsersByPartnerIdAndCategory(fofoStore.getId(),
                                ProfitMandiConstants.TICKET_CATEGORY_SALES, EscalationType.L2);
                Set<String> recipients = new LinkedHashSet<>();
                salesL2.stream().map(AuthUser::getEmailId).filter(Objects::nonNull).forEach(recipients::add);
                recipients.addAll(topLevelEmails(ProfitMandiConstants.TICKET_CATEGORY_SALES, 3));
                recipients.addAll(topLevelEmails(ProfitMandiConstants.TICKET_CATEGORY_ACCOUNTS, 1));
                recipients.add(storeClosure.getClosedBy());

                String businessName = retailer.getBusinessName();
                String city = retailer.getAddress() == null ? "-" : retailer.getAddress().getCity() + ", " + retailer.getAddress().getState();
                String subject = "Store Closure Report - " + fofoStore.getCode() + " - " + businessName;
                StringBuilder body = new StringBuilder();
                body.append("Dear Team,\n\nThe following partner store has been permanently closed.\n\n");
                body.append("Partner       : ").append(businessName).append(" (Partner Id ").append(fofoStore.getId()).append(")\n");
                body.append("Store Code    : ").append(fofoStore.getCode()).append("\n");
                body.append("GSTIN         : ").append(Optional.ofNullable(retailer.getGstNumber()).orElse("-")).append("\n");
                body.append("Location      : ").append(city).append("\n");
                body.append("Sales L2      : ").append(salesL2.isEmpty() ? "-" :
                                salesL2.stream().map(AuthUser::getFullName).collect(Collectors.joining(", "))).append("\n");
                body.append("Closed By     : ").append(storeClosure.getClosedBy()).append("\n");
                body.append("Closed On     : ").append(storeClosure.getCreateTimestamp().format(REPORT_TIME_FORMAT)).append("\n");
                body.append("Reason        : ").append(storeClosure.getReason().getLabel()).append("\n");
                body.append("Remark        : ").append(storeClosure.getRemark()).append("\n\n");
                body.append("The approval mail for this closure is attached.\n\nRegards,\nSmartDukaan");

                String attachmentName = approvalDocument.getDisplayName() != null ? approvalDocument.getDisplayName() : approvalDocument.getName();
                mailOutboxService.queueMailWithAttachments(recipients.toArray(new String[0]), null, subject, body.toString(),
                                "StoreClosureService.sendClosureReport", new Utils.Attachment(attachmentName, new FileSystemResource(approvalFile)));
        }

        /**
         * Emails of active auth users holding the highest {@code levels} distinct escalation levels held by active users
         * in a position category, resolved live so role changes in cs.position flow through without a code change.
         */
        private Set<String> topLevelEmails(int categoryId, int levels) {
                List<Position> positions = positionRepository.selectPositionByCategoryId(categoryId).stream()
                                .filter(x -> x.getAuthUserId() > 0 && x.getEscalationType() != null).collect(Collectors.toList());
                if (positions.isEmpty()) {
                        return new HashSet<>();
                }
                Map<Integer, AuthUser> activeUsers = authRepository.selectByIds(positions.stream().map(Position::getAuthUserId)
                                                .distinct().collect(Collectors.toList())).stream().filter(x -> Boolean.TRUE.equals(x.getActive()))
                                .collect(Collectors.toMap(AuthUser::getId, x -> x));
                List<Position> activePositions = positions.stream().filter(x -> activeUsers.containsKey(x.getAuthUserId()))
                                .collect(Collectors.toList());
                List<EscalationType> topLevels = activePositions.stream().map(Position::getEscalationType).distinct()
                                .sorted(Comparator.reverseOrder()).limit(levels).collect(Collectors.toList());
                return activePositions.stream().filter(x -> topLevels.contains(x.getEscalationType()))
                                .map(x -> activeUsers.get(x.getAuthUserId()).getEmailId()).filter(Objects::nonNull)
                                .map(String::toLowerCase).collect(Collectors.toSet());
        }
}