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.*/@Componentpublic class PriceHikeWorker {private static final Logger LOGGER = LogManager.getLogger(PriceHikeWorker.class);@Autowiredprivate PriceDropRepository priceDropRepository;@Autowiredprivate PriceDropIMEIRepository priceDropIMEIRepository;@Autowiredprivate InventoryItemRepository inventoryItemRepository;@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate 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);}}