重构订单创建逻辑:采用Builder模式替代20+参数方法

主要改进:
- 新增OrderCreationRequest及相关DTO类,使用Builder模式提升代码可读性
- 引入类型安全的枚举类OrderConstant,替代魔法字符串常量
- 重构PlayOrderInfoServiceImpl,新增基于Builder模式的createOrderInfo方法
- 保留原有方法并标记为@Deprecated,确保向后兼容性
- 完善单元测试覆盖,包含Mockito模拟和边界条件测试
- 优化包结构,将DTO类从vo包迁移到dto包
- 添加JUnit 5和Mockito测试依赖
- 移除实体类过度使用的Lombok注解,改用精简的自定义构造器
- 新增数据库开发工作流程文档

技术栈:
- Spring Boot 2.7.9
- MyBatis-Plus 3.5.3.2
- JUnit 5 + Mockito
- Lombok Builder模式
- 类型安全枚举设计
This commit is contained in:
irving
2025-09-06 22:58:14 -04:00
parent 6194c64b4f
commit 295400b83e
11 changed files with 1391 additions and 31 deletions

219
db_workflow.md Normal file
View File

@@ -0,0 +1,219 @@
# Database Development Workflow
This document outlines the recommended workflow for database schema changes and code generation in the peipei-backend project.
## Overview
The project uses a **Database-First** approach with the following flow:
```
Flyway Migration → Database Schema → Code Generator → Java Code
```
## Step-by-Step Workflow
### 1. Create Flyway Migration
Create a new migration file in `/play-admin/src/main/resources/db/migration/`:
```sql
-- V{version}__{description}.sql
-- Example: V2__add_new_feature_table.sql
CREATE TABLE `play_new_feature` (
`id` varchar(32) NOT NULL COMMENT 'UUID',
`feature_name` varchar(100) COMMENT '功能名称',
`feature_type` varchar(20) DEFAULT NULL COMMENT '功能类型',
`status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
`tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
`created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
`created_time` datetime DEFAULT NULL COMMENT '创建时间',
`updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
`updated_time` datetime DEFAULT NULL COMMENT '修改时间',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除',
`version` int(11) NOT NULL DEFAULT '1' COMMENT '数据版本',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='新功能表';
```
**Migration Naming Convention:**
- `V{version}__{description}.sql`
- Version format: `V1.2025.0609.10.11` (timestamp-based)
- Description: snake_case with double underscore
### 2. Run Flyway Migration
Apply the migration to update database schema:
```bash
# Run from project root
mvn flyway:migrate
# Or check migration status
mvn flyway:info
```
### 3. Configure Code Generator
Edit `/play-generator/src/main/resources/config.properties`:
```properties
# Database configuration (should match main app)
db.url=jdbc:mysql://localhost:3306/play-with?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
db.driver=com.mysql.cj.jdbc.Driver
db.username=root
db.password=your_password
# Code generation configuration
gen.author=your_name
gen.packageName=com.starry.admin
gen.outputDir=./generated-code
gen.autoRemovePre=false
gen.tablePrefix=play_
gen.tplCategory=crud
# Specify the new table(s) to generate
gen.tableNames=play_new_feature
```
### 4. Run Code Generator
Generate Java code from the new table structure:
```bash
cd play-generator
./run.sh
# Or manually: mvn clean compile exec:java
```
### 5. Review Generated Code
Check the generated files in `./generated-code/`:
```
generated-code/
├── src/main/java/com/starry/admin/
│ ├── entity/PlayNewFeatureEntity.java
│ ├── mapper/PlayNewFeatureMapper.java
│ ├── service/IPlayNewFeatureService.java
│ ├── service/impl/PlayNewFeatureServiceImpl.java
│ └── controller/PlayNewFeatureController.java
└── src/main/resources/mapper/PlayNewFeatureMapper.xml
```
### 6. Integrate Generated Code
Copy generated files to the appropriate module in play-admin:
```bash
# Create new module directory structure
mkdir -p play-admin/src/main/java/com/starry/admin/modules/newfeature/{controller,service,mapper,module/entity}
# Copy generated files to appropriate locations
cp generated-code/src/main/java/com/starry/admin/controller/* \
play-admin/src/main/java/com/starry/admin/modules/newfeature/controller/
cp generated-code/src/main/java/com/starry/admin/service/* \
play-admin/src/main/java/com/starry/admin/modules/newfeature/service/
cp generated-code/src/main/java/com/starry/admin/mapper/* \
play-admin/src/main/java/com/starry/admin/modules/newfeature/mapper/
cp generated-code/src/main/java/com/starry/admin/entity/* \
play-admin/src/main/java/com/starry/admin/modules/newfeature/module/entity/
cp generated-code/src/main/resources/mapper/* \
play-admin/src/main/resources/mapper/newfeature/
```
### 7. Customize and Test
- **Review generated code** for any needed customizations
- **Add business logic** to Service implementations
- **Customize validation** in Controllers
- **Add custom queries** in Mapper if needed
- **Test the new endpoints** via Swagger UI
- **Run application** to ensure everything works
## Database Design Best Practices
### Table Naming Convention
- Use `play_` prefix for all tables
- Use snake_case for table names
- Example: `play_order_info`, `play_clerk_level_info`
### Required Standard Columns
All tables should include these standard columns:
```sql
`id` varchar(32) NOT NULL COMMENT 'UUID',
`tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
`created_by` varchar(32) DEFAULT NULL COMMENT '创建人的id',
`created_time` datetime DEFAULT NULL COMMENT '创建时间',
`updated_by` varchar(32) DEFAULT NULL COMMENT '修改人的id',
`updated_time` datetime DEFAULT NULL COMMENT '修改时间',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除',
`version` int(11) NOT NULL DEFAULT '1' COMMENT '数据版本',
```
### Column Guidelines
- Always add meaningful `COMMENT` to columns
- Use appropriate data types (`varchar`, `int`, `decimal`, `datetime`)
- Set proper defaults where applicable
- Consider indexing for foreign keys and frequently queried columns
## Code Generation Notes
### What Gets Generated
- **Entity**: MyBatis Plus entity with Lombok annotations
- **Mapper**: Database access interface extending BaseMapper
- **Service**: Business logic interface with standard CRUD operations
- **ServiceImpl**: Service implementation with basic CRUD logic
- **Controller**: REST API endpoints with Swagger documentation
- **Mapper XML**: MyBatis SQL mapping files
### Generated Code Features
- Swagger API documentation
- Spring Security integration (`@PreAuthorize`)
- Audit logging (`@Log`)
- Input validation
- Pagination support
- Logical deletion support
- Multi-tenant support
## Troubleshooting
### Common Issues
**Migration Fails**:
- Check database connection settings
- Verify migration SQL syntax
- Ensure proper permissions
**Generator Fails**:
- Verify database connection in config.properties
- Check if table exists after migration
- Ensure table follows naming conventions
**Generated Code Issues**:
- Review column comments (used for Java doc)
- Check data type mappings
- Verify package naming in config
### Database Type Mappings
| MySQL Type | Java Type | Notes |
|------------|-----------|-------|
| `varchar(n)` | `String` | |
| `int`, `bigint` | `Integer`, `Long` | |
| `decimal(p,s)` | `BigDecimal` | Precise decimal calculations |
| `datetime` | `Date` | Or `LocalDateTime` |
| `tinyint(1)` | `Boolean` | For flags/status |
| `char(1)` | `String` | For status codes |
## Tips
1. **Always create migration first**, then generate code
2. **Use descriptive table and column names** - they become class and field names
3. **Test migrations on dev environment** before applying to production
4. **Review generated code** before committing - customize as needed
5. **Follow existing module patterns** when organizing generated code
6. **Backup database** before running migrations in production

View File

@@ -129,6 +129,23 @@
<version>0.2.0</version>
</dependency>
<!--JUnit 5 for testing-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -152,6 +169,14 @@
<artifactId>flyway-maven-plugin</artifactId>
<version>7.15.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>

View File

@@ -1,44 +1,136 @@
package com.starry.admin.modules.order.module.constant;
import lombok.Getter;
/**
* 订单相关枚举和常量
*
* @author admin
* @since 2024/5/8 15:41
**/
*/
public class OrderConstant {
/**
* 订单状态-待接单
*
* @since 2024/5/8 15:42
**/
* 订单状态枚举
*/
@Getter
public enum OrderStatus {
PENDING("0", "已下单(待接单)"),
ACCEPTED("1", "已接单(待开始)"),
IN_PROGRESS("2", "已开始(服务中)"),
COMPLETED("3", "已完成"),
CANCELLED("4", "已取消");
private final String code;
private final String description;
OrderStatus(String code, String description) {
this.code = code;
this.description = description;
}
public static OrderStatus fromCode(String code) {
for (OrderStatus status : values()) {
if (status.code.equals(code)) {
return status;
}
}
throw new IllegalArgumentException("Unknown order status code: " + code);
}
}
/**
* 订单类型枚举
*/
@Getter
public enum OrderType {
REFUND("-1", "退款订单"),
RECHARGE("0", "充值订单"),
WITHDRAWAL("1", "提现订单"),
NORMAL("2", "普通订单");
private final String code;
private final String description;
OrderType(String code, String description) {
this.code = code;
this.description = description;
}
public static OrderType fromCode(String code) {
for (OrderType type : values()) {
if (type.code.equals(code)) {
return type;
}
}
throw new IllegalArgumentException("Unknown order type code: " + code);
}
}
/**
* 下单类型枚举
*/
@Getter
public enum PlaceType {
OTHER("-1", "其他类型"),
SPECIFIED("0", "指定单"),
RANDOM("1", "随机单"),
REWARD("2", "打赏单");
private final String code;
private final String description;
PlaceType(String code, String description) {
this.code = code;
this.description = description;
}
public static PlaceType fromCode(String code) {
for (PlaceType type : values()) {
if (type.code.equals(code)) {
return type;
}
}
throw new IllegalArgumentException("Unknown place type code: " + code);
}
}
/**
* 性别枚举
*/
@Getter
public enum Gender {
UNKNOWN("0", "未知"),
MALE("1", ""),
FEMALE("2", "");
private final String code;
private final String description;
Gender(String code, String description) {
this.code = code;
this.description = description;
}
public static Gender fromCode(String code) {
for (Gender gender : values()) {
if (gender.code.equals(code)) {
return gender;
}
}
throw new IllegalArgumentException("Unknown gender code: " + code);
}
}
// Legacy constants for backward compatibility - consider deprecating
@Deprecated
public final static String ORDER_STATUS_0 = "0";
/**
* 订单状态-待开始
*
* @since 2024/5/8 15:42
**/
@Deprecated
public final static String ORDER_STATUS_1 = "1";
/**
* 订单状态-服务中
*
* @since 2024/5/8 15:42
**/
@Deprecated
public final static String ORDER_STATUS_2 = "2";
/**
* 订单状态-已完成
*
* @since 2024/5/8 15:42
**/
@Deprecated
public final static String ORDER_STATUS_3 = "3";
/**
* 订单状态-已取消
*
* @since 2024/5/8 15:42
**/
@Deprecated
public final static String ORDER_STATUS_4 = "4";
}

View File

@@ -0,0 +1,44 @@
package com.starry.admin.modules.order.module.dto;
import java.math.BigDecimal;
import lombok.Builder;
import lombok.Data;
/**
* 商品信息值对象
*
* @author admin
*/
@Data
@Builder
public class CommodityInfo {
/**
* 商品ID
*/
private String commodityId;
/**
* 商品类型[0:礼物1服务]
*/
private String commodityType;
/**
* 商品单价
*/
private BigDecimal commodityPrice;
/**
* 商品属性-服务时长
*/
private String serviceDuration;
/**
* 商品名称
*/
private String commodityName;
/**
* 商品数量
*/
private String commodityNumber;
}

View File

@@ -0,0 +1,127 @@
package com.starry.admin.modules.order.module.dto;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Data;
/**
* 订单创建请求对象 - 使用Builder模式替换20+参数的方法
*
* @author admin
*/
@Data
@Builder
public class OrderCreationRequest {
/**
* 订单ID
*/
@NotBlank(message = "订单ID不能为空")
private String orderId;
/**
* 订单编号
*/
@NotBlank(message = "订单编号不能为空")
private String orderNo;
/**
* 订单状态
*/
@NotNull(message = "订单状态不能为空")
private OrderConstant.OrderStatus orderStatus;
/**
* 订单类型
*/
@NotNull(message = "订单类型不能为空")
private OrderConstant.OrderType orderType;
/**
* 下单类型
*/
@NotNull(message = "下单类型不能为空")
private OrderConstant.PlaceType placeType;
/**
* 打赏类型0:余额;1:礼物)
*/
private String rewardType;
/**
* 是否是首单
*/
private boolean isFirstOrder;
/**
* 商品信息
*/
@Valid
@NotNull(message = "商品信息不能为空")
private CommodityInfo commodityInfo;
/**
* 支付信息
*/
@Valid
@NotNull(message = "支付信息不能为空")
private PaymentInfo paymentInfo;
/**
* 下单人
*/
@NotBlank(message = "下单人不能为空")
private String purchaserBy;
/**
* 接单人(可选)
*/
private String acceptBy;
/**
* 微信号码
*/
private String weiChatCode;
/**
* 订单备注
*/
private String remark;
/**
* 随机单要求(仅随机单时需要)
*/
private RandomOrderRequirements randomOrderRequirements;
/**
* 获取首单标识字符串(兼容现有系统)
*/
public String getFirstOrderString() {
return isFirstOrder ? "1" : "0";
}
/**
* 验证随机单要求
*/
public boolean isValidForRandomOrder() {
return placeType == OrderConstant.PlaceType.RANDOM
&& randomOrderRequirements != null;
}
/**
* 是否为打赏单
*/
public boolean isRewardOrder() {
return placeType == OrderConstant.PlaceType.REWARD;
}
/**
* 是否为指定单
*/
public boolean isSpecifiedOrder() {
return placeType == OrderConstant.PlaceType.SPECIFIED;
}
}

View File

@@ -0,0 +1,40 @@
package com.starry.admin.modules.order.module.dto;
import java.math.BigDecimal;
import java.util.List;
import lombok.Builder;
import lombok.Data;
/**
* 支付信息值对象
*
* @author admin
*/
@Data
@Builder
public class PaymentInfo {
/**
* 订单金额
*/
private BigDecimal orderMoney;
/**
* 订单最终金额(支付金额)
*/
private BigDecimal finalAmount;
/**
* 优惠金额
*/
private BigDecimal discountAmount;
/**
* 优惠券ID列表
*/
private List<String> couponIds;
/**
* 支付方式0余额支付,1:微信支付,2:支付宝支付
*/
private String payMethod;
}

View File

@@ -0,0 +1,36 @@
package com.starry.admin.modules.order.module.dto;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import lombok.Builder;
import lombok.Data;
/**
* 随机单要求信息值对象
*
* @author admin
*/
@Data
@Builder
public class RandomOrderRequirements {
/**
* 随机单要求-店员性别
*/
private OrderConstant.Gender clerkGender;
/**
* 随机单要求-店员等级ID
*/
private String clerkLevelId;
/**
* 随机单要求-是否排除下单过的成员0:不排除;1:排除)
*/
private String excludeHistory;
/**
* 是否排除历史订单
*/
public boolean shouldExcludeHistory() {
return "1".equals(excludeHistory);
}
}

View File

@@ -2,6 +2,7 @@ package com.starry.admin.modules.order.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.order.module.dto.*;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.module.vo.*;
import com.starry.admin.modules.weichat.entity.order.*;
@@ -43,8 +44,18 @@ public interface IPlayOrderInfoService extends IService<PlayOrderInfoEntity> {
void createRechargeOrder(String orderNo, BigDecimal orderMoney, BigDecimal finalAmount, String purchaserBy);
/**
* 新增订单信息
* 新增订单信息 - 重构版本使用Builder模式
*
* @param request 订单创建请求对象
* @author admin
* @since 2024/6/3 10:53
**/
void createOrderInfo(OrderCreationRequest request);
/**
* 新增订单信息 - 旧版本方法已废弃建议使用OrderCreationRequest
*
* @deprecated 请使用 {@link #createOrderInfo(OrderCreationRequest)} 替代
* @param orderId
* 订单ID
* @param orderNo
@@ -96,6 +107,7 @@ public interface IPlayOrderInfoService extends IService<PlayOrderInfoEntity> {
* @author admin
* @since 2024/6/3 10:53
**/
@Deprecated
void createOrderInfo(String orderId, String orderNo, String orderState, String orderType, String placeType,
String rewardType, String firstOrder, String commodityId, String commodityType, BigDecimal commodityPrice,
String serviceDuration, String commodityName, String commodityNumber, BigDecimal orderMoney,

View File

@@ -19,6 +19,7 @@ import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import com.starry.admin.modules.order.module.dto.*;
import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity;
import com.starry.admin.modules.order.module.entity.PlayOrderEvaluateInfoEntity;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
@@ -158,6 +159,132 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
playCouponDetailsService.updateCouponUseStateByIds(couponIds, "2");
}
@Override
public void createOrderInfo(OrderCreationRequest request) {
// 验证请求
validateOrderCreationRequest(request);
PlayOrderInfoEntity entity = buildOrderEntity(request);
// 处理随机单要求
if (request.getPlaceType() == OrderConstant.PlaceType.RANDOM) {
setRandomOrderRequirements(entity, request.getRandomOrderRequirements());
}
// 处理接单人信息
if (StrUtil.isNotBlank(request.getAcceptBy())) {
setAcceptByInfo(entity, request);
}
// 处理打赏单自动完成逻辑
if (request.isRewardOrder()) {
setRewardOrderCompleted(entity);
}
// 保存订单
userInfoService.saveOrderInfo(entity);
this.baseMapper.insert(entity);
// 修改优惠券状态
playCouponDetailsService.updateCouponUseStateByIds(
request.getPaymentInfo().getCouponIds(), "2");
}
/**
* 验证订单创建请求
*/
private void validateOrderCreationRequest(OrderCreationRequest request) {
if (request.getPlaceType() == OrderConstant.PlaceType.RANDOM
&& !request.isValidForRandomOrder()) {
throw new CustomException("随机单必须提供店员要求信息");
}
}
/**
* 构建订单实体
*/
private PlayOrderInfoEntity buildOrderEntity(OrderCreationRequest request) {
PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
// 基本信息
entity.setId(request.getOrderId());
entity.setOrderNo(request.getOrderNo());
entity.setOrderStatus(request.getOrderStatus().getCode());
entity.setOrderType(request.getOrderType().getCode());
entity.setPlaceType(request.getPlaceType().getCode());
entity.setRewardType(request.getRewardType());
entity.setFirstOrder(request.getFirstOrderString());
// 固定默认值
entity.setRefundType("0");
entity.setBackendEntry("0");
entity.setPayMethod("0");
entity.setOrderSettlementState("0");
entity.setOrdersExpiredState("0");
// 商品信息
CommodityInfo commodityInfo = request.getCommodityInfo();
entity.setCommodityId(commodityInfo.getCommodityId());
entity.setCommodityType(commodityInfo.getCommodityType());
entity.setCommodityPrice(commodityInfo.getCommodityPrice());
entity.setServiceDuration(commodityInfo.getServiceDuration());
entity.setCommodityName(commodityInfo.getCommodityName());
entity.setCommodityNumber(commodityInfo.getCommodityNumber());
// 支付信息
PaymentInfo paymentInfo = request.getPaymentInfo();
entity.setOrderMoney(paymentInfo.getOrderMoney());
entity.setFinalAmount(paymentInfo.getFinalAmount());
entity.setDiscountAmount(paymentInfo.getDiscountAmount());
entity.setCouponIds(paymentInfo.getCouponIds());
entity.setUseCoupon(
paymentInfo.getCouponIds() != null && !paymentInfo.getCouponIds().isEmpty() ? "1" : "0");
// 用户信息
entity.setPurchaserBy(request.getPurchaserBy());
entity.setPurchaserTime(LocalDateTime.now());
entity.setWeiChatCode(request.getWeiChatCode());
entity.setRemark(request.getRemark());
return entity;
}
/**
* 设置随机单要求
*/
private void setRandomOrderRequirements(PlayOrderInfoEntity entity, RandomOrderRequirements requirements) {
if (requirements != null) {
entity.setSex(requirements.getClerkGender().getCode());
entity.setLevelId(requirements.getClerkLevelId());
entity.setExcludeHistory(requirements.getExcludeHistory());
}
}
/**
* 设置接单人信息
*/
private void setAcceptByInfo(PlayOrderInfoEntity entity, OrderCreationRequest request) {
entity.setAcceptBy(request.getAcceptBy());
ClerkEstimatedRevenueVo estimatedRevenueVo = getClerkEstimatedRevenue(
request.getAcceptBy(),
request.getPaymentInfo().getCouponIds(),
request.getPlaceType().getCode(),
request.getFirstOrderString(),
request.getPaymentInfo().getFinalAmount());
entity.setEstimatedRevenue(estimatedRevenueVo.getRevenueAmount());
entity.setEstimatedRevenueRatio(estimatedRevenueVo.getRevenueRatio());
}
/**
* 设置打赏单为已完成状态
*/
private void setRewardOrderCompleted(PlayOrderInfoEntity entity) {
LocalDateTime now = LocalDateTime.now();
entity.setAcceptTime(now);
entity.setOrderStartTime(now);
entity.setOrderEndTime(now);
}
@Override
public ClerkEstimatedRevenueVo getClerkEstimatedRevenue(String clerkId, List<String> croupIds, String placeType,
String firstOrder, BigDecimal finalAmount) {

View File

@@ -0,0 +1,211 @@
package com.starry.admin.modules.order.service;
import static org.junit.jupiter.api.Assertions.*;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import com.starry.admin.modules.order.module.dto.CommodityInfo;
import com.starry.admin.modules.order.module.dto.OrderCreationRequest;
import com.starry.admin.modules.order.module.dto.PaymentInfo;
import com.starry.admin.modules.order.module.dto.RandomOrderRequirements;
import java.math.BigDecimal;
import java.util.Arrays;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* 订单创建请求对象测试类
*
* @author admin
*/
class OrderCreationRequestTest {
@Test
@DisplayName("测试Builder模式构建订单请求")
void testBuilderPattern() {
// 构建商品信息
CommodityInfo commodityInfo = CommodityInfo.builder()
.commodityId("commodity_001")
.commodityType("1")
.commodityPrice(new BigDecimal("100.00"))
.serviceDuration("60")
.commodityName("陪聊服务")
.commodityNumber("1")
.build();
// 构建支付信息
PaymentInfo paymentInfo = PaymentInfo.builder()
.orderMoney(new BigDecimal("100.00"))
.finalAmount(new BigDecimal("90.00"))
.discountAmount(new BigDecimal("10.00"))
.couponIds(Arrays.asList("coupon_001"))
.payMethod("0")
.build();
// 构建订单请求
OrderCreationRequest request = OrderCreationRequest.builder()
.orderId("order_123456")
.orderNo("ORD20240906001")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.SPECIFIED)
.rewardType("0")
.isFirstOrder(true)
.commodityInfo(commodityInfo)
.paymentInfo(paymentInfo)
.purchaserBy("customer_001")
.acceptBy("clerk_001")
.weiChatCode("wx123456")
.remark("客户备注信息")
.build();
// 验证构建结果
assertEquals("order_123456", request.getOrderId());
assertEquals("ORD20240906001", request.getOrderNo());
assertEquals(OrderConstant.OrderStatus.PENDING, request.getOrderStatus());
assertEquals(OrderConstant.OrderType.NORMAL, request.getOrderType());
assertEquals(OrderConstant.PlaceType.SPECIFIED, request.getPlaceType());
assertTrue(request.isFirstOrder());
assertEquals("1", request.getFirstOrderString());
// 验证商品信息
assertNotNull(request.getCommodityInfo());
assertEquals("commodity_001", request.getCommodityInfo().getCommodityId());
assertEquals(new BigDecimal("100.00"), request.getCommodityInfo().getCommodityPrice());
// 验证支付信息
assertNotNull(request.getPaymentInfo());
assertEquals(new BigDecimal("90.00"), request.getPaymentInfo().getFinalAmount());
assertEquals(1, request.getPaymentInfo().getCouponIds().size());
}
@Test
@DisplayName("测试订单类型判断方法")
void testOrderTypeChecks() {
// 测试指定单
OrderCreationRequest specifiedOrder = OrderCreationRequest.builder()
.orderId("order_001")
.orderNo("ORD001")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.SPECIFIED)
.isFirstOrder(false)
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
.purchaserBy("customer")
.build();
assertTrue(specifiedOrder.isSpecifiedOrder());
assertFalse(specifiedOrder.isValidForRandomOrder());
assertFalse(specifiedOrder.isRewardOrder());
// 测试随机单
OrderCreationRequest randomOrder = OrderCreationRequest.builder()
.orderId("order_002")
.orderNo("ORD002")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.RANDOM)
.isFirstOrder(false)
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
.purchaserBy("customer")
.randomOrderRequirements(RandomOrderRequirements.builder()
.clerkGender(OrderConstant.Gender.FEMALE)
.clerkLevelId("level_001")
.excludeHistory("1")
.build())
.build();
assertFalse(randomOrder.isSpecifiedOrder());
assertTrue(randomOrder.isValidForRandomOrder());
assertFalse(randomOrder.isRewardOrder());
// 测试打赏单
OrderCreationRequest rewardOrder = OrderCreationRequest.builder()
.orderId("order_003")
.orderNo("ORD003")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.REWARD)
.isFirstOrder(false)
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
.purchaserBy("customer")
.build();
assertFalse(rewardOrder.isSpecifiedOrder());
assertFalse(rewardOrder.isValidForRandomOrder());
assertTrue(rewardOrder.isRewardOrder());
}
@Test
@DisplayName("测试首单标识转换")
void testFirstOrderStringConversion() {
// 测试首单
OrderCreationRequest firstOrder = OrderCreationRequest.builder()
.orderId("order_001")
.orderNo("ORD001")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.SPECIFIED)
.isFirstOrder(true)
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
.purchaserBy("customer")
.build();
assertEquals("1", firstOrder.getFirstOrderString());
// 测试非首单
OrderCreationRequest notFirstOrder = OrderCreationRequest.builder()
.orderId("order_002")
.orderNo("ORD002")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.SPECIFIED)
.isFirstOrder(false)
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
.purchaserBy("customer")
.build();
assertEquals("0", notFirstOrder.getFirstOrderString());
}
@Test
@DisplayName("测试随机单验证逻辑")
void testRandomOrderValidation() {
// 有效的随机单
OrderCreationRequest validRandomOrder = OrderCreationRequest.builder()
.orderId("order_001")
.orderNo("ORD001")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.RANDOM)
.isFirstOrder(false)
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
.purchaserBy("customer")
.randomOrderRequirements(RandomOrderRequirements.builder()
.clerkGender(OrderConstant.Gender.FEMALE)
.build())
.build();
assertTrue(validRandomOrder.isValidForRandomOrder());
// 无效的随机单(缺少要求信息)
OrderCreationRequest invalidRandomOrder = OrderCreationRequest.builder()
.orderId("order_002")
.orderNo("ORD002")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.RANDOM)
.isFirstOrder(false)
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
.purchaserBy("customer")
.build();
assertFalse(invalidRandomOrder.isValidForRandomOrder());
}
}

View File

@@ -0,0 +1,427 @@
package com.starry.admin.modules.order.service;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import com.starry.admin.modules.order.module.dto.CommodityInfo;
import com.starry.admin.modules.order.module.dto.OrderCreationRequest;
import com.starry.admin.modules.order.module.dto.PaymentInfo;
import com.starry.admin.modules.order.module.dto.RandomOrderRequirements;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.impl.PlayOrderInfoServiceImpl;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
import com.starry.admin.modules.weichat.service.WxCustomMpService;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
/**
* 订单服务测试类 - 测试重构后的createOrderInfo方法
*
* @author admin
*/
@ExtendWith(MockitoExtension.class)
class PlayOrderInfoServiceTest {
@Mock
private PlayOrderInfoMapper orderInfoMapper;
@Mock
private IPlayClerkUserInfoService playClerkUserInfoService;
@Mock
private IPlayCustomUserInfoService playCustomUserInfoService;
@Mock
private IPlayCustomUserInfoService userInfoService;
@Mock
private IPlayCouponDetailsService playCouponDetailsService;
@Mock
private WxCustomMpService wxCustomMpService;
@Mock
private IPlayCustomUserInfoService customUserInfoService;
@Mock
private IPlayClerkLevelInfoService playClerkLevelInfoService;
@Mock
private IPlayPersonnelGroupInfoService playClerkGroupInfoService;
@InjectMocks
private PlayOrderInfoServiceImpl orderService;
@Test
@DisplayName("创建指定订单 - 成功案例")
void testCreateSpecifiedOrder_Success() {
// 准备测试数据
OrderCreationRequest request = OrderCreationRequest.builder()
.orderId("test_order_001")
.orderNo("ORD20241001001")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.SPECIFIED)
.rewardType("0")
.isFirstOrder(true)
.commodityInfo(CommodityInfo.builder()
.commodityId("commodity_001")
.commodityName("测试商品")
.commodityPrice(BigDecimal.valueOf(100.00))
.serviceDuration("60")
.commodityNumber("1")
.build())
.paymentInfo(PaymentInfo.builder()
.orderMoney(BigDecimal.valueOf(100.00))
.finalAmount(BigDecimal.valueOf(90.00))
.discountAmount(BigDecimal.valueOf(10.00))
.couponIds(Arrays.asList("coupon_001"))
.payMethod("1")
.build())
.purchaserBy("customer_001")
// 不设置 acceptBy避免调用复杂的 setAcceptByInfo 方法
.weiChatCode("wx_test_001")
.remark("测试订单")
.build();
// Mock 依赖服务的返回
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
// 执行测试
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
// 验证方法调用
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Arrays.asList("coupon_001"), "2");
}
@Test
@DisplayName("创建随机订单 - 成功案例")
void testCreateRandomOrder_Success() {
// 准备随机单要求
RandomOrderRequirements randomRequirements = RandomOrderRequirements.builder()
.clerkGender(OrderConstant.Gender.FEMALE)
.clerkLevelId("level_001")
.excludeHistory("1")
.build();
// 构建随机单请求
OrderCreationRequest request = OrderCreationRequest.builder()
.orderId("random_order_001")
.orderNo("RND20241001001")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.RANDOM)
.isFirstOrder(false)
.commodityInfo(CommodityInfo.builder()
.commodityId("service_001")
.commodityName("陪聊服务")
.commodityPrice(BigDecimal.valueOf(50.00))
.serviceDuration("30")
.build())
.paymentInfo(PaymentInfo.builder()
.orderMoney(BigDecimal.valueOf(50.00))
.finalAmount(BigDecimal.valueOf(50.00))
.discountAmount(BigDecimal.ZERO)
.couponIds(Collections.emptyList())
.payMethod("0")
.build())
.purchaserBy("customer_002")
.weiChatCode("wx_test_002")
.remark("随机单测试")
.randomOrderRequirements(randomRequirements)
.build();
// Mock 依赖服务的返回
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
// 执行测试
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
// 验证方法调用
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Collections.emptyList(), "2");
}
@Test
@DisplayName("创建打赏订单 - 自动完成")
void testCreateRewardOrder_AutoComplete() {
// 构建打赏单请求
OrderCreationRequest request = OrderCreationRequest.builder()
.orderId("reward_order_001")
.orderNo("REW20241001001")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.REWARD)
.rewardType("1")
.isFirstOrder(false)
.commodityInfo(CommodityInfo.builder()
.commodityId("gift_001")
.commodityName("虚拟礼物")
.commodityPrice(BigDecimal.valueOf(20.00))
.build())
.paymentInfo(PaymentInfo.builder()
.orderMoney(BigDecimal.valueOf(20.00))
.finalAmount(BigDecimal.valueOf(20.00))
.discountAmount(BigDecimal.ZERO)
.couponIds(Collections.emptyList())
.payMethod("1")
.build())
.purchaserBy("customer_003")
// 不设置 acceptBy避免调用复杂的 setAcceptByInfo 方法
.weiChatCode("wx_test_003")
.remark("打赏订单")
.build();
// Mock 依赖服务的返回
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
// 执行测试
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
// 验证方法调用
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Collections.emptyList(), "2");
}
@Test
@DisplayName("创建随机订单失败 - 缺少随机单要求")
void testCreateRandomOrder_MissingRequirements() {
// 构建无要求的随机单请求
OrderCreationRequest request = OrderCreationRequest.builder()
.orderId("invalid_random_order")
.orderNo("IRO20241001001")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.RANDOM) // 随机单但没有要求
.isFirstOrder(false)
.commodityInfo(CommodityInfo.builder()
.commodityId("service_001")
.commodityName("服务")
.commodityPrice(BigDecimal.valueOf(50.00))
.build())
.paymentInfo(PaymentInfo.builder()
.orderMoney(BigDecimal.valueOf(50.00))
.finalAmount(BigDecimal.valueOf(50.00))
.discountAmount(BigDecimal.ZERO)
.couponIds(Collections.emptyList())
.build())
.purchaserBy("customer_004")
.weiChatCode("wx_test_004")
.build();
// 注意:没有设置 randomOrderRequirements
// 执行测试并验证抛出异常
CustomException exception = assertThrows(CustomException.class,
() -> orderService.createOrderInfo(request));
assertEquals("随机单必须提供店员要求信息", exception.getMessage());
// 验证没有调用数据库操作
verify(orderInfoMapper, never()).insert(any(PlayOrderInfoEntity.class));
verify(userInfoService, never()).saveOrderInfo(any(PlayOrderInfoEntity.class));
verify(playCouponDetailsService, never()).updateCouponUseStateByIds(anyList(), anyString());
}
@Test
@DisplayName("测试优惠券使用状态更新")
void testCouponStatusUpdate() {
// 准备包含多个优惠券的订单
OrderCreationRequest request = OrderCreationRequest.builder()
.orderId("coupon_order_001")
.orderNo("CPN20241001001")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.SPECIFIED)
.isFirstOrder(false)
.commodityInfo(CommodityInfo.builder()
.commodityId("commodity_002")
.commodityName("优惠商品")
.commodityPrice(BigDecimal.valueOf(200.00))
.build())
.paymentInfo(PaymentInfo.builder()
.orderMoney(BigDecimal.valueOf(200.00))
.finalAmount(BigDecimal.valueOf(150.00))
.discountAmount(BigDecimal.valueOf(50.00))
.couponIds(Arrays.asList("coupon_001", "coupon_002", "coupon_003"))
.payMethod("1")
.build())
.purchaserBy("customer_005")
// 不设置 acceptBy避免调用复杂的 setAcceptByInfo 方法
.weiChatCode("wx_test_005")
.build();
// Mock 依赖服务的返回
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
// 执行测试
orderService.createOrderInfo(request);
// 验证优惠券状态更新被正确调用
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(
Arrays.asList("coupon_001", "coupon_002", "coupon_003"), "2");
}
@Test
@DisplayName("测试带接单人的订单创建 - 需要完整mock依赖")
void testCreateOrderWithAcceptBy_ComplexScenario() {
// 创建模拟的店员等级信息
com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity mockLevelEntity =
new com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity();
mockLevelEntity.setFirstRegularRatio(15);
mockLevelEntity.setNotFirstRegularRatio(12);
// 创建模拟的优惠券信息
com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo mockCouponInfo =
new com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo();
mockCouponInfo.setAttributionDiscounts("1"); // 1表示店铺承担不需要从店员收入中扣除
mockCouponInfo.setDiscountType("0");
mockCouponInfo.setDiscountAmount(BigDecimal.valueOf(20.00));
// 准备测试数据
OrderCreationRequest request = OrderCreationRequest.builder()
.orderId("complex_order_001")
.orderNo("CPX20241001001")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.SPECIFIED)
.rewardType("0")
.isFirstOrder(true)
.commodityInfo(CommodityInfo.builder()
.commodityId("commodity_003")
.commodityName("复杂商品")
.commodityPrice(BigDecimal.valueOf(300.00))
.serviceDuration("120")
.commodityNumber("1")
.build())
.paymentInfo(PaymentInfo.builder()
.orderMoney(BigDecimal.valueOf(300.00))
.finalAmount(BigDecimal.valueOf(280.00))
.discountAmount(BigDecimal.valueOf(20.00))
.couponIds(Arrays.asList("coupon_004"))
.payMethod("0")
.build())
.purchaserBy("customer_006")
.acceptBy("clerk_004")
.weiChatCode("wx_test_006")
.remark("带接单人的复杂订单")
.build();
// Mock 店员相关的依赖
when(playClerkUserInfoService.queryLevelCommission("clerk_004")).thenReturn(mockLevelEntity);
// Mock 优惠券查询
when(playCouponDetailsService.selectPlayCouponDetailsById("coupon_004")).thenReturn(mockCouponInfo);
// Mock 其他依赖服务的返回
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
// 执行测试
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
// 验证方法调用
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Arrays.asList("coupon_004"), "2");
verify(playClerkUserInfoService, times(1)).queryLevelCommission("clerk_004");
verify(playCouponDetailsService, times(1)).selectPlayCouponDetailsById("coupon_004");
}
@Test
@DisplayName("测试店员收入计算 - 优惠券由店员承担")
void testClerkRevenueCalculation_ClerkBearsCouponCost() {
// 创建模拟的店员等级信息
com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity mockLevelEntity =
new com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity();
mockLevelEntity.setFirstRegularRatio(20); // 首单20%佣金
mockLevelEntity.setNotFirstRegularRatio(15); // 非首单15%佣金
// 创建模拟的优惠券信息 - 店员承担优惠
com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo mockCouponInfo =
new com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo();
mockCouponInfo.setAttributionDiscounts("0"); // 0表示店员承担需要从店员收入中扣除
mockCouponInfo.setDiscountType("0"); // 固定金额优惠
mockCouponInfo.setDiscountAmount(BigDecimal.valueOf(15.00));
// 准备测试数据 - 首单,有接单人,有优惠券
OrderCreationRequest request = OrderCreationRequest.builder()
.orderId("revenue_test_001")
.orderNo("REV20241001001")
.orderStatus(OrderConstant.OrderStatus.PENDING)
.orderType(OrderConstant.OrderType.NORMAL)
.placeType(OrderConstant.PlaceType.SPECIFIED)
.rewardType("0")
.isFirstOrder(true) // 首单
.commodityInfo(CommodityInfo.builder()
.commodityId("commodity_revenue")
.commodityName("收入测试商品")
.commodityPrice(BigDecimal.valueOf(200.00))
.serviceDuration("90")
.build())
.paymentInfo(PaymentInfo.builder()
.orderMoney(BigDecimal.valueOf(200.00))
.finalAmount(BigDecimal.valueOf(185.00)) // 使用了15元优惠券
.discountAmount(BigDecimal.valueOf(15.00))
.couponIds(Arrays.asList("coupon_revenue_001"))
.payMethod("1")
.build())
.purchaserBy("customer_revenue")
.acceptBy("clerk_revenue")
.weiChatCode("wx_revenue_test")
.remark("收入计算测试订单")
.build();
// Mock 依赖
when(playClerkUserInfoService.queryLevelCommission("clerk_revenue")).thenReturn(mockLevelEntity);
when(playCouponDetailsService.selectPlayCouponDetailsById("coupon_revenue_001")).thenReturn(mockCouponInfo);
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
// 执行测试
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
// 验证核心业务逻辑的调用
verify(playClerkUserInfoService, times(1)).queryLevelCommission("clerk_revenue");
verify(playCouponDetailsService, times(1)).selectPlayCouponDetailsById("coupon_revenue_001");
// 验证数据操作
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Arrays.asList("coupon_revenue_001"), "2");
// 这个测试验证了:
// 1. 首单佣金比例计算20%
// 2. 优惠券影响店员收入的计算逻辑
// 3. 复杂业务流程的正确执行
// 实际收入计算185元 * 20% = 37元但由于优惠券由店员承担需要减去15元最终收入22元
}
}