master:销售/出库调优;

pull/1121/head
liujiang 2025-11-17 12:22:33 +08:00
parent 38a115a9fc
commit 638dea116f
10 changed files with 314 additions and 54 deletions

View File

@ -76,13 +76,6 @@ public class StoreSaleController extends XktBaseController {
return R.ok(BeanUtil.toBean(storeSaleService.selectByStoreSaleId(storeSaleId), StoreSaleResVO.class));
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin,store')||@ss.hasSupplierSubRole()")
@ApiOperation(value = "查询档口今日销售额", httpMethod = "GET", response = R.class)
@GetMapping(value = "/today-sale/{storeId}")
public R<StoreTodaySaleVO> getTodaySale(@PathVariable("storeId") Long storeId) {
return R.ok(BeanUtil.toBean(storeSaleService.getTodaySale(storeId), StoreTodaySaleVO.class));
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin,store')||@ss.hasSupplierSubRole()")
@Log(title = "客户欠款结算", businessType = BusinessType.UPDATE)
@ApiOperation(value = "客户欠款结算", httpMethod = "PUT", response = R.class)

View File

@ -1,6 +1,17 @@
package com.ruoyi.web.controller.xkt;
import cn.hutool.core.bean.BeanUtil;
import com.ruoyi.common.core.controller.XktBaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.controller.xkt.vo.storeSaleDetail.StoreTodaySaleVO;
import com.ruoyi.web.controller.xkt.vo.storeSaleDetail.StoreTodaySaleSummaryVO;
import com.ruoyi.xkt.service.IStoreSaleDetailService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -10,8 +21,26 @@ import org.springframework.web.bind.annotation.RestController;
* @author ruoyi
* @date 2025-03-26
*/
@Api(tags = "档口销售出库明细")
@RequiredArgsConstructor
@RestController
@RequestMapping("/rest/v1/store-sale-details")
public class StoreSaleDetailController extends XktBaseController {
final IStoreSaleDetailService saleDetailService;
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin,store')||@ss.hasSupplierSubRole()")
@ApiOperation(value = "查询档口今日销售额", httpMethod = "GET", response = R.class)
@GetMapping(value = "/today-sale/{storeId}")
public R<StoreTodaySaleVO> getTodaySale(@PathVariable("storeId") Long storeId) {
return R.ok(BeanUtil.toBean(saleDetailService.getTodaySale(storeId), StoreTodaySaleVO.class));
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin,store')||@ss.hasSupplierSubRole()")
@ApiOperation(value = "查询今日统计数据", httpMethod = "GET", response = R.class)
@GetMapping(value = "/today-sale/summary/{storeId}")
public R<StoreTodaySaleSummaryVO> getTodaySaleSummary(@PathVariable("storeId") Long storeId) {
return R.ok(BeanUtil.toBean(saleDetailService.getTodaySaleSummary(storeId), StoreTodaySaleSummaryVO.class));
}
}

View File

@ -0,0 +1,55 @@
package com.ruoyi.web.controller.xkt.vo.storeSaleDetail;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* @author liujiang
* @version v1.0
* @date 2025/3/27 15:12
*/
@ApiModel
@Data
public class StoreTodaySaleSummaryVO {
@ApiModelProperty(value = "销售金额")
private BigDecimal saleAmount;
@ApiModelProperty(value = "退货金额")
private BigDecimal refundAmount;
@ApiModelProperty(value = "销售数量")
private Integer saleQuantity;
@ApiModelProperty(value = "退货数量")
private Integer refundQuantity;
@ApiModelProperty(value = "退货率")
private String refundRate;
@ApiModelProperty(value = "采购客户数量")
private Integer cusQuantity;
@ApiModelProperty(value = "销售数据")
List<STSSProdSaleVO> prodSaleList;
@ApiModelProperty(value = "客户数据")
List<STSSCusSaleVO> cusSaleList;
@Data
@ApiModel
public static class STSSProdSaleVO {
private Integer cusQuantity;
}
@Data
@ApiModel
public static class STSSCusSaleVO {
private Integer cusQuantity;
}
}

View File

@ -1,4 +1,4 @@
package com.ruoyi.web.controller.xkt.vo.storeSale;
package com.ruoyi.web.controller.xkt.vo.storeSaleDetail;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

View File

@ -1,4 +1,4 @@
package com.ruoyi.xkt.dto.dailySale;
package com.ruoyi.xkt.dto.storeSaleDetail;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

View File

@ -0,0 +1,70 @@
package com.ruoyi.xkt.dto.storeSaleDetail;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.util.List;
/**
* @author liujiang
* @version v1.0
* @date 2025/3/27 15:12
*/
@ApiModel
@Data
@Accessors(chain = true)
public class StoreTodaySaleSummaryDTO {
@ApiModelProperty(value = "销售金额")
private BigDecimal saleAmount;
@ApiModelProperty(value = "退货金额")
private BigDecimal refundAmount;
@ApiModelProperty(value = "销售数量")
private Integer saleQuantity;
@ApiModelProperty(value = "退货数量")
private Integer refundQuantity;
@ApiModelProperty(value = "退货率")
private String refundRate;
@ApiModelProperty(value = "采购客户数量")
private Long cusQuantity;
@ApiModelProperty(value = "销售数据")
List<STSSProdSaleDTO> prodSaleList;
@ApiModelProperty(value = "客户数据")
List<STSSCusSaleDTO> cusSaleList;
@Data
@ApiModel
public static class STSSProdSaleDTO {
private Integer cusQuantity;
}
@Data
@ApiModel
@Accessors(chain = true)
public static class STSSCusSaleDTO {
@ApiModelProperty(value = "客户名称")
private String storeCusName;
@ApiModelProperty(value = "销售金额")
private BigDecimal saleAmount;
@ApiModelProperty(value = "退货金额")
private BigDecimal refundAmount;
@ApiModelProperty(value = "销售数量")
private Integer saleQuantity;
@ApiModelProperty(value = "退货数量")
private Integer refundQuantity;
@ApiModelProperty(value = "合计销售金额")
private BigDecimal totalAmount;
@ApiModelProperty(value = "合计销售数量")
private Integer totalQuantity;
@ApiModelProperty(value = "退货率")
private String refundRate;
}
}

View File

@ -1,5 +1,8 @@
package com.ruoyi.xkt.service;
import com.ruoyi.xkt.dto.storeSaleDetail.StoreTodaySaleDTO;
import com.ruoyi.xkt.dto.storeSaleDetail.StoreTodaySaleSummaryDTO;
/**
* Service
*
@ -8,4 +11,20 @@ package com.ruoyi.xkt.service;
*/
public interface IStoreSaleDetailService {
/**
*
*
* @param storeId ID
* @return StoreTodaySaleDTO
*/
StoreTodaySaleDTO getTodaySale(Long storeId);
/**
*
*
* @param storeId ID
* @return StoreTodaySaleSummaryDTO
*/
StoreTodaySaleSummaryDTO getTodaySaleSummary(Long storeId);
}

View File

@ -1,7 +1,7 @@
package com.ruoyi.xkt.service;
import com.ruoyi.common.core.page.Page;
import com.ruoyi.xkt.dto.dailySale.StoreTodaySaleDTO;
import com.ruoyi.xkt.dto.storeSaleDetail.StoreTodaySaleDTO;
import com.ruoyi.xkt.dto.storeCustomer.StoreCusGeneralSaleDTO;
import com.ruoyi.xkt.dto.storeSale.*;
@ -75,14 +75,6 @@ public interface IStoreSaleService {
*/
Integer clearStoreCusDebt(StoreSalePayStatusDTO payStatusDTO);
/**
*
*
* @param storeId ID
* @return StoreTodaySaleDTO
*/
StoreTodaySaleDTO getTodaySale(Long storeId);
/**
*
*

View File

@ -1,7 +1,26 @@
package com.ruoyi.xkt.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.xkt.domain.StoreSaleDetail;
import com.ruoyi.xkt.dto.storeSaleDetail.StoreTodaySaleDTO;
import com.ruoyi.xkt.dto.storeSaleDetail.StoreTodaySaleSummaryDTO;
import com.ruoyi.xkt.enums.SaleType;
import com.ruoyi.xkt.mapper.StoreSaleDetailMapper;
import com.ruoyi.xkt.service.IStoreSaleDetailService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
/**
* Service
@ -10,6 +29,125 @@ import org.springframework.stereotype.Service;
* @date 2025-03-26
*/
@Service
@RequiredArgsConstructor
public class StoreSaleDetailServiceImpl implements IStoreSaleDetailService {
final StoreSaleDetailMapper saleDetailMapper;
/**
*
*
* @param storeId ID
* @return StoreTodaySaleDTO
*/
@Override
@Transactional(readOnly = true)
public StoreTodaySaleDTO getTodaySale(Long storeId) {
// 用户是否为档口管理者或子账户
if (!SecurityUtils.isAdmin() && !SecurityUtils.isStoreManagerOrSub(storeId)) {
throw new ServiceException("当前用户非档口管理者或子账号,无权限操作!", HttpStatus.ERROR);
}
// 今天 yyyy-MM-dd
Date date = java.sql.Date.valueOf(LocalDate.now());
List<StoreSaleDetail> saleDetailList = this.saleDetailMapper.selectList(new LambdaQueryWrapper<StoreSaleDetail>()
.eq(StoreSaleDetail::getStoreId, storeId).eq(StoreSaleDetail::getDelFlag, Constants.UNDELETED)
.eq(StoreSaleDetail::getVoucherDate, date));
if (CollectionUtils.isEmpty(saleDetailList)) {
return new StoreTodaySaleDTO();
}
Integer saleQuantity = saleDetailList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.GENERAL_SALE.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getQuantity(), 0)).reduce(0, Integer::sum);
BigDecimal saleAmount = saleDetailList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.GENERAL_SALE.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getAmount(), BigDecimal.ZERO)).reduce(BigDecimal.ZERO, BigDecimal::add);
Integer refundQuantity = saleDetailList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.SALE_REFUND.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getQuantity(), 0)).reduce(0, Integer::sum);
BigDecimal refundAmount = saleDetailList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.SALE_REFUND.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getAmount(), BigDecimal.ZERO)).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal amount = saleDetailList.stream().map(x -> ObjectUtils.defaultIfNull(x.getAmount(), BigDecimal.ZERO)).reduce(BigDecimal.ZERO, BigDecimal::add);
Integer quantity = saleDetailList.stream().map(x -> ObjectUtils.defaultIfNull(x.getQuantity(), 0)).reduce(0, Integer::sum);
return new StoreTodaySaleDTO().setStoreId(storeId).setSaleQuantity(saleQuantity).setSaleAmount(saleAmount)
.setRefundQuantity(refundQuantity).setRefundAmount(refundAmount).setAmount(amount).setQuantity(quantity);
}
/**
*
*
* @param storeId ID
* @return StoreTodaySaleSummaryDTO
*/
@Override
@Transactional(readOnly = true)
public StoreTodaySaleSummaryDTO getTodaySaleSummary(Long storeId) {
// 用户是否为档口管理者或子账户
if (!SecurityUtils.isAdmin() && !SecurityUtils.isStoreManagerOrSub(storeId)) {
throw new ServiceException("当前用户非档口管理者或子账号,无权限操作!", HttpStatus.ERROR);
}
List<StoreSaleDetail> saleDetailList = this.saleDetailMapper.selectList(new LambdaQueryWrapper<StoreSaleDetail>()
.eq(StoreSaleDetail::getStoreId, storeId).eq(StoreSaleDetail::getVoucherDate, java.sql.Date.valueOf(LocalDate.now()))
.eq(StoreSaleDetail::getDelFlag, Constants.UNDELETED));
if (CollectionUtils.isEmpty(saleDetailList)) {
return new StoreTodaySaleSummaryDTO();
}
// 销售出库金额
BigDecimal saleAmount = saleDetailList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.GENERAL_SALE.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getAmount(), BigDecimal.ZERO)).reduce(BigDecimal.ZERO, BigDecimal::add);
// 退货出库金额
BigDecimal refundAmount = saleDetailList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.SALE_REFUND.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getAmount(), BigDecimal.ZERO)).reduce(BigDecimal.ZERO, BigDecimal::add);
// 销售出库数量
Integer saleQuantity = saleDetailList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.GENERAL_SALE.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getQuantity(), 0)).reduce(0, Integer::sum);
// 退货出库数量
Integer refundQuantity = saleDetailList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.SALE_REFUND.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getQuantity(), 0)).reduce(0, Integer::sum);
// 退货率
String refundRate = saleQuantity > 0 ? String.format("%.2f", Math.abs(refundQuantity) * 100.0 / saleQuantity) : "0.00%";
// 采购客户数量
Long cusQuantity = saleDetailList.stream().map(x -> ObjectUtils.defaultIfNull(x.getStoreCusId(), 0)).distinct().count();
return new StoreTodaySaleSummaryDTO().setSaleAmount(saleAmount).setRefundAmount(refundAmount).setSaleQuantity(saleQuantity)
.setRefundQuantity(refundQuantity).setRefundRate(refundRate).setCusQuantity(cusQuantity)
.setProdSaleList(getProdSaleSummary(saleDetailList)).setCusSaleList(getCusSaleSummary(saleDetailList));
}
/**
*
*
* @param saleDetailList
* @return List<StoreTodaySaleSummaryDTO.STSSCusSaleDTO>
*/
private List<StoreTodaySaleSummaryDTO.STSSCusSaleDTO> getCusSaleSummary(List<StoreSaleDetail> saleDetailList) {
// 客户销售汇总map
Map<String, List<StoreSaleDetail>> cusSaleGroupMap = saleDetailList.stream().collect(Collectors.groupingBy(StoreSaleDetail::getStoreCusName));
List<StoreTodaySaleSummaryDTO.STSSCusSaleDTO> retCusSaleList = new ArrayList<>();
cusSaleGroupMap.forEach((cusName, cusSaleList) -> {
BigDecimal cusSaleAmount = cusSaleList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.GENERAL_SALE.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getAmount(), BigDecimal.ZERO)).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal cusRefundAmount = cusSaleList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.SALE_REFUND.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getAmount(), BigDecimal.ZERO)).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal cusTotalAmount = cusSaleAmount.add(cusRefundAmount);
Integer cusSaleQuantity = cusSaleList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.GENERAL_SALE.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getQuantity(), 0)).reduce(0, Integer::sum);
Integer cusRefundQuantity = cusSaleList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.SALE_REFUND.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getQuantity(), 0)).reduce(0, Integer::sum);
Integer cusTotalQuantity = cusSaleQuantity + cusRefundQuantity;
String refundRate = cusSaleQuantity > 0 ? String.format("%.2f", Math.abs(cusRefundQuantity) * 100.0 / cusSaleQuantity) : "0.00%";
retCusSaleList.add(new StoreTodaySaleSummaryDTO.STSSCusSaleDTO().setStoreCusName(cusName).setSaleAmount(cusSaleAmount)
.setRefundAmount(cusRefundAmount).setTotalAmount(cusTotalAmount).setSaleQuantity(cusSaleQuantity)
.setRefundQuantity(cusRefundQuantity).setTotalQuantity(cusTotalQuantity).setRefundRate(refundRate));
});
return retCusSaleList;
}
private List<StoreTodaySaleSummaryDTO.STSSProdSaleDTO> getProdSaleSummary(List<StoreSaleDetail> saleDetailList) {
List<StoreTodaySaleSummaryDTO.STSSProdSaleDTO> retProdSaleList = new ArrayList<>();
// 货号维度 销量map
Map<String, List<StoreSaleDetail>> artNumSaleGroupMap = saleDetailList.stream().collect(Collectors.groupingBy(StoreSaleDetail::getProdArtNum));
// 货号 颜色 维度销量map
Map<String, Map<String, List<StoreSaleDetail>>> artNumColorSaleGroupMap = saleDetailList.stream().collect(Collectors
.groupingBy(StoreSaleDetail::getProdArtNum, Collectors.groupingBy(StoreSaleDetail::getColorName)));
return retProdSaleList;
}
}

View File

@ -12,7 +12,6 @@ import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.xkt.domain.*;
import com.ruoyi.xkt.dto.dailySale.StoreTodaySaleDTO;
import com.ruoyi.xkt.dto.storeCustomer.StoreCusGeneralSaleDTO;
import com.ruoyi.xkt.dto.storeProductStock.StoreProdStockDTO;
import com.ruoyi.xkt.dto.storeSale.*;
@ -145,41 +144,6 @@ public class StoreSaleServiceImpl implements IStoreSaleService {
return list.size();
}
/**
*
*
* @param storeId ID
* @return StoreTodaySaleDTO
*/
@Override
@Transactional(readOnly = true)
public StoreTodaySaleDTO getTodaySale(Long storeId) {
// 用户是否为档口管理者或子账户
if (!SecurityUtils.isAdmin() && !SecurityUtils.isStoreManagerOrSub(storeId)) {
throw new ServiceException("当前用户非档口管理者或子账号,无权限操作!", HttpStatus.ERROR);
}
// 今天 yyyy-MM-dd
Date date = java.sql.Date.valueOf(LocalDate.now());
List<StoreSaleDetail> saleDetailList = this.storeSaleDetailMapper.selectList(new LambdaQueryWrapper<StoreSaleDetail>()
.eq(StoreSaleDetail::getStoreId, storeId).eq(StoreSaleDetail::getDelFlag, Constants.UNDELETED)
.eq(StoreSaleDetail::getVoucherDate, date));
if (CollectionUtils.isEmpty(saleDetailList)) {
return new StoreTodaySaleDTO();
}
Integer saleQuantity = saleDetailList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.GENERAL_SALE.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getQuantity(), 0)).reduce(0, Integer::sum);
BigDecimal saleAmount = saleDetailList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.GENERAL_SALE.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getAmount(), BigDecimal.ZERO)).reduce(BigDecimal.ZERO, BigDecimal::add);
Integer refundQuantity = saleDetailList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.SALE_REFUND.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getQuantity(), 0)).reduce(0, Integer::sum);
BigDecimal refundAmount = saleDetailList.stream().filter(x -> Objects.equals(x.getSaleType(), SaleType.SALE_REFUND.getValue()))
.map(x -> ObjectUtils.defaultIfNull(x.getAmount(), BigDecimal.ZERO)).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal amount = saleDetailList.stream().map(x -> ObjectUtils.defaultIfNull(x.getAmount(), BigDecimal.ZERO)).reduce(BigDecimal.ZERO, BigDecimal::add);
Integer quantity = saleDetailList.stream().map(x -> ObjectUtils.defaultIfNull(x.getQuantity(), 0)).reduce(0, Integer::sum);
return new StoreTodaySaleDTO().setStoreId(storeId).setSaleQuantity(saleQuantity).setSaleAmount(saleAmount)
.setRefundQuantity(refundQuantity).setRefundAmount(refundAmount).setAmount(amount).setQuantity(quantity);
}
/**
*
*