Subversion Repositories SmartDukaan

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

package com.spice.profitmandi.service.pricing;

import com.spice.profitmandi.common.exception.ProfitMandiBusinessException;
import com.spice.profitmandi.common.model.GrnPendingDataModel;
import com.spice.profitmandi.common.util.FormattingUtils;
import com.spice.profitmandi.dao.entity.fofo.InventoryItem;
import com.spice.profitmandi.dao.entity.transaction.PriceDrop;
import com.spice.profitmandi.dao.entity.transaction.PriceDropIMEI;
import com.spice.profitmandi.dao.enumuration.transaction.PriceDropImeiStatus;
import com.spice.profitmandi.dao.repository.fofo.InventoryItemRepository;
import com.spice.profitmandi.dao.repository.transaction.OrderRepository;
import com.spice.profitmandi.dao.repository.transaction.PriceDropIMEIRepository;
import com.spice.profitmandi.dao.repository.transaction.PriceDropRepository;
import com.spice.profitmandi.service.wallet.WalletService;
import in.shop2020.model.v1.order.WalletReferenceType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Shared unit of work for price-hike deduction. The core {@link #deductPartnerUnits} (no transaction
 * of its own — runs in the caller's) is used by both the GRN hook (inside the GRN transaction) and
 * the on-demand endpoint. The endpoint path additionally uses the REQUIRES_NEW methods here so each
 * retailer is isolated.
 */
@Component
public class PriceHikeWorker {

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

    @Autowired
    private PriceDropRepository priceDropRepository;
    @Autowired
    private PriceDropIMEIRepository priceDropIMEIRepository;
    @Autowired
    private InventoryItemRepository inventoryItemRepository;
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private WalletService walletService;

    /**
     * Idempotent create-APPROVED-rows + wallet debit for one retailer's serials against one hike.
     * Runs in the caller's transaction. Serials already recorded for this hike are skipped.
     */
    public void deductPartnerUnits(PriceDrop hike, int retailerId, Map<String, Integer> serialToInventoryItemId)
            throws ProfitMandiBusinessException {
        if (serialToInventoryItemId.isEmpty()) {
            return;
        }
        Set<String> existing = priceDropIMEIRepository.selectByPriceDropId(hike.getId()).stream()
                .filter(x -> x.getPartnerId() == retailerId)
                .map(PriceDropIMEI::getImei).collect(Collectors.toSet());
        Map<String, Integer> fresh = serialToInventoryItemId.entrySet().stream()
                .filter(e -> !existing.contains(e.getKey()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a));
        if (fresh.isEmpty()) {
            return;
        }
        LocalDateTime now = LocalDateTime.now();
        for (Map.Entry<String, Integer> e : fresh.entrySet()) {
            PriceDropIMEI row = new PriceDropIMEI();
            row.setPriceDropId(hike.getId());
            row.setPartnerId(retailerId);
            row.setImei(e.getKey());
            row.setStatus(PriceDropImeiStatus.APPROVED);
            row.setInventoryItemId(e.getValue() == null ? 0 : e.getValue());
            row.setCreditTimestamp(now);
            row.setUpdateTimestamp(now);
            priceDropIMEIRepository.persist(row);
        }
        // amount is negative for a hike, so amount * count reduces the wallet balance (a debit).
        String reason = MessageFormat.format("Price Hike recovery of Rs.{0} per unit. Total {1} item(s)",
                FormattingUtils.formatDecimal(Math.abs(hike.getDropAmount())), fresh.size());
        walletService.addAmountToWallet(retailerId, hike.getId(), WalletReferenceType.PRICE_DROP, reason,
                hike.getAmount() * fresh.size(), hike.getAffectedOn());
        LOGGER.info("Price hike {} — debited retailer {} for {} unit(s)", hike.getId(), retailerId, fresh.size());
    }

    /** Endpoint path: eligible (gap-billed) serials for a hike, grouped by retailer. Own transaction. */
    @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
    public Map<Integer, List<String>> loadEligibleUnits(int priceDropId) throws ProfitMandiBusinessException {
        PriceDrop pd = priceDropRepository.selectById(priceDropId);
        if (pd == null || pd.getAmount() >= 0) {
            return Collections.emptyMap();
        }
        List<GrnPendingDataModel> billed = orderRepository.selectAllOrdersByBilledDateCatalogId(
                pd.getCatalogItemId(), pd.getAffectedOn(), pd.getCreatedOn());
        return billed.stream()
                .filter(x -> x.getSerialNumber() != null && x.getRetailerId() > 0)
                .collect(Collectors.groupingBy(GrnPendingDataModel::getRetailerId,
                        Collectors.mapping(GrnPendingDataModel::getSerialNumber, Collectors.toList())));
    }

    /** Endpoint path: one retailer, its own transaction, so a single retailer's failure is isolated. */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void deductForRetailer(int priceDropId, int retailerId, List<String> serials)
            throws ProfitMandiBusinessException {
        PriceDrop hike = priceDropRepository.selectById(priceDropId);
        if (hike == null || hike.getAmount() >= 0) {
            return;
        }
        Set<String> serialSet = new HashSet<>(serials);
        Map<String, Integer> serialToInventoryItemId = new HashMap<>();
        for (String serial : serialSet) {
            serialToInventoryItemId.put(serial, 0); // 0 until GRN'd; the DN backfill fills it in later
        }
        // Set the real inventory_item_id for any serial already GRN'd (present in inventory).
        inventoryItemRepository.selectByFofoIdSerialNumbers(retailerId, serialSet, false)
                .forEach(ii -> serialToInventoryItemId.put(ii.getSerialNumber(), ii.getId()));
        deductPartnerUnits(hike, retailerId, serialToInventoryItemId);
    }
}