ソースを参照

收银台功能 营业额功能开发+订单表实体类字段

liudongzhi 2 週間 前
コミット
fdcefd3488

+ 5 - 1
alien-entity/src/main/java/shop/alien/entity/store/StoreOrder.java

@@ -111,7 +111,7 @@ public class StoreOrder {
     @TableField("pay_amount")
     private BigDecimal payAmount;
 
-    @ApiModelProperty(value = "支付方式(1:微信, 2:支付宝, 3:现金)")
+    @ApiModelProperty(value = "支付方式(1:微信, 2:支付宝, 3:现金 ,4:银行卡)")
     @TableField("pay_type")
     private Integer payType;
 
@@ -154,4 +154,8 @@ public class StoreOrder {
     @ApiModelProperty(value = "修改人ID")
     @TableField("updated_user_id")
     private Integer updatedUserId;
+
+    @ApiModelProperty(value = "收款方式 1.手机支付 2.收银台")
+    @TableField("payment_method")
+    private Integer paymentMethod;
 }

+ 28 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/TurnoverBreakdownItemVO.java

@@ -0,0 +1,28 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+@ApiModel(value = "TurnoverBreakdownItemVO", description = "营业额明细-分项占比")
+public class TurnoverBreakdownItemVO {
+
+	@ApiModelProperty("分类编码:payType 或 paymentMethod")
+	private Integer code;
+
+	@ApiModelProperty("分类名称")
+	private String name;
+
+	@ApiModelProperty("成交金额")
+	private BigDecimal amount;
+
+	@ApiModelProperty("交易笔数")
+	private Integer count;
+
+	@ApiModelProperty("占比(0-100,保留两位小数)")
+	private BigDecimal percent;
+}
+

+ 37 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/TurnoverDetailItemVO.java

@@ -0,0 +1,37 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@ApiModel(value = "TurnoverDetailItemVO", description = "营业额对账-明细记录")
+public class TurnoverDetailItemVO {
+
+	@ApiModelProperty("订单号")
+	private String orderNo;
+
+	@ApiModelProperty("实付金额")
+	private BigDecimal payAmount;
+
+	@ApiModelProperty("支付时间")
+	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+	private Date payTime;
+
+	@ApiModelProperty("支付方式(1:微信, 2:支付宝, 3:现金 ,4:银行卡)")
+	private Integer payType;
+
+	@ApiModelProperty("支付方式名称")
+	private String payTypeName;
+
+	@ApiModelProperty("收款方式 1.手机支付 2.收银台")
+	private Integer paymentMethod;
+
+	@ApiModelProperty("收款方式名称")
+	private String paymentMethodName;
+}
+

+ 29 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/TurnoverSummaryVO.java

@@ -0,0 +1,29 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Data
+@ApiModel(value = "TurnoverSummaryVO", description = "营业额对账-汇总数据")
+public class TurnoverSummaryVO {
+
+    @ApiModelProperty("总销售额")
+    private BigDecimal totalSalesAmount;
+
+    @ApiModelProperty("交易笔数")
+    private Integer totalOrderCount;
+
+    @ApiModelProperty("平均客单价(总销售额/交易笔数)")
+    private BigDecimal averageOrderAmount;
+
+    @ApiModelProperty("按支付方式分布:微信/支付宝/银行卡/现金")
+    private List<TurnoverBreakdownItemVO> payTypeBreakdown;
+
+    @ApiModelProperty("按收款方式分布:手机支付/收银台")
+    private List<TurnoverBreakdownItemVO> receiveMethodBreakdown;
+}
+

+ 64 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreTurnoverController.java

@@ -0,0 +1,64 @@
+package shop.alien.store.controller;
+
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.vo.TurnoverDetailItemVO;
+import shop.alien.entity.store.vo.TurnoverSummaryVO;
+import shop.alien.store.service.StoreTurnoverService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+import java.util.Date;
+
+@Slf4j
+@Api(tags = {"二期-营业额对账"})
+@ApiSort(20)
+@CrossOrigin
+@RestController
+@RequestMapping("/store/turnover")
+@RequiredArgsConstructor
+public class StoreTurnoverController {
+
+	private final StoreTurnoverService storeTurnoverService;
+
+	@ApiOperation("营业额汇总")
+	@ApiImplicitParams({
+		@ApiImplicitParam(name = "storeId", value = "门店ID", required = true, dataType = "Integer", paramType = "query"),
+		@ApiImplicitParam(name = "startTime", value = "开始时间 yyyy-MM-dd HH:mm:ss", dataType = "String", paramType = "query"),
+		@ApiImplicitParam(name = "endTime", value = "结束时间 yyyy-MM-dd HH:mm:ss", dataType = "String", paramType = "query")
+	})
+	@GetMapping("/summary")
+	public R<TurnoverSummaryVO> summary(@RequestParam Integer storeId,
+	                                    @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
+	                                    @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime) {
+		log.info("StoreTurnoverController.summary storeId={}, start={}, end={}", storeId, startTime, endTime);
+		return storeTurnoverService.summary(storeId, startTime, endTime);
+	}
+
+	@ApiOperation("支付方式/收款方式明细列表")
+	@ApiImplicitParams({
+		@ApiImplicitParam(name = "storeId", value = "门店ID", required = true, dataType = "Integer", paramType = "query"),
+		@ApiImplicitParam(name = "payType", value = "支付方式(1微信 2支付宝 4银行卡 3现金)", dataType = "Integer", paramType = "query"),
+		@ApiImplicitParam(name = "paymentMethod", value = "收款方式(1手机支付 2收银台)", dataType = "Integer", paramType = "query"),
+		@ApiImplicitParam(name = "startTime", value = "开始时间 yyyy-MM-dd HH:mm:ss", dataType = "String", paramType = "query"),
+		@ApiImplicitParam(name = "endTime", value = "结束时间 yyyy-MM-dd HH:mm:ss", dataType = "String", paramType = "query"),
+		@ApiImplicitParam(name = "pageNum", value = "页码", dataType = "Integer", paramType = "query"),
+		@ApiImplicitParam(name = "pageSize", value = "页大小", dataType = "Integer", paramType = "query")
+	})
+	@GetMapping("/details")
+	public R<IPage<TurnoverDetailItemVO>> details(@RequestParam Integer storeId,
+	                                              @RequestParam(required = false) Integer payType,
+	                                              @RequestParam(required = false) Integer paymentMethod,
+	                                              @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
+	                                              @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime,
+	                                              @RequestParam(defaultValue = "1") Integer pageNum,
+	                                              @RequestParam(defaultValue = "10") Integer pageSize) {
+		log.info("StoreTurnoverController.details storeId={}, payType={}, paymentMethod={}, start={}, end={}, pageNum={}, pageSize={}",
+			storeId, payType, paymentMethod, startTime, endTime, pageNum, pageSize);
+		return storeTurnoverService.details(storeId, payType, paymentMethod, startTime, endTime, pageNum, pageSize);
+	}
+}
+

+ 17 - 0
alien-store/src/main/java/shop/alien/store/service/StoreTurnoverService.java

@@ -0,0 +1,17 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.vo.TurnoverDetailItemVO;
+import shop.alien.entity.store.vo.TurnoverSummaryVO;
+
+import java.util.Date;
+
+public interface StoreTurnoverService {
+
+	R<TurnoverSummaryVO> summary(Integer storeId, Date startTime, Date endTime);
+
+	R<IPage<TurnoverDetailItemVO>> details(Integer storeId, Integer payType, Integer paymentMethod,
+	                                       Date startTime, Date endTime, Integer pageNum, Integer pageSize);
+}
+

+ 178 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreTurnoverServiceImpl.java

@@ -0,0 +1,178 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreOrder;
+import shop.alien.entity.store.vo.TurnoverBreakdownItemVO;
+import shop.alien.entity.store.vo.TurnoverDetailItemVO;
+import shop.alien.entity.store.vo.TurnoverSummaryVO;
+import shop.alien.mapper.StoreOrderMapper;
+import shop.alien.store.service.StoreTurnoverService;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class StoreTurnoverServiceImpl implements StoreTurnoverService {
+
+	private final StoreOrderMapper storeOrderMapper;
+
+	@Override
+	public R<TurnoverSummaryVO> summary(Integer storeId, Date startTime, Date endTime) {
+		LambdaQueryWrapper<StoreOrder> qw = basePaidQuery(storeId, startTime, endTime);
+		List<StoreOrder> orders = storeOrderMapper.selectList(qw);
+
+		BigDecimal totalSales = orders.stream()
+			.map(StoreOrder::getPayAmount)
+			.filter(Objects::nonNull)
+			.reduce(BigDecimal.ZERO, BigDecimal::add);
+		int orderCount = orders.size();
+		BigDecimal avg = orderCount == 0 ? BigDecimal.ZERO :
+			totalSales.divide(BigDecimal.valueOf(orderCount), 2, RoundingMode.HALF_UP);
+
+		// 支付方式分布
+		Map<Integer, String> payTypeName = new HashMap<>();
+		payTypeName.put(1, "微信");
+		payTypeName.put(2, "支付宝");
+		payTypeName.put(4, "银行卡");
+		payTypeName.put(3, "现金");
+
+		List<TurnoverBreakdownItemVO> payTypeItems = buildBreakdown(
+			groupByAndSum(orders, StoreOrder::getPayType),
+			payTypeName,
+			totalSales
+		);
+
+		// 收款方式分布
+		Map<Integer, String> receiveName = new HashMap<>();
+		receiveName.put(1, "手机支付");
+		receiveName.put(2, "收银台");
+		List<TurnoverBreakdownItemVO> receiveItems = buildBreakdown(
+			groupByAndSum(orders, StoreOrder::getPaymentMethod),
+			receiveName,
+			totalSales
+		);
+
+		TurnoverSummaryVO vo = new TurnoverSummaryVO();
+		vo.setTotalSalesAmount(scale2(totalSales));
+		vo.setTotalOrderCount(orderCount);
+		vo.setAverageOrderAmount(scale2(avg));
+		vo.setPayTypeBreakdown(payTypeItems);
+		vo.setReceiveMethodBreakdown(receiveItems);
+		return R.data(vo);
+	}
+
+	@Override
+	public R<IPage<TurnoverDetailItemVO>> details(Integer storeId, Integer payType, Integer paymentMethod,
+	                                              Date startTime, Date endTime, Integer pageNum, Integer pageSize) {
+		LambdaQueryWrapper<StoreOrder> qw = basePaidQuery(storeId, startTime, endTime);
+		if (payType != null) {
+			qw.eq(StoreOrder::getPayType, payType);
+		}
+		if (paymentMethod != null) {
+			qw.eq(StoreOrder::getPaymentMethod, paymentMethod);
+		}
+		qw.orderByDesc(StoreOrder::getPayTime).orderByDesc(StoreOrder::getId);
+		Page<StoreOrder> page = new Page<>(pageNum == null ? 1 : pageNum, pageSize == null ? 10 : pageSize);
+		IPage<StoreOrder> orderPage = storeOrderMapper.selectPage(page, qw);
+
+		Map<Integer, String> payTypeName = new HashMap<>();
+		payTypeName.put(1, "微信");
+		payTypeName.put(2, "支付宝");
+		payTypeName.put(4, "银行卡");
+		payTypeName.put(3, "现金");
+		Map<Integer, String> receiveName = new HashMap<>();
+		receiveName.put(1, "手机支付");
+		receiveName.put(2, "收银台");
+
+		List<TurnoverDetailItemVO> details = orderPage.getRecords().stream().map(o -> {
+			TurnoverDetailItemVO item = new TurnoverDetailItemVO();
+			item.setOrderNo(o.getOrderNo());
+			item.setPayAmount(scale2(o.getPayAmount() == null ? BigDecimal.ZERO : o.getPayAmount()));
+			item.setPayTime(o.getPayTime());
+			item.setPayType(o.getPayType());
+			item.setPayTypeName(payTypeName.getOrDefault(o.getPayType(), "-"));
+			item.setPaymentMethod(o.getPaymentMethod());
+			item.setPaymentMethodName(receiveName.getOrDefault(o.getPaymentMethod(), "-"));
+			return item;
+		}).collect(Collectors.toList());
+
+		Page<TurnoverDetailItemVO> result = new Page<>(orderPage.getCurrent(), orderPage.getSize(), orderPage.getTotal());
+		result.setRecords(details);
+		return R.data(result);
+	}
+
+	private LambdaQueryWrapper<StoreOrder> basePaidQuery(Integer storeId, Date startTime, Date endTime) {
+		LambdaQueryWrapper<StoreOrder> qw = new LambdaQueryWrapper<>();
+		qw.eq(StoreOrder::getStoreId, storeId)
+			.eq(StoreOrder::getPayStatus, 1)
+			.eq(StoreOrder::getOrderStatus, 1)
+			.eq(StoreOrder::getDeleteFlag, 0);
+		if (startTime != null) {
+			qw.ge(StoreOrder::getPayTime, startTime);
+		}
+		if (endTime != null) {
+			qw.le(StoreOrder::getPayTime, endTime);
+		}
+		return qw;
+	}
+
+	private Map<Integer, List<StoreOrder>> groupBy(List<StoreOrder> orders, java.util.function.Function<StoreOrder, Integer> classifier) {
+		if (CollectionUtils.isEmpty(orders)) {
+			return Collections.emptyMap();
+		}
+		return orders.stream().collect(Collectors.groupingBy(classifier));
+	}
+
+	private List<TurnoverBreakdownItemVO> buildBreakdown(Map<Integer, SumCount> map, Map<Integer, String> nameMap, BigDecimal total) {
+		List<TurnoverBreakdownItemVO> list = new ArrayList<>();
+		for (Map.Entry<Integer, SumCount> e : map.entrySet()) {
+			Integer code = e.getKey();
+			SumCount sc = e.getValue();
+			TurnoverBreakdownItemVO item = new TurnoverBreakdownItemVO();
+			item.setCode(code);
+			item.setName(nameMap.getOrDefault(code, "-"));
+			item.setAmount(scale2(sc.amount));
+			item.setCount(sc.count);
+			item.setPercent(total.signum() == 0 ? BigDecimal.ZERO :
+				sc.amount.multiply(BigDecimal.valueOf(100)).divide(total, 2, RoundingMode.HALF_UP));
+			list.add(item);
+		}
+		// 保持稳定顺序:根据 code 升序
+		list.sort(Comparator.comparing(TurnoverBreakdownItemVO::getCode, Comparator.nullsLast(Integer::compareTo)));
+		return list;
+	}
+
+	private Map<Integer, SumCount> groupByAndSum(List<StoreOrder> orders, java.util.function.Function<StoreOrder, Integer> classifier) {
+		Map<Integer, SumCount> result = new HashMap<>();
+		for (StoreOrder o : orders) {
+			Integer key = classifier.apply(o);
+			if (key == null) {
+				continue;
+			}
+			SumCount sc = result.computeIfAbsent(key, k -> new SumCount());
+			BigDecimal amt = o.getPayAmount() == null ? BigDecimal.ZERO : o.getPayAmount();
+			sc.amount = sc.amount.add(amt);
+			sc.count++;
+		}
+		return result;
+	}
+
+	private BigDecimal scale2(BigDecimal v) {
+		return v == null ? BigDecimal.ZERO : v.setScale(2, RoundingMode.HALF_UP);
+	}
+
+	private static class SumCount {
+		private BigDecimal amount = BigDecimal.ZERO;
+		private int count = 0;
+	}
+}
+