重构订单创建逻辑:采用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:
219
db_workflow.md
Normal file
219
db_workflow.md
Normal 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
|
||||||
@@ -129,6 +129,23 @@
|
|||||||
<version>0.2.0</version>
|
<version>0.2.0</version>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -152,6 +169,14 @@
|
|||||||
<artifactId>flyway-maven-plugin</artifactId>
|
<artifactId>flyway-maven-plugin</artifactId>
|
||||||
<version>7.15.0</version>
|
<version>7.15.0</version>
|
||||||
</plugin>
|
</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>
|
<plugin>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
|||||||
@@ -1,44 +1,136 @@
|
|||||||
package com.starry.admin.modules.order.module.constant;
|
package com.starry.admin.modules.order.module.constant;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 订单相关枚举和常量
|
||||||
|
*
|
||||||
* @author admin
|
* @author admin
|
||||||
* @since 2024/5/8 15:41
|
* @since 2024/5/8 15:41
|
||||||
**/
|
*/
|
||||||
public class OrderConstant {
|
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";
|
public final static String ORDER_STATUS_0 = "0";
|
||||||
|
@Deprecated
|
||||||
/**
|
|
||||||
* 订单状态-待开始
|
|
||||||
*
|
|
||||||
* @since 2024/5/8 15:42
|
|
||||||
**/
|
|
||||||
public final static String ORDER_STATUS_1 = "1";
|
public final static String ORDER_STATUS_1 = "1";
|
||||||
|
@Deprecated
|
||||||
/**
|
|
||||||
* 订单状态-服务中
|
|
||||||
*
|
|
||||||
* @since 2024/5/8 15:42
|
|
||||||
**/
|
|
||||||
public final static String ORDER_STATUS_2 = "2";
|
public final static String ORDER_STATUS_2 = "2";
|
||||||
|
@Deprecated
|
||||||
/**
|
|
||||||
* 订单状态-已完成
|
|
||||||
*
|
|
||||||
* @since 2024/5/8 15:42
|
|
||||||
**/
|
|
||||||
public final static String ORDER_STATUS_3 = "3";
|
public final static String ORDER_STATUS_3 = "3";
|
||||||
|
@Deprecated
|
||||||
/**
|
|
||||||
* 订单状态-已取消
|
|
||||||
*
|
|
||||||
* @since 2024/5/8 15:42
|
|
||||||
**/
|
|
||||||
public final static String ORDER_STATUS_4 = "4";
|
public final static String ORDER_STATUS_4 = "4";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.starry.admin.modules.order.service;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
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.entity.PlayOrderInfoEntity;
|
||||||
import com.starry.admin.modules.order.module.vo.*;
|
import com.starry.admin.modules.order.module.vo.*;
|
||||||
import com.starry.admin.modules.weichat.entity.order.*;
|
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);
|
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
|
* @param orderId
|
||||||
* 订单ID
|
* 订单ID
|
||||||
* @param orderNo
|
* @param orderNo
|
||||||
@@ -96,6 +107,7 @@ public interface IPlayOrderInfoService extends IService<PlayOrderInfoEntity> {
|
|||||||
* @author admin
|
* @author admin
|
||||||
* @since 2024/6/3 10:53
|
* @since 2024/6/3 10:53
|
||||||
**/
|
**/
|
||||||
|
@Deprecated
|
||||||
void createOrderInfo(String orderId, String orderNo, String orderState, String orderType, String placeType,
|
void createOrderInfo(String orderId, String orderNo, String orderState, String orderType, String placeType,
|
||||||
String rewardType, String firstOrder, String commodityId, String commodityType, BigDecimal commodityPrice,
|
String rewardType, String firstOrder, String commodityId, String commodityType, BigDecimal commodityPrice,
|
||||||
String serviceDuration, String commodityName, String commodityNumber, BigDecimal orderMoney,
|
String serviceDuration, String commodityName, String commodityNumber, BigDecimal orderMoney,
|
||||||
|
|||||||
@@ -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.custom.service.IPlayCustomUserInfoService;
|
||||||
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
|
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
|
||||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
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.PlayOrderComplaintInfoEntity;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderEvaluateInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderEvaluateInfoEntity;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
@@ -158,6 +159,132 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
playCouponDetailsService.updateCouponUseStateByIds(couponIds, "2");
|
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
|
@Override
|
||||||
public ClerkEstimatedRevenueVo getClerkEstimatedRevenue(String clerkId, List<String> croupIds, String placeType,
|
public ClerkEstimatedRevenueVo getClerkEstimatedRevenue(String clerkId, List<String> croupIds, String placeType,
|
||||||
String firstOrder, BigDecimal finalAmount) {
|
String firstOrder, BigDecimal finalAmount) {
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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元
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user