Compare commits

...

5 Commits

Author SHA1 Message Date
dzq afbd0935e7 feat(商品): 添加商品免审批功能以支持自动审批
在商品相关实体、DTO、查询条件及SQL表中添加`auto_approval`字段,用于标识商品是否免审批。在审批流程中,若商品标记为免审批,则自动完成审批流程,减少人工干预。
2025-04-21 11:40:46 +08:00
dzq 029975787f feat(订单管理): 添加订单列表查询功能并优化返回结果排序
在SearchReturnApprovalQuery中添加按创建时间降序排序功能,并新增ShopOrderController和OrderApplicationService中的订单列表查询接口,以便前端能够获取订单列表数据。
2025-04-19 16:27:46 +08:00
dzq a8be405d36 feat(审批流程): 添加审批人字段以记录审批人信息
在审批流程中,新增了审批人字段`audit_name`,用于记录审批人的姓名。同时,在审批通过和驳回的逻辑中,增加了根据用户ID获取审批人姓名的功能,确保审批人信息能够正确记录和展示。
2025-04-19 10:25:00 +08:00
dzq 9487ba863a feat(shop-goods): 添加格口号字符串字段并调整查询逻辑
为了支持格口号的字符串表示,在ShopGoodsDTO和SearchGoodsDO中添加了cellNoStr字段。同时,调整了SearchShopGoodsQuery的查询逻辑,增加了按goods_id分组的操作。此外,更新了CabinetCellMapper和ShopGoodsMapper的SQL语句,以支持新的字段和查询需求。最后,在CabinetCellController中添加了调整格口商品库存和清空格口商品的接口。
2025-04-19 09:27:42 +08:00
dzq 65b6dbd1d9 fix(库存管理): 修复商品和柜机格口库存同步问题
修复了商品和柜机格口库存不同步的问题,确保在订单创建和退货审批时,商品库存和柜机格口库存能够正确扣减和增加。具体修改包括:
1. 在订单创建时,同时扣减商品和柜机格口的库存。
2. 在退货审批时,同时增加商品和柜机格口的库存。
3. 更新SQL查询以包含柜机格口的库存信息。
2025-04-18 16:27:04 +08:00
18 changed files with 184 additions and 16 deletions

View File

@ -16,6 +16,7 @@ import io.swagger.v3.oas.annotations.Operation;
import java.util.List;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@ -26,6 +27,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/cabinet/cell")
@RequiredArgsConstructor
@ -81,4 +83,24 @@ public class CabinetCellController extends BaseController {
cabinetCellApplicationService.configureGoodsCellsStock(cellId, goodsId, stock);
return ResponseDTO.ok();
}
@Operation(summary = "调整格口商品库存")
@AccessLog(title = "格口管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/changeGoodsCellsStock/{cellId}/{stock}")
public ResponseDTO<Void> changeGoodsCellsStock(@PathVariable Long cellId, @PathVariable Integer stock) {
if (stock < 0) {
log.error("调整格口商品库存,库存不能小于0");
return ResponseDTO.fail();
}
cabinetCellApplicationService.changeGoodsCellsStock(cellId, stock);
return ResponseDTO.ok();
}
@Operation(summary = "清空格口商品")
@AccessLog(title = "格口管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/clearGoodsCells/{cellId}")
public ResponseDTO<Void> clearGoodsCells(@PathVariable Long cellId) {
cabinetCellApplicationService.clearGoodsCells(cellId);
return ResponseDTO.ok();
}
}

View File

@ -0,0 +1,31 @@
package com.agileboot.admin.controller.shop;
import com.agileboot.common.core.base.BaseController;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.shop.order.OrderApplicationService;
import com.agileboot.domain.shop.order.db.ShopOrderEntity;
import com.agileboot.domain.shop.order.query.SearchShopOrderQuery;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/shop/order")
@RequiredArgsConstructor
@Validated
public class ShopOrderController extends BaseController {
private final OrderApplicationService orderApplicationService;
@Operation(summary = "订单列表")
// @PreAuthorize("@permission.has('shop:goods:list')")
@GetMapping("/list")
public ResponseDTO<PageDTO<ShopOrderEntity>> list(SearchShopOrderQuery<ShopOrderEntity> query) {
PageDTO<ShopOrderEntity> page = orderApplicationService.getOrderList(query);
return ResponseDTO.ok(page);
}
}

View File

@ -91,4 +91,14 @@ public class CabinetCellApplicationService {
model.setUsageStatus(2);
model.updateById();
}
public void changeGoodsCellsStock(Long cellId, Integer stock) {
CabinetCellModel model = cabinetCellModelFactory.loadById(cellId);
model.setStock(stock);
model.updateById();
}
public void clearGoodsCells(Long cellId) {
cabinetCellService.clearGoodsIdAndFreeCell(cellId);
}
}

View File

@ -47,7 +47,7 @@ public interface CabinetCellMapper extends BaseMapper<CabinetCellEntity> {
List<CabinetCellEntity> selectByGoodsId(@Param("goodsId")Long goodsId);
@Update("UPDATE cabinet_cell " +
"SET goods_id = NULL, usage_status = 1 " +
"SET stock = 0, goods_id = NULL, usage_status = 1 " +
"WHERE cell_id = #{cellId}")
void clearGoodsIdAndFreeCell(@Param("cellId") Long cellId);

View File

@ -1,7 +1,10 @@
package com.agileboot.domain.shop.approval;
import com.agileboot.common.constant.PayApiConstants;
import com.agileboot.common.constant.WeixinConstants;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.cabinet.cell.model.CabinetCellModel;
import com.agileboot.domain.cabinet.cell.model.CabinetCellModelFactory;
import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.qywx.accessToken.AccessTokenApplicationService;
import com.agileboot.domain.qywx.accessToken.db.QyAccessTokenEntity;
@ -9,6 +12,7 @@ import com.agileboot.domain.qywx.api.QywxApiUtil;
import com.agileboot.domain.qywx.api.response.NewsArticle;
import com.agileboot.domain.qywx.authCorpInfo.AuthCorpInfoApplicationService;
import com.agileboot.domain.qywx.authCorpInfo.db.QyAuthCorpInfoEntity;
import com.agileboot.domain.qywx.user.db.QyUserEntity;
import com.agileboot.domain.qywx.user.db.QyUserService;
import com.agileboot.domain.shop.approval.command.AddReturnApprovalCommand;
import com.agileboot.domain.shop.approval.command.UpdateReturnApprovalCommand;
@ -40,6 +44,7 @@ import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
/**
@ -59,6 +64,7 @@ public class ReturnApprovalApplicationService {
private final AuthCorpInfoApplicationService authCorpInfoApplicationService;
private final AccessTokenApplicationService accessTokenApplicationService;
private final QyUserService qyUserService;
private final CabinetCellModelFactory cabinetCellModelFactory;
/**
* 获取退货审批列表
@ -151,6 +157,16 @@ public class ReturnApprovalApplicationService {
}
}
// 审批人信息
if (StringUtils.isNotBlank(command.getAuditName())) {
model.setAuditName(command.getAuditName());
} else if (StringUtils.isNotBlank(command.getUserid())) {
QyUserEntity qyUserEntity = qyUserService.getUserByUserId(command.getUserid(), WeixinConstants.corpid);
if (null != qyUserEntity) {
model.setAuditName(qyUserEntity.getName());
}
}
// 更新审批状态为通过
model.validateApprovalStatus();
model.setAuditImages(command.getAuditImages());
@ -167,6 +183,11 @@ public class ReturnApprovalApplicationService {
GoodsModel goodsModel = goodsModelFactory.loadById(orderGoodsModel.getGoodsId());
goodsModel.setStock(goodsModel.getStock() + orderGoodsModel.getQuantity());
goodsModel.updateById();
// 更新格口库存
CabinetCellModel cabinetCellModel = cabinetCellModelFactory.loadById(orderGoodsModel.getCellId());
cabinetCellModel.setStock(cabinetCellModel.getStock() + orderGoodsModel.getQuantity());
cabinetCellModel.updateById();
}
/**
@ -197,6 +218,17 @@ public class ReturnApprovalApplicationService {
orderGoodsModel.setStatus(5);
orderGoodsModel.updateById();
GoodsModel goodsModel = goodsModelFactory.loadById(orderGoods.getGoodsId());
// 如果商品免审批则自动审批
if (goodsModel.getAutoApproval().equals(1)) {
UpdateReturnApprovalCommand approveCommand = new UpdateReturnApprovalCommand();
approveCommand.setApprovalId(returnApprovalModel.getApprovalId());
approveCommand.setReturnAmount(orderGoods.getTotalAmount());
approveCommand.setAuditName("自动审批");
approveCommand.setAuditRemark("自动审批");
approveApproval(approveCommand);
}
// 发送审核消息
try {
String appid = "QWTONG_YS_WXSHOP";
@ -239,7 +271,14 @@ public class ReturnApprovalApplicationService {
public void rejectApproval(UpdateReturnApprovalCommand command) {
ReturnApprovalModel model = modelFactory.loadById(command.getApprovalId());
model.validateApprovalStatus();
// 审批人信息
if (StringUtils.isNotBlank(command.getUserid())) {
QyUserEntity qyUserEntity = qyUserService.getUserByUserId(command.getUserid(), WeixinConstants.corpid);
if (null != qyUserEntity) {
model.setAuditName(qyUserEntity.getName());
}
}
// 更新审批状态为驳回
model.setStatus(3);
model.setAuditImages(command.getAuditImages());

View File

@ -13,4 +13,5 @@ public class UpdateReturnApprovalCommand extends AddReturnApprovalCommand {
@PositiveOrZero
private Long approvalId;
private String userid;
}

View File

@ -72,6 +72,10 @@ public class ReturnApprovalEntity extends BaseEntity<ReturnApprovalEntity> {
@TableField("audit_remark")
private String auditRemark;
@ApiModelProperty("审批人")
@TableField("audit_name")
private String auditName;
@ApiModelProperty("审批状态(1待审核 2已通过 3已驳回)")
@TableField("`status`")
private Integer status;

View File

@ -65,6 +65,9 @@ public class ReturnApprovalDTO {
@ExcelColumn(name = "审核说明")
private String auditRemark;
@ExcelColumn(name = "审批人")
private String auditName;
@ExcelColumn(name = "审批状态(1待审核 2已通过 3已驳回)")
private Integer status;

View File

@ -31,7 +31,8 @@ public class SearchReturnApprovalQuery<T> extends AbstractPageQuery<T> {
.eq(status != null, "status", status)
.like(StrUtil.isNotEmpty(returnRemark), "return_remark", returnRemark)
.like(StrUtil.isNotEmpty(auditRemark), "audit_remark", auditRemark)
.between(startTime != null && endTime != null, "create_time", startTime, endTime);
.between(startTime != null && endTime != null, "create_time", startTime, endTime)
.orderByDesc("create_time");
this.timeRangeColumn = "create_time";

View File

@ -18,6 +18,8 @@ public class SearchGoodsDO extends ShopGoodsEntity {
private String cabinetName;
@TableField("cell_no")
private Integer cellNo;
@TableField("cell_no_str")
private String cellNoStr;
@TableField("total_stock")
private Integer totalStock;
}

View File

@ -52,6 +52,10 @@ public class ShopGoodsEntity extends BaseEntity<ShopGoodsEntity> {
@TableField("`status`")
private Integer status;
@ApiModelProperty("免审批0否 1是")
@TableField("`auto_approval`")
private Integer autoApproval;
@ApiModelProperty("封面图URL")
@TableField("cover_img")
private String coverImg;

View File

@ -36,14 +36,13 @@ public interface ShopGoodsMapper extends BaseMapper<ShopGoodsEntity> {
@Param(Constants.WRAPPER) Wrapper<SearchGoodsDO> queryWrapper
); */
@Select("SELECT g.goods_id, g.goods_name, g.category_id, g.price, " +
"g.stock, g.status, g.cover_img, SUM(cc.stock) AS total_stock, " +
"GROUP_CONCAT(DISTINCT cc.cell_no) AS cell_no, " +
"g.stock, g.status, g.auto_approval, g.cover_img, SUM(cc.stock) AS total_stock, " +
"GROUP_CONCAT(DISTINCT cc.cell_no) AS cell_no_str, " +
"GROUP_CONCAT(DISTINCT sc.cabinet_name) AS cabinet_name " +
"FROM shop_goods g " +
"LEFT JOIN cabinet_cell cc ON g.goods_id = cc.goods_id AND cc.deleted = 0 " +
"LEFT JOIN smart_cabinet sc ON cc.cabinet_id = sc.cabinet_id AND sc.deleted = 0 " +
"${ew.customSqlSegment} " +
"GROUP BY g.goods_id")
"${ew.customSqlSegment} ")
Page<SearchGoodsDO> getGoodsList(
Page<SearchGoodsDO> page,
@Param(Constants.WRAPPER) Wrapper<SearchGoodsDO> queryWrapper
@ -63,7 +62,10 @@ public interface ShopGoodsMapper extends BaseMapper<ShopGoodsEntity> {
* 查询商品及其关联的柜机格口信息
* @return 商品列表
*/
@Select("SELECT g.*, sc.cabinet_id, sc.cabinet_name, cc.cell_id " +
@Select("SELECT g.goods_id, g.goods_name, g.category_id, g.price, " +
"g.status, g.cover_img, g.goods_detail, " +
"g.creator_id, g.create_time, g.updater_id, g.update_time, g.remark, g.deleted, " +
"sc.cabinet_id, sc.cabinet_name, cc.stock, cc.cell_id " +
"FROM shop_goods g " +
"LEFT JOIN cabinet_cell cc ON g.goods_id = cc.goods_id " +
"LEFT JOIN smart_cabinet sc ON cc.cabinet_id = sc.cabinet_id " +

View File

@ -20,7 +20,7 @@ public class ShopGoodsServiceImpl extends ServiceImpl<ShopGoodsMapper, ShopGoods
@Override
public Page<SearchGoodsDO> getGoodsList(AbstractPageQuery<SearchGoodsDO> query) {
QueryWrapper<SearchGoodsDO> wrapper = query.toQueryWrapper();
wrapper.orderByAsc("sc.cabinet_id IS NULL", "sc.cabinet_id", "cc.cell_no");
// wrapper.orderByAsc("sc.cabinet_id IS NULL", "sc.cabinet_id", "cc.cell_no");
return baseMapper.getGoodsList(query.toPage(), wrapper);
}

View File

@ -60,6 +60,9 @@ public class ShopGoodsDTO {
@ExcelColumn(name = "商品状态")
private Integer status;
@ExcelColumn(name = "免审批")
private Integer autoApproval;
@ExcelColumn(name = "封面图")
private String coverImg;
@ -79,6 +82,8 @@ public class ShopGoodsDTO {
private String cabinetName;
@ExcelColumn(name = "格口号")
private Integer cellNo;
@ExcelColumn(name = "格口号")
private String cellNoStr;
@ExcelColumn(name = "已分配库存")
private Integer totalStock;
}

View File

@ -14,6 +14,7 @@ public class SearchShopGoodsQuery<T> extends AbstractPageQuery<T> {
protected String goodsName;
protected Long categoryId;
protected Integer status;
protected Integer autoApproval;
protected BigDecimal minPrice;
protected BigDecimal maxPrice;
@ -25,9 +26,11 @@ public class SearchShopGoodsQuery<T> extends AbstractPageQuery<T> {
.like(StrUtil.isNotEmpty(goodsName), "g.goods_name", goodsName)
.eq(categoryId != null, "cc.cabinet_id", categoryId)
.eq(status != null, "g.status", status)
.eq(autoApproval != null, "g.auto_approval", autoApproval)
.ge(minPrice != null, "g.price", minPrice)
.le(maxPrice != null, "g.price", maxPrice)
.eq("g.deleted", 0);
.eq("g.deleted", 0)
.groupBy("g.goods_id");
this.timeRangeColumn = "create_time";

View File

@ -2,6 +2,7 @@ package com.agileboot.domain.shop.order;
import cn.hutool.core.date.DateUtil;
import com.agileboot.common.constant.PayApiConstants;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity;
@ -22,10 +23,12 @@ import com.agileboot.domain.shop.order.db.ShopOrderGoodsEntity;
import com.agileboot.domain.shop.order.db.ShopOrderGoodsService;
import com.agileboot.domain.shop.order.dto.CreateOrderResult;
import com.agileboot.domain.shop.order.dto.GetOrdersByOpenIdDTO;
import com.agileboot.domain.shop.order.dto.ShopOrderDTO;
import com.agileboot.domain.shop.order.model.OrderModel;
import com.agileboot.domain.shop.order.model.OrderModelFactory;
import com.agileboot.domain.shop.order.model.OrderGoodsModel;
import com.agileboot.domain.shop.order.model.OrderGoodsModelFactory;
import com.agileboot.domain.shop.order.query.SearchShopOrderQuery;
import com.agileboot.domain.shop.payment.PaymentApplicationService;
import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateRequest;
import com.agileboot.domain.shop.payment.dto.WxJsApiPreCreateResponse;
@ -36,6 +39,8 @@ import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -60,11 +65,10 @@ public class OrderApplicationService {
private final QyUserService userService;
private final QyUserModelFactory qyUserModelFactory;
/*public PageDTO<ShopOrderDTO> getOrderList(SearchShopOrderQuery<> query) {
public PageDTO<ShopOrderEntity> getOrderList(SearchShopOrderQuery<ShopOrderEntity> query) {
Page<ShopOrderEntity> page = orderService.page(query.toPage(), query.toQueryWrapper());
List<ShopOrderDTO> dtoList = page.getRecords().stream().map(ShopOrderDTO::new).collect(Collectors.toList());
return new PageDTO<>(dtoList, page.getTotal());
}*/
return new PageDTO<>(page.getRecords(), page.getTotal());
}
public void openOrderGoodsCabinet(Long orderId, Long orderGoodsId) {
OrderModel orderModel = orderModelFactory.loadById(orderId);
@ -197,7 +201,7 @@ public class OrderApplicationService {
goodsModel.insert();
// 扣减库存
deductGoodsStock(goodsModel.getGoodsId(), goodsModel.getQuantity());
deductGoodsStock(goodsModel.getGoodsId(), goodsModel.getQuantity(), goodsModel.getCellId());
totalAmount = totalAmount.add(goodsModel.getTotalAmount());
}
@ -208,11 +212,20 @@ public class OrderApplicationService {
orderModel.updateById();
}
private void deductGoodsStock(Long goodsId, Integer quantity) {
private void deductGoodsStock(Long goodsId, Integer quantity, Long cellId) {
CabinetCellEntity cabinetCellEntity = cabinetCellService.getById(cellId);
if (cabinetCellEntity == null || cabinetCellEntity.getStock() < quantity) {
throw new ApiException(ErrorCode.FAILED, "柜子库存不足");
}
ShopGoodsEntity goods = goodsService.getById(goodsId);
if (goods == null || goods.getStock() < quantity) {
throw new ApiException(ErrorCode.FAILED, "商品库存不足");
}
// 扣减库存
cabinetCellEntity.setStock(cabinetCellEntity.getStock() - quantity);
cabinetCellService.updateById(cabinetCellEntity);
goods.setStock(goods.getStock() - quantity);
goodsService.updateById(goods);
}

View File

@ -11,6 +11,7 @@ CREATE TABLE `return_approval` (
`audit_images` VARCHAR(2048) NULL COMMENT '审核图片路径数组',
`return_remark` VARCHAR(1024) NULL COMMENT '归还说明',
`audit_remark` VARCHAR(1024) NULL COMMENT '审核说明',
`audit_name` varchar(30) DEFAULT NULL COMMENT '审批人',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '审批状态(1待审核 2已通过 3已驳回)',
`creator_id` BIGINT NOT NULL DEFAULT 0 COMMENT '创建者ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

View File

@ -0,0 +1,27 @@
DROP TABLE IF EXISTS `cabinet_cell_operation`;
CREATE TABLE `cabinet_cell_operation` (
`operation_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '操作流水号',
`cell_id` BIGINT NOT NULL COMMENT '关联格口ID',
`goods_id` BIGINT NOT NULL COMMENT '关联商品ID',
`goods_name` VARCHAR(255) NOT NULL COMMENT '商品名称',
`userid` varchar(100) NOT NULL COMMENT '企业微信用户ID',
`is_internal` TINYINT(1) DEFAULT 0 COMMENT '是否内部用户0否 1是',
`name` varchar(30) DEFAULT NULL COMMENT '成员名称',
`mobile` varchar(30) DEFAULT NULL COMMENT '手机号码',
`operation_type` TINYINT NOT NULL COMMENT '操作类型1用户 2管理员',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '操作状态1正常 2操作失败',
`creator_id` BIGINT NOT NULL DEFAULT 0 COMMENT '创建者ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater_id` BIGINT NOT NULL DEFAULT 0 COMMENT '更新者ID',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标志0存在 1删除',
PRIMARY KEY (`operation_id`),
KEY `idx_cell` (`cell_id`),
KEY `idx_user` (`userid`),
KEY `idx_operation_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='柜机格口操作记录表';
ALTER TABLE `shop_goods`
ADD COLUMN `auto_approval` TINYINT NOT NULL DEFAULT 0 COMMENT '免审批0否 1是' AFTER `status`;