Compare commits
5 Commits
a8507deb2e
...
wyn
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c86cc6a0d | |||
| 84cdf95978 | |||
| b54476419b | |||
| 68d6b88695 | |||
| e1ff7d4b9e |
@@ -31,6 +31,7 @@ import com.peanut.modules.common.dao.UserCourseBuyDao;
|
|||||||
import com.peanut.modules.common.entity.*;
|
import com.peanut.modules.common.entity.*;
|
||||||
import com.peanut.modules.common.service.*;
|
import com.peanut.modules.common.service.*;
|
||||||
import com.peanut.modules.master.service.CourseCatalogueService;
|
import com.peanut.modules.master.service.CourseCatalogueService;
|
||||||
|
import com.peanut.modules.master.service.InventoryManagementService;
|
||||||
import com.peanut.modules.pay.alipay.dto.ReFundDTO;
|
import com.peanut.modules.pay.alipay.dto.ReFundDTO;
|
||||||
import com.peanut.modules.pay.alipay.service.AliPayService;
|
import com.peanut.modules.pay.alipay.service.AliPayService;
|
||||||
import com.peanut.modules.pay.weChatPay.dto.WeChatRefundInfo;
|
import com.peanut.modules.pay.weChatPay.dto.WeChatRefundInfo;
|
||||||
@@ -127,6 +128,8 @@ public class BuyOrderController {
|
|||||||
private BuyOrderRefundLogService buyOrderRefundLogService;
|
private BuyOrderRefundLogService buyOrderRefundLogService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private AliPayService aliPayService;
|
private AliPayService aliPayService;
|
||||||
|
@Autowired
|
||||||
|
private InventoryManagementService inventoryManagementService;
|
||||||
|
|
||||||
@RequestMapping(value = "/decomposeShipment", method = RequestMethod.POST)
|
@RequestMapping(value = "/decomposeShipment", method = RequestMethod.POST)
|
||||||
public R decomposeShipment(@RequestBody BuyOrderListRequestVo requestVo) {
|
public R decomposeShipment(@RequestBody BuyOrderListRequestVo requestVo) {
|
||||||
@@ -445,7 +448,7 @@ public class BuyOrderController {
|
|||||||
price = shopProductService.getVipPrice(product);
|
price = shopProductService.getVipPrice(product);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!handleStock(buyOrderProduct, product)) {
|
if (!handleStock(buyOrderProduct, product, buyOrder)) {
|
||||||
return R.error(500, "库存不足");
|
return R.error(500, "库存不足");
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotEmpty(mechStr)){
|
if (StringUtils.isNotEmpty(mechStr)){
|
||||||
@@ -511,6 +514,16 @@ public class BuyOrderController {
|
|||||||
buyOrder.setOrderStatus("0");
|
buyOrder.setOrderStatus("0");
|
||||||
buyOrderService.save(buyOrder);
|
buyOrderService.save(buyOrder);
|
||||||
|
|
||||||
|
// 订单已有 orderId 后再写 inv 出库流水(只记 order_id,不记 order_sn)
|
||||||
|
for (BuyOrderProduct buyOrderProduct : buyOrderProductList) {
|
||||||
|
ShopProduct product = shopProductService.getById(buyOrderProduct.getProductId());
|
||||||
|
if (product != null && isBookProduct(product)
|
||||||
|
&& inventoryManagementService.hasInvStockRecord(buyOrderProduct.getProductId(), buyOrder, product)) {
|
||||||
|
inventoryManagementService.saleByBookOrder(
|
||||||
|
buyOrderProduct.getProductId(), buyOrderProduct.getQuantity(), buyOrder, product);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//解决购物车相关问题
|
//解决购物车相关问题
|
||||||
for (BuyOrderProduct buyOrderProduct : buyOrderProductList) {
|
for (BuyOrderProduct buyOrderProduct : buyOrderProductList) {
|
||||||
buyOrderProduct.setOrderId(buyOrder.getOrderId());
|
buyOrderProduct.setOrderId(buyOrder.getOrderId());
|
||||||
@@ -1259,6 +1272,14 @@ public class BuyOrderController {
|
|||||||
return (activityPrice == null || activityPrice.equals(BigDecimal.ZERO)) ? product.getPrice() : activityPrice;
|
return (activityPrice == null || activityPrice.equals(BigDecimal.ZERO)) ? product.getPrice() : activityPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isBookProduct(ShopProduct product) {
|
||||||
|
if (product == null || product.getGoodsType() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
System.out.println("=======GoodsType======"+product.getGoodsType());
|
||||||
|
return "02".equals(product.getGoodsType()) || "03".equals(product.getGoodsType()) || "04".equals(product.getGoodsType());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理商品库存
|
* 处理商品库存
|
||||||
* TODO 新版本上线后删除此方法
|
* TODO 新版本上线后删除此方法
|
||||||
@@ -1295,6 +1316,27 @@ public class BuyOrderController {
|
|||||||
shopProductService.updateById(product);
|
shopProductService.updateById(product);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
private boolean handleStock(BuyOrderProduct buyOrderProduct, ShopProduct product, BuyOrder buyOrder) {
|
||||||
|
int quantity = buyOrderProduct.getQuantity();
|
||||||
|
if (product.getProductStock() - quantity < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isBookProduct(product)) {
|
||||||
|
if (inventoryManagementService.hasInvStockRecord(buyOrderProduct.getProductId(), buyOrder, product)) {
|
||||||
|
Boolean hasProduct = inventoryManagementService.validateBookInvStock(
|
||||||
|
buyOrderProduct.getProductId(), quantity, buyOrder, product);
|
||||||
|
if (!hasProduct) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// inv 出库放到 buyOrder 落库后执行,才能写入 order_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
product.setProductStock(product.getProductStock() - quantity);
|
||||||
|
product.setSumSales(product.getSumSales() + quantity);
|
||||||
|
shopProductService.updateById(product);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.peanut.modules.book.service;
|
||||||
|
|
||||||
|
public interface BuyOrderBatchDeliveryAsyncService {
|
||||||
|
|
||||||
|
void executeBatchDelivery(Integer taskId);
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import com.peanut.modules.book.vo.response.*;
|
|||||||
import com.peanut.modules.common.entity.*;
|
import com.peanut.modules.common.entity.*;
|
||||||
import com.peanut.modules.common.service.*;
|
import com.peanut.modules.common.service.*;
|
||||||
import com.peanut.modules.common.vo.CourseCatalogueVo;
|
import com.peanut.modules.common.vo.CourseCatalogueVo;
|
||||||
|
import com.peanut.modules.master.service.InventoryManagementService;
|
||||||
import com.peanut.modules.oss.service.OssService;
|
import com.peanut.modules.oss.service.OssService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -125,6 +126,8 @@ public class BuyOrderServiceImpl extends ServiceImpl<BuyOrderDao, BuyOrder> impl
|
|||||||
private BuyOrderBatchDeliveryItemDao batchDeliveryItemDao;
|
private BuyOrderBatchDeliveryItemDao batchDeliveryItemDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
private BuyOrderBatchDeliveryAsyncService batchDeliveryAsyncService;
|
private BuyOrderBatchDeliveryAsyncService batchDeliveryAsyncService;
|
||||||
|
@Autowired
|
||||||
|
private InventoryManagementService inventoryManagementService;
|
||||||
|
|
||||||
//private static final int BATCH_DELIVERY_MAX_SIZE = 100;
|
//private static final int BATCH_DELIVERY_MAX_SIZE = 100;
|
||||||
@Override
|
@Override
|
||||||
@@ -573,7 +576,7 @@ public class BuyOrderServiceImpl extends ServiceImpl<BuyOrderDao, BuyOrder> impl
|
|||||||
wrapper.eq(BuyOrder::getUserId,userOrderDto.getUserId());
|
wrapper.eq(BuyOrder::getUserId,userOrderDto.getUserId());
|
||||||
// wrapper.eq(BuyOrder::getOrderType,"order");//这里有点问题
|
// wrapper.eq(BuyOrder::getOrderType,"order");//这里有点问题
|
||||||
if(userOrderDto.getOrderStatus()==null){
|
if(userOrderDto.getOrderStatus()==null){
|
||||||
Integer[] sts = {0,1,2,3,6,7};
|
Integer[] sts = {0,1,2,3,6,7,5};
|
||||||
wrapper.in(BuyOrder::getOrderStatus,sts);
|
wrapper.in(BuyOrder::getOrderStatus,sts);
|
||||||
}else{
|
}else{
|
||||||
wrapper.eq(BuyOrder::getOrderStatus,userOrderDto.getOrderStatus());
|
wrapper.eq(BuyOrder::getOrderStatus,userOrderDto.getOrderStatus());
|
||||||
@@ -1228,6 +1231,8 @@ public class BuyOrderServiceImpl extends ServiceImpl<BuyOrderDao, BuyOrder> impl
|
|||||||
removeCourseToUser(buyOrder);
|
removeCourseToUser(buyOrder);
|
||||||
//回滚库存
|
//回滚库存
|
||||||
shopProductService.rollbackStock(buyOrder);
|
shopProductService.rollbackStock(buyOrder);
|
||||||
|
// 回滚 inv 售出流水:回加采购 remain_quantity,售出记录 del_flag=1
|
||||||
|
inventoryManagementService.rollbackSaleByOrderId(buyOrder.getOrderId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.peanut.modules.common.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.peanut.modules.common.entity.BuyOrderBatchDeliveryItem;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface BuyOrderBatchDeliveryItemDao extends BaseMapper<BuyOrderBatchDeliveryItem> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.peanut.modules.common.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.peanut.modules.common.entity.BuyOrderBatchDeliveryTask;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface BuyOrderBatchDeliveryTaskDao extends BaseMapper<BuyOrderBatchDeliveryTask> {
|
||||||
|
|
||||||
|
@Select("<script>" +
|
||||||
|
"SELECT i.order_id FROM buy_order_batch_delivery_item i " +
|
||||||
|
"INNER JOIN buy_order_batch_delivery_task t ON t.id = i.task_id " +
|
||||||
|
"WHERE t.status IN (0, 1) AND i.status IN (0, 1) AND i.order_id IN " +
|
||||||
|
"<foreach collection='orderIds' item='id' open='(' separator=',' close=')'>" +
|
||||||
|
"#{id}" +
|
||||||
|
"</foreach>" +
|
||||||
|
"</script>")
|
||||||
|
List<Integer> findBusyOrderIds(@Param("orderIds") List<Integer> orderIds);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.peanut.modules.common.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("buy_order_batch_delivery_item")
|
||||||
|
public class BuyOrderBatchDeliveryItem {
|
||||||
|
|
||||||
|
public static final int STATUS_PENDING = 0;
|
||||||
|
public static final int STATUS_SUCCESS = 1;
|
||||||
|
public static final int STATUS_FAILED = 2;
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer id;
|
||||||
|
private Integer taskId;
|
||||||
|
private Integer orderId;
|
||||||
|
private String orderSn;
|
||||||
|
private Integer buyOrderProductId;
|
||||||
|
/** 0待处理 1成功 2失败 */
|
||||||
|
private Integer status;
|
||||||
|
private String failMessage;
|
||||||
|
private Date createTime;
|
||||||
|
private Date updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.peanut.modules.common.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("buy_order_batch_delivery_task")
|
||||||
|
public class BuyOrderBatchDeliveryTask {
|
||||||
|
|
||||||
|
public static final int STATUS_PENDING = 0;
|
||||||
|
public static final int STATUS_RUNNING = 1;
|
||||||
|
public static final int STATUS_COMPLETED = 2;
|
||||||
|
public static final int STATUS_FAILED = 3;
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer id;
|
||||||
|
private String expressCompanyCode;
|
||||||
|
private Integer totalCount;
|
||||||
|
private Integer successCount;
|
||||||
|
private Integer failCount;
|
||||||
|
/** 0待处理 1处理中 2已完成 3异常终止 */
|
||||||
|
private Integer status;
|
||||||
|
private String failMessage;
|
||||||
|
private Date createTime;
|
||||||
|
private Date updateTime;
|
||||||
|
private Date finishTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.peanut.modules.common.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("inv_stock_record")
|
||||||
|
public class InvStockRecord {
|
||||||
|
|
||||||
|
public static final int BIZ_TYPE_PURCHASE = 1;
|
||||||
|
public static final int BIZ_TYPE_SALE = 2;
|
||||||
|
|
||||||
|
/** 采购来源:灵枢 */
|
||||||
|
public static final String SOURCE_LS = "ls";
|
||||||
|
/** 采购来源:众秒 */
|
||||||
|
public static final String SOURCE_ZM = "zm";
|
||||||
|
/** 采购来源:其他 */
|
||||||
|
public static final String SOURCE_OTHER = "other";
|
||||||
|
/** 采购来源:出版社 */
|
||||||
|
public static final String SOURCE_PUBLISHER = "publisher";
|
||||||
|
|
||||||
|
/** 出库类型:捐赠 */
|
||||||
|
public static final String OUTBOUND_DONATION = "donation";
|
||||||
|
/** 出库类型:淘宝 */
|
||||||
|
public static final String OUTBOUND_TAOBAO = "taobao";
|
||||||
|
/** 出库类型:APP */
|
||||||
|
public static final String OUTBOUND_APP = "App";
|
||||||
|
/** 出库类型:套书采购绑定(内部组装,导出售出明细时排除) */
|
||||||
|
public static final String OUTBOUND_BOOKS = "books";
|
||||||
|
|
||||||
|
public static final int MERCHANT_LS = 1;
|
||||||
|
public static final int MERCHANT_ZM = 2;
|
||||||
|
|
||||||
|
/** 订单类型:灵枢 */
|
||||||
|
public static final String ORDER_TYPE_LS = "lsorder";
|
||||||
|
/** 订单类型:众秒 */
|
||||||
|
public static final String ORDER_TYPE_ZM = "order";
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
private Integer productId;
|
||||||
|
/** 商户标识 1灵枢 2众秒 */
|
||||||
|
private Integer merchantId;
|
||||||
|
/**
|
||||||
|
* 采购来源:ls灵枢 zm众秒 other其他 publisher出版社;
|
||||||
|
* 售出时也可存出库类型(兼容)
|
||||||
|
*/
|
||||||
|
private String source;
|
||||||
|
/** 出库类型:donation捐赠 taobao淘宝 app,仅售出有值 */
|
||||||
|
private String outboundType;
|
||||||
|
/** 业务类型 1采购 2售出 */
|
||||||
|
private Integer bizType;
|
||||||
|
/** 数量:采购正数,售出负数 */
|
||||||
|
private Integer quantity;
|
||||||
|
/** 剩余库存:采购时等于 quantity,售出时从采购记录扣减 */
|
||||||
|
private Integer remainQuantity;
|
||||||
|
private BigDecimal unitCost;
|
||||||
|
/** 关联订单ID(图书订单售出时有值) */
|
||||||
|
private Integer orderId;
|
||||||
|
/** 平台/外部订单编号(淘宝等批量导入、图书订单售出时有值) */
|
||||||
|
private String orderSn;
|
||||||
|
/** 订单来源(图书订单售出时有值) */
|
||||||
|
private Integer come;
|
||||||
|
/** 出库所扣减的采购流水ID(售出时有值,退库时回加 remain_quantity) */
|
||||||
|
private Long purchaseRecordId;
|
||||||
|
/** 套书采购流水ID(套书绑定单品出库时有值,指向本次套书采购记录 id) */
|
||||||
|
private Long setPurchaseRecordId;
|
||||||
|
private String remark;
|
||||||
|
private Date createTime;
|
||||||
|
/** 0正常 1作废(订单超时取消等软删售出流水) */
|
||||||
|
@TableLogic
|
||||||
|
private Integer delFlag;
|
||||||
|
}
|
||||||
@@ -116,7 +116,9 @@ public class UserVipLogServiceImpl extends ServiceImpl<UserVipLogDao, UserVipLog
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public List<Map<String, Object>> getUserVipLogInfo(String date) {
|
public List<Map<String, Object>> getUserVipLogInfo(String date) {
|
||||||
|
// date:报表截止日期,如 2026-06-30;从 DAO 查出未删除 + 已退款(del_flag=-1)的 VIP 明细及摊销
|
||||||
List<Map<String, Object>> list = this.baseMapper.getUserVipLogInfo(date);
|
List<Map<String, Object>> list = this.baseMapper.getUserVipLogInfo(date);
|
||||||
|
// 以 uvlId 为 key,缓存「本月发生退款」的订单,供后续匹配主列表并生成退款行
|
||||||
Map<String, Map<String, Object>> refundMap = new HashMap<>();
|
Map<String, Map<String, Object>> refundMap = new HashMap<>();
|
||||||
for (Map<String, Object> refund : this.baseMapper.getMonthRefund(date)) {
|
for (Map<String, Object> refund : this.baseMapper.getMonthRefund(date)) {
|
||||||
Object uvlId = refund.get("uvlId");
|
Object uvlId = refund.get("uvlId");
|
||||||
@@ -124,37 +126,47 @@ public class UserVipLogServiceImpl extends ServiceImpl<UserVipLogDao, UserVipLog
|
|||||||
refundMap.put(uvlId.toString(), refund);
|
refundMap.put(uvlId.toString(), refund);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 最终返回给导出的明细列表(可能比 SQL 原始行数多,因退款会拆成「已付款行 + 已退款行」)
|
||||||
List<Map<String, Object>> result = new ArrayList<>();
|
List<Map<String, Object>> result = new ArrayList<>();
|
||||||
|
// 记录已在主列表中处理过的退款 uvlId,避免第二步重复追加
|
||||||
Set<String> matchedUvlIds = new HashSet<>();
|
Set<String> matchedUvlIds = new HashSet<>();
|
||||||
for (Map<String, Object> row : list) {
|
for (Map<String, Object> row : list) {
|
||||||
|
// 主列表默认标记为已付款
|
||||||
row.put("orderStatus", "已付款");
|
row.put("orderStatus", "已付款");
|
||||||
|
|
||||||
Object uvlId = row.get("uvlId");
|
Object uvlId = row.get("uvlId");
|
||||||
|
// 若该 VIP 记录在本月有退款,则 refund 非空
|
||||||
Map<String, Object> refund = uvlId == null ? null : refundMap.get(uvlId.toString());
|
Map<String, Object> refund = uvlId == null ? null : refundMap.get(uvlId.toString());
|
||||||
if (refund != null) {
|
if (refund != null) {
|
||||||
|
// 本月退款的订单:原「已付款」行当月/剩余摊销清零(退款单独成行体现)
|
||||||
row.put("currentTanxiao",BigDecimal.ZERO);
|
row.put("currentTanxiao",BigDecimal.ZERO);
|
||||||
row.put("notyetTanxiao",BigDecimal.ZERO);
|
row.put("notyetTanxiao",BigDecimal.ZERO);
|
||||||
String exportMonth = date.length() >= 7 ? date.substring(0, 7) : date;
|
String exportMonth = date.length() >= 7 ? date.substring(0, 7) : date;
|
||||||
String payMonth = getPayMonth(row);
|
String payMonth = getPayMonth(row);
|
||||||
|
// 本月付本月退:保留原已付款行(金额为正),再追加一条负金额退款行
|
||||||
if (exportMonth.equals(payMonth)){
|
if (exportMonth.equals(payMonth)){
|
||||||
result.add(row);
|
result.add(row);
|
||||||
}
|
}
|
||||||
matchedUvlIds.add(uvlId.toString());
|
matchedUvlIds.add(uvlId.toString());
|
||||||
|
// 追加「已退款」行,金额/摊销按 buildRefundRow 规则处理
|
||||||
result.add(buildRefundRow(row, refund, date));
|
result.add(buildRefundRow(row, refund, date));
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
|
// 无本月退款:原样加入结果
|
||||||
result.add(row);
|
result.add(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
//其他月份下单、指定月份退款:主列表里没有对应行(user_vip_log已删除),单独追加到列表最下面
|
// 其他月份下单、指定月份退款:主列表里没有对应行(user_vip_log已删除),单独追加到列表最下面
|
||||||
String exportMonth = date.length() >= 7 ? date.substring(0, 7) : date;
|
String exportMonth = date.length() >= 7 ? date.substring(0, 7) : date;
|
||||||
for (Map<String, Object> refund : refundMap.values()) {
|
for (Map<String, Object> refund : refundMap.values()) {
|
||||||
Object uvlId = refund.get("uvlId");
|
Object uvlId = refund.get("uvlId");
|
||||||
|
// 已在主列表匹配过的跳过
|
||||||
if (uvlId != null && matchedUvlIds.contains(uvlId.toString())) {
|
if (uvlId != null && matchedUvlIds.contains(uvlId.toString())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String payMonth = getPayMonth(refund);
|
String payMonth = getPayMonth(refund);
|
||||||
|
// 早月付款、本月退款且主列表无行:仅用 refund 数据构造一条退款行
|
||||||
if (!payMonth.isEmpty() && !exportMonth.equals(payMonth)) {
|
if (!payMonth.isEmpty() && !exportMonth.equals(payMonth)) {
|
||||||
result.add(buildRefundRow(refund, refund, date));
|
result.add(buildRefundRow(refund, refund, date));
|
||||||
}
|
}
|
||||||
@@ -162,18 +174,18 @@ public class UserVipLogServiceImpl extends ServiceImpl<UserVipLogDao, UserVipLog
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
public Map<String, Object> buildRefundRow(Map<String, Object> row, Map<String, Object> refund, String date) {
|
public Map<String, Object> buildRefundRow(Map<String, Object> row, Map<String, Object> refund, String date) {
|
||||||
|
// 复制原订单行作为退款行模板
|
||||||
Map<String, Object> refundRow = new HashMap<>(row);
|
Map<String, Object> refundRow = new HashMap<>(row);
|
||||||
log.info("n======o"+(refund.get("refund_no")==null?"":refund.get("refund_no").toString()));
|
|
||||||
refundRow.put("orderStatus", "已退款");
|
refundRow.put("orderStatus", "已退款");
|
||||||
|
// 支付时间改为退款时间,便于导出展示
|
||||||
refundRow.put("payTime", refund.get("refundTime"));
|
refundRow.put("payTime", refund.get("refundTime"));
|
||||||
refundRow.put("refund_no", refund.get("refund_no"));
|
refundRow.put("refund_no", refund.get("refund_no"));
|
||||||
String exportMonth = date.length() >= 7 ? date.substring(0, 7) : date;
|
String exportMonth = date.length() >= 7 ? date.substring(0, 7) : date;
|
||||||
String payMonth = getPayMonth(row);
|
String payMonth = getPayMonth(row);
|
||||||
BigDecimal dayAmount = toBigDecimal(row.get("dayAmount"));
|
BigDecimal dayAmount = toBigDecimal(row.get("dayAmount"));
|
||||||
log.info(date+":=====p:"+payMonth+",e:"+exportMonth+","+refund.get("orderSn").toString());
|
|
||||||
if (exportMonth.equals(payMonth)) {
|
if (exportMonth.equals(payMonth)) {
|
||||||
|
|
||||||
//当月订单当月退款:金额为负,当月/剩余摊销为0
|
// 当月订单当月退款:金额为负,当月/剩余摊销为0
|
||||||
refundRow.put("price", toBigDecimal(row.get("price")).negate());
|
refundRow.put("price", toBigDecimal(row.get("price")).negate());
|
||||||
refundRow.put("fee", toBigDecimal(row.get("fee")).negate());
|
refundRow.put("fee", toBigDecimal(row.get("fee")).negate());
|
||||||
refundRow.put("alreadyDays", 0);
|
refundRow.put("alreadyDays", 0);
|
||||||
@@ -183,7 +195,7 @@ public class UserVipLogServiceImpl extends ServiceImpl<UserVipLogDao, UserVipLog
|
|||||||
refundRow.put("currentTanxiao", BigDecimal.ZERO);
|
refundRow.put("currentTanxiao", BigDecimal.ZERO);
|
||||||
refundRow.put("notyetTanxiao", BigDecimal.ZERO);
|
refundRow.put("notyetTanxiao", BigDecimal.ZERO);
|
||||||
} else if (!payMonth.isEmpty() && !exportMonth.equals(payMonth)) {
|
} else if (!payMonth.isEmpty() && !exportMonth.equals(payMonth)) {
|
||||||
//其他月份下单、指定月份退款:订单金额为0,冲回已摊销部分
|
// 其他月份下单、指定月份退款:冲回截至上月末的已摊销,当月摊销为负的已摊销额
|
||||||
int alreadyDays = calcAlreadyDaysToPrevMonthEnd(row, date);
|
int alreadyDays = calcAlreadyDaysToPrevMonthEnd(row, date);
|
||||||
BigDecimal alreadyTanxiao = dayAmount.multiply(new BigDecimal(alreadyDays)).setScale(2, RoundingMode.HALF_UP);
|
BigDecimal alreadyTanxiao = dayAmount.multiply(new BigDecimal(alreadyDays)).setScale(2, RoundingMode.HALF_UP);
|
||||||
refundRow.put("price", toBigDecimal(row.get("price")).negate());
|
refundRow.put("price", toBigDecimal(row.get("price")).negate());
|
||||||
@@ -192,10 +204,12 @@ public class UserVipLogServiceImpl extends ServiceImpl<UserVipLogDao, UserVipLog
|
|||||||
refundRow.put("currentDays", 0);
|
refundRow.put("currentDays", 0);
|
||||||
refundRow.put("notyetDays", 0);
|
refundRow.put("notyetDays", 0);
|
||||||
refundRow.put("alreadyTanxiao", 0);
|
refundRow.put("alreadyTanxiao", 0);
|
||||||
|
// 当月摊销 = 负的「付款月至上月末」累计摊销
|
||||||
refundRow.put("currentTanxiao", alreadyTanxiao.negate());
|
refundRow.put("currentTanxiao", alreadyTanxiao.negate());
|
||||||
refundRow.put("notyetTanxiao", 0);
|
refundRow.put("notyetTanxiao", 0);
|
||||||
refundRow.put("startTime", refund.get("refundTime"));
|
refundRow.put("startTime", refund.get("refundTime"));
|
||||||
} else {
|
} else {
|
||||||
|
// 兜底:按原行已摊销冲回
|
||||||
refundRow.put("price", toBigDecimal(row.get("price")).negate());
|
refundRow.put("price", toBigDecimal(row.get("price")).negate());
|
||||||
refundRow.put("fee", toBigDecimal(row.get("fee")).negate());
|
refundRow.put("fee", toBigDecimal(row.get("fee")).negate());
|
||||||
refundRow.put("alreadyDays", row.get("alreadyDays"));
|
refundRow.put("alreadyDays", row.get("alreadyDays"));
|
||||||
|
|||||||
@@ -0,0 +1,250 @@
|
|||||||
|
package com.peanut.modules.master.controller;
|
||||||
|
|
||||||
|
import com.peanut.common.utils.R;
|
||||||
|
import com.peanut.modules.common.entity.InvStockRecord;
|
||||||
|
import com.peanut.modules.master.dto.inventory.InvStockRecordDto;
|
||||||
|
import com.peanut.modules.master.service.InventoryManagementService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 库存管理(采购、售出)
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController("inventoryManagement")
|
||||||
|
@RequestMapping("master/inventoryManagement")
|
||||||
|
public class InventoryManagementController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private InventoryManagementService inventoryManagementService;
|
||||||
|
|
||||||
|
@RequestMapping("/getStockRecordList")
|
||||||
|
public R getStockRecordList(@RequestBody Map<String, Object> params) {
|
||||||
|
return R.ok().put("result", inventoryManagementService.pageStockRecords(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/getStockRecordDetail")
|
||||||
|
public R getStockRecordDetail(@RequestBody Map<String, Object> params) {
|
||||||
|
return R.ok().put("result", inventoryManagementService.getStockRecordDetail(
|
||||||
|
Long.parseLong(params.get("id").toString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 采购/售出;
|
||||||
|
* 采购 source:ls灵枢 zm众秒 other其他 publisher出版社,默认 other;跨商户采购自动两步;
|
||||||
|
* 列表返回时 source 按 sys_dict_data(dict_label=inventory_source_type) 的 dict_type→dict_value 转换;
|
||||||
|
* 售出出库类型:outboundType 或 source,取自 sys_dict_data(dict_label=inventory_outbound_type) 的 dict_type
|
||||||
|
*/
|
||||||
|
@RequestMapping("/purchaseProduct")
|
||||||
|
public R purchaseProduct(@RequestBody InvStockRecordDto dto) {
|
||||||
|
try {
|
||||||
|
if (dto.getBiz_type() != null && dto.getBiz_type() == InvStockRecord.BIZ_TYPE_SALE) {
|
||||||
|
if ((dto.getOutboundType() == null || dto.getOutboundType().isBlank())
|
||||||
|
&& dto.getSource() != null && !dto.getSource().isBlank()) {
|
||||||
|
dto.setOutboundType(dto.getSource());
|
||||||
|
}
|
||||||
|
} else if (dto.getSource() == null || dto.getSource().isBlank()) {
|
||||||
|
dto.setSource(InvStockRecord.SOURCE_OTHER);
|
||||||
|
}
|
||||||
|
inventoryManagementService.purchaseProduct(dto);
|
||||||
|
return R.ok();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return R.error(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Excel批量售出;参数:merchantId、outboundType、file
|
||||||
|
* Excel列:create_time, productName, quantity, ordersn
|
||||||
|
* 任一条解析/匹配失败则全部不入库
|
||||||
|
*/
|
||||||
|
@RequestMapping("/batchOutbound")
|
||||||
|
public R batchSaleByExcel(@RequestParam("merchantId") Integer merchantId,
|
||||||
|
@RequestParam("outboundType") String outboundType,
|
||||||
|
@RequestParam("file") MultipartFile file) {
|
||||||
|
try {
|
||||||
|
int count = inventoryManagementService.batchSaleByExcel(merchantId, outboundType, file);
|
||||||
|
return R.ok().put("count", count);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return R.error(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/stockStatistics")
|
||||||
|
public R stockStatistics(@RequestBody Map<String, Object> params) {
|
||||||
|
if (params.get("merchantId") == null || params.get("merchantId").toString().isBlank()) {
|
||||||
|
return R.error("merchantId为必填");
|
||||||
|
}
|
||||||
|
return R.ok().put("result", inventoryManagementService.stockStatistics(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 采购/售出明细全量导出(忽略 current/limit);支持 merchantId、productName、startTime、endTime、bizType(1入库/2售出,可选) */
|
||||||
|
@RequestMapping("/exportStockRecordList")
|
||||||
|
public void exportStockRecordList(HttpServletResponse response, @RequestBody Map<String, Object> params) {
|
||||||
|
List<Map<String, Object>> maps = inventoryManagementService.exportStockRecordList(params);
|
||||||
|
Integer bizType = parseBizType(params.get("bizType"));
|
||||||
|
XSSFWorkbook wb = new XSSFWorkbook();
|
||||||
|
String fileName;
|
||||||
|
String merchantPrefix = merchantFilePrefix(params.get("merchantId"));
|
||||||
|
if (bizType != null && bizType == InvStockRecord.BIZ_TYPE_PURCHASE) {
|
||||||
|
writePurchaseExportSheet(wb, maps);
|
||||||
|
fileName = merchantPrefix + "入库明细.xlsx";
|
||||||
|
} else if (bizType != null && bizType == InvStockRecord.BIZ_TYPE_SALE) {
|
||||||
|
writeSaleExportSheet(wb, maps);
|
||||||
|
fileName = merchantPrefix + "售出明细.xlsx";
|
||||||
|
} else {
|
||||||
|
List<Map<String, Object>> purchaseRows = maps.stream()
|
||||||
|
.filter(map -> "purchase".equals(cellStr(map.get("exportCategory"))))
|
||||||
|
.toList();
|
||||||
|
List<Map<String, Object>> saleRows = maps.stream()
|
||||||
|
.filter(map -> "sale".equals(cellStr(map.get("exportCategory"))))
|
||||||
|
.toList();
|
||||||
|
writePurchaseExportSheet(wb, purchaseRows);
|
||||||
|
writeSaleExportSheet(wb, saleRows);
|
||||||
|
fileName = merchantPrefix + "采购售出明细.xlsx";
|
||||||
|
}
|
||||||
|
writeExcel(response, wb, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer parseBizType(Object bizTypeObj) {
|
||||||
|
if (bizTypeObj == null || bizTypeObj.toString().isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Integer.parseInt(bizTypeObj.toString().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writePurchaseExportSheet(XSSFWorkbook wb, List<Map<String, Object>> maps) {
|
||||||
|
Sheet sheet = wb.createSheet(wb.getNumberOfSheets() == 0 ? "入库明细" : "入库明细");
|
||||||
|
Row titleRow = sheet.createRow(0);
|
||||||
|
titleRow.createCell(0).setCellValue("商品ID");
|
||||||
|
titleRow.createCell(1).setCellValue("商品名称");
|
||||||
|
titleRow.createCell(2).setCellValue("供应商类型");
|
||||||
|
titleRow.createCell(3).setCellValue("入库时间");
|
||||||
|
titleRow.createCell(4).setCellValue("数量");
|
||||||
|
titleRow.createCell(5).setCellValue("剩余库存");
|
||||||
|
int rowIndex = 1;
|
||||||
|
for (Map<String, Object> map : maps) {
|
||||||
|
Row row = sheet.createRow(rowIndex++);
|
||||||
|
row.createCell(0).setCellValue(cellStr(map.get("productId")));
|
||||||
|
row.createCell(1).setCellValue(cellStr(map.get("productName")));
|
||||||
|
row.createCell(2).setCellValue(cellStr(map.get("sourceLabel")));
|
||||||
|
row.createCell(3).setCellValue(cellStr(map.get("createTime")));
|
||||||
|
row.createCell(4).setCellValue(cellStr(map.get("quantity")));
|
||||||
|
row.createCell(5).setCellValue(cellStr(map.get("remainQuantity")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeSaleExportSheet(XSSFWorkbook wb, List<Map<String, Object>> maps) {
|
||||||
|
String sheetName = wb.getNumberOfSheets() == 0 ? "售出明细" : "售出明细";
|
||||||
|
Sheet sheet = wb.createSheet(sheetName);
|
||||||
|
Row titleRow = sheet.createRow(0);
|
||||||
|
titleRow.createCell(0).setCellValue("商品ID");
|
||||||
|
titleRow.createCell(1).setCellValue("商品名称");
|
||||||
|
titleRow.createCell(2).setCellValue("出库类型");
|
||||||
|
titleRow.createCell(3).setCellValue("成本");
|
||||||
|
titleRow.createCell(4).setCellValue("数量");
|
||||||
|
titleRow.createCell(5).setCellValue("订单编号");
|
||||||
|
titleRow.createCell(6).setCellValue("出库时间");
|
||||||
|
titleRow.createCell(7).setCellValue("come");
|
||||||
|
titleRow.createCell(8).setCellValue("套书名称");
|
||||||
|
titleRow.createCell(9).setCellValue("套书商品ID");
|
||||||
|
titleRow.createCell(10).setCellValue("出库记录ID");
|
||||||
|
int rowIndex = 1;
|
||||||
|
for (Map<String, Object> map : maps) {
|
||||||
|
Row row = sheet.createRow(rowIndex++);
|
||||||
|
row.createCell(0).setCellValue(cellStr(map.get("productId")));
|
||||||
|
row.createCell(1).setCellValue(cellStr(map.get("productName")));
|
||||||
|
row.createCell(2).setCellValue(cellStr(map.get("outboundTypeLabel")));
|
||||||
|
row.createCell(3).setCellValue(cellStr(map.get("unitCost")));
|
||||||
|
row.createCell(4).setCellValue(cellStr(map.get("quantity")));
|
||||||
|
row.createCell(5).setCellValue(cellStr(map.get("orderSn")));
|
||||||
|
row.createCell(6).setCellValue(cellStr(map.get("createTime")));
|
||||||
|
row.createCell(7).setCellValue(cellStr(map.get("come")));
|
||||||
|
row.createCell(8).setCellValue(cellStr(map.get("setProductName")));
|
||||||
|
row.createCell(9).setCellValue(cellStr(map.get("setProductId")));
|
||||||
|
row.createCell(10).setCellValue(cellStr(map.get("stockRecordId")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 商品库存统计全量导出(忽略 current/limit);支持 merchantId、productName、startTime、endTime、isAlert */
|
||||||
|
@RequestMapping("/exportStockStatistics")
|
||||||
|
public void exportStockStatistics(HttpServletResponse response, @RequestBody Map<String, Object> params) {
|
||||||
|
List<Map<String, Object>> maps = inventoryManagementService.exportStockStatistics(params);
|
||||||
|
XSSFWorkbook wb = new XSSFWorkbook();
|
||||||
|
Sheet sheet = wb.createSheet("商品库存统计");
|
||||||
|
Row titleRow = sheet.createRow(0);
|
||||||
|
titleRow.createCell(0).setCellValue("商品名称");
|
||||||
|
titleRow.createCell(1).setCellValue("采购总数");
|
||||||
|
titleRow.createCell(2).setCellValue("售出总数");
|
||||||
|
titleRow.createCell(3).setCellValue("剩余库存");
|
||||||
|
titleRow.createCell(4).setCellValue("剩余比例");
|
||||||
|
int rowIndex = 1;
|
||||||
|
for (Map<String, Object> map : maps) {
|
||||||
|
Row row = sheet.createRow(rowIndex);
|
||||||
|
row.createCell(0).setCellValue(cellStr(map.get("productName")));
|
||||||
|
row.createCell(1).setCellValue(cellStr(map.get("purchaseTotal")));
|
||||||
|
row.createCell(2).setCellValue(cellStr(map.get("saleTotal")));
|
||||||
|
row.createCell(3).setCellValue(cellStr(map.get("remainTotal")));
|
||||||
|
row.createCell(4).setCellValue(cellStr(map.get("remainRate")));
|
||||||
|
rowIndex++;
|
||||||
|
}
|
||||||
|
writeExcel(response, wb, merchantFilePrefix(params.get("merchantId")) + "商品库存统计.xlsx");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** merchantId=1 → 灵枢;merchantId=2 → 众妙 */
|
||||||
|
private String merchantFilePrefix(Object merchantIdObj) {
|
||||||
|
if (merchantIdObj == null || merchantIdObj.toString().isBlank()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String merchantId = merchantIdObj.toString().trim();
|
||||||
|
if ("1".equals(merchantId)) {
|
||||||
|
return "灵枢";
|
||||||
|
}
|
||||||
|
if ("2".equals(merchantId)) {
|
||||||
|
return "众妙";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String cellStr(Object value) {
|
||||||
|
return value == null ? "" : value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeExcel(HttpServletResponse response, XSSFWorkbook wb, String fileName) {
|
||||||
|
OutputStream outputStream = null;
|
||||||
|
try {
|
||||||
|
fileName = URLEncoder.encode(fileName, "UTF-8");
|
||||||
|
response.setContentType("application/vnd.ms-excel");
|
||||||
|
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
|
||||||
|
outputStream = response.getOutputStream();
|
||||||
|
wb.write(outputStream);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("导出Excel失败", e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (outputStream != null) {
|
||||||
|
outputStream.flush();
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
wb.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("关闭Excel流失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -433,7 +433,7 @@ public class StatisticsController {
|
|||||||
row.createCell(20).setCellValue(cellStr(map.get("alreadyTanxiao")));
|
row.createCell(20).setCellValue(cellStr(map.get("alreadyTanxiao")));
|
||||||
row.createCell(21).setCellValue(cellStr(map.get("currentTanxiao")));
|
row.createCell(21).setCellValue(cellStr(map.get("currentTanxiao")));
|
||||||
row.createCell(22).setCellValue(cellStr(map.get("surplusTanxiao")));
|
row.createCell(22).setCellValue(cellStr(map.get("surplusTanxiao")));
|
||||||
row.createCell(22).setCellValue(cellStr(map.get("refund_no")));
|
row.createCell(23).setCellValue(cellStr(map.get("refund_no")));
|
||||||
//序号自增
|
//序号自增
|
||||||
cell++;
|
cell++;
|
||||||
}
|
}
|
||||||
@@ -616,7 +616,6 @@ public class StatisticsController {
|
|||||||
BigDecimal refundFeeToSR = BigDecimal.ZERO;
|
BigDecimal refundFeeToSR = BigDecimal.ZERO;
|
||||||
BigDecimal refundMonthTX = BigDecimal.ZERO;
|
BigDecimal refundMonthTX = BigDecimal.ZERO;
|
||||||
BigDecimal refundYetTX = BigDecimal.ZERO;
|
BigDecimal refundYetTX = BigDecimal.ZERO;
|
||||||
if(date.equals("2026-04-30"))log.info("currentTanxiao======="+map.get("currentTanxiao").toString());
|
|
||||||
for (Map<String,Object> one : monthRefundMap) {
|
for (Map<String,Object> one : monthRefundMap) {
|
||||||
String payMonth = userVipLogService.getPayMonth(one);
|
String payMonth = userVipLogService.getPayMonth(one);
|
||||||
if (exportMonth.equals(payMonth)) {
|
if (exportMonth.equals(payMonth)) {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.peanut.modules.master.dto.inventory;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class InvCostItemDto {
|
||||||
|
private BigDecimal unitCost;
|
||||||
|
private Integer quantity;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.peanut.modules.master.dto.inventory;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class InvStockRecordDto {
|
||||||
|
private Integer productId;
|
||||||
|
/** 商户标识 1灵枢 2众秒 */
|
||||||
|
private Integer merchantId;
|
||||||
|
/** 采购来源:ls灵枢 zm众秒 other其他 publisher出版社;售出时可兼作出库类型 */
|
||||||
|
private String source;
|
||||||
|
/** 出库类型(售出,可选):donation捐赠 taobao淘宝 app */
|
||||||
|
private String outboundType;
|
||||||
|
private Integer quantity;
|
||||||
|
private Integer biz_type;
|
||||||
|
private BigDecimal unitCost;
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.peanut.modules.master.service;
|
||||||
|
|
||||||
|
import com.alipay.api.domain.Product;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.peanut.modules.common.entity.BuyOrder;
|
||||||
|
import com.peanut.modules.common.entity.ShopProduct;
|
||||||
|
import com.peanut.modules.master.dto.inventory.InvStockRecordDto;
|
||||||
|
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface InventoryManagementService {
|
||||||
|
|
||||||
|
Page<Map<String, Object>> pageStockRecords(Map<String, Object> params);
|
||||||
|
|
||||||
|
Map<String, Object> getStockRecordDetail(Long id);
|
||||||
|
|
||||||
|
void purchaseProduct(InvStockRecordDto dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图书订单售出:写入 inv_stock_record 售出流水。
|
||||||
|
* 天医币支付固定扣众秒库存(merchant_id=2);其它支付按商品 payMerchant。
|
||||||
|
*/
|
||||||
|
void saleByBookOrder(Integer productId, int quantity, BuyOrder buyOrder, ShopProduct product);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* inv_stock_record 中是否存在该商品记录。
|
||||||
|
* 天医币支付只查众秒(merchant_id=2);其它支付查对应商户(无则任一商户有流水也算)。
|
||||||
|
*/
|
||||||
|
boolean hasInvStockRecord(Integer productId, BuyOrder buyOrder, ShopProduct product);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验图书库存流水:无记录则通过;有记录则剩余库存须不少于购买数量。
|
||||||
|
* 天医币支付固定校验众秒库存。
|
||||||
|
*/
|
||||||
|
boolean validateBookInvStock(Integer productId, int quantity, BuyOrder buyOrder, ShopProduct product);
|
||||||
|
|
||||||
|
Page<Map<String, Object>> stockStatistics(Map<String, Object> params);
|
||||||
|
|
||||||
|
List<Map<String, Object>> exportStockRecordList(Map<String, Object> params);
|
||||||
|
|
||||||
|
List<Map<String, Object>> exportStockStatistics(Map<String, Object> params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按订单回滚库存流水售出:回加采购批次 remain_quantity,售出记录 del_flag=1
|
||||||
|
*/
|
||||||
|
void rollbackSaleByOrderId(Integer orderId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Excel 批量售出:全部解析/匹配成功才入库,任一条失败则全部不入库
|
||||||
|
*
|
||||||
|
* @param merchantId 商户ID
|
||||||
|
* @param outboundType 出库类型 donation/taobao/app
|
||||||
|
* @param file Excel(列:create_time, productName, quantity, ordersn)
|
||||||
|
* @return 成功入库条数
|
||||||
|
*/
|
||||||
|
int batchSaleByExcel(Integer merchantId, String outboundType, MultipartFile file);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -3,17 +3,23 @@ package com.peanut.modules.mq.Consumer;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.peanut.config.Constants;
|
import com.peanut.config.Constants;
|
||||||
import com.peanut.config.DelayQueueConfig;
|
import com.peanut.config.DelayQueueConfig;
|
||||||
|
import com.peanut.modules.book.service.BuyOrderService;
|
||||||
import com.peanut.modules.common.dao.BuyOrderProductDao;
|
import com.peanut.modules.common.dao.BuyOrderProductDao;
|
||||||
|
import com.peanut.modules.common.dao.MyUserDao;
|
||||||
import com.peanut.modules.common.dao.ShopProductDao;
|
import com.peanut.modules.common.dao.ShopProductDao;
|
||||||
import com.peanut.modules.common.entity.BuyOrder;
|
import com.peanut.modules.common.entity.BuyOrder;
|
||||||
import com.peanut.modules.common.entity.BuyOrderProduct;
|
import com.peanut.modules.common.entity.BuyOrderProduct;
|
||||||
|
import com.peanut.modules.common.entity.MyUserEntity;
|
||||||
import com.peanut.modules.common.entity.ShopProduct;
|
import com.peanut.modules.common.entity.ShopProduct;
|
||||||
import com.peanut.modules.book.service.BuyOrderService;
|
|
||||||
import com.peanut.modules.common.service.CouponService;
|
import com.peanut.modules.common.service.CouponService;
|
||||||
|
import com.peanut.modules.master.service.InventoryManagementService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,6 +27,7 @@ import java.util.List;
|
|||||||
* @Author: Cauchy
|
* @Author: Cauchy
|
||||||
* @CreateTime: 2023/10/10
|
* @CreateTime: 2023/10/10
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class OrderCancelConsumer {
|
public class OrderCancelConsumer {
|
||||||
|
|
||||||
@@ -30,36 +37,70 @@ public class OrderCancelConsumer {
|
|||||||
BuyOrderProductDao buyOrderProductDao;
|
BuyOrderProductDao buyOrderProductDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
ShopProductDao shopProductDao;
|
ShopProductDao shopProductDao;
|
||||||
|
@Autowired
|
||||||
|
MyUserDao myUserDao;
|
||||||
|
@Autowired
|
||||||
|
InventoryManagementService inventoryManagementService;
|
||||||
|
@Autowired
|
||||||
|
CouponService couponService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 30分钟未支付取消。
|
||||||
|
* 临时限制:仅 tester_flag=1 的测试账号订单会执行取消与回滚,正式账号直接跳过。
|
||||||
|
*/
|
||||||
@RabbitListener(queues = DelayQueueConfig.ORDER_CANCEL_DEAD_LETTER_QUEUE)
|
@RabbitListener(queues = DelayQueueConfig.ORDER_CANCEL_DEAD_LETTER_QUEUE)
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void orderConsumer(String orderId) {
|
public void orderConsumer(String orderId) {
|
||||||
if (StringUtils.isNotEmpty(orderId)){
|
if (StringUtils.isEmpty(orderId)) {
|
||||||
BuyOrder buyOrder = buyOrderService.getById(orderId);
|
return;
|
||||||
if(buyOrder == null){
|
}
|
||||||
return;
|
BuyOrder buyOrder = buyOrderService.getById(orderId);
|
||||||
}
|
if (buyOrder == null) {
|
||||||
if(Constants.ORDER_STATUS_TO_BE_PAID.equals(buyOrder.getOrderStatus())){
|
return;
|
||||||
buyOrder.setOrderStatus(Constants.ORDER_STATUS_OUT_OF_TIME);
|
}
|
||||||
//回滚优惠卷
|
if (!Constants.ORDER_STATUS_TO_BE_PAID.equals(buyOrder.getOrderStatus())) {
|
||||||
if (buyOrder.getCouponId()!=null&&buyOrder.getCouponId()!=0){
|
return;
|
||||||
buyOrder.setCouponId(null);
|
}
|
||||||
}
|
/* // TODO 上线全量后删除此测试账号限制
|
||||||
//回滚库存
|
if (!isTesterOrder(buyOrder)) {
|
||||||
LambdaQueryWrapper<BuyOrderProduct> wrapper = new LambdaQueryWrapper<>();
|
log.info("订单超时取消跳过:非测试账号, orderId={}, userId={}", buyOrder.getOrderId(), buyOrder.getUserId());
|
||||||
wrapper.eq(BuyOrderProduct::getOrderId,buyOrder.getOrderId());
|
return;
|
||||||
List<BuyOrderProduct> buyOrderProducts = buyOrderProductDao.selectList(wrapper);
|
}*/
|
||||||
for (BuyOrderProduct b : buyOrderProducts){
|
buyOrder.setOrderStatus(Constants.ORDER_STATUS_OUT_OF_TIME);
|
||||||
ShopProduct shopProduct = shopProductDao.selectById(b.getProductId());
|
|
||||||
if (shopProduct!=null){
|
// 回滚优惠券(buy_order.coupon_id 存的是 coupon_history.id),保留订单 couponId 便于查单
|
||||||
shopProduct.setProductStock(shopProduct.getProductStock()+b.getQuantity());
|
Integer couponHistoryId = buyOrder.getCouponId();
|
||||||
shopProductDao.updateById(shopProduct);
|
if (couponHistoryId != null && couponHistoryId != 0) {
|
||||||
}
|
couponService.rollbackCoupon(couponHistoryId);
|
||||||
}
|
}
|
||||||
buyOrderService.updateById(buyOrder);
|
|
||||||
}
|
// 回滚商品库存与销量
|
||||||
if(Constants.ORDER_STATUS_OUT_OF_TIME.equals(buyOrder.getOrderStatus())){
|
LambdaQueryWrapper<BuyOrderProduct> wrapper = new LambdaQueryWrapper<>();
|
||||||
buyOrderService.removeById(buyOrder);
|
wrapper.eq(BuyOrderProduct::getOrderId, buyOrder.getOrderId());
|
||||||
|
List<BuyOrderProduct> buyOrderProducts = buyOrderProductDao.selectList(wrapper);
|
||||||
|
for (BuyOrderProduct b : buyOrderProducts) {
|
||||||
|
ShopProduct shopProduct = shopProductDao.selectById(b.getProductId());
|
||||||
|
if (shopProduct != null) {
|
||||||
|
int stock = shopProduct.getProductStock() == null ? 0 : shopProduct.getProductStock();
|
||||||
|
int sales = shopProduct.getSumSales() == null ? 0 : shopProduct.getSumSales();
|
||||||
|
int qty = b.getQuantity();
|
||||||
|
shopProduct.setProductStock(stock + qty);
|
||||||
|
shopProduct.setSumSales(Math.max(0, sales - qty));
|
||||||
|
shopProductDao.updateById(shopProduct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 回滚 inv 售出流水:回加采购 remain_quantity,售出记录 del_flag=1
|
||||||
|
inventoryManagementService.rollbackSaleByOrderId(buyOrder.getOrderId());
|
||||||
|
|
||||||
|
buyOrderService.updateById(buyOrder);
|
||||||
|
//buyOrderService.removeById(buyOrder);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private boolean isTesterOrder(BuyOrder buyOrder) {
|
||||||
|
if (buyOrder.getUserId() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MyUserEntity user = myUserDao.selectById(buyOrder.getUserId());
|
||||||
|
return user != null && user.getTesterFlag() != null && user.getTesterFlag() == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ public class WxpayServiceImpl extends ServiceImpl<PayWechatOrderDao, PayWechatOr
|
|||||||
payWechatOrderService.updateById(payWechatOrderEntity);
|
payWechatOrderService.updateById(payWechatOrderEntity);
|
||||||
// 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
|
// 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
|
||||||
BuyOrder order = this.buyOrderService.getOne(new QueryWrapper<BuyOrder>().eq("order_sn", orderNo));
|
BuyOrder order = this.buyOrderService.getOne(new QueryWrapper<BuyOrder>().eq("order_sn", orderNo));
|
||||||
if ("3".equals(order.getOrderStatus())){
|
if ("3".equals(order.getOrderStatus())||"6".equals(order.getOrderStatus())||"7".equals(order.getOrderStatus())){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//使用优惠券
|
//使用优惠券
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
select r.*
|
select r.*
|
||||||
,ROUND(fee-alreadyTanxiao-currentTanxiao,2) surplusTanxiao
|
,ROUND(fee-alreadyTanxiao-currentTanxiao,2) surplusTanxiao
|
||||||
from (select e.*
|
from (select e.*
|
||||||
,ROUND(if(alreadyDay+currentDay>=days,fee-alreadyTanxiao,if(alreadyTanxiao+currentDay*dayAmount>fee,fee-alreadyTanxiao,currentDay*dayAmount)),2) currentTanxiao
|
,ROUND(if(CHAR_LENGTH(IFNULL(endTime,'')) < 10, if(alreadyDay+currentDay>=days,fee-alreadyTanxiao,if(alreadyTanxiao+currentDay*dayAmount>fee,fee-alreadyTanxiao,currentDay*dayAmount)), if(DATEDIFF(#{date}, LEFT(endTime,10)) >= 0, 0, if(alreadyDay+currentDay>=days,fee-alreadyTanxiao,if(alreadyTanxiao+currentDay*dayAmount>fee,fee-alreadyTanxiao,currentDay*dayAmount)))),2) currentTanxiao
|
||||||
from (select w.name,w.tel,w.ctitle,w.cctitle,if(w.startTime is null,'',w.startTime) startTime,if(w.endTime is null,'',w.endTime) endTime,w.totalDays
|
from (select w.name,w.tel,w.ctitle,w.cctitle,if(w.startTime is null,'',w.startTime) startTime,if(w.endTime is null,'',w.endTime) endTime,w.totalDays
|
||||||
,w.type,w.payType,w.payTime,w.orderSn,w.zfbOrder,w.days,w.fee,w.remark,w.dayAmount,w.alreadyDay,w.currentDay
|
,w.type,w.payType,w.payTime,w.orderSn,w.zfbOrder,w.days,w.fee,w.remark,w.dayAmount,w.alreadyDay,w.currentDay
|
||||||
,IF(beginDay=0,startTime,IF(startTime is NULL,startTime,DATE_ADD(startTime,INTERVAL beginDay-1 day))) startTanxiaoTime
|
,IF(beginDay=0,startTime,IF(startTime is NULL,startTime,DATE_ADD(startTime,INTERVAL beginDay-1 day))) startTanxiaoTime
|
||||||
,ROUND(if(alreadyDay=days,fee,if(alreadyDayAmount>fee,fee,alreadyDayAmount)),2) alreadyTanxiao
|
,ROUND(if(CHAR_LENGTH(IFNULL(endTime,'')) < 10, if(alreadyDay=days,fee,if(alreadyDayAmount>fee,fee,alreadyDayAmount)), if(DATEDIFF(#{date}, LEFT(endTime,10)) >= 0, fee, if(alreadyDay=days,fee,if(alreadyDayAmount>fee,fee,alreadyDayAmount)))),2) alreadyTanxiao
|
||||||
from (
|
from (
|
||||||
select q.*,alreadyDay*dayAmount alreadyDayAmount
|
select q.*,alreadyDay*dayAmount alreadyDayAmount
|
||||||
,IF(days-alreadyDay=0,0,IF(beginDay=0,IF(startTime is NULL,0,IF(days-alreadyDay>=monthDays,if(DATE_FORMAT(startTime,'%Y-%m')=SUBSTR(#{date},1,7),DATEDIFF(#{date},startTime)+1,if(DATE_FORMAT(startTime,'%Y-%m')>SUBSTR(#{date},1,7),0,monthDays)),if(DATE_FORMAT(startTime, '%Y-%m')=SUBSTR(#{date},1,7),if(DATEDIFF(#{date},startTime)+1>days,days,DATEDIFF(#{date},startTime)+1),if(DATE_FORMAT(startTime, '%Y-%m') < SUBSTR(#{date},1,7),days-alreadyDay,0)))), if(alreadyTotalDay+monthDays>beginDay,if(alreadyDay>0,if(days-alreadyDay>monthDays,monthDays,monthDays-(days-alreadyDay)),monthDays-(totalDays-days-alreadyTotalDay)),0) )) currentDay
|
,IF(days-alreadyDay=0,0,IF(beginDay=0,IF(startTime is NULL,0,IF(days-alreadyDay>=monthDays,if(DATE_FORMAT(startTime,'%Y-%m')=SUBSTR(#{date},1,7),DATEDIFF(#{date},startTime)+1,if(DATE_FORMAT(startTime,'%Y-%m')>SUBSTR(#{date},1,7),0,monthDays)),if(DATE_FORMAT(startTime, '%Y-%m')=SUBSTR(#{date},1,7),if(DATEDIFF(#{date},startTime)+1>days,days,DATEDIFF(#{date},startTime)+1),if(DATE_FORMAT(startTime, '%Y-%m') < SUBSTR(#{date},1,7),days-alreadyDay,0)))), if(alreadyTotalDay+monthDays>beginDay,if(alreadyDay>0,if(days-alreadyDay>monthDays,monthDays,monthDays-(days-alreadyDay)),monthDays-(totalDays-days-alreadyTotalDay)),0) )) currentDay
|
||||||
@@ -41,12 +41,28 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="getIncome" resultType="map">
|
<select id="getIncome" resultType="map">
|
||||||
select ucbl.pay_type,SUM(ucbl.fee) fee
|
select pay_type, SUM(fee) fee
|
||||||
from user_course_buy_log ucbl
|
from (
|
||||||
left join buy_order bo on bo.order_sn = ucbl.order_sn
|
select ucbl.pay_type, ucbl.fee
|
||||||
where DATE_FORMAT(ucbl.pay_time,'%Y-%m') = SUBSTR(#{date},1,7) and ucbl.del_flag = 0
|
from user_course_buy_log ucbl
|
||||||
and (ucbl.user_id not in (select id from user where tester_flag = 1) or ( ucbl.user_id in (select id from user where tester_flag = 1) and ucbl.pay_type in ('微信','支付宝') and ucbl.create_time>='2026-05-06 00:00:00'))
|
left join buy_order bo on bo.order_sn = ucbl.order_sn
|
||||||
GROUP BY ucbl.pay_type
|
where DATE_FORMAT(ucbl.pay_time,'%Y-%m') = SUBSTR(#{date},1,7) and ucbl.del_flag = 0
|
||||||
|
and (ucbl.user_id not in (select id from user where tester_flag = 1) or ( ucbl.user_id in (select id from user where tester_flag = 1) and ucbl.pay_type in ('微信','支付宝') and ucbl.create_time>='2026-05-06 00:00:00'))
|
||||||
|
union all
|
||||||
|
select IF(bo.payment_method='1','微信',IF(bo.payment_method='2','支付宝',IF(bo.payment_method='4','天医币','其他'))) pay_type,
|
||||||
|
bo.real_money fee
|
||||||
|
from buy_order bo
|
||||||
|
where bo.order_type = 'relearn' and bo.del_flag = 0
|
||||||
|
and bo.order_status not in ('5','6')
|
||||||
|
and DATE_FORMAT(IF(bo.success_time is null,bo.create_time,bo.success_time),'%Y-%m') = SUBSTR(#{date},1,7)
|
||||||
|
and not exists (
|
||||||
|
select 1 from user_course_buy_log ucbl
|
||||||
|
where ucbl.order_sn = bo.order_sn and ucbl.del_flag = 0
|
||||||
|
)
|
||||||
|
and (bo.user_id not in (select id from user where tester_flag = 1)
|
||||||
|
or (bo.user_id in (select id from user where tester_flag = 1) and bo.payment_method in ('1','2') and bo.create_time>='2026-05-06 00:00:00'))
|
||||||
|
) t
|
||||||
|
GROUP BY pay_type
|
||||||
</select>
|
</select>
|
||||||
<select id="getRefund" resultType="map">
|
<select id="getRefund" resultType="map">
|
||||||
select pay_type,SUM(fee) fee
|
select pay_type,SUM(fee) fee
|
||||||
@@ -59,11 +75,11 @@
|
|||||||
and DATE_FORMAT(bor.create_time,'%Y-%m') = SUBSTR(#{date},1,7)
|
and DATE_FORMAT(bor.create_time,'%Y-%m') = SUBSTR(#{date},1,7)
|
||||||
and (bo.user_id not in (select id from user where tester_flag = 1)
|
and (bo.user_id not in (select id from user where tester_flag = 1)
|
||||||
or (bo.user_id in (select id from user where tester_flag = 1) and bo.payment_method in ('1','2') and bo.create_time>='2026-05-06 00:00:00'))
|
or (bo.user_id in (select id from user where tester_flag = 1) and bo.payment_method in ('1','2') and bo.create_time>='2026-05-06 00:00:00'))
|
||||||
and exists (
|
and (bo.order_type = 'relearn' or exists (
|
||||||
select 1 from buy_order_product bop
|
select 1 from buy_order_product bop
|
||||||
inner join shop_product sp on sp.product_id = bop.product_id
|
inner join shop_product sp on sp.product_id = bop.product_id
|
||||||
where bop.order_id = bo.order_id and sp.goods_type = '05'
|
where bop.order_id = bo.order_id and sp.goods_type = '05'
|
||||||
)
|
))
|
||||||
) t
|
) t
|
||||||
GROUP BY pay_type
|
GROUP BY pay_type
|
||||||
</select>
|
</select>
|
||||||
@@ -79,11 +95,11 @@
|
|||||||
and DATE_FORMAT(IF(bo.success_time is null,bo.create_time,bo.success_time),'%Y-%m') = SUBSTR(#{date},1,7)
|
and DATE_FORMAT(IF(bo.success_time is null,bo.create_time,bo.success_time),'%Y-%m') = SUBSTR(#{date},1,7)
|
||||||
and (bo.user_id not in (select id from user where tester_flag = 1)
|
and (bo.user_id not in (select id from user where tester_flag = 1)
|
||||||
or (bo.user_id in (select id from user where tester_flag = 1) and bo.payment_method in ('1','2') and bo.create_time>='2026-05-06 00:00:00'))
|
or (bo.user_id in (select id from user where tester_flag = 1) and bo.payment_method in ('1','2') and bo.create_time>='2026-05-06 00:00:00'))
|
||||||
and exists (
|
and (bo.order_type = 'relearn' or exists (
|
||||||
select 1 from buy_order_product bop
|
select 1 from buy_order_product bop
|
||||||
inner join shop_product sp on sp.product_id = bop.product_id
|
inner join shop_product sp on sp.product_id = bop.product_id
|
||||||
where bop.order_id = bo.order_id and sp.goods_type = '05'
|
where bop.order_id = bo.order_id and sp.goods_type = '05'
|
||||||
)
|
))
|
||||||
) t
|
) t
|
||||||
GROUP BY pay_type
|
GROUP BY pay_type
|
||||||
</select>
|
</select>
|
||||||
@@ -159,11 +175,11 @@
|
|||||||
select r.*
|
select r.*
|
||||||
,ROUND(fee-alreadyTanxiao-currentTanxiao,2) surplusTanxiao
|
,ROUND(fee-alreadyTanxiao-currentTanxiao,2) surplusTanxiao
|
||||||
from (select e.*
|
from (select e.*
|
||||||
,ROUND(if(alreadyDay+currentDay>=days,fee-alreadyTanxiao,if(alreadyTanxiao+currentDay*dayAmount>fee,fee-alreadyTanxiao,currentDay*dayAmount)),2) currentTanxiao
|
,ROUND(if(CHAR_LENGTH(IFNULL(endTime,'')) < 10, if(alreadyDay+currentDay>=days,fee-alreadyTanxiao,if(alreadyTanxiao+currentDay*dayAmount>fee,fee-alreadyTanxiao,currentDay*dayAmount)), if(DATEDIFF(#{date}, LEFT(endTime,10)) >= 0, 0, if(alreadyDay+currentDay>=days,fee-alreadyTanxiao,if(alreadyTanxiao+currentDay*dayAmount>fee,fee-alreadyTanxiao,currentDay*dayAmount)))),2) currentTanxiao
|
||||||
from (select w.name,w.tel,w.ctitle,w.cctitle,if(w.startTime is null,'',w.startTime) startTime,if(w.endTime is null,'',w.endTime) endTime,w.totalDays
|
from (select w.name,w.tel,w.ctitle,w.cctitle,if(w.startTime is null,'',w.startTime) startTime,if(w.endTime is null,'',w.endTime) endTime,w.totalDays
|
||||||
,w.type,w.payType,w.payTime,w.orderSn,w.zfbOrder,w.days,w.fee,w.remark,w.dayAmount,w.alreadyDay,w.currentDay
|
,w.type,w.payType,w.payTime,w.orderSn,w.zfbOrder,w.days,w.fee,w.remark,w.dayAmount,w.alreadyDay,w.currentDay
|
||||||
,IF(beginDay=0,startTime,IF(startTime is NULL,startTime,DATE_ADD(startTime,INTERVAL beginDay-1 day))) startTanxiaoTime
|
,IF(beginDay=0,startTime,IF(startTime is NULL,startTime,DATE_ADD(startTime,INTERVAL beginDay-1 day))) startTanxiaoTime
|
||||||
,ROUND(if(alreadyDay=days,fee,if(alreadyDayAmount>fee,fee,alreadyDayAmount)),2) alreadyTanxiao
|
,ROUND(if(CHAR_LENGTH(IFNULL(endTime,'')) < 10, if(alreadyDay=days,fee,if(alreadyDayAmount>fee,fee,alreadyDayAmount)), if(DATEDIFF(#{date}, LEFT(endTime,10)) >= 0, fee, if(alreadyDay=days,fee,if(alreadyDayAmount>fee,fee,alreadyDayAmount)))),2) alreadyTanxiao
|
||||||
from (
|
from (
|
||||||
select q.*,alreadyDay*dayAmount alreadyDayAmount
|
select q.*,alreadyDay*dayAmount alreadyDayAmount
|
||||||
,IF(days-alreadyDay=0,0,IF(beginDay=0,IF(startTime is NULL,0,IF(days-alreadyDay>=monthDays,if(DATE_FORMAT(startTime,'%Y-%m')=SUBSTR(#{date},1,7),DATEDIFF(#{date},startTime)+1,if(DATE_FORMAT(startTime,'%Y-%m')>SUBSTR(#{date},1,7),0,monthDays)),if(DATE_FORMAT(startTime, '%Y-%m')=SUBSTR(#{date},1,7),if(DATEDIFF(#{date},startTime)+1>days,days,DATEDIFF(#{date},startTime)+1),if(DATE_FORMAT(startTime, '%Y-%m') < SUBSTR(#{date},1,7),days-alreadyDay,0)))), if(alreadyTotalDay+monthDays>beginDay,if(alreadyDay>0,if(days-alreadyDay>monthDays,monthDays,monthDays-(days-alreadyDay)),monthDays-(totalDays-days-alreadyTotalDay)),0) )) currentDay
|
,IF(days-alreadyDay=0,0,IF(beginDay=0,IF(startTime is NULL,0,IF(days-alreadyDay>=monthDays,if(DATE_FORMAT(startTime,'%Y-%m')=SUBSTR(#{date},1,7),DATEDIFF(#{date},startTime)+1,if(DATE_FORMAT(startTime,'%Y-%m')>SUBSTR(#{date},1,7),0,monthDays)),if(DATE_FORMAT(startTime, '%Y-%m')=SUBSTR(#{date},1,7),if(DATEDIFF(#{date},startTime)+1>days,days,DATEDIFF(#{date},startTime)+1),if(DATE_FORMAT(startTime, '%Y-%m') < SUBSTR(#{date},1,7),days-alreadyDay,0)))), if(alreadyTotalDay+monthDays>beginDay,if(alreadyDay>0,if(days-alreadyDay>monthDays,monthDays,monthDays-(days-alreadyDay)),monthDays-(totalDays-days-alreadyTotalDay)),0) )) currentDay
|
||||||
|
|||||||
@@ -15,10 +15,12 @@
|
|||||||
IF(DATE_FORMAT(uvl.start_time, '%Y-%m') > SUBSTR(#{date},1,7),0,IF(DATE_FORMAT(uvl.end_time, '%Y-%m') < SUBSTR(#{date},1,7),0,IF(DATE_FORMAT(uvl.end_time, '%Y-%m') > SUBSTR(#{date},1,7),if(DATE_FORMAT(uvl.start_time, '%Y-%m') = SUBSTR(#{date},1,7),DATEDIFF(#{date},uvl.start_time)+1,DAY(#{date})),DATEDIFF(uvl.end_time,concat(SUBSTR(#{date},1,7),'-01'))+1))) currentDays,
|
IF(DATE_FORMAT(uvl.start_time, '%Y-%m') > SUBSTR(#{date},1,7),0,IF(DATE_FORMAT(uvl.end_time, '%Y-%m') < SUBSTR(#{date},1,7),0,IF(DATE_FORMAT(uvl.end_time, '%Y-%m') > SUBSTR(#{date},1,7),if(DATE_FORMAT(uvl.start_time, '%Y-%m') = SUBSTR(#{date},1,7),DATEDIFF(#{date},uvl.start_time)+1,DAY(#{date})),DATEDIFF(uvl.end_time,concat(SUBSTR(#{date},1,7),'-01'))+1))) currentDays,
|
||||||
IF(DATE_FORMAT(uvl.start_time, '%Y-%m') > SUBSTR(#{date},1,7),DATEDIFF(uvl.end_time,uvl.start_time)+1,(IF(DATE_FORMAT(uvl.end_time, '%Y-%m') <= SUBSTR(#{date},1,7),0,DATEDIFF(uvl.end_time,#{date})))) notyetDays
|
IF(DATE_FORMAT(uvl.start_time, '%Y-%m') > SUBSTR(#{date},1,7),DATEDIFF(uvl.end_time,uvl.start_time)+1,(IF(DATE_FORMAT(uvl.end_time, '%Y-%m') <= SUBSTR(#{date},1,7),0,DATEDIFF(uvl.end_time,#{date})))) notyetDays
|
||||||
from user_vip_log uvl LEFT JOIN buy_order a ON a.order_sn=uvl.order_sn
|
from user_vip_log uvl LEFT JOIN buy_order a ON a.order_sn=uvl.order_sn
|
||||||
|
LEFT JOIN buy_order_refund b ON a.order_id=b.order_id
|
||||||
left join user_vip uv on uv.id = uvl.user_vip_id
|
left join user_vip uv on uv.id = uvl.user_vip_id
|
||||||
left join user u on u.id = uvl.user_id
|
left join user u on u.id = uvl.user_id
|
||||||
left join pay_zfb_order pzo on pzo.relevanceOid = uvl.order_sn and pzo.trade_no is not null
|
left join pay_zfb_order pzo on pzo.relevanceOid = uvl.order_sn and pzo.trade_no is not null
|
||||||
where u.del_flag = 0 and (uvl.del_flag = 0 or (uvl.del_flag = -1 and a.order_status=6))
|
where u.del_flag = 0 and (uvl.del_flag = 0 or (uvl.del_flag = -1 and a.order_status=6))
|
||||||
|
and (DATE_FORMAT(b.create_time, '%Y-%m')>=SUBSTR(#{date},1,7) || ISNULL(b.create_time))
|
||||||
AND (uv.del_flag = 0 OR (uv.del_flag = -1 AND a.order_status=6))
|
AND (uv.del_flag = 0 OR (uv.del_flag = -1 AND a.order_status=6))
|
||||||
and DATE_FORMAT(IF(uvl.pay_time is NULL,uvl.start_time,uvl.pay_time), '%Y-%m') <= SUBSTR(#{date},1,7)
|
and DATE_FORMAT(IF(uvl.pay_time is NULL,uvl.start_time,uvl.pay_time), '%Y-%m') <= SUBSTR(#{date},1,7)
|
||||||
and
|
and
|
||||||
@@ -264,7 +266,8 @@
|
|||||||
left join user_vip uv on uv.id = uvl.user_vip_id
|
left join user_vip uv on uv.id = uvl.user_vip_id
|
||||||
left join user u on u.id = uvl.user_id
|
left join user u on u.id = uvl.user_id
|
||||||
left join pay_zfb_order pzo on pzo.relevanceOid = uvl.order_sn and pzo.trade_no is not null
|
left join pay_zfb_order pzo on pzo.relevanceOid = uvl.order_sn and pzo.trade_no is not null
|
||||||
where u.del_flag = 0 and uvl.del_flag = 0 and uv.del_flag = 0 and DATE_FORMAT(IF(uvl.pay_time is NULL,uvl.start_time,uvl.pay_time), '%Y-%m') <= SUBSTR(#{date},1,7) and u.id not in (select id from user where tester_flag = 1)
|
where u.del_flag = 0 and uvl.del_flag = 0 and uv.del_flag = 0 and DATE_FORMAT(IF(uvl.pay_time is NULL,uvl.start_time,uvl.pay_time), '%Y-%m') <= SUBSTR(#{date},1,7)
|
||||||
|
and (u.id not in (select id from user where tester_flag = 1) or (u.id in (select id from user where tester_flag = 1) and (uvl.pay_type in('微信','支付宝') and uvl.create_time>='2026-05-06 00:00:00')))
|
||||||
order by uvl.end_time asc
|
order by uvl.end_time asc
|
||||||
) t order by currentDays desc
|
) t order by currentDays desc
|
||||||
) s
|
) s
|
||||||
@@ -299,7 +302,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="getUserVipRefundFeeTotal" resultType="java.math.BigDecimal">
|
<select id="getUserVipRefundFeeTotal" resultType="java.math.BigDecimal">
|
||||||
select IFNULL(SUM(c.fee),0)
|
select IFNULL(SUM(uvl.price),0)
|
||||||
from user_vip_log uvl
|
from user_vip_log uvl
|
||||||
left join user_vip uv on uv.id = uvl.user_vip_id
|
left join user_vip uv on uv.id = uvl.user_vip_id
|
||||||
left join user u on u.id = uvl.user_id
|
left join user u on u.id = uvl.user_id
|
||||||
|
|||||||
Reference in New Issue
Block a user