Compare commits
72 Commits
b1fd515fb3
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
036e8156d5 | ||
|
|
6497788b64 | ||
|
|
132ac8796c | ||
|
|
f2a7039a41 | ||
|
|
21bbd0386d | ||
|
|
e683ef6863 | ||
|
|
086aa47226 | ||
|
|
8558d203af | ||
|
|
69909a3b83 | ||
|
|
d7754a66af | ||
|
|
dbf1832f75 | ||
|
|
e10b7bd3be | ||
|
|
5331fd75a2 | ||
|
|
5c0de2201c | ||
|
|
29f168dd67 | ||
|
|
48348609a8 | ||
|
|
25554bac84 | ||
|
|
cec5e965f6 | ||
|
|
4cd2950051 | ||
|
|
cc76710858 | ||
|
|
b51aac0cfa | ||
|
|
ee0fc4d1f6 | ||
|
|
9d20040574 | ||
|
|
2f807a2796 | ||
|
|
49867a30dd | ||
|
|
51c4a5438d | ||
|
|
e616dd6a13 | ||
|
|
ed0edf584a | ||
|
|
b9250566fb | ||
|
|
7b6943d391 | ||
|
|
984e33bd94 | ||
|
|
4fdcf6ddbd | ||
|
|
7d07e32271 | ||
|
|
438aef7af7 | ||
|
|
eaee5f5aa6 | ||
|
|
51ec9dd85b | ||
|
|
9868fb1bb9 | ||
|
|
3df1267272 | ||
|
|
5c3fa1e33f | ||
|
|
15f058617a | ||
|
|
29ff0a2637 | ||
|
|
d7d7c64c01 | ||
|
|
cc59f859af | ||
|
|
6e21143a46 | ||
|
|
d6402d60b2 | ||
|
|
749a99dd01 | ||
|
|
024ee7ebda | ||
|
|
98bbf219f3 | ||
|
|
2857f2057d | ||
|
|
0b7e86cfa3 | ||
|
|
a8cdb27e8e | ||
|
|
d961e62cc2 | ||
|
|
da2902c61c | ||
|
|
f39fc4f040 | ||
|
|
83112b406a | ||
|
|
fe36332ef3 | ||
|
|
7443c33d7a | ||
|
|
c463179e83 | ||
|
|
79b516d81c | ||
|
|
7b9f1fd8c2 | ||
|
|
d01c8a4c6a | ||
|
|
9f83103189 | ||
|
|
60b4b0bd49 | ||
|
|
0faa7f2988 | ||
|
|
cf25e6b116 | ||
|
|
2706bfb3b6 | ||
|
|
2a820f113d | ||
|
|
e6ad24e015 | ||
|
|
4f4b2f9027 | ||
|
|
c7684e3199 | ||
|
|
04b9960e35 | ||
|
|
f3480b6ba0 |
65
README.md
65
README.md
@@ -134,6 +134,71 @@ mvn spotless:apply compile
|
|||||||
mvn spotless:apply checkstyle:check compile
|
mvn spotless:apply checkstyle:check compile
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 数据库迁移
|
||||||
|
|
||||||
|
仓库根目录下提供 `flyway/` 配置与 `flyway.sh` 工具脚本,用于在不同环境执行迁移。
|
||||||
|
|
||||||
|
### Profile 与配置
|
||||||
|
|
||||||
|
| Profile | 配置文件 | 用途 |
|
||||||
|
|------------|-----------------------|------------------------------|
|
||||||
|
| `dev` | `flyway/dev.conf` | 本地 `play-with` 开发库 |
|
||||||
|
| `staging` | `flyway/staging.conf` | 生产克隆 / 本地 staging 库 |
|
||||||
|
| `api-test` | `flyway/api-test.conf`| API 集成测试数据库 |
|
||||||
|
| `prod` | `flyway/prod.conf` | 线上生产库 |
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 校验本地 schema
|
||||||
|
./flyway.sh validate --profile dev
|
||||||
|
|
||||||
|
# 对 API 测试库执行迁移
|
||||||
|
./flyway.sh migrate --profile api-test
|
||||||
|
|
||||||
|
# 修复 staging 库
|
||||||
|
./flyway.sh repair --profile staging
|
||||||
|
```
|
||||||
|
|
||||||
|
当对 `prod` profile 执行 `migrate` 或 `repair` 时,脚本会连续两次提示“你备份数据库了吗?”以避免误操作,输入 `yes` 才会继续。
|
||||||
|
|
||||||
|
## API 集成测试指南
|
||||||
|
|
||||||
|
在 `play-admin` 模块内提供了基于 `apitest` Profile 的端到端测试套件。为了稳定跑通所有 API 场景,请按以下步骤准备环境:
|
||||||
|
|
||||||
|
1. **准备数据库**
|
||||||
|
默认连接信息为 `jdbc:mysql://127.0.0.1:33306/peipei_apitest`,账号密码均为 `apitest`。可通过以下命令初始化:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE DATABASE IF NOT EXISTS peipei_apitest CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
CREATE USER IF NOT EXISTS 'apitest'@'%' IDENTIFIED BY 'apitest';
|
||||||
|
GRANT ALL PRIVILEGES ON peipei_apitest.* TO 'apitest'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
```
|
||||||
|
|
||||||
|
若端口或凭证不同,请同步修改 `play-admin/src/main/resources/application-apitest.yml`。
|
||||||
|
|
||||||
|
2. **准备 Redis(必需)**
|
||||||
|
测试依赖 Redis 记录幂等与缓存信息。可以执行 `docker compose up -d redis`(路径:`docker/docker-compose.yml`)快速启一个实例,默认映射端口为 `36379`。
|
||||||
|
|
||||||
|
3. **执行测试**
|
||||||
|
在仓库根目录运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -pl play-admin -am test
|
||||||
|
```
|
||||||
|
|
||||||
|
如需探查单个用例,可指定测试类:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -pl play-admin -Dtest=WxCustomRandomOrderApiTest test
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **自动数据播种**
|
||||||
|
激活 `apitest` Profile 时,`ApiTestDataSeeder` 会自动创建默认租户、顾客、店员、商品、礼物、优惠券等基线数据,并在每次启动时重置关键计数,因此多次执行结果一致。如果需要彻底清理,可直接清空数据库后重新运行测试。
|
||||||
|
|
||||||
|
按照上述流程,即可可靠地复现订单、优惠券、礼物等核心链路的 API 行为。
|
||||||
|
|
||||||
## 部署说明
|
## 部署说明
|
||||||
|
|
||||||
### Docker 构建和推送
|
### Docker 构建和推送
|
||||||
|
|||||||
18
backup-dev-db.sh
Executable file
18
backup-dev-db.sh
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DB_HOST="primary"
|
||||||
|
DB_PORT="3306"
|
||||||
|
DB_NAME="play-with"
|
||||||
|
DB_USER="root"
|
||||||
|
DB_PASSWORD="123456"
|
||||||
|
|
||||||
|
stamp="$(date +%F)"
|
||||||
|
backup_dir="yunpei/backup/dev/${stamp}"
|
||||||
|
mkdir -p "${backup_dir}"
|
||||||
|
|
||||||
|
echo "[backup] dumping ${DB_NAME} from ${DB_HOST}:${DB_PORT} -> ${backup_dir}/dev.sql.gz"
|
||||||
|
mysqldump -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASSWORD}" "${DB_NAME}" \
|
||||||
|
| gzip > "${backup_dir}/dev.sql.gz"
|
||||||
|
|
||||||
|
echo "[backup] done"
|
||||||
@@ -77,6 +77,20 @@ fi
|
|||||||
TIMESTAMP=$(TZ='Asia/Shanghai' date +"%Y-%m-%d-%Hh-%Mm")
|
TIMESTAMP=$(TZ='Asia/Shanghai' date +"%Y-%m-%d-%Hh-%Mm")
|
||||||
echo -e "${YELLOW}构建时间戳 (UTC+8): ${TIMESTAMP}${NC}"
|
echo -e "${YELLOW}构建时间戳 (UTC+8): ${TIMESTAMP}${NC}"
|
||||||
|
|
||||||
|
# 获取 Git 提交信息用于镜像元数据
|
||||||
|
if git rev-parse HEAD >/dev/null 2>&1; then
|
||||||
|
COMMIT_HASH=$(git rev-parse HEAD)
|
||||||
|
COMMIT_MESSAGE=$(git log -1 --pretty=%s | tr -d '\n')
|
||||||
|
COMMIT_MESSAGE=${COMMIT_MESSAGE//\"/\'}
|
||||||
|
COMMIT_MESSAGE=${COMMIT_MESSAGE//\$/\\$}
|
||||||
|
else
|
||||||
|
COMMIT_HASH="unknown"
|
||||||
|
COMMIT_MESSAGE="unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Git 提交: ${COMMIT_HASH}${NC}"
|
||||||
|
echo -e "${YELLOW}提交说明: ${COMMIT_MESSAGE}${NC}"
|
||||||
|
|
||||||
# 镜像名称和标签
|
# 镜像名称和标签
|
||||||
IMAGE_NAME="peipei-backend"
|
IMAGE_NAME="peipei-backend"
|
||||||
VERSION_TAG="${TIMESTAMP}-${TARGET_ARCH}"
|
VERSION_TAG="${TIMESTAMP}-${TARGET_ARCH}"
|
||||||
@@ -124,6 +138,8 @@ if docker buildx build \
|
|||||||
--load \
|
--load \
|
||||||
--cache-from="type=local,src=${CACHE_DIR}" \
|
--cache-from="type=local,src=${CACHE_DIR}" \
|
||||||
--cache-to="type=local,dest=${CACHE_DIR}" \
|
--cache-to="type=local,dest=${CACHE_DIR}" \
|
||||||
|
--label "org.opencontainers.image.revision=${COMMIT_HASH}" \
|
||||||
|
--label "org.opencontainers.image.commit-message=${COMMIT_MESSAGE}" \
|
||||||
-f docker/Dockerfile \
|
-f docker/Dockerfile \
|
||||||
-t "${IMAGE_NAME}:${VERSION_TAG}" \
|
-t "${IMAGE_NAME}:${VERSION_TAG}" \
|
||||||
-t "${IMAGE_NAME}:${LATEST_TAG}" \
|
-t "${IMAGE_NAME}:${LATEST_TAG}" \
|
||||||
@@ -139,6 +155,9 @@ if [[ "$BUILD_SUCCESS" == "true" ]]; then
|
|||||||
echo -e "${GREEN}镜像标签:${NC}"
|
echo -e "${GREEN}镜像标签:${NC}"
|
||||||
echo -e " - ${IMAGE_NAME}:${VERSION_TAG}"
|
echo -e " - ${IMAGE_NAME}:${VERSION_TAG}"
|
||||||
echo -e " - ${IMAGE_NAME}:${LATEST_TAG}"
|
echo -e " - ${IMAGE_NAME}:${LATEST_TAG}"
|
||||||
|
echo -e "${GREEN}镜像元数据:${NC}"
|
||||||
|
echo -e " - org.opencontainers.image.revision=${COMMIT_HASH}"
|
||||||
|
echo -e " - org.opencontainers.image.commit-message=${COMMIT_MESSAGE}"
|
||||||
|
|
||||||
echo -e "\n${YELLOW}镜像信息:${NC}"
|
echo -e "\n${YELLOW}镜像信息:${NC}"
|
||||||
docker images | grep -E "^${IMAGE_NAME}\s"
|
docker images | grep -E "^${IMAGE_NAME}\s"
|
||||||
|
|||||||
@@ -1,6 +1,75 @@
|
|||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
# Docker deployment script
|
# Docker deployment script with safety checks
|
||||||
set -e
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
prompt_yes_no() {
|
||||||
|
local prompt="$1"
|
||||||
|
local choice_hint="${2:-[y/N]}"
|
||||||
|
local answer
|
||||||
|
read -r -p "$prompt $choice_hint " answer || true
|
||||||
|
answer="${answer:-}"
|
||||||
|
answer="$(printf '%s' "$answer" | tr '[:upper:]' '[:lower:]')"
|
||||||
|
[[ "$answer" == "y" || "$answer" == "yes" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "=== 部署前检查开始 ==="
|
||||||
|
|
||||||
|
if ! git -C "$SCRIPT_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
echo "错误: 当前目录不在 Git 仓库内,无法继续。"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CURRENT_BRANCH=$(git -C "$SCRIPT_DIR" rev-parse --abbrev-ref HEAD)
|
||||||
|
if [[ "$CURRENT_BRANCH" != "master" ]]; then
|
||||||
|
echo "错误: 当前分支为 '$CURRENT_BRANCH'。仅允许在 master 分支上部署。"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if prompt_yes_no "你跑测试了吗?确认已跑请输入 y"; then
|
||||||
|
echo "已确认测试执行完毕。"
|
||||||
|
else
|
||||||
|
if prompt_yes_no "需要我帮你跑测试吗?"; then
|
||||||
|
echo "开始执行测试: mvn test"
|
||||||
|
if ! mvn test; then
|
||||||
|
echo "测试未通过,部署流程终止。"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "测试通过。"
|
||||||
|
else
|
||||||
|
echo "错误: 未执行测试,部署流程终止。"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! prompt_yes_no "你备份数据库了吗?确认已备份请输入 y"; then
|
||||||
|
echo "请先完成数据库备份,再运行部署脚本。"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! prompt_yes_no "你 commit 了吗?确认已提交请输入 y"; then
|
||||||
|
echo "请在提交后再运行部署脚本。"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git -C "$SCRIPT_DIR" diff --quiet; then
|
||||||
|
echo "错误: 检测到未暂存的本地修改,请处理后再试。"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$(git -C "$SCRIPT_DIR" ls-files --others --exclude-standard)" ]]; then
|
||||||
|
echo "错误: 检测到未跟踪的文件,请清理或加入版本控制后再试。"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git -C "$SCRIPT_DIR" diff --cached --quiet; then
|
||||||
|
echo "错误: 检测到未提交的暂存修改,请提交后再试。"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "部署前检查通过。"
|
||||||
|
|
||||||
# Get current time and format it
|
# Get current time and format it
|
||||||
current_time=$(date +"%Y-%m-%d %H:%M:%S")
|
current_time=$(date +"%Y-%m-%d %H:%M:%S")
|
||||||
|
|||||||
27
docker/apitest-mysql.yml
Normal file
27
docker/apitest-mysql.yml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql-apitest:
|
||||||
|
image: mysql:8.0.24
|
||||||
|
container_name: peipei-mysql-apitest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: root
|
||||||
|
MYSQL_DATABASE: peipei_apitest
|
||||||
|
MYSQL_USER: apitest
|
||||||
|
MYSQL_PASSWORD: apitest
|
||||||
|
ports:
|
||||||
|
- "33306:3306"
|
||||||
|
volumes:
|
||||||
|
- ./apitest-mysql/init:/docker-entrypoint-initdb.d:ro
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
command:
|
||||||
|
- "--default-authentication-plugin=mysql_native_password"
|
||||||
|
- "--lower_case_table_names=1"
|
||||||
|
- "--explicit_defaults_for_timestamp=1"
|
||||||
|
- "--character-set-server=utf8mb4"
|
||||||
|
- "--collation-server=utf8mb4_unicode_ci"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
GRANT SELECT ON performance_schema.* TO 'apitest'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
10
docker/apitest-mysql/init/README.md
Normal file
10
docker/apitest-mysql/init/README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# API Test MySQL Seed Files
|
||||||
|
|
||||||
|
将初始化 schema 和种子数据的 SQL 文件放在此目录下,文件会在 `mysql-apitest` 容器启动时自动执行。
|
||||||
|
|
||||||
|
推荐约定:
|
||||||
|
- `000-schema.sql`:创建数据库/表结构(可复用 Flyway 生成的整库脚本)。
|
||||||
|
- `100-seed-*.sql`:插入基础租户、用户、商品、优惠券等测试数据。
|
||||||
|
- `900-cleanup.sql`:可选的清理脚本,用于重置状态。
|
||||||
|
|
||||||
|
容器销毁(`docker-compose down -v`)后数据会一起删除,保证每次测试环境一致。
|
||||||
32
docker/docker-compose-apitest.yml
Normal file
32
docker/docker-compose-apitest.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
version: "3.9"
|
||||||
|
services:
|
||||||
|
mysql-apitest:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: peipei-mysql-apitest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: root
|
||||||
|
MYSQL_DATABASE: peipei_apitest
|
||||||
|
MYSQL_USER: apitest
|
||||||
|
MYSQL_PASSWORD: apitest
|
||||||
|
ports:
|
||||||
|
- "33306:3306"
|
||||||
|
command:
|
||||||
|
- "--default-authentication-plugin=mysql_native_password"
|
||||||
|
- "--lower_case_table_names=1"
|
||||||
|
- "--explicit_defaults_for_timestamp=1"
|
||||||
|
- "--character-set-server=utf8mb4"
|
||||||
|
- "--collation-server=utf8mb4_general_ci"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
redis-apitest:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: peipei-redis-apitest
|
||||||
|
restart: unless-stopped
|
||||||
|
command: ["redis-server", "--appendonly", "no"]
|
||||||
|
ports:
|
||||||
|
- "36379:6379"
|
||||||
@@ -1,9 +1,20 @@
|
|||||||
version: "3.8"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: peipei-redis
|
||||||
|
ports:
|
||||||
|
- "36379:6379"
|
||||||
|
command: ["redis-server", "--save", "", "--appendonly", "no"]
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- peipei-network
|
||||||
|
|
||||||
peipei-backend:
|
peipei-backend:
|
||||||
image: docker-registry.julyhaven.com/peipei/backend:latest
|
image: docker-registry.julyhaven.com/peipei/backend:latest
|
||||||
container_name: peipei-backend
|
container_name: peipei-backend
|
||||||
|
platform: linux/amd64
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
ports:
|
ports:
|
||||||
- "7003:7002"
|
- "7003:7002"
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
92
flyway.sh
Executable file
92
flyway.sh
Executable file
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CONFIG_DIR="$SCRIPT_DIR/flyway"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<USAGE
|
||||||
|
Usage: $(basename "$0") <migrate|validate|repair> --profile <api-test|dev|staging|prod>
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
./flyway.sh validate --profile dev
|
||||||
|
./flyway.sh migrate --profile api-test
|
||||||
|
USAGE
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $# -lt 2 ]]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
action="$1"
|
||||||
|
shift
|
||||||
|
case "$action" in
|
||||||
|
migrate|validate|repair) ;;
|
||||||
|
*) echo "[ERROR] Unsupported action: $action" >&2; usage ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
profile=""
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--profile|-p)
|
||||||
|
[[ $# -ge 2 ]] || usage
|
||||||
|
profile="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "[ERROR] Unknown argument: $1" >&2
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$profile" ]]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$profile" in
|
||||||
|
api-test|apitest)
|
||||||
|
profile="api-test"
|
||||||
|
config_file="$CONFIG_DIR/api-test.conf"
|
||||||
|
;;
|
||||||
|
dev)
|
||||||
|
config_file="$CONFIG_DIR/dev.conf"
|
||||||
|
;;
|
||||||
|
staging)
|
||||||
|
config_file="$CONFIG_DIR/staging.conf"
|
||||||
|
;;
|
||||||
|
prod)
|
||||||
|
config_file="$CONFIG_DIR/prod.conf"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "[ERROR] Unknown profile: $profile" >&2
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ ! -f "$config_file" ]]; then
|
||||||
|
echo "[ERROR] Config file not found: $config_file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
confirm_backup() {
|
||||||
|
local prompt="$1"
|
||||||
|
read -r -p "$prompt (yes/no): " reply
|
||||||
|
if [[ "$reply" != "yes" ]]; then
|
||||||
|
echo "操作已取消"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$profile" == "prod" && ( "$action" == "migrate" || "$action" == "repair" ) ]]; then
|
||||||
|
confirm_backup "你备份数据库了吗?"
|
||||||
|
confirm_backup "你真的备份了吗?"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec mvn \
|
||||||
|
-f "$SCRIPT_DIR/pom.xml" \
|
||||||
|
-pl play-admin \
|
||||||
|
-DskipTests \
|
||||||
|
"flyway:$action" \
|
||||||
|
"-Dflyway.configFiles=$config_file"
|
||||||
10
flyway/api-test.conf
Normal file
10
flyway/api-test.conf
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
flyway.url=jdbc:mysql://127.0.0.1:33306/peipei_apitest?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
|
||||||
|
flyway.user=apitest
|
||||||
|
flyway.password=apitest
|
||||||
|
flyway.locations=classpath:db/migration
|
||||||
|
flyway.table=admin_flyway_schema_history
|
||||||
|
flyway.baselineOnMigrate=true
|
||||||
|
flyway.baselineVersion=1
|
||||||
|
flyway.cleanDisabled=true
|
||||||
|
flyway.outOfOrder=false
|
||||||
|
flyway.validateOnMigrate=false
|
||||||
10
flyway/dev.conf
Normal file
10
flyway/dev.conf
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
flyway.url=jdbc:mysql://127.0.0.1:3307/play-with?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
|
||||||
|
flyway.user=root
|
||||||
|
flyway.password=root
|
||||||
|
flyway.locations=classpath:db/migration
|
||||||
|
flyway.table=admin_flyway_schema_history
|
||||||
|
flyway.baselineOnMigrate=true
|
||||||
|
flyway.baselineVersion=1
|
||||||
|
flyway.cleanDisabled=true
|
||||||
|
flyway.outOfOrder=false
|
||||||
|
flyway.validateOnMigrate=false
|
||||||
10
flyway/prod.conf
Normal file
10
flyway/prod.conf
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
flyway.url=jdbc:mysql://122.51.20.105:3306/play-with?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true&rewriteBatchedStatements=true
|
||||||
|
flyway.user=root
|
||||||
|
flyway.password=KdaKRZ2trpdhNePa
|
||||||
|
flyway.locations=classpath:db/migration
|
||||||
|
flyway.table=admin_flyway_schema_history
|
||||||
|
flyway.baselineOnMigrate=true
|
||||||
|
flyway.baselineVersion=1
|
||||||
|
flyway.cleanDisabled=true
|
||||||
|
flyway.outOfOrder=false
|
||||||
|
flyway.validateOnMigrate=true
|
||||||
10
flyway/staging.conf
Normal file
10
flyway/staging.conf
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
flyway.url=jdbc:mysql://127.0.0.1:3307/play-with?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
|
||||||
|
flyway.user=root
|
||||||
|
flyway.password=root
|
||||||
|
flyway.locations=classpath:db/migration
|
||||||
|
flyway.table=admin_flyway_schema_history
|
||||||
|
flyway.baselineOnMigrate=true
|
||||||
|
flyway.baselineVersion=1
|
||||||
|
flyway.cleanDisabled=true
|
||||||
|
flyway.outOfOrder=false
|
||||||
|
flyway.validateOnMigrate=false
|
||||||
24
media-migration-to-test.md
Normal file
24
media-migration-to-test.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
## 媒資/相簿相容性:手動驗證清單
|
||||||
|
|
||||||
|
1. **WeChat 店員端 - 個資頁預覽**
|
||||||
|
- 登入店員帳號進入「我的資料」,確認相簿縮圖顯示為合併後的 `mediaList + album`,不應出現重覆 URL。
|
||||||
|
- 點擊「照片」進入管理介面,確認 legacy album 中的圖片仍存在;上傳新媒資後應立即出現於列表。
|
||||||
|
|
||||||
|
2. **媒資上傳與排序**
|
||||||
|
- 上傳圖片與影片各一,測試格式/大小超限的錯誤提示與成功上傳後的狀態。
|
||||||
|
- 排序、刪除媒資並提交審核,確認前端列表與預覽更新,且不會重複顯示相同 URL。
|
||||||
|
|
||||||
|
3. **後台店員列表**
|
||||||
|
- 在管理端店員列表中,確認每位店員的照片區塊都展示合併後的媒資清單(舊 album + 新媒資)。
|
||||||
|
- 點擊圖片預覽,確認輪播順序正確、無重覆 URL。
|
||||||
|
|
||||||
|
4. **後台店員審核詳情**
|
||||||
|
- 查看一筆含多張舊相簿照片的申請,確認圖片區塊已改用 `mediaGallery`,兼容新舊媒資。
|
||||||
|
- 點擊照片預覽,確認圖片來源為合併後的清單。
|
||||||
|
|
||||||
|
5. **API 回應驗證**
|
||||||
|
- 呼叫 `/clerk/user/list` 或 WeChat 前端使用的 API,檢查 `album` 欄位仍保留原值,`mediaList` 會包含新媒資並附帶 legacy URL(無重複)。
|
||||||
|
- 若資料仍未遷移,確保 `mediaList` 仍會帶上舊 `album` 的 URL。
|
||||||
|
|
||||||
|
6. **媒資審核流程**
|
||||||
|
- 走一次「上傳 → 排序 → 提交 → 審核」流程,確認審核通過後 `mediaList` 只保留 Approved 的媒資,但 `album` 不會被清除,舊客端仍能看到舊資料。
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
<maven.compiler.source>11</maven.compiler.source>
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
<maven.compiler.target>11</maven.compiler.target>
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<spring.profiles.active>test</spring.profiles.active>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -173,6 +174,22 @@
|
|||||||
<artifactId>json-path</artifactId>
|
<artifactId>json-path</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.junit.vintage</groupId>
|
||||||
|
<artifactId>junit-vintage-engine</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
@@ -203,6 +220,9 @@
|
|||||||
<version>3.0.0-M7</version>
|
<version>3.0.0-M7</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<useSystemClassLoader>false</useSystemClassLoader>
|
<useSystemClassLoader>false</useSystemClassLoader>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<spring.profiles.active>${spring.profiles.active}</spring.profiles.active>
|
||||||
|
</systemPropertyVariables>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.starry.admin.common;
|
||||||
|
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
|
||||||
|
public class PageBuilder {
|
||||||
|
public static final String PAGE_NUM = "pageNum";
|
||||||
|
|
||||||
|
public static final String PAGE_SIZE = "pageSize";
|
||||||
|
|
||||||
|
public static <T> Page<T> build() {
|
||||||
|
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||||
|
Integer pageNum = Integer.valueOf(attributes.getRequest().getParameter(PAGE_NUM));
|
||||||
|
Integer pageSize = Integer.valueOf(attributes.getRequest().getParameter(PAGE_SIZE));
|
||||||
|
return new Page<>(pageNum, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,526 @@
|
|||||||
|
package com.starry.admin.common.apitest;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkCommodityEntity;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.clerk.service.IPlayClerkCommodityService;
|
||||||
|
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
|
||||||
|
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||||
|
import com.starry.admin.modules.custom.mapper.PlayCustomGiftInfoMapper;
|
||||||
|
import com.starry.admin.modules.custom.module.entity.PlayCustomGiftInfoEntity;
|
||||||
|
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.custom.service.IPlayCustomGiftInfoService;
|
||||||
|
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
||||||
|
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
|
||||||
|
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
|
||||||
|
import com.starry.admin.modules.shop.mapper.PlayClerkGiftInfoMapper;
|
||||||
|
import com.starry.admin.modules.shop.module.entity.PlayClerkGiftInfoEntity;
|
||||||
|
import com.starry.admin.modules.shop.module.entity.PlayCommodityAndLevelInfoEntity;
|
||||||
|
import com.starry.admin.modules.shop.module.entity.PlayCommodityInfoEntity;
|
||||||
|
import com.starry.admin.modules.shop.module.entity.PlayGiftInfoEntity;
|
||||||
|
import com.starry.admin.modules.shop.service.IPlayClerkGiftInfoService;
|
||||||
|
import com.starry.admin.modules.shop.service.IPlayCommodityAndLevelInfoService;
|
||||||
|
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
|
||||||
|
import com.starry.admin.modules.shop.service.IPlayGiftInfoService;
|
||||||
|
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
|
||||||
|
import com.starry.admin.modules.system.module.entity.SysTenantPackageEntity;
|
||||||
|
import com.starry.admin.modules.system.module.entity.SysUserEntity;
|
||||||
|
import com.starry.admin.modules.system.service.ISysTenantPackageService;
|
||||||
|
import com.starry.admin.modules.system.service.ISysTenantService;
|
||||||
|
import com.starry.admin.modules.system.service.SysUserService;
|
||||||
|
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||||
|
import com.starry.admin.utils.SecurityUtils;
|
||||||
|
import com.starry.common.context.CustomSecurityContextHolder;
|
||||||
|
import com.starry.common.utils.IdUtils;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Profile("apitest")
|
||||||
|
public class ApiTestDataSeeder implements CommandLineRunner {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ApiTestDataSeeder.class);
|
||||||
|
|
||||||
|
public static final String DEFAULT_PACKAGE_ID = "pkg-basic";
|
||||||
|
public static final String DEFAULT_TENANT_ID = "tenant-apitest";
|
||||||
|
public static final String DEFAULT_TENANT_KEY = "tenant-key-apitest";
|
||||||
|
public static final String DEFAULT_TENANT_NAME = "API Test Tenant";
|
||||||
|
public static final String DEFAULT_ADMIN_USER_ID = "user-apitest-admin";
|
||||||
|
public static final String DEFAULT_ADMIN_USERNAME = "apitest-admin";
|
||||||
|
public static final String DEFAULT_GROUP_ID = "group-basic";
|
||||||
|
public static final String DEFAULT_CLERK_LEVEL_ID = "lvl-basic";
|
||||||
|
public static final String DEFAULT_CLERK_ID = "clerk-apitest";
|
||||||
|
public static final String DEFAULT_CLERK_OPEN_ID = "openid-clerk-apitest";
|
||||||
|
public static final String DEFAULT_COMMODITY_PARENT_ID = "svc-parent";
|
||||||
|
public static final String DEFAULT_COMMODITY_PARENT_NAME = "语音陪聊服务";
|
||||||
|
public static final String DEFAULT_COMMODITY_ID = "svc-basic";
|
||||||
|
public static final String DEFAULT_CLERK_COMMODITY_ID = "clerk-svc-basic";
|
||||||
|
public static final String DEFAULT_CUSTOMER_ID = "customer-apitest";
|
||||||
|
public static final String DEFAULT_GIFT_ID = "gift-basic";
|
||||||
|
public static final String DEFAULT_GIFT_NAME = "API测试礼物";
|
||||||
|
public static final BigDecimal DEFAULT_COMMODITY_PRICE = new BigDecimal("120.00");
|
||||||
|
private static final String GIFT_TYPE_REGULAR = "1";
|
||||||
|
private static final String GIFT_STATE_ACTIVE = "0";
|
||||||
|
private static final BigDecimal DEFAULT_CUSTOMER_BALANCE = new BigDecimal("200.00");
|
||||||
|
private static final BigDecimal DEFAULT_CUSTOMER_RECHARGE = DEFAULT_CUSTOMER_BALANCE;
|
||||||
|
|
||||||
|
private final ISysTenantPackageService tenantPackageService;
|
||||||
|
private final ISysTenantService tenantService;
|
||||||
|
private final SysUserService sysUserService;
|
||||||
|
private final IPlayPersonnelGroupInfoService personnelGroupInfoService;
|
||||||
|
private final IPlayClerkLevelInfoService clerkLevelInfoService;
|
||||||
|
private final IPlayClerkUserInfoService clerkUserInfoService;
|
||||||
|
private final IPlayCommodityInfoService commodityInfoService;
|
||||||
|
private final IPlayCommodityAndLevelInfoService commodityAndLevelInfoService;
|
||||||
|
private final IPlayGiftInfoService giftInfoService;
|
||||||
|
private final IPlayClerkCommodityService clerkCommodityService;
|
||||||
|
private final IPlayClerkGiftInfoService playClerkGiftInfoService;
|
||||||
|
private final IPlayCustomUserInfoService customUserInfoService;
|
||||||
|
private final IPlayCustomGiftInfoService playCustomGiftInfoService;
|
||||||
|
private final PlayClerkGiftInfoMapper playClerkGiftInfoMapper;
|
||||||
|
private final PlayCustomGiftInfoMapper playCustomGiftInfoMapper;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final WxTokenService wxTokenService;
|
||||||
|
|
||||||
|
public ApiTestDataSeeder(
|
||||||
|
ISysTenantPackageService tenantPackageService,
|
||||||
|
ISysTenantService tenantService,
|
||||||
|
SysUserService sysUserService,
|
||||||
|
IPlayPersonnelGroupInfoService personnelGroupInfoService,
|
||||||
|
IPlayClerkLevelInfoService clerkLevelInfoService,
|
||||||
|
IPlayClerkUserInfoService clerkUserInfoService,
|
||||||
|
IPlayCommodityInfoService commodityInfoService,
|
||||||
|
IPlayCommodityAndLevelInfoService commodityAndLevelInfoService,
|
||||||
|
IPlayGiftInfoService giftInfoService,
|
||||||
|
IPlayClerkCommodityService clerkCommodityService,
|
||||||
|
IPlayClerkGiftInfoService playClerkGiftInfoService,
|
||||||
|
IPlayCustomUserInfoService customUserInfoService,
|
||||||
|
IPlayCustomGiftInfoService playCustomGiftInfoService,
|
||||||
|
PlayClerkGiftInfoMapper playClerkGiftInfoMapper,
|
||||||
|
PlayCustomGiftInfoMapper playCustomGiftInfoMapper,
|
||||||
|
PasswordEncoder passwordEncoder,
|
||||||
|
WxTokenService wxTokenService) {
|
||||||
|
this.tenantPackageService = tenantPackageService;
|
||||||
|
this.tenantService = tenantService;
|
||||||
|
this.sysUserService = sysUserService;
|
||||||
|
this.personnelGroupInfoService = personnelGroupInfoService;
|
||||||
|
this.clerkLevelInfoService = clerkLevelInfoService;
|
||||||
|
this.clerkUserInfoService = clerkUserInfoService;
|
||||||
|
this.commodityInfoService = commodityInfoService;
|
||||||
|
this.commodityAndLevelInfoService = commodityAndLevelInfoService;
|
||||||
|
this.giftInfoService = giftInfoService;
|
||||||
|
this.clerkCommodityService = clerkCommodityService;
|
||||||
|
this.playClerkGiftInfoService = playClerkGiftInfoService;
|
||||||
|
this.customUserInfoService = customUserInfoService;
|
||||||
|
this.playCustomGiftInfoService = playCustomGiftInfoService;
|
||||||
|
this.playClerkGiftInfoMapper = playClerkGiftInfoMapper;
|
||||||
|
this.playCustomGiftInfoMapper = playCustomGiftInfoMapper;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.wxTokenService = wxTokenService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void run(String... args) {
|
||||||
|
seedTenantPackage();
|
||||||
|
seedTenant();
|
||||||
|
|
||||||
|
String originalTenant = SecurityUtils.getTenantId();
|
||||||
|
try {
|
||||||
|
SecurityUtils.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
seedAdminUser();
|
||||||
|
seedPersonnelGroup();
|
||||||
|
seedClerkLevel();
|
||||||
|
PlayCommodityInfoEntity commodity = seedCommodityHierarchy();
|
||||||
|
seedCommodityPricing(commodity);
|
||||||
|
seedClerk();
|
||||||
|
seedClerkCommodity();
|
||||||
|
seedGift();
|
||||||
|
resetGiftCounters();
|
||||||
|
seedCustomer();
|
||||||
|
} finally {
|
||||||
|
if (Objects.nonNull(originalTenant)) {
|
||||||
|
SecurityUtils.setTenantId(originalTenant);
|
||||||
|
}
|
||||||
|
CustomSecurityContextHolder.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seedTenantPackage() {
|
||||||
|
long existing = tenantPackageService.count(Wrappers.<SysTenantPackageEntity>lambdaQuery()
|
||||||
|
.eq(SysTenantPackageEntity::getPackageId, DEFAULT_PACKAGE_ID));
|
||||||
|
if (existing > 0) {
|
||||||
|
log.info("API test tenant package {} already exists", DEFAULT_PACKAGE_ID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SysTenantPackageEntity entity = new SysTenantPackageEntity();
|
||||||
|
entity.setPackageId(DEFAULT_PACKAGE_ID);
|
||||||
|
entity.setPackageName("API测试基础套餐");
|
||||||
|
entity.setStatus("0");
|
||||||
|
entity.setMenuIds("[]");
|
||||||
|
entity.setRemarks("Seeded for API integration tests");
|
||||||
|
tenantPackageService.save(entity);
|
||||||
|
log.info("Inserted API test tenant package {}", DEFAULT_PACKAGE_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seedTenant() {
|
||||||
|
SysTenantEntity tenant = tenantService.getById(DEFAULT_TENANT_ID);
|
||||||
|
if (tenant != null) {
|
||||||
|
log.info("API test tenant {} already exists", DEFAULT_TENANT_ID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SysTenantEntity entity = new SysTenantEntity();
|
||||||
|
entity.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
entity.setTenantName(DEFAULT_TENANT_NAME);
|
||||||
|
entity.setTenantType("0");
|
||||||
|
entity.setTenantStatus("0");
|
||||||
|
entity.setTenantCode("apitest");
|
||||||
|
entity.setTenantKey(DEFAULT_TENANT_KEY);
|
||||||
|
entity.setPackageId(DEFAULT_PACKAGE_ID);
|
||||||
|
entity.setTenantTime(new Date(System.currentTimeMillis() + 365L * 24 * 3600 * 1000));
|
||||||
|
entity.setUserName(DEFAULT_ADMIN_USERNAME);
|
||||||
|
entity.setUserPwd(passwordEncoder.encode("apitest-secret"));
|
||||||
|
entity.setPhone("13800000000");
|
||||||
|
entity.setEmail("apitest@example.com");
|
||||||
|
entity.setAddress("API Test Street 1");
|
||||||
|
tenantService.save(entity);
|
||||||
|
log.info("Inserted API test tenant {}", DEFAULT_TENANT_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seedAdminUser() {
|
||||||
|
SysUserEntity existing = sysUserService.getById(DEFAULT_ADMIN_USER_ID);
|
||||||
|
if (existing != null) {
|
||||||
|
log.info("API test admin user {} already exists", DEFAULT_ADMIN_USER_ID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SysUserEntity admin = new SysUserEntity();
|
||||||
|
admin.setUserId(DEFAULT_ADMIN_USER_ID);
|
||||||
|
admin.setUserCode(DEFAULT_ADMIN_USERNAME);
|
||||||
|
admin.setPassWord(passwordEncoder.encode("apitest-secret"));
|
||||||
|
admin.setRealName("API Test Admin");
|
||||||
|
admin.setUserNickname("API Admin");
|
||||||
|
admin.setStatus(0);
|
||||||
|
admin.setUserType(1);
|
||||||
|
admin.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
admin.setMobile("13800000000");
|
||||||
|
admin.setAddTime(LocalDateTime.now());
|
||||||
|
admin.setSuperAdmin(Boolean.TRUE);
|
||||||
|
sysUserService.save(admin);
|
||||||
|
log.info("Inserted API test admin user {}", DEFAULT_ADMIN_USER_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seedPersonnelGroup() {
|
||||||
|
PlayPersonnelGroupInfoEntity group = personnelGroupInfoService.getById(DEFAULT_GROUP_ID);
|
||||||
|
if (group != null) {
|
||||||
|
log.info("API test personnel group {} already exists", DEFAULT_GROUP_ID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayPersonnelGroupInfoEntity entity = new PlayPersonnelGroupInfoEntity();
|
||||||
|
entity.setId(DEFAULT_GROUP_ID);
|
||||||
|
entity.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
entity.setSysUserId(DEFAULT_ADMIN_USER_ID);
|
||||||
|
entity.setSysUserCode(DEFAULT_ADMIN_USERNAME);
|
||||||
|
entity.setGroupName("测试小组");
|
||||||
|
entity.setLeaderName("API Admin");
|
||||||
|
entity.setAddTime(LocalDateTime.now());
|
||||||
|
personnelGroupInfoService.save(entity);
|
||||||
|
log.info("Inserted API test personnel group {}", DEFAULT_GROUP_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seedClerkLevel() {
|
||||||
|
PlayClerkLevelInfoEntity level = clerkLevelInfoService.getById(DEFAULT_CLERK_LEVEL_ID);
|
||||||
|
if (level != null) {
|
||||||
|
log.info("API test clerk level {} already exists", DEFAULT_CLERK_LEVEL_ID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayClerkLevelInfoEntity entity = new PlayClerkLevelInfoEntity();
|
||||||
|
entity.setId(DEFAULT_CLERK_LEVEL_ID);
|
||||||
|
entity.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
entity.setName("基础等级");
|
||||||
|
entity.setLevel(1);
|
||||||
|
entity.setFirstRegularRatio(60);
|
||||||
|
entity.setNotFirstRegularRatio(50);
|
||||||
|
entity.setFirstRandomRadio(55);
|
||||||
|
entity.setNotFirstRandomRadio(45);
|
||||||
|
entity.setFirstRewardRatio(40);
|
||||||
|
entity.setNotFirstRewardRatio(35);
|
||||||
|
entity.setOrderNumber(1L);
|
||||||
|
clerkLevelInfoService.save(entity);
|
||||||
|
log.info("Inserted API test clerk level {}", DEFAULT_CLERK_LEVEL_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayCommodityInfoEntity seedCommodityHierarchy() {
|
||||||
|
PlayCommodityInfoEntity parent = commodityInfoService.getById(DEFAULT_COMMODITY_PARENT_ID);
|
||||||
|
if (parent == null) {
|
||||||
|
parent = new PlayCommodityInfoEntity();
|
||||||
|
parent.setId(DEFAULT_COMMODITY_PARENT_ID);
|
||||||
|
parent.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
parent.setPId("00");
|
||||||
|
parent.setItemType("service-category");
|
||||||
|
parent.setItemName(DEFAULT_COMMODITY_PARENT_NAME);
|
||||||
|
parent.setEnableStace("1");
|
||||||
|
parent.setSort(1);
|
||||||
|
commodityInfoService.save(parent);
|
||||||
|
log.info("Inserted API test commodity parent {}", DEFAULT_COMMODITY_PARENT_ID);
|
||||||
|
} else {
|
||||||
|
boolean parentNeedsUpdate = false;
|
||||||
|
if (!"00".equals(parent.getPId())) {
|
||||||
|
parent.setPId("00");
|
||||||
|
parentNeedsUpdate = true;
|
||||||
|
}
|
||||||
|
if (!"service-category".equals(parent.getItemType())) {
|
||||||
|
parent.setItemType("service-category");
|
||||||
|
parentNeedsUpdate = true;
|
||||||
|
}
|
||||||
|
if (!DEFAULT_TENANT_ID.equals(parent.getTenantId())) {
|
||||||
|
parent.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
parentNeedsUpdate = true;
|
||||||
|
}
|
||||||
|
if (!"1".equals(parent.getEnableStace())) {
|
||||||
|
parent.setEnableStace("1");
|
||||||
|
parentNeedsUpdate = true;
|
||||||
|
}
|
||||||
|
if (parentNeedsUpdate) {
|
||||||
|
commodityInfoService.updateById(parent);
|
||||||
|
log.info("Normalized API test commodity parent {}", DEFAULT_COMMODITY_PARENT_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayCommodityInfoEntity child = commodityInfoService.getById(DEFAULT_COMMODITY_ID);
|
||||||
|
if (child != null) {
|
||||||
|
boolean childNeedsUpdate = false;
|
||||||
|
if (!DEFAULT_COMMODITY_PARENT_ID.equals(child.getPId())) {
|
||||||
|
child.setPId(DEFAULT_COMMODITY_PARENT_ID);
|
||||||
|
childNeedsUpdate = true;
|
||||||
|
}
|
||||||
|
if (!"service".equals(child.getItemType())) {
|
||||||
|
child.setItemType("service");
|
||||||
|
childNeedsUpdate = true;
|
||||||
|
}
|
||||||
|
if (!DEFAULT_TENANT_ID.equals(child.getTenantId())) {
|
||||||
|
child.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
childNeedsUpdate = true;
|
||||||
|
}
|
||||||
|
if (!"1".equals(child.getEnableStace())) {
|
||||||
|
child.setEnableStace("1");
|
||||||
|
childNeedsUpdate = true;
|
||||||
|
}
|
||||||
|
if (childNeedsUpdate) {
|
||||||
|
commodityInfoService.updateById(child);
|
||||||
|
log.info("Normalized API test commodity {}", DEFAULT_COMMODITY_ID);
|
||||||
|
}
|
||||||
|
log.info("API test commodity {} already exists", DEFAULT_COMMODITY_ID);
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
child = new PlayCommodityInfoEntity();
|
||||||
|
child.setId(DEFAULT_COMMODITY_ID);
|
||||||
|
child.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
child.setPId(DEFAULT_COMMODITY_PARENT_ID);
|
||||||
|
child.setItemType("service");
|
||||||
|
child.setItemName("60分钟语音陪聊");
|
||||||
|
child.setServiceDuration("60min");
|
||||||
|
child.setEnableStace("1");
|
||||||
|
child.setSort(1);
|
||||||
|
commodityInfoService.save(child);
|
||||||
|
log.info("Inserted API test commodity {}", DEFAULT_COMMODITY_ID);
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seedCommodityPricing(PlayCommodityInfoEntity commodity) {
|
||||||
|
if (commodity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PlayCommodityAndLevelInfoEntity existing = commodityAndLevelInfoService.lambdaQuery()
|
||||||
|
.eq(PlayCommodityAndLevelInfoEntity::getCommodityId, commodity.getId())
|
||||||
|
.eq(PlayCommodityAndLevelInfoEntity::getLevelId, DEFAULT_CLERK_LEVEL_ID)
|
||||||
|
.one();
|
||||||
|
if (existing != null) {
|
||||||
|
log.info("API test commodity pricing for {} already exists", commodity.getId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayCommodityAndLevelInfoEntity price = new PlayCommodityAndLevelInfoEntity();
|
||||||
|
price.setId(IdUtils.getUuid());
|
||||||
|
price.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
price.setCommodityId(commodity.getId());
|
||||||
|
price.setLevelId(DEFAULT_CLERK_LEVEL_ID);
|
||||||
|
price.setPrice(DEFAULT_COMMODITY_PRICE);
|
||||||
|
price.setSort(1L);
|
||||||
|
commodityAndLevelInfoService.save(price);
|
||||||
|
log.info("Inserted API test commodity pricing for {}", commodity.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seedClerk() {
|
||||||
|
PlayClerkUserInfoEntity clerk = clerkUserInfoService.getById(DEFAULT_CLERK_ID);
|
||||||
|
String clerkToken = wxTokenService.createWxUserToken(DEFAULT_CLERK_ID);
|
||||||
|
if (clerk != null) {
|
||||||
|
clerkUserInfoService.updateTokenById(DEFAULT_CLERK_ID, clerkToken);
|
||||||
|
log.info("API test clerk {} already exists", DEFAULT_CLERK_ID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
|
||||||
|
entity.setId(DEFAULT_CLERK_ID);
|
||||||
|
entity.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
entity.setSysUserId(DEFAULT_ADMIN_USER_ID);
|
||||||
|
entity.setOpenid(DEFAULT_CLERK_OPEN_ID);
|
||||||
|
entity.setNickname("小测官");
|
||||||
|
entity.setGroupId(DEFAULT_GROUP_ID);
|
||||||
|
entity.setLevelId(DEFAULT_CLERK_LEVEL_ID);
|
||||||
|
entity.setFixingLevel("1");
|
||||||
|
entity.setSex("2");
|
||||||
|
entity.setPhone("13900000001");
|
||||||
|
entity.setWeiChatCode("apitest-clerk");
|
||||||
|
entity.setAvatar("https://example.com/avatar.png");
|
||||||
|
entity.setAccountBalance(BigDecimal.ZERO);
|
||||||
|
entity.setOnboardingState("1");
|
||||||
|
entity.setListingState("1");
|
||||||
|
entity.setDisplayState("1");
|
||||||
|
entity.setOnlineState("1");
|
||||||
|
entity.setRandomOrderState("1");
|
||||||
|
entity.setClerkState("1");
|
||||||
|
entity.setEntryTime(LocalDateTime.now());
|
||||||
|
entity.setToken(clerkToken);
|
||||||
|
clerkUserInfoService.save(entity);
|
||||||
|
log.info("Inserted API test clerk {}", DEFAULT_CLERK_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seedClerkCommodity() {
|
||||||
|
PlayClerkCommodityEntity mapping = clerkCommodityService.getById(DEFAULT_CLERK_COMMODITY_ID);
|
||||||
|
if (mapping != null) {
|
||||||
|
log.info("API test clerk commodity {} already exists", DEFAULT_CLERK_COMMODITY_ID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String commodityName = DEFAULT_COMMODITY_PARENT_NAME;
|
||||||
|
PlayCommodityInfoEntity parent = commodityInfoService.getById(DEFAULT_COMMODITY_PARENT_ID);
|
||||||
|
if (parent != null && parent.getItemName() != null) {
|
||||||
|
commodityName = parent.getItemName();
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayClerkCommodityEntity entity = new PlayClerkCommodityEntity();
|
||||||
|
entity.setId(DEFAULT_CLERK_COMMODITY_ID);
|
||||||
|
entity.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
entity.setClerkId(DEFAULT_CLERK_ID);
|
||||||
|
entity.setCommodityId(DEFAULT_COMMODITY_ID);
|
||||||
|
entity.setCommodityName(commodityName);
|
||||||
|
entity.setEnablingState("1");
|
||||||
|
entity.setSort(1);
|
||||||
|
clerkCommodityService.save(entity);
|
||||||
|
log.info("Inserted API test clerk commodity link {}", DEFAULT_CLERK_COMMODITY_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seedGift() {
|
||||||
|
PlayGiftInfoEntity gift = giftInfoService.getById(DEFAULT_GIFT_ID);
|
||||||
|
if (gift != null) {
|
||||||
|
log.info("API test gift {} already exists", DEFAULT_GIFT_ID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayGiftInfoEntity entity = new PlayGiftInfoEntity();
|
||||||
|
entity.setId(DEFAULT_GIFT_ID);
|
||||||
|
entity.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
entity.setHistory("0");
|
||||||
|
entity.setName(DEFAULT_GIFT_NAME);
|
||||||
|
entity.setType(GIFT_TYPE_REGULAR);
|
||||||
|
entity.setUrl("https://example.com/apitest/gift.png");
|
||||||
|
entity.setPrice(new BigDecimal("15.00"));
|
||||||
|
entity.setUnit("CNY");
|
||||||
|
entity.setState(GIFT_STATE_ACTIVE);
|
||||||
|
entity.setListingTime(LocalDateTime.now());
|
||||||
|
entity.setRemark("Seeded gift for API tests");
|
||||||
|
giftInfoService.save(entity);
|
||||||
|
log.info("Inserted API test gift {}", DEFAULT_GIFT_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetGiftCounters() {
|
||||||
|
int customerReset = playCustomGiftInfoMapper.resetGiftCount(DEFAULT_TENANT_ID, DEFAULT_CUSTOMER_ID, DEFAULT_GIFT_ID);
|
||||||
|
if (customerReset == 0) {
|
||||||
|
PlayCustomGiftInfoEntity entity = new PlayCustomGiftInfoEntity();
|
||||||
|
entity.setId(IdUtils.getUuid());
|
||||||
|
entity.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
entity.setCustomId(DEFAULT_CUSTOMER_ID);
|
||||||
|
entity.setGiffId(DEFAULT_GIFT_ID);
|
||||||
|
entity.setGiffNumber(0L);
|
||||||
|
try {
|
||||||
|
playCustomGiftInfoService.save(entity);
|
||||||
|
} catch (org.springframework.dao.DuplicateKeyException duplicateKeyException) {
|
||||||
|
playCustomGiftInfoMapper.resetGiftCount(DEFAULT_TENANT_ID, DEFAULT_CUSTOMER_ID, DEFAULT_GIFT_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int clerkReset = playClerkGiftInfoMapper.resetGiftCount(DEFAULT_TENANT_ID, DEFAULT_CLERK_ID, DEFAULT_GIFT_ID);
|
||||||
|
if (clerkReset == 0) {
|
||||||
|
PlayClerkGiftInfoEntity entity = new PlayClerkGiftInfoEntity();
|
||||||
|
entity.setId(IdUtils.getUuid());
|
||||||
|
entity.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
entity.setClerkId(DEFAULT_CLERK_ID);
|
||||||
|
entity.setGiffId(DEFAULT_GIFT_ID);
|
||||||
|
entity.setGiffNumber(0L);
|
||||||
|
try {
|
||||||
|
playClerkGiftInfoService.save(entity);
|
||||||
|
} catch (org.springframework.dao.DuplicateKeyException duplicateKeyException) {
|
||||||
|
playClerkGiftInfoMapper.resetGiftCount(DEFAULT_TENANT_ID, DEFAULT_CLERK_ID, DEFAULT_GIFT_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seedCustomer() {
|
||||||
|
PlayCustomUserInfoEntity customer = customUserInfoService.getById(DEFAULT_CUSTOMER_ID);
|
||||||
|
String token = wxTokenService.createWxUserToken(DEFAULT_CUSTOMER_ID);
|
||||||
|
if (customer != null) {
|
||||||
|
customUserInfoService.updateTokenById(DEFAULT_CUSTOMER_ID, token);
|
||||||
|
customUserInfoService.lambdaUpdate()
|
||||||
|
.set(PlayCustomUserInfoEntity::getAccountBalance, DEFAULT_CUSTOMER_BALANCE)
|
||||||
|
.set(PlayCustomUserInfoEntity::getAccumulatedRechargeAmount, DEFAULT_CUSTOMER_RECHARGE)
|
||||||
|
.set(PlayCustomUserInfoEntity::getAccumulatedConsumptionAmount, BigDecimal.ZERO)
|
||||||
|
.set(PlayCustomUserInfoEntity::getAccountState, "1")
|
||||||
|
.set(PlayCustomUserInfoEntity::getSubscribeState, "1")
|
||||||
|
.set(PlayCustomUserInfoEntity::getPurchaseState, "1")
|
||||||
|
.set(PlayCustomUserInfoEntity::getMobilePhoneState, "1")
|
||||||
|
.set(PlayCustomUserInfoEntity::getLastLoginTime, new Date())
|
||||||
|
.eq(PlayCustomUserInfoEntity::getId, DEFAULT_CUSTOMER_ID)
|
||||||
|
.update();
|
||||||
|
log.info("API test customer {} already exists, state refreshed", DEFAULT_CUSTOMER_ID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayCustomUserInfoEntity entity = new PlayCustomUserInfoEntity();
|
||||||
|
entity.setId(DEFAULT_CUSTOMER_ID);
|
||||||
|
entity.setTenantId(DEFAULT_TENANT_ID);
|
||||||
|
entity.setOpenid("openid-customer-apitest");
|
||||||
|
entity.setUnionid("unionid-customer-apitest");
|
||||||
|
entity.setNickname("测试顾客");
|
||||||
|
entity.setSex(1);
|
||||||
|
entity.setPhone("13700000002");
|
||||||
|
entity.setWeiChatCode("apitest-customer");
|
||||||
|
entity.setAccountBalance(DEFAULT_CUSTOMER_BALANCE);
|
||||||
|
entity.setAccumulatedRechargeAmount(DEFAULT_CUSTOMER_RECHARGE);
|
||||||
|
entity.setAccumulatedConsumptionAmount(BigDecimal.ZERO);
|
||||||
|
entity.setAccountState("1");
|
||||||
|
entity.setSubscribeState("1");
|
||||||
|
entity.setPurchaseState("1");
|
||||||
|
entity.setMobilePhoneState("1");
|
||||||
|
entity.setRegistrationTime(new Date());
|
||||||
|
entity.setLastLoginTime(new Date());
|
||||||
|
entity.setToken(token);
|
||||||
|
customUserInfoService.save(entity);
|
||||||
|
log.info("Inserted API test customer {}", DEFAULT_CUSTOMER_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.starry.admin.common.conf;
|
|
||||||
|
|
||||||
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Primary;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class DataSourceConfig {
|
|
||||||
|
|
||||||
// For flyway only
|
|
||||||
@Bean(name = "primaryDataSource")
|
|
||||||
@Primary
|
|
||||||
public DataSource dataSource() {
|
|
||||||
return DruidDataSourceBuilder.create().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -32,8 +32,10 @@ public class GlobalExceptionHandler {
|
|||||||
public R handleServiceException(ServiceException e, HttpServletRequest request) {
|
public R handleServiceException(ServiceException e, HttpServletRequest request) {
|
||||||
if ("token异常".equals(e.getMessage()) || "token为空".equals(e.getMessage())) {
|
if ("token异常".equals(e.getMessage()) || "token为空".equals(e.getMessage())) {
|
||||||
log.error("用户token异常");
|
log.error("用户token异常");
|
||||||
|
} else if (log.isDebugEnabled()) {
|
||||||
|
log.debug("业务异常", e);
|
||||||
} else {
|
} else {
|
||||||
log.error(e.getMessage(), e);
|
log.warn("业务异常: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
Integer code = e.getCode();
|
Integer code = e.getCode();
|
||||||
return StringUtils.isNotNull(code) ? R.error(code, e.getMessage()) : R.error(e.getMessage());
|
return StringUtils.isNotNull(code) ? R.error(code, e.getMessage()) : R.error(e.getMessage());
|
||||||
@@ -111,10 +113,11 @@ public class GlobalExceptionHandler {
|
|||||||
public R customException(CustomException e) {
|
public R customException(CustomException e) {
|
||||||
if ("token异常".equals(e.getMessage()) || "token为空".equals(e.getMessage())) {
|
if ("token异常".equals(e.getMessage()) || "token为空".equals(e.getMessage())) {
|
||||||
log.error("用户token异常");
|
log.error("用户token异常");
|
||||||
|
} else if (log.isDebugEnabled()) {
|
||||||
|
log.debug("业务异常", e);
|
||||||
} else {
|
} else {
|
||||||
log.error(e.getMessage(), e);
|
log.warn("业务异常: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return R.error(e.getMessage());
|
return R.error(e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerIntercept
|
|||||||
import com.starry.admin.common.mybatis.handler.MyTenantLineHandler;
|
import com.starry.admin.common.mybatis.handler.MyTenantLineHandler;
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
import org.mybatis.spring.annotation.MapperScan;
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.autoconfigure.flyway.FlywayDataSource;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
@@ -30,6 +32,8 @@ public class MybatisPlusConfig {
|
|||||||
* @return dataSource
|
* @return dataSource
|
||||||
*/
|
*/
|
||||||
@Bean(name = "dataSource")
|
@Bean(name = "dataSource")
|
||||||
|
@Primary
|
||||||
|
@FlywayDataSource
|
||||||
@ConfigurationProperties(prefix = "spring.datasource.druid")
|
@ConfigurationProperties(prefix = "spring.datasource.druid")
|
||||||
public DataSource dataSource() {
|
public DataSource dataSource() {
|
||||||
return DruidDataSourceBuilder.create().build();
|
return DruidDataSourceBuilder.create().build();
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.starry.admin.common.security.config;
|
||||||
|
|
||||||
|
import com.starry.admin.common.security.filter.ApiTestAuthenticationFilter;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
@Profile("apitest")
|
||||||
|
@EnableConfigurationProperties(ApiTestSecurityProperties.class)
|
||||||
|
public class ApiTestSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
private final ApiTestSecurityProperties properties;
|
||||||
|
|
||||||
|
public ApiTestSecurityConfig(ApiTestSecurityProperties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ApiTestAuthenticationFilter apiTestAuthenticationFilter() {
|
||||||
|
return new ApiTestAuthenticationFilter(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
http.csrf().disable()
|
||||||
|
.formLogin().disable()
|
||||||
|
.logout().disable()
|
||||||
|
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||||
|
.authorizeRequests().anyRequest().authenticated().and()
|
||||||
|
.addFilterBefore(apiTestAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.starry.admin.common.security.config;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "apitest.security")
|
||||||
|
public class ApiTestSecurityProperties {
|
||||||
|
|
||||||
|
private String tenantHeader = "X-Tenant";
|
||||||
|
private String userHeader = "X-Test-User";
|
||||||
|
private final Defaults defaults = new Defaults();
|
||||||
|
|
||||||
|
public String getTenantHeader() {
|
||||||
|
return tenantHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantHeader(String tenantHeader) {
|
||||||
|
this.tenantHeader = tenantHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserHeader() {
|
||||||
|
return userHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserHeader(String userHeader) {
|
||||||
|
this.userHeader = userHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Defaults getDefaults() {
|
||||||
|
return defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Defaults {
|
||||||
|
|
||||||
|
private String tenantId = "tenant-apitest";
|
||||||
|
private String userId = "apitest-user";
|
||||||
|
private List<String> roles = new ArrayList<>();
|
||||||
|
private List<String> permissions = new ArrayList<>();
|
||||||
|
|
||||||
|
public String getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(String tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(String userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getRoles() {
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoles(List<String> roles) {
|
||||||
|
this.roles = roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getPermissions() {
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPermissions(List<String> permissions) {
|
||||||
|
this.permissions = permissions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import java.util.Set;
|
|||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
@@ -31,6 +32,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
|||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
@Profile("!apitest")
|
||||||
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.starry.admin.common.security.filter;
|
||||||
|
|
||||||
|
import com.starry.admin.common.domain.LoginUser;
|
||||||
|
import com.starry.admin.common.security.config.ApiTestSecurityProperties;
|
||||||
|
import com.starry.admin.modules.system.module.entity.SysUserEntity;
|
||||||
|
import com.starry.common.constant.SecurityConstants;
|
||||||
|
import com.starry.common.context.CustomSecurityContextHolder;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
public class ApiTestAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private final ApiTestSecurityProperties properties;
|
||||||
|
|
||||||
|
public ApiTestAuthenticationFilter(ApiTestSecurityProperties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
String requestedUser = request.getHeader(properties.getUserHeader());
|
||||||
|
String requestedTenant = request.getHeader(properties.getTenantHeader());
|
||||||
|
|
||||||
|
String userId = StringUtils.hasText(requestedUser) ? requestedUser : properties.getDefaults().getUserId();
|
||||||
|
String tenantId = StringUtils.hasText(requestedTenant) ? requestedTenant : properties.getDefaults().getTenantId();
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(userId) || !StringUtils.hasText(tenantId)) {
|
||||||
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
|
response.setContentType("application/json");
|
||||||
|
response.getWriter().write("{\"code\":401,\"message\":\"Missing test user or tenant header\"}");
|
||||||
|
response.getWriter().flush();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
LoginUser loginUser = buildLoginUser(userId, tenantId);
|
||||||
|
|
||||||
|
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null,
|
||||||
|
Collections.emptyList());
|
||||||
|
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
|
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
CustomSecurityContextHolder.set(SecurityConstants.DETAILS_USER_ID, userId);
|
||||||
|
CustomSecurityContextHolder.set(SecurityConstants.DETAILS_USERNAME, userId);
|
||||||
|
CustomSecurityContextHolder.setTenantId(tenantId);
|
||||||
|
CustomSecurityContextHolder.setPermission(String.join(",", loginUser.getPermissions()));
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
} finally {
|
||||||
|
CustomSecurityContextHolder.remove();
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LoginUser buildLoginUser(String userId, String tenantId) {
|
||||||
|
SysUserEntity sysUser = new SysUserEntity();
|
||||||
|
sysUser.setUserId(userId);
|
||||||
|
sysUser.setUserCode(userId);
|
||||||
|
sysUser.setRealName(userId);
|
||||||
|
sysUser.setTenantId(tenantId);
|
||||||
|
sysUser.setSuperAdmin(Boolean.FALSE);
|
||||||
|
sysUser.setStatus(0);
|
||||||
|
|
||||||
|
LoginUser loginUser = new LoginUser();
|
||||||
|
loginUser.setUser(sysUser);
|
||||||
|
loginUser.setUserId(userId);
|
||||||
|
loginUser.setUserName(userId);
|
||||||
|
loginUser.setToken("apitest-" + userId + "-" + tenantId);
|
||||||
|
loginUser.setLoginTime(System.currentTimeMillis());
|
||||||
|
loginUser.setExpireTime(System.currentTimeMillis() + 3600_000);
|
||||||
|
loginUser.setTenantEndDate(new Date(System.currentTimeMillis() + 3600_000));
|
||||||
|
loginUser.setTenantStatus(0);
|
||||||
|
|
||||||
|
Set<String> roles = new HashSet<>(properties.getDefaults().getRoles());
|
||||||
|
Set<String> permissions = new HashSet<>(properties.getDefaults().getPermissions());
|
||||||
|
loginUser.setRoles(roles);
|
||||||
|
loginUser.setPermissions(permissions);
|
||||||
|
loginUser.setCurrentRole(roles.stream().findFirst().orElse(null));
|
||||||
|
|
||||||
|
return loginUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.starry.admin.modules.blindbox.config;
|
||||||
|
|
||||||
|
import java.time.Clock;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class BlindBoxConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Clock systemClock() {
|
||||||
|
return Clock.systemDefaultZone();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -162,7 +162,7 @@ public class BlindBoxPoolAdminService {
|
|||||||
if (!tenantId.equals(config.getTenantId())) {
|
if (!tenantId.equals(config.getTenantId())) {
|
||||||
throw new CustomException("盲盒不存在或已被移除");
|
throw new CustomException("盲盒不存在或已被移除");
|
||||||
}
|
}
|
||||||
PlayGiftInfoEntity rewardGift = requireRewardGift(tenantId, request.getRewardGiftId());
|
PlayGiftInfoEntity rewardGift = requireRewardGiftForUpdate(tenantId, request.getRewardGiftId());
|
||||||
validateTimeRange(request.getValidFrom(), request.getValidTo());
|
validateTimeRange(request.getValidFrom(), request.getValidTo());
|
||||||
Integer weight = requirePositiveWeight(request.getWeight(), rewardGift.getName());
|
Integer weight = requirePositiveWeight(request.getWeight(), rewardGift.getName());
|
||||||
Integer remainingStock = normalizeRemainingStock(request.getRemainingStock(), rewardGift.getName());
|
Integer remainingStock = normalizeRemainingStock(request.getRemainingStock(), rewardGift.getName());
|
||||||
@@ -326,18 +326,30 @@ public class BlindBoxPoolAdminService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PlayGiftInfoEntity requireRewardGift(String tenantId, String rewardGiftId) {
|
private PlayGiftInfoEntity requireRewardGift(String tenantId, String rewardGiftId) {
|
||||||
|
return requireRewardGift(tenantId, rewardGiftId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayGiftInfoEntity requireRewardGiftForUpdate(String tenantId, String rewardGiftId) {
|
||||||
|
return requireRewardGift(tenantId, rewardGiftId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayGiftInfoEntity requireRewardGift(String tenantId, String rewardGiftId, boolean strictAvailability) {
|
||||||
if (StrUtil.isBlank(rewardGiftId)) {
|
if (StrUtil.isBlank(rewardGiftId)) {
|
||||||
throw new CustomException("请选择中奖礼物");
|
throw new CustomException("请选择中奖礼物");
|
||||||
}
|
}
|
||||||
PlayGiftInfoEntity gift = playGiftInfoMapper.selectById(rewardGiftId);
|
PlayGiftInfoEntity gift = playGiftInfoMapper.selectById(rewardGiftId);
|
||||||
if (gift == null
|
if (gift == null
|
||||||
|| !tenantId.equals(gift.getTenantId())
|
|| !tenantId.equals(gift.getTenantId())
|
||||||
|| !GiftHistory.CURRENT.getCode().equals(gift.getHistory())
|
|
||||||
|| !GiftState.ACTIVE.getCode().equals(gift.getState())
|
|
||||||
|| !GiftType.NORMAL.getCode().equals(gift.getType())
|
|| !GiftType.NORMAL.getCode().equals(gift.getType())
|
||||||
|| Boolean.TRUE.equals(gift.getDeleted())) {
|
|| Boolean.TRUE.equals(gift.getDeleted())) {
|
||||||
throw new CustomException("中奖礼物不存在或已下架");
|
throw new CustomException("中奖礼物不存在或已下架");
|
||||||
}
|
}
|
||||||
|
if (strictAvailability) {
|
||||||
|
if (!GiftHistory.CURRENT.getCode().equals(gift.getHistory())
|
||||||
|
|| !GiftState.ACTIVE.getCode().equals(gift.getState())) {
|
||||||
|
throw new CustomException("中奖礼物不存在或已下架");
|
||||||
|
}
|
||||||
|
}
|
||||||
return gift;
|
return gift;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.starry.admin.modules.clerk.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum ClerkMediaReviewState {
|
||||||
|
|
||||||
|
DRAFT("draft"),
|
||||||
|
PENDING("pending"),
|
||||||
|
APPROVED("approved"),
|
||||||
|
REJECTED("rejected");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
ClerkMediaReviewState(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClerkMediaReviewState fromCode(String code) {
|
||||||
|
if (code == null || code.isEmpty()) {
|
||||||
|
return DRAFT;
|
||||||
|
}
|
||||||
|
for (ClerkMediaReviewState state : values()) {
|
||||||
|
if (state.code.equalsIgnoreCase(code) || state.name().equalsIgnoreCase(code)) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DRAFT;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.starry.admin.modules.clerk.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum ClerkMediaUsage {
|
||||||
|
|
||||||
|
PROFILE("profile"),
|
||||||
|
AVATAR("avatar"),
|
||||||
|
MOMENTS("moments"),
|
||||||
|
VOICE_INTRO("voice_intro"),
|
||||||
|
PROMO("promo"),
|
||||||
|
OTHER("other");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
ClerkMediaUsage(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClerkMediaUsage fromCode(String code) {
|
||||||
|
if (code == null || code.isEmpty()) {
|
||||||
|
return PROFILE;
|
||||||
|
}
|
||||||
|
for (ClerkMediaUsage usage : values()) {
|
||||||
|
if (usage.code.equalsIgnoreCase(code) || usage.name().equalsIgnoreCase(code)) {
|
||||||
|
return usage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PROFILE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.starry.admin.modules.clerk.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkMediaAssetEntity;
|
||||||
|
|
||||||
|
public interface PlayClerkMediaAssetMapper extends BaseMapper<PlayClerkMediaAssetEntity> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.starry.admin.modules.clerk.mapper;
|
package com.starry.admin.modules.clerk.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
|
||||||
import com.github.yulichang.base.MPJBaseMapper;
|
import com.github.yulichang.base.MPJBaseMapper;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 店员Mapper接口
|
* 店员Mapper接口
|
||||||
@@ -11,4 +14,7 @@ import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
|||||||
*/
|
*/
|
||||||
public interface PlayClerkUserInfoMapper extends MPJBaseMapper<PlayClerkUserInfoEntity> {
|
public interface PlayClerkUserInfoMapper extends MPJBaseMapper<PlayClerkUserInfoEntity> {
|
||||||
|
|
||||||
|
@InterceptorIgnore(tenantLine = "true")
|
||||||
|
@Select("SELECT id, tenant_id, album FROM play_clerk_user_info WHERE deleted = 0 AND album IS NOT NULL")
|
||||||
|
List<PlayClerkUserInfoEntity> selectAllWithAlbumIgnoringTenant();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.starry.admin.modules.clerk.module.entity;
|
package com.starry.admin.modules.clerk.module.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.starry.common.domain.BaseEntity;
|
import com.starry.common.domain.BaseEntity;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -69,4 +71,7 @@ public class PlayClerkLevelInfoEntity extends BaseEntity<PlayClerkLevelInfoEntit
|
|||||||
private Integer styleType;
|
private Integer styleType;
|
||||||
|
|
||||||
private String styleImageUrl;
|
private String styleImageUrl;
|
||||||
|
|
||||||
|
@TableField(updateStrategy = FieldStrategy.IGNORED)
|
||||||
|
private Long orderNumber;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.starry.admin.modules.clerk.module.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.starry.common.domain.BaseEntity;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@TableName(value = "play_clerk_media_asset")
|
||||||
|
public class PlayClerkMediaAssetEntity extends BaseEntity<PlayClerkMediaAssetEntity> {
|
||||||
|
|
||||||
|
@TableId
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private String clerkId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租戶 ID,供 TenantLine 過濾
|
||||||
|
*/
|
||||||
|
private String tenantId;
|
||||||
|
|
||||||
|
private String mediaId;
|
||||||
|
|
||||||
|
@TableField("`usage`")
|
||||||
|
private String usage;
|
||||||
|
|
||||||
|
private String reviewState;
|
||||||
|
|
||||||
|
private Integer orderIndex;
|
||||||
|
|
||||||
|
private LocalDateTime submittedTime;
|
||||||
|
|
||||||
|
private String reviewRecordId;
|
||||||
|
|
||||||
|
private String note;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.starry.admin.modules.clerk.module.entity;
|
|||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
|
import com.starry.admin.modules.weichat.entity.clerk.MediaVo;
|
||||||
import io.swagger.annotations.ApiModel;
|
import io.swagger.annotations.ApiModel;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -94,6 +95,12 @@ public class PlayClerkUserReturnVo {
|
|||||||
@ApiModelProperty(value = "相册列表", notes = "店员相册图片URL列表")
|
@ApiModelProperty(value = "相册列表", notes = "店员相册图片URL列表")
|
||||||
private List<String> album = new ArrayList<>();
|
private List<String> album = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 媒资列表
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "媒资列表", notes = "结构化媒资数据")
|
||||||
|
private List<MediaVo> mediaList = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 个性签名
|
* 个性签名
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -60,6 +60,15 @@ public class PlayClerkDataReviewReturnVo {
|
|||||||
@ApiModelProperty(value = "资料内容", example = "[\"https://example.com/photo1.jpg\"]", notes = "资料内容,根据资料类型有不同格式")
|
@ApiModelProperty(value = "资料内容", example = "[\"https://example.com/photo1.jpg\"]", notes = "资料内容,根据资料类型有不同格式")
|
||||||
private List<String> dataContent;
|
private List<String> dataContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 媒资对应的视频地址(仅当资料类型为头像/相册且为视频时有值,顺序与 dataContent 一一对应)
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(
|
||||||
|
value = "媒资视频地址列表",
|
||||||
|
example = "[\"https://example.com/video1.mp4\"]",
|
||||||
|
notes = "仅当资料类型为头像/相册且为视频时有值,顺序与 dataContent 一一对应")
|
||||||
|
private List<String> mediaVideoUrls;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 审核状态(0:未审核:1:审核通过,2:审核不通过)
|
* 审核状态(0:未审核:1:审核通过,2:审核不通过)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -55,4 +55,7 @@ public class PlayClerkLevelAddVo {
|
|||||||
@ApiModelProperty(value = "非首次随机单比例", example = "65", notes = "非首次随机单提成比例,范围0-100%")
|
@ApiModelProperty(value = "非首次随机单比例", example = "65", notes = "非首次随机单提成比例,范围0-100%")
|
||||||
private Integer notFirstRandomRadio;
|
private Integer notFirstRandomRadio;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "排序号", example = "1", notes = "越小的等级在列表越靠前")
|
||||||
|
private Long orderNumber;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,4 +68,6 @@ public class PlayClerkLevelEditVo {
|
|||||||
@ApiModelProperty(value = "样式图片URL", example = "https://example.com/style.jpg", notes = "等级样式图片URL")
|
@ApiModelProperty(value = "样式图片URL", example = "https://example.com/style.jpg", notes = "等级样式图片URL")
|
||||||
private String styleImageUrl;
|
private String styleImageUrl;
|
||||||
|
|
||||||
|
private Long orderNumber;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.starry.admin.modules.clerk.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.starry.admin.modules.clerk.enums.ClerkMediaReviewState;
|
||||||
|
import com.starry.admin.modules.clerk.enums.ClerkMediaUsage;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkMediaAssetEntity;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IPlayClerkMediaAssetService extends IService<PlayClerkMediaAssetEntity> {
|
||||||
|
|
||||||
|
PlayClerkMediaAssetEntity linkDraftAsset(String tenantId, String clerkId, String mediaId, ClerkMediaUsage usage);
|
||||||
|
|
||||||
|
void submitWithOrder(String clerkId, ClerkMediaUsage usage, List<String> mediaIds);
|
||||||
|
|
||||||
|
void reorder(String clerkId, ClerkMediaUsage usage, List<String> mediaIds);
|
||||||
|
|
||||||
|
void softDelete(String clerkId, String mediaId);
|
||||||
|
|
||||||
|
List<PlayClerkMediaAssetEntity> listByState(String clerkId, ClerkMediaUsage usage, Collection<ClerkMediaReviewState> states);
|
||||||
|
|
||||||
|
List<PlayClerkMediaAssetEntity> listActiveByUsage(String clerkId, ClerkMediaUsage usage);
|
||||||
|
|
||||||
|
void applyReviewDecision(String clerkId, ClerkMediaUsage usage, List<String> approvedValues, String reviewRecordId, String note);
|
||||||
|
}
|
||||||
@@ -190,6 +190,17 @@ public interface IPlayClerkUserInfoService extends IService<PlayClerkUserInfoEnt
|
|||||||
*/
|
*/
|
||||||
IPage<PlayClerkUserInfoResultVo> selectPlayClerkUserInfoByPage(PlayClerkUserInfoQueryVo vo);
|
IPage<PlayClerkUserInfoResultVo> selectPlayClerkUserInfoByPage(PlayClerkUserInfoQueryVo vo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建面向顾客的店员详情视图对象(包含媒资与兼容相册)。
|
||||||
|
*
|
||||||
|
* @param clerkId
|
||||||
|
* 店员ID
|
||||||
|
* @param customUserId
|
||||||
|
* 顾客ID(可为空,用于标记关注状态)
|
||||||
|
* @return 店员详情视图对象
|
||||||
|
*/
|
||||||
|
PlayClerkUserInfoResultVo buildCustomerDetail(String clerkId, String customUserId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 确认店员处于可用状态,否则抛出异常
|
* 确认店员处于可用状态,否则抛出异常
|
||||||
*
|
*
|
||||||
@@ -252,5 +263,12 @@ public interface IPlayClerkUserInfoService extends IService<PlayClerkUserInfoEnt
|
|||||||
|
|
||||||
List<PlayClerkUserInfoEntity> simpleList();
|
List<PlayClerkUserInfoEntity> simpleList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询存在相册字段数据的店员(忽略租户隔离)
|
||||||
|
*
|
||||||
|
* @return 店员集合
|
||||||
|
*/
|
||||||
|
List<PlayClerkUserInfoEntity> listWithAlbumIgnoringTenant();
|
||||||
|
|
||||||
JSONObject getPcData(PlayClerkUserInfoEntity entity);
|
JSONObject getPcData(PlayClerkUserInfoEntity entity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.starry.admin.modules.clerk.service.impl;
|
package com.starry.admin.modules.clerk.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
@@ -7,20 +8,33 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||||
import com.starry.admin.common.exception.CustomException;
|
import com.starry.admin.common.exception.CustomException;
|
||||||
|
import com.starry.admin.modules.clerk.enums.ClerkMediaUsage;
|
||||||
import com.starry.admin.modules.clerk.mapper.PlayClerkDataReviewInfoMapper;
|
import com.starry.admin.modules.clerk.mapper.PlayClerkDataReviewInfoMapper;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReviewInfoEntity;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReviewInfoEntity;
|
||||||
|
import com.starry.admin.modules.clerk.module.enums.ClerkDataType;
|
||||||
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewQueryVo;
|
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewQueryVo;
|
||||||
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewReturnVo;
|
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewReturnVo;
|
||||||
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewStateEditVo;
|
import com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewStateEditVo;
|
||||||
import com.starry.admin.modules.clerk.service.IPlayClerkDataReviewInfoService;
|
import com.starry.admin.modules.clerk.service.IPlayClerkDataReviewInfoService;
|
||||||
|
import com.starry.admin.modules.clerk.service.IPlayClerkMediaAssetService;
|
||||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||||
|
import com.starry.admin.modules.media.entity.PlayMediaEntity;
|
||||||
|
import com.starry.admin.modules.media.enums.MediaKind;
|
||||||
|
import com.starry.admin.modules.media.enums.MediaOwnerType;
|
||||||
|
import com.starry.admin.modules.media.enums.MediaStatus;
|
||||||
|
import com.starry.admin.modules.media.service.IPlayMediaService;
|
||||||
import com.starry.common.enums.ClerkReviewState;
|
import com.starry.common.enums.ClerkReviewState;
|
||||||
import com.starry.common.utils.IdUtils;
|
import com.starry.common.utils.IdUtils;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -42,6 +56,12 @@ public class PlayClerkDataReviewInfoServiceImpl
|
|||||||
@Resource
|
@Resource
|
||||||
private IPlayClerkUserInfoService playClerkUserInfoService;
|
private IPlayClerkUserInfoService playClerkUserInfoService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IPlayClerkMediaAssetService clerkMediaAssetService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IPlayMediaService mediaService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询店员资料审核
|
* 查询店员资料审核
|
||||||
*
|
*
|
||||||
@@ -107,8 +127,11 @@ public class PlayClerkDataReviewInfoServiceImpl
|
|||||||
lambdaQueryWrapper.between(PlayClerkDataReviewInfoEntity::getAddTime, vo.getAddTime().get(0),
|
lambdaQueryWrapper.between(PlayClerkDataReviewInfoEntity::getAddTime, vo.getAddTime().get(0),
|
||||||
vo.getAddTime().get(1));
|
vo.getAddTime().get(1));
|
||||||
}
|
}
|
||||||
return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
|
IPage<PlayClerkDataReviewReturnVo> page = this.baseMapper.selectJoinPage(
|
||||||
PlayClerkDataReviewReturnVo.class, lambdaQueryWrapper);
|
new Page<>(vo.getPageNum(), vo.getPageSize()), PlayClerkDataReviewReturnVo.class,
|
||||||
|
lambdaQueryWrapper);
|
||||||
|
enrichDataContentWithMediaPreview(page);
|
||||||
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,6 +152,72 @@ public class PlayClerkDataReviewInfoServiceImpl
|
|||||||
return save(playClerkDataReviewInfo);
|
return save(playClerkDataReviewInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为头像 / 相册审核记录补充可预览的 URL。
|
||||||
|
*
|
||||||
|
* <p>dataContent 中现在可能是媒资 ID(mediaId)或历史 URL,这里做一次向前兼容:
|
||||||
|
* <ul>
|
||||||
|
* <li>如果是 mediaId,则解析到 play_media 记录,并返回封面或原始 URL;</li>
|
||||||
|
* <li>如果查不到媒资,则保留原值。</li>
|
||||||
|
* </ul>
|
||||||
|
* 这样 PC 端审核页面始终可以正确预览图片/视频。</p>
|
||||||
|
*/
|
||||||
|
private void enrichDataContentWithMediaPreview(IPage<PlayClerkDataReviewReturnVo> page) {
|
||||||
|
if (page == null || page.getRecords() == null || page.getRecords().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (PlayClerkDataReviewReturnVo row : page.getRecords()) {
|
||||||
|
ClerkDataType type = row.getDataTypeEnum();
|
||||||
|
if (type == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (type == ClerkDataType.AVATAR || type == ClerkDataType.PHOTO_ALBUM) {
|
||||||
|
List<String> content = row.getDataContent();
|
||||||
|
if (CollectionUtil.isEmpty(content)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<String> previewUrls = new ArrayList<>();
|
||||||
|
List<String> videoUrls = new ArrayList<>();
|
||||||
|
for (String value : content) {
|
||||||
|
if (StrUtil.isBlank(value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
MediaPreviewPair pair = resolvePreviewPair(value);
|
||||||
|
if (pair == null || StrUtil.isBlank(pair.getPreviewUrl())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
previewUrls.add(pair.getPreviewUrl());
|
||||||
|
videoUrls.add(pair.getVideoUrl());
|
||||||
|
}
|
||||||
|
row.setDataContent(previewUrls);
|
||||||
|
row.setMediaVideoUrls(videoUrls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaPreviewPair resolvePreviewPair(String value) {
|
||||||
|
if (StrUtil.isBlank(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
PlayMediaEntity media = mediaService.getById(value);
|
||||||
|
if (media == null) {
|
||||||
|
MediaPreviewPair fallback = new MediaPreviewPair();
|
||||||
|
fallback.setPreviewUrl(value);
|
||||||
|
fallback.setVideoUrl(null);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
MediaPreviewPair pair = new MediaPreviewPair();
|
||||||
|
if (MediaKind.VIDEO.getCode().equals(media.getKind())) {
|
||||||
|
String coverUrl = StrUtil.isNotBlank(media.getCoverUrl()) ? media.getCoverUrl() : media.getUrl();
|
||||||
|
pair.setPreviewUrl(coverUrl);
|
||||||
|
pair.setVideoUrl(media.getUrl());
|
||||||
|
} else {
|
||||||
|
pair.setPreviewUrl(media.getUrl());
|
||||||
|
pair.setVideoUrl(null);
|
||||||
|
}
|
||||||
|
return pair;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateDataReviewState(PlayClerkDataReviewStateEditVo vo) {
|
public void updateDataReviewState(PlayClerkDataReviewStateEditVo vo) {
|
||||||
PlayClerkDataReviewInfoEntity entity = this.selectPlayClerkDataReviewInfoById(vo.getId());
|
PlayClerkDataReviewInfoEntity entity = this.selectPlayClerkDataReviewInfoById(vo.getId());
|
||||||
@@ -147,7 +236,8 @@ public class PlayClerkDataReviewInfoServiceImpl
|
|||||||
userInfo.setAvatar(entity.getDataContent().get(0));
|
userInfo.setAvatar(entity.getDataContent().get(0));
|
||||||
}
|
}
|
||||||
if ("2".equals(entity.getDataType())) {
|
if ("2".equals(entity.getDataType())) {
|
||||||
userInfo.setAlbum(entity.getDataContent());
|
userInfo.setAlbum(new ArrayList<>());
|
||||||
|
synchronizeApprovedAlbumMedia(entity);
|
||||||
}
|
}
|
||||||
if ("3".equals(entity.getDataType())) {
|
if ("3".equals(entity.getDataType())) {
|
||||||
userInfo.setAudio(entity.getDataContent().get(0));
|
userInfo.setAudio(entity.getDataContent().get(0));
|
||||||
@@ -159,6 +249,71 @@ public class PlayClerkDataReviewInfoServiceImpl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void synchronizeApprovedAlbumMedia(PlayClerkDataReviewInfoEntity reviewInfo) {
|
||||||
|
PlayClerkUserInfoEntity clerkInfo = playClerkUserInfoService.getById(reviewInfo.getClerkId());
|
||||||
|
if (clerkInfo == null) {
|
||||||
|
throw new CustomException("店员信息不存在,无法同步媒资");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> rawContent = reviewInfo.getDataContent();
|
||||||
|
List<String> sanitized = CollectionUtil.isEmpty(rawContent)
|
||||||
|
? Collections.emptyList()
|
||||||
|
: rawContent.stream().filter(StrUtil::isNotBlank).map(String::trim).distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<String> resolvedMediaIds = new ArrayList<>();
|
||||||
|
for (String value : sanitized) {
|
||||||
|
PlayMediaEntity media = resolveMediaEntity(clerkInfo, value);
|
||||||
|
if (media == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
clerkMediaAssetService.linkDraftAsset(clerkInfo.getTenantId(), clerkInfo.getId(), media.getId(),
|
||||||
|
ClerkMediaUsage.PROFILE);
|
||||||
|
resolvedMediaIds.add(media.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
clerkMediaAssetService.applyReviewDecision(clerkInfo.getId(), ClerkMediaUsage.PROFILE, resolvedMediaIds,
|
||||||
|
reviewInfo.getId(), reviewInfo.getReviewCon());
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayMediaEntity resolveMediaEntity(PlayClerkUserInfoEntity clerkInfo, String value) {
|
||||||
|
if (StrUtil.isBlank(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
PlayMediaEntity media = mediaService.getById(value);
|
||||||
|
if (media != null) {
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
media = mediaService.lambdaQuery()
|
||||||
|
.eq(PlayMediaEntity::getOwnerType, MediaOwnerType.CLERK)
|
||||||
|
.eq(PlayMediaEntity::getOwnerId, clerkInfo.getId())
|
||||||
|
.eq(PlayMediaEntity::getUrl, value)
|
||||||
|
.last("limit 1")
|
||||||
|
.one();
|
||||||
|
if (media != null) {
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
return createMediaFromLegacyUrl(clerkInfo, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayMediaEntity createMediaFromLegacyUrl(PlayClerkUserInfoEntity clerkInfo, String url) {
|
||||||
|
PlayMediaEntity media = new PlayMediaEntity();
|
||||||
|
media.setId(IdUtils.getUuid());
|
||||||
|
media.setTenantId(clerkInfo.getTenantId());
|
||||||
|
media.setOwnerType(MediaOwnerType.CLERK);
|
||||||
|
media.setOwnerId(clerkInfo.getId());
|
||||||
|
media.setKind(MediaKind.IMAGE.getCode());
|
||||||
|
media.setStatus(MediaStatus.READY.getCode());
|
||||||
|
media.setUrl(url);
|
||||||
|
Map<String, Object> metadata = new HashMap<>();
|
||||||
|
metadata.put("legacySource", "album_review");
|
||||||
|
media.setMetadata(metadata);
|
||||||
|
mediaService.normalizeAndSave(media);
|
||||||
|
media.setStatus(MediaStatus.READY.getCode());
|
||||||
|
mediaService.updateById(media);
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改店员资料审核
|
* 修改店员资料审核
|
||||||
*
|
*
|
||||||
@@ -194,4 +349,28 @@ public class PlayClerkDataReviewInfoServiceImpl
|
|||||||
public int deletePlayClerkDataReviewInfoById(String id) {
|
public int deletePlayClerkDataReviewInfoById(String id) {
|
||||||
return playClerkDataReviewInfoMapper.deleteById(id);
|
return playClerkDataReviewInfoMapper.deleteById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单的预览地址 / 视频地址对,避免在主体逻辑中使用 Map 或魔法下标。
|
||||||
|
*/
|
||||||
|
private static class MediaPreviewPair {
|
||||||
|
private String previewUrl;
|
||||||
|
private String videoUrl;
|
||||||
|
|
||||||
|
String getPreviewUrl() {
|
||||||
|
return previewUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPreviewUrl(String previewUrl) {
|
||||||
|
this.previewUrl = previewUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getVideoUrl() {
|
||||||
|
return videoUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setVideoUrl(String videoUrl) {
|
||||||
|
this.videoUrl = videoUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public class PlayClerkLevelInfoServiceImpl extends ServiceImpl<PlayClerkLevelInf
|
|||||||
entity.setFirstRegularRatio(45);
|
entity.setFirstRegularRatio(45);
|
||||||
entity.setNotFirstRegularRatio(50);
|
entity.setNotFirstRegularRatio(50);
|
||||||
entity.setLevel(1);
|
entity.setLevel(1);
|
||||||
|
entity.setOrderNumber(1L);
|
||||||
entity.setStyleType(entity.getLevel());
|
entity.setStyleType(entity.getLevel());
|
||||||
entity.setTenantId(sysTenantEntity.getTenantId());
|
entity.setTenantId(sysTenantEntity.getTenantId());
|
||||||
this.baseMapper.insert(entity);
|
this.baseMapper.insert(entity);
|
||||||
@@ -64,6 +65,7 @@ public class PlayClerkLevelInfoServiceImpl extends ServiceImpl<PlayClerkLevelInf
|
|||||||
entity.setFirstRegularRatio(45);
|
entity.setFirstRegularRatio(45);
|
||||||
entity.setNotFirstRegularRatio(50);
|
entity.setNotFirstRegularRatio(50);
|
||||||
entity.setLevel(1);
|
entity.setLevel(1);
|
||||||
|
entity.setOrderNumber(1L);
|
||||||
entity.setStyleType(1);
|
entity.setStyleType(1);
|
||||||
this.baseMapper.insert(entity);
|
this.baseMapper.insert(entity);
|
||||||
return entity;
|
return entity;
|
||||||
@@ -116,6 +118,9 @@ public class PlayClerkLevelInfoServiceImpl extends ServiceImpl<PlayClerkLevelInf
|
|||||||
}
|
}
|
||||||
playClerkLevelInfo.setCreatedTime(new Date());
|
playClerkLevelInfo.setCreatedTime(new Date());
|
||||||
playClerkLevelInfo.setStyleType(playClerkLevelInfo.getLevel());
|
playClerkLevelInfo.setStyleType(playClerkLevelInfo.getLevel());
|
||||||
|
if (playClerkLevelInfo.getOrderNumber() == null) {
|
||||||
|
playClerkLevelInfo.setOrderNumber(playClerkLevelInfo.getLevel().longValue());
|
||||||
|
}
|
||||||
return save(playClerkLevelInfo);
|
return save(playClerkLevelInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,280 @@
|
|||||||
|
package com.starry.admin.modules.clerk.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.starry.admin.common.exception.CustomException;
|
||||||
|
import com.starry.admin.modules.clerk.enums.ClerkMediaReviewState;
|
||||||
|
import com.starry.admin.modules.clerk.enums.ClerkMediaUsage;
|
||||||
|
import com.starry.admin.modules.clerk.mapper.PlayClerkMediaAssetMapper;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkMediaAssetEntity;
|
||||||
|
import com.starry.admin.modules.clerk.service.IPlayClerkMediaAssetService;
|
||||||
|
import com.starry.admin.modules.media.entity.PlayMediaEntity;
|
||||||
|
import com.starry.admin.modules.media.service.IPlayMediaService;
|
||||||
|
import com.starry.common.utils.IdUtils;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PlayClerkMediaAssetServiceImpl extends ServiceImpl<PlayClerkMediaAssetMapper, PlayClerkMediaAssetEntity>
|
||||||
|
implements IPlayClerkMediaAssetService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IPlayMediaService mediaService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public PlayClerkMediaAssetEntity linkDraftAsset(String tenantId, String clerkId, String mediaId,
|
||||||
|
ClerkMediaUsage usage) {
|
||||||
|
LambdaQueryWrapper<PlayClerkMediaAssetEntity> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(PlayClerkMediaAssetEntity::getClerkId, clerkId)
|
||||||
|
.eq(StrUtil.isNotBlank(tenantId), PlayClerkMediaAssetEntity::getTenantId, tenantId)
|
||||||
|
.eq(PlayClerkMediaAssetEntity::getUsage, usage.getCode())
|
||||||
|
.eq(PlayClerkMediaAssetEntity::getMediaId, mediaId);
|
||||||
|
PlayClerkMediaAssetEntity existing = this.getOne(wrapper, false);
|
||||||
|
if (existing != null) {
|
||||||
|
if (StrUtil.isBlank(existing.getTenantId()) && StrUtil.isNotBlank(tenantId)) {
|
||||||
|
existing.setTenantId(tenantId);
|
||||||
|
}
|
||||||
|
if (Boolean.TRUE.equals(existing.getDeleted())) {
|
||||||
|
existing.setDeleted(false);
|
||||||
|
}
|
||||||
|
existing.setReviewState(ClerkMediaReviewState.DRAFT.getCode());
|
||||||
|
if (existing.getOrderIndex() == null) {
|
||||||
|
existing.setOrderIndex(resolveNextOrderIndex(clerkId, usage));
|
||||||
|
}
|
||||||
|
this.updateById(existing);
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayClerkMediaAssetEntity entity = new PlayClerkMediaAssetEntity();
|
||||||
|
entity.setId(IdUtils.getUuid());
|
||||||
|
entity.setClerkId(clerkId);
|
||||||
|
entity.setTenantId(tenantId);
|
||||||
|
entity.setMediaId(mediaId);
|
||||||
|
entity.setUsage(usage.getCode());
|
||||||
|
entity.setReviewState(ClerkMediaReviewState.DRAFT.getCode());
|
||||||
|
entity.setOrderIndex(resolveNextOrderIndex(clerkId, usage));
|
||||||
|
entity.setDeleted(false);
|
||||||
|
this.save(entity);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void submitWithOrder(String clerkId, ClerkMediaUsage usage, List<String> mediaIds) {
|
||||||
|
List<String> ordered = distinctMediaIds(mediaIds);
|
||||||
|
List<PlayClerkMediaAssetEntity> assets = listActiveByUsage(clerkId, usage);
|
||||||
|
if (CollectionUtil.isEmpty(assets)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, PlayClerkMediaAssetEntity> assetsByMediaId = assets.stream()
|
||||||
|
.collect(Collectors.toMap(PlayClerkMediaAssetEntity::getMediaId, item -> item));
|
||||||
|
List<PlayClerkMediaAssetEntity> updates = new ArrayList<>();
|
||||||
|
int order = 0;
|
||||||
|
for (String mediaId : ordered) {
|
||||||
|
PlayClerkMediaAssetEntity asset = assetsByMediaId.get(mediaId);
|
||||||
|
if (asset == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
asset.setOrderIndex(order++);
|
||||||
|
asset.setReviewState(ClerkMediaReviewState.PENDING.getCode());
|
||||||
|
asset.setSubmittedTime(LocalDateTime.now());
|
||||||
|
updates.add(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> keepSet = ordered.stream().collect(Collectors.toSet());
|
||||||
|
for (PlayClerkMediaAssetEntity asset : assets) {
|
||||||
|
if (!keepSet.contains(asset.getMediaId())) {
|
||||||
|
asset.setReviewState(ClerkMediaReviewState.REJECTED.getCode());
|
||||||
|
asset.setOrderIndex(0);
|
||||||
|
updates.add(asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (CollectionUtil.isNotEmpty(updates)) {
|
||||||
|
this.updateBatchById(updates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void reorder(String clerkId, ClerkMediaUsage usage, List<String> mediaIds) {
|
||||||
|
List<String> ordered = distinctMediaIds(mediaIds);
|
||||||
|
if (CollectionUtil.isEmpty(ordered)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<PlayClerkMediaAssetEntity> assets = listActiveByUsage(clerkId, usage);
|
||||||
|
if (CollectionUtil.isEmpty(assets)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, PlayClerkMediaAssetEntity> assetsByMediaId = assets.stream()
|
||||||
|
.collect(Collectors.toMap(PlayClerkMediaAssetEntity::getMediaId, item -> item));
|
||||||
|
List<PlayClerkMediaAssetEntity> updates = new ArrayList<>();
|
||||||
|
int order = 0;
|
||||||
|
for (String mediaId : ordered) {
|
||||||
|
PlayClerkMediaAssetEntity asset = assetsByMediaId.get(mediaId);
|
||||||
|
if (asset == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(asset.getOrderIndex(), order)) {
|
||||||
|
asset.setOrderIndex(order);
|
||||||
|
updates.add(asset);
|
||||||
|
}
|
||||||
|
order++;
|
||||||
|
}
|
||||||
|
if (CollectionUtil.isNotEmpty(updates)) {
|
||||||
|
this.updateBatchById(updates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void softDelete(String clerkId, String mediaId) {
|
||||||
|
LambdaQueryWrapper<PlayClerkMediaAssetEntity> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(PlayClerkMediaAssetEntity::getClerkId, clerkId)
|
||||||
|
.eq(PlayClerkMediaAssetEntity::getMediaId, mediaId)
|
||||||
|
.eq(PlayClerkMediaAssetEntity::getDeleted, false);
|
||||||
|
PlayClerkMediaAssetEntity asset = this.getOne(wrapper, false);
|
||||||
|
if (asset == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
asset.setDeleted(true);
|
||||||
|
asset.setReviewState(ClerkMediaReviewState.REJECTED.getCode());
|
||||||
|
this.updateById(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PlayClerkMediaAssetEntity> listByState(String clerkId, ClerkMediaUsage usage,
|
||||||
|
Collection<ClerkMediaReviewState> states) {
|
||||||
|
LambdaQueryWrapper<PlayClerkMediaAssetEntity> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(PlayClerkMediaAssetEntity::getClerkId, clerkId)
|
||||||
|
.eq(PlayClerkMediaAssetEntity::getUsage, usage.getCode())
|
||||||
|
.eq(PlayClerkMediaAssetEntity::getDeleted, false)
|
||||||
|
.orderByAsc(PlayClerkMediaAssetEntity::getOrderIndex)
|
||||||
|
.orderByDesc(PlayClerkMediaAssetEntity::getCreatedTime);
|
||||||
|
if (CollectionUtil.isNotEmpty(states)) {
|
||||||
|
wrapper.in(PlayClerkMediaAssetEntity::getReviewState,
|
||||||
|
states.stream().map(ClerkMediaReviewState::getCode).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
return this.list(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PlayClerkMediaAssetEntity> listActiveByUsage(String clerkId, ClerkMediaUsage usage) {
|
||||||
|
LambdaQueryWrapper<PlayClerkMediaAssetEntity> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(PlayClerkMediaAssetEntity::getClerkId, clerkId)
|
||||||
|
.eq(PlayClerkMediaAssetEntity::getUsage, usage.getCode())
|
||||||
|
.eq(PlayClerkMediaAssetEntity::getDeleted, false)
|
||||||
|
.orderByAsc(PlayClerkMediaAssetEntity::getOrderIndex)
|
||||||
|
.orderByDesc(PlayClerkMediaAssetEntity::getCreatedTime);
|
||||||
|
return this.list(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void applyReviewDecision(String clerkId, ClerkMediaUsage usage, List<String> approvedValues,
|
||||||
|
String reviewRecordId, String note) {
|
||||||
|
List<PlayClerkMediaAssetEntity> assets = listActiveByUsage(clerkId, usage);
|
||||||
|
if (CollectionUtil.isEmpty(assets)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> normalized = distinctMediaIds(approvedValues);
|
||||||
|
Map<String, PlayClerkMediaAssetEntity> byMediaId = assets.stream()
|
||||||
|
.collect(Collectors.toMap(PlayClerkMediaAssetEntity::getMediaId, item -> item));
|
||||||
|
Map<String, PlayClerkMediaAssetEntity> byUrl = buildAssetByUrlMap(assets);
|
||||||
|
|
||||||
|
List<PlayClerkMediaAssetEntity> updates = new ArrayList<>();
|
||||||
|
Set<String> approvedAssetIds = new java.util.HashSet<>();
|
||||||
|
int order = 0;
|
||||||
|
for (String value : normalized) {
|
||||||
|
PlayClerkMediaAssetEntity asset = byMediaId.get(value);
|
||||||
|
if (asset == null) {
|
||||||
|
asset = byUrl.get(value);
|
||||||
|
}
|
||||||
|
if (asset == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
asset.setReviewState(ClerkMediaReviewState.APPROVED.getCode());
|
||||||
|
asset.setOrderIndex(order++);
|
||||||
|
asset.setReviewRecordId(reviewRecordId);
|
||||||
|
if (StrUtil.isNotBlank(note)) {
|
||||||
|
asset.setNote(note);
|
||||||
|
}
|
||||||
|
updates.add(asset);
|
||||||
|
approvedAssetIds.add(asset.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PlayClerkMediaAssetEntity asset : assets) {
|
||||||
|
if (approvedAssetIds.contains(asset.getId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
asset.setReviewState(ClerkMediaReviewState.REJECTED.getCode());
|
||||||
|
asset.setReviewRecordId(reviewRecordId);
|
||||||
|
updates.add(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CollectionUtil.isNotEmpty(updates)) {
|
||||||
|
this.updateBatchById(updates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int resolveNextOrderIndex(String clerkId, ClerkMediaUsage usage) {
|
||||||
|
LambdaQueryWrapper<PlayClerkMediaAssetEntity> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(PlayClerkMediaAssetEntity::getClerkId, clerkId)
|
||||||
|
.eq(PlayClerkMediaAssetEntity::getUsage, usage.getCode())
|
||||||
|
.eq(PlayClerkMediaAssetEntity::getDeleted, false)
|
||||||
|
.orderByDesc(PlayClerkMediaAssetEntity::getOrderIndex)
|
||||||
|
.last("limit 1");
|
||||||
|
PlayClerkMediaAssetEntity last = this.getOne(wrapper, false);
|
||||||
|
if (last == null || last.getOrderIndex() == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return last.getOrderIndex() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> distinctMediaIds(List<String> mediaIds) {
|
||||||
|
if (CollectionUtil.isEmpty(mediaIds)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return mediaIds.stream()
|
||||||
|
.filter(StrUtil::isNotBlank)
|
||||||
|
.map(String::trim)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, PlayClerkMediaAssetEntity> buildAssetByUrlMap(List<PlayClerkMediaAssetEntity> assets) {
|
||||||
|
if (CollectionUtil.isEmpty(assets)) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
List<String> mediaIds = assets.stream().map(PlayClerkMediaAssetEntity::getMediaId).collect(Collectors.toList());
|
||||||
|
List<PlayMediaEntity> mediaList = mediaService.listByIds(mediaIds);
|
||||||
|
if (CollectionUtil.isEmpty(mediaList)) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
Map<String, String> mediaIdToUrl = mediaList.stream()
|
||||||
|
.filter(item -> StrUtil.isNotBlank(item.getUrl()))
|
||||||
|
.collect(Collectors.toMap(PlayMediaEntity::getId, PlayMediaEntity::getUrl, (left, right) -> left));
|
||||||
|
Map<String, PlayClerkMediaAssetEntity> map = new HashMap<>();
|
||||||
|
for (PlayClerkMediaAssetEntity asset : assets) {
|
||||||
|
String url = mediaIdToUrl.get(asset.getMediaId());
|
||||||
|
if (StrUtil.isNotBlank(url)) {
|
||||||
|
map.put(url, asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.starry.admin.modules.clerk.service.impl;
|
package com.starry.admin.modules.clerk.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
@@ -12,10 +13,13 @@ import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
|||||||
import com.starry.admin.common.component.JwtToken;
|
import com.starry.admin.common.component.JwtToken;
|
||||||
import com.starry.admin.common.domain.LoginUser;
|
import com.starry.admin.common.domain.LoginUser;
|
||||||
import com.starry.admin.common.exception.CustomException;
|
import com.starry.admin.common.exception.CustomException;
|
||||||
|
import com.starry.admin.modules.clerk.enums.ClerkMediaReviewState;
|
||||||
|
import com.starry.admin.modules.clerk.enums.ClerkMediaUsage;
|
||||||
import com.starry.admin.modules.clerk.mapper.PlayClerkUserInfoMapper;
|
import com.starry.admin.modules.clerk.mapper.PlayClerkUserInfoMapper;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkCommodityEntity;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkCommodityEntity;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkMediaAssetEntity;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserQueryVo;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserQueryVo;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReturnVo;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReturnVo;
|
||||||
@@ -29,9 +33,13 @@ import com.starry.admin.modules.clerk.module.vo.PlayClerkUnsettledWagesInfoRetur
|
|||||||
import com.starry.admin.modules.clerk.service.IPlayClerkCommodityService;
|
import com.starry.admin.modules.clerk.service.IPlayClerkCommodityService;
|
||||||
import com.starry.admin.modules.clerk.service.IPlayClerkDataReviewInfoService;
|
import com.starry.admin.modules.clerk.service.IPlayClerkDataReviewInfoService;
|
||||||
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
|
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
|
||||||
|
import com.starry.admin.modules.clerk.service.IPlayClerkMediaAssetService;
|
||||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||||
import com.starry.admin.modules.custom.entity.PlayCustomFollowInfoEntity;
|
import com.starry.admin.modules.custom.entity.PlayCustomFollowInfoEntity;
|
||||||
import com.starry.admin.modules.custom.service.IPlayCustomFollowInfoService;
|
import com.starry.admin.modules.custom.service.IPlayCustomFollowInfoService;
|
||||||
|
import com.starry.admin.modules.media.entity.PlayMediaEntity;
|
||||||
|
import com.starry.admin.modules.media.enums.MediaStatus;
|
||||||
|
import com.starry.admin.modules.media.service.IPlayMediaService;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
||||||
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity;
|
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity;
|
||||||
@@ -43,7 +51,9 @@ import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService
|
|||||||
import com.starry.admin.modules.personnel.service.IPlayPersonnelWaiterInfoService;
|
import com.starry.admin.modules.personnel.service.IPlayPersonnelWaiterInfoService;
|
||||||
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoQueryVo;
|
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoQueryVo;
|
||||||
import com.starry.admin.modules.system.service.LoginService;
|
import com.starry.admin.modules.system.service.LoginService;
|
||||||
|
import com.starry.admin.modules.weichat.assembler.ClerkMediaAssembler;
|
||||||
import com.starry.admin.modules.weichat.entity.PlayClerkUserLoginResponseVo;
|
import com.starry.admin.modules.weichat.entity.PlayClerkUserLoginResponseVo;
|
||||||
|
import com.starry.admin.modules.weichat.entity.clerk.MediaVo;
|
||||||
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoQueryVo;
|
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoQueryVo;
|
||||||
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoResultVo;
|
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoResultVo;
|
||||||
import com.starry.admin.utils.SecurityUtils;
|
import com.starry.admin.utils.SecurityUtils;
|
||||||
@@ -53,7 +63,9 @@ import com.starry.common.utils.StringUtils;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -69,9 +81,7 @@ import org.springframework.stereotype.Service;
|
|||||||
* @since 2024-03-30
|
* @since 2024-03-30
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoMapper, PlayClerkUserInfoEntity>
|
public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoMapper, PlayClerkUserInfoEntity> implements IPlayClerkUserInfoService {
|
||||||
implements
|
|
||||||
IPlayClerkUserInfoService {
|
|
||||||
|
|
||||||
private static final String OFFBOARD_MESSAGE = "你已离职,需要复职请联系店铺管理员";
|
private static final String OFFBOARD_MESSAGE = "你已离职,需要复职请联系店铺管理员";
|
||||||
private static final String DELISTED_MESSAGE = "你已被下架,没有权限访问";
|
private static final String DELISTED_MESSAGE = "你已被下架,没有权限访问";
|
||||||
@@ -87,6 +97,10 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
@Resource
|
@Resource
|
||||||
private IPlayCustomFollowInfoService customFollowInfoService;
|
private IPlayCustomFollowInfoService customFollowInfoService;
|
||||||
@Resource
|
@Resource
|
||||||
|
private IPlayClerkMediaAssetService clerkMediaAssetService;
|
||||||
|
@Resource
|
||||||
|
private IPlayMediaService mediaService;
|
||||||
|
@Resource
|
||||||
private IPlayBalanceDetailsInfoService playBalanceDetailsInfoService;
|
private IPlayBalanceDetailsInfoService playBalanceDetailsInfoService;
|
||||||
@Resource
|
@Resource
|
||||||
private IPlayOrderInfoService playOrderInfoService;
|
private IPlayOrderInfoService playOrderInfoService;
|
||||||
@@ -131,10 +145,18 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
MPJLambdaWrapper<PlayClerkUserInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
|
MPJLambdaWrapper<PlayClerkUserInfoEntity> lambdaWrapper = new MPJLambdaWrapper<>();
|
||||||
lambdaWrapper.selectAll(PlayClerkLevelInfoEntity.class);
|
lambdaWrapper.selectAll(PlayClerkLevelInfoEntity.class);
|
||||||
lambdaWrapper.selectAs(PlayClerkUserInfoEntity::getLevelId, "levelId");
|
lambdaWrapper.selectAs(PlayClerkUserInfoEntity::getLevelId, "levelId");
|
||||||
lambdaWrapper.leftJoin(PlayClerkLevelInfoEntity.class, PlayClerkLevelInfoEntity::getId,
|
lambdaWrapper.leftJoin(PlayClerkLevelInfoEntity.class, PlayClerkLevelInfoEntity::getId, PlayClerkUserInfoEntity::getLevelId);
|
||||||
PlayClerkUserInfoEntity::getLevelId);
|
|
||||||
lambdaWrapper.eq(PlayClerkUserInfoEntity::getId, clerkId);
|
lambdaWrapper.eq(PlayClerkUserInfoEntity::getId, clerkId);
|
||||||
return this.baseMapper.selectJoinOne(PlayClerkLevelInfoEntity.class, lambdaWrapper);
|
PlayClerkLevelInfoEntity levelInfo = this.baseMapper.selectJoinOne(PlayClerkLevelInfoEntity.class, lambdaWrapper);
|
||||||
|
if (levelInfo != null) {
|
||||||
|
return levelInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayClerkUserInfoEntity clerk = this.baseMapper.selectById(clerkId);
|
||||||
|
if (clerk == null || StringUtils.isBlank(clerk.getLevelId())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return playClerkLevelInfoService.getById(clerk.getLevelId());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,8 +170,7 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
/**
|
/**
|
||||||
* 查询店员
|
* 查询店员
|
||||||
*
|
*
|
||||||
* @param id
|
* @param id 店员主键
|
||||||
* 店员主键
|
|
||||||
* @return 店员
|
* @return 店员
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -164,13 +185,9 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
@Override
|
@Override
|
||||||
public PlayClerkUserLoginResponseVo getVo(PlayClerkUserInfoEntity userInfo) {
|
public PlayClerkUserLoginResponseVo getVo(PlayClerkUserInfoEntity userInfo) {
|
||||||
PlayClerkUserLoginResponseVo result = ConvertUtil.entityToVo(userInfo, PlayClerkUserLoginResponseVo.class);
|
PlayClerkUserLoginResponseVo result = ConvertUtil.entityToVo(userInfo, PlayClerkUserLoginResponseVo.class);
|
||||||
List<PlayClerkDataReviewInfoEntity> pendingReviews = playClerkDataReviewInfoService
|
List<PlayClerkDataReviewInfoEntity> pendingReviews = playClerkDataReviewInfoService.queryByClerkId(userInfo.getId(), "0");
|
||||||
.queryByClerkId(userInfo.getId(), "0");
|
|
||||||
if (pendingReviews != null && !pendingReviews.isEmpty()) {
|
if (pendingReviews != null && !pendingReviews.isEmpty()) {
|
||||||
Set<String> pendingTypes = pendingReviews.stream()
|
Set<String> pendingTypes = pendingReviews.stream().map(PlayClerkDataReviewInfoEntity::getDataType).filter(StrUtil::isNotBlank).collect(Collectors.toSet());
|
||||||
.map(PlayClerkDataReviewInfoEntity::getDataType)
|
|
||||||
.filter(StrUtil::isNotBlank)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
if (pendingTypes.contains("0")) {
|
if (pendingTypes.contains("0")) {
|
||||||
result.setNicknameAllowEdit(false);
|
result.setNicknameAllowEdit(false);
|
||||||
}
|
}
|
||||||
@@ -208,18 +225,19 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 查询店员服务项目
|
// 查询店员服务项目
|
||||||
List<PlayClerkCommodityEntity> clerkCommodityEntities = playClerkCommodityService
|
List<PlayClerkCommodityEntity> clerkCommodityEntities = playClerkCommodityService.selectCommodityTypeByUser(userInfo.getId(), "");
|
||||||
.selectCommodityTypeByUser(userInfo.getId(), "");
|
|
||||||
List<PlayClerkCommodityQueryVo> playClerkCommodityQueryVos = new ArrayList<>();
|
List<PlayClerkCommodityQueryVo> playClerkCommodityQueryVos = new ArrayList<>();
|
||||||
for (PlayClerkCommodityEntity clerkCommodityEntity : clerkCommodityEntities) {
|
for (PlayClerkCommodityEntity clerkCommodityEntity : clerkCommodityEntities) {
|
||||||
playClerkCommodityQueryVos.add(new PlayClerkCommodityQueryVo(clerkCommodityEntity.getCommodityName(),
|
playClerkCommodityQueryVos.add(new PlayClerkCommodityQueryVo(clerkCommodityEntity.getCommodityName(), clerkCommodityEntity.getEnablingState()));
|
||||||
clerkCommodityEntity.getEnablingState()));
|
|
||||||
}
|
}
|
||||||
result.setCommodity(playClerkCommodityQueryVos);
|
result.setCommodity(playClerkCommodityQueryVos);
|
||||||
result.setArea(userInfo.getProvince() + "-" + userInfo.getCity());
|
result.setArea(userInfo.getProvince() + "-" + userInfo.getCity());
|
||||||
|
|
||||||
result.setPcData(this.getPcData(userInfo));
|
result.setPcData(this.getPcData(userInfo));
|
||||||
result.setLevelInfo(playClerkLevelInfoService.selectPlayClerkLevelInfoById(userInfo.getLevelId()));
|
result.setLevelInfo(playClerkLevelInfoService.selectPlayClerkLevelInfoById(userInfo.getLevelId()));
|
||||||
|
List<MediaVo> mediaList = loadMediaForClerk(userInfo.getId(), true);
|
||||||
|
result.setMediaList(mergeLegacyAlbum(userInfo.getAlbum(), mediaList));
|
||||||
|
result.setAlbum(CollectionUtil.isEmpty(userInfo.getAlbum()) ? new ArrayList<>() : new ArrayList<>(userInfo.getAlbum()));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,10 +274,7 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
if (StrUtil.isBlank(clerkId)) {
|
if (StrUtil.isBlank(clerkId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LambdaUpdateWrapper<PlayClerkUserInfoEntity> wrapper = Wrappers.lambdaUpdate(PlayClerkUserInfoEntity.class)
|
LambdaUpdateWrapper<PlayClerkUserInfoEntity> wrapper = Wrappers.lambdaUpdate(PlayClerkUserInfoEntity.class).eq(PlayClerkUserInfoEntity::getId, clerkId).set(PlayClerkUserInfoEntity::getToken, "empty").set(PlayClerkUserInfoEntity::getOnlineState, "0");
|
||||||
.eq(PlayClerkUserInfoEntity::getId, clerkId)
|
|
||||||
.set(PlayClerkUserInfoEntity::getToken, "empty")
|
|
||||||
.set(PlayClerkUserInfoEntity::getOnlineState, "0");
|
|
||||||
this.baseMapper.update(null, wrapper);
|
this.baseMapper.update(null, wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,21 +292,17 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateAccountBalanceById(String userId, BigDecimal balanceBeforeOperation,
|
public void updateAccountBalanceById(String userId, BigDecimal balanceBeforeOperation, BigDecimal balanceAfterOperation, String operationType, String operationAction, BigDecimal balanceMoney, String orderId) {
|
||||||
BigDecimal balanceAfterOperation, String operationType, String operationAction, BigDecimal balanceMoney,
|
|
||||||
String orderId) {
|
|
||||||
// 修改用户余额
|
// 修改用户余额
|
||||||
this.baseMapper.updateById(new PlayClerkUserInfoEntity(userId, balanceAfterOperation));
|
this.baseMapper.updateById(new PlayClerkUserInfoEntity(userId, balanceAfterOperation));
|
||||||
// 记录余额变更记录
|
// 记录余额变更记录
|
||||||
playBalanceDetailsInfoService.insertBalanceDetailsInfo("0", userId, balanceBeforeOperation,
|
playBalanceDetailsInfoService.insertBalanceDetailsInfo("0", userId, balanceBeforeOperation, balanceAfterOperation, operationType, operationAction, balanceMoney, BigDecimal.ZERO, orderId);
|
||||||
balanceAfterOperation, operationType, operationAction, balanceMoney, BigDecimal.ZERO, orderId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询店员列表
|
* 查询店员列表
|
||||||
*
|
*
|
||||||
* @param vo
|
* @param vo 店员查询对象
|
||||||
* 店员查询对象
|
|
||||||
* @return 店员
|
* @return 店员
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -302,12 +313,10 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
// 查询不隐藏的
|
// 查询不隐藏的
|
||||||
lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getDisplayState, "1");
|
lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getDisplayState, "1");
|
||||||
// 查询主表全部字段
|
// 查询主表全部字段
|
||||||
lambdaQueryWrapper.selectAll(PlayClerkUserInfoEntity.class).selectAs(PlayClerkUserInfoEntity::getCity,
|
lambdaQueryWrapper.selectAll(PlayClerkUserInfoEntity.class).selectAs(PlayClerkUserInfoEntity::getCity, "address");
|
||||||
"address");
|
|
||||||
// 等级表
|
// 等级表
|
||||||
lambdaQueryWrapper.selectAs(PlayClerkLevelInfoEntity::getName, "levelName");
|
lambdaQueryWrapper.selectAs(PlayClerkLevelInfoEntity::getName, "levelName");
|
||||||
lambdaQueryWrapper.leftJoin(PlayClerkLevelInfoEntity.class, PlayClerkLevelInfoEntity::getId,
|
lambdaQueryWrapper.leftJoin(PlayClerkLevelInfoEntity.class, PlayClerkLevelInfoEntity::getId, PlayClerkUserInfoEntity::getLevelId);
|
||||||
PlayClerkUserInfoEntity::getLevelId);
|
|
||||||
|
|
||||||
// 服务项目表
|
// 服务项目表
|
||||||
if (StrUtil.isNotBlank(vo.getNickname())) {
|
if (StrUtil.isNotBlank(vo.getNickname())) {
|
||||||
@@ -335,12 +344,34 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getOnboardingState, vo.getOnboardingState());
|
lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getOnboardingState, vo.getOnboardingState());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 排序:置顶状态优先,在线用户其次,最后按创建时间倒序
|
// 排序:非空的等级排序号优先,值越小越靠前;同一排序号在线状态优先
|
||||||
lambdaQueryWrapper.orderByDesc(PlayClerkUserInfoEntity::getPinToTopState)
|
lambdaQueryWrapper
|
||||||
.orderByDesc(PlayClerkUserInfoEntity::getOnlineState)
|
.orderByDesc(PlayClerkUserInfoEntity::getOnlineState)
|
||||||
.orderByDesc(PlayClerkUserInfoEntity::getCreatedTime);
|
.orderByDesc(PlayClerkUserInfoEntity::getPinToTopState)
|
||||||
|
.orderByAsc(true, "CASE WHEN t1.order_number IS NULL THEN 1 ELSE 0 END")
|
||||||
|
.orderByAsc(PlayClerkLevelInfoEntity::getOrderNumber)
|
||||||
|
.orderByAsc(PlayClerkUserInfoEntity::getCreatedTime)
|
||||||
|
.orderByAsc(PlayClerkUserInfoEntity::getNickname)
|
||||||
|
.orderByAsc(PlayClerkUserInfoEntity::getId);
|
||||||
|
|
||||||
return this.baseMapper.selectJoinPage(page, PlayClerkUserInfoResultVo.class, lambdaQueryWrapper);
|
IPage<PlayClerkUserInfoResultVo> pageResult = this.baseMapper.selectJoinPage(page,
|
||||||
|
PlayClerkUserInfoResultVo.class, lambdaQueryWrapper);
|
||||||
|
if (pageResult != null && pageResult.getRecords() != null) {
|
||||||
|
List<PlayClerkUserInfoResultVo> deduped = new ArrayList<>();
|
||||||
|
Set<String> seen = new HashSet<>();
|
||||||
|
for (PlayClerkUserInfoResultVo record : pageResult.getRecords()) {
|
||||||
|
String id = record.getId();
|
||||||
|
if (id == null || !seen.add(id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
deduped.add(record);
|
||||||
|
}
|
||||||
|
pageResult.setRecords(deduped);
|
||||||
|
}
|
||||||
|
if (pageResult != null) {
|
||||||
|
attachMediaToResultVos(pageResult.getRecords(), false);
|
||||||
|
}
|
||||||
|
return pageResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -355,8 +386,7 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
public IPage<PlayClerkUnsettledWagesInfoReturnVo> listUnsettledWagesByPage(PlayClerkUnsettledWagesInfoQueryVo vo) {
|
public IPage<PlayClerkUnsettledWagesInfoReturnVo> listUnsettledWagesByPage(PlayClerkUnsettledWagesInfoQueryVo vo) {
|
||||||
MPJLambdaWrapper<PlayClerkUserInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
|
MPJLambdaWrapper<PlayClerkUserInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
|
||||||
// 查询所有店员
|
// 查询所有店员
|
||||||
lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname")
|
lambdaQueryWrapper.selectAs(PlayClerkUserInfoEntity::getNickname, "clerkNickname").selectAs(PlayClerkUserInfoEntity::getId, "clerkId");
|
||||||
.selectAs(PlayClerkUserInfoEntity::getId, "clerkId");
|
|
||||||
lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getClerkState, ClerkRoleStatus.CLERK.getCode());
|
lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getClerkState, ClerkRoleStatus.CLERK.getCode());
|
||||||
// 加入组员的筛选
|
// 加入组员的筛选
|
||||||
List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
|
List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
|
||||||
@@ -368,14 +398,11 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getListingState, vo.getListingState());
|
lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getListingState, vo.getListingState());
|
||||||
}
|
}
|
||||||
// 查询店员订单信息
|
// 查询店员订单信息
|
||||||
lambdaQueryWrapper.selectCollection(PlayOrderInfoEntity.class,
|
lambdaQueryWrapper.selectCollection(PlayOrderInfoEntity.class, PlayClerkUnsettledWagesInfoReturnVo::getOrderInfoEntities);
|
||||||
PlayClerkUnsettledWagesInfoReturnVo::getOrderInfoEntities);
|
lambdaQueryWrapper.leftJoin(PlayOrderInfoEntity.class, PlayOrderInfoEntity::getAcceptBy, PlayClerkUserInfoEntity::getId);
|
||||||
lambdaQueryWrapper.leftJoin(PlayOrderInfoEntity.class, PlayOrderInfoEntity::getAcceptBy,
|
|
||||||
PlayClerkUserInfoEntity::getId);
|
|
||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderSettlementState, "0");
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderSettlementState, "0");
|
||||||
|
|
||||||
return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()),
|
return this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()), PlayClerkUnsettledWagesInfoReturnVo.class, lambdaQueryWrapper);
|
||||||
PlayClerkUnsettledWagesInfoReturnVo.class, lambdaQueryWrapper);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -474,12 +501,9 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
lambdaQueryWrapper.in(PlayClerkUserInfoEntity::getId, clerkIdList);
|
lambdaQueryWrapper.in(PlayClerkUserInfoEntity::getId, clerkIdList);
|
||||||
|
|
||||||
// 排序:置顶状态优先,在线用户其次,最后按创建时间倒序
|
// 排序:置顶状态优先,在线用户其次,最后按创建时间倒序
|
||||||
lambdaQueryWrapper.orderByDesc(PlayClerkUserInfoEntity::getPinToTopState)
|
lambdaQueryWrapper.orderByDesc(PlayClerkUserInfoEntity::getPinToTopState).orderByDesc(PlayClerkUserInfoEntity::getOnlineState).orderByDesc(PlayClerkUserInfoEntity::getCreatedTime);
|
||||||
.orderByDesc(PlayClerkUserInfoEntity::getOnlineState)
|
|
||||||
.orderByDesc(PlayClerkUserInfoEntity::getCreatedTime);
|
|
||||||
|
|
||||||
IPage<PlayClerkUserReturnVo> page = this.baseMapper.selectJoinPage(
|
IPage<PlayClerkUserReturnVo> page = this.baseMapper.selectJoinPage(new Page<>(vo.getPageNum(), vo.getPageSize()), PlayClerkUserReturnVo.class, lambdaQueryWrapper);
|
||||||
new Page<>(vo.getPageNum(), vo.getPageSize()), PlayClerkUserReturnVo.class, lambdaQueryWrapper);
|
|
||||||
|
|
||||||
for (PlayClerkUserReturnVo record : page.getRecords()) {
|
for (PlayClerkUserReturnVo record : page.getRecords()) {
|
||||||
BigDecimal orderTotalAmount = new BigDecimal("0");
|
BigDecimal orderTotalAmount = new BigDecimal("0");
|
||||||
@@ -499,6 +523,7 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
record.setOrderContinueNumber(String.valueOf(orderContinueNumber));
|
record.setOrderContinueNumber(String.valueOf(orderContinueNumber));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attachMediaToAdminVos(page.getRecords());
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,10 +535,8 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
if (StrUtil.isNotBlank(customUserId)) {
|
if (StrUtil.isNotBlank(customUserId)) {
|
||||||
LambdaQueryWrapper<PlayCustomFollowInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<PlayCustomFollowInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
lambdaQueryWrapper.eq(PlayCustomFollowInfoEntity::getCustomId, customUserId);
|
lambdaQueryWrapper.eq(PlayCustomFollowInfoEntity::getCustomId, customUserId);
|
||||||
List<PlayCustomFollowInfoEntity> customFollowInfoEntities = customFollowInfoService
|
List<PlayCustomFollowInfoEntity> customFollowInfoEntities = customFollowInfoService.list(lambdaQueryWrapper);
|
||||||
.list(lambdaQueryWrapper);
|
customFollows = customFollowInfoEntities.stream().collect(Collectors.toMap(PlayCustomFollowInfoEntity::getClerkId, PlayCustomFollowInfoEntity::getFollowState));
|
||||||
customFollows = customFollowInfoEntities.stream().collect(Collectors
|
|
||||||
.toMap(PlayCustomFollowInfoEntity::getClerkId, PlayCustomFollowInfoEntity::getFollowState));
|
|
||||||
}
|
}
|
||||||
for (PlayClerkUserInfoResultVo record : voPage.getRecords()) {
|
for (PlayClerkUserInfoResultVo record : voPage.getRecords()) {
|
||||||
record.setFollowState(customFollows.containsKey(record.getId()) ? "1" : "0");
|
record.setFollowState(customFollows.containsKey(record.getId()) ? "1" : "0");
|
||||||
@@ -525,11 +548,37 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
return voPage;
|
return voPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PlayClerkUserInfoResultVo buildCustomerDetail(String clerkId, String customUserId) {
|
||||||
|
PlayClerkUserInfoEntity entity = this.baseMapper.selectById(clerkId);
|
||||||
|
if (entity == null) {
|
||||||
|
throw new CustomException("店员不存在");
|
||||||
|
}
|
||||||
|
PlayClerkUserInfoResultVo vo = ConvertUtil.entityToVo(entity, PlayClerkUserInfoResultVo.class);
|
||||||
|
vo.setAddress(entity.getCity());
|
||||||
|
vo.setCommodity(playClerkCommodityService.getClerkCommodityList(vo.getId(), "1"));
|
||||||
|
|
||||||
|
String followState = "0";
|
||||||
|
if (StrUtil.isNotBlank(customUserId)) {
|
||||||
|
LambdaQueryWrapper<PlayCustomFollowInfoEntity> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(PlayCustomFollowInfoEntity::getCustomId, customUserId)
|
||||||
|
.eq(PlayCustomFollowInfoEntity::getClerkId, clerkId);
|
||||||
|
PlayCustomFollowInfoEntity followInfo = customFollowInfoService.getOne(wrapper, false);
|
||||||
|
if (followInfo != null && "1".equals(followInfo.getFollowState())) {
|
||||||
|
followState = "1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vo.setFollowState(followState);
|
||||||
|
|
||||||
|
List<MediaVo> mediaList = loadMediaForClerk(clerkId, false);
|
||||||
|
vo.setMediaList(mergeLegacyAlbum(entity.getAlbum(), mediaList));
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增店员
|
* 新增店员
|
||||||
*
|
*
|
||||||
* @param playClerkUserInfo
|
* @param playClerkUserInfo 店员
|
||||||
* 店员
|
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -543,16 +592,12 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
/**
|
/**
|
||||||
* 修改店员
|
* 修改店员
|
||||||
*
|
*
|
||||||
* @param playClerkUserInfo
|
* @param playClerkUserInfo 店员
|
||||||
* 店员
|
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean update(PlayClerkUserInfoEntity playClerkUserInfo) {
|
public boolean update(PlayClerkUserInfoEntity playClerkUserInfo) {
|
||||||
boolean inspectStatus = StringUtils.isNotBlank(playClerkUserInfo.getId())
|
boolean inspectStatus = StringUtils.isNotBlank(playClerkUserInfo.getId()) && (StrUtil.isNotBlank(playClerkUserInfo.getOnboardingState()) || StrUtil.isNotBlank(playClerkUserInfo.getListingState()) || StrUtil.isNotBlank(playClerkUserInfo.getClerkState()));
|
||||||
&& (StrUtil.isNotBlank(playClerkUserInfo.getOnboardingState())
|
|
||||||
|| StrUtil.isNotBlank(playClerkUserInfo.getListingState())
|
|
||||||
|| StrUtil.isNotBlank(playClerkUserInfo.getClerkState()));
|
|
||||||
PlayClerkUserInfoEntity beforeUpdate = null;
|
PlayClerkUserInfoEntity beforeUpdate = null;
|
||||||
if (inspectStatus) {
|
if (inspectStatus) {
|
||||||
beforeUpdate = this.baseMapper.selectById(playClerkUserInfo.getId());
|
beforeUpdate = this.baseMapper.selectById(playClerkUserInfo.getId());
|
||||||
@@ -567,8 +612,7 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
/**
|
/**
|
||||||
* 批量删除店员
|
* 批量删除店员
|
||||||
*
|
*
|
||||||
* @param ids
|
* @param ids 需要删除的店员主键
|
||||||
* 需要删除的店员主键
|
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -579,8 +623,7 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
/**
|
/**
|
||||||
* 删除店员信息
|
* 删除店员信息
|
||||||
*
|
*
|
||||||
* @param id
|
* @param id 店员主键
|
||||||
* 店员主键
|
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -594,13 +637,16 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
LambdaQueryWrapper<PlayClerkUserInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<PlayClerkUserInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getDeleted, 0);
|
lambdaQueryWrapper.eq(PlayClerkUserInfoEntity::getDeleted, 0);
|
||||||
lambdaQueryWrapper.in(PlayClerkUserInfoEntity::getId, clerkIdList);
|
lambdaQueryWrapper.in(PlayClerkUserInfoEntity::getId, clerkIdList);
|
||||||
lambdaQueryWrapper.select(PlayClerkUserInfoEntity::getId, PlayClerkUserInfoEntity::getNickname,
|
lambdaQueryWrapper.select(PlayClerkUserInfoEntity::getId, PlayClerkUserInfoEntity::getNickname, PlayClerkUserInfoEntity::getAvatar, PlayClerkUserInfoEntity::getTypeId, PlayClerkUserInfoEntity::getGroupId, PlayClerkUserInfoEntity::getPhone);
|
||||||
PlayClerkUserInfoEntity::getAvatar, PlayClerkUserInfoEntity::getTypeId,
|
|
||||||
PlayClerkUserInfoEntity::getGroupId, PlayClerkUserInfoEntity::getPhone);
|
|
||||||
lambdaQueryWrapper.orderByDesc(PlayClerkUserInfoEntity::getId);
|
lambdaQueryWrapper.orderByDesc(PlayClerkUserInfoEntity::getId);
|
||||||
return this.baseMapper.selectList(lambdaQueryWrapper);
|
return this.baseMapper.selectList(lambdaQueryWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PlayClerkUserInfoEntity> listWithAlbumIgnoringTenant() {
|
||||||
|
return playClerkUserInfoMapper.selectAllWithAlbumIgnoringTenant();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JSONObject getPcData(PlayClerkUserInfoEntity entity) {
|
public JSONObject getPcData(PlayClerkUserInfoEntity entity) {
|
||||||
JSONObject data = new JSONObject();
|
JSONObject data = new JSONObject();
|
||||||
@@ -612,8 +658,7 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
LoginUser loginUserInfo = loginService.getLoginUserInfo(entity.getSysUserId());
|
LoginUser loginUserInfo = loginService.getLoginUserInfo(entity.getSysUserId());
|
||||||
Map<String, Object> tokenMap = jwtToken.createToken(loginUserInfo);
|
Map<String, Object> tokenMap = jwtToken.createToken(loginUserInfo);
|
||||||
data.fluentPut("token", tokenMap.get("token"));
|
data.fluentPut("token", tokenMap.get("token"));
|
||||||
PlayPersonnelAdminInfoEntity adminInfoEntity = playPersonnelAdminInfoService
|
PlayPersonnelAdminInfoEntity adminInfoEntity = playPersonnelAdminInfoService.selectByUserId(entity.getSysUserId());
|
||||||
.selectByUserId(entity.getSysUserId());
|
|
||||||
if (Objects.nonNull(adminInfoEntity)) {
|
if (Objects.nonNull(adminInfoEntity)) {
|
||||||
data.fluentPut("role", "operator");
|
data.fluentPut("role", "operator");
|
||||||
return data;
|
return data;
|
||||||
@@ -623,8 +668,7 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
data.fluentPut("role", "leader");
|
data.fluentPut("role", "leader");
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
PlayPersonnelWaiterInfoEntity waiterInfoEntity = playClerkWaiterInfoService
|
PlayPersonnelWaiterInfoEntity waiterInfoEntity = playClerkWaiterInfoService.selectByUserId(entity.getSysUserId());
|
||||||
.selectByUserId(entity.getSysUserId());
|
|
||||||
if (Objects.nonNull(waiterInfoEntity)) {
|
if (Objects.nonNull(waiterInfoEntity)) {
|
||||||
data.fluentPut("role", "waiter");
|
data.fluentPut("role", "waiter");
|
||||||
return data;
|
return data;
|
||||||
@@ -636,13 +680,101 @@ public class PlayClerkUserInfoServiceImpl extends ServiceImpl<PlayClerkUserInfoM
|
|||||||
if (beforeUpdate == null) {
|
if (beforeUpdate == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (OnboardingStatus.transitionedToOffboarded(updatedPayload.getOnboardingState(),
|
if (OnboardingStatus.transitionedToOffboarded(updatedPayload.getOnboardingState(), beforeUpdate.getOnboardingState()) || ListingStatus.transitionedToDelisted(updatedPayload.getListingState(), beforeUpdate.getListingState()) || ClerkRoleStatus.transitionedToNonClerk(updatedPayload.getClerkState(), beforeUpdate.getClerkState())) {
|
||||||
beforeUpdate.getOnboardingState())
|
|
||||||
|| ListingStatus.transitionedToDelisted(updatedPayload.getListingState(),
|
|
||||||
beforeUpdate.getListingState())
|
|
||||||
|| ClerkRoleStatus.transitionedToNonClerk(updatedPayload.getClerkState(),
|
|
||||||
beforeUpdate.getClerkState())) {
|
|
||||||
invalidateClerkSession(beforeUpdate.getId());
|
invalidateClerkSession(beforeUpdate.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private void attachMediaToResultVos(List<PlayClerkUserInfoResultVo> records, boolean includePending) {
|
||||||
|
if (CollectionUtil.isEmpty(records)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, List<MediaVo>> mediaMap = resolveMediaByAssets(
|
||||||
|
records.stream().map(PlayClerkUserInfoResultVo::getId).collect(Collectors.toList()), includePending);
|
||||||
|
for (PlayClerkUserInfoResultVo record : records) {
|
||||||
|
List<MediaVo> mediaList = new ArrayList<>(mediaMap.getOrDefault(record.getId(), Collections.emptyList()));
|
||||||
|
record.setMediaList(mergeLegacyAlbum(record.getAlbum(), mediaList));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attachMediaToAdminVos(List<PlayClerkUserReturnVo> records) {
|
||||||
|
if (CollectionUtil.isEmpty(records)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, List<MediaVo>> mediaMap = resolveMediaByAssets(
|
||||||
|
records.stream().map(PlayClerkUserReturnVo::getId).collect(Collectors.toList()), true);
|
||||||
|
for (PlayClerkUserReturnVo record : records) {
|
||||||
|
List<MediaVo> mediaList = new ArrayList<>(mediaMap.getOrDefault(record.getId(), Collections.emptyList()));
|
||||||
|
record.setMediaList(mergeLegacyAlbum(record.getAlbum(), mediaList));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MediaVo> loadMediaForClerk(String clerkId, boolean includePending) {
|
||||||
|
Map<String, List<MediaVo>> mediaMap = resolveMediaByAssets(Collections.singletonList(clerkId), includePending);
|
||||||
|
return new ArrayList<>(mediaMap.getOrDefault(clerkId, Collections.emptyList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, List<MediaVo>> resolveMediaByAssets(List<String> clerkIds, boolean includePending) {
|
||||||
|
if (CollectionUtil.isEmpty(clerkIds)) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ClerkMediaReviewState> targetStates = includePending
|
||||||
|
? Arrays.asList(ClerkMediaReviewState.APPROVED, ClerkMediaReviewState.PENDING,
|
||||||
|
ClerkMediaReviewState.DRAFT, ClerkMediaReviewState.REJECTED)
|
||||||
|
: Collections.singletonList(ClerkMediaReviewState.APPROVED);
|
||||||
|
|
||||||
|
List<PlayClerkMediaAssetEntity> assets = clerkMediaAssetService.lambdaQuery()
|
||||||
|
.in(PlayClerkMediaAssetEntity::getClerkId, clerkIds)
|
||||||
|
.eq(PlayClerkMediaAssetEntity::getUsage, ClerkMediaUsage.PROFILE.getCode())
|
||||||
|
.eq(PlayClerkMediaAssetEntity::getDeleted, false)
|
||||||
|
.in(CollectionUtil.isNotEmpty(targetStates), PlayClerkMediaAssetEntity::getReviewState,
|
||||||
|
targetStates.stream().map(ClerkMediaReviewState::getCode).collect(Collectors.toList()))
|
||||||
|
.orderByAsc(PlayClerkMediaAssetEntity::getOrderIndex)
|
||||||
|
.orderByDesc(PlayClerkMediaAssetEntity::getCreatedTime)
|
||||||
|
.list();
|
||||||
|
if (CollectionUtil.isEmpty(assets)) {
|
||||||
|
Map<String, List<MediaVo>> empty = new HashMap<>();
|
||||||
|
clerkIds.forEach(id -> empty.put(id, Collections.emptyList()));
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> mediaIds = assets.stream().map(PlayClerkMediaAssetEntity::getMediaId).distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Map<String, PlayMediaEntity> mediaById = CollectionUtil.isEmpty(mediaIds)
|
||||||
|
? Collections.emptyMap()
|
||||||
|
: mediaService.listByIds(mediaIds).stream()
|
||||||
|
.collect(Collectors.toMap(PlayMediaEntity::getId, item -> item, (left, right) -> left));
|
||||||
|
|
||||||
|
Map<String, List<PlayClerkMediaAssetEntity>> groupedAssets = assets.stream()
|
||||||
|
.collect(Collectors.groupingBy(PlayClerkMediaAssetEntity::getClerkId));
|
||||||
|
|
||||||
|
Map<String, List<MediaVo>> result = new HashMap<>(groupedAssets.size());
|
||||||
|
groupedAssets.forEach((clerkId, assetList) -> result.put(clerkId, ClerkMediaAssembler.toVoList(assetList, mediaById)));
|
||||||
|
|
||||||
|
clerkIds.forEach(id -> result.computeIfAbsent(id, key -> Collections.emptyList()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<MediaVo> mergeLegacyAlbum(List<String> legacyAlbum, List<MediaVo> destination) {
|
||||||
|
if (CollectionUtil.isEmpty(legacyAlbum)) {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
Set<String> existingUrls = destination.stream()
|
||||||
|
.map(MediaVo::getUrl)
|
||||||
|
.filter(StrUtil::isNotBlank)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
for (String url : legacyAlbum) {
|
||||||
|
if (StrUtil.isBlank(url) || !existingUrls.add(url)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
MediaVo legacyVo = new MediaVo();
|
||||||
|
legacyVo.setId(url);
|
||||||
|
legacyVo.setUrl(url);
|
||||||
|
legacyVo.setUsage(ClerkMediaUsage.PROFILE.getCode());
|
||||||
|
legacyVo.setStatus(MediaStatus.READY.getCode());
|
||||||
|
legacyVo.setReviewState(ClerkMediaReviewState.APPROVED.getCode());
|
||||||
|
destination.add(legacyVo);
|
||||||
|
}
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import com.starry.admin.modules.clerk.service.IPlayClerkCommodityService;
|
|||||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserReviewInfoService;
|
import com.starry.admin.modules.clerk.service.IPlayClerkUserReviewInfoService;
|
||||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||||
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
import com.starry.admin.modules.weichat.service.NotificationSender;
|
||||||
import com.starry.common.utils.IdUtils;
|
import com.starry.common.utils.IdUtils;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
@@ -45,7 +45,7 @@ public class PlayClerkUserReviewInfoServiceImpl
|
|||||||
@Resource
|
@Resource
|
||||||
private IPlayClerkCommodityService clerkCommodityService;
|
private IPlayClerkCommodityService clerkCommodityService;
|
||||||
@Resource
|
@Resource
|
||||||
private WxCustomMpService wxCustomMpService;
|
private NotificationSender notificationSender;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PlayClerkUserReviewInfoEntity queryByClerkId(String clerkId, String reviewState) {
|
public PlayClerkUserReviewInfoEntity queryByClerkId(String clerkId, String reviewState) {
|
||||||
@@ -186,7 +186,7 @@ public class PlayClerkUserReviewInfoServiceImpl
|
|||||||
this.update(entity);
|
this.update(entity);
|
||||||
|
|
||||||
// 发送消息
|
// 发送消息
|
||||||
wxCustomMpService.sendCheckMessage(entity, userInfo, vo.getReviewState());
|
notificationSender.sendCheckMessage(entity, userInfo, vo.getReviewState());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package com.starry.admin.modules.clerk.task;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.starry.admin.modules.clerk.enums.ClerkMediaUsage;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.clerk.service.IPlayClerkMediaAssetService;
|
||||||
|
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||||
|
import com.starry.admin.modules.media.entity.PlayMediaEntity;
|
||||||
|
import com.starry.admin.modules.media.enums.MediaKind;
|
||||||
|
import com.starry.admin.modules.media.enums.MediaOwnerType;
|
||||||
|
import com.starry.admin.modules.media.enums.MediaStatus;
|
||||||
|
import com.starry.admin.modules.media.service.IPlayMediaService;
|
||||||
|
import com.starry.admin.utils.SecurityUtils;
|
||||||
|
import com.starry.common.utils.IdUtils;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.ApplicationArguments;
|
||||||
|
import org.springframework.boot.ApplicationRunner;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一次性迁移旧相册数据到媒资表。启用方式:启动时配置
|
||||||
|
* {@code clerk.media.migration-enabled=true}。
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ConditionalOnProperty(prefix = "clerk.media", name = "migration-enabled", havingValue = "true")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class ClerkAlbumMigrationRunner implements ApplicationRunner {
|
||||||
|
|
||||||
|
private final IPlayClerkUserInfoService clerkUserInfoService;
|
||||||
|
private final IPlayMediaService mediaService;
|
||||||
|
private final IPlayClerkMediaAssetService clerkMediaAssetService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void run(ApplicationArguments args) {
|
||||||
|
log.info("[ClerkAlbumMigration] start migration from legacy album column");
|
||||||
|
List<PlayClerkUserInfoEntity> candidates = clerkUserInfoService.listWithAlbumIgnoringTenant();
|
||||||
|
|
||||||
|
if (CollectionUtil.isEmpty(candidates)) {
|
||||||
|
log.info("[ClerkAlbumMigration] no clerk records with legacy album found, skip");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicInteger migratedOwners = new AtomicInteger();
|
||||||
|
AtomicInteger migratedMedia = new AtomicInteger();
|
||||||
|
String originalTenantId = SecurityUtils.getTenantId();
|
||||||
|
for (PlayClerkUserInfoEntity clerk : candidates) {
|
||||||
|
String tenantId = StrUtil.blankToDefault(clerk.getTenantId(), originalTenantId);
|
||||||
|
SecurityUtils.setTenantId(tenantId);
|
||||||
|
try {
|
||||||
|
List<String> album = clerk.getAlbum();
|
||||||
|
if (CollectionUtil.isEmpty(album)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<String> sanitizedAlbum = album.stream()
|
||||||
|
.filter(StrUtil::isNotBlank)
|
||||||
|
.map(String::trim)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (CollectionUtil.isEmpty(sanitizedAlbum)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<String> approvedMediaIds = new ArrayList<>();
|
||||||
|
for (String value : sanitizedAlbum) {
|
||||||
|
PlayMediaEntity media = resolveMediaEntity(clerk, value);
|
||||||
|
if (media == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
media.setStatus(MediaStatus.READY.getCode());
|
||||||
|
mediaService.updateById(media);
|
||||||
|
clerkMediaAssetService.linkDraftAsset(clerk.getTenantId(), clerk.getId(), media.getId(),
|
||||||
|
ClerkMediaUsage.PROFILE);
|
||||||
|
approvedMediaIds.add(media.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
clerkMediaAssetService.applyReviewDecision(clerk.getId(), ClerkMediaUsage.PROFILE, approvedMediaIds,
|
||||||
|
null, null);
|
||||||
|
migratedOwners.incrementAndGet();
|
||||||
|
migratedMedia.addAndGet(approvedMediaIds.size());
|
||||||
|
log.info("[ClerkAlbumMigration] processed {} media for clerk {}", approvedMediaIds.size(),
|
||||||
|
clerk.getId());
|
||||||
|
} finally {
|
||||||
|
SecurityUtils.setTenantId(originalTenantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("[ClerkAlbumMigration] completed, owners migrated: {}, media migrated: {}", migratedOwners.get(),
|
||||||
|
migratedMedia.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayMediaEntity resolveMediaEntity(PlayClerkUserInfoEntity clerk, String value) {
|
||||||
|
if (StrUtil.isBlank(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
PlayMediaEntity byId = mediaService.getById(value);
|
||||||
|
if (byId != null) {
|
||||||
|
return byId;
|
||||||
|
}
|
||||||
|
PlayMediaEntity byUrl = mediaService.lambdaQuery()
|
||||||
|
.eq(PlayMediaEntity::getOwnerType, MediaOwnerType.CLERK)
|
||||||
|
.eq(PlayMediaEntity::getOwnerId, clerk.getId())
|
||||||
|
.eq(PlayMediaEntity::getUrl, value)
|
||||||
|
.last("limit 1")
|
||||||
|
.one();
|
||||||
|
if (byUrl != null) {
|
||||||
|
return byUrl;
|
||||||
|
}
|
||||||
|
PlayMediaEntity media = new PlayMediaEntity();
|
||||||
|
media.setId(IdUtils.getUuid());
|
||||||
|
media.setTenantId(clerk.getTenantId());
|
||||||
|
media.setOwnerType(MediaOwnerType.CLERK);
|
||||||
|
media.setOwnerId(clerk.getId());
|
||||||
|
media.setKind(MediaKind.IMAGE.getCode());
|
||||||
|
media.setStatus(MediaStatus.READY.getCode());
|
||||||
|
media.setUrl(value);
|
||||||
|
Map<String, Object> metadata = new HashMap<>();
|
||||||
|
metadata.put("legacySource", "album_migration");
|
||||||
|
media.setMetadata(metadata);
|
||||||
|
mediaService.normalizeAndSave(media);
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,4 +29,9 @@ public interface PlayCustomGiftInfoMapper extends MPJBaseMapper<PlayCustomGiftIn
|
|||||||
int incrementGiftCount(@Param("customId") String customId, @Param("giftId") String giftId,
|
int incrementGiftCount(@Param("customId") String customId, @Param("giftId") String giftId,
|
||||||
@Param("tenantId") String tenantId, @Param("delta") long delta);
|
@Param("tenantId") String tenantId, @Param("delta") long delta);
|
||||||
|
|
||||||
|
@Update("UPDATE play_custom_gift_info SET giff_number = 0, deleted = 0 "
|
||||||
|
+ "WHERE tenant_id = #{tenantId} AND custom_id = #{customId} AND giff_id = #{giftId}")
|
||||||
|
int resetGiftCount(@Param("tenantId") String tenantId, @Param("customId") String customId,
|
||||||
|
@Param("giftId") String giftId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.starry.admin.modules.media.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 媒资表 play_media
|
||||||
|
*
|
||||||
|
* <p>存储各类业务(店员、顾客等)的图片/视频。</p>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName(value = "play_media", autoResultMap = true)
|
||||||
|
public class PlayMediaEntity {
|
||||||
|
|
||||||
|
@TableId
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户ID
|
||||||
|
*/
|
||||||
|
private String tenantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归属业务类型,例如 clerk/custom/order
|
||||||
|
*/
|
||||||
|
private String ownerType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归属业务主键,例如店员ID
|
||||||
|
*/
|
||||||
|
private String ownerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 媒资类型 image / video
|
||||||
|
*/
|
||||||
|
private String kind;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 媒资状态 uploaded / processing / ready / approved / rejected
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源地址
|
||||||
|
*/
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频封面地址
|
||||||
|
*/
|
||||||
|
private String coverUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时长(毫秒)
|
||||||
|
*/
|
||||||
|
private Long durationMs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 媒资宽度
|
||||||
|
*/
|
||||||
|
private Integer width;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 媒资高度
|
||||||
|
*/
|
||||||
|
private Integer height;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件大小(字节)
|
||||||
|
*/
|
||||||
|
private Long sizeBytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序序号,从 0 开始
|
||||||
|
*/
|
||||||
|
private Integer orderIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扩展字段
|
||||||
|
*/
|
||||||
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
|
private Map<String, Object> metadata;
|
||||||
|
|
||||||
|
private Date createdTime;
|
||||||
|
|
||||||
|
private Date updatedTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.starry.admin.modules.media.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum MediaKind {
|
||||||
|
|
||||||
|
IMAGE("image"),
|
||||||
|
VIDEO("video");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
MediaKind(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isVideo(String value) {
|
||||||
|
return VIDEO.code.equalsIgnoreCase(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isImage(String value) {
|
||||||
|
return IMAGE.code.equalsIgnoreCase(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MediaKind fromCode(String value) {
|
||||||
|
for (MediaKind kind : values()) {
|
||||||
|
if (kind.code.equalsIgnoreCase(value)) {
|
||||||
|
return kind;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unsupported media kind: " + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.starry.admin.modules.media.enums;
|
||||||
|
|
||||||
|
public final class MediaOwnerType {
|
||||||
|
|
||||||
|
private MediaOwnerType() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String CLERK = "clerk";
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.starry.admin.modules.media.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum MediaStatus {
|
||||||
|
UPLOADED("uploaded"),
|
||||||
|
PROCESSING("processing"),
|
||||||
|
READY("ready"),
|
||||||
|
APPROVED("approved"),
|
||||||
|
REJECTED("rejected");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
MediaStatus(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isTerminal(String value) {
|
||||||
|
return APPROVED.code.equalsIgnoreCase(value) || REJECTED.code.equalsIgnoreCase(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.starry.admin.modules.media.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.starry.admin.modules.media.entity.PlayMediaEntity;
|
||||||
|
|
||||||
|
public interface PlayMediaMapper extends BaseMapper<PlayMediaEntity> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.starry.admin.modules.media.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.starry.admin.modules.media.entity.PlayMediaEntity;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IPlayMediaService extends IService<PlayMediaEntity> {
|
||||||
|
|
||||||
|
List<PlayMediaEntity> listByOwner(String ownerType, String ownerId);
|
||||||
|
|
||||||
|
List<PlayMediaEntity> listByOwner(String ownerType, String ownerId, Collection<String> statuses);
|
||||||
|
|
||||||
|
List<PlayMediaEntity> listApprovedByOwner(String ownerType, String ownerId);
|
||||||
|
|
||||||
|
PlayMediaEntity normalizeAndSave(PlayMediaEntity entity);
|
||||||
|
|
||||||
|
void updateOrder(String ownerType, String ownerId, List<String> orderedIds);
|
||||||
|
|
||||||
|
void softDelete(String ownerType, String ownerId, String mediaId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package com.starry.admin.modules.media.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.starry.admin.common.exception.CustomException;
|
||||||
|
import com.starry.admin.modules.media.entity.PlayMediaEntity;
|
||||||
|
import com.starry.admin.modules.media.enums.MediaOwnerType;
|
||||||
|
import com.starry.admin.modules.media.enums.MediaStatus;
|
||||||
|
import com.starry.admin.modules.media.mapper.PlayMediaMapper;
|
||||||
|
import com.starry.admin.modules.media.service.IPlayMediaService;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PlayMediaServiceImpl extends ServiceImpl<PlayMediaMapper, PlayMediaEntity>
|
||||||
|
implements IPlayMediaService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PlayMediaEntity> listByOwner(String ownerType, String ownerId) {
|
||||||
|
return listByOwner(ownerType, ownerId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PlayMediaEntity> listByOwner(String ownerType, String ownerId, Collection<String> statuses) {
|
||||||
|
LambdaQueryWrapper<PlayMediaEntity> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(PlayMediaEntity::getOwnerType, ownerType)
|
||||||
|
.eq(PlayMediaEntity::getOwnerId, ownerId)
|
||||||
|
.orderByAsc(PlayMediaEntity::getOrderIndex)
|
||||||
|
.orderByDesc(PlayMediaEntity::getCreatedTime);
|
||||||
|
if (CollectionUtil.isNotEmpty(statuses)) {
|
||||||
|
wrapper.in(PlayMediaEntity::getStatus, statuses);
|
||||||
|
}
|
||||||
|
return this.list(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PlayMediaEntity> listApprovedByOwner(String ownerType, String ownerId) {
|
||||||
|
return listByOwner(ownerType, ownerId, Collections.singleton(MediaStatus.READY.getCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public PlayMediaEntity normalizeAndSave(PlayMediaEntity entity) {
|
||||||
|
Assert.notNull(entity, "媒资信息不能为空");
|
||||||
|
Assert.isTrue(StrUtil.isNotBlank(entity.getOwnerId()), "媒资归属ID不能为空");
|
||||||
|
// ownerType 默认 clerk
|
||||||
|
if (StrUtil.isBlank(entity.getOwnerType())) {
|
||||||
|
entity.setOwnerType(MediaOwnerType.CLERK);
|
||||||
|
}
|
||||||
|
if (entity.getOrderIndex() == null) {
|
||||||
|
entity.setOrderIndex(resolveNextOrderIndex(entity.getOwnerType(), entity.getOwnerId()));
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(entity.getStatus())) {
|
||||||
|
entity.setStatus(MediaStatus.UPLOADED.getCode());
|
||||||
|
}
|
||||||
|
boolean saved = this.save(entity);
|
||||||
|
if (!saved) {
|
||||||
|
throw new CustomException("媒资保存失败");
|
||||||
|
}
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void updateOrder(String ownerType, String ownerId, List<String> orderedIds) {
|
||||||
|
List<PlayMediaEntity> mediaList = listByOwner(ownerType, ownerId);
|
||||||
|
if (CollectionUtil.isEmpty(mediaList)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, PlayMediaEntity> mediaById = mediaList.stream()
|
||||||
|
.collect(Collectors.toMap(PlayMediaEntity::getId, item -> item));
|
||||||
|
Set<String> keepSet = new LinkedHashSet<>();
|
||||||
|
if (CollectionUtil.isNotEmpty(orderedIds)) {
|
||||||
|
keepSet.addAll(orderedIds);
|
||||||
|
}
|
||||||
|
List<PlayMediaEntity> updates = new ArrayList<>();
|
||||||
|
int index = 0;
|
||||||
|
for (String mediaId : keepSet) {
|
||||||
|
PlayMediaEntity entity = mediaById.get(mediaId);
|
||||||
|
if (entity == null) {
|
||||||
|
throw new CustomException("媒资不存在或已被删除");
|
||||||
|
}
|
||||||
|
entity.setOrderIndex(index++);
|
||||||
|
if (MediaStatus.REJECTED.getCode().equals(entity.getStatus())) {
|
||||||
|
entity.setStatus(MediaStatus.READY.getCode());
|
||||||
|
}
|
||||||
|
updates.add(entity);
|
||||||
|
}
|
||||||
|
// 其他未保留的标记为 rejected
|
||||||
|
for (PlayMediaEntity entity : mediaList) {
|
||||||
|
if (!keepSet.contains(entity.getId())
|
||||||
|
&& !MediaStatus.REJECTED.getCode().equals(entity.getStatus())) {
|
||||||
|
entity.setStatus(MediaStatus.REJECTED.getCode());
|
||||||
|
entity.setOrderIndex(0);
|
||||||
|
updates.add(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (CollectionUtil.isNotEmpty(updates)) {
|
||||||
|
this.updateBatchById(updates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void softDelete(String ownerType, String ownerId, String mediaId) {
|
||||||
|
PlayMediaEntity entity = this.getById(mediaId);
|
||||||
|
if (entity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ownerType.equals(entity.getOwnerType()) || !ownerId.equals(entity.getOwnerId())) {
|
||||||
|
throw new CustomException("无权删除该媒资");
|
||||||
|
}
|
||||||
|
entity.setStatus(MediaStatus.REJECTED.getCode());
|
||||||
|
entity.setOrderIndex(0);
|
||||||
|
this.updateById(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int resolveNextOrderIndex(String ownerType, String ownerId) {
|
||||||
|
LambdaQueryWrapper<PlayMediaEntity> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(PlayMediaEntity::getOwnerType, ownerType)
|
||||||
|
.eq(PlayMediaEntity::getOwnerId, ownerId)
|
||||||
|
.ne(PlayMediaEntity::getStatus, MediaStatus.REJECTED.getCode())
|
||||||
|
.orderByDesc(PlayMediaEntity::getOrderIndex)
|
||||||
|
.last("limit 1");
|
||||||
|
PlayMediaEntity last = this.getOne(wrapper, false);
|
||||||
|
if (last == null || last.getOrderIndex() == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return last.getOrderIndex() + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
|||||||
import com.starry.admin.modules.order.module.constant.OrderConstant.OperatorType;
|
import com.starry.admin.modules.order.module.constant.OrderConstant.OperatorType;
|
||||||
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderTriggerSource;
|
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderTriggerSource;
|
||||||
import com.starry.admin.modules.order.module.dto.OrderRefundContext;
|
import com.starry.admin.modules.order.module.dto.OrderRefundContext;
|
||||||
|
import com.starry.admin.modules.order.module.dto.OrderRevocationContext;
|
||||||
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.order.service.IOrderLifecycleService;
|
import com.starry.admin.modules.order.service.IOrderLifecycleService;
|
||||||
@@ -14,6 +15,7 @@ import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
|||||||
import com.starry.admin.modules.shop.module.vo.PlayCommodityInfoVo;
|
import com.starry.admin.modules.shop.module.vo.PlayCommodityInfoVo;
|
||||||
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
|
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
|
||||||
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
||||||
|
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||||
import com.starry.admin.utils.SecurityUtils;
|
import com.starry.admin.utils.SecurityUtils;
|
||||||
import com.starry.common.annotation.Log;
|
import com.starry.common.annotation.Log;
|
||||||
import com.starry.common.context.CustomSecurityContextHolder;
|
import com.starry.common.context.CustomSecurityContextHolder;
|
||||||
@@ -27,6 +29,7 @@ import io.swagger.annotations.ApiResponse;
|
|||||||
import io.swagger.annotations.ApiResponses;
|
import io.swagger.annotations.ApiResponses;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -58,6 +61,9 @@ public class PlayOrderInfoController {
|
|||||||
@Resource
|
@Resource
|
||||||
private IPlayClerkUserInfoService clerkUserInfoService;
|
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IEarningsService earningsService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询订单列表
|
* 分页查询订单列表
|
||||||
*/
|
*/
|
||||||
@@ -76,7 +82,14 @@ public class PlayOrderInfoController {
|
|||||||
public R sendNotice(String orderId) {
|
public R sendNotice(String orderId) {
|
||||||
PlayOrderInfoEntity orderInfo = orderInfoService.selectOrderInfoById(orderId);
|
PlayOrderInfoEntity orderInfo = orderInfoService.selectOrderInfoById(orderId);
|
||||||
List<PlayClerkUserInfoEntity> clerkList = clerkUserInfoService.list(Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class).isNotNull(PlayClerkUserInfoEntity::getOpenid).eq(PlayClerkUserInfoEntity::getClerkState, "1").eq(PlayClerkUserInfoEntity::getSex, orderInfo.getSex()));
|
List<PlayClerkUserInfoEntity> clerkList = clerkUserInfoService.list(Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class).isNotNull(PlayClerkUserInfoEntity::getOpenid).eq(PlayClerkUserInfoEntity::getClerkState, "1").eq(PlayClerkUserInfoEntity::getSex, orderInfo.getSex()));
|
||||||
wxCustomMpService.sendCreateOrderMessageBatch(clerkList, orderInfo.getOrderNo(), orderInfo.getOrderMoney().toString(), orderInfo.getCommodityName(), orderId);
|
wxCustomMpService.sendCreateOrderMessageBatch(
|
||||||
|
clerkList,
|
||||||
|
orderInfo.getOrderNo(),
|
||||||
|
orderInfo.getOrderMoney().toString(),
|
||||||
|
orderInfo.getCommodityName(),
|
||||||
|
orderId,
|
||||||
|
orderInfo.getPlaceType(),
|
||||||
|
orderInfo.getRewardType());
|
||||||
return R.ok();
|
return R.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +112,46 @@ public class PlayOrderInfoController {
|
|||||||
return R.ok("退款成功");
|
return R.ok("退款成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation(value = "撤销已完成订单", notes = "管理员操作撤销,支持可选退款与收益处理")
|
||||||
|
@PostMapping("/revokeCompleted")
|
||||||
|
public R revokeCompleted(@Validated @RequestBody PlayOrderRevocationVo vo) {
|
||||||
|
OrderRevocationContext context = new OrderRevocationContext();
|
||||||
|
context.setOrderId(vo.getOrderId());
|
||||||
|
context.setRefundToCustomer(vo.isRefundToCustomer());
|
||||||
|
context.setRefundAmount(vo.getRefundAmount());
|
||||||
|
context.setRefundReason(vo.getRefundReason());
|
||||||
|
context.setDeductClerkEarnings(vo.isDeductClerkEarnings());
|
||||||
|
context.setEarningsAdjustAmount(vo.getDeductAmount());
|
||||||
|
context.setOperatorType(OperatorType.ADMIN.getCode());
|
||||||
|
context.setOperatorId(SecurityUtils.getUserId());
|
||||||
|
context.withTriggerSource(OrderTriggerSource.ADMIN_API);
|
||||||
|
orderLifecycleService.revokeCompletedOrder(context);
|
||||||
|
return R.ok("撤销成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation(value = "撤销限额", notes = "查询指定订单可退金额与可扣回收益")
|
||||||
|
@GetMapping("/{id}/revocationLimits")
|
||||||
|
public R getRevocationLimits(@PathVariable("id") String id) {
|
||||||
|
PlayOrderInfoEntity order = orderInfoService.selectOrderInfoById(id);
|
||||||
|
BigDecimal maxRefundAmount = Optional.ofNullable(order.getFinalAmount()).orElse(BigDecimal.ZERO);
|
||||||
|
BigDecimal maxDeductAmount = BigDecimal.ZERO;
|
||||||
|
if (order.getAcceptBy() != null) {
|
||||||
|
maxDeductAmount = Optional.ofNullable(earningsService.getRemainingEarningsForOrder(order.getId(), order.getAcceptBy()))
|
||||||
|
.orElse(BigDecimal.ZERO);
|
||||||
|
}
|
||||||
|
if (maxDeductAmount.compareTo(BigDecimal.ZERO) < 0) {
|
||||||
|
maxDeductAmount = BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayOrderRevocationLimitsVo limitsVo = new PlayOrderRevocationLimitsVo();
|
||||||
|
limitsVo.setOrderId(order.getId());
|
||||||
|
limitsVo.setMaxRefundAmount(maxRefundAmount);
|
||||||
|
limitsVo.setMaxDeductAmount(maxDeductAmount);
|
||||||
|
limitsVo.setDefaultDeductAmount(maxDeductAmount);
|
||||||
|
limitsVo.setDeductible(order.getAcceptBy() != null);
|
||||||
|
return R.ok(limitsVo);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理后台强制取消进行中订单
|
* 管理后台强制取消进行中订单
|
||||||
*/
|
*/
|
||||||
@@ -126,8 +179,15 @@ public class PlayOrderInfoController {
|
|||||||
PlayOrderInfoEntity orderInfo = orderInfoService.selectOrderInfoById(vo.getOrderId());
|
PlayOrderInfoEntity orderInfo = orderInfoService.selectOrderInfoById(vo.getOrderId());
|
||||||
PlayCommodityInfoVo commodityInfo = playCommodityInfoService.queryCommodityInfo(orderInfo.getCommodityId(),
|
PlayCommodityInfoVo commodityInfo = playCommodityInfoService.queryCommodityInfo(orderInfo.getCommodityId(),
|
||||||
clerkUserInfo.getLevelId());
|
clerkUserInfo.getLevelId());
|
||||||
wxCustomMpService.sendCreateOrderMessage(clerkUserInfo.getTenantId(), clerkUserInfo.getOpenid(),
|
wxCustomMpService.sendCreateOrderMessage(
|
||||||
orderInfo.getOrderNo(), orderInfo.getOrderMoney().toString(), commodityInfo.getCommodityName(), vo.getOrderId());
|
clerkUserInfo.getTenantId(),
|
||||||
|
clerkUserInfo.getOpenid(),
|
||||||
|
orderInfo.getOrderNo(),
|
||||||
|
orderInfo.getOrderMoney().toString(),
|
||||||
|
commodityInfo.getCommodityName(),
|
||||||
|
vo.getOrderId(),
|
||||||
|
orderInfo.getPlaceType(),
|
||||||
|
orderInfo.getRewardType());
|
||||||
return R.ok("操作成功");
|
return R.ok("操作成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,14 @@ public class OrderJob {
|
|||||||
redisTemplate.opsForValue().set("order_notice_" + orderInfo.getId(), "1", 30, java.util.concurrent.TimeUnit.MINUTES);
|
redisTemplate.opsForValue().set("order_notice_" + orderInfo.getId(), "1", 30, java.util.concurrent.TimeUnit.MINUTES);
|
||||||
List<PlayClerkUserInfoEntity> clerkList = clerkUserInfoService.list(Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class).isNotNull(PlayClerkUserInfoEntity::getOpenid).eq(PlayClerkUserInfoEntity::getClerkState, "1")
|
List<PlayClerkUserInfoEntity> clerkList = clerkUserInfoService.list(Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class).isNotNull(PlayClerkUserInfoEntity::getOpenid).eq(PlayClerkUserInfoEntity::getClerkState, "1")
|
||||||
.eq(PlayClerkUserInfoEntity::getSex, orderInfo.getSex()).eq(PlayClerkUserInfoEntity::getTenantId, orderInfo.getTenantId()));
|
.eq(PlayClerkUserInfoEntity::getSex, orderInfo.getSex()).eq(PlayClerkUserInfoEntity::getTenantId, orderInfo.getTenantId()));
|
||||||
wxCustomMpService.sendCreateOrderMessageBatch(clerkList, orderInfo.getOrderNo(), orderInfo.getOrderMoney().toString(), orderInfo.getCommodityName(), orderInfo.getId());
|
wxCustomMpService.sendCreateOrderMessageBatch(
|
||||||
|
clerkList,
|
||||||
|
orderInfo.getOrderNo(),
|
||||||
|
orderInfo.getOrderMoney().toString(),
|
||||||
|
orderInfo.getCommodityName(),
|
||||||
|
orderInfo.getId(),
|
||||||
|
orderInfo.getPlaceType(),
|
||||||
|
orderInfo.getRewardType());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.starry.admin.modules.order.listener;
|
||||||
|
|
||||||
|
import com.starry.admin.common.exception.CustomException;
|
||||||
|
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant.BalanceOperationType;
|
||||||
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
|
import com.starry.admin.modules.order.module.event.OrderRevocationEvent;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.event.TransactionPhase;
|
||||||
|
import org.springframework.transaction.event.TransactionalEventListener;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class OrderRevocationBalanceListener {
|
||||||
|
|
||||||
|
private final IPlayCustomUserInfoService customUserInfoService;
|
||||||
|
|
||||||
|
public OrderRevocationBalanceListener(IPlayCustomUserInfoService customUserInfoService) {
|
||||||
|
this.customUserInfoService = customUserInfoService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
|
||||||
|
public void handle(OrderRevocationEvent event) {
|
||||||
|
if (event == null || event.getContext() == null || event.getOrderSnapshot() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!event.getContext().isRefundToCustomer()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal refundAmount = Optional.ofNullable(event.getContext().getRefundAmount()).orElse(BigDecimal.ZERO);
|
||||||
|
if (refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayOrderInfoEntity order = event.getOrderSnapshot();
|
||||||
|
PlayCustomUserInfoEntity customer = customUserInfoService.getById(order.getPurchaserBy());
|
||||||
|
if (customer == null) {
|
||||||
|
throw new CustomException("顾客信息不存在");
|
||||||
|
}
|
||||||
|
BigDecimal currentBalance = Optional.ofNullable(customer.getAccountBalance()).orElse(BigDecimal.ZERO);
|
||||||
|
customUserInfoService.updateAccountBalanceById(
|
||||||
|
customer.getId(),
|
||||||
|
currentBalance,
|
||||||
|
currentBalance.add(refundAmount),
|
||||||
|
BalanceOperationType.REFUND.getCode(),
|
||||||
|
"已完成订单撤销退款",
|
||||||
|
refundAmount,
|
||||||
|
BigDecimal.ZERO,
|
||||||
|
order.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.starry.admin.modules.order.listener;
|
||||||
|
|
||||||
|
import com.starry.admin.common.exception.CustomException;
|
||||||
|
import com.starry.admin.modules.order.module.dto.OrderRevocationContext;
|
||||||
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
|
import com.starry.admin.modules.order.module.event.OrderRevocationEvent;
|
||||||
|
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.event.TransactionPhase;
|
||||||
|
import org.springframework.transaction.event.TransactionalEventListener;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class OrderRevocationEarningsListener {
|
||||||
|
|
||||||
|
private final IEarningsService earningsService;
|
||||||
|
|
||||||
|
public OrderRevocationEarningsListener(IEarningsService earningsService) {
|
||||||
|
this.earningsService = earningsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
|
||||||
|
public void handle(OrderRevocationEvent event) {
|
||||||
|
if (event == null || event.getContext() == null || event.getOrderSnapshot() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OrderRevocationContext context = event.getContext();
|
||||||
|
if (!context.isDeductClerkEarnings()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
createCounterLine(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createCounterLine(OrderRevocationEvent event) {
|
||||||
|
OrderRevocationContext context = event.getContext();
|
||||||
|
if (context == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PlayOrderInfoEntity order = event.getOrderSnapshot();
|
||||||
|
String targetClerkId = order.getAcceptBy();
|
||||||
|
if (targetClerkId == null || targetClerkId.trim().isEmpty()) {
|
||||||
|
throw new CustomException("需要指定收益冲销目标账号");
|
||||||
|
}
|
||||||
|
BigDecimal amount = context.getEarningsAdjustAmount();
|
||||||
|
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
amount = Optional.ofNullable(order.getEstimatedRevenue()).orElse(BigDecimal.ZERO);
|
||||||
|
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
earningsService.createCounterLine(order.getId(), order.getTenantId(), targetClerkId, amount, context.getOperatorId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,8 @@ public class OrderConstant {
|
|||||||
ACCEPTED("1", "已接单(待开始)"),
|
ACCEPTED("1", "已接单(待开始)"),
|
||||||
IN_PROGRESS("2", "已开始(服务中)"),
|
IN_PROGRESS("2", "已开始(服务中)"),
|
||||||
COMPLETED("3", "已完成"),
|
COMPLETED("3", "已完成"),
|
||||||
CANCELLED("4", "已取消");
|
CANCELLED("4", "已取消"),
|
||||||
|
REVOKED("5", "已撤销");
|
||||||
|
|
||||||
private final String code;
|
private final String code;
|
||||||
private final String description;
|
private final String description;
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.starry.admin.modules.order.module.constant;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves human-readable labels for order scenarios used in notifications.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public final class OrderMessageLabelResolver {
|
||||||
|
|
||||||
|
private OrderMessageLabelResolver() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String resolve(String placeTypeCode, String rewardTypeCode) {
|
||||||
|
OrderConstant.PlaceType placeTypeEnum = OrderConstant.PlaceType.RANDOM;
|
||||||
|
if (StringUtils.isNotBlank(placeTypeCode)) {
|
||||||
|
try {
|
||||||
|
placeTypeEnum = OrderConstant.PlaceType.fromCode(placeTypeCode);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
log.warn("未知的下单类型,placeTypeCode={},按随机单处理。", placeTypeCode, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (placeTypeEnum) {
|
||||||
|
case SPECIFIED:
|
||||||
|
return "指定单";
|
||||||
|
case REWARD:
|
||||||
|
OrderConstant.RewardType rewardTypeEnum = OrderConstant.RewardType.BALANCE;
|
||||||
|
if (StringUtils.isNotBlank(rewardTypeCode)) {
|
||||||
|
try {
|
||||||
|
rewardTypeEnum = OrderConstant.RewardType.fromCode(rewardTypeCode);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
log.warn("未知的打赏类型,rewardTypeCode={},按打赏处理。", rewardTypeCode, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rewardTypeEnum == OrderConstant.RewardType.GIFT ? "礼物" : "打赏";
|
||||||
|
case RANDOM:
|
||||||
|
default:
|
||||||
|
return "随机单";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.starry.admin.modules.order.module.dto;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderTriggerSource;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class OrderRevocationContext {
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String orderId;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String operatorId;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String operatorType;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private BigDecimal refundAmount;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String refundReason;
|
||||||
|
|
||||||
|
private boolean refundToCustomer;
|
||||||
|
|
||||||
|
private boolean deductClerkEarnings;
|
||||||
|
|
||||||
|
private OrderTriggerSource triggerSource = OrderTriggerSource.UNKNOWN;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private BigDecimal earningsAdjustAmount;
|
||||||
|
|
||||||
|
public OrderRevocationContext withTriggerSource(OrderTriggerSource triggerSource) {
|
||||||
|
this.triggerSource = triggerSource;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.starry.admin.modules.order.module.event;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.order.module.dto.OrderRevocationContext;
|
||||||
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class OrderRevocationEvent {
|
||||||
|
|
||||||
|
private final OrderRevocationContext context;
|
||||||
|
private final PlayOrderInfoEntity orderSnapshot;
|
||||||
|
|
||||||
|
public OrderRevocationEvent(OrderRevocationContext context, PlayOrderInfoEntity orderSnapshot) {
|
||||||
|
this.context = context;
|
||||||
|
this.orderSnapshot = orderSnapshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,12 @@ public class PlayOrderInfoQueryVo extends BasePageEntity {
|
|||||||
@ApiModelProperty(value = "订单编号", example = "ORDER20240320001", notes = "订单的编号,支持模糊查询")
|
@ApiModelProperty(value = "订单编号", example = "ORDER20240320001", notes = "订单的编号,支持模糊查询")
|
||||||
private String orderNo;
|
private String orderNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一关键字(订单号或店员昵称)
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "关键词", example = "ORDER20240320001", notes = "支持订单号或店员昵称模糊查询")
|
||||||
|
private String keyword;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单状态【0:1:2:3:4】 0:已下单(待接单) 1:已接单(待开始) 2:已开始(服务中) 3:已完成 4:已取消
|
* 订单状态【0:1:2:3:4】 0:已下单(待接单) 1:已接单(待开始) 2:已开始(服务中) 3:已完成 4:已取消
|
||||||
*/
|
*/
|
||||||
@@ -54,6 +60,12 @@ public class PlayOrderInfoQueryVo extends BasePageEntity {
|
|||||||
@ApiModelProperty(value = "是否首单", example = "1", notes = "0:不是,1:是")
|
@ApiModelProperty(value = "是否首单", example = "1", notes = "0:不是,1:是")
|
||||||
private String firstOrder;
|
private String firstOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机单要求-店员性别(0:未知;1:男;2:女)
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "店员性别", example = "2", notes = "0:未知;1:男;2:女")
|
||||||
|
private String sex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否使用优惠券[0:未使用,1:已使用]
|
* 是否使用优惠券[0:未使用,1:已使用]
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.starry.admin.modules.order.module.vo;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "撤销限额信息", description = "展示撤销时可退金额、可扣回收益等信息")
|
||||||
|
public class PlayOrderRevocationLimitsVo {
|
||||||
|
|
||||||
|
@ApiModelProperty("订单ID")
|
||||||
|
private String orderId;
|
||||||
|
|
||||||
|
@ApiModelProperty("最大可退金额")
|
||||||
|
private BigDecimal maxRefundAmount = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@ApiModelProperty("最大可扣回收益")
|
||||||
|
private BigDecimal maxDeductAmount = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@ApiModelProperty("建议扣回金额")
|
||||||
|
private BigDecimal defaultDeductAmount = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@ApiModelProperty("是否存在可扣回店员")
|
||||||
|
private boolean deductible;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.starry.admin.modules.order.module.vo;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "订单撤销参数", description = "撤销已完成订单的请求参数")
|
||||||
|
public class PlayOrderRevocationVo {
|
||||||
|
|
||||||
|
@NotBlank(message = "订单ID不能为空")
|
||||||
|
@ApiModelProperty(value = "订单ID", required = true)
|
||||||
|
private String orderId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "是否退还顾客余额")
|
||||||
|
private boolean refundToCustomer;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "退款金额,未填写则默认订单实付金额")
|
||||||
|
private BigDecimal refundAmount;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "撤销原因")
|
||||||
|
private String refundReason;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "是否扣回店员收益")
|
||||||
|
private boolean deductClerkEarnings;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "扣回金额,未填写则默认按本单收益全额扣回")
|
||||||
|
private BigDecimal deductAmount;
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import com.starry.admin.modules.order.module.dto.OrderCreationContext;
|
|||||||
import com.starry.admin.modules.order.module.dto.OrderPlacementCommand;
|
import com.starry.admin.modules.order.module.dto.OrderPlacementCommand;
|
||||||
import com.starry.admin.modules.order.module.dto.OrderPlacementResult;
|
import com.starry.admin.modules.order.module.dto.OrderPlacementResult;
|
||||||
import com.starry.admin.modules.order.module.dto.OrderRefundContext;
|
import com.starry.admin.modules.order.module.dto.OrderRefundContext;
|
||||||
|
import com.starry.admin.modules.order.module.dto.OrderRevocationContext;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
|
|
||||||
public interface IOrderLifecycleService {
|
public interface IOrderLifecycleService {
|
||||||
@@ -14,4 +15,6 @@ public interface IOrderLifecycleService {
|
|||||||
void completeOrder(String orderId, OrderCompletionContext context);
|
void completeOrder(String orderId, OrderCompletionContext context);
|
||||||
|
|
||||||
void refundOrder(OrderRefundContext context);
|
void refundOrder(OrderRefundContext context);
|
||||||
|
|
||||||
|
void revokeCompletedOrder(OrderRevocationContext context);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,6 +199,8 @@ public interface IPlayOrderInfoService extends IService<PlayOrderInfoEntity> {
|
|||||||
*/
|
*/
|
||||||
List<PlayOrderInfoEntity> customSelectOrderInfoByList(String customId);
|
List<PlayOrderInfoEntity> customSelectOrderInfoByList(String customId);
|
||||||
|
|
||||||
|
void revokeCompletedOrder(OrderRevocationContext context);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改订单状态为接单 只有管理员或者店员本人才能操作
|
* 修改订单状态为接单 只有管理员或者店员本人才能操作
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.starry.admin.modules.order.module.dto.OrderPlacementCommand;
|
|||||||
import com.starry.admin.modules.order.module.dto.OrderPlacementResult;
|
import com.starry.admin.modules.order.module.dto.OrderPlacementResult;
|
||||||
import com.starry.admin.modules.order.module.dto.PaymentInfo;
|
import com.starry.admin.modules.order.module.dto.PaymentInfo;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
abstract class AbstractOrderPlacementStrategy implements OrderPlacementStrategy {
|
abstract class AbstractOrderPlacementStrategy implements OrderPlacementStrategy {
|
||||||
|
|
||||||
@@ -26,14 +27,20 @@ abstract class AbstractOrderPlacementStrategy implements OrderPlacementStrategy
|
|||||||
throw new CustomException("支付信息不能为空");
|
throw new CustomException("支付信息不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BigDecimal netAmount = service.normalizeMoney(paymentInfo.getFinalAmount());
|
||||||
|
boolean shouldDeduct = command.isDeductBalance() && service.shouldDeductBalance(context);
|
||||||
|
if (shouldDeduct) {
|
||||||
|
service.validateSufficientBalance(context.getPurchaserBy(), netAmount);
|
||||||
|
}
|
||||||
|
|
||||||
PlayOrderInfoEntity order = service.createOrderRecord(context);
|
PlayOrderInfoEntity order = service.createOrderRecord(context);
|
||||||
|
|
||||||
if (command.isDeductBalance() && service.shouldDeductBalance(context)) {
|
if (shouldDeduct) {
|
||||||
service.deductCustomerBalance(
|
service.deductCustomerBalance(
|
||||||
context.getPurchaserBy(),
|
context.getPurchaserBy(),
|
||||||
service.normalizeMoney(paymentInfo.getFinalAmount()),
|
netAmount,
|
||||||
command.getBalanceOperationAction(),
|
command.getBalanceOperationAction(),
|
||||||
order.getId());
|
context.getOrderId());
|
||||||
}
|
}
|
||||||
|
|
||||||
OrderAmountBreakdown amountBreakdown =
|
OrderAmountBreakdown amountBreakdown =
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.starry.admin.modules.order.module.constant.OrderConstant.OrderStatus;
|
|||||||
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderTriggerSource;
|
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderTriggerSource;
|
||||||
import com.starry.admin.modules.order.module.constant.OrderConstant.OrdersExpiredState;
|
import com.starry.admin.modules.order.module.constant.OrderConstant.OrdersExpiredState;
|
||||||
import com.starry.admin.modules.order.module.constant.OrderConstant.PayMethod;
|
import com.starry.admin.modules.order.module.constant.OrderConstant.PayMethod;
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant.PaymentSource;
|
||||||
import com.starry.admin.modules.order.module.constant.OrderConstant.PlaceType;
|
import com.starry.admin.modules.order.module.constant.OrderConstant.PlaceType;
|
||||||
import com.starry.admin.modules.order.module.constant.OrderConstant.ReviewRequirement;
|
import com.starry.admin.modules.order.module.constant.OrderConstant.ReviewRequirement;
|
||||||
import com.starry.admin.modules.order.module.constant.OrderConstant.YesNoFlag;
|
import com.starry.admin.modules.order.module.constant.OrderConstant.YesNoFlag;
|
||||||
@@ -33,21 +34,24 @@ import com.starry.admin.modules.order.module.dto.OrderCreationContext;
|
|||||||
import com.starry.admin.modules.order.module.dto.OrderPlacementCommand;
|
import com.starry.admin.modules.order.module.dto.OrderPlacementCommand;
|
||||||
import com.starry.admin.modules.order.module.dto.OrderPlacementResult;
|
import com.starry.admin.modules.order.module.dto.OrderPlacementResult;
|
||||||
import com.starry.admin.modules.order.module.dto.OrderRefundContext;
|
import com.starry.admin.modules.order.module.dto.OrderRefundContext;
|
||||||
|
import com.starry.admin.modules.order.module.dto.OrderRevocationContext;
|
||||||
import com.starry.admin.modules.order.module.dto.PaymentInfo;
|
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.dto.RandomOrderRequirements;
|
||||||
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.entity.PlayOrderLogInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderLogInfoEntity;
|
||||||
|
import com.starry.admin.modules.order.module.event.OrderRevocationEvent;
|
||||||
import com.starry.admin.modules.order.module.vo.ClerkEstimatedRevenueVo;
|
import com.starry.admin.modules.order.module.vo.ClerkEstimatedRevenueVo;
|
||||||
import com.starry.admin.modules.order.service.IOrderLifecycleService;
|
import com.starry.admin.modules.order.service.IOrderLifecycleService;
|
||||||
import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService;
|
import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService;
|
||||||
import com.starry.admin.modules.order.service.support.ClerkRevenueCalculator;
|
import com.starry.admin.modules.order.service.support.ClerkRevenueCalculator;
|
||||||
|
import com.starry.admin.modules.personnel.service.IPlayBalanceDetailsInfoService;
|
||||||
import com.starry.admin.modules.shop.module.constant.CouponUseState;
|
import com.starry.admin.modules.shop.module.constant.CouponUseState;
|
||||||
import com.starry.admin.modules.shop.module.entity.PlayCouponDetailsEntity;
|
import com.starry.admin.modules.shop.module.entity.PlayCouponDetailsEntity;
|
||||||
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
|
import com.starry.admin.modules.shop.module.entity.PlayCouponInfoEntity;
|
||||||
import com.starry.admin.modules.shop.module.enums.CouponDiscountType;
|
import com.starry.admin.modules.shop.module.enums.CouponDiscountType;
|
||||||
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
|
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
|
||||||
import com.starry.admin.modules.shop.service.IPlayCouponInfoService;
|
import com.starry.admin.modules.shop.service.IPlayCouponInfoService;
|
||||||
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
import com.starry.admin.modules.weichat.service.NotificationSender;
|
||||||
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||||
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||||
import com.starry.admin.utils.SecurityUtils;
|
import com.starry.admin.utils.SecurityUtils;
|
||||||
@@ -59,9 +63,11 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@@ -75,7 +81,8 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
private enum LifecycleOperation {
|
private enum LifecycleOperation {
|
||||||
CREATE,
|
CREATE,
|
||||||
COMPLETE,
|
COMPLETE,
|
||||||
REFUND
|
REFUND,
|
||||||
|
REVOKE_COMPLETED
|
||||||
}
|
}
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
@@ -85,7 +92,7 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
private IEarningsService earningsService;
|
private IEarningsService earningsService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private WxCustomMpService wxCustomMpService;
|
private NotificationSender notificationSender;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private IPlayOrderRefundInfoService orderRefundInfoService;
|
private IPlayOrderRefundInfoService orderRefundInfoService;
|
||||||
@@ -105,6 +112,12 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
@Resource
|
@Resource
|
||||||
private PlayOrderLogInfoMapper orderLogInfoMapper;
|
private PlayOrderLogInfoMapper orderLogInfoMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IPlayBalanceDetailsInfoService playBalanceDetailsInfoService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
private Map<StrategyKey, OrderPlacementStrategy> placementStrategies;
|
private Map<StrategyKey, OrderPlacementStrategy> placementStrategies;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
@@ -162,6 +175,7 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
|
|
||||||
validateCouponUsage(context);
|
validateCouponUsage(context);
|
||||||
|
|
||||||
|
|
||||||
OrderConstant.RewardType rewardType = context.getRewardType() != null
|
OrderConstant.RewardType rewardType = context.getRewardType() != null
|
||||||
? context.getRewardType()
|
? context.getRewardType()
|
||||||
: OrderConstant.RewardType.NOT_APPLICABLE;
|
: OrderConstant.RewardType.NOT_APPLICABLE;
|
||||||
@@ -278,6 +292,18 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
return OrderAmountBreakdown.of(grossAmount, discountAmount, netAmount);
|
return OrderAmountBreakdown.of(grossAmount, discountAmount, netAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void validateSufficientBalance(String customerId, BigDecimal requiredAmount) {
|
||||||
|
PlayCustomUserInfoEntity customer = customUserInfoService.selectById(customerId);
|
||||||
|
if (customer == null) {
|
||||||
|
throw new CustomException("顾客不存在");
|
||||||
|
}
|
||||||
|
BigDecimal before = normalizeMoney(customer.getAccountBalance());
|
||||||
|
BigDecimal required = normalizeMoney(requiredAmount);
|
||||||
|
if (required.compareTo(before) > 0) {
|
||||||
|
throw new ServiceException("余额不足", 998);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void deductCustomerBalance(
|
void deductCustomerBalance(
|
||||||
String customerId,
|
String customerId,
|
||||||
BigDecimal netAmount,
|
BigDecimal netAmount,
|
||||||
@@ -288,10 +314,11 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
throw new CustomException("顾客不存在");
|
throw new CustomException("顾客不存在");
|
||||||
}
|
}
|
||||||
BigDecimal before = normalizeMoney(customer.getAccountBalance());
|
BigDecimal before = normalizeMoney(customer.getAccountBalance());
|
||||||
if (netAmount.compareTo(before) > 0) {
|
BigDecimal amountToDeduct = normalizeMoney(netAmount);
|
||||||
|
if (amountToDeduct.compareTo(before) > 0) {
|
||||||
throw new ServiceException("余额不足", 998);
|
throw new ServiceException("余额不足", 998);
|
||||||
}
|
}
|
||||||
BigDecimal after = normalizeMoney(before.subtract(netAmount));
|
BigDecimal after = normalizeMoney(before.subtract(amountToDeduct));
|
||||||
String action = StrUtil.isNotBlank(operationAction) ? operationAction : "下单";
|
String action = StrUtil.isNotBlank(operationAction) ? operationAction : "下单";
|
||||||
customUserInfoService.updateAccountBalanceById(
|
customUserInfoService.updateAccountBalanceById(
|
||||||
customerId,
|
customerId,
|
||||||
@@ -299,7 +326,7 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
after,
|
after,
|
||||||
BalanceOperationType.CONSUME.getCode(),
|
BalanceOperationType.CONSUME.getCode(),
|
||||||
action,
|
action,
|
||||||
netAmount,
|
amountToDeduct,
|
||||||
BigDecimal.ZERO,
|
BigDecimal.ZERO,
|
||||||
orderId);
|
orderId);
|
||||||
}
|
}
|
||||||
@@ -466,7 +493,7 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldNotify) {
|
if (shouldNotify) {
|
||||||
wxCustomMpService.sendOrderFinishMessageAsync(latest);
|
notificationSender.sendOrderFinishMessageAsync(latest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,6 +528,11 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
throw new CustomException("每个订单只能退款一次~");
|
throw new CustomException("每个订单只能退款一次~");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isBalancePaidOrder(order)
|
||||||
|
&& !playBalanceDetailsInfoService.existsCustomerConsumeRecord(order.getPurchaserBy(), order.getId())) {
|
||||||
|
throw new CustomException("订单未发生余额扣款,无法退款");
|
||||||
|
}
|
||||||
|
|
||||||
UpdateWrapper<PlayOrderInfoEntity> refundUpdate = new UpdateWrapper<>();
|
UpdateWrapper<PlayOrderInfoEntity> refundUpdate = new UpdateWrapper<>();
|
||||||
refundUpdate.eq("id", order.getId())
|
refundUpdate.eq("id", order.getId())
|
||||||
.eq("refund_type", OrderRefundFlag.NOT_REFUNDED.getCode())
|
.eq("refund_type", OrderRefundFlag.NOT_REFUNDED.getCode())
|
||||||
@@ -567,6 +599,136 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
refundOperationType);
|
refundOperationType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isBalancePaidOrder(PlayOrderInfoEntity order) {
|
||||||
|
String sourceCode = order.getPaymentSource();
|
||||||
|
if (StrUtil.isBlank(sourceCode)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return PaymentSource.fromCode(sourceCode) == PaymentSource.BALANCE;
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
log.warn("Unknown payment source {}, defaulting to balance for refund guard", sourceCode);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void revokeCompletedOrder(OrderRevocationContext context) {
|
||||||
|
if (context == null || StrUtil.isBlank(context.getOrderId())) {
|
||||||
|
throw new CustomException("订单ID不能为空");
|
||||||
|
}
|
||||||
|
PlayOrderInfoEntity order = orderInfoMapper.selectById(context.getOrderId());
|
||||||
|
if (order == null) {
|
||||||
|
throw new CustomException("订单不存在");
|
||||||
|
}
|
||||||
|
if (OrderStatus.REVOKED.getCode().equals(order.getOrderStatus())) {
|
||||||
|
throw new CustomException("订单已撤销");
|
||||||
|
}
|
||||||
|
if (!OrderStatus.COMPLETED.getCode().equals(order.getOrderStatus())) {
|
||||||
|
throw new CustomException("当前状态无法撤销");
|
||||||
|
}
|
||||||
|
if (!OrderConstant.OrderType.NORMAL.getCode().equals(order.getOrderType())) {
|
||||||
|
throw new CustomException("仅支持撤销普通服务订单");
|
||||||
|
}
|
||||||
|
|
||||||
|
String operatorType = StrUtil.isNotBlank(context.getOperatorType()) ? context.getOperatorType() : OperatorType.ADMIN.getCode();
|
||||||
|
context.setOperatorType(operatorType);
|
||||||
|
String operatorId = StrUtil.isNotBlank(context.getOperatorId()) ? context.getOperatorId() : SecurityUtils.getUserId();
|
||||||
|
context.setOperatorId(operatorId);
|
||||||
|
if (context.isDeductClerkEarnings()) {
|
||||||
|
String targetClerkId = order.getAcceptBy();
|
||||||
|
if (StrUtil.isBlank(targetClerkId)) {
|
||||||
|
throw new CustomException("未找到可冲销的店员收益账号");
|
||||||
|
}
|
||||||
|
BigDecimal availableEarnings = Optional.ofNullable(
|
||||||
|
earningsService.getRemainingEarningsForOrder(order.getId(), targetClerkId))
|
||||||
|
.orElse(BigDecimal.ZERO);
|
||||||
|
if (availableEarnings.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
throw new CustomException("本单店员收益已全部扣回");
|
||||||
|
}
|
||||||
|
BigDecimal requested = context.getEarningsAdjustAmount();
|
||||||
|
if (requested == null || requested.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
requested = availableEarnings;
|
||||||
|
}
|
||||||
|
if (requested.compareTo(availableEarnings) > 0) {
|
||||||
|
throw new CustomException("扣回金额不能超过本单收益" + availableEarnings);
|
||||||
|
}
|
||||||
|
context.setEarningsAdjustAmount(requested);
|
||||||
|
} else {
|
||||||
|
context.setEarningsAdjustAmount(BigDecimal.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal finalAmount = Optional.ofNullable(order.getFinalAmount()).orElse(BigDecimal.ZERO);
|
||||||
|
BigDecimal refundAmount = context.getRefundAmount();
|
||||||
|
if (refundAmount == null) {
|
||||||
|
refundAmount = context.isRefundToCustomer() ? finalAmount : BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
if (refundAmount.compareTo(BigDecimal.ZERO) < 0) {
|
||||||
|
throw new CustomException("退款金额不能小于0");
|
||||||
|
}
|
||||||
|
if (refundAmount.compareTo(finalAmount) > 0) {
|
||||||
|
throw new CustomException("退款金额不能大于支付金额");
|
||||||
|
}
|
||||||
|
context.setRefundAmount(refundAmount);
|
||||||
|
|
||||||
|
if (refundAmount.compareTo(BigDecimal.ZERO) > 0 && context.isRefundToCustomer()) {
|
||||||
|
if (isBalancePaidOrder(order)
|
||||||
|
&& !playBalanceDetailsInfoService.existsCustomerConsumeRecord(order.getPurchaserBy(), order.getId())) {
|
||||||
|
throw new CustomException("订单未发生余额扣款,无法退款");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateWrapper<PlayOrderInfoEntity> updateWrapper = new UpdateWrapper<>();
|
||||||
|
updateWrapper.eq("id", order.getId())
|
||||||
|
.eq("order_status", OrderStatus.COMPLETED.getCode())
|
||||||
|
.set("order_status", OrderStatus.REVOKED.getCode())
|
||||||
|
.set("order_cancel_time", LocalDateTime.now())
|
||||||
|
.set("refund_amount", refundAmount)
|
||||||
|
.set("refund_reason", context.getRefundReason());
|
||||||
|
if (refundAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
updateWrapper.set("refund_type", OrderRefundFlag.REFUNDED.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean updated = orderInfoMapper.update(null, updateWrapper) > 0;
|
||||||
|
PlayOrderInfoEntity latest = orderInfoMapper.selectById(order.getId());
|
||||||
|
if (!updated && (latest == null || !OrderStatus.REVOKED.getCode().equals(latest.getOrderStatus()))) {
|
||||||
|
throw new CustomException("订单状态已变化,无法撤销");
|
||||||
|
}
|
||||||
|
if (latest == null) {
|
||||||
|
latest = order;
|
||||||
|
latest.setOrderStatus(OrderStatus.REVOKED.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refundAmount.compareTo(BigDecimal.ZERO) > 0 && context.isRefundToCustomer()) {
|
||||||
|
OrderRefundRecordType recordType = finalAmount.compareTo(refundAmount) == 0
|
||||||
|
? OrderRefundRecordType.FULL
|
||||||
|
: OrderRefundRecordType.PARTIAL;
|
||||||
|
orderRefundInfoService.add(
|
||||||
|
latest.getId(),
|
||||||
|
latest.getPurchaserBy(),
|
||||||
|
latest.getAcceptBy(),
|
||||||
|
latest.getPayMethod(),
|
||||||
|
recordType.getCode(),
|
||||||
|
refundAmount,
|
||||||
|
context.getRefundReason(),
|
||||||
|
context.getOperatorType(),
|
||||||
|
context.getOperatorId(),
|
||||||
|
OrderRefundState.PROCESSING.getCode(),
|
||||||
|
ReviewRequirement.NOT_REQUIRED.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
OrderActor actor = resolveCompletionActor(context.getOperatorType());
|
||||||
|
String operationType = String.format(
|
||||||
|
"%s_%s",
|
||||||
|
LifecycleOperation.REVOKE_COMPLETED.name(),
|
||||||
|
context.isDeductClerkEarnings() ? "DEDUCT" : "KEEP");
|
||||||
|
recordOrderLog(latest, actor, context.getOperatorId(), LifecycleOperation.REVOKE_COMPLETED,
|
||||||
|
context.getRefundReason(), operationType);
|
||||||
|
|
||||||
|
applicationEventPublisher.publishEvent(new OrderRevocationEvent(context, latest));
|
||||||
|
}
|
||||||
|
|
||||||
private void validateOrderCreationRequest(OrderCreationContext context) {
|
private void validateOrderCreationRequest(OrderCreationContext context) {
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
throw new CustomException("订单创建请求不能为空");
|
throw new CustomException("订单创建请求不能为空");
|
||||||
@@ -711,6 +873,10 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean ensureEarnings(PlayOrderInfoEntity order, OrderTriggerSource source) {
|
private boolean ensureEarnings(PlayOrderInfoEntity order, OrderTriggerSource source) {
|
||||||
|
if (OrderConstant.OrderType.BLIND_BOX_PURCHASE.getCode().equals(order.getOrderType())) {
|
||||||
|
log.debug("Skip earnings creation for blind box purchase order {}", order.getId());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
Long existing = earningsService.lambdaQuery()
|
Long existing = earningsService.lambdaQuery()
|
||||||
.eq(EarningsLineEntity::getTenantId, order.getTenantId())
|
.eq(EarningsLineEntity::getTenantId, order.getTenantId())
|
||||||
.eq(EarningsLineEntity::getOrderId, order.getId())
|
.eq(EarningsLineEntity::getOrderId, order.getId())
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import com.starry.admin.modules.order.module.vo.PlayOrderComplaintQueryVo;
|
|||||||
import com.starry.admin.modules.order.module.vo.PlayOrderComplaintReturnVo;
|
import com.starry.admin.modules.order.module.vo.PlayOrderComplaintReturnVo;
|
||||||
import com.starry.admin.modules.order.service.IPlayOrderComplaintInfoService;
|
import com.starry.admin.modules.order.service.IPlayOrderComplaintInfoService;
|
||||||
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
|
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
|
||||||
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
import com.starry.admin.modules.weichat.service.NotificationSender;
|
||||||
import com.starry.admin.utils.SecurityUtils;
|
import com.starry.admin.utils.SecurityUtils;
|
||||||
import com.starry.common.utils.IdUtils;
|
import com.starry.common.utils.IdUtils;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -46,7 +46,7 @@ public class PlayOrderComplaintInfoServiceImpl
|
|||||||
@Resource
|
@Resource
|
||||||
private IPlayPersonnelGroupInfoService playClerkGroupInfoService;
|
private IPlayPersonnelGroupInfoService playClerkGroupInfoService;
|
||||||
@Resource
|
@Resource
|
||||||
private WxCustomMpService wxCustomMpService;
|
private NotificationSender notificationSender;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询订单投诉信息
|
* 查询订单投诉信息
|
||||||
@@ -169,7 +169,7 @@ public class PlayOrderComplaintInfoServiceImpl
|
|||||||
playOrderComplaintInfo.setClerkId(orderInfo.getAcceptBy());
|
playOrderComplaintInfo.setClerkId(orderInfo.getAcceptBy());
|
||||||
playOrderComplaintInfo.setCommodityId(orderInfo.getCommodityId());
|
playOrderComplaintInfo.setCommodityId(orderInfo.getCommodityId());
|
||||||
// 发送投诉消息给管理员以及客服
|
// 发送投诉消息给管理员以及客服
|
||||||
wxCustomMpService.sendComplaintMessage(playOrderComplaintInfo, orderInfo);
|
notificationSender.sendComplaintMessage(playOrderComplaintInfo, orderInfo);
|
||||||
return save(playOrderComplaintInfo);
|
return save(playOrderComplaintInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.starry.admin.modules.order.service.impl;
|
package com.starry.admin.modules.order.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
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.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||||
@@ -41,10 +43,11 @@ import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService;
|
|||||||
import com.starry.admin.modules.order.service.support.ClerkRevenueCalculator;
|
import com.starry.admin.modules.order.service.support.ClerkRevenueCalculator;
|
||||||
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
|
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
|
||||||
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
|
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
|
||||||
|
import com.starry.admin.modules.shop.module.constant.CouponUseState;
|
||||||
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo;
|
import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo;
|
||||||
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
|
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
|
||||||
import com.starry.admin.modules.weichat.entity.order.*;
|
import com.starry.admin.modules.weichat.entity.order.*;
|
||||||
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
import com.starry.admin.modules.weichat.service.NotificationSender;
|
||||||
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||||
import com.starry.admin.utils.DateRangeUtils;
|
import com.starry.admin.utils.DateRangeUtils;
|
||||||
import com.starry.admin.utils.SecurityUtils;
|
import com.starry.admin.utils.SecurityUtils;
|
||||||
@@ -95,7 +98,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
private IPlayCouponDetailsService playCouponDetailsService;
|
private IPlayCouponDetailsService playCouponDetailsService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private WxCustomMpService wxCustomMpService;
|
private NotificationSender notificationSender;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private IPlayCustomUserInfoService customUserInfoService;
|
private IPlayCustomUserInfoService customUserInfoService;
|
||||||
@@ -239,7 +242,6 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
public List<PlayOrderInfoEntity> listByEndTime(String clerkId, LocalDateTime endTime) {
|
public List<PlayOrderInfoEntity> listByEndTime(String clerkId, LocalDateTime endTime) {
|
||||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
|
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
|
||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getAcceptBy, clerkId);
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getAcceptBy, clerkId);
|
||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderStatus, OrderStatus.COMPLETED.getCode());
|
|
||||||
lambdaQueryWrapper.lt(PlayOrderInfoEntity::getOrderEndTime, endTime);
|
lambdaQueryWrapper.lt(PlayOrderInfoEntity::getOrderEndTime, endTime);
|
||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderSettlementState, "0");
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderSettlementState, "0");
|
||||||
return this.baseMapper.selectList(lambdaQueryWrapper);
|
return this.baseMapper.selectList(lambdaQueryWrapper);
|
||||||
@@ -400,7 +402,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
public IPage<PlayOrderInfoReturnVo> selectOrderInfoPage(PlayOrderInfoQueryVo vo) {
|
public IPage<PlayOrderInfoReturnVo> selectOrderInfoPage(PlayOrderInfoQueryVo vo) {
|
||||||
|
|
||||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
|
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
|
||||||
ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class));
|
ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class), vo.getKeyword());
|
||||||
lambdaQueryWrapper.in(PlayOrderInfoEntity::getPlaceType, "0", "1", "2");
|
lambdaQueryWrapper.in(PlayOrderInfoEntity::getPlaceType, "0", "1", "2");
|
||||||
if (StringUtils.isNotBlank(vo.getGroupId())) {
|
if (StringUtils.isNotBlank(vo.getGroupId())) {
|
||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getGroupId, vo.getGroupId());
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getGroupId, vo.getGroupId());
|
||||||
@@ -420,6 +422,25 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
if (StringUtils.isNotBlank(vo.getOrderType())) {
|
if (StringUtils.isNotBlank(vo.getOrderType())) {
|
||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderType, vo.getOrderType());
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderType, vo.getOrderType());
|
||||||
}
|
}
|
||||||
|
if (StringUtils.isNotBlank(vo.getSex())) {
|
||||||
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getSex, vo.getSex());
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(vo.getPayMethod())) {
|
||||||
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getPayMethod, vo.getPayMethod());
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(vo.getUseCoupon())) {
|
||||||
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getUseCoupon, vo.getUseCoupon());
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(vo.getBackendEntry())) {
|
||||||
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getBackendEntry, vo.getBackendEntry());
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(vo.getFirstOrder())) {
|
||||||
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getFirstOrder, vo.getFirstOrder());
|
||||||
|
}
|
||||||
|
applyRangeFilter(lambdaQueryWrapper, vo.getPurchaserTime(), PlayOrderInfoEntity::getPurchaserTime);
|
||||||
|
applyRangeFilter(lambdaQueryWrapper, vo.getAcceptTime(), PlayOrderInfoEntity::getAcceptTime);
|
||||||
|
applyRangeFilter(lambdaQueryWrapper, vo.getStartOrderTime(), PlayOrderInfoEntity::getOrderStartTime);
|
||||||
|
applyRangeFilter(lambdaQueryWrapper, vo.getEndOrderTime(), PlayOrderInfoEntity::getOrderEndTime);
|
||||||
// 加入组员的筛选,要么acceptBy为空,要么就在in里面
|
// 加入组员的筛选,要么acceptBy为空,要么就在in里面
|
||||||
List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(),
|
List<String> clerkIdList = playClerkGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(),
|
||||||
vo.getClerkNickName());
|
vo.getClerkNickName());
|
||||||
@@ -433,7 +454,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
public PlayClerkOrderDetailsReturnVo clerkSelectOrderDetails(String clerkId, String orderId) {
|
public PlayClerkOrderDetailsReturnVo clerkSelectOrderDetails(String clerkId, String orderId) {
|
||||||
PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
|
PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
|
||||||
entity.setId(orderId);
|
entity.setId(orderId);
|
||||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(entity);
|
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(entity, null);
|
||||||
// 拼接用户等级
|
// 拼接用户等级
|
||||||
lambdaQueryWrapper.selectAs(PlayCustomLevelInfoEntity::getId, "customLevelId")
|
lambdaQueryWrapper.selectAs(PlayCustomLevelInfoEntity::getId, "customLevelId")
|
||||||
.selectAs(PlayCustomLevelInfoEntity::getName, "customLevelName");
|
.selectAs(PlayCustomLevelInfoEntity::getName, "customLevelName");
|
||||||
@@ -484,7 +505,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
@Override
|
@Override
|
||||||
public IPage<PlayClerkOrderListReturnVo> clerkSelectOrderInfoByPage(PlayClerkOrderInfoQueryVo vo) {
|
public IPage<PlayClerkOrderListReturnVo> clerkSelectOrderInfoByPage(PlayClerkOrderInfoQueryVo vo) {
|
||||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
|
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
|
||||||
ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class));
|
ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class), null);
|
||||||
// 拼接用户等级
|
// 拼接用户等级
|
||||||
lambdaQueryWrapper.selectAs(PlayCustomLevelInfoEntity::getId, "customLevelId")
|
lambdaQueryWrapper.selectAs(PlayCustomLevelInfoEntity::getId, "customLevelId")
|
||||||
.selectAs(PlayCustomLevelInfoEntity::getName, "customLevelName");
|
.selectAs(PlayCustomLevelInfoEntity::getName, "customLevelName");
|
||||||
@@ -499,7 +520,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
|
PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
|
||||||
entity.setId(orderId);
|
entity.setId(orderId);
|
||||||
entity.setPurchaserBy(customId);
|
entity.setPurchaserBy(customId);
|
||||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(entity);
|
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(entity, null);
|
||||||
PlayCustomOrderDetailsReturnVo returnVo = this.baseMapper.selectJoinOne(PlayCustomOrderDetailsReturnVo.class,
|
PlayCustomOrderDetailsReturnVo returnVo = this.baseMapper.selectJoinOne(PlayCustomOrderDetailsReturnVo.class,
|
||||||
lambdaQueryWrapper);
|
lambdaQueryWrapper);
|
||||||
// 如果订单状态为退款,查询订单退款原因
|
// 如果订单状态为退款,查询订单退款原因
|
||||||
@@ -525,7 +546,12 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
@Override
|
@Override
|
||||||
public IPage<PlayCustomOrderListReturnVo> customSelectOrderInfoByPage(PlayCustomOrderInfoQueryVo vo) {
|
public IPage<PlayCustomOrderListReturnVo> customSelectOrderInfoByPage(PlayCustomOrderInfoQueryVo vo) {
|
||||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
|
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = getCommonOrderQueryVo(
|
||||||
ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class));
|
ConvertUtil.entityToVo(vo, PlayOrderInfoEntity.class), null);
|
||||||
|
if (StringUtils.isBlank(vo.getOrderType())) {
|
||||||
|
lambdaQueryWrapper.notIn(PlayOrderInfoEntity::getOrderType,
|
||||||
|
OrderConstant.OrderType.RECHARGE.getCode(),
|
||||||
|
OrderConstant.OrderType.WITHDRAWAL.getCode());
|
||||||
|
}
|
||||||
IPage<PlayCustomOrderListReturnVo> page = this.baseMapper.selectJoinPage(
|
IPage<PlayCustomOrderListReturnVo> page = this.baseMapper.selectJoinPage(
|
||||||
new Page<>(vo.getPageNum(), vo.getPageSize()), PlayCustomOrderListReturnVo.class, lambdaQueryWrapper);
|
new Page<>(vo.getPageNum(), vo.getPageSize()), PlayCustomOrderListReturnVo.class, lambdaQueryWrapper);
|
||||||
// 获取当前顾客所有订单评价信息,将订单评价信息转化为 map<订单ID,订单ID>的结构
|
// 获取当前顾客所有订单评价信息,将订单评价信息转化为 map<订单ID,订单ID>的结构
|
||||||
@@ -644,7 +670,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
orderInfo.setFirstOrder(firstOrderFlag);
|
orderInfo.setFirstOrder(firstOrderFlag);
|
||||||
log.info("Order accepted successfully. orderId={}, orderNo={}, acceptBy={}, operatorByType={}",
|
log.info("Order accepted successfully. orderId={}, orderNo={}, acceptBy={}, operatorByType={}",
|
||||||
orderId, orderInfo.getOrderNo(), acceptBy, operatorByType);
|
orderId, orderInfo.getOrderNo(), acceptBy, operatorByType);
|
||||||
wxCustomMpService.sendOrderMessageAsync(orderInfo);
|
notificationSender.sendOrderMessageAsync(orderInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateClerkQualificationForRandomOrder(PlayOrderInfoEntity orderInfo,
|
private void validateClerkQualificationForRandomOrder(PlayOrderInfoEntity orderInfo,
|
||||||
@@ -685,7 +711,7 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
*
|
*
|
||||||
* @return MPJLambdaWrapper<PlayOrderInfoEntity>
|
* @return MPJLambdaWrapper<PlayOrderInfoEntity>
|
||||||
*/
|
*/
|
||||||
public MPJLambdaWrapper<PlayOrderInfoEntity> getCommonOrderQueryVo(PlayOrderInfoEntity entity) {
|
public MPJLambdaWrapper<PlayOrderInfoEntity> getCommonOrderQueryVo(PlayOrderInfoEntity entity, String keyword) {
|
||||||
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
|
MPJLambdaWrapper<PlayOrderInfoEntity> lambdaQueryWrapper = new MPJLambdaWrapper<>();
|
||||||
// 查询主表全部字段
|
// 查询主表全部字段
|
||||||
lambdaQueryWrapper.selectAll(PlayOrderInfoEntity.class);
|
lambdaQueryWrapper.selectAll(PlayOrderInfoEntity.class);
|
||||||
@@ -722,11 +748,43 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderType, entity.getOrderType());
|
lambdaQueryWrapper.eq(PlayOrderInfoEntity::getOrderType, entity.getOrderType());
|
||||||
}
|
}
|
||||||
lambdaQueryWrapper.like(StringUtils.isNotEmpty(entity.getOrderNo()), PlayOrderInfoEntity::getOrderNo, entity.getOrderNo());
|
lambdaQueryWrapper.like(StringUtils.isNotEmpty(entity.getOrderNo()), PlayOrderInfoEntity::getOrderNo, entity.getOrderNo());
|
||||||
|
if (StringUtils.isNotBlank(keyword)) {
|
||||||
|
lambdaQueryWrapper.and(w -> w.like(PlayOrderInfoEntity::getOrderNo, keyword)
|
||||||
|
.or()
|
||||||
|
.like(PlayClerkUserInfoEntity::getNickname, keyword));
|
||||||
|
}
|
||||||
lambdaQueryWrapper.orderByDesc(PlayOrderInfoEntity::getCreatedTime);
|
lambdaQueryWrapper.orderByDesc(PlayOrderInfoEntity::getCreatedTime);
|
||||||
return lambdaQueryWrapper;
|
return lambdaQueryWrapper;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyRangeFilter(MPJLambdaWrapper<PlayOrderInfoEntity> wrapper, List<String> range,
|
||||||
|
SFunction<PlayOrderInfoEntity, ?> column) {
|
||||||
|
if (CollectionUtil.isEmpty(range)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String start = normalizeRangeValue(range, 0, false);
|
||||||
|
String end = normalizeRangeValue(range, 1, true);
|
||||||
|
if (StrUtil.isNotBlank(start) && StrUtil.isNotBlank(end)) {
|
||||||
|
wrapper.between(column, start, end);
|
||||||
|
} else if (StrUtil.isNotBlank(start)) {
|
||||||
|
wrapper.ge(column, start);
|
||||||
|
} else if (StrUtil.isNotBlank(end)) {
|
||||||
|
wrapper.le(column, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeRangeValue(List<String> range, int index, boolean isEnd) {
|
||||||
|
if (range.size() <= index) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String raw = range.get(index);
|
||||||
|
if (StrUtil.isBlank(raw)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return isEnd ? DateRangeUtils.normalizeEndOptional(raw) : DateRangeUtils.normalizeStartOptional(raw);
|
||||||
|
}
|
||||||
|
|
||||||
public void updateStateTo23(String operatorByType, String operatorBy, String orderState, String orderId) {
|
public void updateStateTo23(String operatorByType, String operatorBy, String orderState, String orderId) {
|
||||||
OperatorType operatorType = resolveOperatorTypeOrThrow(operatorByType);
|
OperatorType operatorType = resolveOperatorTypeOrThrow(operatorByType);
|
||||||
boolean isCustomer = operatorType == OperatorType.CUSTOMER;
|
boolean isCustomer = operatorType == OperatorType.CUSTOMER;
|
||||||
@@ -835,7 +893,8 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
playOrderRefundInfoService.add(orderInfo.getId(), orderInfo.getPurchaserBy(), orderInfo.getAcceptBy(),
|
playOrderRefundInfoService.add(orderInfo.getId(), orderInfo.getPurchaserBy(), orderInfo.getAcceptBy(),
|
||||||
orderInfo.getPayMethod(), OrderRefundRecordType.FULL.getCode(), orderInfo.getFinalAmount(), refundReason, operatorByType, operatorBy,
|
orderInfo.getPayMethod(), OrderRefundRecordType.FULL.getCode(), orderInfo.getFinalAmount(), refundReason, operatorByType, operatorBy,
|
||||||
OrderRefundState.PROCESSING.getCode(), ReviewRequirement.NOT_REQUIRED.getCode());
|
OrderRefundState.PROCESSING.getCode(), ReviewRequirement.NOT_REQUIRED.getCode());
|
||||||
wxCustomMpService.sendOrderCancelMessageAsync(orderInfo, refundReason);
|
restoreCouponsForOrder(orderInfo);
|
||||||
|
notificationSender.sendOrderCancelMessageAsync(orderInfo, refundReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -888,8 +947,18 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
orderInfo.getPayMethod(), forceCancelRefundType.getCode(), actualRefundAmount, refundReason, operatorByType, operatorBy,
|
orderInfo.getPayMethod(), forceCancelRefundType.getCode(), actualRefundAmount, refundReason, operatorByType, operatorBy,
|
||||||
OrderRefundState.PROCESSING.getCode(), ReviewRequirement.NOT_REQUIRED.getCode());
|
OrderRefundState.PROCESSING.getCode(), ReviewRequirement.NOT_REQUIRED.getCode());
|
||||||
|
|
||||||
|
restoreCouponsForOrder(orderInfo);
|
||||||
|
|
||||||
PlayOrderInfoEntity latest = this.selectOrderInfoById(orderId);
|
PlayOrderInfoEntity latest = this.selectOrderInfoById(orderId);
|
||||||
wxCustomMpService.sendOrderCancelMessageAsync(latest, refundReason);
|
notificationSender.sendOrderCancelMessageAsync(latest, refundReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void revokeCompletedOrder(OrderRevocationContext context) {
|
||||||
|
if (context == null || StrUtil.isBlank(context.getOrderId())) {
|
||||||
|
throw new CustomException("订单信息缺失");
|
||||||
|
}
|
||||||
|
orderLifecycleService.revokeCompletedOrder(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1038,4 +1107,11 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
|
|||||||
return OrderTriggerSource.SYSTEM;
|
return OrderTriggerSource.SYSTEM;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void restoreCouponsForOrder(PlayOrderInfoEntity orderInfo) {
|
||||||
|
if (orderInfo == null || CollectionUtil.isEmpty(orderInfo.getCouponIds())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
playCouponDetailsService.updateCouponUseStateByIds(orderInfo.getCouponIds(), CouponUseState.UNUSED.getCode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,13 @@ public class ClerkRevenueCalculator {
|
|||||||
BigDecimal baseAmount = orderAmount == null ? BigDecimal.ZERO : orderAmount;
|
BigDecimal baseAmount = orderAmount == null ? BigDecimal.ZERO : orderAmount;
|
||||||
ClerkEstimatedRevenueVo estimatedRevenueVo = new ClerkEstimatedRevenueVo();
|
ClerkEstimatedRevenueVo estimatedRevenueVo = new ClerkEstimatedRevenueVo();
|
||||||
|
|
||||||
|
if (levelInfo == null) {
|
||||||
|
log.warn("店员{}缺少等级提成配置,预计收益按0处理", clerkId);
|
||||||
|
estimatedRevenueVo.setRevenueRatio(0);
|
||||||
|
estimatedRevenueVo.setRevenueAmount(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
|
||||||
|
return estimatedRevenueVo;
|
||||||
|
}
|
||||||
|
|
||||||
boolean fallbackToOther = false;
|
boolean fallbackToOther = false;
|
||||||
OrderConstant.PlaceType placeTypeEnum;
|
OrderConstant.PlaceType placeTypeEnum;
|
||||||
try {
|
try {
|
||||||
@@ -49,13 +56,13 @@ public class ClerkRevenueCalculator {
|
|||||||
|
|
||||||
switch (placeTypeEnum) {
|
switch (placeTypeEnum) {
|
||||||
case SPECIFIED: // 指定单
|
case SPECIFIED: // 指定单
|
||||||
fillRegularOrderRevenue(firstOrder, baseAmount, levelInfo, estimatedRevenueVo);
|
fillRegularOrderRevenue(clerkId, firstOrder, baseAmount, levelInfo, estimatedRevenueVo);
|
||||||
break;
|
break;
|
||||||
case RANDOM: // 随机单
|
case RANDOM: // 随机单
|
||||||
fillRandomOrderRevenue(firstOrder, baseAmount, levelInfo, estimatedRevenueVo);
|
fillRandomOrderRevenue(clerkId, firstOrder, baseAmount, levelInfo, estimatedRevenueVo);
|
||||||
break;
|
break;
|
||||||
case REWARD: // 打赏单
|
case REWARD: // 打赏单
|
||||||
fillRewardOrderRevenue(firstOrder, baseAmount, levelInfo, estimatedRevenueVo);
|
fillRewardOrderRevenue(clerkId, firstOrder, baseAmount, levelInfo, estimatedRevenueVo);
|
||||||
break;
|
break;
|
||||||
case OTHER:
|
case OTHER:
|
||||||
default:
|
default:
|
||||||
@@ -71,42 +78,56 @@ public class ClerkRevenueCalculator {
|
|||||||
return estimatedRevenueVo;
|
return estimatedRevenueVo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillRegularOrderRevenue(String firstOrder, BigDecimal orderAmount,
|
private void fillRegularOrderRevenue(String clerkId, String firstOrder, BigDecimal orderAmount,
|
||||||
PlayClerkLevelInfoEntity levelInfo, ClerkEstimatedRevenueVo vo) {
|
PlayClerkLevelInfoEntity levelInfo, ClerkEstimatedRevenueVo vo) {
|
||||||
if ("1".equals(firstOrder)) {
|
if ("1".equals(firstOrder)) {
|
||||||
vo.setRevenueRatio(levelInfo.getFirstRegularRatio());
|
int ratio = safeRatio(levelInfo.getFirstRegularRatio(), "firstRegularRatio", clerkId);
|
||||||
vo.setRevenueAmount(scaleAmount(orderAmount, levelInfo.getFirstRegularRatio()));
|
vo.setRevenueRatio(ratio);
|
||||||
|
vo.setRevenueAmount(scaleAmount(orderAmount, ratio));
|
||||||
} else {
|
} else {
|
||||||
vo.setRevenueRatio(levelInfo.getNotFirstRegularRatio());
|
int ratio = safeRatio(levelInfo.getNotFirstRegularRatio(), "notFirstRegularRatio", clerkId);
|
||||||
vo.setRevenueAmount(scaleAmount(orderAmount, levelInfo.getNotFirstRegularRatio()));
|
vo.setRevenueRatio(ratio);
|
||||||
|
vo.setRevenueAmount(scaleAmount(orderAmount, ratio));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillRandomOrderRevenue(String firstOrder, BigDecimal orderAmount,
|
private void fillRandomOrderRevenue(String clerkId, String firstOrder, BigDecimal orderAmount,
|
||||||
PlayClerkLevelInfoEntity levelInfo, ClerkEstimatedRevenueVo vo) {
|
PlayClerkLevelInfoEntity levelInfo, ClerkEstimatedRevenueVo vo) {
|
||||||
if ("1".equals(firstOrder)) {
|
if ("1".equals(firstOrder)) {
|
||||||
vo.setRevenueRatio(levelInfo.getFirstRandomRadio());
|
int ratio = safeRatio(levelInfo.getFirstRandomRadio(), "firstRandomRatio", clerkId);
|
||||||
vo.setRevenueAmount(scaleAmount(orderAmount, levelInfo.getFirstRandomRadio()));
|
vo.setRevenueRatio(ratio);
|
||||||
|
vo.setRevenueAmount(scaleAmount(orderAmount, ratio));
|
||||||
} else {
|
} else {
|
||||||
vo.setRevenueRatio(levelInfo.getNotFirstRandomRadio());
|
int ratio = safeRatio(levelInfo.getNotFirstRandomRadio(), "notFirstRandomRatio", clerkId);
|
||||||
vo.setRevenueAmount(scaleAmount(orderAmount, levelInfo.getNotFirstRandomRadio()));
|
vo.setRevenueRatio(ratio);
|
||||||
|
vo.setRevenueAmount(scaleAmount(orderAmount, ratio));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillRewardOrderRevenue(String firstOrder, BigDecimal orderAmount,
|
private void fillRewardOrderRevenue(String clerkId, String firstOrder, BigDecimal orderAmount,
|
||||||
PlayClerkLevelInfoEntity levelInfo, ClerkEstimatedRevenueVo vo) {
|
PlayClerkLevelInfoEntity levelInfo, ClerkEstimatedRevenueVo vo) {
|
||||||
if ("1".equals(firstOrder)) {
|
if ("1".equals(firstOrder)) {
|
||||||
vo.setRevenueRatio(levelInfo.getFirstRewardRatio());
|
int ratio = safeRatio(levelInfo.getFirstRewardRatio(), "firstRewardRatio", clerkId);
|
||||||
vo.setRevenueAmount(scaleAmount(orderAmount, levelInfo.getFirstRewardRatio()));
|
vo.setRevenueRatio(ratio);
|
||||||
|
vo.setRevenueAmount(scaleAmount(orderAmount, ratio));
|
||||||
} else {
|
} else {
|
||||||
vo.setRevenueRatio(levelInfo.getNotFirstRewardRatio());
|
int ratio = safeRatio(levelInfo.getNotFirstRewardRatio(), "notFirstRewardRatio", clerkId);
|
||||||
vo.setRevenueAmount(scaleAmount(orderAmount, levelInfo.getNotFirstRewardRatio()));
|
vo.setRevenueRatio(ratio);
|
||||||
|
vo.setRevenueAmount(scaleAmount(orderAmount, ratio));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal scaleAmount(BigDecimal baseAmount, Integer ratio) {
|
private int safeRatio(Integer ratio, String ratioField, String clerkId) {
|
||||||
|
if (ratio == null) {
|
||||||
|
log.warn("店员{}的等级配置字段{}缺失,已按0%处理", clerkId, ratioField);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal scaleAmount(BigDecimal baseAmount, int ratio) {
|
||||||
return baseAmount
|
return baseAmount
|
||||||
.multiply(new BigDecimal(ratio).divide(new BigDecimal(100), 4, RoundingMode.HALF_UP))
|
.multiply(BigDecimal.valueOf(ratio).divide(new BigDecimal(100), 4, RoundingMode.HALF_UP))
|
||||||
.setScale(2, RoundingMode.HALF_UP);
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.starry.admin.modules.personnel.module.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户类型枚举(0:陪聊;1:顾客)
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum BalanceDetailsUserType {
|
||||||
|
CLERK("0"),
|
||||||
|
CUSTOMER("1");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
BalanceDetailsUserType(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,6 +69,17 @@ public interface IPlayBalanceDetailsInfoService extends IService<PlayBalanceDeta
|
|||||||
BigDecimal balanceAfterOperation, String operationType, String operationAction, BigDecimal balanceMoney,
|
BigDecimal balanceAfterOperation, String operationType, String operationAction, BigDecimal balanceMoney,
|
||||||
BigDecimal giftAmount, String orderId);
|
BigDecimal giftAmount, String orderId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断顾客是否对指定订单发生过余额扣款。
|
||||||
|
*
|
||||||
|
* @param userId
|
||||||
|
* 顾客ID
|
||||||
|
* @param orderId
|
||||||
|
* 订单ID
|
||||||
|
* @return 存在消费流水返回true
|
||||||
|
*/
|
||||||
|
boolean existsCustomerConsumeRecord(String userId, String orderId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增余额明细
|
* 新增余额明细
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||||
|
import com.starry.admin.common.exception.CustomException;
|
||||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant.BalanceOperationType;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
||||||
import com.starry.admin.modules.personnel.mapper.PlayBalanceDetailsInfoMapper;
|
import com.starry.admin.modules.personnel.mapper.PlayBalanceDetailsInfoMapper;
|
||||||
import com.starry.admin.modules.personnel.module.entity.PlayBalanceDetailsInfoEntity;
|
import com.starry.admin.modules.personnel.module.entity.PlayBalanceDetailsInfoEntity;
|
||||||
|
import com.starry.admin.modules.personnel.module.enums.BalanceDetailsUserType;
|
||||||
import com.starry.admin.modules.personnel.module.vo.PlayBalanceDetailsQueryVo;
|
import com.starry.admin.modules.personnel.module.vo.PlayBalanceDetailsQueryVo;
|
||||||
import com.starry.admin.modules.personnel.module.vo.PlayBalanceDetailsReturnVo;
|
import com.starry.admin.modules.personnel.module.vo.PlayBalanceDetailsReturnVo;
|
||||||
import com.starry.admin.modules.personnel.service.IPlayBalanceDetailsInfoService;
|
import com.starry.admin.modules.personnel.service.IPlayBalanceDetailsInfoService;
|
||||||
@@ -114,7 +117,12 @@ public class PlayBalanceDetailsInfoServiceImpl
|
|||||||
public void insertBalanceDetailsInfo(String userType, String userId, BigDecimal balanceBeforeOperation,
|
public void insertBalanceDetailsInfo(String userType, String userId, BigDecimal balanceBeforeOperation,
|
||||||
BigDecimal balanceAfterOperation, String operationType, String operationAction, BigDecimal balanceMoney,
|
BigDecimal balanceAfterOperation, String operationType, String operationAction, BigDecimal balanceMoney,
|
||||||
BigDecimal giftAmount, String orderId) {
|
BigDecimal giftAmount, String orderId) {
|
||||||
PlayOrderInfoEntity orderInfo = playOrderInfoService.selectOrderInfoById(orderId);
|
PlayOrderInfoEntity orderInfo = null;
|
||||||
|
try {
|
||||||
|
orderInfo = playOrderInfoService.selectOrderInfoById(orderId);
|
||||||
|
} catch (CustomException ex) {
|
||||||
|
orderInfo = null;
|
||||||
|
}
|
||||||
PlayBalanceDetailsInfoEntity entity = new PlayBalanceDetailsInfoEntity();
|
PlayBalanceDetailsInfoEntity entity = new PlayBalanceDetailsInfoEntity();
|
||||||
entity.setId(IdUtils.getUuid());
|
entity.setId(IdUtils.getUuid());
|
||||||
entity.setUserType(userType);
|
entity.setUserType(userType);
|
||||||
@@ -180,4 +188,15 @@ public class PlayBalanceDetailsInfoServiceImpl
|
|||||||
public int deletePlayBalanceDetailsInfoById(String id) {
|
public int deletePlayBalanceDetailsInfoById(String id) {
|
||||||
return playBalanceDetailsInfoMapper.deleteById(id);
|
return playBalanceDetailsInfoMapper.deleteById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean existsCustomerConsumeRecord(String userId, String orderId) {
|
||||||
|
return lambdaQuery()
|
||||||
|
.eq(PlayBalanceDetailsInfoEntity::getUserType, BalanceDetailsUserType.CUSTOMER.getCode())
|
||||||
|
.eq(PlayBalanceDetailsInfoEntity::getUserId, userId)
|
||||||
|
.eq(PlayBalanceDetailsInfoEntity::getOrderId, orderId)
|
||||||
|
.eq(PlayBalanceDetailsInfoEntity::getOperationType, BalanceOperationType.CONSUME.getCode())
|
||||||
|
.last("limit 1")
|
||||||
|
.one() != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,9 +109,13 @@ public class PlayCommodityInfoController {
|
|||||||
if (!jsonObject.containsKey(playClerkLevelInfoEntity.getId())) {
|
if (!jsonObject.containsKey(playClerkLevelInfoEntity.getId())) {
|
||||||
throw new CustomException("请求参数错误");
|
throw new CustomException("请求参数错误");
|
||||||
}
|
}
|
||||||
|
String rawPrice = jsonObject.getString(playClerkLevelInfoEntity.getId());
|
||||||
|
if (rawPrice == null || rawPrice.trim().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
double price = 0.0;
|
double price = 0.0;
|
||||||
try {
|
try {
|
||||||
price = Double.parseDouble(jsonObject.getString(playClerkLevelInfoEntity.getId()));
|
price = Double.parseDouble(rawPrice);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw new CustomException("请求参数错误,价格格式为空");
|
throw new CustomException("请求参数错误,价格格式为空");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,4 +29,9 @@ public interface PlayClerkGiftInfoMapper extends BaseMapper<PlayClerkGiftInfoEnt
|
|||||||
int incrementGiftCount(@Param("clerkId") String clerkId, @Param("giftId") String giftId,
|
int incrementGiftCount(@Param("clerkId") String clerkId, @Param("giftId") String giftId,
|
||||||
@Param("tenantId") String tenantId, @Param("delta") long delta);
|
@Param("tenantId") String tenantId, @Param("delta") long delta);
|
||||||
|
|
||||||
|
@Update("UPDATE play_clerk_gift_info SET giff_number = 0, deleted = 0 "
|
||||||
|
+ "WHERE tenant_id = #{tenantId} AND clerk_id = #{clerkId} AND giff_id = #{giftId}")
|
||||||
|
int resetGiftCount(@Param("tenantId") String tenantId, @Param("clerkId") String clerkId,
|
||||||
|
@Param("giftId") String giftId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ public class PlayCommodityInfoEntity extends BaseEntity<PlayCommodityInfoEntity>
|
|||||||
*/
|
*/
|
||||||
private String serviceDuration;
|
private String serviceDuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接单后自动结算等待时长(单位:秒,-1 表示不自动结算)
|
||||||
|
*/
|
||||||
|
private Integer automaticSettlementDuration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启用状态(0:停用,1:启用)
|
* 启用状态(0:停用,1:启用)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -103,7 +103,11 @@ public class PlayCommodityInfoServiceImpl extends ServiceImpl<PlayCommodityInfoM
|
|||||||
@Override
|
@Override
|
||||||
public List<PlayCommodityInfoEntity> selectByType() {
|
public List<PlayCommodityInfoEntity> selectByType() {
|
||||||
LambdaQueryWrapper<PlayCommodityInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<PlayCommodityInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
lambdaQueryWrapper.eq(PlayCommodityInfoEntity::getItemType, "服务类型");
|
String tenantId = SecurityUtils.getTenantId();
|
||||||
|
if (StrUtil.isNotBlank(tenantId)) {
|
||||||
|
lambdaQueryWrapper.eq(PlayCommodityInfoEntity::getTenantId, tenantId);
|
||||||
|
}
|
||||||
|
lambdaQueryWrapper.eq(PlayCommodityInfoEntity::getPId, "00");
|
||||||
lambdaQueryWrapper.orderByDesc(PlayCommodityInfoEntity::getSort);
|
lambdaQueryWrapper.orderByDesc(PlayCommodityInfoEntity::getSort);
|
||||||
return this.baseMapper.selectList(lambdaQueryWrapper);
|
return this.baseMapper.selectList(lambdaQueryWrapper);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.starry.admin.modules.shop.service.impl;
|
|||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||||
@@ -158,12 +159,12 @@ public class PlayCouponDetailsServiceImpl extends ServiceImpl<PlayCouponDetailsM
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateCouponUseStateByIds(List<String> ids, String useState) {
|
public void updateCouponUseStateByIds(List<String> ids, String useState) {
|
||||||
|
LocalDateTime useTime = CouponUseState.USED.getCode().equals(useState) ? LocalDateTime.now() : null;
|
||||||
for (String id : ids) {
|
for (String id : ids) {
|
||||||
PlayCouponDetailsEntity entity = new PlayCouponDetailsEntity();
|
baseMapper.update(null, com.baomidou.mybatisplus.core.toolkit.Wrappers.<PlayCouponDetailsEntity>lambdaUpdate()
|
||||||
entity.setId(id);
|
.eq(PlayCouponDetailsEntity::getId, id)
|
||||||
entity.setUseState(useState);
|
.set(PlayCouponDetailsEntity::getUseState, useState)
|
||||||
entity.setUseTime(LocalDateTime.now());
|
.set(PlayCouponDetailsEntity::getUseTime, useTime));
|
||||||
baseMapper.updateById(entity);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||||
|
import com.starry.admin.common.PageBuilder;
|
||||||
import com.starry.admin.common.exception.CustomException;
|
import com.starry.admin.common.exception.CustomException;
|
||||||
import com.starry.admin.modules.custom.module.entity.PlayCustomGiftInfoEntity;
|
import com.starry.admin.modules.custom.module.entity.PlayCustomGiftInfoEntity;
|
||||||
import com.starry.admin.modules.custom.service.IPlayCustomGiftInfoService;
|
import com.starry.admin.modules.custom.service.IPlayCustomGiftInfoService;
|
||||||
@@ -16,6 +17,7 @@ import com.starry.admin.modules.shop.service.IPlayClerkGiftInfoService;
|
|||||||
import com.starry.admin.modules.shop.service.IPlayGiftInfoService;
|
import com.starry.admin.modules.shop.service.IPlayGiftInfoService;
|
||||||
import com.starry.admin.modules.weichat.entity.gift.PlayClerkGiftReturnVo;
|
import com.starry.admin.modules.weichat.entity.gift.PlayClerkGiftReturnVo;
|
||||||
import com.starry.common.utils.IdUtils;
|
import com.starry.common.utils.IdUtils;
|
||||||
|
import com.starry.common.utils.StringUtils;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -165,8 +167,11 @@ public class PlayGiftInfoServiceImpl extends ServiceImpl<PlayGiftInfoMapper, Pla
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public IPage<PlayGiftInfoEntity> selectPlayGiftInfoByPage(PlayGiftInfoEntity playGiftInfo) {
|
public IPage<PlayGiftInfoEntity> selectPlayGiftInfoByPage(PlayGiftInfoEntity playGiftInfo) {
|
||||||
Page<PlayGiftInfoEntity> page = new Page<>(1, 10);
|
Page<PlayGiftInfoEntity> page = PageBuilder.build();
|
||||||
return this.baseMapper.selectPage(page, new LambdaQueryWrapper<>());
|
LambdaQueryWrapper<PlayGiftInfoEntity> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.like(StringUtils.isNotBlank(playGiftInfo.getName()), PlayGiftInfoEntity::getName, playGiftInfo.getName());
|
||||||
|
wrapper.eq(StringUtils.isNotBlank(playGiftInfo.getState()), PlayGiftInfoEntity::getState, playGiftInfo.getState());
|
||||||
|
return this.baseMapper.selectPage(page, wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
|
|||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
|
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
|
||||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||||
|
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
||||||
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
|
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
|
||||||
@@ -209,6 +210,7 @@ public class PlayClerkPerformanceController {
|
|||||||
int orderContinueNumber = 0;
|
int orderContinueNumber = 0;
|
||||||
int orderRefundNumber = 0;
|
int orderRefundNumber = 0;
|
||||||
int ordersExpiredNumber = 0;
|
int ordersExpiredNumber = 0;
|
||||||
|
int completedOrders = 0;
|
||||||
BigDecimal orderMoney = BigDecimal.ZERO;
|
BigDecimal orderMoney = BigDecimal.ZERO;
|
||||||
BigDecimal finalAmount = BigDecimal.ZERO;
|
BigDecimal finalAmount = BigDecimal.ZERO;
|
||||||
BigDecimal orderFirstAmount = BigDecimal.ZERO;
|
BigDecimal orderFirstAmount = BigDecimal.ZERO;
|
||||||
@@ -217,6 +219,10 @@ public class PlayClerkPerformanceController {
|
|||||||
BigDecimal orderRefundAmount = BigDecimal.ZERO;
|
BigDecimal orderRefundAmount = BigDecimal.ZERO;
|
||||||
BigDecimal estimatedRevenue = BigDecimal.ZERO;
|
BigDecimal estimatedRevenue = BigDecimal.ZERO;
|
||||||
for (PlayOrderInfoEntity orderInfoEntity : orderInfoEntities) {
|
for (PlayOrderInfoEntity orderInfoEntity : orderInfoEntities) {
|
||||||
|
if (!isCompletedOrder(orderInfoEntity)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
completedOrders++;
|
||||||
customIds.add(orderInfoEntity.getPurchaserBy());
|
customIds.add(orderInfoEntity.getPurchaserBy());
|
||||||
finalAmount = finalAmount.add(orderInfoEntity.getFinalAmount());
|
finalAmount = finalAmount.add(orderInfoEntity.getFinalAmount());
|
||||||
orderMoney = orderMoney.add(orderInfoEntity.getOrderMoney());
|
orderMoney = orderMoney.add(orderInfoEntity.getOrderMoney());
|
||||||
@@ -238,7 +244,7 @@ public class PlayClerkPerformanceController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
PlayClerkPerformanceInfoReturnVo returnVo = new PlayClerkPerformanceInfoReturnVo();
|
PlayClerkPerformanceInfoReturnVo returnVo = new PlayClerkPerformanceInfoReturnVo();
|
||||||
returnVo.setOrderNumber(orderInfoEntities.size());
|
returnVo.setOrderNumber(completedOrders);
|
||||||
returnVo.setOrderContinueNumber(orderContinueNumber);
|
returnVo.setOrderContinueNumber(orderContinueNumber);
|
||||||
returnVo.setOrderRefundNumber(orderRefundNumber);
|
returnVo.setOrderRefundNumber(orderRefundNumber);
|
||||||
returnVo.setOrdersExpiredNumber(ordersExpiredNumber);
|
returnVo.setOrdersExpiredNumber(ordersExpiredNumber);
|
||||||
@@ -281,6 +287,7 @@ public class PlayClerkPerformanceController {
|
|||||||
int orderContinueNumber = 0;
|
int orderContinueNumber = 0;
|
||||||
int orderRefundNumber = 0;
|
int orderRefundNumber = 0;
|
||||||
int ordersExpiredNumber = 0;
|
int ordersExpiredNumber = 0;
|
||||||
|
int completedOrders = 0;
|
||||||
BigDecimal orderMoney = BigDecimal.ZERO;
|
BigDecimal orderMoney = BigDecimal.ZERO;
|
||||||
BigDecimal finalAmount = BigDecimal.ZERO;
|
BigDecimal finalAmount = BigDecimal.ZERO;
|
||||||
BigDecimal orderFirstAmount = BigDecimal.ZERO;
|
BigDecimal orderFirstAmount = BigDecimal.ZERO;
|
||||||
@@ -289,6 +296,10 @@ public class PlayClerkPerformanceController {
|
|||||||
BigDecimal orderRefundAmount = BigDecimal.ZERO;
|
BigDecimal orderRefundAmount = BigDecimal.ZERO;
|
||||||
BigDecimal estimatedRevenue = BigDecimal.ZERO;
|
BigDecimal estimatedRevenue = BigDecimal.ZERO;
|
||||||
for (PlayOrderInfoEntity orderInfoEntity : itemOrderInfo) {
|
for (PlayOrderInfoEntity orderInfoEntity : itemOrderInfo) {
|
||||||
|
if (!isCompletedOrder(orderInfoEntity)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
completedOrders++;
|
||||||
customIds.add(orderInfoEntity.getPurchaserBy());
|
customIds.add(orderInfoEntity.getPurchaserBy());
|
||||||
finalAmount = finalAmount.add(orderInfoEntity.getFinalAmount());
|
finalAmount = finalAmount.add(orderInfoEntity.getFinalAmount());
|
||||||
orderMoney = orderMoney.add(orderInfoEntity.getOrderMoney());
|
orderMoney = orderMoney.add(orderInfoEntity.getOrderMoney());
|
||||||
@@ -311,7 +322,7 @@ public class PlayClerkPerformanceController {
|
|||||||
}
|
}
|
||||||
PlayClerkPerformanceInfoReturnVo returnVo = new PlayClerkPerformanceInfoReturnVo();
|
PlayClerkPerformanceInfoReturnVo returnVo = new PlayClerkPerformanceInfoReturnVo();
|
||||||
returnVo.setPerformanceDate(performanceDate);
|
returnVo.setPerformanceDate(performanceDate);
|
||||||
returnVo.setOrderNumber(itemOrderInfo.size());
|
returnVo.setOrderNumber(completedOrders);
|
||||||
returnVo.setOrderContinueNumber(orderContinueNumber);
|
returnVo.setOrderContinueNumber(orderContinueNumber);
|
||||||
returnVo.setOrderRefundNumber(orderRefundNumber);
|
returnVo.setOrderRefundNumber(orderRefundNumber);
|
||||||
returnVo.setOrdersExpiredNumber(ordersExpiredNumber);
|
returnVo.setOrdersExpiredNumber(ordersExpiredNumber);
|
||||||
@@ -326,4 +337,9 @@ public class PlayClerkPerformanceController {
|
|||||||
return returnVo;
|
return returnVo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isCompletedOrder(PlayOrderInfoEntity orderInfoEntity) {
|
||||||
|
return orderInfoEntity != null
|
||||||
|
&& OrderConstant.OrderStatus.COMPLETED.getCode().equals(orderInfoEntity.getOrderStatus());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
|
|||||||
int orderContinueNumber = 0;
|
int orderContinueNumber = 0;
|
||||||
int orderRefundNumber = 0;
|
int orderRefundNumber = 0;
|
||||||
int ordersExpiredNumber = 0;
|
int ordersExpiredNumber = 0;
|
||||||
|
int completedOrderCount = 0;
|
||||||
BigDecimal finalAmount = BigDecimal.ZERO;
|
BigDecimal finalAmount = BigDecimal.ZERO;
|
||||||
BigDecimal orderFirstAmount = BigDecimal.ZERO;
|
BigDecimal orderFirstAmount = BigDecimal.ZERO;
|
||||||
BigDecimal orderTotalAmount = BigDecimal.ZERO;
|
BigDecimal orderTotalAmount = BigDecimal.ZERO;
|
||||||
@@ -84,6 +85,10 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
|
|||||||
BigDecimal orderRefundAmount = BigDecimal.ZERO;
|
BigDecimal orderRefundAmount = BigDecimal.ZERO;
|
||||||
|
|
||||||
for (PlayOrderInfoEntity orderInfoEntity : orderInfoEntities) {
|
for (PlayOrderInfoEntity orderInfoEntity : orderInfoEntities) {
|
||||||
|
if (!isCompletedOrder(orderInfoEntity)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
completedOrderCount++;
|
||||||
customIds.add(orderInfoEntity.getPurchaserBy());
|
customIds.add(orderInfoEntity.getPurchaserBy());
|
||||||
finalAmount = finalAmount.add(defaultZero(orderInfoEntity.getFinalAmount()));
|
finalAmount = finalAmount.add(defaultZero(orderInfoEntity.getFinalAmount()));
|
||||||
if (OrderConstant.YesNoFlag.YES.getCode().equals(orderInfoEntity.getFirstOrder())) {
|
if (OrderConstant.YesNoFlag.YES.getCode().equals(orderInfoEntity.getFirstOrder())) {
|
||||||
@@ -121,7 +126,7 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
|
|||||||
returnVo.setGroupName(infoEntity.getGroupName());
|
returnVo.setGroupName(infoEntity.getGroupName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
returnVo.setOrderNumber(orderInfoEntities.size());
|
returnVo.setOrderNumber(completedOrderCount);
|
||||||
returnVo.setOrderContinueNumber(orderContinueNumber);
|
returnVo.setOrderContinueNumber(orderContinueNumber);
|
||||||
returnVo.setOrderRefundNumber(orderRefundNumber);
|
returnVo.setOrderRefundNumber(orderRefundNumber);
|
||||||
returnVo.setOrdersExpiredNumber(ordersExpiredNumber);
|
returnVo.setOrdersExpiredNumber(ordersExpiredNumber);
|
||||||
@@ -223,7 +228,10 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
|
|||||||
|
|
||||||
private List<ClerkPerformanceTrendPointVo> buildTrend(List<PlayOrderInfoEntity> orders, DateRange range,
|
private List<ClerkPerformanceTrendPointVo> buildTrend(List<PlayOrderInfoEntity> orders, DateRange range,
|
||||||
int trendDays) {
|
int trendDays) {
|
||||||
if (CollectionUtil.isEmpty(orders)) {
|
List<PlayOrderInfoEntity> completedOrders = orders.stream()
|
||||||
|
.filter(this::isCompletedOrder)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (CollectionUtil.isEmpty(completedOrders)) {
|
||||||
return buildEmptyTrend(range, trendDays);
|
return buildEmptyTrend(range, trendDays);
|
||||||
}
|
}
|
||||||
LocalDate end = range.endDate;
|
LocalDate end = range.endDate;
|
||||||
@@ -231,7 +239,7 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
|
|||||||
if (start.isBefore(range.startDate)) {
|
if (start.isBefore(range.startDate)) {
|
||||||
start = range.startDate;
|
start = range.startDate;
|
||||||
}
|
}
|
||||||
Map<LocalDate, List<PlayOrderInfoEntity>> grouped = orders.stream()
|
Map<LocalDate, List<PlayOrderInfoEntity>> grouped = completedOrders.stream()
|
||||||
.filter(order -> order.getPurchaserTime() != null)
|
.filter(order -> order.getPurchaserTime() != null)
|
||||||
.collect(Collectors.groupingBy(order -> order.getPurchaserTime().toLocalDate()));
|
.collect(Collectors.groupingBy(order -> order.getPurchaserTime().toLocalDate()));
|
||||||
List<ClerkPerformanceTrendPointVo> points = new ArrayList<>();
|
List<ClerkPerformanceTrendPointVo> points = new ArrayList<>();
|
||||||
@@ -421,6 +429,7 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
|
|||||||
return BigDecimal.ZERO;
|
return BigDecimal.ZERO;
|
||||||
}
|
}
|
||||||
List<String> orderIds = orders.stream()
|
List<String> orderIds = orders.stream()
|
||||||
|
.filter(this::isCompletedOrder)
|
||||||
.map(PlayOrderInfoEntity::getId)
|
.map(PlayOrderInfoEntity::getId)
|
||||||
.filter(StrUtil::isNotBlank)
|
.filter(StrUtil::isNotBlank)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
@@ -453,7 +462,12 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
|
|||||||
int refundCount = 0;
|
int refundCount = 0;
|
||||||
int expiredCount = 0;
|
int expiredCount = 0;
|
||||||
Map<String, Integer> userOrderMap = new HashMap<>();
|
Map<String, Integer> userOrderMap = new HashMap<>();
|
||||||
|
int orderCount = 0;
|
||||||
for (PlayOrderInfoEntity order : orders) {
|
for (PlayOrderInfoEntity order : orders) {
|
||||||
|
if (!isCompletedOrder(order)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
orderCount++;
|
||||||
BigDecimal finalAmount = defaultZero(order.getFinalAmount());
|
BigDecimal finalAmount = defaultZero(order.getFinalAmount());
|
||||||
gmv = gmv.add(finalAmount);
|
gmv = gmv.add(finalAmount);
|
||||||
userOrderMap.merge(order.getPurchaserBy(), 1, Integer::sum);
|
userOrderMap.merge(order.getPurchaserBy(), 1, Integer::sum);
|
||||||
@@ -475,7 +489,6 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
|
|||||||
expiredCount++;
|
expiredCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int orderCount = orders.size();
|
|
||||||
int userCount = userOrderMap.size();
|
int userCount = userOrderMap.size();
|
||||||
int continuedUserCount = (int) userOrderMap.values().stream().filter(cnt -> cnt > 1).count();
|
int continuedUserCount = (int) userOrderMap.values().stream().filter(cnt -> cnt > 1).count();
|
||||||
BigDecimal estimatedRevenue = calculateEarningsAmount(clerk.getId(), orders, startTime, endTime);
|
BigDecimal estimatedRevenue = calculateEarningsAmount(clerk.getId(), orders, startTime, endTime);
|
||||||
@@ -568,6 +581,10 @@ public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceSer
|
|||||||
return value == null ? BigDecimal.ZERO : value;
|
return value == null ? BigDecimal.ZERO : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isCompletedOrder(PlayOrderInfoEntity order) {
|
||||||
|
return order != null && OrderConstant.OrderStatus.COMPLETED.getCode().equals(order.getOrderStatus());
|
||||||
|
}
|
||||||
|
|
||||||
private static final class DateRange {
|
private static final class DateRange {
|
||||||
private final String startTime;
|
private final String startTime;
|
||||||
private final String endTime;
|
private final String endTime;
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.starry.admin.modules.weichat.assembler;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkMediaAssetEntity;
|
||||||
|
import com.starry.admin.modules.media.entity.PlayMediaEntity;
|
||||||
|
import com.starry.admin.modules.weichat.entity.clerk.MediaVo;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public final class ClerkMediaAssembler {
|
||||||
|
|
||||||
|
private ClerkMediaAssembler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MediaVo toVo(PlayMediaEntity media, PlayClerkMediaAssetEntity asset) {
|
||||||
|
if (media == null || asset == null || Boolean.TRUE.equals(asset.getDeleted())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
MediaVo vo = new MediaVo();
|
||||||
|
vo.setId(media.getId());
|
||||||
|
vo.setMediaId(media.getId());
|
||||||
|
vo.setAssetId(asset.getId());
|
||||||
|
vo.setKind(media.getKind());
|
||||||
|
vo.setStatus(media.getStatus());
|
||||||
|
vo.setUrl(media.getUrl());
|
||||||
|
vo.setCoverUrl(media.getCoverUrl());
|
||||||
|
vo.setDurationMs(media.getDurationMs());
|
||||||
|
vo.setWidth(media.getWidth());
|
||||||
|
vo.setHeight(media.getHeight());
|
||||||
|
vo.setSizeBytes(media.getSizeBytes());
|
||||||
|
vo.setOrderIndex(asset.getOrderIndex());
|
||||||
|
vo.setMetadata(media.getMetadata());
|
||||||
|
vo.setUsage(asset.getUsage());
|
||||||
|
vo.setReviewState(asset.getReviewState());
|
||||||
|
vo.setSubmittedTime(asset.getSubmittedTime());
|
||||||
|
vo.setReviewNote(asset.getNote());
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MediaVo> toVoList(List<PlayClerkMediaAssetEntity> assets,
|
||||||
|
Map<String, PlayMediaEntity> mediaById) {
|
||||||
|
if (assets == null || assets.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return assets.stream()
|
||||||
|
.map(asset -> toVo(mediaById.get(asset.getMediaId()), asset))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.starry.admin.modules.weichat.assembler;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.media.entity.PlayMediaEntity;
|
||||||
|
import com.starry.admin.modules.weichat.entity.clerk.MediaVo;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public final class MediaAssembler {
|
||||||
|
|
||||||
|
private MediaAssembler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MediaVo toVo(PlayMediaEntity entity) {
|
||||||
|
if (entity == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
MediaVo vo = new MediaVo();
|
||||||
|
vo.setId(entity.getId());
|
||||||
|
vo.setMediaId(entity.getId());
|
||||||
|
vo.setKind(entity.getKind());
|
||||||
|
vo.setStatus(entity.getStatus());
|
||||||
|
vo.setUrl(entity.getUrl());
|
||||||
|
vo.setCoverUrl(entity.getCoverUrl());
|
||||||
|
vo.setDurationMs(entity.getDurationMs());
|
||||||
|
vo.setWidth(entity.getWidth());
|
||||||
|
vo.setHeight(entity.getHeight());
|
||||||
|
vo.setSizeBytes(entity.getSizeBytes());
|
||||||
|
vo.setOrderIndex(entity.getOrderIndex());
|
||||||
|
vo.setMetadata(entity.getMetadata());
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MediaVo> toVoList(List<PlayMediaEntity> entities) {
|
||||||
|
if (entities == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return entities.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(MediaAssembler::toVo)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,12 +6,16 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||||||
import com.starry.admin.common.aspect.ClerkUserLogin;
|
import com.starry.admin.common.aspect.ClerkUserLogin;
|
||||||
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
|
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
|
||||||
import com.starry.admin.common.exception.CustomException;
|
import com.starry.admin.common.exception.CustomException;
|
||||||
|
import com.starry.admin.modules.clerk.enums.ClerkMediaReviewState;
|
||||||
|
import com.starry.admin.modules.clerk.enums.ClerkMediaUsage;
|
||||||
import com.starry.admin.modules.clerk.module.entity.*;
|
import com.starry.admin.modules.clerk.module.entity.*;
|
||||||
import com.starry.admin.modules.clerk.module.vo.PlayClerkCommodityEditVo;
|
import com.starry.admin.modules.clerk.module.vo.PlayClerkCommodityEditVo;
|
||||||
import com.starry.admin.modules.clerk.module.vo.PlayClerkCommodityQueryVo;
|
import com.starry.admin.modules.clerk.module.vo.PlayClerkCommodityQueryVo;
|
||||||
import com.starry.admin.modules.clerk.service.*;
|
import com.starry.admin.modules.clerk.service.*;
|
||||||
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserInfoServiceImpl;
|
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserInfoServiceImpl;
|
||||||
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserReviewInfoServiceImpl;
|
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserReviewInfoServiceImpl;
|
||||||
|
import com.starry.admin.modules.media.enums.MediaOwnerType;
|
||||||
|
import com.starry.admin.modules.media.service.IPlayMediaService;
|
||||||
import com.starry.admin.modules.order.module.constant.OrderConstant.OperatorType;
|
import com.starry.admin.modules.order.module.constant.OrderConstant.OperatorType;
|
||||||
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.PlayOrderCompleteVo;
|
import com.starry.admin.modules.order.module.vo.PlayOrderCompleteVo;
|
||||||
@@ -41,6 +45,7 @@ import com.starry.admin.utils.SecurityUtils;
|
|||||||
import com.starry.admin.utils.SmsUtils;
|
import com.starry.admin.utils.SmsUtils;
|
||||||
import com.starry.common.redis.RedisCache;
|
import com.starry.common.redis.RedisCache;
|
||||||
import com.starry.common.result.R;
|
import com.starry.common.result.R;
|
||||||
|
import com.starry.common.result.TypedR;
|
||||||
import com.starry.common.utils.ConvertUtil;
|
import com.starry.common.utils.ConvertUtil;
|
||||||
import com.starry.common.utils.StringUtils;
|
import com.starry.common.utils.StringUtils;
|
||||||
import com.starry.common.utils.VerificationCodeUtils;
|
import com.starry.common.utils.VerificationCodeUtils;
|
||||||
@@ -53,6 +58,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -120,6 +126,10 @@ public class WxClerkController {
|
|||||||
private SmsUtils smsUtils;
|
private SmsUtils smsUtils;
|
||||||
@Resource
|
@Resource
|
||||||
private WxCustomMpService wxCustomMpService;
|
private WxCustomMpService wxCustomMpService;
|
||||||
|
@Resource
|
||||||
|
private com.starry.admin.modules.clerk.service.IPlayClerkMediaAssetService clerkMediaAssetService;
|
||||||
|
@Resource
|
||||||
|
private IPlayMediaService mediaService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 店员获取个人业绩信息
|
* 店员获取个人业绩信息
|
||||||
@@ -268,7 +278,7 @@ public class WxClerkController {
|
|||||||
entity.setReviewState("0");
|
entity.setReviewState("0");
|
||||||
entity.setDataContent(Collections.singletonList(vo.getNickname()));
|
entity.setDataContent(Collections.singletonList(vo.getNickname()));
|
||||||
playClerkDataReviewInfoService.create(entity);
|
playClerkDataReviewInfoService.create(entity);
|
||||||
return R.ok("提交成功,等待审核~");
|
return R.ok().message("提交成功,等待审核~");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation(value = "更新性别", notes = "店员更新性别")
|
@ApiOperation(value = "更新性别", notes = "店员更新性别")
|
||||||
@@ -283,7 +293,7 @@ public class WxClerkController {
|
|||||||
entity.setReviewState("0");
|
entity.setReviewState("0");
|
||||||
entity.setDataContent(Collections.singletonList(String.valueOf(vo.getSex())));
|
entity.setDataContent(Collections.singletonList(String.valueOf(vo.getSex())));
|
||||||
playClerkDataReviewInfoService.create(entity);
|
playClerkDataReviewInfoService.create(entity);
|
||||||
return R.ok("提交成功,等待审核~");
|
return R.ok().message("提交成功,等待审核~");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation(value = "更新头像", notes = "店员更新头像")
|
@ApiOperation(value = "更新头像", notes = "店员更新头像")
|
||||||
@@ -305,25 +315,138 @@ public class WxClerkController {
|
|||||||
list.add(vo.getAvatar());
|
list.add(vo.getAvatar());
|
||||||
entity.setDataContent(list);
|
entity.setDataContent(list);
|
||||||
playClerkDataReviewInfoService.create(entity);
|
playClerkDataReviewInfoService.create(entity);
|
||||||
return R.ok("提交成功,等待审核~");
|
return R.ok().message("提交成功,等待审核~");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ClerkUserLogin
|
@ClerkUserLogin
|
||||||
@PostMapping("/user/updateAlbum")
|
@PostMapping("/user/updateAlbum")
|
||||||
public R updateAlbum(@Validated @RequestBody PlayClerkUserAlbumVo vo) {
|
public R updateAlbum(@Validated @RequestBody PlayClerkUserAlbumVo vo) {
|
||||||
PlayClerkUserInfoEntity userInfo = ThreadLocalRequestDetail.getClerkUserInfo();
|
PlayClerkUserInfoEntity userInfo = ThreadLocalRequestDetail.getClerkUserInfo();
|
||||||
// PlayClerkDataReviewInfoEntity entity =
|
List<String> requested = vo.getAlbum() == null ? new ArrayList<>() : vo.getAlbum().stream()
|
||||||
// playClerkDataReviewInfoService.queryByClerkId(userInfo.getId(), "2", "0");
|
.filter(StrUtil::isNotBlank)
|
||||||
// if (entity != null) {
|
.map(String::trim)
|
||||||
// throw new CustomException("已有申请未审核");
|
.distinct()
|
||||||
// }
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 查询当前所有已审核通过的 PROFILE 媒资
|
||||||
|
List<PlayClerkMediaAssetEntity> approvedAssets = clerkMediaAssetService.listByState(
|
||||||
|
userInfo.getId(),
|
||||||
|
ClerkMediaUsage.PROFILE,
|
||||||
|
Collections.singletonList(ClerkMediaReviewState.APPROVED));
|
||||||
|
|
||||||
|
LinkedHashSet<String> requestedSet = new LinkedHashSet<>(requested);
|
||||||
|
if (requestedSet.isEmpty()) {
|
||||||
|
throw new CustomException("最少上传一张照片");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算哪些是新媒资(需走审核),哪些是纯删除/排序
|
||||||
|
java.util.Set<String> approvedIds = approvedAssets.stream()
|
||||||
|
.map(PlayClerkMediaAssetEntity::getMediaId)
|
||||||
|
.filter(StrUtil::isNotBlank)
|
||||||
|
.collect(java.util.stream.Collectors.toSet());
|
||||||
|
java.util.Set<String> newMediaIds = requestedSet.stream()
|
||||||
|
.filter(id -> !approvedIds.contains(id))
|
||||||
|
.collect(java.util.stream.Collectors.toSet());
|
||||||
|
|
||||||
|
if (log.isInfoEnabled()) {
|
||||||
|
log.info("[ClerkAlbumUpdate] clerkId={} tenantId={} requestedSet={} approvedIds={} newMediaIds={}",
|
||||||
|
userInfo.getId(), userInfo.getTenantId(), requestedSet, approvedIds, newMediaIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newMediaIds.isEmpty()) {
|
||||||
|
// 新增媒资必须是当前店员本人名下、已就绪的媒资,才能进入审核流程
|
||||||
|
java.util.List<com.starry.admin.modules.media.entity.PlayMediaEntity> newMediaEntities =
|
||||||
|
mediaService.lambdaQuery()
|
||||||
|
.in(com.starry.admin.modules.media.entity.PlayMediaEntity::getId, newMediaIds)
|
||||||
|
.list();
|
||||||
|
|
||||||
|
java.util.Set<String> existingMediaIds = newMediaEntities.stream()
|
||||||
|
.map(com.starry.admin.modules.media.entity.PlayMediaEntity::getId)
|
||||||
|
.collect(java.util.stream.Collectors.toSet());
|
||||||
|
java.util.Set<String> missingMediaIds = new java.util.HashSet<>(newMediaIds);
|
||||||
|
missingMediaIds.removeAll(existingMediaIds);
|
||||||
|
|
||||||
|
if (!missingMediaIds.isEmpty()) {
|
||||||
|
// 这里很可能是历史相册里的纯 URL(未经过媒资化),我们记录日志但不直接失败,
|
||||||
|
// 在审核内容中仍然保留这些字符串,由审核端用回显逻辑处理。
|
||||||
|
log.warn(
|
||||||
|
"[ClerkAlbumUpdate] some album entries not found in play_media, treating as legacy values, clerkId={} tenantId={} missingIds={} existingIds={}",
|
||||||
|
userInfo.getId(),
|
||||||
|
userInfo.getTenantId(),
|
||||||
|
missingMediaIds,
|
||||||
|
existingMediaIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isInfoEnabled()) {
|
||||||
|
log.info(
|
||||||
|
"[ClerkAlbumUpdate] loaded newMediaEntities for validation, clerkId={} tenantId={} mediaSummaries={}",
|
||||||
|
userInfo.getId(),
|
||||||
|
userInfo.getTenantId(),
|
||||||
|
newMediaEntities.stream()
|
||||||
|
.map(m -> String.format("id=%s,status=%s,ownerType=%s,ownerId=%s,tenantId=%s",
|
||||||
|
m.getId(), m.getStatus(), m.getOwnerType(), m.getOwnerId(), m.getTenantId()))
|
||||||
|
.collect(java.util.stream.Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (com.starry.admin.modules.media.entity.PlayMediaEntity media : newMediaEntities) {
|
||||||
|
boolean tenantMatched = userInfo.getTenantId().equals(media.getTenantId());
|
||||||
|
boolean ownerTypeMatched = MediaOwnerType.CLERK.equals(media.getOwnerType());
|
||||||
|
boolean ownerIdMatched = userInfo.getId().equals(media.getOwnerId());
|
||||||
|
boolean statusReady = com.starry.admin.modules.media.enums.MediaStatus.READY.getCode()
|
||||||
|
.equals(media.getStatus());
|
||||||
|
|
||||||
|
if (!tenantMatched || !ownerTypeMatched || !ownerIdMatched || !statusReady) {
|
||||||
|
log.warn(
|
||||||
|
"[ClerkAlbumUpdate] invalid new media for clerk, clerkId={} tenantId={} mediaId={} mediaStatus={} mediaTenantId={} mediaOwnerType={} mediaOwnerId={} tenantMatched={} ownerTypeMatched={} ownerIdMatched={} statusReady={}",
|
||||||
|
userInfo.getId(),
|
||||||
|
userInfo.getTenantId(),
|
||||||
|
media.getId(),
|
||||||
|
media.getStatus(),
|
||||||
|
media.getTenantId(),
|
||||||
|
media.getOwnerType(),
|
||||||
|
media.getOwnerId(),
|
||||||
|
tenantMatched,
|
||||||
|
ownerTypeMatched,
|
||||||
|
ownerIdMatched,
|
||||||
|
statusReady);
|
||||||
|
throw new CustomException("存在无效的照片/视频,请刷新后重试");
|
||||||
|
}
|
||||||
|
if (!statusReady) {
|
||||||
|
log.warn(
|
||||||
|
"[ClerkAlbumUpdate] media not in READY state for clerk, clerkId={} tenantId={} mediaId={} mediaStatus={}",
|
||||||
|
userInfo.getId(),
|
||||||
|
userInfo.getTenantId(),
|
||||||
|
media.getId(),
|
||||||
|
media.getStatus());
|
||||||
|
throw new CustomException("存在未完成上传的照片/视频,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只要存在新增媒资,则按原有逻辑走资料审核,由审核通过时统一生效
|
||||||
PlayClerkDataReviewInfoEntity entity = new PlayClerkDataReviewInfoEntity();
|
PlayClerkDataReviewInfoEntity entity = new PlayClerkDataReviewInfoEntity();
|
||||||
entity.setClerkId(userInfo.getId());
|
entity.setClerkId(userInfo.getId());
|
||||||
entity.setDataType("2");
|
entity.setDataType("2");
|
||||||
entity.setReviewState("0");
|
entity.setReviewState("0");
|
||||||
entity.setDataContent(vo.getAlbum());
|
entity.setDataContent(new ArrayList<>(requestedSet));
|
||||||
playClerkDataReviewInfoService.create(entity);
|
playClerkDataReviewInfoService.create(entity);
|
||||||
return R.ok("提交成功,等待审核~");
|
return R.ok().message("提交成功,等待审核~");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅删除/排序:直接应用变更,不再生成审核记录
|
||||||
|
// 先根据新的顺序更新 orderIndex
|
||||||
|
clerkMediaAssetService.reorder(userInfo.getId(), ClerkMediaUsage.PROFILE, new ArrayList<>(requestedSet));
|
||||||
|
|
||||||
|
// 再对不再保留的媒资执行软删除
|
||||||
|
java.util.Set<String> requestedOnly = new java.util.HashSet<>(requestedSet);
|
||||||
|
java.util.Set<String> deletedMediaIds = approvedIds.stream()
|
||||||
|
.filter(id -> !requestedOnly.contains(id))
|
||||||
|
.collect(java.util.stream.Collectors.toSet());
|
||||||
|
for (String mediaId : deletedMediaIds) {
|
||||||
|
clerkMediaAssetService.softDelete(userInfo.getId(), mediaId);
|
||||||
|
mediaService.softDelete(MediaOwnerType.CLERK, userInfo.getId(), mediaId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return R.ok().message("修改成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ClerkUserLogin
|
@ClerkUserLogin
|
||||||
@@ -343,7 +466,7 @@ public class WxClerkController {
|
|||||||
list.add(vo.getAudio());
|
list.add(vo.getAudio());
|
||||||
entity.setDataContent(list);
|
entity.setDataContent(list);
|
||||||
playClerkDataReviewInfoService.create(entity);
|
playClerkDataReviewInfoService.create(entity);
|
||||||
return R.ok("提交成功,等待审核~");
|
return R.ok().message("提交成功,等待审核~");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ClerkUserLogin
|
@ClerkUserLogin
|
||||||
@@ -394,10 +517,10 @@ public class WxClerkController {
|
|||||||
* @return 店员列表
|
* @return 店员列表
|
||||||
*/
|
*/
|
||||||
@PostMapping("/user/queryByPage")
|
@PostMapping("/user/queryByPage")
|
||||||
public R queryByPage(@RequestBody PlayClerkUserInfoQueryVo vo) {
|
public TypedR<IPage<PlayClerkUserInfoResultVo>> queryByPage(@RequestBody PlayClerkUserInfoQueryVo vo) {
|
||||||
IPage<PlayClerkUserInfoResultVo> page = playClerkUserInfoService.selectByPage(vo,
|
IPage<PlayClerkUserInfoResultVo> page = playClerkUserInfoService.selectByPage(vo,
|
||||||
customUserService.getLoginUserId());
|
customUserService.getLoginUserId());
|
||||||
return R.ok(page);
|
return TypedR.ok(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package com.starry.admin.modules.weichat.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.starry.admin.common.aspect.ClerkUserLogin;
|
||||||
|
import com.starry.admin.common.conf.ThreadLocalRequestDetail;
|
||||||
|
import com.starry.admin.common.exception.CustomException;
|
||||||
|
import com.starry.admin.modules.clerk.enums.ClerkMediaReviewState;
|
||||||
|
import com.starry.admin.modules.clerk.enums.ClerkMediaUsage;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkMediaAssetEntity;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.clerk.service.IPlayClerkMediaAssetService;
|
||||||
|
import com.starry.admin.modules.media.entity.PlayMediaEntity;
|
||||||
|
import com.starry.admin.modules.media.enums.MediaOwnerType;
|
||||||
|
import com.starry.admin.modules.media.service.IPlayMediaService;
|
||||||
|
import com.starry.admin.modules.weichat.assembler.ClerkMediaAssembler;
|
||||||
|
import com.starry.admin.modules.weichat.entity.clerk.MediaOrderRequest;
|
||||||
|
import com.starry.admin.modules.weichat.entity.clerk.MediaVo;
|
||||||
|
import com.starry.admin.modules.weichat.service.MediaUploadService;
|
||||||
|
import com.starry.common.result.R;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
@Api(tags = "店员媒资接口")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/wx/clerk/media")
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WxClerkMediaController {
|
||||||
|
|
||||||
|
private final MediaUploadService mediaUploadService;
|
||||||
|
private final IPlayMediaService mediaService;
|
||||||
|
private final IPlayClerkMediaAssetService clerkMediaAssetService;
|
||||||
|
|
||||||
|
@ApiOperation("上传媒资(图片/视频)")
|
||||||
|
@PostMapping("/upload")
|
||||||
|
@ClerkUserLogin
|
||||||
|
public R upload(@RequestParam("file") MultipartFile file,
|
||||||
|
@RequestParam(value = "usage", required = false) String usageCode) {
|
||||||
|
PlayClerkUserInfoEntity clerkInfo = requireClerkInfo();
|
||||||
|
MediaVo vo = mediaUploadService.upload(file, clerkInfo, ClerkMediaUsage.fromCode(usageCode));
|
||||||
|
return R.ok(vo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("更新媒资顺序并提交保留列表")
|
||||||
|
@PutMapping("/order")
|
||||||
|
@ClerkUserLogin
|
||||||
|
public R updateOrder(@Valid @RequestBody MediaOrderRequest request) {
|
||||||
|
PlayClerkUserInfoEntity clerkInfo = requireClerkInfo();
|
||||||
|
ClerkMediaUsage usage = ClerkMediaUsage.fromCode(request.getUsage());
|
||||||
|
List<String> mediaIds = CollUtil.isEmpty(request.getMediaIds()) ? Collections.emptyList()
|
||||||
|
: request.getMediaIds().stream().distinct().collect(Collectors.toList());
|
||||||
|
clerkMediaAssetService.submitWithOrder(clerkInfo.getId(), usage, mediaIds);
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("删除媒资(软删除)")
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
@ClerkUserLogin
|
||||||
|
public R delete(@PathVariable("id") String mediaId) {
|
||||||
|
PlayClerkUserInfoEntity clerkInfo = requireClerkInfo();
|
||||||
|
clerkMediaAssetService.softDelete(clerkInfo.getId(), mediaId);
|
||||||
|
mediaService.softDelete(MediaOwnerType.CLERK, clerkInfo.getId(), mediaId);
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("查询草稿媒资列表")
|
||||||
|
@GetMapping("/list")
|
||||||
|
@ClerkUserLogin
|
||||||
|
public R listDraft(@RequestParam(value = "usage", required = false) String usageCode) {
|
||||||
|
PlayClerkUserInfoEntity clerkInfo = requireClerkInfo();
|
||||||
|
ClerkMediaUsage usage = ClerkMediaUsage.fromCode(usageCode);
|
||||||
|
List<PlayClerkMediaAssetEntity> assets = clerkMediaAssetService.listByState(clerkInfo.getId(), usage,
|
||||||
|
Arrays.asList(ClerkMediaReviewState.DRAFT, ClerkMediaReviewState.PENDING,
|
||||||
|
ClerkMediaReviewState.REJECTED));
|
||||||
|
Map<String, PlayMediaEntity> mediaMap = loadMediaMap(assets);
|
||||||
|
return R.ok(ClerkMediaAssembler.toVoList(assets, mediaMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("查询已审核通过的媒资")
|
||||||
|
@GetMapping("/approved")
|
||||||
|
@ClerkUserLogin
|
||||||
|
public R listApproved(@RequestParam(value = "usage", required = false) String usageCode) {
|
||||||
|
PlayClerkUserInfoEntity clerkInfo = requireClerkInfo();
|
||||||
|
ClerkMediaUsage usage = ClerkMediaUsage.fromCode(usageCode);
|
||||||
|
List<PlayClerkMediaAssetEntity> assets = clerkMediaAssetService.listByState(clerkInfo.getId(), usage,
|
||||||
|
Collections.singletonList(ClerkMediaReviewState.APPROVED));
|
||||||
|
Map<String, PlayMediaEntity> mediaMap = loadMediaMap(assets);
|
||||||
|
return R.ok(ClerkMediaAssembler.toVoList(assets, mediaMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayClerkUserInfoEntity requireClerkInfo() {
|
||||||
|
PlayClerkUserInfoEntity clerk = ThreadLocalRequestDetail.getClerkUserInfo();
|
||||||
|
if (clerk == null) {
|
||||||
|
throw new CustomException("店员未登录");
|
||||||
|
}
|
||||||
|
return clerk;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, PlayMediaEntity> loadMediaMap(List<PlayClerkMediaAssetEntity> assets) {
|
||||||
|
if (CollUtil.isEmpty(assets)) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
List<String> mediaIds = assets.stream().map(PlayClerkMediaAssetEntity::getMediaId).distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
List<PlayMediaEntity> mediaList = mediaService.listByIds(mediaIds);
|
||||||
|
if (CollUtil.isEmpty(mediaList)) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
return mediaList.stream().collect(Collectors.toMap(PlayMediaEntity::getId, item -> item));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -151,16 +151,9 @@ public class WxCustomController {
|
|||||||
@ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayClerkUserInfoResultVo.class)})
|
@ApiResponses({@ApiResponse(code = 200, message = "操作成功", response = PlayClerkUserInfoResultVo.class)})
|
||||||
@GetMapping("/queryClerkDetailedById")
|
@GetMapping("/queryClerkDetailedById")
|
||||||
public R queryClerkDetailedById(@RequestParam("id") String id) {
|
public R queryClerkDetailedById(@RequestParam("id") String id) {
|
||||||
PlayClerkUserInfoEntity entity = clerkUserInfoService.selectById(id);
|
|
||||||
PlayClerkUserInfoResultVo vo = ConvertUtil.entityToVo(entity, PlayClerkUserInfoResultVo.class);
|
|
||||||
vo.setAddress(entity.getCity());
|
|
||||||
// 查询是否关注,未登录情况下,默认为未关注
|
|
||||||
String loginUserId = customUserService.getLoginUserId();
|
String loginUserId = customUserService.getLoginUserId();
|
||||||
if (StringUtils.isNotEmpty(loginUserId)) {
|
PlayClerkUserInfoResultVo vo = clerkUserInfoService.buildCustomerDetail(id,
|
||||||
vo.setFollowState(playCustomFollowInfoService.queryFollowState(loginUserId, vo.getId()));
|
StringUtils.isNotEmpty(loginUserId) ? loginUserId : "");
|
||||||
}
|
|
||||||
// 服务项目
|
|
||||||
vo.setCommodity(playClerkCommodityService.getClerkCommodityList(vo.getId(), "1"));
|
|
||||||
return R.ok(vo);
|
return R.ok(vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,7 +348,9 @@ public class WxCustomController {
|
|||||||
orderNo,
|
orderNo,
|
||||||
netAmount.toString(),
|
netAmount.toString(),
|
||||||
commodityInfo.getCommodityName(),
|
commodityInfo.getCommodityName(),
|
||||||
order.getId());
|
order.getId(),
|
||||||
|
order.getPlaceType(),
|
||||||
|
order.getRewardType());
|
||||||
return R.ok("成功");
|
return R.ok("成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,7 +425,14 @@ public class WxCustomController {
|
|||||||
.eq(PlayClerkUserInfoEntity::getListingState, ListingStatus.LISTED.getCode())
|
.eq(PlayClerkUserInfoEntity::getListingState, ListingStatus.LISTED.getCode())
|
||||||
.eq(PlayClerkUserInfoEntity::getOnlineState, "1")
|
.eq(PlayClerkUserInfoEntity::getOnlineState, "1")
|
||||||
.eq(PlayClerkUserInfoEntity::getSex, vo.getSex()));
|
.eq(PlayClerkUserInfoEntity::getSex, vo.getSex()));
|
||||||
wxCustomMpService.sendCreateOrderMessageBatch(clerkList, orderNo, netAmount.toString(), commodityInfo.getCommodityName(),order.getId());
|
wxCustomMpService.sendCreateOrderMessageBatch(
|
||||||
|
clerkList,
|
||||||
|
orderNo,
|
||||||
|
netAmount.toString(),
|
||||||
|
commodityInfo.getCommodityName(),
|
||||||
|
order.getId(),
|
||||||
|
order.getPlaceType(),
|
||||||
|
order.getRewardType());
|
||||||
// 记录订单,指定指定未接单后,进行退款处理
|
// 记录订单,指定指定未接单后,进行退款处理
|
||||||
overdueOrderHandlerTask.enqueue(orderId + "_" + SecurityUtils.getTenantId());
|
overdueOrderHandlerTask.enqueue(orderId + "_" + SecurityUtils.getTenantId());
|
||||||
// 下单成功后,先根据用户条件进行随机分配
|
// 下单成功后,先根据用户条件进行随机分配
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.starry.admin.modules.weichat.entity;
|
|||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
|
||||||
import com.starry.admin.modules.clerk.module.vo.PlayClerkCommodityQueryVo;
|
import com.starry.admin.modules.clerk.module.vo.PlayClerkCommodityQueryVo;
|
||||||
|
import com.starry.admin.modules.weichat.entity.clerk.MediaVo;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -45,6 +46,11 @@ public class PlayClerkUserLoginResponseVo {
|
|||||||
*/
|
*/
|
||||||
private List<String> album = new ArrayList<>();
|
private List<String> album = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新媒资列表
|
||||||
|
*/
|
||||||
|
private List<MediaVo> mediaList = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 相册是否运行编辑
|
* 相册是否运行编辑
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.starry.admin.modules.weichat.entity.clerk;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class MediaOrderRequest {
|
||||||
|
|
||||||
|
private String usage;
|
||||||
|
|
||||||
|
@NotNull(message = "媒资ID列表不能为空")
|
||||||
|
private List<String> mediaIds;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.starry.admin.modules.weichat.entity.clerk;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class MediaVo implements Serializable {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private String assetId;
|
||||||
|
|
||||||
|
private String mediaId;
|
||||||
|
|
||||||
|
private String kind;
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
private String coverUrl;
|
||||||
|
|
||||||
|
private Long durationMs;
|
||||||
|
|
||||||
|
private Integer width;
|
||||||
|
|
||||||
|
private Integer height;
|
||||||
|
|
||||||
|
private Long sizeBytes;
|
||||||
|
|
||||||
|
private Integer orderIndex;
|
||||||
|
|
||||||
|
private Map<String, Object> metadata;
|
||||||
|
|
||||||
|
private String usage;
|
||||||
|
|
||||||
|
private String reviewState;
|
||||||
|
|
||||||
|
private LocalDateTime submittedTime;
|
||||||
|
|
||||||
|
private String reviewNote;
|
||||||
|
}
|
||||||
@@ -75,6 +75,12 @@ public class PlayClerkUserInfoResultVo {
|
|||||||
@ApiModelProperty(value = "相册列表", notes = "店员相册图片URL列表")
|
@ApiModelProperty(value = "相册列表", notes = "店员相册图片URL列表")
|
||||||
private List<String> album = new ArrayList<>();
|
private List<String> album = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 媒资列表
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "媒资列表", notes = "结构化媒资数据")
|
||||||
|
private List<MediaVo> mediaList = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 个性签名
|
* 个性签名
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ public class PlayCustomOrderDetailsReturnVo {
|
|||||||
private String orderNo;
|
private String orderNo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单状态【0:1:2:3:4】 0:已下单(待接单) 1:已接单(待开始) 2:已开始(服务中) 3:已完成 4:已取消
|
* 订单状态【0:1:2:3:4:5】
|
||||||
|
* 0:已下单(待接单) 1:已接单(待开始) 2:已开始(服务中) 3:已完成 4:已取消 5:已撤销
|
||||||
*/
|
*/
|
||||||
private String orderStatus;
|
private String orderStatus;
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,15 @@ public class PlayCustomOrderInfoQueryVo extends BasePageEntity {
|
|||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
/**
|
/**
|
||||||
* 订单状态【0:1:2:3:4】 0:已下单 1:已接单 2:已开始 3:已完成 4:已取消
|
* 订单状态【0:1:2:3:4:5】
|
||||||
|
* 0:已下单 1:已接单 2:已开始 3:已完成 4:已取消 5:已撤销
|
||||||
*/
|
*/
|
||||||
private String orderStatus;
|
private String orderStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单类型【0:充值订单;1:提现订单;2:普通订单】
|
* 订单类型(为空时默认排除充值/提现)
|
||||||
*/
|
*/
|
||||||
private String orderType = "2";
|
private String orderType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下单类型(0:指定单,1:随机单。2:打赏单)
|
* 下单类型(0:指定单,1:随机单。2:打赏单)
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ public class PlayCustomOrderListReturnVo {
|
|||||||
private String orderNo;
|
private String orderNo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单状态【0:1:2:3:4】 0:已下单(待接单) 1:已接单(待开始) 2:已开始(服务中) 3:已完成 4:已取消
|
* 订单状态【0:1:2:3:4:5】
|
||||||
|
* 0:已下单(待接单) 1:已接单(待开始) 2:已开始(服务中) 3:已完成 4:已取消 5:已撤销
|
||||||
*/
|
*/
|
||||||
private String orderStatus;
|
private String orderStatus;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.starry.admin.modules.weichat.service;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReviewInfoEntity;
|
||||||
|
import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity;
|
||||||
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@Profile("!apitest")
|
||||||
|
public class DefaultNotificationSender implements NotificationSender {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private WxCustomMpService wxCustomMpService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendOrderMessageAsync(PlayOrderInfoEntity orderInfo) {
|
||||||
|
wxCustomMpService.sendOrderMessageAsync(orderInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendOrderFinishMessageAsync(PlayOrderInfoEntity order) {
|
||||||
|
wxCustomMpService.sendOrderFinishMessageAsync(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendOrderCancelMessageAsync(PlayOrderInfoEntity orderInfo, String refundReason) {
|
||||||
|
wxCustomMpService.sendOrderCancelMessageAsync(orderInfo, refundReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendComplaintMessage(PlayOrderComplaintInfoEntity info, PlayOrderInfoEntity orderInfo) {
|
||||||
|
wxCustomMpService.sendComplaintMessage(info, orderInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendCheckMessage(PlayClerkUserReviewInfoEntity entity, PlayClerkUserInfoEntity userInfo,
|
||||||
|
String reviewState) {
|
||||||
|
wxCustomMpService.sendCheckMessage(entity, userInfo, reviewState);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
package com.starry.admin.modules.weichat.service;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileTypeUtil;
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.starry.admin.common.exception.CustomException;
|
||||||
|
import com.starry.admin.common.oss.service.IOssFileService;
|
||||||
|
import com.starry.admin.modules.clerk.enums.ClerkMediaUsage;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkMediaAssetEntity;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.clerk.service.IPlayClerkMediaAssetService;
|
||||||
|
import com.starry.admin.modules.media.entity.PlayMediaEntity;
|
||||||
|
import com.starry.admin.modules.media.enums.MediaKind;
|
||||||
|
import com.starry.admin.modules.media.enums.MediaOwnerType;
|
||||||
|
import com.starry.admin.modules.media.enums.MediaStatus;
|
||||||
|
import com.starry.admin.modules.media.service.IPlayMediaService;
|
||||||
|
import com.starry.admin.modules.weichat.assembler.ClerkMediaAssembler;
|
||||||
|
import com.starry.admin.modules.weichat.entity.clerk.MediaVo;
|
||||||
|
import com.starry.common.utils.IdUtils;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import ws.schild.jave.Encoder;
|
||||||
|
import ws.schild.jave.MultimediaObject;
|
||||||
|
import ws.schild.jave.encode.AudioAttributes;
|
||||||
|
import ws.schild.jave.encode.EncodingAttributes;
|
||||||
|
import ws.schild.jave.encode.VideoAttributes;
|
||||||
|
import ws.schild.jave.info.MultimediaInfo;
|
||||||
|
import ws.schild.jave.info.VideoInfo;
|
||||||
|
import ws.schild.jave.info.VideoSize;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class MediaUploadService {
|
||||||
|
|
||||||
|
private static final long MAX_VIDEO_BYTES = 30L * 1024 * 1024;
|
||||||
|
private static final long MAX_VIDEO_DURATION_MS = 45_000;
|
||||||
|
private static final String IMAGE_OUTPUT_FORMAT = "image2";
|
||||||
|
private static final String VIDEO_OUTPUT_FORMAT = "mp4";
|
||||||
|
|
||||||
|
private final IOssFileService ossFileService;
|
||||||
|
private final IPlayMediaService mediaService;
|
||||||
|
private final IPlayClerkMediaAssetService clerkMediaAssetService;
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public MediaVo upload(MultipartFile file, PlayClerkUserInfoEntity clerkInfo, ClerkMediaUsage usage) {
|
||||||
|
if (file == null || file.isEmpty()) {
|
||||||
|
throw new CustomException("请选择要上传的文件");
|
||||||
|
}
|
||||||
|
if (clerkInfo == null) {
|
||||||
|
throw new CustomException("店员信息不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
String originalFilename = StrUtil.blankToDefault(file.getOriginalFilename(), file.getName());
|
||||||
|
File tempFile = null;
|
||||||
|
File processedVideoFile = null;
|
||||||
|
File coverFile = null;
|
||||||
|
try {
|
||||||
|
String suffix = resolveSuffix(originalFilename);
|
||||||
|
tempFile = createTempFile("media_", suffix);
|
||||||
|
file.transferTo(tempFile);
|
||||||
|
|
||||||
|
String detectedType = detectFileType(tempFile, file.getContentType());
|
||||||
|
boolean isVideo = isVideoType(detectedType, file.getContentType());
|
||||||
|
boolean isImage = isImageType(detectedType, file.getContentType());
|
||||||
|
if (!isVideo && !isImage) {
|
||||||
|
log.warn("Unsupported media type: {} / {}", detectedType, file.getContentType());
|
||||||
|
throw new CustomException("不支持的文件格式");
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayMediaEntity entity = buildSkeletonEntity(file, clerkInfo,
|
||||||
|
isVideo ? MediaKind.VIDEO : MediaKind.IMAGE);
|
||||||
|
entity.getMetadata().put("detectedType", detectedType);
|
||||||
|
entity.getMetadata().put("isVideo", isVideo);
|
||||||
|
|
||||||
|
if (isImage) {
|
||||||
|
handleImageUpload(tempFile, entity, clerkInfo, originalFilename);
|
||||||
|
} else {
|
||||||
|
processedVideoFile = createTempFile("media_video_", ".mp4");
|
||||||
|
coverFile = createTempFile("media_cover_", ".jpg");
|
||||||
|
handleVideoUpload(tempFile, processedVideoFile, coverFile, entity, clerkInfo, originalFilename);
|
||||||
|
}
|
||||||
|
entity.setStatus(MediaStatus.READY.getCode());
|
||||||
|
mediaService.normalizeAndSave(entity);
|
||||||
|
PlayClerkMediaAssetEntity asset = clerkMediaAssetService.linkDraftAsset(
|
||||||
|
clerkInfo.getTenantId(),
|
||||||
|
clerkInfo.getId(),
|
||||||
|
entity.getId(),
|
||||||
|
usage == null ? ClerkMediaUsage.PROFILE : usage);
|
||||||
|
return ClerkMediaAssembler.toVo(entity, asset);
|
||||||
|
} catch (CustomException customException) {
|
||||||
|
throw customException;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("媒资上传失败", ex);
|
||||||
|
throw new CustomException("媒资上传失败,请稍后重试");
|
||||||
|
} finally {
|
||||||
|
deleteQuietly(tempFile);
|
||||||
|
deleteQuietly(processedVideoFile);
|
||||||
|
deleteQuietly(coverFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayMediaEntity buildSkeletonEntity(MultipartFile file, PlayClerkUserInfoEntity clerkInfo, MediaKind kind) {
|
||||||
|
PlayMediaEntity entity = new PlayMediaEntity();
|
||||||
|
entity.setId(IdUtils.getUuid());
|
||||||
|
entity.setTenantId(clerkInfo.getTenantId());
|
||||||
|
entity.setOwnerType(MediaOwnerType.CLERK);
|
||||||
|
entity.setOwnerId(clerkInfo.getId());
|
||||||
|
entity.setKind(kind.getCode());
|
||||||
|
entity.setStatus(MediaStatus.UPLOADED.getCode());
|
||||||
|
entity.setSizeBytes(file.getSize());
|
||||||
|
Map<String, Object> metadata = new HashMap<>();
|
||||||
|
metadata.put("originalFilename", file.getOriginalFilename());
|
||||||
|
metadata.put("contentType", file.getContentType());
|
||||||
|
metadata.put("uploadTraceId", IdUtil.fastUUID());
|
||||||
|
metadata.put("sourceSizeBytes", file.getSize());
|
||||||
|
entity.setMetadata(metadata);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleImageUpload(File tempFile, PlayMediaEntity entity, PlayClerkUserInfoEntity clerkInfo,
|
||||||
|
String originalFilename) throws IOException {
|
||||||
|
BufferedImage image = ImageIO.read(tempFile);
|
||||||
|
if (image == null) {
|
||||||
|
throw new CustomException("图片文件已损坏或格式不受支持");
|
||||||
|
}
|
||||||
|
entity.setWidth(image.getWidth());
|
||||||
|
entity.setHeight(image.getHeight());
|
||||||
|
try (InputStream is = Files.newInputStream(tempFile.toPath())) {
|
||||||
|
String targetName = buildObjectName("img", originalFilename);
|
||||||
|
String url = ossFileService.upload(is, clerkInfo.getTenantId(), targetName);
|
||||||
|
entity.setUrl(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleVideoUpload(File sourceFile, File targetFile, File coverFile, PlayMediaEntity entity,
|
||||||
|
PlayClerkUserInfoEntity clerkInfo, String originalFilename) throws Exception {
|
||||||
|
if (entity.getSizeBytes() != null && entity.getSizeBytes() > MAX_VIDEO_BYTES) {
|
||||||
|
throw new CustomException("视频大小不能超过30MB");
|
||||||
|
}
|
||||||
|
MultimediaObject multimediaObject = new MultimediaObject(sourceFile);
|
||||||
|
MultimediaInfo info = multimediaObject.getInfo();
|
||||||
|
if (info == null || info.getVideo() == null) {
|
||||||
|
throw new CustomException("无法读取视频信息");
|
||||||
|
}
|
||||||
|
long durationMs = info.getDuration();
|
||||||
|
if (durationMs > MAX_VIDEO_DURATION_MS) {
|
||||||
|
throw new CustomException("视频时长不能超过45秒");
|
||||||
|
}
|
||||||
|
VideoInfo videoInfo = info.getVideo();
|
||||||
|
VideoSize size = videoInfo.getSize();
|
||||||
|
if (size != null) {
|
||||||
|
entity.setWidth(size.getWidth());
|
||||||
|
entity.setHeight(size.getHeight());
|
||||||
|
}
|
||||||
|
entity.setDurationMs(durationMs);
|
||||||
|
|
||||||
|
AudioAttributes audioAttrs = new AudioAttributes();
|
||||||
|
audioAttrs.setCodec("aac");
|
||||||
|
audioAttrs.setBitRate(128_000);
|
||||||
|
audioAttrs.setChannels(2);
|
||||||
|
audioAttrs.setSamplingRate(44_100);
|
||||||
|
|
||||||
|
VideoAttributes videoAttrs = new VideoAttributes();
|
||||||
|
videoAttrs.setCodec("h264");
|
||||||
|
videoAttrs.setBitRate(1_500_000);
|
||||||
|
if (size != null) {
|
||||||
|
videoAttrs.setSize(size);
|
||||||
|
}
|
||||||
|
float frameRate = videoInfo.getFrameRate();
|
||||||
|
videoAttrs.setFrameRate(frameRate > 0 ? Math.round(frameRate) : 30);
|
||||||
|
|
||||||
|
Encoder encoder = new Encoder();
|
||||||
|
EncodingAttributes attrs = new EncodingAttributes();
|
||||||
|
attrs.setOutputFormat(VIDEO_OUTPUT_FORMAT);
|
||||||
|
attrs.setAudioAttributes(audioAttrs);
|
||||||
|
attrs.setVideoAttributes(videoAttrs);
|
||||||
|
encoder.encode(multimediaObject, targetFile, attrs);
|
||||||
|
|
||||||
|
long processedSize = targetFile.length();
|
||||||
|
entity.setSizeBytes(processedSize);
|
||||||
|
|
||||||
|
// 抽取首帧作为封面
|
||||||
|
EncodingAttributes coverAttrs = new EncodingAttributes();
|
||||||
|
VideoAttributes coverVideoAttrs = new VideoAttributes();
|
||||||
|
coverVideoAttrs.setCodec("mjpeg");
|
||||||
|
if (size != null) {
|
||||||
|
coverVideoAttrs.setSize(size);
|
||||||
|
}
|
||||||
|
coverAttrs.setOutputFormat(IMAGE_OUTPUT_FORMAT);
|
||||||
|
coverAttrs.setVideoAttributes(coverVideoAttrs);
|
||||||
|
coverAttrs.setDuration(0.01f);
|
||||||
|
coverAttrs.setOffset(0f);
|
||||||
|
coverAttrs.setAudioAttributes(null);
|
||||||
|
encoder.encode(new MultimediaObject(targetFile), coverFile, coverAttrs);
|
||||||
|
|
||||||
|
try (InputStream videoIs = Files.newInputStream(targetFile.toPath());
|
||||||
|
InputStream coverIs = Files.newInputStream(coverFile.toPath())) {
|
||||||
|
String videoName = buildObjectName("video", originalFilename);
|
||||||
|
String coverName = buildObjectName("cover", originalFilename + ".jpg");
|
||||||
|
String videoUrl = ossFileService.upload(videoIs, clerkInfo.getTenantId(), videoName);
|
||||||
|
String coverUrl = ossFileService.upload(coverIs, clerkInfo.getTenantId(), coverName);
|
||||||
|
entity.setUrl(videoUrl);
|
||||||
|
entity.setCoverUrl(coverUrl);
|
||||||
|
}
|
||||||
|
if (entity.getMetadata() != null) {
|
||||||
|
entity.getMetadata().put("durationMs", durationMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String detectFileType(File file, String contentType) {
|
||||||
|
String type = null;
|
||||||
|
try {
|
||||||
|
type = FileTypeUtil.getType(file);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("Failed to read file type via signature, fallback to contentType: {}", contentType, ex);
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(type)) {
|
||||||
|
return type.toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(contentType)) {
|
||||||
|
return contentType.toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isVideoType(String detectedType, String mime) {
|
||||||
|
if (StrUtil.isBlank(detectedType) && StrUtil.isBlank(mime)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String lower = StrUtil.blankToDefault(detectedType, "");
|
||||||
|
if (lower.contains("mp4") || lower.contains("mov") || lower.contains("quicktime")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String mimeLower = StrUtil.blankToDefault(mime, "").toLowerCase(Locale.ROOT);
|
||||||
|
return mimeLower.startsWith("video/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isImageType(String detectedType, String mime) {
|
||||||
|
String lower = StrUtil.blankToDefault(detectedType, "");
|
||||||
|
if (lower.contains("jpg") || lower.contains("jpeg") || lower.contains("png") || lower.contains("gif")
|
||||||
|
|| lower.contains("webp")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String mimeLower = StrUtil.blankToDefault(mime, "").toLowerCase(Locale.ROOT);
|
||||||
|
return mimeLower.startsWith("image/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildObjectName(String category, String originalFilename) {
|
||||||
|
String ext = resolveSuffix(originalFilename);
|
||||||
|
return StrUtil.join("/", "clerk", category, IdUtils.getUuid() + ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveSuffix(String filename) {
|
||||||
|
if (StrUtil.isBlank(filename) || !filename.contains(".")) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return filename.substring(filename.lastIndexOf('.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteQuietly(File file) {
|
||||||
|
if (file != null && file.exists()) {
|
||||||
|
FileUtil.del(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File createTempFile(String prefix, String suffix) throws IOException {
|
||||||
|
String effectiveSuffix = StrUtil.isBlank(suffix) ? ".tmp" : suffix;
|
||||||
|
return Files.createTempFile(prefix, effectiveSuffix).toFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.starry.admin.modules.weichat.service;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReviewInfoEntity;
|
||||||
|
import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity;
|
||||||
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Primary
|
||||||
|
@Component
|
||||||
|
@Profile("apitest")
|
||||||
|
public class MockNotificationSender implements NotificationSender {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendOrderMessageAsync(PlayOrderInfoEntity orderInfo) {
|
||||||
|
log.debug("[wechat-mock] skip sendOrderMessageAsync orderId={}", orderInfo.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendOrderFinishMessageAsync(PlayOrderInfoEntity order) {
|
||||||
|
log.debug("[wechat-mock] skip sendOrderFinishMessageAsync orderId={}", order.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendOrderCancelMessageAsync(PlayOrderInfoEntity orderInfo, String refundReason) {
|
||||||
|
log.debug("[wechat-mock] skip sendOrderCancelMessageAsync orderId={}", orderInfo.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendComplaintMessage(PlayOrderComplaintInfoEntity info, PlayOrderInfoEntity orderInfo) {
|
||||||
|
log.debug("[wechat-mock] skip sendComplaintMessage orderId={}", orderInfo.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendCheckMessage(PlayClerkUserReviewInfoEntity entity, PlayClerkUserInfoEntity userInfo,
|
||||||
|
String reviewState) {
|
||||||
|
log.debug("[wechat-mock] skip sendCheckMessage clerkId={}", userInfo == null ? null : userInfo.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.starry.admin.modules.weichat.service;
|
||||||
|
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReviewInfoEntity;
|
||||||
|
import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity;
|
||||||
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽象出发送微信通知的接口,便于在测试环境下替换为 Mock 实现。
|
||||||
|
*/
|
||||||
|
public interface NotificationSender {
|
||||||
|
|
||||||
|
void sendOrderMessageAsync(PlayOrderInfoEntity orderInfo);
|
||||||
|
|
||||||
|
void sendOrderFinishMessageAsync(PlayOrderInfoEntity order);
|
||||||
|
|
||||||
|
void sendOrderCancelMessageAsync(PlayOrderInfoEntity orderInfo, String refundReason);
|
||||||
|
|
||||||
|
void sendComplaintMessage(PlayOrderComplaintInfoEntity info, PlayOrderInfoEntity orderInfo);
|
||||||
|
|
||||||
|
void sendCheckMessage(PlayClerkUserReviewInfoEntity entity, PlayClerkUserInfoEntity userInfo, String reviewState);
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import com.starry.admin.modules.clerk.module.enums.OnboardingStatus;
|
|||||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
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.module.constant.OrderMessageLabelResolver;
|
||||||
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.PlayOrderInfoEntity;
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity;
|
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelAdminInfoEntity;
|
||||||
@@ -37,6 +38,7 @@ import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
|
|||||||
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
|
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
|
||||||
import me.chanjar.weixin.mp.config.impl.WxMpMapConfigImpl;
|
import me.chanjar.weixin.mp.config.impl.WxMpMapConfigImpl;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -61,6 +63,7 @@ public class WxCustomMpService {
|
|||||||
@Resource
|
@Resource
|
||||||
private ThreadPoolTaskExecutor executor;
|
private ThreadPoolTaskExecutor executor;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付成功回调地址
|
* 支付成功回调地址
|
||||||
*/
|
*/
|
||||||
@@ -147,7 +150,8 @@ public class WxCustomMpService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendCreateOrderMessageBatch(List<PlayClerkUserInfoEntity> clerkList, String orderNo, String string, String commodityName, String orderId) {
|
public void sendCreateOrderMessageBatch(List<PlayClerkUserInfoEntity> clerkList, String orderNo, String string,
|
||||||
|
String commodityName, String orderId, String placeType, String rewardType) {
|
||||||
if (CollectionUtils.isEmpty(clerkList)) {
|
if (CollectionUtils.isEmpty(clerkList)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -157,7 +161,7 @@ public class WxCustomMpService {
|
|||||||
.filter(ca -> OnboardingStatus.isActive(ca.getOnboardingState()))
|
.filter(ca -> OnboardingStatus.isActive(ca.getOnboardingState()))
|
||||||
.filter(ca -> ListingStatus.isListed(ca.getListingState()))
|
.filter(ca -> ListingStatus.isListed(ca.getListingState()))
|
||||||
.forEach(ca -> sendCreateOrderMessage(ca.getTenantId(), ca.getOpenid(), orderNo, string, commodityName,
|
.forEach(ca -> sendCreateOrderMessage(ca.getTenantId(), ca.getOpenid(), orderNo, string, commodityName,
|
||||||
orderId)));
|
orderId, placeType, rewardType)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -171,7 +175,7 @@ public class WxCustomMpService {
|
|||||||
* @param orderId
|
* @param orderId
|
||||||
*/
|
*/
|
||||||
public void sendCreateOrderMessage(String tenantId, String openId, String orderNo, String money,
|
public void sendCreateOrderMessage(String tenantId, String openId, String orderNo, String money,
|
||||||
String commodityName, String orderId) {
|
String commodityName, String orderId, String placeType, String rewardType) {
|
||||||
|
|
||||||
SysTenantEntity tenant = tenantService.selectSysTenantByTenantId(tenantId);
|
SysTenantEntity tenant = tenantService.selectSysTenantByTenantId(tenantId);
|
||||||
WxMpTemplateMessage templateMessage = getWxMpTemplateMessage(tenant.getXindingdanshoulitongzhiTemplateId(),
|
WxMpTemplateMessage templateMessage = getWxMpTemplateMessage(tenant.getXindingdanshoulitongzhiTemplateId(),
|
||||||
@@ -179,7 +183,7 @@ public class WxCustomMpService {
|
|||||||
List<WxMpTemplateData> data = new ArrayList<>();
|
List<WxMpTemplateData> data = new ArrayList<>();
|
||||||
data.add(new WxMpTemplateData("time6", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")));
|
data.add(new WxMpTemplateData("time6", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")));
|
||||||
data.add(new WxMpTemplateData("character_string9", orderNo));
|
data.add(new WxMpTemplateData("character_string9", orderNo));
|
||||||
data.add(new WxMpTemplateData("short_thing5", "陪聊下单"));
|
data.add(new WxMpTemplateData("short_thing5", OrderMessageLabelResolver.resolve(placeType, rewardType)));
|
||||||
data.add(new WxMpTemplateData("thing11", commodityName));
|
data.add(new WxMpTemplateData("thing11", commodityName));
|
||||||
data.add(new WxMpTemplateData("amount8", money));
|
data.add(new WxMpTemplateData("amount8", money));
|
||||||
templateMessage.setData(data);
|
templateMessage.setData(data);
|
||||||
@@ -196,7 +200,6 @@ public class WxCustomMpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 店员接单后,通过微信公众号发送消息
|
* 店员接单后,通过微信公众号发送消息
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,6 +3,13 @@ package com.starry.admin.modules.withdraw.controller;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.starry.admin.common.exception.CustomException;
|
||||||
|
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||||
|
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||||
|
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
||||||
|
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||||
|
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
|
||||||
import com.starry.admin.modules.withdraw.entity.EarningsBackfillLogEntity;
|
import com.starry.admin.modules.withdraw.entity.EarningsBackfillLogEntity;
|
||||||
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||||
import com.starry.admin.modules.withdraw.entity.WithdrawalLogEntity;
|
import com.starry.admin.modules.withdraw.entity.WithdrawalLogEntity;
|
||||||
@@ -13,6 +20,7 @@ import com.starry.admin.modules.withdraw.service.IEarningsService;
|
|||||||
import com.starry.admin.modules.withdraw.service.ITenantAlipayConfigService;
|
import com.starry.admin.modules.withdraw.service.ITenantAlipayConfigService;
|
||||||
import com.starry.admin.modules.withdraw.service.IWithdrawalLogService;
|
import com.starry.admin.modules.withdraw.service.IWithdrawalLogService;
|
||||||
import com.starry.admin.modules.withdraw.service.IWithdrawalService;
|
import com.starry.admin.modules.withdraw.service.IWithdrawalService;
|
||||||
|
import com.starry.admin.modules.withdraw.vo.ClerkEarningLineVo;
|
||||||
import com.starry.admin.modules.withdraw.vo.EarningsAdminQueryVo;
|
import com.starry.admin.modules.withdraw.vo.EarningsAdminQueryVo;
|
||||||
import com.starry.admin.modules.withdraw.vo.EarningsAdminSummaryVo;
|
import com.starry.admin.modules.withdraw.vo.EarningsAdminSummaryVo;
|
||||||
import com.starry.admin.modules.withdraw.vo.EarningsBackfillRequest;
|
import com.starry.admin.modules.withdraw.vo.EarningsBackfillRequest;
|
||||||
@@ -24,11 +32,16 @@ import io.swagger.annotations.Api;
|
|||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@@ -49,6 +62,12 @@ public class AdminWithdrawalController {
|
|||||||
private IEarningsBackfillService earningsBackfillService;
|
private IEarningsBackfillService earningsBackfillService;
|
||||||
@Resource
|
@Resource
|
||||||
private IEarningsBackfillLogService backfillLogService;
|
private IEarningsBackfillLogService backfillLogService;
|
||||||
|
@Resource
|
||||||
|
private IPlayOrderInfoService orderInfoService;
|
||||||
|
@Resource
|
||||||
|
private IPlayClerkUserInfoService clerkUserInfoService;
|
||||||
|
@Resource
|
||||||
|
private IPlayCustomUserInfoService customUserInfoService;
|
||||||
|
|
||||||
@ApiOperation("分页查询提现请求")
|
@ApiOperation("分页查询提现请求")
|
||||||
@PostMapping("/requests/listByPage")
|
@PostMapping("/requests/listByPage")
|
||||||
@@ -72,6 +91,110 @@ public class AdminWithdrawalController {
|
|||||||
return TypedR.ok(list);
|
return TypedR.ok(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation("提现请求审计")
|
||||||
|
@GetMapping("/requests/{id}/audit")
|
||||||
|
public TypedR<List<ClerkEarningLineVo>> getRequestAudit(@PathVariable("id") String id) {
|
||||||
|
String tenantId = SecurityUtils.getTenantId();
|
||||||
|
WithdrawalRequestEntity request = withdrawalService.getById(id);
|
||||||
|
if (request == null || !tenantId.equals(request.getTenantId())) {
|
||||||
|
throw new CustomException("提现申请不存在或无权查看");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EarningsLineEntity> lines = earningsService.lambdaQuery()
|
||||||
|
.eq(EarningsLineEntity::getTenantId, tenantId)
|
||||||
|
.eq(EarningsLineEntity::getWithdrawalId, id)
|
||||||
|
.orderByAsc(EarningsLineEntity::getCreatedTime)
|
||||||
|
.list();
|
||||||
|
if (lines.isEmpty()) {
|
||||||
|
return TypedR.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> orderIds = lines.stream()
|
||||||
|
.map(EarningsLineEntity::getOrderId)
|
||||||
|
.filter(orderId -> orderId != null && !orderId.isEmpty())
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Map<String, PlayOrderInfoEntity> orderMap = orderIds.isEmpty()
|
||||||
|
? Collections.emptyMap()
|
||||||
|
: orderInfoService.lambdaQuery()
|
||||||
|
.eq(PlayOrderInfoEntity::getTenantId, tenantId)
|
||||||
|
.in(PlayOrderInfoEntity::getId, orderIds)
|
||||||
|
.list()
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(PlayOrderInfoEntity::getId, Function.identity()));
|
||||||
|
|
||||||
|
Map<String, PlayClerkUserInfoEntity> clerkMap = Collections.emptyMap();
|
||||||
|
Map<String, PlayCustomUserInfoEntity> customerMap = Collections.emptyMap();
|
||||||
|
if (!orderMap.isEmpty()) {
|
||||||
|
List<String> clerkIds = orderMap.values().stream()
|
||||||
|
.map(PlayOrderInfoEntity::getAcceptBy)
|
||||||
|
.filter(clerkIdValue -> clerkIdValue != null && !clerkIdValue.isEmpty())
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (!clerkIds.isEmpty()) {
|
||||||
|
clerkMap = clerkUserInfoService.lambdaQuery()
|
||||||
|
.eq(PlayClerkUserInfoEntity::getTenantId, tenantId)
|
||||||
|
.in(PlayClerkUserInfoEntity::getId, clerkIds)
|
||||||
|
.list()
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(PlayClerkUserInfoEntity::getId, Function.identity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> customerIds = orderMap.values().stream()
|
||||||
|
.map(PlayOrderInfoEntity::getPurchaserBy)
|
||||||
|
.filter(customerIdValue -> customerIdValue != null && !customerIdValue.isEmpty())
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (!customerIds.isEmpty()) {
|
||||||
|
customerMap = customUserInfoService.lambdaQuery()
|
||||||
|
.eq(PlayCustomUserInfoEntity::getTenantId, tenantId)
|
||||||
|
.in(PlayCustomUserInfoEntity::getId, customerIds)
|
||||||
|
.list()
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(PlayCustomUserInfoEntity::getId, Function.identity()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ClerkEarningLineVo> vos = new ArrayList<>(lines.size());
|
||||||
|
for (EarningsLineEntity line : lines) {
|
||||||
|
ClerkEarningLineVo vo = new ClerkEarningLineVo();
|
||||||
|
vo.setId(line.getId());
|
||||||
|
vo.setAmount(line.getAmount());
|
||||||
|
vo.setStatus(line.getStatus());
|
||||||
|
vo.setEarningType(line.getEarningType());
|
||||||
|
vo.setWithdrawalId(line.getWithdrawalId());
|
||||||
|
vo.setUnlockTime(line.getUnlockTime());
|
||||||
|
vo.setCreatedTime(toLocalDateTime(line.getCreatedTime()));
|
||||||
|
vo.setOrderId(line.getOrderId());
|
||||||
|
if (line.getOrderId() != null) {
|
||||||
|
PlayOrderInfoEntity order = orderMap.get(line.getOrderId());
|
||||||
|
if (order != null) {
|
||||||
|
vo.setOrderNo(order.getOrderNo());
|
||||||
|
vo.setOrderStatus(order.getOrderStatus());
|
||||||
|
vo.setOrderEndTime(toLocalDateTime(order.getOrderEndTime()));
|
||||||
|
String clerkId = order.getAcceptBy();
|
||||||
|
if (clerkId != null && !clerkId.isEmpty()) {
|
||||||
|
vo.setOrderClerkId(clerkId);
|
||||||
|
PlayClerkUserInfoEntity clerk = clerkMap.get(clerkId);
|
||||||
|
if (clerk != null) {
|
||||||
|
vo.setOrderClerkNickname(clerk.getNickname());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String customerId = order.getPurchaserBy();
|
||||||
|
if (customerId != null && !customerId.isEmpty()) {
|
||||||
|
vo.setOrderCustomerId(customerId);
|
||||||
|
PlayCustomUserInfoEntity customer = customerMap.get(customerId);
|
||||||
|
if (customer != null) {
|
||||||
|
vo.setOrderCustomerNickname(customer.getNickname());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vos.add(vo);
|
||||||
|
}
|
||||||
|
return TypedR.ok(vos);
|
||||||
|
}
|
||||||
|
|
||||||
@ApiOperation("分页查询收益明细")
|
@ApiOperation("分页查询收益明细")
|
||||||
@PostMapping("/earnings/listByPage")
|
@PostMapping("/earnings/listByPage")
|
||||||
public TypedR<List<EarningsLineEntity>> listEarnings(@RequestBody EarningsAdminQueryVo vo) {
|
public TypedR<List<EarningsLineEntity>> listEarnings(@RequestBody EarningsAdminQueryVo vo) {
|
||||||
@@ -182,4 +305,17 @@ public class AdminWithdrawalController {
|
|||||||
q.orderByDesc(EarningsLineEntity::getCreatedTime);
|
q.orderByDesc(EarningsLineEntity::getCreatedTime);
|
||||||
return q;
|
return q;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LocalDateTime toLocalDateTime(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value instanceof LocalDateTime) {
|
||||||
|
return (LocalDateTime) value;
|
||||||
|
}
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return ((Date) value).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user