Compare commits
177 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3dd3e106dc | ||
|
|
1427538598 | ||
|
|
8f01832b65 | ||
|
|
379df40aee | ||
|
|
a0aa1432ab | ||
|
|
4da4b1f922 | ||
|
|
c12912f1c5 | ||
|
|
5e7cdc1bc4 | ||
|
|
4f39d3ec45 | ||
|
|
4ac409020a | ||
|
|
84324774da | ||
|
|
c74a46b110 | ||
|
|
ed0138c777 | ||
|
|
0a62e19d51 | ||
|
|
083a8cb4c7 | ||
|
|
6446f75239 | ||
|
|
abd1d32e14 | ||
|
|
161fe60bd7 | ||
|
|
3f7f6f8c32 | ||
|
|
5750adeb03 | ||
|
|
18832cf72d | ||
|
|
e39643fdb7 | ||
|
|
5d9774aaaf | ||
|
|
6b9ecb43a2 | ||
|
|
7fbb95542a | ||
|
|
75e996aa30 | ||
|
|
35a56fe17d | ||
|
|
6ee37775b1 | ||
|
|
c4de5572f5 | ||
|
|
4611bfed18 | ||
|
|
c3d764ec40 | ||
|
|
a51427790b | ||
|
|
01afb31fdc | ||
|
|
842e09aac5 | ||
|
|
fe60e7647b | ||
|
|
ac599762da | ||
|
|
9a51e16fef | ||
|
|
19d7917b39 | ||
|
|
4f5fbcdc8f | ||
|
|
b422efe757 | ||
|
|
ae47dde897 | ||
|
|
4dddae325c | ||
|
|
de0a9c1fa7 | ||
|
|
02383f1f3a | ||
| 69365e076e | |||
|
|
3e19abd320 | ||
|
|
74666a33cb | ||
|
|
1888d82f58 | ||
|
|
8e9e614521 | ||
|
|
f672b5dacc | ||
| 230d7d451e | |||
|
|
2ac5483eb2 | ||
|
|
8ae9200707 | ||
|
|
289f3ce2ad | ||
|
|
4bbafb3de2 | ||
|
|
0e2d60395d | ||
|
|
f0a5034b9a | ||
|
|
f04754c325 | ||
|
|
da2da5fe75 | ||
|
|
0fe6f473df | ||
|
|
2f8a34e2e0 | ||
| 45199d8aba | |||
|
|
96ee3da7d0 | ||
| a3beef7d29 | |||
|
|
aad2ea0904 | ||
| 77779cfdbd | |||
|
|
0a474babea | ||
| 029745021c | |||
| cc191c7f03 | |||
| 4766a599b8 | |||
|
|
9e2ddf56fc | ||
| ac111fbc74 | |||
| cc5f37fbe2 | |||
| d7a476f7b4 | |||
|
|
be908f7534 | ||
|
|
8435048156 | ||
|
|
e09440ba63 | ||
|
|
8683ef126b | ||
| 46ca9c7931 | |||
| d9218793cf | |||
|
|
7b760f794b | ||
|
|
a8b8d270ab | ||
| 8c5295094b | |||
| befbb692a9 | |||
|
|
d03bd5a36c | ||
|
|
be26d87552 | ||
| 72ffc10f6c | |||
| 1196328ddb | |||
| 3aaf217287 | |||
| 01d3ef2abf | |||
| b2e315a1de | |||
|
|
614570a244 | ||
|
|
4368fded04 | ||
|
|
cffb88d368 | ||
|
|
4fb18fc40e | ||
|
|
f0ce1fe825 | ||
|
|
4c163620be | ||
|
|
709fb0433b | ||
|
|
8c1b5d8be3 | ||
|
|
3d84686fcf | ||
|
|
8e47a764b3 | ||
|
|
9567309655 | ||
|
|
a03c097dfb | ||
|
|
1368b13488 | ||
| 8d1d677a33 | |||
| 0c43adc6a8 | |||
|
|
e5df00b806 | ||
|
|
6834d7c83c | ||
|
|
7b9a858fbc | ||
|
|
9a663f80a8 | ||
|
|
eca597f3e4 | ||
|
|
69609352ba | ||
| 14fcb55e51 | |||
| 559db4c1e1 | |||
|
|
30baa1a00a | ||
|
|
8d286b6d2f | ||
|
|
cb0a8615df | ||
| 6f917c8f88 | |||
| 6a187dc53f | |||
| 089ff04ff6 | |||
| 06c58e3333 | |||
| 30a9ad15c7 | |||
| 678135dc80 | |||
| 507f64fac6 | |||
| e0f5523a11 | |||
| 415b16fb17 | |||
| f2b65cc219 | |||
|
|
01778fbfef | ||
|
|
d35d19c30a | ||
|
|
2dd4a3dedf | ||
|
|
6103847d4e | ||
| 1da24de6d1 | |||
|
|
697dc03d59 | ||
|
|
2cf6e108ca | ||
|
|
81fe1c6adb | ||
|
|
b19f09af24 | ||
|
|
b7268f4447 | ||
|
|
01fd4b90ce | ||
|
|
ac2c95ae69 | ||
|
|
d1e39c2179 | ||
| 0025d9d6e7 | |||
|
|
48512a4027 | ||
| 606e1402f2 | |||
|
|
df3af2ade4 | ||
|
|
2af7db1fe8 | ||
|
|
73c2377afd | ||
|
|
1aa1eee372 | ||
|
|
e6cc7ff683 | ||
| cd0807f1fc | |||
| 47ce88eb2b | |||
|
|
e2eb0a9ce1 | ||
|
|
038c10699a | ||
|
|
176f587996 | ||
|
|
3a5046809b | ||
|
|
888fab6805 | ||
|
|
97928ddeda | ||
|
|
b28df6b2af | ||
|
|
d4a0af5f80 | ||
|
|
d1f218d170 | ||
|
|
d57df76616 | ||
|
|
9be32342d3 | ||
|
|
13fc39d484 | ||
|
|
a8bfaf2d4b | ||
|
|
7616fa5e7f | ||
|
|
8e0bd4ac6c | ||
|
|
139f49e87b | ||
|
|
488dd0ae56 | ||
|
|
cbec4824de | ||
|
|
209bc304ba | ||
|
|
a9139f0388 | ||
|
|
d1e66c8101 | ||
|
|
45435b7601 | ||
|
|
ff71aa633c | ||
|
|
ce92e1e056 | ||
|
|
7b7ad367ed | ||
|
|
141f890d1a | ||
|
|
e4fa8ff7ad |
97
.drone.yml
Normal file
97
.drone.yml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: kubernetes
|
||||||
|
|
||||||
|
name: build-and-deploy-dev
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: maven-cache
|
||||||
|
claim:
|
||||||
|
name: maven-cache-pvc
|
||||||
|
|
||||||
|
service_account_name: drone-builder-sa
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
branch:
|
||||||
|
- dev
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: dev-build
|
||||||
|
image: ccr.ccs.tencentyun.com/violin/maven:3.9-eclipse-temurin-22
|
||||||
|
commands:
|
||||||
|
- mvn clean package -DskipTests
|
||||||
|
volumes:
|
||||||
|
- name: maven-cache
|
||||||
|
path: /root/.m2
|
||||||
|
|
||||||
|
- name: dev-build-image
|
||||||
|
# 使用 plugin/kaniko # # # # # #
|
||||||
|
image: ccr.ccs.tencentyun.com/violin/drone-kaniko:latest
|
||||||
|
settings:
|
||||||
|
repo: ccr.ccs.tencentyun.com/violin/todo-backend
|
||||||
|
tags:
|
||||||
|
- latest
|
||||||
|
- ${DRONE_COMMIT_SHA:0:8}
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
context: .
|
||||||
|
registry: ccr.ccs.tencentyun.com
|
||||||
|
username: 100024540033
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
|
||||||
|
- name: dev-deploy
|
||||||
|
image: ccr.ccs.tencentyun.com/violin/kubectl:latest
|
||||||
|
cluster: kubernetes
|
||||||
|
namespace: drone
|
||||||
|
commands:
|
||||||
|
- kubectl set image deployment/todo-backend todo-backend=ccr.ccs.tencentyun.com/violin/todo-backend:${DRONE_COMMIT_SHA:0:8} -n drone
|
||||||
|
- kubectl rollout restart deployment/todo-backend -n drone
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: kubernetes
|
||||||
|
|
||||||
|
name: build-and-deploy-prod
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: maven-cache
|
||||||
|
claim:
|
||||||
|
name: maven-cache-pvc
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
branch:
|
||||||
|
- master
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: prod-build
|
||||||
|
image: ccr.ccs.tencentyun.com/violin/maven:3.9-eclipse-temurin-22
|
||||||
|
commands:
|
||||||
|
- mvn clean package -DskipTests
|
||||||
|
volumes:
|
||||||
|
- name: maven-cache
|
||||||
|
path: /root/.m2
|
||||||
|
|
||||||
|
- name: prod-build-image
|
||||||
|
##
|
||||||
|
image: ccr.ccs.tencentyun.com/violin/drone-kaniko:latest
|
||||||
|
settings:
|
||||||
|
repo: ccr.ccs.tencentyun.com/violin/todo-backend
|
||||||
|
tags:
|
||||||
|
- v1.0.0
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
context: .
|
||||||
|
registry: ccr.ccs.tencentyun.com
|
||||||
|
username: 100024540033
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
|
||||||
|
- name: prod-deploy
|
||||||
|
image: ccr.ccs.tencentyun.com/violin/kubectl:latest
|
||||||
|
cluster: kubernetes
|
||||||
|
namespace: drone
|
||||||
|
commands:
|
||||||
|
- kubectl set image deployment/todo-backend todo-backend=ccr.ccs.tencentyun.com/violin/todo-backend:v1.0.0
|
||||||
|
- kubectl rollout status deployment/todo-backend
|
||||||
14
Dockerfile
14
Dockerfile
@@ -1,18 +1,8 @@
|
|||||||
FROM eclipse-temurin:17-jdk-alpine AS builder
|
FROM ccr.ccs.tencentyun.com/violin/eclipse-temurin:22-jre
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY pom.xml .
|
COPY target/*.jar app.jar
|
||||||
COPY src ./src
|
|
||||||
|
|
||||||
RUN apk add --no-cache maven && \
|
|
||||||
mvn clean package -DskipTests
|
|
||||||
|
|
||||||
FROM eclipse-temurin:17-jre-alpine
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY --from=builder /app/target/*.jar app.jar
|
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
|
|||||||
16
k8s/configmap.yaml
Normal file
16
k8s/configmap.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: todo-backend-config
|
||||||
|
namespace: drone
|
||||||
|
data:
|
||||||
|
SPRING_DATASOURCE_HOST: "192.168.3.49"
|
||||||
|
SPRING_DATASOURCE_PORT: "5432"
|
||||||
|
SPRING_DATASOURCE_DB: "sales_dev"
|
||||||
|
SPRING_DATASOURCE_USERNAME: "sales_dev"
|
||||||
|
SPRING_REDIS_HOST: "redis.drone.svc.cluster.local"
|
||||||
|
SPRING_REDIS_PORT: "6379"
|
||||||
|
SPRING_REDIS_PASSWORD: ""
|
||||||
|
JWT_SECRET: "building-materials-secret-key-2024"
|
||||||
|
JWT_EXPIRATION: "7200000"
|
||||||
|
JWT_REFRESH_EXPIRATION: "604800000"
|
||||||
47
k8s/deployment.yaml
Normal file
47
k8s/deployment.yaml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: todo-backend
|
||||||
|
namespace: drone
|
||||||
|
labels:
|
||||||
|
app: todo-backend
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: todo-backend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: todo-backend
|
||||||
|
spec:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: tencentyun-secret
|
||||||
|
containers:
|
||||||
|
- name: todo-backend
|
||||||
|
image: ccr.ccs.tencentyun.com/violin/todo-backend:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: todo-backend-config
|
||||||
|
env:
|
||||||
|
- name: SPRING_DATASOURCE_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: todo-backend-secret
|
||||||
|
key: SPRING_DATASOURCE_PASSWORD
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: todo-backend
|
||||||
|
namespace: drone
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: todo-backend
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
type: ClusterIP
|
||||||
9
k8s/secret.yaml
Normal file
9
k8s/secret.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: todo-backend-secret
|
||||||
|
namespace: drone
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
# echo -n 'your_password' | base64
|
||||||
|
SPRING_DATASOURCE_PASSWORD: c2FsZXNfZGV2Cg==
|
||||||
39
pom.xml
39
pom.xml
@@ -46,6 +46,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>42.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -79,7 +80,8 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<optional>true</optional>
|
<version>1.18.30</version>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Apache Commons -->
|
<!-- Apache Commons -->
|
||||||
@@ -95,6 +97,13 @@
|
|||||||
<version>2.0.43</version>
|
<version>2.0.43</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Flyway 数据库版本管理 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-core</artifactId>
|
||||||
|
<version>8.5.13</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Test -->
|
<!-- Test -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@@ -106,17 +115,33 @@
|
|||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<excludes>
|
<release>17</release>
|
||||||
<exclude>
|
<encoding>UTF-8</encoding>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
</exclude>
|
<version>1.18.30</version>
|
||||||
</excludes>
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>2.7.18</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ public class RedisConfig {
|
|||||||
ObjectMapper.DefaultTyping.NON_FINAL);
|
ObjectMapper.DefaultTyping.NON_FINAL);
|
||||||
|
|
||||||
// 使用Jackson2JsonRedisSerializer序列化value
|
// 使用Jackson2JsonRedisSerializer序列化value
|
||||||
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(mapper, Object.class);
|
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||||
|
serializer.setObjectMapper(mapper);
|
||||||
|
|
||||||
// 设置key和value的序列化方式
|
// 设置key和value的序列化方式
|
||||||
template.setKeySerializer(new StringRedisSerializer());
|
template.setKeySerializer(new StringRedisSerializer());
|
||||||
|
|||||||
@@ -38,10 +38,16 @@ public class AuthController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 微信登录
|
* 微信登录
|
||||||
|
* @param code 微信授权码
|
||||||
|
* @param nickname 微信昵称(可选)
|
||||||
|
* @param avatar 微信头像URL(可选)
|
||||||
*/
|
*/
|
||||||
@PostMapping("/wechat")
|
@PostMapping("/wechat")
|
||||||
public Result<Map<String, Object>> wechatLogin(@RequestParam String code) {
|
public Result<Map<String, Object>> wechatLogin(
|
||||||
Map<String, Object> result = authService.wechatLogin(code);
|
@RequestParam String code,
|
||||||
|
@RequestParam(required = false) String nickname,
|
||||||
|
@RequestParam(required = false) String avatar) {
|
||||||
|
Map<String, Object> result = authService.wechatLogin(code, nickname, avatar);
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ public class CustomerController {
|
|||||||
@GetMapping
|
@GetMapping
|
||||||
public Result<Page<Customer>> getCustomers(
|
public Result<Page<Customer>> getCustomers(
|
||||||
@RequestParam(required = false) String keyword,
|
@RequestParam(required = false) String keyword,
|
||||||
|
@RequestParam(required = false) String type,
|
||||||
@RequestParam(defaultValue = "1") Integer page,
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
@RequestParam(defaultValue = "20") Integer pageSize) {
|
@RequestParam(defaultValue = "20") Integer pageSize) {
|
||||||
return Result.success(customerService.getCustomers(keyword, page, pageSize));
|
return Result.success(customerService.getCustomers(keyword, type, page, pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.example.building.common.Result;
|
|||||||
import com.example.building.dto.CreateOrderRequest;
|
import com.example.building.dto.CreateOrderRequest;
|
||||||
import com.example.building.entity.Order;
|
import com.example.building.entity.Order;
|
||||||
import com.example.building.service.OrderService;
|
import com.example.building.service.OrderService;
|
||||||
|
import com.example.building.service.SystemConfigService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ import java.util.Map;
|
|||||||
* - 订单创建:计算原价(total_amount)、优惠金额(discount_amount)、实付金额(actual_amount)
|
* - 订单创建:计算原价(total_amount)、优惠金额(discount_amount)、实付金额(actual_amount)
|
||||||
* - 订单原价 = 商品标价 × 数量之和
|
* - 订单原价 = 商品标价 × 数量之和
|
||||||
* - 实付金额 = 原价 - 优惠金额
|
* - 实付金额 = 原价 - 优惠金额
|
||||||
|
* - 顾客角色:只能查看半年内的订单(可配置)
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/orders")
|
@RequestMapping("/api/v1/orders")
|
||||||
@@ -24,40 +26,57 @@ public class OrderController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private OrderService orderService;
|
private OrderService orderService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemConfigService systemConfigService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建订单
|
* 创建订单
|
||||||
* 核心逻辑:
|
* 顾客角色不允许创建订单
|
||||||
* 1. 计算订单原价(total_amount) = Σ(item.price × item.quantity)
|
|
||||||
* 2. 计算优惠金额(discount_amount) = total_amount × (100 - discount_rate) / 100
|
|
||||||
* 3. 计算实付金额(actual_amount) = total_amount - discount_amount
|
|
||||||
*/
|
*/
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public Result<Order> createOrder(@RequestBody CreateOrderRequest request,
|
public Result<Order> createOrder(@RequestBody CreateOrderRequest request,
|
||||||
@RequestHeader("X-User-Id") String operatorId,
|
@RequestHeader("X-User-Id") String operatorId,
|
||||||
@RequestHeader("X-Username") String operatorName) {
|
@RequestHeader("X-Username") String operatorName,
|
||||||
|
@RequestHeader(value = "X-User-Role", required = false) String role) {
|
||||||
|
// 顾客角色不允许创建订单
|
||||||
|
if ("customer".equals(role)) {
|
||||||
|
return Result.error("顾客账号不允许创建订单,请联系销售人员");
|
||||||
|
}
|
||||||
return Result.success(orderService.createOrder(request, operatorId, operatorName));
|
return Result.success(orderService.createOrder(request, operatorId, operatorName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取订单列表
|
* 获取订单列表
|
||||||
|
* 顾客角色只能看到半年内的订单(可配置)
|
||||||
*/
|
*/
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public Result<Page<Order>> getOrders(
|
public Result<Page<Order>> getOrders(
|
||||||
@RequestParam(required = false) String customerId,
|
@RequestParam(required = false) String customerId,
|
||||||
|
@RequestParam(required = false) String customerName,
|
||||||
@RequestParam(required = false) Integer status,
|
@RequestParam(required = false) Integer status,
|
||||||
@RequestParam(required = false) String startDate,
|
@RequestParam(required = false) String startDate,
|
||||||
@RequestParam(required = false) String endDate,
|
@RequestParam(required = false) String endDate,
|
||||||
@RequestParam(defaultValue = "1") Integer page,
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
@RequestParam(defaultValue = "20") Integer pageSize) {
|
@RequestParam(defaultValue = "20") Integer pageSize,
|
||||||
return Result.success(orderService.getOrders(customerId, status, startDate, endDate, page, pageSize));
|
@RequestHeader(value = "X-User-Role", required = false) String role) {
|
||||||
|
|
||||||
|
// 顾客角色:限制时间范围
|
||||||
|
if ("customer".equals(role) && startDate == null) {
|
||||||
|
startDate = systemConfigService.getCustomerOrderStartDate().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(orderService.getOrders(customerId, customerName, status, startDate, endDate, page, pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取订单详情
|
* 获取订单详情
|
||||||
|
* 顾客只能查看自己的订单
|
||||||
*/
|
*/
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
public Result<Map<String, Object>> getOrderDetail(@PathVariable String id) {
|
public Result<Map<String, Object>> getOrderDetail(@PathVariable String id,
|
||||||
return Result.success(orderService.getOrderDetail(id));
|
@RequestHeader("X-User-Id") String userId,
|
||||||
|
@RequestHeader(value = "X-User-Role", required = false) String role) {
|
||||||
|
return Result.success(orderService.getOrderDetail(id, userId, role));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +84,12 @@ public class OrderController {
|
|||||||
*/
|
*/
|
||||||
@PutMapping("/{id}/cancel")
|
@PutMapping("/{id}/cancel")
|
||||||
public Result<Void> cancelOrder(@PathVariable String id,
|
public Result<Void> cancelOrder(@PathVariable String id,
|
||||||
@RequestHeader("X-User-Id") String operatorId) {
|
@RequestHeader("X-User-Id") String operatorId,
|
||||||
|
@RequestHeader(value = "X-User-Role", required = false) String role) {
|
||||||
|
// 顾客不能取消订单
|
||||||
|
if ("customer".equals(role)) {
|
||||||
|
return Result.error("顾客账号不允许取消订单");
|
||||||
|
}
|
||||||
orderService.cancelOrder(id, operatorId);
|
orderService.cancelOrder(id, operatorId);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
@@ -75,18 +99,58 @@ public class OrderController {
|
|||||||
*/
|
*/
|
||||||
@PutMapping("/{id}/refund")
|
@PutMapping("/{id}/refund")
|
||||||
public Result<Void> refundOrder(@PathVariable String id,
|
public Result<Void> refundOrder(@PathVariable String id,
|
||||||
@RequestHeader("X-User-Id") String operatorId) {
|
@RequestHeader("X-User-Id") String operatorId,
|
||||||
|
@RequestHeader(value = "X-User-Role", required = false) String role) {
|
||||||
|
// 顾客不能退款
|
||||||
|
if ("customer".equals(role)) {
|
||||||
|
return Result.error("顾客账号不允许退款操作");
|
||||||
|
}
|
||||||
orderService.refundOrder(id, operatorId);
|
orderService.refundOrder(id, operatorId);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新订单状态(确认完成/取消)
|
||||||
|
*/
|
||||||
|
@PutMapping("/{id}/status")
|
||||||
|
public Result<Void> updateOrderStatus(@PathVariable String id,
|
||||||
|
@RequestBody Map<String, Integer> params,
|
||||||
|
@RequestHeader("X-User-Id") String operatorId,
|
||||||
|
@RequestHeader(value = "X-User-Role", required = false) String role) {
|
||||||
|
if ("customer".equals(role)) {
|
||||||
|
return Result.error("顾客账号不允许操作");
|
||||||
|
}
|
||||||
|
Integer status = params.get("status");
|
||||||
|
orderService.updateOrderStatus(id, status, operatorId);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新订单(编辑)
|
||||||
|
*/
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public Result<Order> updateOrder(@PathVariable String id,
|
||||||
|
@RequestBody CreateOrderRequest request,
|
||||||
|
@RequestHeader("X-User-Id") String operatorId,
|
||||||
|
@RequestHeader(value = "X-User-Role", required = false) String role) {
|
||||||
|
if ("customer".equals(role)) {
|
||||||
|
return Result.error("顾客账号不允许操作");
|
||||||
|
}
|
||||||
|
return Result.success(orderService.updateOrder(id, request, operatorId));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单统计
|
* 订单统计
|
||||||
*/
|
*/
|
||||||
@GetMapping("/statistics")
|
@GetMapping("/statistics")
|
||||||
public Result<Map<String, Object>> getStatistics(
|
public Result<Map<String, Object>> getStatistics(
|
||||||
@RequestParam(required = false) String startDate,
|
@RequestParam(required = false) String startDate,
|
||||||
@RequestParam(required = false) String endDate) {
|
@RequestParam(required = false) String endDate,
|
||||||
|
@RequestHeader(value = "X-User-Role", required = false) String role) {
|
||||||
|
// 顾客不能查看统计
|
||||||
|
if ("customer".equals(role)) {
|
||||||
|
return Result.error("顾客账号不允许查看统计");
|
||||||
|
}
|
||||||
return Result.success(orderService.getStatistics(startDate, endDate));
|
return Result.success(orderService.getStatistics(startDate, endDate));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,26 +31,38 @@ public class ProductController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增分类
|
* 新增分类(仅管理员)
|
||||||
*/
|
*/
|
||||||
@PostMapping("/categories")
|
@PostMapping("/categories")
|
||||||
public Result<Category> createCategory(@RequestBody Category category) {
|
public Result<Category> createCategory(@RequestBody Category category,
|
||||||
|
@RequestHeader(value = "X-User-Role", required = false) String role) {
|
||||||
|
if (!"admin".equals(role)) {
|
||||||
|
return Result.error("只有管理员可以操作");
|
||||||
|
}
|
||||||
return Result.success(productService.createCategory(category));
|
return Result.success(productService.createCategory(category));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改分类
|
* 修改分类(仅管理员)
|
||||||
*/
|
*/
|
||||||
@PutMapping("/categories/{id}")
|
@PutMapping("/categories/{id}")
|
||||||
public Result<Category> updateCategory(@PathVariable String id, @RequestBody Category category) {
|
public Result<Category> updateCategory(@PathVariable String id, @RequestBody Category category,
|
||||||
|
@RequestHeader(value = "X-User-Role", required = false) String role) {
|
||||||
|
if (!"admin".equals(role)) {
|
||||||
|
return Result.error("只有管理员可以操作");
|
||||||
|
}
|
||||||
return Result.success(productService.updateCategory(id, category));
|
return Result.success(productService.updateCategory(id, category));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除分类
|
* 删除分类(仅管理员)
|
||||||
*/
|
*/
|
||||||
@DeleteMapping("/categories/{id}")
|
@DeleteMapping("/categories/{id}")
|
||||||
public Result<Void> deleteCategory(@PathVariable String id) {
|
public Result<Void> deleteCategory(@PathVariable String id,
|
||||||
|
@RequestHeader(value = "X-User-Role", required = false) String role) {
|
||||||
|
if (!"admin".equals(role)) {
|
||||||
|
return Result.error("只有管理员可以操作");
|
||||||
|
}
|
||||||
productService.deleteCategory(id);
|
productService.deleteCategory(id);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
@@ -67,6 +79,18 @@ public class ProductController {
|
|||||||
return Result.success(productService.getProducts(categoryId, keyword, page, pageSize));
|
return Result.success(productService.getProducts(categoryId, keyword, page, pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有商品(包括下架的,用于管理)
|
||||||
|
*/
|
||||||
|
@GetMapping("/all")
|
||||||
|
public Result<Page<Product>> getAllProducts(
|
||||||
|
@RequestParam(required = false) String categoryId,
|
||||||
|
@RequestParam(required = false) String keyword,
|
||||||
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(defaultValue = "20") Integer pageSize) {
|
||||||
|
return Result.success(productService.getAllProducts(categoryId, keyword, page, pageSize));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取商品详情
|
* 获取商品详情
|
||||||
*/
|
*/
|
||||||
@@ -76,26 +100,38 @@ public class ProductController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增商品
|
* 新增商品(仅管理员)
|
||||||
*/
|
*/
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public Result<Product> createProduct(@RequestBody Product product) {
|
public Result<Product> createProduct(@RequestBody Product product,
|
||||||
|
@RequestHeader(value = "X-User-Role", required = false) String role) {
|
||||||
|
if (!"admin".equals(role)) {
|
||||||
|
return Result.error("只有管理员可以操作");
|
||||||
|
}
|
||||||
return Result.success(productService.createProduct(product));
|
return Result.success(productService.createProduct(product));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改商品
|
* 修改商品(仅管理员)
|
||||||
*/
|
*/
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
public Result<Product> updateProduct(@PathVariable String id, @RequestBody Product product) {
|
public Result<Product> updateProduct(@PathVariable String id, @RequestBody Product product,
|
||||||
|
@RequestHeader(value = "X-User-Role", required = false) String role) {
|
||||||
|
if (!"admin".equals(role)) {
|
||||||
|
return Result.error("只有管理员可以操作");
|
||||||
|
}
|
||||||
return Result.success(productService.updateProduct(id, product));
|
return Result.success(productService.updateProduct(id, product));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除商品
|
* 删除商品(仅管理员)
|
||||||
*/
|
*/
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
public Result<Void> deleteProduct(@PathVariable String id) {
|
public Result<Void> deleteProduct(@PathVariable String id,
|
||||||
|
@RequestHeader(value = "X-User-Role", required = false) String role) {
|
||||||
|
if (!"admin".equals(role)) {
|
||||||
|
return Result.error("只有管理员可以操作");
|
||||||
|
}
|
||||||
productService.deleteProduct(id);
|
productService.deleteProduct(id);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package com.example.building.controller;
|
||||||
|
|
||||||
|
import com.example.building.common.Result;
|
||||||
|
import com.example.building.entity.Order;
|
||||||
|
import com.example.building.entity.OrderItem;
|
||||||
|
import com.example.building.mapper.OrderItemMapper;
|
||||||
|
import com.example.building.mapper.OrderMapper;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公开订单查看控制器
|
||||||
|
* 无需权限认证,用于客户通过分享链接查看订单
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/public")
|
||||||
|
public class PublicOrderController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrderMapper orderMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrderItemMapper orderItemMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过订单号和客户ID查询订单详情
|
||||||
|
* 公开接口,需同时提供订单号和客户ID才能查看
|
||||||
|
*/
|
||||||
|
@GetMapping("/orders/{orderNo}")
|
||||||
|
public Result<Map<String, Object>> getOrderByNo(
|
||||||
|
@PathVariable String orderNo,
|
||||||
|
@RequestParam String customerId) {
|
||||||
|
|
||||||
|
// 查询订单
|
||||||
|
Order order = orderMapper.selectOne(
|
||||||
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Order>()
|
||||||
|
.eq(Order::getOrderNo, orderNo)
|
||||||
|
.eq(Order::getCustomerId, customerId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (order == null) {
|
||||||
|
return Result.error("订单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询订单明细
|
||||||
|
List<OrderItem> items = orderItemMapper.selectList(
|
||||||
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<OrderItem>()
|
||||||
|
.eq(OrderItem::getOrderId, order.getOrderId())
|
||||||
|
);
|
||||||
|
|
||||||
|
// 构建返回数据(只返回公开信息)
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("orderNo", order.getOrderNo());
|
||||||
|
result.put("status", order.getStatus());
|
||||||
|
result.put("statusText", getStatusText(order.getStatus()));
|
||||||
|
result.put("customerName", order.getCustomerName() != null ? order.getCustomerName() : "散客");
|
||||||
|
result.put("customerPhone", order.getCustomerPhone());
|
||||||
|
result.put("totalAmount", order.getTotalAmount());
|
||||||
|
result.put("discountAmount", order.getDiscountAmount());
|
||||||
|
result.put("actualAmount", order.getActualAmount());
|
||||||
|
result.put("paymentMethod", getPaymentText(order.getPaymentMethod()));
|
||||||
|
result.put("createdAt", order.getCreatedAt());
|
||||||
|
result.put("items", items);
|
||||||
|
result.put("remark", order.getRemark());
|
||||||
|
|
||||||
|
return Result.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getStatusText(Integer status) {
|
||||||
|
if (status == null) return "未知";
|
||||||
|
switch (status) {
|
||||||
|
case 0: return "进行中";
|
||||||
|
case 1: return "已完成";
|
||||||
|
case 2: return "已取消";
|
||||||
|
case 3: return "退款中";
|
||||||
|
case 4: return "已退款";
|
||||||
|
case 9: return "退货中";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPaymentText(String method) {
|
||||||
|
if (method == null) return "-";
|
||||||
|
switch (method) {
|
||||||
|
case "cash": return "现金";
|
||||||
|
case "wechat": return "微信";
|
||||||
|
case "alipay": return "支付宝";
|
||||||
|
default: return method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.example.building.controller;
|
||||||
|
|
||||||
|
import com.example.building.common.Result;
|
||||||
|
import com.example.building.entity.SystemConfig;
|
||||||
|
import com.example.building.mapper.SystemConfigMapper;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统配置控制器
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/system-config")
|
||||||
|
public class SystemConfigController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemConfigMapper systemConfigMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有配置
|
||||||
|
*/
|
||||||
|
@GetMapping
|
||||||
|
public Result<Map<String, String>> getAllConfig() {
|
||||||
|
List<SystemConfig> configs = systemConfigMapper.selectList(null);
|
||||||
|
Map<String, String> result = new HashMap<>();
|
||||||
|
for (SystemConfig config : configs) {
|
||||||
|
result.put(config.getConfigKey(), config.getConfigValue());
|
||||||
|
}
|
||||||
|
return Result.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置
|
||||||
|
*/
|
||||||
|
@PutMapping
|
||||||
|
public Result<Void> updateConfig(@RequestBody SystemConfig config) {
|
||||||
|
// 检查是否存在
|
||||||
|
SystemConfig existing = systemConfigMapper.selectOne(
|
||||||
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SystemConfig>()
|
||||||
|
.eq(SystemConfig::getConfigKey, config.getConfigKey())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existing != null) {
|
||||||
|
existing.setConfigValue(config.getConfigValue());
|
||||||
|
existing.setRemark(config.getRemark());
|
||||||
|
systemConfigMapper.updateById(existing);
|
||||||
|
} else {
|
||||||
|
config.setConfigId(java.util.UUID.randomUUID().toString());
|
||||||
|
systemConfigMapper.insert(config);
|
||||||
|
}
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.example.building.dto;
|
|||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
import javax.validation.constraints.NotEmpty;
|
import javax.validation.constraints.NotEmpty;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -29,10 +30,15 @@ public class CreateOrderRequest {
|
|||||||
private List<OrderItemDTO> items;
|
private List<OrderItemDTO> items;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 折扣率(百分比), 默认100
|
* 折扣率(百分比), 默认100(保留此逻辑)
|
||||||
*/
|
*/
|
||||||
private BigDecimal discountRate;
|
private BigDecimal discountRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠金额(元)
|
||||||
|
*/
|
||||||
|
private BigDecimal discountMoney;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 备注
|
* 备注
|
||||||
*/
|
*/
|
||||||
@@ -64,5 +70,20 @@ public class CreateOrderRequest {
|
|||||||
* 销售单价(用户可自定义)
|
* 销售单价(用户可自定义)
|
||||||
*/
|
*/
|
||||||
private BigDecimal price;
|
private BigDecimal price;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 长度(cm)
|
||||||
|
*/
|
||||||
|
private BigDecimal length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 宽度(cm)
|
||||||
|
*/
|
||||||
|
private BigDecimal width;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 面积(m²)
|
||||||
|
*/
|
||||||
|
private BigDecimal area;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ public class Category {
|
|||||||
*/
|
*/
|
||||||
private String icon;
|
private String icon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态: 1启用 0禁用
|
* 状态: 1启用 0禁用
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -31,6 +31,16 @@ public class Customer {
|
|||||||
*/
|
*/
|
||||||
private String wechatOpenid;
|
private String wechatOpenid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信昵称
|
||||||
|
*/
|
||||||
|
private String wechatNickname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信头像
|
||||||
|
*/
|
||||||
|
private String wechatAvatar;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 地址
|
* 地址
|
||||||
*/
|
*/
|
||||||
@@ -41,6 +51,11 @@ public class Customer {
|
|||||||
*/
|
*/
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户种类: customer=顾客, carpenter=木匠, company=装修公司
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 累计消费金额
|
* 累计消费金额
|
||||||
*/
|
*/
|
||||||
@@ -51,6 +66,11 @@ public class Customer {
|
|||||||
*/
|
*/
|
||||||
private String createdBy;
|
private String createdBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后登录时间(用于订单客户排序)
|
||||||
|
*/
|
||||||
|
private LocalDateTime lastLoginAt;
|
||||||
|
|
||||||
@TableField(fill = FieldFill.INSERT)
|
@TableField(fill = FieldFill.INSERT)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import lombok.Data;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单实体类
|
* 订单实体类
|
||||||
@@ -57,6 +58,11 @@ public class Order {
|
|||||||
*/
|
*/
|
||||||
private BigDecimal discountAmount;
|
private BigDecimal discountAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠金额(元,用户输入)
|
||||||
|
*/
|
||||||
|
private BigDecimal discountMoney;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 实收金额
|
* 实收金额
|
||||||
* 计算公式: totalAmount - discountAmount
|
* 计算公式: totalAmount - discountAmount
|
||||||
@@ -64,12 +70,12 @@ public class Order {
|
|||||||
private BigDecimal actualAmount;
|
private BigDecimal actualAmount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 折扣率(百分比)
|
* 折扣率(百分比,保留此逻辑)
|
||||||
*/
|
*/
|
||||||
private BigDecimal discountRate;
|
private BigDecimal discountRate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态: 1已完成 2已取消 3退款中 4已退款
|
* 状态: 0未完成 1已完成 2已取消 3退款中 4已退款
|
||||||
*/
|
*/
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
@@ -98,4 +104,16 @@ public class Order {
|
|||||||
|
|
||||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除标记: 0未删除 1已删除
|
||||||
|
*/
|
||||||
|
@TableField("deleted")
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单明细(不存数据库,仅用于API返回)
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private List<OrderItem> items;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ public class OrderItem {
|
|||||||
*/
|
*/
|
||||||
private String productId;
|
private String productId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分类ID(冗余)
|
||||||
|
*/
|
||||||
|
private String categoryId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 商品名称(冗余)
|
* 商品名称(冗余)
|
||||||
*/
|
*/
|
||||||
@@ -41,11 +46,36 @@ public class OrderItem {
|
|||||||
*/
|
*/
|
||||||
private String unit;
|
private String unit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成本价(冗余)
|
||||||
|
*/
|
||||||
|
private BigDecimal costPrice;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 销售单价(当时标价)
|
* 销售单价(当时标价)
|
||||||
*/
|
*/
|
||||||
private BigDecimal price;
|
private BigDecimal price;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品图片URL(冗余)
|
||||||
|
*/
|
||||||
|
private String imageUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品条码(冗余)
|
||||||
|
*/
|
||||||
|
private String barcode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 库存预警阈值(冗余)
|
||||||
|
*/
|
||||||
|
private Integer stockAlert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品描述(冗余)
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数量
|
* 数量
|
||||||
*/
|
*/
|
||||||
@@ -56,6 +86,21 @@ public class OrderItem {
|
|||||||
*/
|
*/
|
||||||
private BigDecimal subtotal;
|
private BigDecimal subtotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 长度(cm)
|
||||||
|
*/
|
||||||
|
private BigDecimal length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 宽度(cm)
|
||||||
|
*/
|
||||||
|
private BigDecimal width;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 面积(m²)
|
||||||
|
*/
|
||||||
|
private BigDecimal area;
|
||||||
|
|
||||||
@TableField(fill = FieldFill.INSERT)
|
@TableField(fill = FieldFill.INSERT)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import lombok.Data;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 商品实体类
|
* 商品实体类
|
||||||
@@ -41,6 +43,21 @@ public class Product {
|
|||||||
*/
|
*/
|
||||||
private BigDecimal price;
|
private BigDecimal price;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 长度(mm)
|
||||||
|
*/
|
||||||
|
private BigDecimal length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 宽度(mm)
|
||||||
|
*/
|
||||||
|
private BigDecimal width;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 面积(m²)
|
||||||
|
*/
|
||||||
|
private BigDecimal area;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 成本价
|
* 成本价
|
||||||
*/
|
*/
|
||||||
@@ -71,6 +88,9 @@ public class Product {
|
|||||||
*/
|
*/
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private List<Map<String, Object>> attributes;
|
||||||
|
|
||||||
@TableField(fill = FieldFill.INSERT)
|
@TableField(fill = FieldFill.INSERT)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
|||||||
38
src/main/java/com/example/building/entity/SystemConfig.java
Normal file
38
src/main/java/com/example/building/entity/SystemConfig.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package com.example.building.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统配置实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("system_config")
|
||||||
|
public class SystemConfig {
|
||||||
|
|
||||||
|
@TableId(type = IdType.ASSIGN_UUID)
|
||||||
|
private String configId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置键
|
||||||
|
*/
|
||||||
|
private String configKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置值
|
||||||
|
*/
|
||||||
|
private String configValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.example.building.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.example.building.entity.SystemConfig;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface SystemConfigMapper extends BaseMapper<SystemConfig> {
|
||||||
|
}
|
||||||
@@ -19,8 +19,11 @@ public interface AuthService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 微信扫码登录
|
* 微信扫码登录
|
||||||
|
* @param code 微信授权码
|
||||||
|
* @param nickname 微信昵称
|
||||||
|
* @param avatar 微信头像
|
||||||
*/
|
*/
|
||||||
Map<String, Object> wechatLogin(String code);
|
Map<String, Object> wechatLogin(String code, String nickname, String avatar);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付宝扫码登录
|
* 支付宝扫码登录
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public interface CustomerService {
|
|||||||
/**
|
/**
|
||||||
* 客户列表
|
* 客户列表
|
||||||
*/
|
*/
|
||||||
Page<Customer> getCustomers(String keyword, Integer page, Integer pageSize);
|
Page<Customer> getCustomers(String keyword, String type, Integer page, Integer pageSize);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户详情
|
* 客户详情
|
||||||
|
|||||||
@@ -24,12 +24,13 @@ public interface OrderService {
|
|||||||
/**
|
/**
|
||||||
* 获取订单列表
|
* 获取订单列表
|
||||||
*/
|
*/
|
||||||
Page<Order> getOrders(String customerId, Integer status, String startDate, String endDate, Integer page, Integer pageSize);
|
Page<Order> getOrders(String customerId, String customerName, Integer status, String startDate, String endDate, Integer page, Integer pageSize);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取订单详情(含明细)
|
* 获取订单详情(含明细)
|
||||||
|
* 顾客只能查看自己的订单
|
||||||
*/
|
*/
|
||||||
Map<String, Object> getOrderDetail(String orderId);
|
Map<String, Object> getOrderDetail(String orderId, String userId, String role);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取消订单
|
* 取消订单
|
||||||
@@ -41,6 +42,16 @@ public interface OrderService {
|
|||||||
*/
|
*/
|
||||||
void refundOrder(String orderId, String operatorId);
|
void refundOrder(String orderId, String operatorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新订单状态
|
||||||
|
*/
|
||||||
|
void updateOrderStatus(String orderId, Integer status, String operatorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新订单(编辑)
|
||||||
|
*/
|
||||||
|
Order updateOrder(String orderId, CreateOrderRequest request, String operatorId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单统计
|
* 订单统计
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ public interface ProductService {
|
|||||||
*/
|
*/
|
||||||
Page<Product> getProducts(String categoryId, String keyword, Integer page, Integer pageSize);
|
Page<Product> getProducts(String categoryId, String keyword, Integer page, Integer pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有商品(包括下架的)
|
||||||
|
*/
|
||||||
|
Page<Product> getAllProducts(String categoryId, String keyword, Integer page, Integer pageSize);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取商品详情
|
* 获取商品详情
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.example.building.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.example.building.entity.SystemConfig;
|
||||||
|
import com.example.building.mapper.SystemConfigMapper;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SystemConfigService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemConfigMapper systemConfigMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取顾客订单可见天数配置
|
||||||
|
* 默认180天(半年)
|
||||||
|
*/
|
||||||
|
public int getCustomerOrderVisibleDays() {
|
||||||
|
LambdaQueryWrapper<SystemConfig> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(SystemConfig::getConfigKey, "customer_order_visible_days");
|
||||||
|
SystemConfig config = systemConfigMapper.selectOne(wrapper);
|
||||||
|
|
||||||
|
if (config != null && config.getConfigValue() != null) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(config.getConfigValue());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return 180;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 180; // 默认半年
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取顾客可见订单开始日期
|
||||||
|
*/
|
||||||
|
public LocalDate getCustomerOrderStartDate() {
|
||||||
|
int days = getCustomerOrderVisibleDays();
|
||||||
|
return LocalDate.now().minusDays(days);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
package com.example.building.service.impl;
|
package com.example.building.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.example.building.common.JwtUtil;
|
import com.example.building.common.JwtUtil;
|
||||||
|
import com.example.building.entity.Customer;
|
||||||
import com.example.building.entity.User;
|
import com.example.building.entity.User;
|
||||||
|
import com.example.building.mapper.CustomerMapper;
|
||||||
import com.example.building.mapper.UserMapper;
|
import com.example.building.mapper.UserMapper;
|
||||||
import com.example.building.service.AuthService;
|
import com.example.building.service.AuthService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -10,6 +13,7 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -25,6 +29,9 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserMapper userMapper;
|
private UserMapper userMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CustomerMapper customerMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private JwtUtil jwtUtil;
|
private JwtUtil jwtUtil;
|
||||||
|
|
||||||
@@ -81,25 +88,49 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
* 实际生产中需要调用微信API获取openid
|
* 实际生产中需要调用微信API获取openid
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> wechatLogin(String code) {
|
public Map<String, Object> wechatLogin(String code, String nickname, String avatar) {
|
||||||
// TODO: 调用微信API获取openid
|
// TODO: 调用微信API获取openid
|
||||||
// String openid = wechatService.getOpenId(code);
|
// String openid = wechatService.getOpenId(code);
|
||||||
String openid = "wechat_" + code;
|
String openid = "wechat_" + code;
|
||||||
|
|
||||||
// 查询用户,不存在则创建
|
// 查询或创建顾客
|
||||||
User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
|
Customer customer = customerMapper.selectOne(new LambdaQueryWrapper<Customer>()
|
||||||
.eq(User::getWechatOpenid, openid));
|
.eq(Customer::getWechatOpenid, openid));
|
||||||
if (user == null) {
|
|
||||||
user = new User();
|
if (customer == null) {
|
||||||
user.setUserId(UUID.randomUUID().toString());
|
// 新顾客
|
||||||
user.setWechatOpenid(openid);
|
customer = new Customer();
|
||||||
user.setUsername("微信用户");
|
customer.setCustomerId(UUID.randomUUID().toString());
|
||||||
user.setRole("sales");
|
customer.setWechatOpenid(openid);
|
||||||
user.setStatus(1);
|
customer.setName(nickname != null ? nickname : "微信用户");
|
||||||
userMapper.insert(user);
|
customer.setWechatNickname(nickname);
|
||||||
|
customer.setWechatAvatar(avatar);
|
||||||
|
customer.setTotalAmount(new java.math.BigDecimal("0"));
|
||||||
|
customer.setLastLoginAt(LocalDateTime.now());
|
||||||
|
customerMapper.insert(customer);
|
||||||
|
} else {
|
||||||
|
// 更新已有顾客信息
|
||||||
|
LambdaUpdateWrapper<Customer> updateWrapper = new LambdaUpdateWrapper<>();
|
||||||
|
updateWrapper.eq(Customer::getCustomerId, customer.getCustomerId());
|
||||||
|
if (nickname != null) {
|
||||||
|
updateWrapper.set(Customer::getWechatNickname, nickname);
|
||||||
|
}
|
||||||
|
if (avatar != null) {
|
||||||
|
updateWrapper.set(Customer::getWechatAvatar, avatar);
|
||||||
|
}
|
||||||
|
updateWrapper.set(Customer::getLastLoginAt, LocalDateTime.now());
|
||||||
|
customerMapper.update(null, updateWrapper);
|
||||||
|
customer.setLastLoginAt(LocalDateTime.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
return generateTokens(user);
|
// 返回顾客信息
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("token", "customer_token_" + customer.getCustomerId());
|
||||||
|
result.put("customerId", customer.getCustomerId());
|
||||||
|
result.put("name", customer.getName());
|
||||||
|
result.put("role", "customer");
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public class CustomerServiceImpl implements CustomerService {
|
|||||||
* 客户列表
|
* 客户列表
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Page<Customer> getCustomers(String keyword, Integer page, Integer pageSize) {
|
public Page<Customer> getCustomers(String keyword, String type, Integer page, Integer pageSize) {
|
||||||
Page<Customer> pageParam = new Page<>(page, pageSize);
|
Page<Customer> pageParam = new Page<>(page, pageSize);
|
||||||
LambdaQueryWrapper<Customer> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<Customer> wrapper = new LambdaQueryWrapper<>();
|
||||||
if (StringUtils.hasText(keyword)) {
|
if (StringUtils.hasText(keyword)) {
|
||||||
@@ -33,6 +33,10 @@ public class CustomerServiceImpl implements CustomerService {
|
|||||||
.or()
|
.or()
|
||||||
.like(Customer::getPhone, keyword);
|
.like(Customer::getPhone, keyword);
|
||||||
}
|
}
|
||||||
|
if (StringUtils.hasText(type)) {
|
||||||
|
wrapper.eq(Customer::getType, type);
|
||||||
|
}
|
||||||
|
wrapper.orderByDesc(Customer::getLastLoginAt);
|
||||||
wrapper.orderByDesc(Customer::getCreatedAt);
|
wrapper.orderByDesc(Customer::getCreatedAt);
|
||||||
return customerMapper.selectPage(pageParam, wrapper);
|
return customerMapper.selectPage(pageParam, wrapper);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
order.setDiscountRate(request.getDiscountRate() != null ? request.getDiscountRate() : new BigDecimal("100"));
|
order.setDiscountRate(request.getDiscountRate() != null ? request.getDiscountRate() : new BigDecimal("100"));
|
||||||
order.setRemark(request.getRemark());
|
order.setRemark(request.getRemark());
|
||||||
order.setPaymentMethod(request.getPaymentMethod());
|
order.setPaymentMethod(request.getPaymentMethod());
|
||||||
order.setStatus(1); // 已完成
|
order.setStatus(0); // 未完成
|
||||||
|
order.setDeleted(0); // 未删除
|
||||||
|
|
||||||
// 2. 查询客户信息(如果指定了客户)
|
// 2. 查询客户信息(如果指定了客户)
|
||||||
if (request.getCustomerId() != null) {
|
if (request.getCustomerId() != null) {
|
||||||
@@ -101,15 +102,24 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
item.setItemId(UUID.randomUUID().toString());
|
item.setItemId(UUID.randomUUID().toString());
|
||||||
item.setOrderId(order.getOrderId());
|
item.setOrderId(order.getOrderId());
|
||||||
item.setProductId(product.getProductId());
|
item.setProductId(product.getProductId());
|
||||||
|
item.setCategoryId(product.getCategoryId());
|
||||||
item.setProductName(product.getName());
|
item.setProductName(product.getName());
|
||||||
item.setProductSpec(product.getSpec());
|
item.setProductSpec(product.getSpec());
|
||||||
item.setUnit(product.getUnit());
|
item.setUnit(product.getUnit());
|
||||||
|
item.setCostPrice(product.getCostPrice());
|
||||||
|
item.setImageUrl(product.getImageUrl());
|
||||||
|
item.setBarcode(product.getBarcode());
|
||||||
|
item.setStockAlert(product.getStockAlert());
|
||||||
|
item.setDescription(product.getDescription());
|
||||||
item.setPrice(price);
|
item.setPrice(price);
|
||||||
item.setQuantity(itemDTO.getQuantity());
|
item.setQuantity(itemDTO.getQuantity());
|
||||||
|
item.setLength(itemDTO.getLength());
|
||||||
|
item.setWidth(itemDTO.getWidth());
|
||||||
|
item.setArea(itemDTO.getArea());
|
||||||
item.setSubtotal(subtotal);
|
item.setSubtotal(subtotal);
|
||||||
orderItems.add(item);
|
orderItems.add(item);
|
||||||
|
|
||||||
// 4. 扣减库存
|
// 4. 创建订单时立即扣减库存
|
||||||
decreaseStock(product.getProductId(), itemDTO.getQuantity(), order.getOrderId(), operatorId);
|
decreaseStock(product.getProductId(), itemDTO.getQuantity(), order.getOrderId(), operatorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,9 +127,21 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
order.setTotalAmount(totalAmount);
|
order.setTotalAmount(totalAmount);
|
||||||
|
|
||||||
// 5. 计算优惠金额和实付金额
|
// 5. 计算优惠金额和实付金额
|
||||||
|
BigDecimal discountAmount;
|
||||||
BigDecimal discountRate = order.getDiscountRate();
|
BigDecimal discountRate = order.getDiscountRate();
|
||||||
BigDecimal discountAmount = totalAmount.multiply(new BigDecimal("100").subtract(discountRate))
|
BigDecimal discountMoney = request.getDiscountMoney();
|
||||||
.divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
|
|
||||||
|
// 如果优惠金额大于0,直接使用;否则用折扣率计算
|
||||||
|
if (discountMoney != null && discountMoney.compareTo(BigDecimal.ZERO) >= 0) {
|
||||||
|
discountAmount = discountMoney;
|
||||||
|
} else {
|
||||||
|
// 折扣率默认100,即不打折
|
||||||
|
if (discountRate == null) {
|
||||||
|
discountRate = new BigDecimal("100");
|
||||||
|
}
|
||||||
|
discountAmount = totalAmount.multiply(new BigDecimal("100").subtract(discountRate))
|
||||||
|
.divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
BigDecimal actualAmount = totalAmount.subtract(discountAmount);
|
BigDecimal actualAmount = totalAmount.subtract(discountAmount);
|
||||||
|
|
||||||
order.setDiscountAmount(discountAmount);
|
order.setDiscountAmount(discountAmount);
|
||||||
@@ -150,12 +172,15 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
* 获取订单列表
|
* 获取订单列表
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Page<Order> getOrders(String customerId, Integer status, String startDate, String endDate, Integer page, Integer pageSize) {
|
public Page<Order> getOrders(String customerId, String customerName, Integer status, String startDate, String endDate, Integer page, Integer pageSize) {
|
||||||
Page<Order> pageParam = new Page<>(page, pageSize);
|
Page<Order> pageParam = new Page<>(page, pageSize);
|
||||||
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
|
||||||
if (customerId != null) {
|
if (customerId != null) {
|
||||||
wrapper.eq(Order::getCustomerId, customerId);
|
wrapper.eq(Order::getCustomerId, customerId);
|
||||||
}
|
}
|
||||||
|
if (customerName != null && !customerName.isEmpty()) {
|
||||||
|
wrapper.like(Order::getCustomerName, customerName);
|
||||||
|
}
|
||||||
if (status != null) {
|
if (status != null) {
|
||||||
wrapper.eq(Order::getStatus, status);
|
wrapper.eq(Order::getStatus, status);
|
||||||
}
|
}
|
||||||
@@ -166,19 +191,45 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
wrapper.le(Order::getCreatedAt, endDate);
|
wrapper.le(Order::getCreatedAt, endDate);
|
||||||
}
|
}
|
||||||
wrapper.orderByDesc(Order::getCreatedAt);
|
wrapper.orderByDesc(Order::getCreatedAt);
|
||||||
return orderMapper.selectPage(pageParam, wrapper);
|
Page<Order> result = orderMapper.selectPage(pageParam, wrapper);
|
||||||
|
|
||||||
|
// 查询每个订单的明细
|
||||||
|
if (result.getRecords() != null && !result.getRecords().isEmpty()) {
|
||||||
|
for (Order order : result.getRecords()) {
|
||||||
|
try {
|
||||||
|
if (order.getOrderId() != null) {
|
||||||
|
List<OrderItem> items = orderItemMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<OrderItem>().eq(OrderItem::getOrderId, order.getOrderId()));
|
||||||
|
order.setItems(items);
|
||||||
|
} else {
|
||||||
|
order.setItems(new ArrayList<>());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 查询明细失败时设置空列表
|
||||||
|
order.setItems(new ArrayList<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取订单详情(含明细)
|
* 获取订单详情(含明细)
|
||||||
|
* 顾客只能查看自己的订单
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getOrderDetail(String orderId) {
|
public Map<String, Object> getOrderDetail(String orderId, String userId, String role) {
|
||||||
Order order = orderMapper.selectById(orderId);
|
Order order = orderMapper.selectById(orderId);
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
throw new RuntimeException("订单不存在");
|
throw new RuntimeException("订单不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 顾客只能查看自己的订单
|
||||||
|
if ("customer".equals(role) && !userId.equals(order.getCustomerId())) {
|
||||||
|
throw new RuntimeException("无权查看该订单");
|
||||||
|
}
|
||||||
|
|
||||||
List<OrderItem> items = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
|
List<OrderItem> items = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
|
||||||
.eq(OrderItem::getOrderId, orderId));
|
.eq(OrderItem::getOrderId, orderId));
|
||||||
|
|
||||||
@@ -198,11 +249,11 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
if (order == null) {
|
if (order == null) {
|
||||||
throw new RuntimeException("订单不存在");
|
throw new RuntimeException("订单不存在");
|
||||||
}
|
}
|
||||||
if (order.getStatus() != 1) {
|
if (order.getStatus() != 0) {
|
||||||
throw new RuntimeException("订单状态不允许取消");
|
throw new RuntimeException("订单状态不允许取消");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复库存
|
// 取消订单需要恢复库存(因为创建时已扣减库存)
|
||||||
List<OrderItem> items = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
|
List<OrderItem> items = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
|
||||||
.eq(OrderItem::getOrderId, orderId));
|
.eq(OrderItem::getOrderId, orderId));
|
||||||
for (OrderItem item : items) {
|
for (OrderItem item : items) {
|
||||||
@@ -240,6 +291,155 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
orderMapper.updateById(order);
|
orderMapper.updateById(order);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新订单状态(确认完成/取消)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void updateOrderStatus(String orderId, Integer status, String operatorId) {
|
||||||
|
Order order = orderMapper.selectById(orderId);
|
||||||
|
if (order == null) {
|
||||||
|
throw new RuntimeException("订单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有未完成状态可以操作
|
||||||
|
if (order.getStatus() == null || order.getStatus() != 0) {
|
||||||
|
throw new RuntimeException("订单状态不允许操作");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == 1) {
|
||||||
|
// 确认完成:订单在创建时已扣减库存,此处不再扣减
|
||||||
|
// 只需更新状态为已完成
|
||||||
|
}
|
||||||
|
// 取消订单需要恢复库存(因为创建时已扣减)
|
||||||
|
if (status == 2) {
|
||||||
|
// 取消订单需要恢复库存(因为创建时已扣减)
|
||||||
|
List<OrderItem> items = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
|
||||||
|
.eq(OrderItem::getOrderId, orderId));
|
||||||
|
for (OrderItem item : items) {
|
||||||
|
increaseStock(item.getProductId(), item.getQuantity(), orderId, operatorId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
order.setStatus(status);
|
||||||
|
orderMapper.updateById(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新订单(编辑)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Order updateOrder(String orderId, CreateOrderRequest request, String operatorId) {
|
||||||
|
Order order = orderMapper.selectById(orderId);
|
||||||
|
if (order == null) {
|
||||||
|
throw new RuntimeException("订单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有未完成状态可以编辑
|
||||||
|
if (order.getStatus() == null || order.getStatus() != 0) {
|
||||||
|
throw new RuntimeException("订单状态不允许编辑");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新客户信息
|
||||||
|
if (request.getCustomerId() != null) {
|
||||||
|
Customer customer = customerMapper.selectById(request.getCustomerId());
|
||||||
|
if (customer != null) {
|
||||||
|
order.setCustomerId(customer.getCustomerId());
|
||||||
|
order.setCustomerName(customer.getName());
|
||||||
|
order.setCustomerPhone(customer.getPhone());
|
||||||
|
order.setCustomerWechat(customer.getWechatOpenid());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
order.setCustomerId(null);
|
||||||
|
order.setCustomerName(null);
|
||||||
|
order.setCustomerPhone(null);
|
||||||
|
order.setCustomerWechat(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新折扣和备注
|
||||||
|
order.setDiscountRate(request.getDiscountRate() != null ? request.getDiscountRate() : new BigDecimal("100"));
|
||||||
|
order.setDiscountMoney(request.getDiscountMoney() != null ? request.getDiscountMoney() : BigDecimal.ZERO);
|
||||||
|
order.setRemark(request.getRemark());
|
||||||
|
order.setPaymentMethod(request.getPaymentMethod());
|
||||||
|
|
||||||
|
// 重新计算金额
|
||||||
|
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
// 删除旧的订单明细前,先恢复库存
|
||||||
|
List<OrderItem> oldItems = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
|
||||||
|
.eq(OrderItem::getOrderId, orderId));
|
||||||
|
for (OrderItem oldItem : oldItems) {
|
||||||
|
increaseStock(oldItem.getProductId(), oldItem.getQuantity(), orderId, operatorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除旧的订单明细
|
||||||
|
orderItemMapper.delete(new LambdaQueryWrapper<OrderItem>().eq(OrderItem::getOrderId, orderId));
|
||||||
|
|
||||||
|
// 重新创建订单明细,并扣减库存
|
||||||
|
List<OrderItem> orderItems = new ArrayList<>();
|
||||||
|
for (CreateOrderRequest.OrderItemDTO itemDTO : request.getItems()) {
|
||||||
|
Product product = productMapper.selectById(itemDTO.getProductId());
|
||||||
|
if (product == null) {
|
||||||
|
throw new RuntimeException("商品不存在: " + itemDTO.getProductId());
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal price = itemDTO.getPrice() != null ? itemDTO.getPrice() : product.getPrice();
|
||||||
|
BigDecimal subtotal = price.multiply(new BigDecimal(itemDTO.getQuantity()))
|
||||||
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
totalAmount = totalAmount.add(subtotal);
|
||||||
|
|
||||||
|
OrderItem item = new OrderItem();
|
||||||
|
item.setItemId(UUID.randomUUID().toString());
|
||||||
|
item.setOrderId(orderId);
|
||||||
|
item.setProductId(product.getProductId());
|
||||||
|
item.setCategoryId(product.getCategoryId());
|
||||||
|
item.setProductName(product.getName());
|
||||||
|
item.setProductSpec(product.getSpec());
|
||||||
|
item.setUnit(product.getUnit());
|
||||||
|
item.setCostPrice(product.getCostPrice());
|
||||||
|
item.setImageUrl(product.getImageUrl());
|
||||||
|
item.setBarcode(product.getBarcode());
|
||||||
|
item.setStockAlert(product.getStockAlert());
|
||||||
|
item.setDescription(product.getDescription());
|
||||||
|
item.setPrice(price);
|
||||||
|
item.setQuantity(itemDTO.getQuantity());
|
||||||
|
item.setLength(itemDTO.getLength());
|
||||||
|
item.setWidth(itemDTO.getWidth());
|
||||||
|
item.setArea(itemDTO.getArea());
|
||||||
|
item.setSubtotal(subtotal);
|
||||||
|
orderItems.add(item);
|
||||||
|
orderItemMapper.insert(item);
|
||||||
|
// 扣减库存
|
||||||
|
decreaseStock(product.getProductId(), itemDTO.getQuantity(), orderId, operatorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
order.setTotalAmount(totalAmount);
|
||||||
|
|
||||||
|
BigDecimal discountAmount;
|
||||||
|
BigDecimal discountRate = order.getDiscountRate();
|
||||||
|
BigDecimal discountMoney = order.getDiscountMoney();
|
||||||
|
|
||||||
|
// 如果优惠金额大于等于0,直接使用;否则用折扣率计算
|
||||||
|
if (discountMoney != null && discountMoney.compareTo(BigDecimal.ZERO) >= 0) {
|
||||||
|
discountAmount = discountMoney;
|
||||||
|
} else {
|
||||||
|
if (discountRate == null) {
|
||||||
|
discountRate = new BigDecimal("100");
|
||||||
|
}
|
||||||
|
discountAmount = totalAmount.multiply(new BigDecimal("100").subtract(discountRate))
|
||||||
|
.divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
BigDecimal actualAmount = totalAmount.subtract(discountAmount);
|
||||||
|
|
||||||
|
order.setDiscountAmount(discountAmount);
|
||||||
|
order.setActualAmount(actualAmount);
|
||||||
|
|
||||||
|
orderMapper.updateById(order);
|
||||||
|
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单统计
|
* 订单统计
|
||||||
*/
|
*/
|
||||||
@@ -247,11 +447,13 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
public Map<String, Object> getStatistics(String startDate, String endDate) {
|
public Map<String, Object> getStatistics(String startDate, String endDate) {
|
||||||
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(Order::getStatus, 1);
|
wrapper.eq(Order::getStatus, 1);
|
||||||
if (startDate != null) {
|
if (startDate != null && !startDate.isEmpty()) {
|
||||||
wrapper.ge(Order::getCreatedAt, startDate);
|
LocalDateTime start = LocalDate.parse(startDate, DateTimeFormatter.ISO_LOCAL_DATE).atStartOfDay();
|
||||||
|
wrapper.ge(Order::getCreatedAt, start);
|
||||||
}
|
}
|
||||||
if (endDate != null) {
|
if (endDate != null && !endDate.isEmpty()) {
|
||||||
wrapper.le(Order::getCreatedAt, endDate);
|
LocalDateTime end = LocalDate.parse(endDate, DateTimeFormatter.ISO_LOCAL_DATE).atTime(23, 59, 59);
|
||||||
|
wrapper.le(Order::getCreatedAt, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Order> orders = orderMapper.selectList(wrapper);
|
List<Order> orders = orderMapper.selectList(wrapper);
|
||||||
|
|||||||
@@ -68,9 +68,16 @@ public class ProductServiceImpl implements ProductService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除分类
|
* 删除分类
|
||||||
|
* 检查是否有商品使用该分类,存在则不允许删除
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void deleteCategory(String id) {
|
public void deleteCategory(String id) {
|
||||||
|
// 检查是否有商品使用该分类
|
||||||
|
Long count = productMapper.selectCount(new LambdaQueryWrapper<Product>()
|
||||||
|
.eq(Product::getCategoryId, id));
|
||||||
|
if (count > 0) {
|
||||||
|
throw new RuntimeException("该分类下有商品,无法删除");
|
||||||
|
}
|
||||||
categoryMapper.deleteById(id);
|
categoryMapper.deleteById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +99,20 @@ public class ProductServiceImpl implements ProductService {
|
|||||||
return productMapper.selectPage(pageParam, wrapper);
|
return productMapper.selectPage(pageParam, wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<Product> getAllProducts(String categoryId, String keyword, Integer page, Integer pageSize) {
|
||||||
|
Page<Product> pageParam = new Page<>(page, pageSize);
|
||||||
|
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
if (StringUtils.hasText(categoryId)) {
|
||||||
|
wrapper.eq(Product::getCategoryId, categoryId);
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(keyword)) {
|
||||||
|
wrapper.like(Product::getName, keyword);
|
||||||
|
}
|
||||||
|
wrapper.orderByDesc(Product::getCreatedAt);
|
||||||
|
return productMapper.selectPage(pageParam, wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取商品详情
|
* 获取商品详情
|
||||||
*/
|
*/
|
||||||
@@ -112,6 +133,15 @@ public class ProductServiceImpl implements ProductService {
|
|||||||
product.setProductId(UUID.randomUUID().toString());
|
product.setProductId(UUID.randomUUID().toString());
|
||||||
product.setStatus(1);
|
product.setStatus(1);
|
||||||
productMapper.insert(product);
|
productMapper.insert(product);
|
||||||
|
|
||||||
|
// 创建商品时自动创建库存记录
|
||||||
|
Stock stock = new Stock();
|
||||||
|
stock.setStockId(UUID.randomUUID().toString());
|
||||||
|
stock.setProductId(product.getProductId());
|
||||||
|
stock.setQuantity(0);
|
||||||
|
stock.setLockedQuantity(0);
|
||||||
|
stockMapper.insert(stock);
|
||||||
|
|
||||||
return product;
|
return product;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,14 +160,11 @@ public class ProductServiceImpl implements ProductService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除商品(软删)
|
* 删除商品(硬删除)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void deleteProduct(String id) {
|
public void deleteProduct(String id) {
|
||||||
Product product = new Product();
|
productMapper.deleteById(id);
|
||||||
product.setProductId(id);
|
|
||||||
product.setStatus(0);
|
|
||||||
productMapper.updateById(product);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,15 +8,24 @@ spring:
|
|||||||
# PostgreSQL数据库配置
|
# PostgreSQL数据库配置
|
||||||
datasource:
|
datasource:
|
||||||
driver-class-name: org.postgresql.Driver
|
driver-class-name: org.postgresql.Driver
|
||||||
url: jdbc:postgresql://localhost:5432/building_materials
|
url: jdbc:postgresql://${SPRING_DATASOURCE_HOST:localhost}:${SPRING_DATASOURCE_PORT:5432}/${SPRING_DATASOURCE_DB:building_materials}
|
||||||
username: postgres
|
username: ${SPRING_DATASOURCE_USERNAME:postgres}
|
||||||
password: postgres
|
password: ${SPRING_DATASOURCE_PASSWORD:postgres}
|
||||||
|
hikari:
|
||||||
|
initialization-fail-timeout: 60000
|
||||||
|
|
||||||
# Redis配置
|
# Flyway 数据库版本管理
|
||||||
|
flyway:
|
||||||
|
enabled: true
|
||||||
|
baseline-on-migrate: true
|
||||||
|
locations: classpath:db/migration
|
||||||
|
baseline-version: 0
|
||||||
|
|
||||||
|
# Redis配置 - 环境变量注入
|
||||||
redis:
|
redis:
|
||||||
host: localhost
|
host: ${SPRING_REDIS_HOST:localhost}
|
||||||
port: 6379
|
port: ${SPRING_REDIS_PORT:6379}
|
||||||
password:
|
password: ${SPRING_REDIS_PASSWORD:}
|
||||||
database: 0
|
database: 0
|
||||||
timeout: 3000ms
|
timeout: 3000ms
|
||||||
lettuce:
|
lettuce:
|
||||||
@@ -34,15 +43,17 @@ mybatis-plus:
|
|||||||
global-config:
|
global-config:
|
||||||
db-config:
|
db-config:
|
||||||
id-type: assign_uuid
|
id-type: assign_uuid
|
||||||
logic-delete-field: status
|
logic-delete-field: deleted
|
||||||
logic-delete-value: 0
|
logic-delete-value: 1
|
||||||
logic-not-delete-value: 1
|
logic-not-delete-value: 0
|
||||||
|
mapper-locations: classpath*:/mapper/**/*.xml
|
||||||
|
type-aliases-package: com.example.building.entity
|
||||||
|
|
||||||
# JWT配置
|
# JWT配置 - 环境变量注入
|
||||||
jwt:
|
jwt:
|
||||||
secret: building-materials-secret-key-2024
|
secret: ${JWT_SECRET:building-materials-secret-key-2024}
|
||||||
expiration: 7200000 # 2小时
|
expiration: ${JWT_EXPIRATION:7200000}
|
||||||
refresh-expiration: 604800000 # 7天
|
refresh-expiration: ${JWT_REFRESH_EXPIRATION:604800000}
|
||||||
|
|
||||||
# 日志配置
|
# 日志配置
|
||||||
logging:
|
logging:
|
||||||
|
|||||||
72
src/main/resources/db/migration/R1__data.sql
Normal file
72
src/main/resources/db/migration/R1__data.sql
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
-- 测试数据
|
||||||
|
-- 先删除已有数据
|
||||||
|
DELETE FROM stock_flow;
|
||||||
|
DELETE FROM order_items;
|
||||||
|
DELETE FROM orders;
|
||||||
|
DELETE FROM stock;
|
||||||
|
DELETE FROM products;
|
||||||
|
DELETE FROM categories;
|
||||||
|
DELETE FROM customers;
|
||||||
|
DELETE FROM users;
|
||||||
|
DELETE FROM system_config;
|
||||||
|
|
||||||
|
-- 用户表 - 管理员和销售
|
||||||
|
INSERT INTO users (user_id, username, phone, password, role, status) VALUES
|
||||||
|
('u-admin-001', 'admin', '13800000001', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5EH', 'admin', 1),
|
||||||
|
('u-sales-001', '张三', '13800000002', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5EH', 'sales', 1),
|
||||||
|
('u-sales-002', '李四', '13800000003', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5EH', 'sales', 1);
|
||||||
|
|
||||||
|
-- 客户表
|
||||||
|
INSERT INTO customers (customer_id, name, phone, address, remark, total_amount, created_by) VALUES
|
||||||
|
('c-001', '王五', '13900000001', '北京市朝阳区XX街道', 'VIP客户', 50000.00, 'u-admin-001'),
|
||||||
|
('c-002', '赵六', '13900000002', '上海市浦东新区XX路', '普通客户', 15000.00, 'u-sales-001'),
|
||||||
|
('c-003', '钱七', '13900000003', '广州市天河区XX大道', '新客户', 5000.00, 'u-sales-001'),
|
||||||
|
('c-004', '孙八', '13900000004', '深圳市南山区XX园', '潜在客户', 0.00, 'u-sales-002');
|
||||||
|
|
||||||
|
-- 商品分类表
|
||||||
|
INSERT INTO categories (category_id, name, parent_id, sort_order, status) VALUES
|
||||||
|
('cat-001', '钢材', NULL, 1, 1),
|
||||||
|
('cat-002', '水泥', NULL, 2, 1),
|
||||||
|
('cat-003', '木材', NULL, 3, 1),
|
||||||
|
('cat-004', '管材', NULL, 4, 1),
|
||||||
|
('cat-005', '螺纹钢', 'cat-001', 1, 1),
|
||||||
|
('cat-006', '型材', 'cat-001', 2, 1),
|
||||||
|
('cat-007', 'PC32.5', 'cat-002', 1, 1),
|
||||||
|
('cat-008', 'PC42.5', 'cat-002', 2, 1);
|
||||||
|
|
||||||
|
-- 商品表
|
||||||
|
INSERT INTO products (product_id, category_id, name, spec, unit, price, cost_price, stock_alert, status) VALUES
|
||||||
|
('p-001', 'cat-005', '螺纹钢 Φ12', '12mm', '吨', 4200.00, 3800.00, 10, 1),
|
||||||
|
('p-002', 'cat-005', '螺纹钢 Φ16', '16mm', '吨', 4100.00, 3700.00, 10, 1),
|
||||||
|
('p-003', 'cat-005', '螺纹钢 Φ20', '20mm', '吨', 4050.00, 3650.00, 10, 1),
|
||||||
|
('p-004', 'cat-006', 'H型钢 H200*200*8*12', '200*200', '吨', 3800.00, 3400.00, 5, 1),
|
||||||
|
('p-005', 'cat-007', '水泥 PC32.5', '32.5级', '吨', 450.00, 380.00, 50, 1),
|
||||||
|
('p-006', 'cat-008', '水泥 PC42.5', '42.5级', '吨', 520.00, 450.00, 50, 1),
|
||||||
|
('p-007', 'cat-003', '木材 杉木方', '4*9cm', '立方米', 1200.00, 900.00, 20, 1),
|
||||||
|
('p-008', 'cat-003', '木材 松木板', '18mm', '平方米', 85.00, 60.00, 100, 1),
|
||||||
|
('p-009', 'cat-004', 'PVC管 Φ50', '50mm', '米', 12.00, 8.00, 200, 1),
|
||||||
|
('p-010', 'cat-004', 'PVC管 Φ100', '100mm', '米', 28.00, 20.00, 100, 1),
|
||||||
|
('p-011', 'cat-004', 'PPR管 Φ25', '25mm', '米', 8.00, 5.50, 300, 1),
|
||||||
|
('p-012', 'cat-004', '镀锌管 Φ32', '32mm', '米', 22.00, 16.00, 150, 1);
|
||||||
|
|
||||||
|
-- 库存表
|
||||||
|
INSERT INTO stock (stock_id, product_id, quantity, locked_quantity) VALUES
|
||||||
|
('s-001', 'p-001', 100, 0),
|
||||||
|
('s-002', 'p-002', 80, 0),
|
||||||
|
('s-003', 'p-003', 60, 0),
|
||||||
|
('s-004', 'p-004', 25, 0),
|
||||||
|
('s-005', 'p-005', 200, 0),
|
||||||
|
('s-006', 'p-006', 150, 0),
|
||||||
|
('s-007', 'p-007', 30, 0),
|
||||||
|
('s-008', 'p-008', 500, 0),
|
||||||
|
('s-009', 'p-009', 1000, 0),
|
||||||
|
('s-010', 'p-010', 500, 0),
|
||||||
|
('s-011', 'p-011', 2000, 0),
|
||||||
|
('s-012', 'p-012', 800, 0);
|
||||||
|
|
||||||
|
-- 系统配置表
|
||||||
|
INSERT INTO system_config (config_id, config_key, config_value, remark) VALUES
|
||||||
|
('cfg-001', 'order_view_days', '180', '顾客可查看订单天数'),
|
||||||
|
('cfg-002', 'default_discount_rate', '100', '默认折扣率'),
|
||||||
|
('cfg-003', 'stock_alert_enabled', 'true', '是否启用库存预警'),
|
||||||
|
('cfg-004', 'wechat_enabled', 'true', '是否启用微信登录');
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
-- 订单明细表增加商品冗余字段
|
||||||
|
ALTER TABLE order_items ADD COLUMN IF NOT EXISTS category_id VARCHAR(36);
|
||||||
|
ALTER TABLE order_items ADD COLUMN IF NOT EXISTS cost_price DECIMAL(10,2);
|
||||||
|
ALTER TABLE order_items ADD COLUMN IF NOT EXISTS image_url VARCHAR(500);
|
||||||
|
ALTER TABLE order_items ADD COLUMN IF NOT EXISTS barcode VARCHAR(50);
|
||||||
|
ALTER TABLE order_items ADD COLUMN IF NOT EXISTS stock_alert INTEGER;
|
||||||
|
ALTER TABLE order_items ADD COLUMN IF NOT EXISTS description TEXT;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- 订单明细表外键移除级联删除(商品信息已冗余保存)
|
||||||
|
ALTER TABLE order_items DROP CONSTRAINT IF EXISTS order_items_product_id_fkey;
|
||||||
|
ALTER TABLE order_items ADD CONSTRAINT order_items_product_id_fkey
|
||||||
|
FOREIGN KEY (product_id) REFERENCES products(product_id);
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-- 安全处理订单明细外键约束(使用IF EXISTS避免重复执行报错)
|
||||||
|
-- 先检查约束是否存在,存在则删除
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.table_constraints
|
||||||
|
WHERE constraint_name = 'order_items_product_id_fkey'
|
||||||
|
AND table_name = 'order_items'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE order_items DROP CONSTRAINT IF EXISTS order_items_product_id_fkey;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
-- 重新添加外键约束(不级联删除)
|
||||||
|
ALTER TABLE order_items ADD CONSTRAINT order_items_product_id_fkey
|
||||||
|
FOREIGN KEY (product_id) REFERENCES products(product_id);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
-- 添加商品长度、宽度、面积字段
|
||||||
|
ALTER TABLE products ADD COLUMN length DECIMAL(10,2);
|
||||||
|
COMMENT ON COLUMN products.length IS '长度(mm)';
|
||||||
|
ALTER TABLE products ADD COLUMN width DECIMAL(10,2);
|
||||||
|
COMMENT ON COLUMN products.width IS '宽度(mm)';
|
||||||
|
ALTER TABLE products ADD COLUMN area DECIMAL(10,4);
|
||||||
|
COMMENT ON COLUMN products.area IS '面积(m²)';
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
-- 添加订单明细长度、宽度、面积字段
|
||||||
|
ALTER TABLE order_items ADD COLUMN length DECIMAL(10,2);
|
||||||
|
COMMENT ON COLUMN order_items.length IS '长度(mm)';
|
||||||
|
ALTER TABLE order_items ADD COLUMN width DECIMAL(10,2);
|
||||||
|
COMMENT ON COLUMN order_items.width IS '宽度(mm)';
|
||||||
|
ALTER TABLE order_items ADD COLUMN area DECIMAL(10,4);
|
||||||
|
COMMENT ON COLUMN order_items.area IS '面积(m²)';
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- 添加分类描述字段
|
||||||
|
ALTER TABLE categories ADD COLUMN IF NOT EXISTS description TEXT;
|
||||||
142
src/main/resources/db/migration/V1__init.sql
Normal file
142
src/main/resources/db/migration/V1__init.sql
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
-- 建材销售管家数据库表结构
|
||||||
|
-- 创建数据库: create database building_materials;
|
||||||
|
|
||||||
|
-- 用户表
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
user_id VARCHAR(36) PRIMARY KEY,
|
||||||
|
username VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
phone VARCHAR(20),
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
wechat_openid VARCHAR(128),
|
||||||
|
wechat_unionid VARCHAR(128),
|
||||||
|
alipay_openid VARCHAR(128),
|
||||||
|
role VARCHAR(20) NOT NULL DEFAULT 'sales',
|
||||||
|
status INTEGER NOT NULL DEFAULT 1,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 客户表
|
||||||
|
CREATE TABLE IF NOT EXISTS customers (
|
||||||
|
customer_id VARCHAR(36) PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
phone VARCHAR(20),
|
||||||
|
wechat_openid VARCHAR(128),
|
||||||
|
wechat_nickname VARCHAR(100),
|
||||||
|
wechat_avatar VARCHAR(500),
|
||||||
|
address VARCHAR(500),
|
||||||
|
remark TEXT,
|
||||||
|
total_amount DECIMAL(12,2) DEFAULT 0,
|
||||||
|
created_by VARCHAR(36),
|
||||||
|
last_login_at TIMESTAMP,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 商品分类表
|
||||||
|
CREATE TABLE IF NOT EXISTS categories (
|
||||||
|
category_id VARCHAR(36) PRIMARY KEY,
|
||||||
|
name VARCHAR(50) NOT NULL,
|
||||||
|
parent_id VARCHAR(36),
|
||||||
|
sort_order INTEGER DEFAULT 0,
|
||||||
|
icon VARCHAR(255),
|
||||||
|
status INTEGER DEFAULT 1,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 商品表
|
||||||
|
CREATE TABLE IF NOT EXISTS products (
|
||||||
|
product_id VARCHAR(36) PRIMARY KEY,
|
||||||
|
category_id VARCHAR(36),
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
spec VARCHAR(100),
|
||||||
|
unit VARCHAR(20) DEFAULT '个',
|
||||||
|
price DECIMAL(10,2) NOT NULL,
|
||||||
|
cost_price DECIMAL(10,2),
|
||||||
|
image_url VARCHAR(500),
|
||||||
|
barcode VARCHAR(50),
|
||||||
|
stock_alert INTEGER DEFAULT 10,
|
||||||
|
description TEXT,
|
||||||
|
status INTEGER DEFAULT 1,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (category_id) REFERENCES categories(category_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 订单表
|
||||||
|
CREATE TABLE IF NOT EXISTS orders (
|
||||||
|
order_id VARCHAR(36) PRIMARY KEY,
|
||||||
|
order_no VARCHAR(32) NOT NULL UNIQUE,
|
||||||
|
customer_id VARCHAR(36),
|
||||||
|
customer_name VARCHAR(100),
|
||||||
|
customer_phone VARCHAR(20),
|
||||||
|
customer_wechat VARCHAR(128),
|
||||||
|
total_amount DECIMAL(12,2) NOT NULL,
|
||||||
|
discount_amount DECIMAL(12,2) DEFAULT 0,
|
||||||
|
discount_money DECIMAL(12,2) DEFAULT 0,
|
||||||
|
actual_amount DECIMAL(12,2) NOT NULL,
|
||||||
|
discount_rate DECIMAL(5,2) DEFAULT 100,
|
||||||
|
status INTEGER NOT NULL DEFAULT 1,
|
||||||
|
payment_method VARCHAR(20),
|
||||||
|
remark TEXT,
|
||||||
|
operator_id VARCHAR(36),
|
||||||
|
operator_name VARCHAR(50),
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 订单明细表
|
||||||
|
CREATE TABLE IF NOT EXISTS order_items (
|
||||||
|
item_id VARCHAR(36) PRIMARY KEY,
|
||||||
|
order_id VARCHAR(36) NOT NULL,
|
||||||
|
product_id VARCHAR(36) NOT NULL,
|
||||||
|
product_name VARCHAR(100),
|
||||||
|
product_spec VARCHAR(100),
|
||||||
|
unit VARCHAR(20),
|
||||||
|
price DECIMAL(10,2) NOT NULL,
|
||||||
|
quantity INTEGER NOT NULL,
|
||||||
|
subtotal DECIMAL(12,2) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (order_id) REFERENCES orders(order_id),
|
||||||
|
FOREIGN KEY (product_id) REFERENCES products(product_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 库存表
|
||||||
|
CREATE TABLE IF NOT EXISTS stock (
|
||||||
|
stock_id VARCHAR(36) PRIMARY KEY,
|
||||||
|
product_id VARCHAR(36) NOT NULL UNIQUE,
|
||||||
|
warehouse_id VARCHAR(36),
|
||||||
|
quantity INTEGER NOT NULL DEFAULT 0,
|
||||||
|
locked_quantity INTEGER DEFAULT 0,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (product_id) REFERENCES products(product_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 库存流水表
|
||||||
|
CREATE TABLE IF NOT EXISTS stock_flow (
|
||||||
|
flow_id VARCHAR(36) PRIMARY KEY,
|
||||||
|
product_id VARCHAR(36) NOT NULL,
|
||||||
|
type INTEGER NOT NULL,
|
||||||
|
quantity INTEGER NOT NULL,
|
||||||
|
before_quantity INTEGER,
|
||||||
|
after_quantity INTEGER,
|
||||||
|
related_id VARCHAR(36),
|
||||||
|
related_type VARCHAR(20),
|
||||||
|
operator_id VARCHAR(36),
|
||||||
|
remark TEXT,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (product_id) REFERENCES products(product_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 系统配置表
|
||||||
|
CREATE TABLE IF NOT EXISTS system_config (
|
||||||
|
config_id VARCHAR(36) PRIMARY KEY,
|
||||||
|
config_key VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
config_value TEXT,
|
||||||
|
remark VARCHAR(200),
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- 订单表增加 discount_money 字段
|
||||||
|
ALTER TABLE orders ADD COLUMN discount_money DECIMAL(12,2) DEFAULT 0;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- 增加 deleted 字段用于逻辑删除
|
||||||
|
ALTER TABLE orders ADD COLUMN deleted INTEGER DEFAULT 0;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- 客户手机号唯一索引
|
||||||
|
CREATE UNIQUE INDEX idx_customer_phone ON customers(phone);
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- 为客户表增加种类字段
|
||||||
|
ALTER TABLE customers ADD COLUMN type VARCHAR(20) DEFAULT 'customer';
|
||||||
11
src/main/resources/db/migration/V6__add_printers_table.sql
Normal file
11
src/main/resources/db/migration/V6__add_printers_table.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
-- 打印机表
|
||||||
|
CREATE TABLE IF NOT EXISTS printers (
|
||||||
|
printer_id VARCHAR(36) PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
ip VARCHAR(50) NOT NULL,
|
||||||
|
port INTEGER DEFAULT 9100,
|
||||||
|
is_default INTEGER DEFAULT 0,
|
||||||
|
status INTEGER DEFAULT 1,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- 删除种类属性相关表和字段(回滚 V7)
|
||||||
|
DROP TABLE IF EXISTS product_attributes;
|
||||||
|
DROP TABLE IF EXISTS category_attributes;
|
||||||
|
ALTER TABLE products DROP COLUMN IF EXISTS default_unit;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
-- 修改商品表外键约束,级联删除(库存、库存流水)
|
||||||
|
-- 但订单明细不级联删除,因为已冗余保存商品信息
|
||||||
|
ALTER TABLE stock DROP CONSTRAINT IF EXISTS stock_product_id_fkey;
|
||||||
|
ALTER TABLE stock ADD CONSTRAINT stock_product_id_fkey
|
||||||
|
FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE stock_flow DROP CONSTRAINT IF EXISTS stock_flow_product_id_fkey;
|
||||||
|
ALTER TABLE stock_flow ADD CONSTRAINT stock_flow_product_id_fkey
|
||||||
|
FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
-- 订单明细不再级联删除(已冗余保存商品信息)
|
||||||
|
ALTER TABLE order_items DROP CONSTRAINT IF EXISTS order_items_product_id_fkey;
|
||||||
|
ALTER TABLE order_items ADD CONSTRAINT order_items_product_id_fkey
|
||||||
|
FOREIGN KEY (product_id) REFERENCES products(product_id);
|
||||||
Reference in New Issue
Block a user