Subversion Repositories SmartDukaan

Rev

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

Rev Author Line No. Line
33172 tejus.loha 1
package com.spice.profitmandi.service.order;
2
 
3
import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
4
import com.spice.profitmandi.common.model.BulkOrderModel;
33341 tejus.loha 5
import com.spice.profitmandi.common.model.LineItemModel;
33547 tejus.loha 6
import com.spice.profitmandi.common.model.ProfitMandiConstants;
33341 tejus.loha 7
import com.spice.profitmandi.common.model.TransactionApprovalModel;
33172 tejus.loha 8
import com.spice.profitmandi.common.util.ExcelUtils;
35971 aman 9
import com.spice.profitmandi.common.util.Utils;
33172 tejus.loha 10
import com.spice.profitmandi.dao.cart.CartService;
33341 tejus.loha 11
import com.spice.profitmandi.dao.entity.auth.AuthUser;
34443 vikas.jang 12
import com.spice.profitmandi.dao.entity.catalog.Bid;
34832 ranu 13
import com.spice.profitmandi.dao.entity.catalog.Item;
33172 tejus.loha 14
import com.spice.profitmandi.dao.entity.catalog.TagListing;
34856 ranu 15
import com.spice.profitmandi.dao.entity.fofo.FofoStore;
34566 ranu 16
import com.spice.profitmandi.dao.entity.fofo.LoanTransaction;
37119 ranu 17
import com.spice.profitmandi.dao.entity.onBoarding.LoiForm;
34674 aman.kumar 18
import com.spice.profitmandi.dao.entity.transaction.LineItem;
19
import com.spice.profitmandi.dao.entity.transaction.Order;
20
import com.spice.profitmandi.dao.entity.transaction.Transaction;
21
import com.spice.profitmandi.dao.entity.transaction.TransactionApproval;
35971 aman 22
import com.spice.profitmandi.dao.entity.user.StoreTimelinetb;
23
import com.spice.profitmandi.dao.enumuration.cs.EscalationType;
24
import com.spice.profitmandi.dao.enumuration.dtr.StoreTimeline;
33213 tejus.loha 25
import com.spice.profitmandi.dao.enumuration.transaction.TransactionApprovalStatus;
33172 tejus.loha 26
import com.spice.profitmandi.dao.model.CartItem;
27
import com.spice.profitmandi.dao.model.UserCart;
33341 tejus.loha 28
import com.spice.profitmandi.dao.repository.auth.AuthRepository;
34468 vikas.jang 29
import com.spice.profitmandi.dao.repository.catalog.BidRepository;
37302 amit 30
import com.spice.profitmandi.dao.entity.catalog.Category;
31
import com.spice.profitmandi.dao.entity.user.CartLine;
32
import com.spice.profitmandi.dao.repository.catalog.CategoryRepository;
34832 ranu 33
import com.spice.profitmandi.dao.repository.catalog.ItemRepository;
37302 amit 34
import com.spice.profitmandi.dao.repository.user.CartLineRepository;
34468 vikas.jang 35
import com.spice.profitmandi.dao.repository.catalog.LiquidationRepository;
33172 tejus.loha 36
import com.spice.profitmandi.dao.repository.catalog.TagListingRepository;
34443 vikas.jang 37
import com.spice.profitmandi.dao.repository.cs.CsService;
34856 ranu 38
import com.spice.profitmandi.dao.repository.dtr.FofoStoreRepository;
33172 tejus.loha 39
import com.spice.profitmandi.dao.repository.dtr.UserRepository;
34566 ranu 40
import com.spice.profitmandi.dao.repository.fofo.LoanTransactionRepository;
33341 tejus.loha 41
import com.spice.profitmandi.dao.repository.transaction.OrderRepository;
33338 amit.gupta 42
import com.spice.profitmandi.dao.repository.transaction.SDCreditRequirementRepository;
33213 tejus.loha 43
import com.spice.profitmandi.dao.repository.transaction.TransactionApprovalRepository;
44
import com.spice.profitmandi.dao.repository.transaction.TransactionRepository;
45
import com.spice.profitmandi.dao.repository.user.AddressRepository;
37119 ranu 46
import com.spice.profitmandi.dao.repository.user.LoiFormRepository;
34468 vikas.jang 47
import com.spice.profitmandi.dao.service.BidService;
34832 ranu 48
import com.spice.profitmandi.service.catalog.BrandsService;
34661 ranu 49
import com.spice.profitmandi.service.transaction.BlockLoanIdSanctionId;
33338 amit.gupta 50
import com.spice.profitmandi.service.transaction.SDCreditService;
33172 tejus.loha 51
import com.spice.profitmandi.service.transaction.TransactionService;
52
import com.spice.profitmandi.service.wallet.CommonPaymentService;
53
import com.spice.profitmandi.service.wallet.WalletService;
54
import org.apache.logging.log4j.LogManager;
55
import org.apache.logging.log4j.Logger;
33213 tejus.loha 56
import org.apache.poi.ss.usermodel.Cell;
33172 tejus.loha 57
import org.apache.poi.ss.usermodel.Row;
58
import org.apache.poi.xssf.usermodel.XSSFRow;
59
import org.apache.poi.xssf.usermodel.XSSFSheet;
60
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
61
import org.springframework.beans.factory.annotation.Autowired;
35971 aman 62
import org.springframework.mail.javamail.JavaMailSender;
33213 tejus.loha 63
import org.springframework.stereotype.Service;
33172 tejus.loha 64
import org.springframework.web.multipart.MultipartFile;
65
 
33338 amit.gupta 66
import java.math.BigDecimal;
35956 amit 67
import java.time.LocalDateTime;
35995 aman 68
import java.util.*;
33172 tejus.loha 69
import java.util.stream.Collectors;
70
 
33213 tejus.loha 71
@Service
33172 tejus.loha 72
public class BulkOrderService {
73
    private static final Logger LOGGER = LogManager.getLogger(BulkOrderService.class);
74
    @Autowired
75
    CartService cartService;
76
    @Autowired
77
    TransactionService transactionService;
78
    @Autowired
79
    CommonPaymentService commonPaymentService;
80
    @Autowired
81
    TagListingRepository tagListingRepository;
82
    @Autowired
83
    WalletService walletService;
84
    @Autowired
85
    UserRepository userRepository;
33213 tejus.loha 86
    @Autowired
87
    TransactionRepository transactionRepository;
88
    @Autowired
89
    TransactionApprovalRepository transactionApprovalRepository;
90
    @Autowired
91
    AddressRepository addressRepository;
33341 tejus.loha 92
    @Autowired
93
    OrderRepository orderRepository;
34443 vikas.jang 94
    //TODO:Tejus need to check
33341 tejus.loha 95
    @Autowired
96
    SDCreditRequirementRepository sdCreditRequirementRepository;
33338 amit.gupta 97
    @Autowired
98
    SDCreditService sdCreditService;
34443 vikas.jang 99
    @Autowired
100
    private CsService csService;
101
    @Autowired
34468 vikas.jang 102
    private AuthRepository authRepository;
34443 vikas.jang 103
    @Autowired
34468 vikas.jang 104
    private BidRepository bidRepository;
105
    @Autowired
106
    private BidService bidService;
107
    @Autowired
34832 ranu 108
    ItemRepository itemRepository;
109
    @Autowired
37302 amit 110
    CategoryRepository categoryRepository;
111
    @Autowired
112
    CartLineRepository cartLineRepository;
113
    @Autowired
34832 ranu 114
    private BrandsService brandsService;
115
    @Autowired
34566 ranu 116
    private LoanTransactionRepository loanTransactionRepository;
117
 
118
    @Autowired
34468 vikas.jang 119
    private LiquidationRepository liquidationRepository;
33338 amit.gupta 120
 
34674 aman.kumar 121
    @Autowired
122
    com.spice.profitmandi.dao.repository.user.UserRepository user_userRepository;
33338 amit.gupta 123
 
34856 ranu 124
    @Autowired
125
    FofoStoreRepository fofoStoreRepository;
34674 aman.kumar 126
 
35971 aman 127
    @Autowired
36399 amit 128
    JavaMailSender gmailRelaySender;
34856 ranu 129
 
35971 aman 130
    @Autowired
131
    com.spice.profitmandi.service.user.StoreTimelineTatService storeTimelineTatService;
132
 
133
    @Autowired
134
    com.spice.profitmandi.dao.repository.dtr.PartnerOnBoardingPanelRepository partnerOnBoardingPanelRepository;
135
 
136
    @Autowired
137
    com.spice.profitmandi.dao.repository.user.StoreTimelinetbRepository storeTimelinetbRepository;
138
 
37091 amit 139
    @Autowired
140
    com.spice.profitmandi.service.catalog.ComboService comboService;
35971 aman 141
 
37091 amit 142
    @Autowired
143
    com.spice.profitmandi.dao.repository.cs.PositionRepository positionRepository;
144
 
37119 ranu 145
    @Autowired
146
    LoiFormRepository loiFormRepository;
37091 amit 147
 
37119 ranu 148
 
34468 vikas.jang 149
    public void parseBulkOrders(MultipartFile file, int creatorId) throws Exception {
33172 tejus.loha 150
        XSSFWorkbook myWorkBook = new XSSFWorkbook(file.getInputStream());
151
 
152
        myWorkBook.setMissingCellPolicy(Row.MissingCellPolicy.RETURN_BLANK_AS_NULL);
153
        // Return first sheet from the XLSX workbook
154
        XSSFSheet mySheet = myWorkBook.getSheetAt(0);
155
        LOGGER.info("rowCellNum {}", mySheet.getLastRowNum());
156
        List<BulkOrderModel> bulkOrderModels = new ArrayList<>();
33547 tejus.loha 157
        LOGGER.info("mySheet.getLastRowNum() - {}", mySheet.getLastRowNum());
33172 tejus.loha 158
        for (int rowNumber = 1; rowNumber <= mySheet.getLastRowNum(); rowNumber++) {
159
            XSSFRow row = mySheet.getRow(rowNumber);
33547 tejus.loha 160
            LOGGER.info("Row - {}", row);
161
            if (row != null) {
162
                BulkOrderModel bulkOrderModel = this.createBulkModel(row);
163
                bulkOrderModels.add(bulkOrderModel);
164
            } else {
165
                break;
166
            }
33172 tejus.loha 167
        }
34468 vikas.jang 168
        this.generatePurchaseOrder(bulkOrderModels, creatorId, ProfitMandiConstants.PO_TYPE.MANUAL, ProfitMandiConstants.BID_CRON_ENUM.TODAY);
34443 vikas.jang 169
    }
170
 
34592 vikas.jang 171
    public ProfitMandiConstants.BID_ENUM generatePurchaseOrder(List<BulkOrderModel> bulkOrderModels, int creatorId, ProfitMandiConstants.PO_TYPE type, ProfitMandiConstants.BID_CRON_ENUM scheduleType) throws Exception {
33172 tejus.loha 172
        Map<Integer, List<BulkOrderModel>> fofoBulkOrdersMap = bulkOrderModels.stream().collect(Collectors.groupingBy(x -> x.getFofoId()));
34443 vikas.jang 173
        boolean approvalRequired = false;
34592 vikas.jang 174
        ProfitMandiConstants.BID_ENUM finalBidStatus = ProfitMandiConstants.BID_ENUM.CLOSED;
37091 amit 175
        // Combo enforcement is gated to RBM/SALES creators only (creatorId is constant for the whole file).
176
        boolean enforceCombo = isRbmOrSalesCreator(creatorId);
33172 tejus.loha 177
        for (Map.Entry<Integer, List<BulkOrderModel>> fofoBulkOrderEntry : fofoBulkOrdersMap.entrySet()) {
178
            int fofoId = fofoBulkOrderEntry.getKey();
179
            List<BulkOrderModel> fofoBulkOrderModels = fofoBulkOrderEntry.getValue();
180
            Map<Integer, Long> orderItemCountMap = fofoBulkOrderModels.stream().collect(Collectors.groupingBy(x -> x.getItemId(), Collectors.counting()));
181
 
182
            if (orderItemCountMap.entrySet().stream().filter(x -> x.getValue() > 1).count() > 0) {
183
                throw new ProfitMandiBusinessException("Fofo ID", fofoId, "Duplicate in items");
184
            }
185
 
186
            boolean hasZeroQuantity = fofoBulkOrderModels.stream().filter(x -> x.getQuantity() <= 0).count() > 0;
187
            if (hasZeroQuantity) {
188
                throw new ProfitMandiBusinessException("Item Quantity", "", "Should be greater than 0");
189
            }
190
 
35690 amit 191
            // Batch-fetch per-fofo data once (instead of per-item)
192
            FofoStore fofoStore = fofoStoreRepository.selectByRetailerId(fofoId);
193
 
194
            // Batch-fetch items and tag listings for all items in this fofo's order (2 queries instead of 2N)
195
            Set<Integer> allItemIds = fofoBulkOrderModels.stream().map(BulkOrderModel::getItemId).collect(Collectors.toSet());
196
            Map<Integer, TagListing> tagListingMap = tagListingRepository.selectByItemIds(allItemIds);
197
            Map<Integer, Item> itemMap = itemRepository.selectByIds(allItemIds).stream()
198
                    .collect(Collectors.toMap(Item::getId, item -> item));
199
 
37143 amit 200
            if (!fofoStore.isInternal()) {
201
                List<String> orderBrands = itemMap.values().stream().map(Item::getBrand).distinct()
202
                        .collect(Collectors.toList());
203
                brandsService.validateOrderableBrands(fofoId, orderBrands);
204
            }
205
 
33172 tejus.loha 206
            List<CartItem> cartItems = new ArrayList<>();
33338 amit.gupta 207
            double totalPayableAmount = 0;
34957 amit 208
            BigDecimal totalPayableAmountBD = new BigDecimal(0);
33172 tejus.loha 209
            for (BulkOrderModel fofoBulkOrderModel : fofoBulkOrderModels) {
210
                CartItem cartItem = new CartItem();
211
                cartItem.setQuantity(fofoBulkOrderModel.getQuantity());
212
                cartItem.setItemId(fofoBulkOrderModel.getItemId());
35690 amit 213
                TagListing tagListing = tagListingMap.get(fofoBulkOrderModel.getItemId());
33556 amit.gupta 214
                if (tagListing == null) {
215
                    String message = "Pricing Does not exist for " + fofoBulkOrderModel.getItemId() + "(" + fofoBulkOrderModel.getDescription() + ")";
216
                    throw new ProfitMandiBusinessException(message, message, message);
217
                }
35690 amit 218
                Item item = itemMap.get(fofoBulkOrderModel.getItemId());
219
                if (item == null) {
220
                    throw new ProfitMandiBusinessException("Item not found", fofoBulkOrderModel.getItemId(), "Item does not exist: " + fofoBulkOrderModel.getItemId());
221
                }
33213 tejus.loha 222
                double itemSellingPrice = tagListing.getSellingPrice();
33597 tejus.loha 223
                boolean isActualPrice = fofoBulkOrderModel.getItemPrice() == itemSellingPrice;
224
                boolean isPriceZero = fofoBulkOrderModel.getItemPrice() == 0d;
33213 tejus.loha 225
                double customSellingPrice = fofoBulkOrderModel.getItemPrice();
226
                int itemId = cartItem.getItemId();
33597 tejus.loha 227
                if (isPriceZero || isActualPrice) {
33213 tejus.loha 228
                    cartItem.setSellingPrice(itemSellingPrice);
229
                } else {
33216 tejus.loha 230
                    if (customSellingPrice <= tagListing.getMrp() || customSellingPrice <= tagListing.getMop()) {
33213 tejus.loha 231
                        cartItem.setSellingPrice(customSellingPrice);
34443 vikas.jang 232
                        approvalRequired = true;
33213 tejus.loha 233
 
234
                    } else {
235
                        throw new ProfitMandiBusinessException("Given price is greater than selling price for item Id - ", itemId, " it should be less or equal of DP");
236
                    }
237
 
238
                }
34958 amit 239
                totalPayableAmountBD = totalPayableAmountBD.add(new BigDecimal(String.valueOf(cartItem.getSellingPrice())).multiply(new BigDecimal(cartItem.getQuantity())));
33172 tejus.loha 240
                cartItems.add(cartItem);
241
            }
34957 amit 242
            totalPayableAmount = totalPayableAmountBD.doubleValue();
34501 vikas.jang 243
            Bid bid = null;
244
            if (type.equals(ProfitMandiConstants.PO_TYPE.AUTO)) {
245
                bid = bidRepository.selectById(fofoBulkOrderModels.get(0).getRowIndex());
34592 vikas.jang 246
                approvalRequired = false;
34957 amit 247
                totalPayableAmount = totalPayableAmountBD.doubleValue() - ProfitMandiConstants.BID_CHARGES;
34501 vikas.jang 248
            }
33338 amit.gupta 249
            LOGGER.info("totalAmount of item " + totalPayableAmount);
33172 tejus.loha 250
            double walletAmount = walletService.getWalletAmount(fofoId);
33338 amit.gupta 251
 
252
            BigDecimal creditAvailability = sdCreditService.getAvailableAmount(fofoId);
253
 
254
            double netAmountInHand = creditAvailability.doubleValue() + walletAmount;
33442 tejus.loha 255
            LOGGER.info("netAmountInHand - " + netAmountInHand);
33547 tejus.loha 256
            if (totalPayableAmount > ProfitMandiConstants.MAX_NEGATIVE_WALLET_VALUE && netAmountInHand < totalPayableAmount) {
34468 vikas.jang 257
                if (type.equals(ProfitMandiConstants.PO_TYPE.MANUAL)) {
34695 aman.kumar 258
                    throw new ProfitMandiBusinessException("Skipping order due to insufficient balance for id - ", fofoId, String.valueOf(fofoId));
34443 vikas.jang 259
                } else {
34468 vikas.jang 260
                    if (scheduleType.equals(ProfitMandiConstants.BID_CRON_ENUM.TODAY)) {
34592 vikas.jang 261
                        finalBidStatus = bidService.sendMailToRBM(netAmountInHand, totalPayableAmount, fofoId);
34572 vikas.jang 262
                        LOGGER.info("Skipping order due to insufficient balance for id - "+ fofoId+ " Sending mail to RBM");
263
                        //throw new ProfitMandiBusinessException("Skipping order due to insufficient balance for id - ", fofoId, " ,Sending mail to RBM");
34468 vikas.jang 264
                    } else {
34592 vikas.jang 265
                        finalBidStatus = bidService.cancelYesterdayProcessBid(bid);
34572 vikas.jang 266
                        LOGGER.info("Skipping order due to insufficient balance for id - "+ fofoId+ " Cancelling the BID");
267
                        //throw new ProfitMandiBusinessException("Skipping order due to insufficient balance for id - ", fofoId, " ,Cancelling the BID");
34468 vikas.jang 268
                    }
34443 vikas.jang 269
                }
33172 tejus.loha 270
            }
271
            UserCart userCart = cartService.setCartItems(fofoId, cartItems);
37091 amit 272
            // RBM/SALES bulk orders must satisfy combo requirements: if a combo main is present without its
273
            // required sides, validateCombo throws "Missing required Qty for Combo" (restrict-only; never injects a side).
274
            if (enforceCombo) {
275
                comboService.validateCombo(userCart);
276
            }
33213 tejus.loha 277
            // createtransactionInternally set the value in transaction table
33338 amit.gupta 278
 
37302 amit 279
            // Refurbished (margin scheme) lines are invoiced on their own RF- series, so they must not share a
280
            // transaction with regular lines - one billing group per invoice. Everything above (pricing, combo,
281
            // credit availability) still validates the order as a whole; only the transaction is raised per group.
282
            List<List<CartLine>> cartLineGroups = this.splitCartLinesByMarginScheme(userCart.getCartId());
283
            // A loan settles against an invoice, so each group needs its own. Wallet is consumed in group order,
284
            // so only what a group cannot fund from the balance left to it is borrowed.
285
            List<Integer> groupLoanIds = new ArrayList<>();
286
            double remainingWalletAmount = walletAmount;
287
            for (List<CartLine> groupCartLines : cartLineGroups) {
288
                double groupPayableAmount = this.getPayableAmount(groupCartLines);
289
                double walletUsedByGroup = Math.min(remainingWalletAmount, groupPayableAmount);
290
                double creditAmountRequired = groupPayableAmount - walletUsedByGroup;
291
                remainingWalletAmount -= walletUsedByGroup;
292
                int loanId = 0;
293
                try {
294
                    if (creditAmountRequired > ProfitMandiConstants.MAX_NEGATIVE_WALLET_VALUE) {
295
                        LOGGER.info("Creating new loan for: {}",userCart.getUserId());
296
                        BlockLoanIdSanctionId loan = sdCreditService.createSDDirectOrder(userCart.getUserId(), groupPayableAmount, 0);
297
                        loanId = loan.getLoanId();
298
                    }
299
                } catch (Exception exception){
300
                    if (type.equals(ProfitMandiConstants.PO_TYPE.AUTO)) {
301
                        finalBidStatus = bidService.sendMailToRBM(netAmountInHand, totalPayableAmount, fofoId);
302
                        LOGGER.info("Skipping order due to insufficient balance for id - "+ fofoId+ " Cancelling the BID");
303
                        //throw new ProfitMandiBusinessException("Skipping order unable to create load for id - ", fofoId, " ,Sending mail to RBM");
304
                    }
34492 vikas.jang 305
                }
37302 amit 306
                groupLoanIds.add(loanId);
33338 amit.gupta 307
            }
34443 vikas.jang 308
 
34576 vikas.jang 309
            LOGGER.info("finalBidStatus: {}",finalBidStatus);
34573 vikas.jang 310
            if (finalBidStatus.equals(ProfitMandiConstants.BID_ENUM.CLOSED)) {
36534 aman 311
                // Block all PO creation while a first PO is pending approval for this partner
312
                if (transactionApprovalRepository.hasPendingFirstPoByRetailerId(fofoId)) {
313
                    if (type.equals(ProfitMandiConstants.PO_TYPE.MANUAL)) {
314
                        throw new ProfitMandiBusinessException(
315
                                "First PO is pending approval",
316
                                fofoId,
317
                                "First PO is already pending approval for this partner. Please wait for approval or rejection before creating another order.");
318
                    } else {
319
                        LOGGER.warn("Skipping AUTO PO for fofoId={}: first PO is pending approval", fofoId);
320
                        continue;
321
                    }
322
                }
323
 
36518 aman 324
                // First PO if: no transactions at all, OR all previous first-PO approvals were REJECTED
325
                boolean isFirstPO = !transactionRepository.hasTransactionsByRetailerId(fofoId)
326
                        || transactionApprovalRepository.allFirstPoRejectedByRetailerId(fofoId);
35971 aman 327
 
36518 aman 328
                if (isFirstPO) {
329
                    // Block if a first PO is already pending approval
330
                    if (transactionApprovalRepository.hasPendingFirstPoByRetailerId(fofoId)) {
331
                        throw new ProfitMandiBusinessException(
332
                                "First PO is pending approval",
333
                                fofoId,
334
                                "First PO is already pending approval. Please wait for approval or rejection before creating another order.");
335
                    }
336
 
337
                    // Block first PO if FULL_STOCK_PAYMENT is not done (for LOI-flow partners only)
338
                    if (fofoStore != null && !fofoStore.isInternal() && fofoStore.getCode() != null) {
339
                        com.spice.profitmandi.dao.entity.fofo.PartnerOnBoardingPanel pob =
340
                                partnerOnBoardingPanelRepository.selectByCode(fofoStore.getCode());
341
                        if (pob != null) {
342
                            StoreTimelinetb fspEntry = storeTimelinetbRepository.selectByOnboardingIdAndEvent(
343
                                    pob.getId(), StoreTimeline.FULL_STOCK_PAYMENT);
344
                            if (fspEntry == null) {
345
                                LOGGER.warn("PO creation blocked for fofoId={}, onboardingId={}: FULL_STOCK_PAYMENT not done", fofoId, pob.getId());
346
                                throw new ProfitMandiBusinessException(
347
                                        "Full Stock Payment is required before creating PO",
348
                                        fofoStore.getCode(),
349
                                        "Full Stock Payment must be completed before first PO can be created");
350
                            }
37110 aman 351
 
352
                            // Block first PO until the business agreement e-sign has been verified
353
                            // (by Legal / Gaurav Sharma) — sits between FULL_STOCK_PAYMENT and PO creation.
37119 ranu 354
                            // (by Legal / Gaurav Sharma) — sits between FULL_STOCK_PAYMENT and PO creation.
355
                            LoiForm loiForm = loiFormRepository.selectByOnboardingId(pob.getId());
356
 
357
                            if (loiForm != null) {
358
                                StoreTimelinetb esignEntry = storeTimelinetbRepository.selectByOnboardingIdAndEvent(
359
                                        pob.getId(),
360
                                        StoreTimeline.AGREEMENT_ESIGN
361
                                );
362
 
363
                                if (esignEntry == null) {
364
                                    LOGGER.warn(
365
                                            "PO creation blocked for fofoId={}, onboardingId={}: AGREEMENT_ESIGN not done",
366
                                            fofoId,
367
                                            pob.getId()
368
                                    );
369
 
370
                                    throw new ProfitMandiBusinessException(
371
                                            "Business agreement e-sign is required before creating PO",
372
                                            fofoStore.getCode(),
373
                                            "Business agreement e-sign must be verified before first PO can be created"
374
                                    );
375
                                }
37110 aman 376
                            }
37119 ranu 377
 
35971 aman 378
                        }
379
                    }
380
                }
381
 
35995 aman 382
                // First PO always requires approval
383
                if (isFirstPO) {
384
                    approvalRequired = true;
385
                }
386
 
37302 amit 387
                for (int groupIndex = 0; groupIndex < cartLineGroups.size(); groupIndex++) {
388
                    List<CartLine> groupCartLines = cartLineGroups.get(groupIndex);
389
                    int loanId = groupLoanIds.get(groupIndex);
390
                    double groupPayableAmount = this.getPayableAmount(groupCartLines);
391
 
392
                    LOGGER.info("totalPayableAmount - {}", groupPayableAmount);
393
                    int transactionId = transactionService.createTransactionForCartLines(userCart, groupCartLines, groupPayableAmount, 0);
394
                    //Set here created by
395
                    Transaction transaction = transactionRepository.selectById(transactionId);
396
                    transaction.setCreatedBy(creatorId);
397
                    LOGGER.info("transaction created by {}", transaction.getCreatedBy());
398
                    commonPaymentService.payThroughWallet(transactionId);
399
 
400
                    if (approvalRequired) {
401
                        this.createApproval(transactionId, isFirstPO);
402
                        if (loanId > 0) {
403
                            LoanTransaction loanTransaction = new LoanTransaction();
404
                            loanTransaction.setLoanId(loanId);
405
                            loanTransaction.setTransactionId(transactionId);
406
                            loanTransactionRepository.persist(loanTransaction);
407
                        }
408
                    } else {
409
                        transactionService.processTransaction(transactionId, loanId);
34573 vikas.jang 410
                    }
35971 aman 411
 
37302 amit 412
                    // Send approval email and track timeline for first PO
413
                    if (isFirstPO) {
414
                        try {
415
                            sendFirstPOApprovalEmail(fofoStore, transactionId, groupPayableAmount, creatorId);
416
                        } catch (Exception e) {
417
                            LOGGER.error("Failed to send first PO approval email for fofoId: " + fofoId, e);
418
                        }
419
                        // Track PO_CREATION on timeline for first PO (direct call like BILLING — bypasses BLOCKER_MAP
420
                        // since the PO is actually being created, the timeline must record it)
421
                        try {
422
                            if (fofoStore != null && fofoStore.getCode() != null) {
423
                                com.spice.profitmandi.dao.entity.fofo.PartnerOnBoardingPanel pob =
424
                                        partnerOnBoardingPanelRepository.selectByCode(fofoStore.getCode());
425
                                if (pob != null) {
426
                                    storeTimelineTatService.createStoreTimelinetb(pob.getId(), StoreTimeline.PO_CREATION);
427
                                }
35971 aman 428
                            }
37302 amit 429
                        } catch (Exception e) {
430
                            LOGGER.error("Failed to track PO_CREATION timeline for fofoId: " + fofoId, e);
35971 aman 431
                        }
432
                    }
433
                }
33213 tejus.loha 434
            }
435
 
33172 tejus.loha 436
        }
34592 vikas.jang 437
        return finalBidStatus;
33172 tejus.loha 438
    }
439
 
37091 amit 440
    /** True if the bulk-order creator holds an RBM or SALES position — the only creators combo enforcement applies to. Fail-open (returns false) if positions can't be resolved. */
441
    private boolean isRbmOrSalesCreator(int creatorId) {
442
        try {
443
            return positionRepository.selectPositionByAuthId(creatorId).stream()
444
                    .anyMatch(p -> p.getCategoryId() == ProfitMandiConstants.TICKET_CATEGORY_RBM
445
                            || p.getCategoryId() == ProfitMandiConstants.TICKET_CATEGORY_SALES);
446
        } catch (Exception e) {
447
            LOGGER.warn("Combo-enforce gate: could not resolve positions for creator {} - {}", creatorId, e.getMessage());
448
            return false;
449
        }
450
    }
451
 
37302 amit 452
    /**
453
     * Splits the cart into billing groups: regular lines and margin-scheme (refurbished) lines. Returns one
454
     * group when the cart is homogeneous - which is the common case - so a single-kind bulk order behaves
455
     * exactly as it did before. Lines whose item or category cannot be resolved stay with the regular group.
456
     */
457
    private List<List<CartLine>> splitCartLinesByMarginScheme(int cartId) throws ProfitMandiBusinessException {
458
        List<CartLine> cartLines = cartLineRepository.selectAllByCart(cartId);
459
        Set<Integer> itemIds = cartLines.stream().map(CartLine::getItemId).collect(Collectors.toSet());
460
        Map<Integer, Item> itemMap = itemRepository.selectByIds(itemIds).stream()
461
                .collect(Collectors.toMap(Item::getId, x -> x));
462
        List<Integer> categoryIds = itemMap.values().stream().map(Item::getCategoryId).distinct().collect(Collectors.toList());
463
        Map<Integer, Category> categoryMap = categoryRepository.selectByIds(categoryIds).stream()
464
                .collect(Collectors.toMap(Category::getId, x -> x));
465
 
466
        List<CartLine> regularCartLines = new ArrayList<>();
467
        List<CartLine> marginSchemeCartLines = new ArrayList<>();
468
        for (CartLine cartLine : cartLines) {
469
            Item item = itemMap.get(cartLine.getItemId());
470
            Category category = (item == null) ? null : categoryMap.get(item.getCategoryId());
471
            if (category != null && category.isMarginOnly()) {
472
                marginSchemeCartLines.add(cartLine);
473
            } else {
474
                regularCartLines.add(cartLine);
475
            }
476
        }
477
 
478
        List<List<CartLine>> cartLineGroups = new ArrayList<>();
479
        if (!regularCartLines.isEmpty()) {
480
            cartLineGroups.add(regularCartLines);
481
        }
482
        if (!marginSchemeCartLines.isEmpty()) {
483
            cartLineGroups.add(marginSchemeCartLines);
484
        }
485
        return cartLineGroups;
486
    }
487
 
488
    /**
489
     * Cart value of a billing group, matching the rule createTransactionForWarehouse validates against - the
490
     * 0.01 carry bag is excluded - so the amount passed in always reconciles with the lines it is passed with.
491
     */
492
    private double getPayableAmount(List<CartLine> cartLines) {
493
        BigDecimal payableAmount = new BigDecimal(0);
494
        for (CartLine cartLine : cartLines) {
495
            if (cartLine.getItemId() == ProfitMandiConstants.ITEM_CARRY_BAG
496
                    && Utils.compareDouble(cartLine.getActualPrice(), 0.01) == 0) {
497
                continue;
498
            }
499
            payableAmount = payableAmount.add(BigDecimal.valueOf(cartLine.getActualPrice())
500
                    .multiply(BigDecimal.valueOf(cartLine.getQuantity())));
501
        }
502
        return payableAmount.doubleValue();
503
    }
504
 
35995 aman 505
    public void createApproval(int transactionId, boolean firstPo) {
33213 tejus.loha 506
        TransactionApproval transactionApproval = new TransactionApproval();
507
        transactionApproval.setId(transactionId);
508
        transactionApproval.setStatus(TransactionApprovalStatus.PENDING);
35995 aman 509
        transactionApproval.setFirstPo(firstPo);
33213 tejus.loha 510
        transactionApprovalRepository.persist(transactionApproval);
511
    }
512
 
33172 tejus.loha 513
    private BulkOrderModel createBulkModel(XSSFRow row) throws ProfitMandiBusinessException {
514
        BulkOrderModel bulkOrderModel = new BulkOrderModel();
515
        int i = 0;
516
        bulkOrderModel.setRowIndex(row.getRowNum());
517
        try {
33696 amit.gupta 518
            Cell partnerName = row.getCell(i++);
519
            if (partnerName == null)
33213 tejus.loha 520
                bulkOrderModel.setPartnerName("");
521
            else
33696 amit.gupta 522
                bulkOrderModel.setPartnerName(partnerName.getStringCellValue().trim());
33213 tejus.loha 523
            Cell description = row.getCell(i++);
524
            if (description == null)
525
                bulkOrderModel.setDescription("");
526
            else
527
                bulkOrderModel.setDescription(description.getStringCellValue().trim());
528
 
33172 tejus.loha 529
            bulkOrderModel.setFofoId((int) row.getCell(i++).getNumericCellValue());
530
            bulkOrderModel.setItemId((int) row.getCell(i++).getNumericCellValue());
33213 tejus.loha 531
            bulkOrderModel.setItemPrice(row.getCell(i++).getNumericCellValue());
33172 tejus.loha 532
            bulkOrderModel.setQuantity((int) row.getCell(i++).getNumericCellValue());
533
        } catch (Throwable e) {
534
            LOGGER.info(e.getCause());
33213 tejus.loha 535
            throw new ProfitMandiBusinessException("Field", "Field at row - " + row.getRowNum() + ", column - " + (i - 1), "Invalid field value at - " + ExcelUtils.toAlphabet(i - 1) + (row.getRowNum() + 1) + ", " + ExcelUtils.getCellValue(row.getCell(i - 1)));
33172 tejus.loha 536
        }
537
        LOGGER.info(bulkOrderModel);
538
        return bulkOrderModel;
539
    }
540
 
33341 tejus.loha 541
    // create model for transaction Approval so that finance team see all order and approve
542
    public List<TransactionApprovalModel> getAllPendingTransactionApproval() throws ProfitMandiBusinessException {
543
        List<TransactionApproval> transactionApprovals = transactionApprovalRepository.selectAllPending();
544
        LOGGER.info("list of Approval transaction Id " + transactionApprovals);
545
        List<TransactionApprovalModel> approvalModelList = new ArrayList<>();
546
        for (TransactionApproval transactionApproval : transactionApprovals) {
547
            List<Order> orderList = orderRepository.selectAllByTransactionId(transactionApproval.getId());
548
            Transaction transaction = transactionRepository.selectById(transactionApproval.getId());
549
            List<LineItemModel> lineItemModelList = new ArrayList<>();
35995 aman 550
 
551
            // Fetch stock availability for all items in this order
552
            Map<Integer, List<com.spice.profitmandi.model.WarehouseItemQtyModel>> stockMap = Collections.emptyMap();
553
            try {
554
                int retailerId = transaction.getRetailerId();
555
                FofoStore fofoStore = fofoStoreRepository.selectByRetailerId(retailerId);
556
                if (fofoStore != null) {
557
                    List<Integer> itemIds = orderList.stream()
558
                            .map(o -> o.getLineItem().getItemId())
559
                            .collect(java.util.stream.Collectors.toList());
560
                    stockMap = orderRepository.getItemAvailability(fofoStore.getWarehouseId(), itemIds);
561
                }
562
            } catch (Exception e) {
563
                LOGGER.error("Failed to fetch stock availability for transactionId: {}", transactionApproval.getId(), e);
564
            }
565
 
33341 tejus.loha 566
            for (Order order : orderList) {
567
                LineItem lineItem = order.getLineItem();
568
                LineItemModel lineItemModel = new LineItemModel();
569
                lineItemModel.setItemId(lineItem.getItemId());
570
                lineItemModel.setItemName(lineItem.getItem().getItemDescription());
571
                lineItemModel.setItemQuantity(lineItem.getQuantity());
572
                lineItemModel.setSellingPrice(lineItem.getUnitPrice());
573
                lineItemModel.setDp(tagListingRepository.selectByItemId(lineItem.getItemId()).getSellingPrice());
35995 aman 574
                // Set available stock (show 0 if negative — negative means over-committed)
575
                List<com.spice.profitmandi.model.WarehouseItemQtyModel> itemStock = stockMap.get(lineItem.getItemId());
576
                if (itemStock != null && !itemStock.isEmpty()) {
577
                    int totalAvailable = itemStock.stream().mapToInt(s -> s.getNetAvailability()).sum();
578
                    lineItemModel.setAvailableStock(Math.max(0, totalAvailable));
579
                }
33341 tejus.loha 580
                lineItemModelList.add(lineItemModel);
581
            }
582
            AuthUser authUser = authRepository.selectById(transaction.getCreatedBy());
583
            TransactionApprovalModel transactionApprovalModel = new TransactionApprovalModel();
584
            String retailerName = " ";
585
            retailerName = orderList.get(0).getRetailerName();
586
            transactionApprovalModel.setRetailerName(retailerName);
34573 vikas.jang 587
            if (authUser == null) {
588
                transactionApprovalModel.setCreatedBy(retailerName);
589
            } else {
590
                transactionApprovalModel.setCreatedBy(authUser.getFullName());
591
            }
33341 tejus.loha 592
            transactionApprovalModel.setCreatedOn(transaction.getCreateTimestamp());
593
            transactionApprovalModel.setTransactionId(transactionApproval.getId());
594
            transactionApprovalModel.setLineItemModels(lineItemModelList);
35995 aman 595
            transactionApprovalModel.setFirstPo(transactionApproval.isFirstPo());
33341 tejus.loha 596
            approvalModelList.add(transactionApprovalModel);
33172 tejus.loha 597
 
33341 tejus.loha 598
        }
599
        return approvalModelList;
600
    }
601
 
35956 amit 602
    public List<TransactionApprovalModel> getBulkOrderApprovalReport(LocalDateTime startDate, LocalDateTime endDate) throws ProfitMandiBusinessException {
603
        List<TransactionApproval> transactionApprovals = transactionApprovalRepository.selectAllByDateRange(startDate, endDate);
604
        LOGGER.info("Approval report: found {} records", transactionApprovals.size());
605
        List<TransactionApprovalModel> approvalModelList = new ArrayList<>();
606
        for (TransactionApproval transactionApproval : transactionApprovals) {
607
            List<Order> orderList = orderRepository.selectAllByTransactionId(transactionApproval.getId());
608
            Transaction transaction = transactionRepository.selectById(transactionApproval.getId());
609
            List<LineItemModel> lineItemModelList = new ArrayList<>();
610
            for (Order order : orderList) {
611
                LineItem lineItem = order.getLineItem();
612
                LineItemModel lineItemModel = new LineItemModel();
613
                lineItemModel.setItemId(lineItem.getItemId());
614
                lineItemModel.setItemName(lineItem.getItem().getItemDescription());
615
                lineItemModel.setItemQuantity(lineItem.getQuantity());
616
                lineItemModel.setSellingPrice(lineItem.getUnitPrice());
617
                lineItemModel.setDp(tagListingRepository.selectByItemId(lineItem.getItemId()).getSellingPrice());
618
                lineItemModelList.add(lineItemModel);
619
            }
620
            AuthUser authUser = authRepository.selectById(transaction.getCreatedBy());
621
            TransactionApprovalModel model = new TransactionApprovalModel();
622
            String retailerName = orderList.isEmpty() ? "" : orderList.get(0).getRetailerName();
623
            model.setRetailerName(retailerName);
624
            if (authUser == null) {
625
                model.setCreatedBy(retailerName);
626
            } else {
627
                model.setCreatedBy(authUser.getFullName());
628
            }
629
            model.setCreatedOn(transaction.getCreateTimestamp());
630
            model.setTransactionId(transactionApproval.getId());
631
            model.setLineItemModels(lineItemModelList);
632
            model.setStatus(transactionApproval.getStatus().name());
633
            model.setApprovedBy(transactionApproval.getApprovedBy());
634
            model.setApprovedOn(transactionApproval.getApprovedOn());
635
            model.setRemark(transactionApproval.getRemark());
35995 aman 636
            model.setFirstPo(transactionApproval.isFirstPo());
35956 amit 637
            approvalModelList.add(model);
638
        }
639
        return approvalModelList;
640
    }
641
 
35971 aman 642
    private void sendFirstPOApprovalEmail(FofoStore fofoStore, int transactionId, double totalAmount, int creatorId) throws Exception {
643
        String partnerName = "Partner";
644
        if (fofoStore.getUserAddress() != null && fofoStore.getUserAddress().getName() != null) {
645
            partnerName = fofoStore.getUserAddress().getName();
646
        }
647
        String storeCode = fofoStore.getCode() != null ? fofoStore.getCode() : "";
648
 
649
        String createdByName = "";
650
        if (creatorId > 0) {
651
            AuthUser creator = authRepository.selectById(creatorId);
652
            if (creator != null) {
653
                createdByName = creator.getFullName();
654
            }
655
        }
656
 
657
        String subject = "First PO Created - Approval Required - " + partnerName + " (" + storeCode + ")";
658
 
659
        StringBuilder sb = new StringBuilder();
660
        sb.append("<html><body>");
661
        sb.append("<p>Dear Team,</p>");
662
        sb.append("<p>The <strong>first Purchase Order</strong> has been created for the below partner. Please review and approve.</p><br/>");
663
        sb.append("<table style='border:1px solid black; border-collapse: collapse;'>");
664
        sb.append("<tbody>");
665
        sb.append("<tr>");
666
        sb.append("<th style='border:1px solid black; padding: 5px;'>Partner Name</th>");
667
        sb.append("<th style='border:1px solid black; padding: 5px;'>Store Code</th>");
668
        sb.append("<th style='border:1px solid black; padding: 5px;'>Transaction ID</th>");
669
        sb.append("<th style='border:1px solid black; padding: 5px;'>Total Amount</th>");
670
        sb.append("<th style='border:1px solid black; padding: 5px;'>Created By</th>");
671
        sb.append("</tr>");
672
        sb.append("<tr>");
673
        sb.append("<td style='border:1px solid black; padding: 5px;'>").append(partnerName).append("</td>");
674
        sb.append("<td style='border:1px solid black; padding: 5px;'>").append(storeCode).append("</td>");
675
        sb.append("<td style='border:1px solid black; padding: 5px;'>").append(transactionId).append("</td>");
676
        sb.append("<td style='border:1px solid black; padding: 5px;'>").append(String.format("%.2f", totalAmount)).append("</td>");
677
        sb.append("<td style='border:1px solid black; padding: 5px;'>").append(createdByName).append("</td>");
678
        sb.append("</tr>");
679
        sb.append("</tbody></table>");
680
        sb.append("<br/><p>Please approve this order from the <strong>Transaction Approvals</strong> panel.</p>");
681
        sb.append("<br/><p>Regards,<br/>Smart Dukaan</p>");
682
        sb.append("</body></html>");
683
 
684
        // Send to Sales L3
685
        List<AuthUser> salesL3Users = csService.getAuthUserByCategoryId(
36613 aman 686
                ProfitMandiConstants.TICKET_CATEGORY_SALES, EscalationType.L4);
35971 aman 687
 
688
        List<String> sendTo = new ArrayList<>();
689
        if (!salesL3Users.isEmpty()) {
690
            sendTo.addAll(salesL3Users.stream().map(AuthUser::getEmailId).collect(Collectors.toList()));
691
        }
692
 
693
        if (!sendTo.isEmpty()) {
694
            String[] emailArray = sendTo.toArray(new String[0]);
36936 vikas 695
            String[] bccArray = {"tarun.verma@smartdukaan.com"};
36399 amit 696
            Utils.sendMailWithAttachments(gmailRelaySender, emailArray, null, bccArray, subject, sb.toString(), true);
35971 aman 697
            LOGGER.info("First PO approval email sent for fofoId: {} transactionId: {}", fofoStore.getId(), transactionId);
698
        }
699
    }
700
 
33172 tejus.loha 701
}