|
|
@@ -0,0 +1,666 @@
|
|
|
+package com.haha.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.haha.common.enums.*;
|
|
|
+import com.haha.common.exception.BusinessException;
|
|
|
+import com.haha.common.vo.StatusLabel;
|
|
|
+import com.haha.entity.*;
|
|
|
+import com.haha.entity.dto.TimedDiscountCreateDTO;
|
|
|
+import com.haha.entity.dto.TimedDiscountQueryDTO;
|
|
|
+import com.haha.entity.dto.TimedDiscountRecordQueryDTO;
|
|
|
+import com.haha.entity.dto.TimedDiscountUpdateDTO;
|
|
|
+import com.haha.mapper.*;
|
|
|
+import com.haha.service.TimedDiscountService;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.LocalTime;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class TimedDiscountServiceImpl extends ServiceImpl<TimedDiscountActivityMapper, TimedDiscountActivity> implements TimedDiscountService {
|
|
|
+
|
|
|
+ private final TimedDiscountActivityMapper activityMapper;
|
|
|
+ private final TimedDiscountShopMapper discountShopMapper;
|
|
|
+ private final TimedDiscountProductMapper discountProductMapper;
|
|
|
+ private final TimedDiscountRecordMapper recordMapper;
|
|
|
+ private final PriceAdjustmentLogMapper priceAdjustmentLogMapper;
|
|
|
+ private final TimedDiscountStatisticsMapper statisticsMapper;
|
|
|
+ private final DeviceInventoryMapper deviceInventoryMapper;
|
|
|
+ private final ProductMapper productMapper;
|
|
|
+ private final DeviceMapper deviceMapper;
|
|
|
+ private final ShopMapper shopMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IPage<TimedDiscountActivity> getPage(TimedDiscountQueryDTO queryDTO) {
|
|
|
+ queryDTO.validate();
|
|
|
+ Page<TimedDiscountActivity> page = new Page<>(queryDTO.getPage(), queryDTO.getPageSize());
|
|
|
+
|
|
|
+ LambdaQueryWrapper<TimedDiscountActivity> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.like(StringUtils.hasText(queryDTO.getActivityName()), TimedDiscountActivity::getActivityName, queryDTO.getActivityName());
|
|
|
+ wrapper.eq(queryDTO.getStatus() != null, TimedDiscountActivity::getStatus, queryDTO.getStatus());
|
|
|
+ wrapper.eq(queryDTO.getDiscountType() != null, TimedDiscountActivity::getDiscountType, queryDTO.getDiscountType());
|
|
|
+ wrapper.eq(TimedDiscountActivity::getDeleted, 0);
|
|
|
+ wrapper.orderByDesc(TimedDiscountActivity::getCreateTime);
|
|
|
+
|
|
|
+ IPage<TimedDiscountActivity> result = this.page(page, wrapper);
|
|
|
+ result.getRecords().forEach(this::fillLabels);
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public TimedDiscountActivity getDetail(Long id) {
|
|
|
+ TimedDiscountActivity activity = this.getById(id);
|
|
|
+ if (activity == null || activity.getDeleted() == 1) {
|
|
|
+ throw new BusinessException(404, "活动不存在");
|
|
|
+ }
|
|
|
+ fillLabels(activity);
|
|
|
+ activity.setShopIds(getActivityShopIds(id));
|
|
|
+ activity.setProductIds(getActivityProductIds(id));
|
|
|
+
|
|
|
+ if (StringUtils.hasText(activity.getProductTypes())) {
|
|
|
+ activity.setProductTypeList(Arrays.asList(activity.getProductTypes().split(",")));
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(activity.getPriorityProductTypes())) {
|
|
|
+ activity.setPriorityProductTypeList(Arrays.asList(activity.getPriorityProductTypes().split(",")));
|
|
|
+ }
|
|
|
+ return activity;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public TimedDiscountActivity create(TimedDiscountCreateDTO dto, Long creatorId, String creatorName) {
|
|
|
+ TimedDiscountActivity activity = new TimedDiscountActivity();
|
|
|
+ activity.setActivityName(dto.getActivityName());
|
|
|
+ activity.setActivityDesc(dto.getActivityDesc());
|
|
|
+ activity.setStartTime(dto.getStartTime());
|
|
|
+ activity.setEndTime(dto.getEndTime());
|
|
|
+ activity.setDiscountType(dto.getDiscountType());
|
|
|
+ activity.setDiscountValue(dto.getDiscountValue());
|
|
|
+ activity.setMinDiscountPrice(dto.getMinDiscountPrice());
|
|
|
+ activity.setApplyScope(dto.getApplyScope());
|
|
|
+ activity.setProductScope(dto.getProductScope());
|
|
|
+ activity.setProductTypes(dto.getProductTypes());
|
|
|
+ activity.setPriorityProductTypes(dto.getPriorityProductTypes());
|
|
|
+ activity.setMinStock(dto.getMinStock() != null ? dto.getMinStock() : 1);
|
|
|
+ activity.setMaxStock(dto.getMaxStock());
|
|
|
+ activity.setStatus(NightDiscountStatusEnum.ENABLED.getCode());
|
|
|
+ activity.setAutoExecute(dto.getAutoExecute() != null ? dto.getAutoExecute() : 1);
|
|
|
+ activity.setNotifyOnExecute(dto.getNotifyOnExecute() != null ? dto.getNotifyOnExecute() : 0);
|
|
|
+ activity.setCreatorId(creatorId);
|
|
|
+ activity.setCreatorName(creatorName);
|
|
|
+ activity.setDeleted(0);
|
|
|
+
|
|
|
+ this.save(activity);
|
|
|
+
|
|
|
+ saveActivityShops(activity.getId(), dto.getShopIds(), dto.getApplyScope());
|
|
|
+ saveActivityProducts(activity.getId(), dto.getProductIds(), dto.getProductScope());
|
|
|
+
|
|
|
+ log.info("创建定时折扣活动成功: id={}, name={}", activity.getId(), activity.getActivityName());
|
|
|
+ return activity;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public TimedDiscountActivity update(TimedDiscountUpdateDTO dto) {
|
|
|
+ TimedDiscountActivity activity = this.getById(dto.getId());
|
|
|
+ if (activity == null || activity.getDeleted() == 1) {
|
|
|
+ throw new BusinessException(404, "活动不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ activity.setActivityName(dto.getActivityName());
|
|
|
+ activity.setActivityDesc(dto.getActivityDesc());
|
|
|
+ activity.setStartTime(dto.getStartTime());
|
|
|
+ activity.setEndTime(dto.getEndTime());
|
|
|
+ activity.setDiscountType(dto.getDiscountType());
|
|
|
+ activity.setDiscountValue(dto.getDiscountValue());
|
|
|
+ activity.setMinDiscountPrice(dto.getMinDiscountPrice());
|
|
|
+ activity.setApplyScope(dto.getApplyScope());
|
|
|
+ activity.setProductScope(dto.getProductScope());
|
|
|
+ activity.setProductTypes(dto.getProductTypes());
|
|
|
+ activity.setPriorityProductTypes(dto.getPriorityProductTypes());
|
|
|
+ activity.setMinStock(dto.getMinStock());
|
|
|
+ activity.setMaxStock(dto.getMaxStock());
|
|
|
+ activity.setAutoExecute(dto.getAutoExecute());
|
|
|
+ activity.setNotifyOnExecute(dto.getNotifyOnExecute());
|
|
|
+
|
|
|
+ this.updateById(activity);
|
|
|
+
|
|
|
+ deleteActivityShops(activity.getId());
|
|
|
+ deleteActivityProducts(activity.getId());
|
|
|
+ saveActivityShops(activity.getId(), dto.getShopIds(), dto.getApplyScope());
|
|
|
+ saveActivityProducts(activity.getId(), dto.getProductIds(), dto.getProductScope());
|
|
|
+
|
|
|
+ log.info("更新定时折扣活动成功: id={}", activity.getId());
|
|
|
+ return activity;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public boolean updateStatus(Long id, Integer status) {
|
|
|
+ TimedDiscountActivity activity = this.getById(id);
|
|
|
+ if (activity == null || activity.getDeleted() == 1) {
|
|
|
+ throw new BusinessException(404, "活动不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ activity.setStatus(status);
|
|
|
+ boolean result = this.updateById(activity);
|
|
|
+
|
|
|
+ if (result) {
|
|
|
+ log.info("更新定时折扣活动状态成功: id={}, status={}", id, status);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public boolean deleteActivity(Long id) {
|
|
|
+ TimedDiscountActivity activity = this.getById(id);
|
|
|
+ if (activity == null) {
|
|
|
+ throw new BusinessException(404, "活动不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ activity.setDeleted(1);
|
|
|
+ boolean result = this.updateById(activity);
|
|
|
+
|
|
|
+ if (result) {
|
|
|
+ deleteActivityShops(id);
|
|
|
+ deleteActivityProducts(id);
|
|
|
+ log.info("删除定时折扣活动成功: id={}", id);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<Long> getActivityShopIds(Long activityId) {
|
|
|
+ LambdaQueryWrapper<TimedDiscountShop> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(TimedDiscountShop::getActivityId, activityId);
|
|
|
+ return discountShopMapper.selectList(wrapper).stream()
|
|
|
+ .map(TimedDiscountShop::getShopId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<Long> getActivityProductIds(Long activityId) {
|
|
|
+ LambdaQueryWrapper<TimedDiscountProduct> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(TimedDiscountProduct::getActivityId, activityId);
|
|
|
+ return discountProductMapper.selectList(wrapper).stream()
|
|
|
+ .map(TimedDiscountProduct::getProductId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<TimedDiscountActivity> getEnabledActivities() {
|
|
|
+ LambdaQueryWrapper<TimedDiscountActivity> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(TimedDiscountActivity::getStatus, NightDiscountStatusEnum.ENABLED.getCode());
|
|
|
+ wrapper.eq(TimedDiscountActivity::getDeleted, 0);
|
|
|
+ wrapper.eq(TimedDiscountActivity::getAutoExecute, 1);
|
|
|
+ return this.list(wrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IPage<TimedDiscountRecord> getRecordPage(TimedDiscountRecordQueryDTO queryDTO) {
|
|
|
+ queryDTO.validate();
|
|
|
+ Page<TimedDiscountRecord> page = new Page<>(queryDTO.getPage(), queryDTO.getPageSize());
|
|
|
+
|
|
|
+ LambdaQueryWrapper<TimedDiscountRecord> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(queryDTO.getActivityId() != null, TimedDiscountRecord::getActivityId, queryDTO.getActivityId());
|
|
|
+ wrapper.ge(queryDTO.getExecuteDateStart() != null, TimedDiscountRecord::getExecuteDate, queryDTO.getExecuteDateStart());
|
|
|
+ wrapper.le(queryDTO.getExecuteDateEnd() != null, TimedDiscountRecord::getExecuteDate, queryDTO.getExecuteDateEnd());
|
|
|
+ wrapper.eq(queryDTO.getShopId() != null, TimedDiscountRecord::getShopId, queryDTO.getShopId());
|
|
|
+ wrapper.eq(StringUtils.hasText(queryDTO.getDeviceId()), TimedDiscountRecord::getDeviceId, queryDTO.getDeviceId());
|
|
|
+ wrapper.eq(queryDTO.getStatus() != null, TimedDiscountRecord::getStatus, queryDTO.getStatus());
|
|
|
+ wrapper.orderByDesc(TimedDiscountRecord::getExecuteTime);
|
|
|
+
|
|
|
+ IPage<TimedDiscountRecord> result = recordMapper.selectPage(page, wrapper);
|
|
|
+ result.getRecords().forEach(this::fillRecordLabels);
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public TimedDiscountRecord getRecordDetail(Long recordId) {
|
|
|
+ TimedDiscountRecord record = recordMapper.selectById(recordId);
|
|
|
+ if (record != null) {
|
|
|
+ fillRecordLabels(record);
|
|
|
+ }
|
|
|
+ return record;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IPage<PriceAdjustmentLog> getPriceAdjustmentLogs(Long recordId, int page, int pageSize) {
|
|
|
+ Page<PriceAdjustmentLog> pageObj = new Page<>(page, pageSize);
|
|
|
+ LambdaQueryWrapper<PriceAdjustmentLog> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(PriceAdjustmentLog::getRecordId, recordId);
|
|
|
+ wrapper.orderByAsc(PriceAdjustmentLog::getAdjustTime);
|
|
|
+
|
|
|
+ IPage<PriceAdjustmentLog> result = priceAdjustmentLogMapper.selectPage(pageObj, wrapper);
|
|
|
+ result.getRecords().forEach(this::fillPriceLogLabels);
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IPage<TimedDiscountStatistics> getStatisticsPage(Long activityId, int page, int pageSize) {
|
|
|
+ Page<TimedDiscountStatistics> pageObj = new Page<>(page, pageSize);
|
|
|
+ LambdaQueryWrapper<TimedDiscountStatistics> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(TimedDiscountStatistics::getActivityId, activityId);
|
|
|
+ wrapper.orderByDesc(TimedDiscountStatistics::getStatDate);
|
|
|
+
|
|
|
+ return statisticsMapper.selectPage(pageObj, wrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Map<String, Object> getActivityStatistics(Long activityId) {
|
|
|
+ Map<String, Object> stats = new HashMap<>();
|
|
|
+
|
|
|
+ LambdaQueryWrapper<TimedDiscountStatistics> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(TimedDiscountStatistics::getActivityId, activityId);
|
|
|
+ List<TimedDiscountStatistics> statList = statisticsMapper.selectList(wrapper);
|
|
|
+
|
|
|
+ int totalAdjustedProducts = statList.stream().mapToInt(TimedDiscountStatistics::getTotalAdjustedProducts).sum();
|
|
|
+ int totalSoldQuantity = statList.stream().mapToInt(TimedDiscountStatistics::getTotalSoldQuantity).sum();
|
|
|
+ BigDecimal totalOriginalValue = statList.stream().map(TimedDiscountStatistics::getOriginalValue).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ BigDecimal totalActualSales = statList.stream().map(TimedDiscountStatistics::getActualSales).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ BigDecimal totalSavedAmount = statList.stream().map(TimedDiscountStatistics::getSavedAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+
|
|
|
+ stats.put("totalAdjustedProducts", totalAdjustedProducts);
|
|
|
+ stats.put("totalSoldQuantity", totalSoldQuantity);
|
|
|
+ stats.put("totalOriginalValue", totalOriginalValue);
|
|
|
+ stats.put("totalActualSales", totalActualSales);
|
|
|
+ stats.put("totalSavedAmount", totalSavedAmount);
|
|
|
+ stats.put("executionCount", statList.size());
|
|
|
+
|
|
|
+ return stats;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void executeDiscountActivity(Long activityId) {
|
|
|
+ TimedDiscountActivity activity = this.getById(activityId);
|
|
|
+ if (activity == null || activity.getStatus() != NightDiscountStatusEnum.ENABLED.getCode()) {
|
|
|
+ log.warn("活动不存在或未启用: activityId={}", activityId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalTime now = LocalTime.now();
|
|
|
+ if (now.isBefore(activity.getStartTime())) {
|
|
|
+ log.info("未到活动开始时间: activityId={}, startTime={}", activityId, activity.getStartTime());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+ log.info("开始执行定时折扣活动: activityId={}, name={}", activityId, activity.getActivityName());
|
|
|
+
|
|
|
+ try {
|
|
|
+ List<Long> shopIds = getTargetShopIds(activity);
|
|
|
+ List<String> productTypes = getProductTypes(activity);
|
|
|
+ List<String> priorityTypes = getPriorityProductTypes(activity);
|
|
|
+
|
|
|
+ int totalProducts = 0;
|
|
|
+ int priorityProducts = 0;
|
|
|
+ int totalStock = 0;
|
|
|
+ BigDecimal originalValue = BigDecimal.ZERO;
|
|
|
+ BigDecimal discountValue = BigDecimal.ZERO;
|
|
|
+
|
|
|
+ for (Long shopId : shopIds) {
|
|
|
+ Shop shop = shopMapper.selectById(shopId);
|
|
|
+ String shopName = shop != null ? shop.getName() : "";
|
|
|
+
|
|
|
+ List<Device> devices = getDevicesByShop(shopId);
|
|
|
+ for (Device device : devices) {
|
|
|
+ List<Map<String, Object>> inventoryList = getEligibleInventory(device.getDeviceId(), activity, productTypes);
|
|
|
+
|
|
|
+ for (Map<String, Object> inventory : inventoryList) {
|
|
|
+ Long productId = (Long) inventory.get("productId");
|
|
|
+ Integer stock = (Integer) inventory.get("stock");
|
|
|
+ Product product = productMapper.selectById(productId);
|
|
|
+
|
|
|
+ if (product == null || product.getRetailPrice() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal originalPrice = BigDecimal.valueOf(product.getRetailPrice());
|
|
|
+ BigDecimal discountedPrice = calculateDiscountedPrice(originalPrice, activity);
|
|
|
+ boolean isPriority = isPriorityProduct(product.getType(), priorityTypes);
|
|
|
+
|
|
|
+ PriceAdjustmentLog logEntry = new PriceAdjustmentLog();
|
|
|
+ logEntry.setActivityId(activityId);
|
|
|
+ logEntry.setShopId(shopId);
|
|
|
+ logEntry.setShopName(shopName);
|
|
|
+ logEntry.setDeviceId(device.getDeviceId());
|
|
|
+ logEntry.setDeviceName(device.getName());
|
|
|
+ logEntry.setProductId(productId);
|
|
|
+ logEntry.setProductName(product.getName());
|
|
|
+ logEntry.setProductType(product.getType());
|
|
|
+ logEntry.setStock(stock);
|
|
|
+ logEntry.setOriginalPrice(originalPrice);
|
|
|
+ logEntry.setDiscountType(activity.getDiscountType());
|
|
|
+ logEntry.setDiscountValue(activity.getDiscountValue());
|
|
|
+ logEntry.setDiscountedPrice(discountedPrice);
|
|
|
+ logEntry.setIsPriority(isPriority ? 1 : 0);
|
|
|
+ logEntry.setAdjustTime(LocalDateTime.now());
|
|
|
+ logEntry.setRestoreStatus(RestoreStatusEnum.NOT_RESTORED.getCode());
|
|
|
+ logEntry.setSaleQuantity(0);
|
|
|
+
|
|
|
+ priceAdjustmentLogMapper.insert(logEntry);
|
|
|
+
|
|
|
+ totalProducts++;
|
|
|
+ if (isPriority) {
|
|
|
+ priorityProducts++;
|
|
|
+ }
|
|
|
+ totalStock += stock;
|
|
|
+ originalValue = originalValue.add(originalPrice.multiply(BigDecimal.valueOf(stock)));
|
|
|
+ discountValue = discountValue.add(discountedPrice.multiply(BigDecimal.valueOf(stock)));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ TimedDiscountRecord record = new TimedDiscountRecord();
|
|
|
+ record.setActivityId(activityId);
|
|
|
+ record.setActivityName(activity.getActivityName());
|
|
|
+ record.setExecuteDate(LocalDate.now());
|
|
|
+ record.setExecuteTime(LocalDateTime.now());
|
|
|
+ record.setTotalProducts(totalProducts);
|
|
|
+ record.setPriorityProducts(priorityProducts);
|
|
|
+ record.setTotalStock(totalStock);
|
|
|
+ record.setOriginalValue(originalValue);
|
|
|
+ record.setDiscountValue(discountValue);
|
|
|
+ record.setSavedValue(originalValue.subtract(discountValue));
|
|
|
+ record.setStatus(ExecuteStatusEnum.SUCCESS.getCode());
|
|
|
+ record.setExecuteDuration((int) (System.currentTimeMillis() - startTime));
|
|
|
+
|
|
|
+ recordMapper.insert(record);
|
|
|
+
|
|
|
+ log.info("定时折扣活动执行完成: activityId={}, totalProducts={}, savedValue={}",
|
|
|
+ activityId, totalProducts, record.getSavedValue());
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("定时折扣活动执行失败: activityId={}", activityId, e);
|
|
|
+
|
|
|
+ TimedDiscountRecord record = new TimedDiscountRecord();
|
|
|
+ record.setActivityId(activityId);
|
|
|
+ record.setActivityName(activity.getActivityName());
|
|
|
+ record.setExecuteDate(LocalDate.now());
|
|
|
+ record.setExecuteTime(LocalDateTime.now());
|
|
|
+ record.setStatus(ExecuteStatusEnum.FAILED.getCode());
|
|
|
+ record.setErrorMessage(e.getMessage());
|
|
|
+ record.setExecuteDuration((int) (System.currentTimeMillis() - startTime));
|
|
|
+ recordMapper.insert(record);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void executeAllEnabledActivities() {
|
|
|
+ List<TimedDiscountActivity> activities = getEnabledActivities();
|
|
|
+ for (TimedDiscountActivity activity : activities) {
|
|
|
+ try {
|
|
|
+ executeDiscountActivity(activity.getId());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("执行定时折扣活动异常: activityId={}", activity.getId(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void restorePrices() {
|
|
|
+ log.info("开始恢复商品价格");
|
|
|
+
|
|
|
+ LambdaQueryWrapper<PriceAdjustmentLog> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(PriceAdjustmentLog::getRestoreStatus, RestoreStatusEnum.NOT_RESTORED.getCode());
|
|
|
+ wrapper.lt(PriceAdjustmentLog::getAdjustTime, LocalDateTime.now().minusHours(12));
|
|
|
+
|
|
|
+ List<PriceAdjustmentLog> logs = priceAdjustmentLogMapper.selectList(wrapper);
|
|
|
+ for (PriceAdjustmentLog logEntry : logs) {
|
|
|
+ logEntry.setRestoreStatus(RestoreStatusEnum.RESTORED.getCode());
|
|
|
+ logEntry.setRestoreTime(LocalDateTime.now());
|
|
|
+ priceAdjustmentLogMapper.updateById(logEntry);
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("商品价格恢复完成: count={}", logs.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void updateSalesStatistics() {
|
|
|
+ log.info("开始更新销售统计");
|
|
|
+
|
|
|
+ LocalDate today = LocalDate.now();
|
|
|
+ LambdaQueryWrapper<TimedDiscountRecord> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(TimedDiscountRecord::getExecuteDate, today);
|
|
|
+ List<TimedDiscountRecord> records = recordMapper.selectList(wrapper);
|
|
|
+
|
|
|
+ for (TimedDiscountRecord record : records) {
|
|
|
+ updateRecordStatistics(record);
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("销售统计更新完成");
|
|
|
+ }
|
|
|
+
|
|
|
+ private void updateRecordStatistics(TimedDiscountRecord record) {
|
|
|
+ LambdaQueryWrapper<PriceAdjustmentLog> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(PriceAdjustmentLog::getRecordId, record.getId());
|
|
|
+ List<PriceAdjustmentLog> logs = priceAdjustmentLogMapper.selectList(wrapper);
|
|
|
+
|
|
|
+ int totalSold = logs.stream().mapToInt(PriceAdjustmentLog::getSaleQuantity).sum();
|
|
|
+ BigDecimal totalSales = logs.stream()
|
|
|
+ .filter(l -> l.getSaleAmount() != null)
|
|
|
+ .map(PriceAdjustmentLog::getSaleAmount)
|
|
|
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+
|
|
|
+ TimedDiscountStatistics stats = new TimedDiscountStatistics();
|
|
|
+ stats.setActivityId(record.getActivityId());
|
|
|
+ stats.setStatDate(record.getExecuteDate());
|
|
|
+ stats.setShopId(record.getShopId());
|
|
|
+ stats.setShopName(record.getShopName());
|
|
|
+ stats.setTotalAdjustedProducts(record.getTotalProducts());
|
|
|
+ stats.setPriorityProducts(record.getPriorityProducts());
|
|
|
+ stats.setTotalAdjustedStock(record.getTotalStock());
|
|
|
+ stats.setTotalSoldQuantity(totalSold);
|
|
|
+ stats.setOriginalValue(record.getOriginalValue());
|
|
|
+ stats.setActualSales(totalSales);
|
|
|
+ stats.setSavedAmount(record.getSavedValue());
|
|
|
+
|
|
|
+ if (record.getTotalStock() > 0) {
|
|
|
+ BigDecimal sellThroughRate = BigDecimal.valueOf(totalSold)
|
|
|
+ .divide(BigDecimal.valueOf(record.getTotalStock()), 4, RoundingMode.HALF_UP)
|
|
|
+ .multiply(BigDecimal.valueOf(100));
|
|
|
+ stats.setSellThroughRate(sellThroughRate);
|
|
|
+ }
|
|
|
+
|
|
|
+ LambdaQueryWrapper<TimedDiscountStatistics> existWrapper = new LambdaQueryWrapper<>();
|
|
|
+ existWrapper.eq(TimedDiscountStatistics::getActivityId, record.getActivityId());
|
|
|
+ existWrapper.eq(TimedDiscountStatistics::getStatDate, record.getExecuteDate());
|
|
|
+ existWrapper.eq(TimedDiscountStatistics::getShopId, record.getShopId());
|
|
|
+ TimedDiscountStatistics exist = statisticsMapper.selectOne(existWrapper);
|
|
|
+
|
|
|
+ if (exist != null) {
|
|
|
+ stats.setId(exist.getId());
|
|
|
+ statisticsMapper.updateById(stats);
|
|
|
+ } else {
|
|
|
+ statisticsMapper.insert(stats);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Long> getTargetShopIds(TimedDiscountActivity activity) {
|
|
|
+ if (activity.getApplyScope() == ApplyScopeEnum.ALL_SHOP.getCode()) {
|
|
|
+ LambdaQueryWrapper<Shop> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(Shop::getStatus, 1);
|
|
|
+ return shopMapper.selectList(wrapper).stream().map(Shop::getId).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+ return getActivityShopIds(activity.getId());
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<String> getProductTypes(TimedDiscountActivity activity) {
|
|
|
+ if (activity.getProductScope() == 1) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(activity.getProductTypes())) {
|
|
|
+ return Arrays.asList(activity.getProductTypes().split(","));
|
|
|
+ }
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<String> getPriorityProductTypes(TimedDiscountActivity activity) {
|
|
|
+ if (StringUtils.hasText(activity.getPriorityProductTypes())) {
|
|
|
+ return Arrays.asList(activity.getPriorityProductTypes().split(","));
|
|
|
+ }
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Device> getDevicesByShop(Long shopId) {
|
|
|
+ LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(Device::getShopId, shopId);
|
|
|
+ return deviceMapper.selectList(wrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map<String, Object>> getEligibleInventory(String deviceId, TimedDiscountActivity activity, List<String> productTypes) {
|
|
|
+ LambdaQueryWrapper<DeviceInventory> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(DeviceInventory::getDeviceId, deviceId);
|
|
|
+ wrapper.ge(DeviceInventory::getStock, activity.getMinStock());
|
|
|
+ if (activity.getMaxStock() != null) {
|
|
|
+ wrapper.le(DeviceInventory::getStock, activity.getMaxStock());
|
|
|
+ }
|
|
|
+
|
|
|
+ List<DeviceInventory> inventoryList = deviceInventoryMapper.selectList(wrapper);
|
|
|
+ List<Map<String, Object>> result = new ArrayList<>();
|
|
|
+
|
|
|
+ for (DeviceInventory inventory : inventoryList) {
|
|
|
+ Product product = productMapper.selectById(inventory.getProductId());
|
|
|
+ if (product == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (productTypes != null && !productTypes.isEmpty()) {
|
|
|
+ if (!productTypes.contains(product.getType())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> map = new HashMap<>();
|
|
|
+ map.put("productId", inventory.getProductId());
|
|
|
+ map.put("stock", inventory.getStock());
|
|
|
+ result.add(map);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private BigDecimal calculateDiscountedPrice(BigDecimal originalPrice, TimedDiscountActivity activity) {
|
|
|
+ BigDecimal discountedPrice;
|
|
|
+
|
|
|
+ if (activity.getDiscountType() == DiscountTypeEnum.FIXED_DISCOUNT.getCode()) {
|
|
|
+ discountedPrice = originalPrice.multiply(activity.getDiscountValue());
|
|
|
+ } else if (activity.getDiscountType() == DiscountTypeEnum.FIXED_PRICE.getCode()) {
|
|
|
+ discountedPrice = activity.getDiscountValue();
|
|
|
+ } else {
|
|
|
+ discountedPrice = originalPrice.multiply(activity.getDiscountValue());
|
|
|
+ }
|
|
|
+
|
|
|
+ discountedPrice = discountedPrice.setScale(2, RoundingMode.HALF_UP);
|
|
|
+
|
|
|
+ if (activity.getMinDiscountPrice() != null) {
|
|
|
+ if (discountedPrice.compareTo(activity.getMinDiscountPrice()) < 0) {
|
|
|
+ discountedPrice = activity.getMinDiscountPrice();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return discountedPrice;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isPriorityProduct(String productType, List<String> priorityTypes) {
|
|
|
+ if (priorityTypes == null || priorityTypes.isEmpty()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return priorityTypes.contains(productType);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void saveActivityShops(Long activityId, List<Long> shopIds, Integer applyScope) {
|
|
|
+ if (applyScope != null && applyScope == ApplyScopeEnum.SPECIFIED_SHOP.getCode()
|
|
|
+ && shopIds != null && !shopIds.isEmpty()) {
|
|
|
+ for (Long shopId : shopIds) {
|
|
|
+ TimedDiscountShop discountShop = new TimedDiscountShop();
|
|
|
+ discountShop.setActivityId(activityId);
|
|
|
+ discountShop.setShopId(shopId);
|
|
|
+ discountShopMapper.insert(discountShop);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void saveActivityProducts(Long activityId, List<Long> productIds, Integer productScope) {
|
|
|
+ if (productScope != null && productScope == 3
|
|
|
+ && productIds != null && !productIds.isEmpty()) {
|
|
|
+ for (Long productId : productIds) {
|
|
|
+ TimedDiscountProduct discountProduct = new TimedDiscountProduct();
|
|
|
+ discountProduct.setActivityId(activityId);
|
|
|
+ discountProduct.setProductId(productId);
|
|
|
+ discountProductMapper.insert(discountProduct);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void deleteActivityShops(Long activityId) {
|
|
|
+ LambdaQueryWrapper<TimedDiscountShop> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(TimedDiscountShop::getActivityId, activityId);
|
|
|
+ discountShopMapper.delete(wrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void deleteActivityProducts(Long activityId) {
|
|
|
+ LambdaQueryWrapper<TimedDiscountProduct> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(TimedDiscountProduct::getActivityId, activityId);
|
|
|
+ discountProductMapper.delete(wrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void fillLabels(TimedDiscountActivity activity) {
|
|
|
+ StatusLabel statusLabel = NightDiscountStatusEnum.getLabelByCode(activity.getStatus());
|
|
|
+ activity.setStatusLabel(statusLabel.getLabel());
|
|
|
+ activity.setStatusColor(statusLabel.getColor());
|
|
|
+
|
|
|
+ StatusLabel discountTypeLabel = DiscountTypeEnum.getLabelByCode(activity.getDiscountType());
|
|
|
+ activity.setDiscountTypeLabel(discountTypeLabel.getLabel());
|
|
|
+
|
|
|
+ StatusLabel applyScopeLabel = ApplyScopeEnum.getLabelByCode(activity.getApplyScope());
|
|
|
+ activity.setApplyScopeLabel(applyScopeLabel.getLabel());
|
|
|
+
|
|
|
+ if (activity.getProductScope() != null) {
|
|
|
+ if (activity.getProductScope() == 1) {
|
|
|
+ activity.setProductScopeLabel("全部商品");
|
|
|
+ } else if (activity.getProductScope() == 2) {
|
|
|
+ activity.setProductScopeLabel("指定分类");
|
|
|
+ } else {
|
|
|
+ activity.setProductScopeLabel("指定商品");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void fillRecordLabels(TimedDiscountRecord record) {
|
|
|
+ StatusLabel statusLabel = ExecuteStatusEnum.getLabelByCode(record.getStatus());
|
|
|
+ record.setStatusLabel(statusLabel.getLabel());
|
|
|
+ record.setStatusColor(statusLabel.getColor());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void fillPriceLogLabels(PriceAdjustmentLog log) {
|
|
|
+ StatusLabel restoreStatusLabel = RestoreStatusEnum.getLabelByCode(log.getRestoreStatus());
|
|
|
+ log.setRestoreStatusLabel(restoreStatusLabel.getLabel());
|
|
|
+ log.setRestoreStatusColor(restoreStatusLabel.getColor());
|
|
|
+
|
|
|
+ StatusLabel discountTypeLabel = DiscountTypeEnum.getLabelByCode(log.getDiscountType());
|
|
|
+ log.setDiscountTypeLabel(discountTypeLabel.getLabel());
|
|
|
+ }
|
|
|
+}
|