Merge branch 'master' of http://101.43.112.107:3000/root/allLikeMall into cxw
This commit is contained in:
commit
9860ff79d5
49
yudao-admin-vue3/src/api/mall/promotion/pointorder/index.ts
Normal file
49
yudao-admin-vue3/src/api/mall/promotion/pointorder/index.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
// 兑换记录 VO
|
||||
export interface PointOrderVO {
|
||||
id: number // id
|
||||
orderNumber: string // 订单号
|
||||
userId: number // 用户id
|
||||
productId: number // 商品id
|
||||
integral: number // 兑换积分
|
||||
orderStatus: number // 订单状态
|
||||
orderTime: Date // 下单时间
|
||||
userName: string
|
||||
productName: string
|
||||
imageUrl: string
|
||||
|
||||
}
|
||||
|
||||
// 兑换记录 API
|
||||
export const PointOrderApi = {
|
||||
// 查询兑换记录分页
|
||||
getPointOrderPage: async (params: any) => {
|
||||
return await request.get({ url: `/promotion/point-order/page`, params })
|
||||
},
|
||||
|
||||
// 查询兑换记录详情
|
||||
getPointOrder: async (id: number) => {
|
||||
return await request.get({ url: `/promotion/point-order/get?id=` + id })
|
||||
},
|
||||
|
||||
// 新增兑换记录
|
||||
createPointOrder: async (data: PointOrderVO) => {
|
||||
return await request.post({ url: `/promotion/point-order/create`, data })
|
||||
},
|
||||
|
||||
// 修改兑换记录
|
||||
updatePointOrder: async (data: PointOrderVO) => {
|
||||
return await request.put({ url: `/promotion/point-order/update`, data })
|
||||
},
|
||||
|
||||
// 删除兑换记录
|
||||
deletePointOrder: async (id: number) => {
|
||||
return await request.delete({ url: `/promotion/point-order/delete?id=` + id })
|
||||
},
|
||||
|
||||
// 导出兑换记录 Excel
|
||||
exportPointOrder: async (params) => {
|
||||
return await request.download({ url: `/promotion/point-order/export-excel`, params })
|
||||
},
|
||||
}
|
@ -22,8 +22,8 @@
|
||||
<div class="ml-10px w-100%">
|
||||
<div class="flex justify-between items-center w-100%">
|
||||
<span class="username">{{ item.userNickname }}</span>
|
||||
<span class="color-[#989EA6]">
|
||||
{{ formatPast(item.lastMessageTime, 'YYYY-mm-dd') }}
|
||||
<span class="color-[var(--left-menu-text-color)]" style="font-size: 13px">
|
||||
{{ formatPast(item.lastMessageTime, 'YYYY-MM-DD') }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- 最后聊天内容 -->
|
||||
@ -31,8 +31,9 @@
|
||||
v-dompurify-html="
|
||||
getConversationDisplayText(item.lastMessageContentType, item.lastMessageContent)
|
||||
"
|
||||
class="last-message flex items-center color-[#989EA6]"
|
||||
></div>
|
||||
class="last-message flex items-center color-[var(--left-menu-text-color)]"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -182,7 +183,7 @@ watch(showRightMenu, (val) => {
|
||||
&-conversation {
|
||||
height: 60px;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
//background-color: #fff;
|
||||
transition: border-left 0.05s ease-in-out; /* 设置过渡效果 */
|
||||
|
||||
.username {
|
||||
@ -196,6 +197,7 @@ watch(showRightMenu, (val) => {
|
||||
}
|
||||
|
||||
.last-message {
|
||||
font-size: 13px;
|
||||
width: 200px;
|
||||
overflow: hidden; // 隐藏超出的文本
|
||||
white-space: nowrap; // 禁止换行
|
||||
@ -205,17 +207,17 @@ watch(showRightMenu, (val) => {
|
||||
|
||||
.active {
|
||||
border-left: 5px #3271ff solid;
|
||||
background-color: #eff0f1;
|
||||
background-color: var(--login-bg-color);
|
||||
}
|
||||
|
||||
.pinned {
|
||||
background-color: #eff0f1;
|
||||
background-color: var(--left-menu-bg-active-color);
|
||||
}
|
||||
|
||||
.right-menu-ul {
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
padding: 10px;
|
||||
background-color: var(--app-content-bg-color);
|
||||
padding: 5px;
|
||||
margin: 0;
|
||||
list-style-type: none; /* 移除默认的项目符号 */
|
||||
border-radius: 12px;
|
||||
@ -228,7 +230,7 @@ watch(showRightMenu, (val) => {
|
||||
border-radius: 12px;
|
||||
transition: background-color 0.3s; /* 平滑过渡 */
|
||||
&:hover {
|
||||
background-color: #e0e0e0; /* 悬停时的背景颜色 */
|
||||
background-color: var(--left-menu-bg-active-color); /* 悬停时的背景颜色 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +71,7 @@
|
||||
<MessageItem :message="item">
|
||||
<ProductItem
|
||||
v-if="KeFuMessageContentTypeEnum.PRODUCT === item.contentType"
|
||||
:spuId="getMessageContent(item).spuId"
|
||||
:picUrl="getMessageContent(item).picUrl"
|
||||
:price="getMessageContent(item).price"
|
||||
:skuText="getMessageContent(item).introduction"
|
||||
@ -85,7 +86,7 @@
|
||||
<OrderItem
|
||||
v-if="KeFuMessageContentTypeEnum.ORDER === item.contentType"
|
||||
:message="item"
|
||||
class="max-w-70%"
|
||||
class="max-w-100%"
|
||||
/>
|
||||
</MessageItem>
|
||||
</div>
|
||||
@ -369,9 +370,10 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
||||
position: absolute;
|
||||
bottom: 35px;
|
||||
right: 35px;
|
||||
background-color: #fff;
|
||||
background-color: var(--app-content-bg-color);
|
||||
padding: 10px;
|
||||
border-radius: 30px;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 阴影效果 */
|
||||
}
|
||||
|
||||
@ -392,7 +394,7 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
||||
border-left: 5px solid transparent;
|
||||
border-bottom: 5px solid transparent;
|
||||
border-top: 5px solid transparent;
|
||||
border-right: 5px solid #ffffff;
|
||||
border-right: 5px solid var(--app-content-bg-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -411,7 +413,7 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
||||
right: -19px;
|
||||
top: calc(50% - 10px);
|
||||
position: absolute;
|
||||
border-left: 5px solid #ffffff;
|
||||
border-left: 5px solid var(--app-content-bg-color);
|
||||
border-bottom: 5px solid transparent;
|
||||
border-top: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
@ -421,9 +423,9 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
||||
|
||||
// 消息气泡
|
||||
.kefu-message {
|
||||
color: #333;
|
||||
color: #a9a9a9;
|
||||
border-radius: 5px;
|
||||
box-shadow: 3px 5px 15px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 3px 3px 5px rgba(220, 220, 220, 0.1);
|
||||
padding: 5px 10px;
|
||||
width: auto;
|
||||
max-width: 50%;
|
||||
@ -431,7 +433,7 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
||||
display: inline-block !important;
|
||||
position: relative;
|
||||
word-break: break-all;
|
||||
background-color: #ffffff;
|
||||
background-color: var(--app-content-bg-color);
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
@ -445,7 +447,7 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
||||
border-radius: 12rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
background-color: #e8e8e8;
|
||||
//background-color: #e8e8e8;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
@ -453,7 +455,7 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
|
||||
|
||||
.chat-tools {
|
||||
width: 100%;
|
||||
border: #e4e0e0 solid 1px;
|
||||
border: var(--el-border-color) solid 1px;
|
||||
border-radius: 10px;
|
||||
height: 44px;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<el-tab-pane label="订单列表" name="b" />
|
||||
</el-tabs>
|
||||
<div>
|
||||
<el-scrollbar ref="scrollbarRef" always height="calc(100vh - 400px)" @scroll="handleScroll">
|
||||
<el-scrollbar ref="scrollbarRef" always height="calc(115vh - 400px)" @scroll="handleScroll">
|
||||
<!-- 最近浏览 -->
|
||||
<ProductBrowsingHistory v-if="activeName === 'a'" ref="productBrowsingHistoryRef" />
|
||||
<!-- 订单列表 -->
|
||||
@ -25,7 +25,7 @@ import OrderBrowsingHistory from './OrderBrowsingHistory.vue'
|
||||
import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { ElScrollbar as ElScrollbarType } from 'element-plus/es/components/scrollbar'
|
||||
import { ElScrollbar as ElScrollbarType } from 'element-plus/es/components/scrollbar/index'
|
||||
|
||||
defineOptions({ name: 'MemberBrowsingHistory' })
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<ProductItem
|
||||
v-for="item in list"
|
||||
:spu-id="item.spuId"
|
||||
:key="item.id"
|
||||
:picUrl="item.picUrl"
|
||||
:price="item.price"
|
||||
|
@ -1,14 +1,20 @@
|
||||
<template>
|
||||
<div v-if="isObject(getMessageContent)">
|
||||
<div :key="getMessageContent.id" class="order-list-card-box mt-14px">
|
||||
<div class="order-card-header flex items-center justify-between p-x-20px">
|
||||
<div class="order-no">订单号:{{ getMessageContent.no }}</div>
|
||||
<div class="order-card-header flex items-center justify-between p-x-5px">
|
||||
<div class="order-no">
|
||||
订单号:
|
||||
<span style="cursor: pointer" @click="openDetail(getMessageContent.id)">
|
||||
{{ getMessageContent.no }}
|
||||
</span>
|
||||
</div>
|
||||
<div :class="formatOrderColor(getMessageContent)" class="order-state font-16">
|
||||
{{ formatOrderStatus(getMessageContent) }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="item in getMessageContent.items" :key="item.id" class="border-bottom">
|
||||
<ProductItem
|
||||
:spu-id="item.spuId"
|
||||
:num="item.count"
|
||||
:picUrl="item.picUrl"
|
||||
:price="item.price"
|
||||
@ -16,7 +22,7 @@
|
||||
:title="item.spuName"
|
||||
/>
|
||||
</div>
|
||||
<div class="pay-box flex justify-end pr-20px">
|
||||
<div class="pay-box flex justify-end pr-5px">
|
||||
<div class="flex items-center">
|
||||
<div class="discounts-title pay-color"
|
||||
>共 {{ getMessageContent?.productCount }} 件商品,总金额:
|
||||
@ -36,6 +42,8 @@ import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message'
|
||||
import { isObject } from '@/utils/is'
|
||||
import ProductItem from '@/views/mall/promotion/kefu/components/message/ProductItem.vue'
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
defineOptions({ name: 'OrderItem' })
|
||||
const props = defineProps<{
|
||||
message?: KeFuMessageRespVO
|
||||
@ -46,6 +54,12 @@ const getMessageContent = computed(() =>
|
||||
typeof props.message !== 'undefined' ? jsonParse(props!.message!.content) : props.order
|
||||
)
|
||||
|
||||
/** 查看订单详情 */
|
||||
const openDetail = (id: number) => {
|
||||
console.log(getMessageContent)
|
||||
push({ name: 'TradeOrderDetail', params: { id } })
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化订单状态的颜色
|
||||
*
|
||||
@ -97,18 +111,28 @@ function formatOrderStatus(order: any) {
|
||||
.order-list-card-box {
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
background-color: #e2e2e2;
|
||||
border: 1px var(--el-border-color) solid;
|
||||
background-color: var(--app-content-bg-color);
|
||||
|
||||
.order-card-header {
|
||||
height: 28px;
|
||||
|
||||
.order-no {
|
||||
font-size: 16px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
|
||||
span {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--left-menu-bg-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pay-box {
|
||||
padding-top: 10px;
|
||||
|
||||
.discounts-title {
|
||||
font-size: 16px;
|
||||
line-height: normal;
|
||||
@ -123,24 +147,33 @@ function formatOrderStatus(order: any) {
|
||||
}
|
||||
|
||||
.pay-color {
|
||||
color: #333;
|
||||
font-size: 13px;
|
||||
color: var(--left-menu-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.warning-color {
|
||||
color: #faad14;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.danger-color {
|
||||
color: #ff3000;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.success-color {
|
||||
color: #52c41a;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.info-color {
|
||||
color: #999999;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div @click.stop="openDetail(props.spuId)" style="cursor: pointer;">
|
||||
<div>
|
||||
<slot name="top"></slot>
|
||||
</div>
|
||||
@ -15,6 +15,7 @@
|
||||
class="order-img"
|
||||
fit="contain"
|
||||
preview-teleported
|
||||
@click.stop
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@ -53,8 +54,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { fenToYuan } from '@/utils'
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
defineOptions({ name: 'ProductItem' })
|
||||
const props = defineProps({
|
||||
spuId: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
picUrl: {
|
||||
type: String,
|
||||
default: 'https://img1.baidu.com/it/u=1601695551,235775011&fm=26&fmt=auto'
|
||||
@ -107,13 +114,20 @@ const skuString = computed(() => {
|
||||
}
|
||||
return props.skuText
|
||||
})
|
||||
|
||||
/** 查看商品详情 */
|
||||
const openDetail = (spuId: number) => {
|
||||
console.log(props.spuId)
|
||||
push({ name: 'ProductSpuDetail', params: { id: spuId } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ss-order-card-warp {
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
background-color: #e2e2e2;
|
||||
border: 1px var(--el-border-color) solid;
|
||||
background-color: var(--app-content-bg-color);
|
||||
|
||||
.img-box {
|
||||
width: 80px;
|
||||
@ -133,19 +147,19 @@ const skuString = computed(() => {
|
||||
|
||||
.tool-box {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
right: 0;
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 16px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.spec-text {
|
||||
font-size: 16px;
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
color: #999999;
|
||||
min-width: 0;
|
||||
@ -157,13 +171,13 @@ const skuString = computed(() => {
|
||||
}
|
||||
|
||||
.price-text {
|
||||
font-size: 16px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
font-family: OPPOSANS;
|
||||
}
|
||||
|
||||
.total-text {
|
||||
font-size: 16px;
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
color: #999999;
|
||||
|
@ -66,7 +66,7 @@ export const useEmoji = () => {
|
||||
)
|
||||
for (const path in pathList) {
|
||||
const imageModule: any = await pathList[path]()
|
||||
emojiPathList.value.push(imageModule.default)
|
||||
emojiPathList.value.push({ path: path, src: imageModule.default })
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,8 +80,8 @@ export const useEmoji = () => {
|
||||
/**
|
||||
* 将文本中的表情替换成图片
|
||||
*
|
||||
* @param data 文本
|
||||
* @return 替换后的文本
|
||||
* @param content 消息内容
|
||||
*/
|
||||
const replaceEmoji = (content: string) => {
|
||||
let newData = content
|
||||
@ -93,7 +93,7 @@ export const useEmoji = () => {
|
||||
const emojiFile = getEmojiFileByName(item)
|
||||
newData = newData.replace(
|
||||
item,
|
||||
`<img class="chat-img" style="width: 24px;height: 24px;margin: 0 3px;" src="${emojiFile}"/>`
|
||||
`<img class="chat-img" style="width: 24px;height: 24px;margin: 0 3px;" src="${emojiFile}" alt=""/>`
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -116,7 +116,10 @@ export const useEmoji = () => {
|
||||
function getEmojiFileByName(name: string) {
|
||||
for (const emoji of emojiList) {
|
||||
if (emoji.name === name) {
|
||||
return emojiPathList.value.find((item: string) => item.indexOf(emoji.file) > -1)
|
||||
const emojiPath = emojiPathList.value.find(
|
||||
(item: { path: string; src: string }) => item.path.indexOf(emoji.file) > -1
|
||||
)
|
||||
return emojiPath ? emojiPath.src : undefined
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
@ -22,119 +22,118 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { KeFuConversationList, KeFuMessageList, MemberBrowsingHistory } from './components'
|
||||
import { WebSocketMessageTypeConstants } from './components/tools/constants'
|
||||
import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import { useWebSocket } from '@vueuse/core'
|
||||
import { KeFuConversationList, KeFuMessageList, MemberBrowsingHistory } from './components'
|
||||
import { WebSocketMessageTypeConstants } from './components/tools/constants'
|
||||
import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { getRefreshToken } from '@/utils/auth'
|
||||
import { useWebSocket } from '@vueuse/core'
|
||||
|
||||
defineOptions({ name: 'KeFu' })
|
||||
defineOptions({ name: 'KeFu' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
// ======================= WebSocket start =======================
|
||||
const server = ref(
|
||||
(import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') + '?token=' + getAccessToken()
|
||||
) // WebSocket 服务地址
|
||||
// ======================= WebSocket start =======================
|
||||
const server = ref(
|
||||
(import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') +
|
||||
'?token=' +
|
||||
getRefreshToken() // 使用 getRefreshToken() 方法,而不使用 getAccessToken() 方法的原因:WebSocket 无法方便的刷新访问令牌
|
||||
) // WebSocket 服务地址
|
||||
|
||||
/** 发起 WebSocket 连接 */
|
||||
const { data, close, open } = useWebSocket(server.value, {
|
||||
autoReconnect: true,
|
||||
heartbeat: true
|
||||
})
|
||||
/** 发起 WebSocket 连接 */
|
||||
const { data, close, open } = useWebSocket(server.value, {
|
||||
autoReconnect: true,
|
||||
heartbeat: true
|
||||
})
|
||||
|
||||
/** 监听 WebSocket 数据 */
|
||||
watchEffect(() => {
|
||||
if (!data.value) {
|
||||
console.log('111')
|
||||
return
|
||||
/** 监听 WebSocket 数据 */
|
||||
watchEffect(() => {
|
||||
if (!data.value) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 1. 收到心跳
|
||||
if (data.value === 'pong') {
|
||||
return
|
||||
}
|
||||
|
||||
// 2.1 解析 type 消息类型
|
||||
const jsonMessage = JSON.parse(data.value)
|
||||
const type = jsonMessage.type
|
||||
if (!type) {
|
||||
message.error('未知的消息类型:' + data.value)
|
||||
return
|
||||
}
|
||||
// 2.2 消息类型:KEFU_MESSAGE_TYPE
|
||||
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) {
|
||||
// 刷新会话列表
|
||||
// TODO @puhui999:不应该刷新列表,而是根据消息,本地 update 列表的数据;
|
||||
getConversationList()
|
||||
// 刷新消息列表
|
||||
keFuChatBoxRef.value?.refreshMessageList(JSON.parse(jsonMessage.content))
|
||||
return
|
||||
}
|
||||
// 2.3 消息类型:KEFU_MESSAGE_ADMIN_READ
|
||||
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
|
||||
// 刷新会话列表
|
||||
// TODO @puhui999:不应该刷新列表,而是根据消息,本地 update 列表的数据;
|
||||
getConversationList()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
// ======================= WebSocket end =======================
|
||||
/** 加载会话列表 */
|
||||
const keFuConversationRef = ref<InstanceType<typeof KeFuConversationList>>()
|
||||
const getConversationList = () => {
|
||||
keFuConversationRef.value?.getConversationList()
|
||||
}
|
||||
try {
|
||||
// 1. 收到心跳
|
||||
if (data.value === 'pong') {
|
||||
console.log('666')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('777')
|
||||
// 2.1 解析 type 消息类型
|
||||
const jsonMessage = JSON.parse(data.value)
|
||||
const type = jsonMessage.type
|
||||
if (!type) {
|
||||
message.error('未知的消息类型:' + data.value)
|
||||
return
|
||||
}
|
||||
// 2.2 消息类型:KEFU_MESSAGE_TYPE
|
||||
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) {
|
||||
// 刷新会话列表
|
||||
// TODO @puhui999:不应该刷新列表,而是根据消息,本地 update 列表的数据;
|
||||
getConversationList()
|
||||
// 刷新消息列表
|
||||
keFuChatBoxRef.value?.refreshMessageList(JSON.parse(jsonMessage.content))
|
||||
return
|
||||
}
|
||||
// 2.3 消息类型:KEFU_MESSAGE_ADMIN_READ
|
||||
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
|
||||
// 刷新会话列表
|
||||
// TODO @puhui999:不应该刷新列表,而是根据消息,本地 update 列表的数据;
|
||||
getConversationList()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
/** 加载指定会话的消息列表 */
|
||||
const keFuChatBoxRef = ref<InstanceType<typeof KeFuMessageList>>()
|
||||
const memberBrowsingHistoryRef = ref<InstanceType<typeof MemberBrowsingHistory>>()
|
||||
const handleChange = (conversation: KeFuConversationRespVO) => {
|
||||
keFuChatBoxRef.value?.getNewMessageList(conversation)
|
||||
memberBrowsingHistoryRef.value?.initHistory(conversation)
|
||||
}
|
||||
})
|
||||
// ======================= WebSocket end =======================
|
||||
/** 加载会话列表 */
|
||||
const keFuConversationRef = ref<InstanceType<typeof KeFuConversationList>>()
|
||||
const getConversationList = () => {
|
||||
keFuConversationRef.value?.getConversationList()
|
||||
}
|
||||
|
||||
/** 加载指定会话的消息列表 */
|
||||
const keFuChatBoxRef = ref<InstanceType<typeof KeFuMessageList>>()
|
||||
const memberBrowsingHistoryRef = ref<InstanceType<typeof MemberBrowsingHistory>>()
|
||||
const handleChange = (conversation: KeFuConversationRespVO) => {
|
||||
keFuChatBoxRef.value?.getNewMessageList(conversation)
|
||||
memberBrowsingHistoryRef.value?.initHistory(conversation)
|
||||
}
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getConversationList()
|
||||
// 打开 websocket 连接
|
||||
open()
|
||||
})
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getConversationList()
|
||||
// 打开 websocket 连接
|
||||
open()
|
||||
})
|
||||
|
||||
/** 销毁 */
|
||||
onBeforeUnmount(() => {
|
||||
// 关闭 websocket 连接
|
||||
close()
|
||||
})
|
||||
/** 销毁 */
|
||||
onBeforeUnmount(() => {
|
||||
// 关闭 websocket 连接
|
||||
close()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.kefu {
|
||||
height: calc(100vh - 165px);
|
||||
overflow: auto; /* 确保内容可滚动 */
|
||||
}
|
||||
.kefu {
|
||||
height: calc(100vh - 165px);
|
||||
overflow: auto; /* 确保内容可滚动 */
|
||||
}
|
||||
|
||||
/* 定义滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 6px;
|
||||
}
|
||||
/* 定义滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
/* 定义滚动条轨道 内阴影+圆角 */
|
||||
::-webkit-scrollbar-track {
|
||||
box-shadow: inset 0 0 0 rgba(240, 240, 240, 0.5);
|
||||
border-radius: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
/* 定义滚动条轨道 内阴影+圆角 */
|
||||
::-webkit-scrollbar-track {
|
||||
box-shadow: inset 0 0 0 rgba(240, 240, 240, 0.5);
|
||||
border-radius: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 定义滑块 内阴影+圆角 */
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
box-shadow: inset 0 0 0 rgba(240, 240, 240, 0.5);
|
||||
background-color: rgba(240, 240, 240, 0.5);
|
||||
}
|
||||
/* 定义滑块 内阴影+圆角 */
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
box-shadow: inset 0 0 0 rgba(240, 240, 240, 0.5);
|
||||
background-color: rgba(240, 240, 240, 0.5);
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="订单号" prop="orderNumber">
|
||||
<el-input v-model="formData.orderNumber" placeholder="请输入订单号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户id" prop="userId">
|
||||
<el-input v-model="formData.userId" placeholder="请输入用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品id" prop="productId">
|
||||
<el-input v-model="formData.productId" placeholder="请输入商品" />
|
||||
</el-form-item>
|
||||
<el-form-item label="兑换积分" prop="integral">
|
||||
<el-input v-model="formData.integral" placeholder="请输入兑换积分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="订单状态" prop="orderStatus">
|
||||
<el-radio-group v-model="formData.orderStatus">
|
||||
<el-radio label="1">请选择字典生成</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="下单时间" prop="orderTime">
|
||||
<el-date-picker
|
||||
v-model="formData.orderTime"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择下单时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { PointOrderApi, PointOrderVO } from '@/api/mall/promotion/pointorder'
|
||||
|
||||
/** 兑换记录 表单 */
|
||||
defineOptions({ name: 'PointOrderForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
orderNumber: undefined,
|
||||
userId: undefined,
|
||||
productId: undefined,
|
||||
integral: undefined,
|
||||
orderStatus: undefined,
|
||||
orderTime: undefined,
|
||||
})
|
||||
const formRules = reactive({
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await PointOrderApi.getPointOrder(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as PointOrderVO
|
||||
if (formType.value === 'create') {
|
||||
await PointOrderApi.createPointOrder(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await PointOrderApi.updatePointOrder(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
orderNumber: undefined,
|
||||
userId: undefined,
|
||||
productId: undefined,
|
||||
integral: undefined,
|
||||
orderStatus: undefined,
|
||||
orderTime: undefined,
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
220
yudao-admin-vue3/src/views/mall/promotion/pointorder/index.vue
Normal file
220
yudao-admin-vue3/src/views/mall/promotion/pointorder/index.vue
Normal file
@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||
<el-form-item label="订单号" prop="orderNumber">
|
||||
<el-input v-model="queryParams.orderNumber" placeholder="请输入订单号" clearable @keyup.enter="handleQuery"
|
||||
class="!w-240px" />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="用户" prop="userId">
|
||||
<el-input
|
||||
v-model="queryParams.userId"
|
||||
placeholder="请输入用户"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品id" prop="productId">
|
||||
<el-input
|
||||
v-model="queryParams.productId"
|
||||
placeholder="请输入商品id"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="兑换积分" prop="integral">
|
||||
<el-input v-model="queryParams.integral" placeholder="请输入兑换积分" clearable @keyup.enter="handleQuery"
|
||||
class="!w-240px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="订单状态" prop="orderStatus">
|
||||
<el-select v-model="queryParams.orderStatus" class="!w-280px" clearable placeholder="全部">
|
||||
<el-option v-for="dict in getIntDictOptions(DICT_TYPE.TRADE_ORDER_STATUS)" :key="dict.value"
|
||||
:label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="下单时间" prop="orderTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.orderTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker v-model="queryParams.createTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
|
||||
start-placeholder="开始日期" end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon icon="ep:search" class="mr-5px" /> 搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon icon="ep:refresh" class="mr-5px" /> 重置
|
||||
</el-button>
|
||||
<!-- <el-button type="primary" plain @click="openForm('create')"
|
||||
v-hasPermi="['promotion:point-order:create']">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button> -->
|
||||
<el-button type="success" plain @click="handleExport" :loading="exportLoading"
|
||||
v-hasPermi="['promotion:point-order:export']">
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<!-- <el-table-column label="ID" align="center" prop="id" /> -->
|
||||
<el-table-column label="订单号" align="center" prop="orderNumber" />
|
||||
<el-table-column label="用户" align="center" prop="userName" />
|
||||
<el-table-column label="商品信息" align="center">
|
||||
<template #default="{ row }">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<img :src="row.imageUrl" alt="Product Image" style="width: 50px; height: 50px; margin-right: 10px;" />
|
||||
<span>{{ row.productName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="兑换积分" align="center" prop="integral" />
|
||||
<!-- <el-table-column label="订单状态" align="center" prop="orderStatus" /> -->
|
||||
<el-table-column label="订单状态" align="center" prop="orderStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.TRADE_ORDER_STATUS" :value="scope.row.orderStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下单时间" align="center" prop="orderTime" :formatter="dateFormatter" width="180px" />
|
||||
<!-- <el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/> -->
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<!-- <el-button link type="primary" @click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['promotion:point-order:update']">
|
||||
编辑
|
||||
</el-button> -->
|
||||
<el-button link type="danger" @click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['promotion:point-order:delete']">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList" />
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<PointOrderForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { PointOrderApi, PointOrderVO } from '@/api/mall/promotion/pointorder'
|
||||
import PointOrderForm from './PointOrderForm.vue'
|
||||
|
||||
/** 兑换记录 列表 */
|
||||
defineOptions({ name: 'PointOrder' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<PointOrderVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
orderNumber: undefined,
|
||||
userId: undefined,
|
||||
productId: undefined,
|
||||
integral: undefined,
|
||||
orderStatus: undefined,
|
||||
orderTime: [],
|
||||
createTime: [],
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await PointOrderApi.getPointOrderPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type : string, id ?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id : number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await PointOrderApi.deletePointOrder(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch { }
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await PointOrderApi.exportPointOrder(queryParams)
|
||||
download.excel(data, '兑换记录.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.SortingField;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.enums.SqlConstants;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
@ -151,7 +152,8 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
|
||||
*/
|
||||
default Boolean insertBatch(Collection<T> entities) {
|
||||
// 特殊:SQL Server 批量插入后,获取 id 会报错,因此通过循环处理
|
||||
if (Objects.equals(SqlConstants.DB_TYPE, DbType.SQL_SERVER)) {
|
||||
DbType dbType = JdbcUtils.getDbType();
|
||||
if (JdbcUtils.isSQLServer(dbType)) {
|
||||
entities.forEach(this::insert);
|
||||
return CollUtil.isNotEmpty(entities);
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
package cn.iocoder.yudao.framework.mybatis.core.util;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.enums.DbTypeEnum;
|
||||
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
@ -43,14 +45,28 @@ public class JdbcUtils {
|
||||
return com.baomidou.mybatisplus.extension.toolkit.JdbcUtils.getDbType(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 JDBC 连接是否为 SQLServer 数据库
|
||||
*
|
||||
* @param dbType DB 类型
|
||||
* @return 是否为 SQLServer 数据库
|
||||
*/
|
||||
public static boolean isSQLServer(DbType dbType) {
|
||||
return ObjectUtils.equalsAny(dbType, DbType.SQL_SERVER, DbType.SQL_SERVER2005);
|
||||
}
|
||||
/**
|
||||
* 通过当前数据库连接获得对应的 DB 类型
|
||||
*
|
||||
* @return DB 类型
|
||||
*/
|
||||
public static DbType getDbType() {
|
||||
DynamicRoutingDataSource dynamicRoutingDataSource = SpringUtils.getBean(DynamicRoutingDataSource.class);
|
||||
DataSource dataSource = dynamicRoutingDataSource.determineDataSource();
|
||||
DataSource dataSource;
|
||||
try {
|
||||
DynamicRoutingDataSource dynamicRoutingDataSource = SpringUtils.getBean(DynamicRoutingDataSource.class);
|
||||
dataSource = dynamicRoutingDataSource.determineDataSource();
|
||||
} catch (NoSuchBeanDefinitionException e) {
|
||||
dataSource = SpringUtils.getBean(DataSource.class);
|
||||
}
|
||||
try (Connection conn = dataSource.getConnection()) {
|
||||
return DbTypeEnum.find(conn.getMetaData().getDatabaseProductName());
|
||||
} catch (SQLException e) {
|
||||
|
@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.product.api.spu.dto;
|
||||
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// TODO @LeeYan9: ProductSpuRespDTO
|
||||
/**
|
||||
* 商品 SPU 信息 Response DTO
|
||||
@ -69,6 +71,13 @@ public class ProductSpuRespDTO {
|
||||
|
||||
// ========== 物流相关字段 =========
|
||||
|
||||
/**
|
||||
* 配送方式数组
|
||||
*
|
||||
* 对应 DeliveryTypeEnum 枚举
|
||||
*/
|
||||
private List<Integer> deliveryTypes;
|
||||
|
||||
/**
|
||||
* 物流配置模板编号
|
||||
*
|
||||
|
@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 优惠劵 API 接口
|
||||
@ -20,6 +21,15 @@ public interface CouponApi {
|
||||
*/
|
||||
void useCoupon(@Valid CouponUseReqDTO useReqDTO);
|
||||
|
||||
/**
|
||||
* 获得用户的优惠劵列表
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param status 优惠劵状态
|
||||
* @return 优惠劵列表
|
||||
*/
|
||||
List<CouponRespDTO> getCouponListByUserId(Long userId, Integer status);
|
||||
|
||||
/**
|
||||
* 退还已使用的优惠券
|
||||
*
|
||||
|
@ -27,4 +27,6 @@ public interface RewardActivityApi {
|
||||
*/
|
||||
RewardActivityDTO getRewardActivityById(Long id);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,14 @@
|
||||
package cn.iocoder.yudao.module.promotion.api.reward.dto;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 满减送活动的匹配 Response DTO
|
||||
@ -13,6 +18,12 @@ import java.util.List;
|
||||
@Data
|
||||
public class RewardActivityMatchRespDTO {
|
||||
|
||||
/**
|
||||
* 匹配的 SPU 数组
|
||||
*/
|
||||
private List<Long> spuIds;
|
||||
|
||||
|
||||
/**
|
||||
* 活动编号,主键自增
|
||||
*/
|
||||
@ -21,28 +32,50 @@ public class RewardActivityMatchRespDTO {
|
||||
* 活动标题
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* 枚举 {@link CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
private LocalDateTime startTime;
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
private LocalDateTime endTime;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
/**
|
||||
* 条件类型
|
||||
*
|
||||
* 枚举 {@link PromotionConditionTypeEnum}
|
||||
*/
|
||||
private Integer conditionType;
|
||||
/**
|
||||
* 商品范围
|
||||
*
|
||||
* 枚举 {@link PromotionProductScopeEnum}
|
||||
*/
|
||||
private Integer productScope;
|
||||
/**
|
||||
* 商品 SPU 编号的数组
|
||||
*/
|
||||
private List<Long> productScopeValues;
|
||||
/**
|
||||
* 优惠规则的数组
|
||||
*/
|
||||
private List<Rule> rules;
|
||||
|
||||
/**
|
||||
* 商品 SPU 编号的数组
|
||||
*/
|
||||
private List<Long> spuIds;
|
||||
|
||||
// TODO 芋艿:后面 RewardActivityRespDTO 有了之后,Rule 可以放过去
|
||||
/**
|
||||
* 优惠规则
|
||||
*/
|
||||
@Data
|
||||
public static class Rule {
|
||||
public static class Rule implements Serializable {
|
||||
|
||||
/**
|
||||
* 优惠门槛
|
||||
@ -64,13 +97,21 @@ public class RewardActivityMatchRespDTO {
|
||||
*/
|
||||
private Integer point;
|
||||
/**
|
||||
* 赠送的优惠劵编号的数组
|
||||
* 赠送的优惠劵
|
||||
*
|
||||
* key: 优惠劵模版编号
|
||||
* value:对应的优惠券数量
|
||||
*
|
||||
* 目的:用于订单支付后赠送优惠券
|
||||
*/
|
||||
private List<Long> couponIds;
|
||||
private Map<Long, Integer> giveCouponTemplateCounts;
|
||||
|
||||
/**
|
||||
* 赠送的优惠券数量的数组
|
||||
* 规则描述
|
||||
*
|
||||
* 通过 {@link #limit}、{@link #discountPrice} 等字段进行拼接
|
||||
*/
|
||||
private List<Integer> couponCounts;
|
||||
private String description;
|
||||
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,9 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode DISCOUNT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_013_001_003, "限时折扣活动未关闭,不能删除");
|
||||
ErrorCode DISCOUNT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_001_004, "限时折扣活动已关闭,不能重复关闭");
|
||||
|
||||
// ========== 兑换记录 TODO 补充编号 ==========
|
||||
ErrorCode POINT_ORDER_NOT_EXISTS = new ErrorCode(11111, "兑换记录不存在");
|
||||
|
||||
// ========== Banner 相关 1-013-002-000 ============
|
||||
ErrorCode BANNER_NOT_EXISTS = new ErrorCode(1_013_002_000, "Banner 不存在");
|
||||
|
||||
|
@ -5,6 +5,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 营销的商品范围枚举
|
||||
@ -15,10 +16,9 @@ import java.util.Arrays;
|
||||
@AllArgsConstructor
|
||||
public enum PromotionProductScopeEnum implements IntArrayValuable {
|
||||
|
||||
ALL(1, "通用券"), // 全部商品
|
||||
SPU(2, "商品券"), // 指定商品
|
||||
CATEGORY(3, "品类券"), // 指定品类
|
||||
;
|
||||
ALL(1, "全部商品"),
|
||||
SPU(2, "指定商品"),
|
||||
CATEGORY(3, "指定品类");
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PromotionProductScopeEnum::getScope).toArray();
|
||||
|
||||
@ -36,4 +36,17 @@ public enum PromotionProductScopeEnum implements IntArrayValuable {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
public static boolean isAll(Integer scope) {
|
||||
return Objects.equals(scope, ALL.scope);
|
||||
}
|
||||
|
||||
public static boolean isSpu(Integer scope) {
|
||||
return Objects.equals(scope, SPU.scope);
|
||||
}
|
||||
|
||||
public static boolean isCategory(Integer scope) {
|
||||
return Objects.equals(scope, CATEGORY.scope);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cn.iocoder.yudao.module.promotion.api.coupon;
|
||||
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
|
||||
@ -11,6 +12,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 优惠劵 API 实现类
|
||||
@ -24,6 +26,11 @@ public class CouponApiImpl implements CouponApi {
|
||||
@Resource
|
||||
private CouponService couponService;
|
||||
|
||||
@Override
|
||||
public List<CouponRespDTO> getCouponListByUserId(Long userId, Integer status) {
|
||||
return BeanUtils.toBean(couponService.getCouponList(userId, status), CouponRespDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void useCoupon(CouponUseReqDTO useReqDTO) {
|
||||
couponService.useCoupon(useReqDTO.getId(), useReqDTO.getUserId(),
|
||||
|
@ -1,7 +1,9 @@
|
||||
package cn.iocoder.yudao.module.promotion.api.discount;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.convert.discount.DiscountActivityConvert;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
|
||||
import cn.iocoder.yudao.module.promotion.service.discount.DiscountActivityService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@ -24,7 +26,7 @@ public class DiscountActivityApiImpl implements DiscountActivityApi {
|
||||
|
||||
@Override
|
||||
public List<DiscountProductRespDTO> getMatchDiscountProductList(Collection<Long> skuIds) {
|
||||
return DiscountActivityConvert.INSTANCE.convertList02(discountActivityService.getMatchDiscountProductList(skuIds));
|
||||
}
|
||||
List<DiscountProductDO> list = discountActivityService.getMatchDiscountProductList(skuIds);
|
||||
return BeanUtils.toBean(list, DiscountProductRespDTO.class); }
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,95 @@
|
||||
package cn.iocoder.yudao.module.promotion.controller.admin.pointorder;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
|
||||
import javax.validation.*;
|
||||
import javax.servlet.http.*;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
|
||||
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.pointorder.vo.*;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.pointorder.PointOrderDO;
|
||||
import cn.iocoder.yudao.module.promotion.service.pointorder.PointOrderService;
|
||||
|
||||
@Tag(name = "管理后台 - 兑换记录")
|
||||
@RestController
|
||||
@RequestMapping("/promotion/point-order")
|
||||
@Validated
|
||||
public class PointOrderController {
|
||||
|
||||
@Resource
|
||||
private PointOrderService pointOrderService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建兑换记录")
|
||||
@PreAuthorize("@ss.hasPermission('promotion:point-order:create')")
|
||||
public CommonResult<Long> createPointOrder(@Valid @RequestBody PointOrderSaveReqVO createReqVO) {
|
||||
return success(pointOrderService.createPointOrder(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新兑换记录")
|
||||
@PreAuthorize("@ss.hasPermission('promotion:point-order:update')")
|
||||
public CommonResult<Boolean> updatePointOrder(@Valid @RequestBody PointOrderSaveReqVO updateReqVO) {
|
||||
pointOrderService.updatePointOrder(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除兑换记录")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('promotion:point-order:delete')")
|
||||
public CommonResult<Boolean> deletePointOrder(@RequestParam("id") Long id) {
|
||||
pointOrderService.deletePointOrder(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得兑换记录")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('promotion:point-order:query')")
|
||||
public CommonResult<PointOrderRespVO> getPointOrder(@RequestParam("id") Long id) {
|
||||
PointOrderDO pointOrder = pointOrderService.getPointOrder(id);
|
||||
return success(BeanUtils.toBean(pointOrder, PointOrderRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得兑换记录分页")
|
||||
@PreAuthorize("@ss.hasPermission('promotion:point-order:query')")
|
||||
public CommonResult<PageResult<PointOrderRespVO>> getPointOrderPage(@Valid PointOrderPageReqVO pageReqVO) {
|
||||
PageResult<PointOrderDO> pageResult = pointOrderService.getPointOrderPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, PointOrderRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出兑换记录 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('promotion:point-order:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportPointOrderExcel(@Valid PointOrderPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<PointOrderDO> list = pointOrderService.getPointOrderPage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "兑换记录.xls", "数据", PointOrderRespVO.class,
|
||||
BeanUtils.toBean(list, PointOrderRespVO.class));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package cn.iocoder.yudao.module.promotion.controller.admin.pointorder.vo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 兑换记录分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class PointOrderPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "订单号")
|
||||
private String orderNumber;
|
||||
|
||||
@Schema(description = "用户id", example = "13639")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "商品id", example = "2315")
|
||||
private Long productId;
|
||||
|
||||
@Schema(description = "兑换积分")
|
||||
private Integer integral;
|
||||
|
||||
@Schema(description = "订单状态", example = "2")
|
||||
private Integer orderStatus;
|
||||
|
||||
@Schema(description = "下单时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] orderTime;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package cn.iocoder.yudao.module.promotion.controller.admin.pointorder.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import com.alibaba.excel.annotation.*;
|
||||
|
||||
@Schema(description = "管理后台 - 兑换记录 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class PointOrderRespVO {
|
||||
|
||||
@Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED, example = "19849")
|
||||
@ExcelProperty("id")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "订单号")
|
||||
@ExcelProperty("订单号")
|
||||
private String orderNumber;
|
||||
|
||||
@Schema(description = "用户id", example = "13639")
|
||||
@ExcelProperty("用户id")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "商品id", example = "2315")
|
||||
@ExcelProperty("商品id")
|
||||
private Long productId;
|
||||
|
||||
@Schema(description = "兑换积分")
|
||||
@ExcelProperty("兑换积分")
|
||||
private Integer integral;
|
||||
|
||||
@Schema(description = "订单状态", example = "2")
|
||||
@ExcelProperty("订单状态")
|
||||
private Integer orderStatus;
|
||||
|
||||
@Schema(description = "下单时间")
|
||||
@ExcelProperty("下单时间")
|
||||
private LocalDateTime orderTime;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
private String userName;
|
||||
|
||||
private String productName;
|
||||
|
||||
private String imageUrl;
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package cn.iocoder.yudao.module.promotion.controller.admin.pointorder.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import javax.validation.constraints.*;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 兑换记录新增/修改 Request VO")
|
||||
@Data
|
||||
public class PointOrderSaveReqVO {
|
||||
|
||||
@Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED, example = "19849")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "订单号")
|
||||
private String orderNumber;
|
||||
|
||||
@Schema(description = "用户id", example = "13639")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "商品id", example = "2315")
|
||||
private Long productId;
|
||||
|
||||
@Schema(description = "兑换积分")
|
||||
private Integer integral;
|
||||
|
||||
@Schema(description = "订单状态", example = "2")
|
||||
private Integer orderStatus;
|
||||
|
||||
@Schema(description = "下单时间")
|
||||
private LocalDateTime orderTime;
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package cn.iocoder.yudao.module.promotion.dal.dataobject.pointorder;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDateTime;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
|
||||
/**
|
||||
* 兑换记录 DO
|
||||
*
|
||||
* @author 管理员
|
||||
*/
|
||||
@TableName("promotion_point_order")
|
||||
@KeySequence("promotion_point_order_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PointOrderDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
private String orderNumber;
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 商品id
|
||||
*/
|
||||
private Long productId;
|
||||
/**
|
||||
* 兑换积分
|
||||
*/
|
||||
private Integer integral;
|
||||
/**
|
||||
* 订单状态
|
||||
*/
|
||||
private Integer orderStatus;
|
||||
/**
|
||||
* 下单时间
|
||||
*/
|
||||
private LocalDateTime orderTime;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 商品名称
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String productName;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String imageUrl;
|
||||
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
package cn.iocoder.yudao.module.promotion.dal.mysql.discount;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.discount.DiscountProductDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -22,6 +24,15 @@ public interface DiscountProductMapper extends BaseMapperX<DiscountProductDO> {
|
||||
return selectList(DiscountProductDO::getSkuId, skuIds);
|
||||
}
|
||||
|
||||
default List<DiscountProductDO> selectListBySkuIdsAndStatusAndNow(Collection<Long> skuIds, Integer status) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
return selectList(new LambdaQueryWrapperX<DiscountProductDO>()
|
||||
.in(DiscountProductDO::getSkuId, skuIds)
|
||||
.eq(DiscountProductDO::getActivityStatus,status)
|
||||
.lt(DiscountProductDO::getActivityStartTime, now)
|
||||
.gt(DiscountProductDO::getActivityEndTime, now));
|
||||
}
|
||||
|
||||
default List<DiscountProductDO> selectListByActivityId(Long activityId) {
|
||||
return selectList(DiscountProductDO::getActivityId, activityId);
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
package cn.iocoder.yudao.module.promotion.dal.mysql.pointorder;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.pointorder.PointOrderDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.pointorder.vo.*;
|
||||
|
||||
/**
|
||||
* 兑换记录 Mapper
|
||||
*
|
||||
* @author 管理员
|
||||
*/
|
||||
@Mapper
|
||||
public interface PointOrderMapper extends BaseMapperX<PointOrderDO> {
|
||||
|
||||
default PageResult<PointOrderDO> selectPage(PointOrderPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<PointOrderDO>()
|
||||
.eqIfPresent(PointOrderDO::getOrderNumber, reqVO.getOrderNumber())
|
||||
.eqIfPresent(PointOrderDO::getUserId, reqVO.getUserId())
|
||||
.eqIfPresent(PointOrderDO::getProductId, reqVO.getProductId())
|
||||
.eqIfPresent(PointOrderDO::getIntegral, reqVO.getIntegral())
|
||||
.eqIfPresent(PointOrderDO::getOrderStatus, reqVO.getOrderStatus())
|
||||
.betweenIfPresent(PointOrderDO::getOrderTime, reqVO.getOrderTime())
|
||||
.betweenIfPresent(PointOrderDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(PointOrderDO::getId));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package cn.iocoder.yudao.module.promotion.service.pointorder;
|
||||
|
||||
import java.util.*;
|
||||
import javax.validation.*;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.pointorder.vo.*;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.pointorder.PointOrderDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
|
||||
/**
|
||||
* 兑换记录 Service 接口
|
||||
*
|
||||
* @author 管理员
|
||||
*/
|
||||
public interface PointOrderService {
|
||||
|
||||
/**
|
||||
* 创建兑换记录
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createPointOrder(@Valid PointOrderSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新兑换记录
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updatePointOrder(@Valid PointOrderSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除兑换记录
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deletePointOrder(Long id);
|
||||
|
||||
/**
|
||||
* 获得兑换记录
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 兑换记录
|
||||
*/
|
||||
PointOrderDO getPointOrder(Long id);
|
||||
|
||||
/**
|
||||
* 获得兑换记录分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 兑换记录分页
|
||||
*/
|
||||
PageResult<PointOrderDO> getPointOrderPage(PointOrderPageReqVO pageReqVO);
|
||||
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package cn.iocoder.yudao.module.promotion.service.pointorder;
|
||||
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
|
||||
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
|
||||
import org.springframework.stereotype.Service;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import cn.iocoder.yudao.module.promotion.controller.admin.pointorder.vo.*;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.pointorder.PointOrderDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
|
||||
import cn.iocoder.yudao.module.promotion.dal.mysql.pointorder.PointOrderMapper;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 兑换记录 Service 实现类
|
||||
*
|
||||
* @author 管理员
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class PointOrderServiceImpl implements PointOrderService {
|
||||
|
||||
@Resource
|
||||
private PointOrderMapper pointOrderMapper;
|
||||
|
||||
@Resource
|
||||
private MemberUserApi memberUserApi;
|
||||
|
||||
@Resource
|
||||
private ProductSpuApi productSpuApi;
|
||||
|
||||
@Override
|
||||
public Long createPointOrder(PointOrderSaveReqVO createReqVO) {
|
||||
// 插入
|
||||
PointOrderDO pointOrder = BeanUtils.toBean(createReqVO, PointOrderDO.class);
|
||||
pointOrderMapper.insert(pointOrder);
|
||||
// 返回
|
||||
return pointOrder.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePointOrder(PointOrderSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validatePointOrderExists(updateReqVO.getId());
|
||||
// 更新
|
||||
PointOrderDO updateObj = BeanUtils.toBean(updateReqVO, PointOrderDO.class);
|
||||
pointOrderMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePointOrder(Long id) {
|
||||
// 校验存在
|
||||
validatePointOrderExists(id);
|
||||
// 删除
|
||||
pointOrderMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private void validatePointOrderExists(Long id) {
|
||||
if (pointOrderMapper.selectById(id) == null) {
|
||||
throw exception(POINT_ORDER_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointOrderDO getPointOrder(Long id) {
|
||||
return pointOrderMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PointOrderDO> getPointOrderPage(PointOrderPageReqVO pageReqVO) {
|
||||
PageResult<PointOrderDO> pointOrderDOPageResult = pointOrderMapper.selectPage(pageReqVO);
|
||||
List<PointOrderDO> list = pointOrderDOPageResult.getList();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
PointOrderDO pointOrderDO = list.get(i);
|
||||
|
||||
//设置用户名称
|
||||
MemberUserRespDTO user = memberUserApi.getUser(pointOrderDO.getUserId());
|
||||
pointOrderDO.setUserName(user.getNickname());
|
||||
|
||||
//获取商品信息
|
||||
ProductSpuRespDTO spu = productSpuApi.getSpu(pointOrderDO.getProductId());
|
||||
pointOrderDO.setProductName(spu.getName());
|
||||
pointOrderDO.setImageUrl(spu.getPicUrl());
|
||||
|
||||
|
||||
}
|
||||
return pointOrderDOPageResult;
|
||||
}
|
||||
|
||||
}
|
@ -35,6 +35,7 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP = new ErrorCode(1_011_000_030, "交易订单自提失败,收货方式不是【用户自提】");
|
||||
ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单不是【待发货】状态");
|
||||
ErrorCode ORDER_CREATE_FAIL_EXIST_UNPAID = new ErrorCode(1_011_000_032, "交易订单创建失败,原因:存在未付款订单");
|
||||
ErrorCode ORDER_CANCEL_PAID_FAIL = new ErrorCode(1_011_000_033, "交易订单取消支付失败,原因:订单不是【{}】状态");
|
||||
|
||||
// ========== After Sale 模块 1-011-000-100 ==========
|
||||
ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
|
||||
@ -53,12 +54,15 @@ public interface ErrorCodeConstants {
|
||||
|
||||
// ========== Cart 模块 1-011-002-000 ==========
|
||||
ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1_011_002_000, "购物车项不存在");
|
||||
ErrorCode PRICE_CALCULATE_POINT_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_004, "参与积分活动的商品,超过了积分活动商品总限购数量");
|
||||
|
||||
// ========== Price 相关 1-011-003-000 ============
|
||||
ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1_011_003_000, "支付价格计算异常,原因:价格小于等于 0");
|
||||
ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1_011_003_002, "计算快递运费异常,找不到对应的运费模板");
|
||||
ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1_011_003_004, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵");
|
||||
ErrorCode PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_005, "参与秒杀的商品,超过了秒杀总限购数量");
|
||||
ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL = new ErrorCode(1_011_003_005, "计算快递运费异常,配送方式不匹配");
|
||||
ErrorCode PRICE_CALCULATE_COUPON_CAN_NOT_USE = new ErrorCode(1_011_003_006, "该优惠劵无法使用,原因:{}」");
|
||||
|
||||
// ========== 物流 Express 模块 1-011-004-000 ==========
|
||||
ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在");
|
||||
|
@ -12,5 +12,7 @@ public interface MessageTemplateConstants {
|
||||
|
||||
String BROKERAGE_WITHDRAW_AUDIT_APPROVE = "brokerage_withdraw_audit_approve"; // 佣金提现(审核通过)
|
||||
String BROKERAGE_WITHDRAW_AUDIT_REJECT = "brokerage_withdraw_audit_reject"; // 佣金提现(审核不通过)
|
||||
// ======================= 小程序订阅消息模版 =======================
|
||||
|
||||
String WXA_ORDER_DELIVERY = "订单发货通知";
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ public enum TradeOrderCancelTypeEnum implements IntArrayValuable {
|
||||
|
||||
PAY_TIMEOUT(10, "超时未支付"),
|
||||
AFTER_SALE_CLOSE(20, "退款关闭"),
|
||||
MEMBER_CANCEL(30, "买家取消");
|
||||
|
||||
MEMBER_CANCEL(30, "买家取消"),
|
||||
COMBINATION_CLOSE(40, "拼团关闭");
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TradeOrderCancelTypeEnum::getType).toArray();
|
||||
|
||||
/**
|
||||
|
@ -15,11 +15,11 @@ import java.util.Arrays;
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum TradeOrderTypeEnum implements IntArrayValuable {
|
||||
|
||||
NORMAL(0, "普通订单"),
|
||||
SECKILL(1, "秒杀订单"),
|
||||
BARGAIN(2, "砍价订单"),
|
||||
COMBINATION(3, "拼团订单"),
|
||||
POINT(4, "积分商城"),
|
||||
;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TradeOrderTypeEnum::getType).toArray();
|
||||
@ -54,4 +54,8 @@ public enum TradeOrderTypeEnum implements IntArrayValuable {
|
||||
return ObjectUtil.equal(type, COMBINATION.getType());
|
||||
}
|
||||
|
||||
public static boolean isPoint(Integer type) {
|
||||
return ObjectUtil.equal(type, POINT.getType());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -92,6 +92,11 @@
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-excel</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-promotion-biz</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -1,33 +0,0 @@
|
||||
### 获得交易售后分页 => 成功
|
||||
GET {{baseUrl}}/trade/after-sale/page?pageNo=1&pageSize=10
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenentId}}
|
||||
|
||||
### 同意售后 => 成功
|
||||
PUT {{baseUrl}}/trade/after-sale/agree?id=7
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenentId}}
|
||||
Content-Type: application/json
|
||||
|
||||
### 拒绝售后 => 成功
|
||||
PUT {{baseUrl}}/trade/after-sale/disagree
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenentId}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 6,
|
||||
"auditReason": "阿巴巴"
|
||||
}
|
||||
|
||||
### 确认退款 => 成功
|
||||
PUT {{baseUrl}}/trade/after-sale/refund?id=6
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenentId}}
|
||||
Content-Type: application/json
|
||||
|
||||
### 确认收货 => 成功
|
||||
PUT {{baseUrl}}/trade/after-sale/receive?id=7
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenentId}}
|
||||
Content-Type: application/json
|
@ -1,8 +1,6 @@
|
||||
package cn.iocoder.yudao.module.trade.controller.admin.order;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
@ -12,13 +10,9 @@ import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderLogDO;
|
||||
import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
|
||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderLogService;
|
||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
|
||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@ -28,11 +22,10 @@ import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
@ -121,94 +114,6 @@ public class TradeOrderController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/test")
|
||||
public CommonResult<String> test(@RequestBody Map<String, String> map){
|
||||
System.out.println("==========================================================");
|
||||
for (String s : map.keySet()) {
|
||||
System.out.println(map.get(s));
|
||||
}
|
||||
System.out.println("==========================================================");
|
||||
return success("成功");
|
||||
}
|
||||
@PostMapping("/test2")
|
||||
public void test2(){
|
||||
//获取access_token
|
||||
String accessTokenApiurl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wx63c280fe3248a3e7&secret=6f270509224a7ae1296bbf1c8cb97aed";
|
||||
|
||||
// JSONObject map = new JSONObject();
|
||||
// map.put("grant_type","client_credential");
|
||||
// map.put("appid","wx63c280fe3248a3e7");
|
||||
// map.put("secret","6f270509224a7ae1296bbf1c8cb97aed");
|
||||
HttpResponse get = HttpUtil.createGet(accessTokenApiurl).execute();
|
||||
String body = get.body();
|
||||
System.out.println("-----------------------------------------------");
|
||||
// String jsonString = "82_fCctY_2YxIDOQMpk315ZH76M0J49nXlx_aEdbrDfJEF_-E640cLl0_Yu3kAh4eHgAgtTpu2SskXMNFCsraCBvlGlEOKsXtpqEbkrh5Ydi2arCv-MoHWiS45UyzEMDYfAJAYPK";
|
||||
JSONObject jsonObject = JSON.parseObject(body);
|
||||
System.out.println(jsonObject);
|
||||
System.out.println(body);
|
||||
String token = jsonObject.getString("access_token");
|
||||
System.out.println(token);
|
||||
System.out.println("-----------------------------------------------");
|
||||
|
||||
//查询小程序是否已开通发货信息管理服务
|
||||
String apiurlSec = "https://api.weixin.qq.com/wxa/sec/order/is_trade_managed?access_token="+token;
|
||||
JSONObject mapSec = new JSONObject();
|
||||
mapSec.put("appid","wx63c280fe3248a3e7");
|
||||
HttpResponse postSec = HttpUtil.createPost(apiurlSec)
|
||||
// .header("Authorization","Bearer 8e79d003102a4b5c80fd823c3c04347c")
|
||||
// .header("tenant-id","1")
|
||||
.body(mapSec.toJSONString())
|
||||
.execute();
|
||||
String bodySec = postSec.body();
|
||||
System.out.println("-----------------------------------------------6");
|
||||
System.out.println(bodySec);
|
||||
System.out.println("-----------------------------------------------6");
|
||||
|
||||
|
||||
// 微信官方发货信息录入接口
|
||||
// String apiurl = "https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?access_token="+token;
|
||||
//
|
||||
// JSONObject map = new JSONObject();
|
||||
// map.put("order_id","P202407170517541");
|
||||
//
|
||||
// JSONObject orderKey = new JSONObject();
|
||||
// orderKey.put("order_number_type",1);
|
||||
//
|
||||
// map.put("order_key",orderKey);
|
||||
// map.put("logistics_type",3);
|
||||
// map.put("delivery_mode",1);
|
||||
//
|
||||
// JSONArray shippingList = new JSONArray();
|
||||
// JSONObject object = new JSONObject();
|
||||
// object.put("item_desc","测试商品666");
|
||||
// shippingList.add(object);
|
||||
//
|
||||
// map.put("shipping_list",shippingList);
|
||||
// // 获取当前时间的 OffsetDateTime 对象
|
||||
// OffsetDateTime currentDateTime = OffsetDateTime.now();
|
||||
//
|
||||
// // 定义日期时间格式化器,用于格式化成特定格式
|
||||
// DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
|
||||
//
|
||||
// // 格式化 OffsetDateTime 对象为指定格式的字符串
|
||||
// String formattedDateTime = currentDateTime.format(formatter);
|
||||
// map.put("upload_time",formattedDateTime);
|
||||
// JSONObject payer = new JSONObject();
|
||||
// payer.put("openid","oWKZ063Eb6IGr63vJT9Zgf3jufOY");
|
||||
// map.put("payer",payer);
|
||||
//
|
||||
// HttpResponse post = HttpUtil.createPost(apiurl)
|
||||
//// .header("Authorization","Bearer 8e79d003102a4b5c80fd823c3c04347c")
|
||||
//// .header("tenant-id","1")
|
||||
// .body(map.toJSONString())
|
||||
// .execute();
|
||||
// String Pbody = post.body();
|
||||
// System.out.println("-----------------------------------------------");
|
||||
// System.out.println(Pbody);
|
||||
// System.out.println(apiurl);
|
||||
// System.out.println("-----------------------------------------------");
|
||||
}
|
||||
|
||||
@PutMapping("/update-remark")
|
||||
@Operation(summary = "订单备注")
|
||||
@PreAuthorize("@ss.hasPermission('trade:order:update')")
|
||||
|
@ -1,42 +0,0 @@
|
||||
### 请求 /trade/cart/add 接口 => 成功
|
||||
POST {{appApi}}/trade/cart/add
|
||||
tenant-id: {{appTenentId}}
|
||||
Authorization: Bearer {{appToken}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"skuId": 1,
|
||||
"count": 10,
|
||||
"addStatus": true
|
||||
}
|
||||
|
||||
### 请求 /trade/cart/update 接口 => 成功
|
||||
PUT {{appApi}}/trade/cart/update
|
||||
tenant-id: {{appTenentId}}
|
||||
Authorization: Bearer {{appToken}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 35,
|
||||
"count": 5
|
||||
}
|
||||
|
||||
### 请求 /trade/cart/delete 接口 => 成功
|
||||
DELETE {{appApi}}/trade/cart/delete?ids=1
|
||||
tenant-id: {{appTenentId}}
|
||||
Authorization: Bearer {{appToken}}
|
||||
|
||||
### 请求 /trade/cart/get-count 接口 => 成功
|
||||
GET {{appApi}}/trade/cart/get-count
|
||||
tenant-id: {{appTenentId}}
|
||||
Authorization: Bearer {{appToken}}
|
||||
|
||||
### 请求 /trade/cart/get-count-map 接口 => 成功
|
||||
GET {{appApi}}/trade/cart/get-count-map
|
||||
tenant-id: {{appTenentId}}
|
||||
Authorization: Bearer {{appToken}}
|
||||
|
||||
### 请求 /trade/cart/list 接口 => 成功
|
||||
GET {{appApi}}/trade/cart/list
|
||||
tenant-id: {{appTenentId}}
|
||||
Authorization: Bearer {{appToken}}
|
@ -62,3 +62,8 @@ tenant-id: {{appTenentId}}
|
||||
GET {{appApi}}/trade/order/get-express-track-list?id=70
|
||||
Authorization: Bearer {{appToken}}
|
||||
tenant-id: {{appTenentId}}
|
||||
|
||||
### /trade-order/settlement-product 获得商品结算信息
|
||||
GET {{appApi}}/trade/order/settlement-product?spuIds=633
|
||||
Authorization: Bearer {{appToken}}
|
||||
tenant-id: {{appTenentId}}
|
@ -1,12 +1,7 @@
|
||||
package cn.iocoder.yudao.module.trade.controller.app.order;
|
||||
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
|
||||
import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
|
||||
import cn.iocoder.yudao.module.trade.controller.app.order.vo.*;
|
||||
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
|
||||
@ -21,17 +16,18 @@ import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
|
||||
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
|
||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
|
||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import cn.iocoder.yudao.module.trade.service.price.TradePriceService;
|
||||
import com.google.common.collect.Maps;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -53,28 +49,24 @@ public class AppTradeOrderController {
|
||||
private TradeOrderQueryService tradeOrderQueryService;
|
||||
@Resource
|
||||
private DeliveryExpressService deliveryExpressService;
|
||||
|
||||
@Resource
|
||||
private AfterSaleService afterSaleService;
|
||||
@Resource
|
||||
private TradePriceService priceService;
|
||||
|
||||
@Resource
|
||||
private TradeOrderProperties tradeOrderProperties;
|
||||
@Resource
|
||||
private MemberUserApi memberUserApi;
|
||||
|
||||
@GetMapping("/settlement")
|
||||
@Operation(summary = "获得订单结算信息")
|
||||
@PreAuthenticated
|
||||
public CommonResult<AppTradeOrderSettlementRespVO> settlementOrder(@Valid AppTradeOrderSettlementReqVO settlementReqVO) {
|
||||
AppTradeOrderSettlementRespVO order = tradeOrderUpdateService.settlementOrder(getLoginUserId(), settlementReqVO);
|
||||
order.setTotalPoint(memberUserApi.getUser(getLoginUserId()).getPoint());
|
||||
order.setUsedPoint(0);
|
||||
return success(order);
|
||||
return success(tradeOrderUpdateService.settlementOrder(getLoginUserId(), settlementReqVO));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建订单")
|
||||
@PreAuthenticated
|
||||
public CommonResult<AppTradeOrderCreateRespVO> createOrder(@Valid @RequestBody AppTradeOrderCreateReqVO createReqVO) {
|
||||
TradeOrderDO order = tradeOrderUpdateService.createOrder(getLoginUserId(), createReqVO);
|
||||
return success(new AppTradeOrderCreateRespVO().setId(order.getId()).setPayOrderId(order.getPayOrderId()));
|
||||
@ -82,6 +74,7 @@ public class AppTradeOrderController {
|
||||
|
||||
@PostMapping("/update-paid")
|
||||
@Operation(summary = "更新订单为已支付") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
|
||||
@PermitAll
|
||||
public CommonResult<Boolean> updateOrderPaid(@RequestBody PayOrderNotifyReqDTO notifyReqDTO) {
|
||||
tradeOrderUpdateService.updateOrderPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
|
||||
notifyReqDTO.getPayOrderId());
|
||||
@ -90,20 +83,31 @@ public class AppTradeOrderController {
|
||||
|
||||
@GetMapping("/get-detail")
|
||||
@Operation(summary = "获得交易订单")
|
||||
@Parameter(name = "id", description = "交易订单编号")
|
||||
public CommonResult<AppTradeOrderDetailRespVO> getOrder(@RequestParam("id") Long id) {
|
||||
// 查询订单
|
||||
@Parameters({
|
||||
@Parameter(name = "id", description = "交易订单编号"),
|
||||
@Parameter(name = "sync", description = "是否同步支付状态", example = "true")
|
||||
})
|
||||
public CommonResult<AppTradeOrderDetailRespVO> getOrderDetail(@RequestParam("id") Long id,
|
||||
@RequestParam(value = "sync", required = false) Boolean sync) {
|
||||
// 1.1 查询订单
|
||||
TradeOrderDO order = tradeOrderQueryService.getOrder(getLoginUserId(), id);
|
||||
if (order == null) {
|
||||
return success(null);
|
||||
}
|
||||
// 1.2 sync 仅在等待支付
|
||||
if (Boolean.TRUE.equals(sync)
|
||||
&& TradeOrderStatusEnum.isUnpaid(order.getStatus()) && !order.getPayStatus()) {
|
||||
tradeOrderUpdateService.syncOrderPayStatusQuietly(order.getId(), order.getPayOrderId());
|
||||
// 重新查询,因为同步后,可能会有变化
|
||||
order = tradeOrderQueryService.getOrder(id);
|
||||
}
|
||||
|
||||
// 查询订单项
|
||||
// 2.1 查询订单项
|
||||
List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(order.getId());
|
||||
// 查询物流公司
|
||||
// 2.2 查询物流公司
|
||||
DeliveryExpressDO express = order.getLogisticsId() != null && order.getLogisticsId() > 0 ?
|
||||
deliveryExpressService.getDeliveryExpress(order.getLogisticsId()) : null;
|
||||
// 最终组合
|
||||
// 2.3 最终组合
|
||||
return success(TradeOrderConvert.INSTANCE.convert02(order, orderItems, tradeOrderProperties, express));
|
||||
}
|
||||
|
||||
@ -158,32 +162,6 @@ public class AppTradeOrderController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// @PostMapping("/test")
|
||||
// public void test(){
|
||||
// //获取access_token
|
||||
// String accessTokenApiurl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wx63c280fe3248a3e7&secret=6f270509224a7ae1296bbf1c8cb97aed";
|
||||
// HttpResponse get = HttpUtil.createGet(accessTokenApiurl).execute();
|
||||
// String body = get.body();
|
||||
// System.out.println("-----------------------------------------------");
|
||||
// JSONObject jsonObject = JSON.parseObject(body);
|
||||
// System.out.println(jsonObject);
|
||||
// System.out.println(body);
|
||||
// String token = jsonObject.getString("access_token");
|
||||
// System.out.println(token);
|
||||
// System.out.println("-----------------------------------------------");
|
||||
//
|
||||
// String apiurlSec = "https://api.weixin.qq.com/wxa/sec/order/is_trade_managed?access_token="+token;
|
||||
// JSONObject mapSec = new JSONObject();
|
||||
// mapSec.put("out_order_id","wx63c280fe3248a3e7");
|
||||
// mapSec.put("openid","wx63c280fe3248a3e7");
|
||||
// HttpResponse postSec = HttpUtil.createPost(apiurlSec)
|
||||
//// .header("Authorization","Bearer 8e79d003102a4b5c80fd823c3c04347c")
|
||||
//// .header("tenant-id","1")
|
||||
// .body(mapSec.toJSONString())
|
||||
// .execute();
|
||||
// String bodySec = postSec.body();
|
||||
// }
|
||||
|
||||
@DeleteMapping("/cancel")
|
||||
@Operation(summary = "取消交易订单")
|
||||
@Parameter(name = "id", description = "交易订单编号")
|
||||
@ -216,4 +194,9 @@ public class AppTradeOrderController {
|
||||
return success(tradeOrderUpdateService.createOrderItemCommentByMember(getLoginUserId(), createReqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/getPointOrder")
|
||||
public CommonResult<List<AppPointOrderVO>> getPointOrder(){
|
||||
return success(tradeOrderQueryService.getPointOrder(getLoginUserId()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class AppPointOrderVO {
|
||||
private String status; //状态
|
||||
private String label;
|
||||
private LocalDateTime createTime; //时间
|
||||
private Integer payPrice; //支付金额
|
||||
private Integer usePoint; //兑换积分
|
||||
private String name; //产品名称
|
||||
private String picUrl; //图片
|
||||
private Integer count; //兑换数量
|
||||
}
|
@ -62,9 +62,9 @@ public class AppTradeOrderSettlementReqVO {
|
||||
@Schema(description = "砍价记录编号", example = "123")
|
||||
private Long bargainRecordId;
|
||||
|
||||
// ========== 优惠活动相关字段 ==========
|
||||
@Schema(description = "满减送活动编号", example = "123")
|
||||
private Long rewardId;
|
||||
// ========== 积分商城活动相关字段 ==========
|
||||
@Schema(description = "积分商城活动编号", example = "123")
|
||||
private Long pointActivityId;
|
||||
|
||||
@AssertTrue(message = "活动商品每次只能购买一种规格")
|
||||
@JsonIgnore
|
||||
|
@ -1,12 +1,13 @@
|
||||
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
|
||||
|
||||
import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "用户 App - 交易订单结算信息 Response VO")
|
||||
@ -19,6 +20,9 @@ public class AppTradeOrderSettlementRespVO {
|
||||
@Schema(description = "购物项数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<Item> items;
|
||||
|
||||
@Schema(description = "优惠劵数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<Coupon> coupons; // 可用 + 不可用
|
||||
|
||||
@Schema(description = "费用", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Price price;
|
||||
|
||||
@ -26,13 +30,17 @@ public class AppTradeOrderSettlementRespVO {
|
||||
private Address address;
|
||||
|
||||
@Schema(description = "已使用的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Integer usedPoint;
|
||||
private Integer usePoint;
|
||||
|
||||
@Schema(description = "总积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Integer totalPoint;
|
||||
|
||||
@Schema(description = "商品使用积分状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "111")
|
||||
private Integer pointsStatus;
|
||||
/**
|
||||
* 营销活动数组
|
||||
*
|
||||
* 只对应 {@link TradePriceCalculateRespBO.Price#} 商品匹配的活动
|
||||
*/
|
||||
private List<TradePriceCalculateRespBO.Promotion> promotions;
|
||||
|
||||
@Schema(description = "购物项")
|
||||
@Data
|
||||
@ -112,7 +120,6 @@ public class AppTradeOrderSettlementRespVO {
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "地区编号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "地区编号不能为空")
|
||||
private Long areaId;
|
||||
@Schema(description = "地区名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "上海上海市普陀区")
|
||||
private String areaName;
|
||||
@ -125,4 +132,43 @@ public class AppTradeOrderSettlementRespVO {
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "优惠劵信息")
|
||||
@Data
|
||||
public static class Coupon {
|
||||
|
||||
@Schema(description = "优惠劵编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "优惠劵名", requiredMode = Schema.RequiredMode.REQUIRED, example = "春节送送送")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "是否设置满多少金额可用", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") // 单位:分;0 - 不限制
|
||||
private Integer usePrice;
|
||||
|
||||
@Schema(description = "固定日期 - 生效开始时间")
|
||||
private LocalDateTime validStartTime;
|
||||
|
||||
@Schema(description = "固定日期 - 生效结束时间")
|
||||
private LocalDateTime validEndTime;
|
||||
|
||||
@Schema(description = "优惠类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer discountType;
|
||||
|
||||
@Schema(description = "折扣百分比", example = "80") // 例如说,80% 为 80
|
||||
private Integer discountPercent;
|
||||
|
||||
@Schema(description = "优惠金额", example = "10")
|
||||
private Integer discountPrice;
|
||||
|
||||
@Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用
|
||||
private Integer discountLimitPrice;
|
||||
|
||||
@Schema(description = "是否可用", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
private Boolean match;
|
||||
|
||||
@Schema(description = "不可用原因", example = "优惠劵已过期")
|
||||
private String mismatchReason;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,81 @@
|
||||
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Schema(description = "用户 App - 商品结算信息 Response VO")
|
||||
@Data
|
||||
public class AppTradeProductSettlementRespVO {
|
||||
|
||||
@Schema(description = "SPU 商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long spuId;
|
||||
|
||||
@Schema(description = "SKU 价格信息数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private List<Sku> skus;
|
||||
|
||||
@Schema(description = "满减送活动信息", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private RewardActivity rewardActivity;
|
||||
|
||||
@Schema(description = "SKU 价格信息")
|
||||
@Data
|
||||
public static class Sku implements Serializable {
|
||||
|
||||
@Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "优惠后价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
|
||||
private Integer promotionPrice;
|
||||
|
||||
@Schema(description = "营销类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "4")
|
||||
private Integer promotionType; // 对应 PromotionTypeEnum 枚举,目前只有 4 和 6 两种
|
||||
|
||||
@Schema(description = "营销编号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long promotionId; // 目前只有限时折扣活动的编号
|
||||
|
||||
@Schema(description = "活动结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime promotionEndTime;
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "满减送活动信息")
|
||||
@Data
|
||||
public static class RewardActivity {
|
||||
|
||||
@Schema(description = "满减活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "条件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer conditionType;
|
||||
|
||||
@Schema(description = "优惠规则的数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<RewardActivityRule> rules;
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "优惠规则")
|
||||
@Data
|
||||
public static class RewardActivityRule {
|
||||
|
||||
@Schema(description = "优惠门槛", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") // 1. 满 N 元,单位:分; 2. 满 N 件
|
||||
private Integer limit;
|
||||
|
||||
@Schema(description = "优惠价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
|
||||
private Integer discountPrice;
|
||||
|
||||
@Schema(description = "是否包邮", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
private Boolean freeDelivery;
|
||||
|
||||
@Schema(description = "赠送的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
|
||||
private Integer point;
|
||||
|
||||
@Schema(description = "赠送的优惠劵编号的数组")
|
||||
private Map<Long, Integer> giveCouponTemplateCounts;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -219,7 +219,9 @@ public interface TradeOrderConvert {
|
||||
.setSeckillActivityId(settlementReqVO.getSeckillActivityId())
|
||||
.setBargainRecordId(settlementReqVO.getBargainRecordId())
|
||||
.setCombinationActivityId(settlementReqVO.getCombinationActivityId())
|
||||
.setCombinationHeadId(settlementReqVO.getCombinationHeadId());
|
||||
.setCombinationHeadId(settlementReqVO.getCombinationHeadId())
|
||||
.setPointActivityId(settlementReqVO.getPointActivityId());
|
||||
|
||||
// 商品项的构建
|
||||
Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId);
|
||||
for (AppTradeOrderSettlementReqVO.Item item : settlementReqVO.getItems()) {
|
||||
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.dal.dataobject.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
|
||||
@ -12,10 +13,14 @@ import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 交易订单 DO
|
||||
@ -291,6 +296,24 @@ public class TradeOrderDO extends BaseDO {
|
||||
*/
|
||||
private Integer vipPrice;
|
||||
|
||||
/**
|
||||
* 赠送的优惠劵
|
||||
*
|
||||
* key: 优惠劵模版编号
|
||||
* value:对应的优惠券数量
|
||||
*
|
||||
* 目的:用于订单支付后赠送优惠券
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private Map<Long, Integer> giveCouponTemplateCounts;
|
||||
/**
|
||||
* 赠送的优惠劵编号
|
||||
*
|
||||
* 目的:用于后续取消或者售后订单时,需要扣减赠送
|
||||
*/
|
||||
@TableField(typeHandler = LongListTypeHandler.class)
|
||||
private List<Long> giveCouponIds;
|
||||
|
||||
/**
|
||||
* 秒杀活动编号
|
||||
*
|
||||
@ -330,4 +353,11 @@ public class TradeOrderDO extends BaseDO {
|
||||
*/
|
||||
private Long combinationRecordId;
|
||||
|
||||
/**
|
||||
* 积分商城活动的编号
|
||||
*
|
||||
* 关联 PointActivityDO 的 id 字段
|
||||
*/
|
||||
private Long pointActivityId;
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
@ -20,6 +21,7 @@ import java.util.List;
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "trade_order_item", autoResultMap = true)
|
||||
@KeySequence("trade_order_item_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
|
@ -5,10 +5,12 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppPointOrderVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
@ -129,4 +131,11 @@ public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> {
|
||||
wrapperX.eq(TradeOrderDO::getUserId, userId).eq(TradeOrderDO::getSeckillActivityId, activityId);
|
||||
return selectList(wrapperX);
|
||||
}
|
||||
|
||||
@Select(" SELECT a.status,d.label,a.create_time,a.pay_price,a.use_point,c.name,c.pic_url,b.count FROM trade_order a " +
|
||||
" inner join trade_order_item b on a.id = b.order_id " +
|
||||
" inner join (SELECT sort, label, value, dict_type, css_class FROM system_dict_data WHERE dict_type = 'trade_order_status') d on d.sort = a.status "+
|
||||
" inner join product_spu c on b.spu_id = c.id " +
|
||||
" where a.user_id = #{userId} and a.use_point > 0 ")
|
||||
List<AppPointOrderVO> getPointOrder(Long userId);
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ public class ExpressClientFactoryImpl implements ExpressClientFactory {
|
||||
}
|
||||
|
||||
private ExpressClient createExpressClient(ExpressClientEnum queryProviderEnum,
|
||||
TradeExpressProperties tradeExpressProperties) {
|
||||
TradeExpressProperties tradeExpressProperties) {
|
||||
switch (queryProviderEnum) {
|
||||
case NOT_PROVIDE:
|
||||
return new NoProvideExpressClient();
|
||||
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.trade.service.order;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderSummaryRespVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppPointOrderVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
|
||||
@ -163,4 +164,9 @@ public interface TradeOrderQueryService {
|
||||
* @return 拼团商品数量
|
||||
*/
|
||||
Integer getCombinationProductCount(Long userId, Long activityId);
|
||||
|
||||
List<AppPointOrderVO> getPointOrder(Long userId);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderSummaryRespVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppPointOrderVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
|
||||
@ -259,6 +260,12 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
|
||||
return tradeOrderItemMapper.selectProductSumByOrderId(convertSet(orders, TradeOrderDO::getId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AppPointOrderVO> getPointOrder(Long userId) {
|
||||
List<AppPointOrderVO> pointOrder = tradeOrderMapper.getPointOrder(userId);
|
||||
return pointOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得自身的代理对象,解决 AOP 生效问题
|
||||
*
|
||||
@ -268,4 +275,8 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
|
||||
return SpringUtil.getBean(getClass());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -41,6 +41,16 @@ public interface TradeOrderUpdateService {
|
||||
*/
|
||||
TradeOrderDO createOrder(Long userId, AppTradeOrderCreateReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 同步订单的支付状态
|
||||
*
|
||||
* 1. Quietly 表示,即使同步失败,也不会抛出异常
|
||||
* 2. 什么时候回出现异常?因为是主动同步,可能和支付模块的回调通知 {@link #updateOrderPaid(Long, Long)} 存在并发冲突,导致抛出异常
|
||||
*
|
||||
* @param id 订单编号
|
||||
* @param payOrderId 支付订单编号
|
||||
*/
|
||||
void syncOrderPayStatusQuietly(Long id, Long payOrderId);
|
||||
/**
|
||||
* 更新交易订单已支付
|
||||
*
|
||||
|
@ -23,6 +23,8 @@ import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
|
||||
import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
|
||||
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.dataobject.pointorder.PointOrderDO;
|
||||
import cn.iocoder.yudao.module.promotion.dal.mysql.pointorder.PointOrderMapper;
|
||||
import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
|
||||
@ -75,6 +77,7 @@ import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.min
|
||||
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
|
||||
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getTerminal;
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_NOT_FOUND;
|
||||
|
||||
/**
|
||||
* 交易订单【写】Service 实现类
|
||||
@ -105,6 +108,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
@Resource
|
||||
private TradeMessageService tradeMessageService;
|
||||
|
||||
@Resource
|
||||
private PointOrderMapper pointOrderMapper;
|
||||
|
||||
@Resource
|
||||
private PayOrderApi payOrderApi;
|
||||
@Resource
|
||||
@ -118,13 +124,17 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
@Resource
|
||||
private SocialUserApi socialUserApi;
|
||||
|
||||
|
||||
@Resource
|
||||
private ProductSpuApi productSpuApi;
|
||||
|
||||
|
||||
// =================== Order ===================
|
||||
|
||||
|
||||
@Override
|
||||
public AppTradeOrderSettlementRespVO settlementOrder(Long userId, AppTradeOrderSettlementReqVO settlementReqVO) {
|
||||
// settlementReqVO.setPointActivityId((long) 9);
|
||||
// 1. 获得收货地址
|
||||
MemberAddressRespDTO address = getAddress(userId, settlementReqVO.getAddressId());
|
||||
if (address != null) {
|
||||
@ -135,21 +145,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, settlementReqVO);
|
||||
|
||||
// 3. 拼接返回
|
||||
AppTradeOrderSettlementRespVO convert = TradeOrderConvert.INSTANCE.convert(calculateRespBO, address);
|
||||
//默认禁用积分
|
||||
convert.setPointsStatus(0);
|
||||
//spu数组
|
||||
ArrayList<Long> list = new ArrayList<>();
|
||||
for (TradePriceCalculateRespBO.OrderItem item : calculateRespBO.getItems()) {
|
||||
list.add(item.getSpuId());
|
||||
}
|
||||
List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(list);
|
||||
for (ProductSpuRespDTO respDTO : spuList) {
|
||||
if (respDTO.getPointsStatus() == 1){
|
||||
convert.setPointsStatus(1);
|
||||
}
|
||||
}
|
||||
return convert;
|
||||
return TradeOrderConvert.INSTANCE.convert(calculateRespBO, address);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -182,9 +178,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
TradePriceCalculateReqBO calculateReqBO = TradeOrderConvert.INSTANCE.convert(userId, settlementReqVO, cartList);
|
||||
calculateReqBO.getItems().forEach(item -> Assert.isTrue(item.getSelected(), // 防御性编程,保证都是选中的
|
||||
"商品({}) 未设置为选中", item.getSkuId()));
|
||||
return tradePriceService.calculatePrice(calculateReqBO);
|
||||
return tradePriceService.calculateOrderPrice(calculateReqBO);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CREATE)
|
||||
@ -203,11 +200,46 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
orderItems.forEach(orderItem -> orderItem.setOrderId(order.getId()));
|
||||
tradeOrderItemMapper.insertBatch(orderItems);
|
||||
|
||||
//添加进积分订单记录
|
||||
if (order.getUsePoint() > 0){
|
||||
|
||||
//查询spu_id
|
||||
TradeOrderItemDO tradeOrderItemDO = tradeOrderItemMapper.selectOne("order_id", order.getId());
|
||||
|
||||
PointOrderDO pointOrderDO = new PointOrderDO();
|
||||
pointOrderDO.setOrderNumber(order.getNo());
|
||||
pointOrderDO.setUserId(order.getUserId());
|
||||
pointOrderDO.setProductId(tradeOrderItemDO.getSpuId());
|
||||
pointOrderDO.setIntegral(order.getUsePoint());
|
||||
pointOrderDO.setOrderStatus(order.getStatus());
|
||||
pointOrderDO.setOrderTime(order.getCreateTime());
|
||||
|
||||
pointOrderMapper.insert(pointOrderDO);
|
||||
}
|
||||
|
||||
|
||||
// 4. 订单创建后的逻辑
|
||||
afterCreateTradeOrder(order, orderItems, createReqVO);
|
||||
return order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncOrderPayStatusQuietly(Long id, Long payOrderId) {
|
||||
PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
|
||||
if (payOrder == null) {
|
||||
return;
|
||||
}
|
||||
if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
getSelf().updateOrderPaid(id, payOrderId);
|
||||
} catch (Throwable e) {
|
||||
log.warn("[syncOrderPayStatusQuietly][id({}) payOrderId({}) 同步支付状态失败]", id, payOrderId, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private TradeOrderDO buildTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO,
|
||||
TradePriceCalculateRespBO calculateRespBO) {
|
||||
TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, createReqVO, calculateRespBO);
|
||||
@ -217,6 +249,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
|
||||
order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum));
|
||||
order.setUserIp(getClientIP()).setTerminal(getTerminal());
|
||||
// 使用 + 赠送优惠券
|
||||
order.setGiveCouponTemplateCounts(calculateRespBO.getGiveCouponTemplateCounts());
|
||||
// 支付 + 退款信息
|
||||
order.setAdjustPrice(0).setPayStatus(false);
|
||||
order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0);
|
||||
@ -260,7 +294,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
}
|
||||
|
||||
// 3. 生成预支付
|
||||
createPayOrder(order, orderItems);
|
||||
// 特殊情况:积分兑换时,可能支付金额为零
|
||||
if (order.getPayPrice() > 0) {
|
||||
createPayOrder(order, orderItems);
|
||||
}
|
||||
|
||||
// 4. 插入订单日志
|
||||
TradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getStatus());
|
||||
@ -305,6 +342,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
TradeOrderLogUtils.setUserInfo(order.getUserId(), UserTypeEnum.MEMBER.getValue());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 校验交易订单满足被支付的条件
|
||||
* <p>
|
||||
|
@ -78,12 +78,10 @@ public class TradeMemberPointOrderHandler implements TradeOrderHandler {
|
||||
|
||||
@Override
|
||||
public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
|
||||
// 扣减(回滚)积分(订单赠送)
|
||||
reducePoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE_CANCEL_ITEM,
|
||||
orderItem.getId());
|
||||
// 增加(回滚)积分(订单抵扣)
|
||||
addPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE_CANCEL_ITEM,
|
||||
orderItem.getId());
|
||||
addPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE_CANCEL_ITEM, orderItem.getId());
|
||||
// 扣减(回滚)积分(订单赠送)
|
||||
reducePoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE_CANCEL_ITEM, orderItem.getId());
|
||||
|
||||
// 扣减(回滚)用户经验
|
||||
AfterSaleDO afterSale = afterSaleService.getAfterSale(orderItem.getAfterSaleId());
|
||||
|
@ -0,0 +1,71 @@
|
||||
package cn.iocoder.yudao.module.trade.service.order.handler;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.module.promotion.api.point.PointActivityApi;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 积分商城活动订单的 {@link TradeOrderHandler} 实现类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class TradePointOrderHandler implements TradeOrderHandler {
|
||||
|
||||
@Resource
|
||||
private PointActivityApi pointActivityApi;
|
||||
|
||||
@Override
|
||||
public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
|
||||
if (!TradeOrderTypeEnum.isPoint(order.getType())) {
|
||||
return;
|
||||
}
|
||||
// 明确校验一下
|
||||
Assert.isTrue(orderItems.size() == 1, "积分商城活动兑换商品兑换时,只允许选择一个商品");
|
||||
|
||||
// 扣减积分商城活动的库存
|
||||
pointActivityApi.updatePointStockDecr(order.getPointActivityId(),
|
||||
orderItems.get(0).getSkuId(), orderItems.get(0).getCount());
|
||||
|
||||
// 如果支付金额为 0,则直接设置为已支付
|
||||
if (Objects.equals(order.getPayPrice(), 0)) {
|
||||
order.setPayStatus(true).setStatus(TradeOrderStatusEnum.UNDELIVERED.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
|
||||
if (!TradeOrderTypeEnum.isPoint(order.getType())) {
|
||||
return;
|
||||
}
|
||||
// 明确校验一下
|
||||
Assert.isTrue(orderItems.size() == 1, "积分商城活动兑换商品兑换时,只允许选择一个商品");
|
||||
|
||||
// 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚
|
||||
orderItems = filterOrderItemListByNoneAfterSale(orderItems);
|
||||
if (CollUtil.isEmpty(orderItems)) {
|
||||
return;
|
||||
}
|
||||
afterCancelOrderItem(order, orderItems.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
|
||||
if (!TradeOrderTypeEnum.isPoint(order.getType())) {
|
||||
return;
|
||||
}
|
||||
// 恢复积分商城活动的库存
|
||||
pointActivityApi.updatePointStockIncr(order.getPointActivityId(),
|
||||
orderItem.getSkuId(), orderItem.getCount());
|
||||
}
|
||||
|
||||
}
|
@ -18,6 +18,16 @@ public interface TradePriceService {
|
||||
* @param calculateReqDTO 计算信息
|
||||
* @return 计算结果
|
||||
*/
|
||||
TradePriceCalculateRespBO calculatePrice(@Valid TradePriceCalculateReqBO calculateReqDTO);
|
||||
// TradePriceCalculateRespBO calculatePrice(@Valid TradePriceCalculateReqBO calculateReqDTO);
|
||||
|
||||
/**
|
||||
* 【订单】价格计算
|
||||
*
|
||||
* @param calculateReqDTO 计算信息
|
||||
* @return 计算结果
|
||||
*/
|
||||
TradePriceCalculateRespBO calculateOrderPrice(@Valid TradePriceCalculateReqBO calculateReqDTO);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,21 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
|
||||
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
|
||||
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
|
||||
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeProductSettlementRespVO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.calculator.TradeDiscountActivityPriceCalculator;
|
||||
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator;
|
||||
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -17,8 +27,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
||||
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
|
||||
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
|
||||
@ -37,12 +46,19 @@ public class TradePriceServiceImpl implements TradePriceService {
|
||||
private ProductSkuApi productSkuApi;
|
||||
@Resource
|
||||
private ProductSpuApi productSpuApi;
|
||||
@Resource
|
||||
private DiscountActivityApi discountActivityApi;
|
||||
@Resource
|
||||
private RewardActivityApi rewardActivityApi;
|
||||
|
||||
@Resource
|
||||
private List<TradePriceCalculator> priceCalculators;
|
||||
|
||||
@Resource
|
||||
private TradeDiscountActivityPriceCalculator discountActivityPriceCalculator;
|
||||
|
||||
@Override
|
||||
public TradePriceCalculateRespBO calculatePrice(TradePriceCalculateReqBO calculateReqBO) {
|
||||
public TradePriceCalculateRespBO calculateOrderPrice(TradePriceCalculateReqBO calculateReqBO) {
|
||||
// 1.1 获得商品 SKU 数组
|
||||
List<ProductSkuRespDTO> skuList = checkSkuList(calculateReqBO);
|
||||
// 1.2 获得商品 SPU 数组
|
||||
@ -51,12 +67,10 @@ public class TradePriceServiceImpl implements TradePriceService {
|
||||
// 2.1 计算价格
|
||||
TradePriceCalculateRespBO calculateRespBO = TradePriceCalculatorHelper
|
||||
.buildCalculateResp(calculateReqBO, spuList, skuList);
|
||||
// priceCalculators.forEach(calculator -> calculator.calculate(calculateReqBO, calculateRespBO));
|
||||
for (TradePriceCalculator calculator : priceCalculators) {
|
||||
calculator.calculate(calculateReqBO, calculateRespBO);
|
||||
}
|
||||
priceCalculators.forEach(calculator -> calculator.calculate(calculateReqBO, calculateRespBO));
|
||||
// 2.2 如果最终支付金额小于等于 0,则抛出业务异常
|
||||
if (calculateRespBO.getPrice().getPayPrice() <= 0) {
|
||||
if (calculateReqBO.getPointActivityId() == null // 积分订单,允许支付金额为 0
|
||||
&& calculateRespBO.getPrice().getPayPrice() <= 0) {
|
||||
log.error("[calculatePrice][价格计算不正确,请求 calculateReqDTO({}),结果 priceCalculate({})]",
|
||||
calculateReqBO, calculateRespBO);
|
||||
throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
|
||||
@ -84,8 +98,9 @@ public class TradePriceServiceImpl implements TradePriceService {
|
||||
}
|
||||
|
||||
private List<ProductSpuRespDTO> checkSpuList(List<ProductSkuRespDTO> skuList) {
|
||||
// 获得商品 SPU 数组
|
||||
return productSpuApi.validateSpuList(convertSet(skuList, ProductSkuRespDTO::getSpuId));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -84,11 +84,11 @@ public class TradePriceCalculateReqBO {
|
||||
*/
|
||||
private Long bargainRecordId;
|
||||
|
||||
// ========== 满减送活动相关字段 ==========
|
||||
// ========== 积分商城活动相关字段 ==========
|
||||
/**
|
||||
* 满减送活动编号
|
||||
* 积分商城活动编号
|
||||
*/
|
||||
private Long rewardId;
|
||||
private Long pointActivityId;
|
||||
|
||||
/**
|
||||
* 商品 SKU
|
||||
|
@ -5,7 +5,9 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 价格计算 Response BO
|
||||
@ -44,17 +46,25 @@ public class TradePriceCalculateRespBO {
|
||||
private List<Promotion> promotions;
|
||||
|
||||
/**
|
||||
* 优惠劵编号
|
||||
* 使用的优惠劵编号
|
||||
*/
|
||||
private Long couponId;
|
||||
/**
|
||||
* 用户的优惠劵列表(可用 + 不可用)
|
||||
*/
|
||||
private List<Coupon> coupons;
|
||||
|
||||
/**
|
||||
* 会员剩余积分
|
||||
*/
|
||||
private Integer totalPoint;
|
||||
/**
|
||||
* 使用的积分
|
||||
*/
|
||||
private Integer usePoint;
|
||||
|
||||
/**
|
||||
* 使用的积分
|
||||
* 赠送的积分
|
||||
*/
|
||||
private Integer givePoint;
|
||||
|
||||
@ -63,6 +73,21 @@ public class TradePriceCalculateRespBO {
|
||||
*/
|
||||
private Long bargainActivityId;
|
||||
|
||||
/**
|
||||
* 是否包邮
|
||||
*/
|
||||
private Boolean freeDelivery;
|
||||
|
||||
/**
|
||||
* 赠送的优惠劵
|
||||
*
|
||||
* key: 优惠劵模版编号
|
||||
* value:对应的优惠券数量
|
||||
*
|
||||
* 目的:用于订单支付后赠送优惠券
|
||||
*/
|
||||
private Map<Long, Integer> giveCouponTemplateCounts;
|
||||
|
||||
/**
|
||||
* 订单价格
|
||||
*/
|
||||
@ -209,8 +234,19 @@ public class TradePriceCalculateRespBO {
|
||||
*/
|
||||
private Long categoryId;
|
||||
|
||||
// ========== 物流相关字段 =========
|
||||
|
||||
/**
|
||||
* 运费模板 Id
|
||||
* 配送方式数组
|
||||
*
|
||||
* 对应 DeliveryTypeEnum 枚举
|
||||
*/
|
||||
private List<Integer> deliveryTypes;
|
||||
|
||||
/**
|
||||
* 物流配置模板编号
|
||||
*
|
||||
* 对应 TradeDeliveryExpressTemplateDO 的 id 编号
|
||||
*/
|
||||
private Long deliveryTemplateId;
|
||||
|
||||
@ -230,7 +266,7 @@ public class TradePriceCalculateRespBO {
|
||||
private List<ProductPropertyValueDetailRespDTO> properties;
|
||||
|
||||
/**
|
||||
* 使用的积分
|
||||
* 赠送的积分
|
||||
*/
|
||||
private Integer givePoint;
|
||||
|
||||
@ -308,4 +344,62 @@ public class TradePriceCalculateRespBO {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 优惠劵信息
|
||||
*/
|
||||
@Data
|
||||
public static class Coupon {
|
||||
|
||||
/**
|
||||
* 优惠劵编号
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 优惠劵名
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 是否设置满多少金额可用,单位:分
|
||||
*/
|
||||
private Integer usePrice;
|
||||
|
||||
/**
|
||||
* 生效开始时间
|
||||
*/
|
||||
private LocalDateTime validStartTime;
|
||||
/**
|
||||
* 生效结束时间
|
||||
*/
|
||||
private LocalDateTime validEndTime;
|
||||
|
||||
/**
|
||||
* 优惠类型
|
||||
*/
|
||||
private Integer discountType;
|
||||
/**
|
||||
* 折扣百分比
|
||||
*/
|
||||
private Integer discountPercent;
|
||||
/**
|
||||
* 优惠金额,单位:分
|
||||
*/
|
||||
private Integer discountPrice;
|
||||
/**
|
||||
* 折扣上限,单位:分
|
||||
*/
|
||||
private Integer discountLimitPrice;
|
||||
|
||||
/**
|
||||
* 是否匹配
|
||||
*/
|
||||
private Boolean match;
|
||||
/**
|
||||
* 不匹配的原因
|
||||
*/
|
||||
private String mismatchReason;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -50,5 +50,4 @@ public class TradeCombinationActivityPriceCalculator implements TradePriceCalcul
|
||||
TradePriceCalculatorHelper.recountPayPrice(orderItem);
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,12 +4,15 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
@ -21,9 +24,11 @@ import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_MIN_PRICE;
|
||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_SPU;
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_CAN_NOT_USE;
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER;
|
||||
|
||||
/**
|
||||
@ -40,33 +45,37 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
|
||||
|
||||
@Override
|
||||
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||
// 1.1 校验优惠劵
|
||||
// 只有【普通】订单,才允许使用优惠劵
|
||||
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
|
||||
if (param.getCouponId() != null) {
|
||||
throw exception(PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.1 加载用户的优惠劵列表
|
||||
List<CouponRespDTO> coupons = couponApi.getCouponListByUserId(param.getUserId(), CouponStatusEnum.UNUSED.getStatus());
|
||||
coupons.removeIf(coupon -> LocalDateTimeUtils.beforeNow(coupon.getValidEndTime()));
|
||||
// 1.2 计算优惠劵的使用条件
|
||||
result.setCoupons(calculateCoupons(coupons, result));
|
||||
|
||||
// 2. 校验优惠劵是否可用
|
||||
if (param.getCouponId() == null) {
|
||||
return;
|
||||
}
|
||||
CouponRespDTO coupon = couponApi.validateCoupon(new CouponValidReqDTO()
|
||||
.setId(param.getCouponId()).setUserId(param.getUserId()));
|
||||
Assert.notNull(coupon, "校验通过的优惠劵({}),不能为空", param.getCouponId());
|
||||
// 1.2 只有【普通】订单,才允许使用优惠劵
|
||||
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
|
||||
throw exception(PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER);
|
||||
TradePriceCalculateRespBO.Coupon couponBO = CollUtil.findOne(result.getCoupons(), item -> item.getId().equals(param.getCouponId()));
|
||||
CouponRespDTO coupon = CollUtil.findOne(coupons, item -> item.getId().equals(param.getCouponId()));
|
||||
if (couponBO == null || coupon == null) {
|
||||
throw exception(PRICE_CALCULATE_COUPON_CAN_NOT_USE, "优惠劵不存在");
|
||||
}
|
||||
|
||||
// 2.1 获得匹配的商品 SKU 数组
|
||||
List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);
|
||||
if (CollUtil.isEmpty(orderItems)) {
|
||||
throw exception(COUPON_NO_MATCH_SPU);
|
||||
}
|
||||
// 2.2 计算是否满足优惠劵的使用金额
|
||||
Integer totalPayPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
|
||||
if (totalPayPrice < coupon.getUsePrice()) {
|
||||
throw exception(COUPON_NO_MATCH_MIN_PRICE);
|
||||
if (Boolean.FALSE.equals(couponBO.getMatch())) {
|
||||
throw exception(PRICE_CALCULATE_COUPON_CAN_NOT_USE, couponBO.getMismatchReason());
|
||||
}
|
||||
|
||||
// 3.1 计算可以优惠的金额
|
||||
List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);
|
||||
Integer totalPayPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
|
||||
Integer couponPrice = getCouponPrice(coupon, totalPayPrice);
|
||||
Assert.isTrue(couponPrice < totalPayPrice,
|
||||
"优惠劵({}) 的优惠金额({}),不能大于订单总金额({})", coupon.getId(), couponPrice, totalPayPrice);
|
||||
// 3.2 计算分摊的优惠金额
|
||||
List<Integer> divideCouponPrices = TradePriceCalculatorHelper.dividePrice(orderItems, couponPrice);
|
||||
|
||||
@ -74,7 +83,7 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
|
||||
result.setCouponId(param.getCouponId());
|
||||
// 4.2 记录优惠明细
|
||||
TradePriceCalculatorHelper.addPromotion(result, orderItems,
|
||||
param.getCouponId(), coupon.getName(), PromotionTypeEnum.COUPON.getType(),
|
||||
param.getCouponId(), couponBO.getName(), PromotionTypeEnum.COUPON.getType(),
|
||||
StrUtil.format("优惠劵:省 {} 元", TradePriceCalculatorHelper.formatPrice(couponPrice)),
|
||||
divideCouponPrices);
|
||||
// 4.3 更新 SKU 优惠金额
|
||||
@ -86,6 +95,43 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 计算用户的优惠劵列表(可用 + 不可用)
|
||||
*
|
||||
* @param coupons 优惠劵
|
||||
* @param result 计算结果
|
||||
* @return 优惠劵列表
|
||||
*/
|
||||
private List<TradePriceCalculateRespBO.Coupon> calculateCoupons(List<CouponRespDTO> coupons,
|
||||
TradePriceCalculateRespBO result) {
|
||||
return convertList(coupons, coupon -> {
|
||||
TradePriceCalculateRespBO.Coupon matchCoupon = BeanUtils.toBean(coupon, TradePriceCalculateRespBO.Coupon.class);
|
||||
// 1.1 优惠劵未到使用时间
|
||||
if (LocalDateTimeUtils.afterNow(coupon.getValidStartTime())) {
|
||||
return matchCoupon.setMatch(false).setMismatchReason("优惠劵未到使用时间");
|
||||
}
|
||||
// 1.2 优惠劵没有匹配的商品
|
||||
List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);
|
||||
if (CollUtil.isEmpty(orderItems)) {
|
||||
return matchCoupon.setMatch(false).setMismatchReason("优惠劵没有匹配的商品");
|
||||
}
|
||||
// 1.3 差 %1$,.2f 元可用优惠劵
|
||||
Integer totalPayPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
|
||||
if (totalPayPrice < coupon.getUsePrice()) {
|
||||
return matchCoupon.setMatch(false)
|
||||
.setMismatchReason(String.format("差 %1$,.2f 元可用优惠劵", (coupon.getUsePrice() - totalPayPrice) / 100D));
|
||||
}
|
||||
// 1.4 优惠金额超过订单金额
|
||||
Integer couponPrice = getCouponPrice(coupon, totalPayPrice);
|
||||
if (couponPrice >= totalPayPrice) {
|
||||
return matchCoupon.setMatch(false).setMismatchReason("优惠金额超过订单金额");
|
||||
}
|
||||
|
||||
// 2. 满足条件
|
||||
return matchCoupon.setMatch(true);
|
||||
});
|
||||
}
|
||||
private Integer getCouponPrice(CouponRespDTO coupon, Integer totalPayPrice) {
|
||||
if (PromotionDiscountTypeEnum.PRICE.getType().equals(coupon.getDiscountType())) { // 减价
|
||||
return coupon.getDiscountPrice();
|
||||
|
@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.member.api.address.MemberAddressApi;
|
||||
import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
|
||||
@ -29,6 +30,7 @@ import java.util.Set;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL;
|
||||
|
||||
/**
|
||||
* 运费的 {@link TradePriceCalculator} 实现类
|
||||
@ -55,6 +57,11 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||
if (param.getDeliveryType() == null) {
|
||||
return;
|
||||
}
|
||||
// 校验是不是存在商品不能门店自提,或者不能快递发货的情况。就是说,配送方式不匹配哈
|
||||
if (CollectionUtils.anyMatch(result.getItems(), item -> !item.getDeliveryTypes().contains(param.getDeliveryType()))) {
|
||||
throw exception(PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL);
|
||||
}
|
||||
|
||||
if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) {
|
||||
calculateByPickUp(param);
|
||||
} else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) {
|
||||
@ -89,7 +96,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况二:快递模版
|
||||
// 情况二:活动包邮
|
||||
if (Boolean.TRUE.equals(result.getFreeDelivery())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 情况三:快递模版
|
||||
// 2.1 过滤出已选中的商品 SKU
|
||||
List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected);
|
||||
Set<Long> deliveryTemplateIds = convertSet(selectedItem, OrderItem::getDeliveryTemplateId);
|
||||
@ -111,9 +123,9 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||
*/
|
||||
private boolean isGlobalExpressFree(TradePriceCalculateRespBO result) {
|
||||
TradeConfigDO config = tradeConfigService.getTradeConfig();
|
||||
return config != null
|
||||
&& Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮
|
||||
&& result.getPrice().getPayPrice() >= config.getDeliveryExpressFreePrice(); // 满足包邮的价格
|
||||
return config == null
|
||||
|| Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮
|
||||
|| result.getPrice().getPayPrice() >= config.getDeliveryExpressFreePrice(); // 满足包邮的价格
|
||||
}
|
||||
|
||||
private void calculateDeliveryPrice(List<OrderItem> selectedSkus,
|
||||
@ -123,7 +135,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||
Map<Long, List<OrderItem>> template2ItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId);
|
||||
// 依次计算快递运费
|
||||
for (Map.Entry<Long, List<OrderItem>> entry : template2ItemMap.entrySet()) {
|
||||
Long templateId = entry.getKey();
|
||||
Long templateId = entry.getKey();
|
||||
List<OrderItem> orderItems = entry.getValue();
|
||||
DeliveryExpressTemplateRespBO templateBO = expressTemplateMap.get(templateId);
|
||||
if (templateBO == null) {
|
||||
@ -143,8 +155,8 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||
/**
|
||||
* 按配送方式来计算运费
|
||||
*
|
||||
* @param orderItems SKU 商品项目
|
||||
* @param chargeMode 配送计费方式
|
||||
* @param orderItems SKU 商品项目
|
||||
* @param chargeMode 配送计费方式
|
||||
* @param templateCharge 快递运费配置
|
||||
*/
|
||||
private void calculateExpressFeeByChargeMode(List<OrderItem> orderItems, Integer chargeMode,
|
||||
|
@ -3,6 +3,10 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
|
||||
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
|
||||
@ -19,6 +23,7 @@ import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.framework.common.util.number.MoneyUtils.calculateRatePrice;
|
||||
import static cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper.formatPrice;
|
||||
|
||||
/**
|
||||
@ -33,44 +38,115 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato
|
||||
@Resource
|
||||
private DiscountActivityApi discountActivityApi;
|
||||
|
||||
@Resource
|
||||
private MemberUserApi memberUserApi;
|
||||
|
||||
@Resource
|
||||
private MemberLevelApi memberLevelApi;
|
||||
|
||||
/**
|
||||
* 计算会员 VIP 的优惠价格
|
||||
*
|
||||
* @param level 会员等级
|
||||
* @param orderItem 交易项
|
||||
* @return 优惠价格
|
||||
*/
|
||||
public Integer calculateVipPrice(MemberLevelRespDTO level,
|
||||
TradePriceCalculateRespBO.OrderItem orderItem) {
|
||||
if (level == null || level.getDiscountPercent() == null) {
|
||||
return 0;
|
||||
}
|
||||
Integer newPrice = calculateRatePrice(orderItem.getPayPrice(), level.getDiscountPercent().doubleValue());
|
||||
return orderItem.getPayPrice() - newPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算优惠活动的价格
|
||||
*
|
||||
* @param discount 优惠活动
|
||||
* @param orderItem 交易项
|
||||
* @return 优惠价格
|
||||
*/
|
||||
public Integer calculateActivityPrice(DiscountProductRespDTO discount,
|
||||
TradePriceCalculateRespBO.OrderItem orderItem) {
|
||||
if (discount == null) {
|
||||
return 0;
|
||||
}
|
||||
Integer newPrice = orderItem.getPayPrice();
|
||||
if (PromotionDiscountTypeEnum.PRICE.getType().equals(discount.getDiscountType())) { // 减价
|
||||
newPrice -= discount.getDiscountPrice() * orderItem.getCount();
|
||||
} else if (PromotionDiscountTypeEnum.PERCENT.getType().equals(discount.getDiscountType())) { // 打折
|
||||
newPrice = calculateRatePrice(orderItem.getPayPrice(), discount.getDiscountPercent() / 100.0);
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("优惠活动的商品(%s) 的优惠类型不正确", discount));
|
||||
}
|
||||
return orderItem.getPayPrice() - newPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得用户的等级
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @return 用户等级
|
||||
*/
|
||||
public MemberLevelRespDTO getMemberLevel(Long userId) {
|
||||
MemberUserRespDTO user = memberUserApi.getUser(userId);
|
||||
if (user == null || user.getLevelId() == null || user.getLevelId() <= 0) {
|
||||
return null;
|
||||
}
|
||||
return memberLevelApi.getMemberLevel(user.getLevelId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||
// 0. 只有【普通】订单,才计算该优惠
|
||||
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
|
||||
return;
|
||||
}
|
||||
// 获得 SKU 对应的限时折扣活动
|
||||
|
||||
// 1.1 获得 SKU 对应的限时折扣活动
|
||||
List<DiscountProductRespDTO> discountProducts = discountActivityApi.getMatchDiscountProductList(
|
||||
convertSet(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSkuId));
|
||||
if (CollUtil.isEmpty(discountProducts)) {
|
||||
return;
|
||||
}
|
||||
Map<Long, DiscountProductRespDTO> discountProductMap = convertMap(discountProducts, DiscountProductRespDTO::getSkuId);
|
||||
// 1.2 获得会员等级
|
||||
MemberLevelRespDTO level = getMemberLevel(param.getUserId());
|
||||
|
||||
// 处理每个 SKU 的限时折扣
|
||||
// 2. 计算每个 SKU 的优惠金额
|
||||
result.getItems().forEach(orderItem -> {
|
||||
// 1. 获取该 SKU 的优惠信息
|
||||
DiscountProductRespDTO discountProduct = discountProductMap.get(orderItem.getSkuId());
|
||||
if (discountProduct == null) {
|
||||
if (!orderItem.getSelected()) {
|
||||
return;
|
||||
}
|
||||
// 2.1 计算限时折扣的优惠金额
|
||||
DiscountProductRespDTO discountProduct = discountProductMap.get(orderItem.getSkuId());
|
||||
Integer discountPrice = calculateActivityPrice(discountProduct, orderItem);
|
||||
// 2.2 计算 VIP 优惠金额
|
||||
Integer vipPrice = calculateVipPrice(level, orderItem);
|
||||
if (discountPrice <= 0 && vipPrice <= 0) {
|
||||
return;
|
||||
}
|
||||
// 2. 计算优惠金额
|
||||
Integer newPayPrice = calculatePayPrice(discountProduct, orderItem);
|
||||
Integer newDiscountPrice = orderItem.getPayPrice() - newPayPrice;
|
||||
|
||||
// 3.1 记录优惠明细
|
||||
if (orderItem.getSelected()) {
|
||||
// 注意,只有在选中的情况下,才会记录到优惠明细。否则仅仅是更新 SKU 优惠金额,用于展示
|
||||
// 3. 选择优惠金额多的
|
||||
if (discountPrice > vipPrice) {
|
||||
TradePriceCalculatorHelper.addPromotion(result, orderItem,
|
||||
discountProduct.getActivityId(), discountProduct.getActivityName(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(),
|
||||
StrUtil.format("限时折扣:省 {} 元", formatPrice(newDiscountPrice)),
|
||||
newDiscountPrice);
|
||||
StrUtil.format("限时折扣:省 {} 元", formatPrice(discountPrice)),
|
||||
discountPrice);
|
||||
// 更新 SKU 优惠金额
|
||||
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + discountPrice);
|
||||
} else {
|
||||
assert level != null;
|
||||
TradePriceCalculatorHelper.addPromotion(result, orderItem,
|
||||
level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(),
|
||||
String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)),
|
||||
vipPrice);
|
||||
// 更新 SKU 的优惠金额
|
||||
orderItem.setVipPrice(vipPrice);
|
||||
}
|
||||
// 3.2 更新 SKU 优惠金额
|
||||
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + newDiscountPrice);
|
||||
|
||||
// 4. 分摊优惠
|
||||
TradePriceCalculatorHelper.recountPayPrice(orderItem);
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
});
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
}
|
||||
|
||||
private Integer calculatePayPrice(DiscountProductRespDTO discountProduct,
|
||||
|
@ -1,88 +1,88 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
|
||||
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper.formatPrice;
|
||||
|
||||
/**
|
||||
* 会员 VIP 折扣的 {@link TradePriceCalculator} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Order(TradePriceCalculator.ORDER_MEMBER_LEVEL)
|
||||
public class TradeMemberLevelPriceCalculator implements TradePriceCalculator {
|
||||
|
||||
@Resource
|
||||
private MemberLevelApi memberLevelApi;
|
||||
@Resource
|
||||
private MemberUserApi memberUserApi;
|
||||
|
||||
@Override
|
||||
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||
// 0. 只有【普通】订单,才计算该优惠
|
||||
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
|
||||
return;
|
||||
}
|
||||
// 1. 获得用户的会员等级
|
||||
MemberUserRespDTO user = memberUserApi.getUser(param.getUserId());
|
||||
if (user.getLevelId() == null || user.getLevelId() <= 0) {
|
||||
return;
|
||||
}
|
||||
MemberLevelRespDTO level = memberLevelApi.getMemberLevel(user.getLevelId());
|
||||
if (level == null || level.getDiscountPercent() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 计算每个 SKU 的优惠金额
|
||||
result.getItems().forEach(orderItem -> {
|
||||
// 2.1 计算优惠金额
|
||||
Integer vipPrice = calculateVipPrice(orderItem.getPayPrice(), level.getDiscountPercent());
|
||||
if (vipPrice <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2.2 记录优惠明细
|
||||
if (orderItem.getSelected()) {
|
||||
// 注意,只有在选中的情况下,才会记录到优惠明细。否则仅仅是更新 SKU 优惠金额,用于展示
|
||||
TradePriceCalculatorHelper.addPromotion(result, orderItem,
|
||||
level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(),
|
||||
String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)),
|
||||
vipPrice);
|
||||
}
|
||||
|
||||
// 2.3 更新 SKU 的优惠金额
|
||||
orderItem.setVipPrice(vipPrice);
|
||||
TradePriceCalculatorHelper.recountPayPrice(orderItem);
|
||||
});
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算会员 VIP 优惠价格
|
||||
*
|
||||
* @param price 原价
|
||||
* @param discountPercent 折扣
|
||||
* @return 优惠价格
|
||||
*/
|
||||
public Integer calculateVipPrice(Integer price, Integer discountPercent) {
|
||||
if (discountPercent == null) {
|
||||
return 0;
|
||||
}
|
||||
Integer newPrice = price * discountPercent / 100;
|
||||
return price - newPrice;
|
||||
}
|
||||
|
||||
}
|
||||
//package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
//
|
||||
//import cn.hutool.core.util.ObjectUtil;
|
||||
//import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
|
||||
//import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
|
||||
//import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
//import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
//import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
//import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
//import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
//import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
//import org.springframework.core.annotation.Order;
|
||||
//import org.springframework.stereotype.Component;
|
||||
//
|
||||
//import javax.annotation.Resource;
|
||||
//
|
||||
//import static cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper.formatPrice;
|
||||
//
|
||||
///**
|
||||
// * 会员 VIP 折扣的 {@link TradePriceCalculator} 实现类
|
||||
// *
|
||||
// * @author 芋道源码
|
||||
// */
|
||||
//@Component
|
||||
//@Order(TradePriceCalculator.ORDER_MEMBER_LEVEL)
|
||||
//public class TradeMemberLevelPriceCalculator implements TradePriceCalculator {
|
||||
//
|
||||
// @Resource
|
||||
// private MemberLevelApi memberLevelApi;
|
||||
// @Resource
|
||||
// private MemberUserApi memberUserApi;
|
||||
//
|
||||
// @Override
|
||||
// public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||
// // 0. 只有【普通】订单,才计算该优惠
|
||||
// if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
|
||||
// return;
|
||||
// }
|
||||
// // 1. 获得用户的会员等级
|
||||
// MemberUserRespDTO user = memberUserApi.getUser(param.getUserId());
|
||||
// if (user.getLevelId() == null || user.getLevelId() <= 0) {
|
||||
// return;
|
||||
// }
|
||||
// MemberLevelRespDTO level = memberLevelApi.getMemberLevel(user.getLevelId());
|
||||
// if (level == null || level.getDiscountPercent() == null) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 2. 计算每个 SKU 的优惠金额
|
||||
// result.getItems().forEach(orderItem -> {
|
||||
// // 2.1 计算优惠金额
|
||||
// Integer vipPrice = calculateVipPrice(orderItem.getPayPrice(), level.getDiscountPercent());
|
||||
// if (vipPrice <= 0) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 2.2 记录优惠明细
|
||||
// if (orderItem.getSelected()) {
|
||||
// // 注意,只有在选中的情况下,才会记录到优惠明细。否则仅仅是更新 SKU 优惠金额,用于展示
|
||||
// TradePriceCalculatorHelper.addPromotion(result, orderItem,
|
||||
// level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(),
|
||||
// String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)),
|
||||
// vipPrice);
|
||||
// }
|
||||
//
|
||||
// // 2.3 更新 SKU 的优惠金额
|
||||
// orderItem.setVipPrice(vipPrice);
|
||||
// TradePriceCalculatorHelper.recountPayPrice(orderItem);
|
||||
// });
|
||||
// TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 计算会员 VIP 优惠价格
|
||||
// *
|
||||
// * @param price 原价
|
||||
// * @param discountPercent 折扣
|
||||
// * @return 优惠价格
|
||||
// */
|
||||
// public Integer calculateVipPrice(Integer price, Integer discountPercent) {
|
||||
// if (discountPercent == null) {
|
||||
// return 0;
|
||||
// }
|
||||
// Integer newPrice = price * discountPercent / 100;
|
||||
// return price - newPrice;
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
@ -0,0 +1,95 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.point.PointActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.point.dto.PointValidateJoinRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_POINT_TOTAL_LIMIT_COUNT;
|
||||
|
||||
/**
|
||||
* 积分商城的 {@link TradePriceCalculator} 实现类
|
||||
*
|
||||
* @author owen
|
||||
*/
|
||||
@Component
|
||||
@Order(TradePriceCalculator.ORDER_POINT_ACTIVITY)
|
||||
@Slf4j
|
||||
public class TradePointActivityPriceCalculator implements TradePriceCalculator {
|
||||
|
||||
@Resource
|
||||
private PointActivityApi pointActivityApi;
|
||||
@Resource
|
||||
private MemberUserApi memberUserApi;
|
||||
|
||||
@Resource
|
||||
private TradeOrderQueryService tradeOrderQueryService;
|
||||
|
||||
@Override
|
||||
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||
// 1.1 判断订单类型是否为积分商城活动
|
||||
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.POINT.getType())) {
|
||||
return;
|
||||
}
|
||||
// 1.2 初始化积分
|
||||
MemberUserRespDTO user = memberUserApi.getUser(param.getUserId());
|
||||
result.setTotalPoint(user.getPoint()).setUsePoint(0);
|
||||
|
||||
// 1.3 校验用户积分余额
|
||||
if (user.getPoint() == null || user.getPoint() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.isTrue(param.getItems().size() == 1, "积分商城兑换商品时,只允许选择一个商品");
|
||||
// 2. 校验是否可以参与积分商城活动
|
||||
TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0);
|
||||
PointValidateJoinRespDTO activity = validateJoinSeckill(
|
||||
param.getUserId(), param.getPointActivityId(),
|
||||
orderItem.getSkuId(), orderItem.getCount());
|
||||
|
||||
// 3.1 记录优惠明细
|
||||
int discountPrice = orderItem.getPayPrice(); // 情况一:单使用积分兑换
|
||||
Assert.isTrue(activity.getPoint() >= 1, "积分商城商品兑换积分必须大于 1");
|
||||
result.setUsePoint(activity.getPoint() * orderItem.getCount());
|
||||
orderItem.setUsePoint(activity.getPoint() * orderItem.getCount());
|
||||
if (activity.getPrice() != null && activity.getPrice() > 0) { // 情况二:积分 + 金额
|
||||
discountPrice = orderItem.getPayPrice() - activity.getPrice() * orderItem.getCount();
|
||||
}
|
||||
// 3.2 记录优惠明细
|
||||
TradePriceCalculatorHelper.addPromotion(result, orderItem,
|
||||
param.getPointActivityId(), "积分商城活动", PromotionTypeEnum.POINT.getType(),
|
||||
StrUtil.format("积分商城活动:省 {} 元", TradePriceCalculatorHelper.formatPrice(discountPrice)),
|
||||
discountPrice);
|
||||
|
||||
// 3.3 更新 SKU 优惠金额
|
||||
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + discountPrice);
|
||||
TradePriceCalculatorHelper.recountPayPrice(orderItem);
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
}
|
||||
|
||||
private PointValidateJoinRespDTO validateJoinSeckill(Long userId, Long activityId, Long skuId, Integer count) {
|
||||
// 1. 校验是否可以参与积分商城活动
|
||||
PointValidateJoinRespDTO pointValidateJoinRespDTO = pointActivityApi.validateJoinPointActivity(activityId, skuId, count);
|
||||
// 2. 校验总限购数量,目前只有 trade 有具体下单的数据,需要交给 trade 价格计算使用
|
||||
int activityProductCount = tradeOrderQueryService.getSeckillProductCount(userId, activityId);
|
||||
if (activityProductCount + count > pointValidateJoinRespDTO.getCount()) {
|
||||
throw exception(PRICE_CALCULATE_POINT_TOTAL_LIMIT_COUNT);
|
||||
}
|
||||
return pointValidateJoinRespDTO;
|
||||
}
|
||||
|
||||
}
|
@ -1,15 +1,14 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
|
||||
import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
|
||||
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -17,8 +16,6 @@ import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
@ -39,58 +36,33 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
|
||||
private MemberConfigApi memberConfigApi;
|
||||
@Resource
|
||||
private MemberUserApi memberUserApi;
|
||||
@Resource
|
||||
private ProductSpuApi productSpuApi;
|
||||
|
||||
@Override
|
||||
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||
// 默认使用积分为 0
|
||||
result.setUsePoint(0);
|
||||
// 判断订单类型是否不为积分商城活动
|
||||
if (ObjectUtil.equal(result.getType(), TradeOrderTypeEnum.POINT.getType())) {
|
||||
return;
|
||||
}
|
||||
// 0. 初始化积分
|
||||
MemberUserRespDTO user = memberUserApi.getUser(param.getUserId());
|
||||
result.setTotalPoint(user.getPoint()).setUsePoint(0);
|
||||
|
||||
// 1.1 校验是否使用积分
|
||||
if (!BooleanUtil.isTrue(param.getPointStatus())) {
|
||||
result.setUsePoint(0);
|
||||
return;
|
||||
}
|
||||
// 1.2 校验积分抵扣是否开启
|
||||
MemberConfigRespDTO config = memberConfigApi.getConfig();
|
||||
//全局
|
||||
if (!isDeductPointEnable(config)) {
|
||||
return;
|
||||
}
|
||||
//部分商品
|
||||
ArrayList<Long> list = new ArrayList<>();
|
||||
for (TradePriceCalculateRespBO.OrderItem item : result.getItems()) {
|
||||
list.add(item.getSpuId());
|
||||
}
|
||||
List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(list);
|
||||
int status = 0;
|
||||
int usePointMax = 0;
|
||||
for (ProductSpuRespDTO respDTO : spuList) {
|
||||
usePointMax += respDTO.getUsePointsMax();
|
||||
if (respDTO.getPointsStatus() == 1){
|
||||
status = 1;
|
||||
}
|
||||
}
|
||||
if (status == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.3 校验用户积分余额
|
||||
MemberUserRespDTO user = memberUserApi.getUser(param.getUserId());
|
||||
if (user.getPoint() == null || user.getPoint() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int calculatePointPricePar = 0;
|
||||
if (user.getPoint() >= usePointMax){
|
||||
calculatePointPricePar = usePointMax;
|
||||
}else {
|
||||
calculatePointPricePar = user.getPoint();
|
||||
}
|
||||
|
||||
|
||||
// 2.1 计算积分优惠金额
|
||||
int pointPrice = calculatePointPrice(config, calculatePointPricePar, result);
|
||||
int pointPrice = calculatePointPrice(config, user.getPoint(), result);
|
||||
// 2.2 计算分摊的积分、抵扣金额
|
||||
List<TradePriceCalculateRespBO.OrderItem> orderItems = filterList(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSelected);
|
||||
List<Integer> dividePointPrices = TradePriceCalculatorHelper.dividePrice(orderItems, pointPrice);
|
||||
@ -125,18 +97,18 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
|
||||
// TODO @疯狂:这里应该是,抵扣到只剩下 0.01;
|
||||
// 积分优惠金额(分)
|
||||
int pointPrice = usePoint * config.getPointTradeDeductUnitPrice();
|
||||
// if (result.getPrice().getPayPrice() <= pointPrice) {
|
||||
// // 禁止 0 元购
|
||||
// throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
|
||||
// }
|
||||
// // 允许0 元购!!!:用户积分比较多时,积分可以抵扣的金额要大于支付金额,这时需要根据支付金额反推使用多少积分
|
||||
if (result.getPrice().getPayPrice() <= pointPrice) {
|
||||
pointPrice = result.getPrice().getPayPrice() - 1;
|
||||
// 反推需要扣除的积分
|
||||
usePoint = NumberUtil.toBigDecimal(pointPrice)
|
||||
.divide(NumberUtil.toBigDecimal(config.getPointTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP)
|
||||
.intValue();
|
||||
// 禁止 0 元购
|
||||
throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
|
||||
}
|
||||
// // 允许0 元购!!!:用户积分比较多时,积分可以抵扣的金额要大于支付金额,这时需要根据支付金额反推使用多少积分
|
||||
// if (result.getPrice().getPayPrice() < pointPrice) {
|
||||
// pointPrice = result.getPrice().getPayPrice();
|
||||
// // 反推需要扣除的积分
|
||||
// usePoint = NumberUtil.toBigDecimal(pointPrice)
|
||||
// .divide(NumberUtil.toBigDecimal(config.getPointTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP)
|
||||
// .intValue();
|
||||
// }
|
||||
// 记录使用的积分
|
||||
result.setUsePoint(usePoint);
|
||||
return pointPrice;
|
||||
|
@ -18,6 +18,7 @@ public interface TradePriceCalculator {
|
||||
int ORDER_SECKILL_ACTIVITY = 8;
|
||||
int ORDER_BARGAIN_ACTIVITY = 8;
|
||||
int ORDER_COMBINATION_ACTIVITY = 8;
|
||||
int ORDER_POINT_ACTIVITY = 8;
|
||||
|
||||
int ORDER_DISCOUNT_ACTIVITY = 10;
|
||||
int ORDER_REWARD_ACTIVITY = 20;
|
||||
|
@ -1,18 +1,17 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
|
||||
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityDTO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -28,15 +27,12 @@ import static java.util.Collections.singletonList;
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class TradePriceCalculatorHelper {
|
||||
@Resource
|
||||
private static RewardActivityApi rewardActivityApi;
|
||||
|
||||
public static TradePriceCalculateRespBO buildCalculateResp(TradePriceCalculateReqBO param,
|
||||
List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList) {
|
||||
// 创建 PriceCalculateRespDTO 对象
|
||||
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO();
|
||||
result.setType(getOrderType(param));
|
||||
result.setPromotions(new ArrayList<>());
|
||||
result.setType(getOrderType(param)).setPromotions(new ArrayList<>()).setGiveCouponTemplateCounts(new LinkedHashMap<>());
|
||||
|
||||
// 创建它的 OrderItem 属性
|
||||
result.setItems(new ArrayList<>(param.getItems().size()));
|
||||
@ -64,9 +60,9 @@ public class TradePriceCalculatorHelper {
|
||||
.setWeight(sku.getWeight()).setVolume(sku.getVolume());
|
||||
// spu 信息
|
||||
orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId())
|
||||
.setDeliveryTemplateId(spu.getDeliveryTemplateId())
|
||||
.setDeliveryTypes(spu.getDeliveryTypes()).setDeliveryTemplateId(spu.getDeliveryTemplateId())
|
||||
.setGivePoint(spu.getGiveIntegral()).setUsePoint(0);
|
||||
if (orderItem.getPicUrl() == null) {
|
||||
if (StrUtil.isBlank(orderItem.getPicUrl())) {
|
||||
orderItem.setPicUrl(spu.getPicUrl());
|
||||
}
|
||||
});
|
||||
@ -75,30 +71,6 @@ public class TradePriceCalculatorHelper {
|
||||
result.setPrice(new TradePriceCalculateRespBO.Price());
|
||||
recountAllPrice(result);
|
||||
recountAllGivePoint(result);
|
||||
// param.setRewardId(1L);
|
||||
|
||||
//满减送活动优惠价格计算
|
||||
// if (param.getRewardId() != null){
|
||||
// RewardActivityDTO activityDTO = rewardActivityApi.getRewardActivityById(param.getRewardId());
|
||||
// TradePriceCalculateRespBO.Price price = result.getPrice();
|
||||
//// List<TradePriceCalculateRespBO.OrderItem> items = result.getItems();
|
||||
// int limitPrice = 0;
|
||||
// int discountPrice = 0;
|
||||
// Boolean deliveryPrice = null;
|
||||
// for (RewardActivityDTO.Rule rule : activityDTO.getRules()) {
|
||||
// if (price.getTotalPrice() >= rule.getLimit() && rule.getLimit() > limitPrice){
|
||||
// limitPrice = rule.getLimit();
|
||||
// discountPrice = rule.getDiscountPrice();
|
||||
// deliveryPrice = rule.getFreeDelivery();
|
||||
// }
|
||||
// }
|
||||
// price.setDiscountPrice(price.getDiscountPrice() + discountPrice);
|
||||
// if (deliveryPrice != null && deliveryPrice){
|
||||
// price.setDeliveryPrice(0);
|
||||
// }
|
||||
// price.setPayPrice(price.getPayPrice() - discountPrice);
|
||||
// }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -118,6 +90,9 @@ public class TradePriceCalculatorHelper {
|
||||
if (param.getBargainRecordId() != null) {
|
||||
return TradeOrderTypeEnum.BARGAIN.getType();
|
||||
}
|
||||
if (param.getPointActivityId() != null) {
|
||||
return TradeOrderTypeEnum.POINT.getType();
|
||||
}
|
||||
return TradeOrderTypeEnum.NORMAL.getType();
|
||||
}
|
||||
|
||||
@ -269,7 +244,7 @@ public class TradePriceCalculatorHelper {
|
||||
*
|
||||
* 和 {@link #dividePrice(List, Integer)} 逻辑一致,只是传入的是 TradeOrderItemDO 对象
|
||||
*
|
||||
* @param items 订单项
|
||||
* @param items 订单项
|
||||
* @param price 订单支付金额
|
||||
* @return 分摊金额数组,和传入的 orderItems 一一对应
|
||||
*/
|
||||
|
@ -44,9 +44,10 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
|
||||
if (CollUtil.isEmpty(rewardActivities)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理每个满减送活动
|
||||
rewardActivities.forEach(rewardActivity -> calculate(param, result, rewardActivity));
|
||||
// 处理最新的满减送活动
|
||||
if (!rewardActivities.isEmpty()) {
|
||||
calculate(param, result, rewardActivities.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
private void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result,
|
||||
|
@ -17,6 +17,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT;
|
||||
|
||||
// TODO huihui:单测需要补充
|
||||
|
||||
/**
|
||||
* 秒杀活动的 {@link TradePriceCalculator} 实现类
|
||||
*
|
||||
|
@ -1,137 +1,137 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
|
||||
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
|
||||
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
|
||||
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
|
||||
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link TradePriceServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Disabled // TODO 芋艿:后续 fix 补充的单测
|
||||
public class TradePriceServiceImplTest extends BaseMockitoUnitTest {
|
||||
|
||||
@InjectMocks
|
||||
private TradePriceServiceImpl tradePriceService;
|
||||
|
||||
@Mock
|
||||
private ProductSkuApi productSkuApi;
|
||||
@Mock
|
||||
private ProductSpuApi productSpuApi;
|
||||
@Mock
|
||||
private List<TradePriceCalculator> priceCalculators;
|
||||
|
||||
@Test
|
||||
public void testCalculatePrice() {
|
||||
// 准备参数
|
||||
TradePriceCalculateReqBO calculateReqBO = new TradePriceCalculateReqBO()
|
||||
.setUserId(10L)
|
||||
.setCouponId(20L).setAddressId(30L)
|
||||
.setItems(Arrays.asList(
|
||||
new TradePriceCalculateReqBO.Item().setSkuId(100L).setCount(1).setSelected(true),
|
||||
new TradePriceCalculateReqBO.Item().setSkuId(200L).setCount(3).setSelected(true),
|
||||
new TradePriceCalculateReqBO.Item().setSkuId(300L).setCount(6).setCartId(233L).setSelected(false)
|
||||
));
|
||||
// mock 方法
|
||||
List<ProductSkuRespDTO> skuList = Arrays.asList(
|
||||
new ProductSkuRespDTO().setId(100L).setStock(500).setPrice(1000).setPicUrl("https://t.cn/1.png").setSpuId(1001L)
|
||||
.setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(1L).setPropertyName("颜色")
|
||||
.setValueId(2L).setValueName("红色"))),
|
||||
new ProductSkuRespDTO().setId(200L).setStock(400).setPrice(2000).setPicUrl("https://t.cn/2.png").setSpuId(1001L)
|
||||
.setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(1L).setPropertyName("颜色")
|
||||
.setValueId(3L).setValueName("黄色"))),
|
||||
new ProductSkuRespDTO().setId(300L).setStock(600).setPrice(3000).setPicUrl("https://t.cn/3.png").setSpuId(1001L)
|
||||
.setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(1L).setPropertyName("颜色")
|
||||
.setValueId(4L).setValueName("黑色")))
|
||||
);
|
||||
when(productSkuApi.getSkuList(Mockito.eq(asSet(100L, 200L, 300L)))).thenReturn(skuList);
|
||||
when(productSpuApi.getSpuList(Mockito.eq(asSet(1001L))))
|
||||
.thenReturn(singletonList(new ProductSpuRespDTO().setId(1001L).setName("小菜").setCategoryId(666L)
|
||||
.setStatus(ProductSpuStatusEnum.ENABLE.getStatus())));
|
||||
|
||||
// 调用
|
||||
TradePriceCalculateRespBO calculateRespBO = tradePriceService.calculatePrice(calculateReqBO);
|
||||
// 断言
|
||||
assertEquals(TradeOrderTypeEnum.NORMAL.getType(), calculateRespBO.getType());
|
||||
assertEquals(0, calculateRespBO.getPromotions().size());
|
||||
assertNull(calculateRespBO.getCouponId());
|
||||
// 断言:订单价格
|
||||
assertEquals(7000, calculateRespBO.getPrice().getTotalPrice());
|
||||
assertEquals(0, calculateRespBO.getPrice().getDiscountPrice());
|
||||
assertEquals(0, calculateRespBO.getPrice().getDeliveryPrice());
|
||||
assertEquals(0, calculateRespBO.getPrice().getCouponPrice());
|
||||
assertEquals(0, calculateRespBO.getPrice().getPointPrice());
|
||||
assertEquals(7000, calculateRespBO.getPrice().getPayPrice());
|
||||
// 断言:SKU 1
|
||||
assertEquals(1001L, calculateRespBO.getItems().get(0).getSpuId());
|
||||
assertEquals(100L, calculateRespBO.getItems().get(0).getSkuId());
|
||||
assertEquals(1, calculateRespBO.getItems().get(0).getCount());
|
||||
assertNull(calculateRespBO.getItems().get(0).getCartId());
|
||||
assertTrue(calculateRespBO.getItems().get(0).getSelected());
|
||||
assertEquals(1000, calculateRespBO.getItems().get(0).getPrice());
|
||||
assertEquals(0, calculateRespBO.getItems().get(0).getDiscountPrice());
|
||||
assertEquals(0, calculateRespBO.getItems().get(0).getDeliveryPrice());
|
||||
assertEquals(0, calculateRespBO.getItems().get(0).getCouponPrice());
|
||||
assertEquals(0, calculateRespBO.getItems().get(0).getPointPrice());
|
||||
assertEquals(1000, calculateRespBO.getItems().get(0).getPayPrice());
|
||||
assertEquals("小菜", calculateRespBO.getItems().get(0).getSpuName());
|
||||
assertEquals("https://t.cn/1.png", calculateRespBO.getItems().get(0).getPicUrl());
|
||||
assertEquals(666L, calculateRespBO.getItems().get(0).getCategoryId());
|
||||
assertEquals(skuList.get(0).getProperties(), calculateRespBO.getItems().get(0).getProperties());
|
||||
// 断言:SKU 2
|
||||
assertEquals(1001L, calculateRespBO.getItems().get(1).getSpuId());
|
||||
assertEquals(200L, calculateRespBO.getItems().get(1).getSkuId());
|
||||
assertEquals(3, calculateRespBO.getItems().get(1).getCount());
|
||||
assertNull(calculateRespBO.getItems().get(1).getCartId());
|
||||
assertTrue(calculateRespBO.getItems().get(1).getSelected());
|
||||
assertEquals(2000, calculateRespBO.getItems().get(1).getPrice());
|
||||
assertEquals(0, calculateRespBO.getItems().get(1).getDiscountPrice());
|
||||
assertEquals(0, calculateRespBO.getItems().get(1).getDeliveryPrice());
|
||||
assertEquals(0, calculateRespBO.getItems().get(1).getCouponPrice());
|
||||
assertEquals(0, calculateRespBO.getItems().get(1).getPointPrice());
|
||||
assertEquals(6000, calculateRespBO.getItems().get(1).getPayPrice());
|
||||
assertEquals("小菜", calculateRespBO.getItems().get(1).getSpuName());
|
||||
assertEquals("https://t.cn/2.png", calculateRespBO.getItems().get(1).getPicUrl());
|
||||
assertEquals(666L, calculateRespBO.getItems().get(1).getCategoryId());
|
||||
assertEquals(skuList.get(1).getProperties(), calculateRespBO.getItems().get(1).getProperties());
|
||||
// 断言:SKU 3
|
||||
assertEquals(1001L, calculateRespBO.getItems().get(2).getSpuId());
|
||||
assertEquals(300L, calculateRespBO.getItems().get(2).getSkuId());
|
||||
assertEquals(6, calculateRespBO.getItems().get(2).getCount());
|
||||
assertEquals(233L, calculateRespBO.getItems().get(2).getCartId());
|
||||
assertFalse(calculateRespBO.getItems().get(2).getSelected());
|
||||
assertEquals(3000, calculateRespBO.getItems().get(2).getPrice());
|
||||
assertEquals(0, calculateRespBO.getItems().get(2).getDiscountPrice());
|
||||
assertEquals(0, calculateRespBO.getItems().get(2).getDeliveryPrice());
|
||||
assertEquals(0, calculateRespBO.getItems().get(2).getCouponPrice());
|
||||
assertEquals(0, calculateRespBO.getItems().get(2).getPointPrice());
|
||||
assertEquals(18000, calculateRespBO.getItems().get(2).getPayPrice());
|
||||
assertEquals("小菜", calculateRespBO.getItems().get(2).getSpuName());
|
||||
assertEquals("https://t.cn/3.png", calculateRespBO.getItems().get(2).getPicUrl());
|
||||
assertEquals(666L, calculateRespBO.getItems().get(2).getCategoryId());
|
||||
assertEquals(skuList.get(2).getProperties(), calculateRespBO.getItems().get(2).getProperties());
|
||||
}
|
||||
|
||||
}
|
||||
//package cn.iocoder.yudao.module.trade.service.price;
|
||||
//
|
||||
//import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
//import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
|
||||
//import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
|
||||
//import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
|
||||
//import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
|
||||
//import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
|
||||
//import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
|
||||
//import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
//import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
//import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
//import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator;
|
||||
//import org.junit.jupiter.api.Disabled;
|
||||
//import org.junit.jupiter.api.Test;
|
||||
//import org.mockito.InjectMocks;
|
||||
//import org.mockito.Mock;
|
||||
//import org.mockito.Mockito;
|
||||
//
|
||||
//import java.util.Arrays;
|
||||
//import java.util.List;
|
||||
//
|
||||
//import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
|
||||
//import static java.util.Collections.singletonList;
|
||||
//import static org.junit.jupiter.api.Assertions.*;
|
||||
//import static org.mockito.Mockito.when;
|
||||
//
|
||||
///**
|
||||
// * {@link TradePriceServiceImpl} 的单元测试
|
||||
// *
|
||||
// * @author 芋道源码
|
||||
// */
|
||||
//@Disabled // TODO 芋艿:后续 fix 补充的单测
|
||||
//public class TradePriceServiceImplTest extends BaseMockitoUnitTest {
|
||||
//
|
||||
// @InjectMocks
|
||||
// private TradePriceServiceImpl tradePriceService;
|
||||
//
|
||||
// @Mock
|
||||
// private ProductSkuApi productSkuApi;
|
||||
// @Mock
|
||||
// private ProductSpuApi productSpuApi;
|
||||
// @Mock
|
||||
// private List<TradePriceCalculator> priceCalculators;
|
||||
//
|
||||
// @Test
|
||||
// public void testCalculatePrice() {
|
||||
// // 准备参数
|
||||
// TradePriceCalculateReqBO calculateReqBO = new TradePriceCalculateReqBO()
|
||||
// .setUserId(10L)
|
||||
// .setCouponId(20L).setAddressId(30L)
|
||||
// .setItems(Arrays.asList(
|
||||
// new TradePriceCalculateReqBO.Item().setSkuId(100L).setCount(1).setSelected(true),
|
||||
// new TradePriceCalculateReqBO.Item().setSkuId(200L).setCount(3).setSelected(true),
|
||||
// new TradePriceCalculateReqBO.Item().setSkuId(300L).setCount(6).setCartId(233L).setSelected(false)
|
||||
// ));
|
||||
// // mock 方法
|
||||
// List<ProductSkuRespDTO> skuList = Arrays.asList(
|
||||
// new ProductSkuRespDTO().setId(100L).setStock(500).setPrice(1000).setPicUrl("https://t.cn/1.png").setSpuId(1001L)
|
||||
// .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(1L).setPropertyName("颜色")
|
||||
// .setValueId(2L).setValueName("红色"))),
|
||||
// new ProductSkuRespDTO().setId(200L).setStock(400).setPrice(2000).setPicUrl("https://t.cn/2.png").setSpuId(1001L)
|
||||
// .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(1L).setPropertyName("颜色")
|
||||
// .setValueId(3L).setValueName("黄色"))),
|
||||
// new ProductSkuRespDTO().setId(300L).setStock(600).setPrice(3000).setPicUrl("https://t.cn/3.png").setSpuId(1001L)
|
||||
// .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(1L).setPropertyName("颜色")
|
||||
// .setValueId(4L).setValueName("黑色")))
|
||||
// );
|
||||
// when(productSkuApi.getSkuList(Mockito.eq(asSet(100L, 200L, 300L)))).thenReturn(skuList);
|
||||
// when(productSpuApi.getSpuList(Mockito.eq(asSet(1001L))))
|
||||
// .thenReturn(singletonList(new ProductSpuRespDTO().setId(1001L).setName("小菜").setCategoryId(666L)
|
||||
// .setStatus(ProductSpuStatusEnum.ENABLE.getStatus())));
|
||||
//
|
||||
// // 调用
|
||||
// TradePriceCalculateRespBO calculateRespBO = tradePriceService.calculatePrice(calculateReqBO);
|
||||
// // 断言
|
||||
// assertEquals(TradeOrderTypeEnum.NORMAL.getType(), calculateRespBO.getType());
|
||||
// assertEquals(0, calculateRespBO.getPromotions().size());
|
||||
// assertNull(calculateRespBO.getCouponId());
|
||||
// // 断言:订单价格
|
||||
// assertEquals(7000, calculateRespBO.getPrice().getTotalPrice());
|
||||
// assertEquals(0, calculateRespBO.getPrice().getDiscountPrice());
|
||||
// assertEquals(0, calculateRespBO.getPrice().getDeliveryPrice());
|
||||
// assertEquals(0, calculateRespBO.getPrice().getCouponPrice());
|
||||
// assertEquals(0, calculateRespBO.getPrice().getPointPrice());
|
||||
// assertEquals(7000, calculateRespBO.getPrice().getPayPrice());
|
||||
// // 断言:SKU 1
|
||||
// assertEquals(1001L, calculateRespBO.getItems().get(0).getSpuId());
|
||||
// assertEquals(100L, calculateRespBO.getItems().get(0).getSkuId());
|
||||
// assertEquals(1, calculateRespBO.getItems().get(0).getCount());
|
||||
// assertNull(calculateRespBO.getItems().get(0).getCartId());
|
||||
// assertTrue(calculateRespBO.getItems().get(0).getSelected());
|
||||
// assertEquals(1000, calculateRespBO.getItems().get(0).getPrice());
|
||||
// assertEquals(0, calculateRespBO.getItems().get(0).getDiscountPrice());
|
||||
// assertEquals(0, calculateRespBO.getItems().get(0).getDeliveryPrice());
|
||||
// assertEquals(0, calculateRespBO.getItems().get(0).getCouponPrice());
|
||||
// assertEquals(0, calculateRespBO.getItems().get(0).getPointPrice());
|
||||
// assertEquals(1000, calculateRespBO.getItems().get(0).getPayPrice());
|
||||
// assertEquals("小菜", calculateRespBO.getItems().get(0).getSpuName());
|
||||
// assertEquals("https://t.cn/1.png", calculateRespBO.getItems().get(0).getPicUrl());
|
||||
// assertEquals(666L, calculateRespBO.getItems().get(0).getCategoryId());
|
||||
// assertEquals(skuList.get(0).getProperties(), calculateRespBO.getItems().get(0).getProperties());
|
||||
// // 断言:SKU 2
|
||||
// assertEquals(1001L, calculateRespBO.getItems().get(1).getSpuId());
|
||||
// assertEquals(200L, calculateRespBO.getItems().get(1).getSkuId());
|
||||
// assertEquals(3, calculateRespBO.getItems().get(1).getCount());
|
||||
// assertNull(calculateRespBO.getItems().get(1).getCartId());
|
||||
// assertTrue(calculateRespBO.getItems().get(1).getSelected());
|
||||
// assertEquals(2000, calculateRespBO.getItems().get(1).getPrice());
|
||||
// assertEquals(0, calculateRespBO.getItems().get(1).getDiscountPrice());
|
||||
// assertEquals(0, calculateRespBO.getItems().get(1).getDeliveryPrice());
|
||||
// assertEquals(0, calculateRespBO.getItems().get(1).getCouponPrice());
|
||||
// assertEquals(0, calculateRespBO.getItems().get(1).getPointPrice());
|
||||
// assertEquals(6000, calculateRespBO.getItems().get(1).getPayPrice());
|
||||
// assertEquals("小菜", calculateRespBO.getItems().get(1).getSpuName());
|
||||
// assertEquals("https://t.cn/2.png", calculateRespBO.getItems().get(1).getPicUrl());
|
||||
// assertEquals(666L, calculateRespBO.getItems().get(1).getCategoryId());
|
||||
// assertEquals(skuList.get(1).getProperties(), calculateRespBO.getItems().get(1).getProperties());
|
||||
// // 断言:SKU 3
|
||||
// assertEquals(1001L, calculateRespBO.getItems().get(2).getSpuId());
|
||||
// assertEquals(300L, calculateRespBO.getItems().get(2).getSkuId());
|
||||
// assertEquals(6, calculateRespBO.getItems().get(2).getCount());
|
||||
// assertEquals(233L, calculateRespBO.getItems().get(2).getCartId());
|
||||
// assertFalse(calculateRespBO.getItems().get(2).getSelected());
|
||||
// assertEquals(3000, calculateRespBO.getItems().get(2).getPrice());
|
||||
// assertEquals(0, calculateRespBO.getItems().get(2).getDiscountPrice());
|
||||
// assertEquals(0, calculateRespBO.getItems().get(2).getDeliveryPrice());
|
||||
// assertEquals(0, calculateRespBO.getItems().get(2).getCouponPrice());
|
||||
// assertEquals(0, calculateRespBO.getItems().get(2).getPointPrice());
|
||||
// assertEquals(18000, calculateRespBO.getItems().get(2).getPayPrice());
|
||||
// assertEquals("小菜", calculateRespBO.getItems().get(2).getSpuName());
|
||||
// assertEquals("https://t.cn/3.png", calculateRespBO.getItems().get(2).getPicUrl());
|
||||
// assertEquals(666L, calculateRespBO.getItems().get(2).getCategoryId());
|
||||
// assertEquals(skuList.get(2).getProperties(), calculateRespBO.getItems().get(2).getProperties());
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
@ -1,118 +1,118 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
|
||||
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link TradeMemberLevelPriceCalculator} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class TradeMemberLevelPriceCalculatorTest extends BaseMockitoUnitTest {
|
||||
|
||||
@InjectMocks
|
||||
private TradeMemberLevelPriceCalculator memberLevelPriceCalculator;
|
||||
|
||||
@Mock
|
||||
private MemberLevelApi memberLevelApi;
|
||||
@Mock
|
||||
private MemberUserApi memberUserApi;
|
||||
|
||||
@Test
|
||||
public void testCalculate() {
|
||||
// 准备参数
|
||||
TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
|
||||
.setUserId(1024L)
|
||||
.setItems(asList(
|
||||
new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 匹配活动,且已选中
|
||||
new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(false) // 匹配活动,但未选中
|
||||
));
|
||||
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
|
||||
.setType(TradeOrderTypeEnum.NORMAL.getType())
|
||||
.setPrice(new TradePriceCalculateRespBO.Price())
|
||||
.setPromotions(new ArrayList<>())
|
||||
.setItems(asList(
|
||||
new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
|
||||
.setPrice(100),
|
||||
new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(false)
|
||||
.setPrice(50)
|
||||
));
|
||||
// 保证价格被初始化上
|
||||
TradePriceCalculatorHelper.recountPayPrice(result.getItems());
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
|
||||
// mock 方法(会员等级)
|
||||
when(memberUserApi.getUser(eq(1024L))).thenReturn(new MemberUserRespDTO().setLevelId(2048L));
|
||||
when(memberLevelApi.getMemberLevel(eq(2048L))).thenReturn(
|
||||
new MemberLevelRespDTO().setId(2048L).setName("VIP 会员").setDiscountPercent(60));
|
||||
|
||||
// 调用
|
||||
memberLevelPriceCalculator.calculate(param, result);
|
||||
// 断言:Price 部分
|
||||
TradePriceCalculateRespBO.Price price = result.getPrice();
|
||||
assertEquals(price.getTotalPrice(), 200);
|
||||
assertEquals(price.getDiscountPrice(), 0);
|
||||
assertEquals(price.getPointPrice(), 0);
|
||||
assertEquals(price.getDeliveryPrice(), 0);
|
||||
assertEquals(price.getCouponPrice(), 0);
|
||||
assertEquals(price.getVipPrice(), 80);
|
||||
assertEquals(price.getPayPrice(), 120);
|
||||
assertNull(result.getCouponId());
|
||||
// 断言:SKU 1
|
||||
assertEquals(result.getItems().size(), 2);
|
||||
TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
|
||||
assertEquals(orderItem01.getSkuId(), 10L);
|
||||
assertEquals(orderItem01.getCount(), 2);
|
||||
assertEquals(orderItem01.getPrice(), 100);
|
||||
assertEquals(orderItem01.getDiscountPrice(), 0);
|
||||
assertEquals(orderItem01.getDeliveryPrice(), 0);
|
||||
assertEquals(orderItem01.getCouponPrice(), 0);
|
||||
assertEquals(orderItem01.getPointPrice(), 0);
|
||||
assertEquals(orderItem01.getVipPrice(), 80);
|
||||
assertEquals(orderItem01.getPayPrice(), 120);
|
||||
// 断言:SKU 2
|
||||
TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
|
||||
assertEquals(orderItem02.getSkuId(), 20L);
|
||||
assertEquals(orderItem02.getCount(), 3);
|
||||
assertEquals(orderItem02.getPrice(), 50);
|
||||
assertEquals(orderItem02.getDiscountPrice(), 0);
|
||||
assertEquals(orderItem02.getDeliveryPrice(), 0);
|
||||
assertEquals(orderItem02.getCouponPrice(), 0);
|
||||
assertEquals(orderItem02.getPointPrice(), 0);
|
||||
assertEquals(orderItem02.getVipPrice(), 60);
|
||||
assertEquals(orderItem02.getPayPrice(), 90);
|
||||
// 断言:Promotion 部分
|
||||
assertEquals(result.getPromotions().size(), 1);
|
||||
TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
|
||||
assertEquals(promotion01.getId(), 2048L);
|
||||
assertEquals(promotion01.getName(), "VIP 会员");
|
||||
assertEquals(promotion01.getType(), PromotionTypeEnum.MEMBER_LEVEL.getType());
|
||||
assertEquals(promotion01.getTotalPrice(), 200);
|
||||
assertEquals(promotion01.getDiscountPrice(), 80);
|
||||
assertTrue(promotion01.getMatch());
|
||||
assertEquals(promotion01.getDescription(), "会员等级折扣:省 0.80 元");
|
||||
TradePriceCalculateRespBO.PromotionItem promotionItem01 = promotion01.getItems().get(0);
|
||||
assertEquals(promotion01.getItems().size(), 1);
|
||||
assertEquals(promotionItem01.getSkuId(), 10L);
|
||||
assertEquals(promotionItem01.getTotalPrice(), 200);
|
||||
assertEquals(promotionItem01.getDiscountPrice(), 80);
|
||||
}
|
||||
|
||||
}
|
||||
//package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
//
|
||||
//import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
//import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
|
||||
//import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
|
||||
//import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
//import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
//import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
//import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
//import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
//import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
//import org.junit.jupiter.api.Test;
|
||||
//import org.mockito.InjectMocks;
|
||||
//import org.mockito.Mock;
|
||||
//
|
||||
//import java.util.ArrayList;
|
||||
//
|
||||
//import static java.util.Arrays.asList;
|
||||
//import static org.junit.jupiter.api.Assertions.*;
|
||||
//import static org.mockito.ArgumentMatchers.eq;
|
||||
//import static org.mockito.Mockito.when;
|
||||
//
|
||||
///**
|
||||
// * {@link TradeMemberLevelPriceCalculator} 的单元测试类
|
||||
// *
|
||||
// * @author 芋道源码
|
||||
// */
|
||||
//public class TradeMemberLevelPriceCalculatorTest extends BaseMockitoUnitTest {
|
||||
//
|
||||
// @InjectMocks
|
||||
// private TradeMemberLevelPriceCalculator memberLevelPriceCalculator;
|
||||
//
|
||||
// @Mock
|
||||
// private MemberLevelApi memberLevelApi;
|
||||
// @Mock
|
||||
// private MemberUserApi memberUserApi;
|
||||
//
|
||||
// @Test
|
||||
// public void testCalculate() {
|
||||
// // 准备参数
|
||||
// TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
|
||||
// .setUserId(1024L)
|
||||
// .setItems(asList(
|
||||
// new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 匹配活动,且已选中
|
||||
// new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(false) // 匹配活动,但未选中
|
||||
// ));
|
||||
// TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
|
||||
// .setType(TradeOrderTypeEnum.NORMAL.getType())
|
||||
// .setPrice(new TradePriceCalculateRespBO.Price())
|
||||
// .setPromotions(new ArrayList<>())
|
||||
// .setItems(asList(
|
||||
// new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
|
||||
// .setPrice(100),
|
||||
// new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(false)
|
||||
// .setPrice(50)
|
||||
// ));
|
||||
// // 保证价格被初始化上
|
||||
// TradePriceCalculatorHelper.recountPayPrice(result.getItems());
|
||||
// TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
//
|
||||
// // mock 方法(会员等级)
|
||||
// when(memberUserApi.getUser(eq(1024L))).thenReturn(new MemberUserRespDTO().setLevelId(2048L));
|
||||
// when(memberLevelApi.getMemberLevel(eq(2048L))).thenReturn(
|
||||
// new MemberLevelRespDTO().setId(2048L).setName("VIP 会员").setDiscountPercent(60));
|
||||
//
|
||||
// // 调用
|
||||
// memberLevelPriceCalculator.calculate(param, result);
|
||||
// // 断言:Price 部分
|
||||
// TradePriceCalculateRespBO.Price price = result.getPrice();
|
||||
// assertEquals(price.getTotalPrice(), 200);
|
||||
// assertEquals(price.getDiscountPrice(), 0);
|
||||
// assertEquals(price.getPointPrice(), 0);
|
||||
// assertEquals(price.getDeliveryPrice(), 0);
|
||||
// assertEquals(price.getCouponPrice(), 0);
|
||||
// assertEquals(price.getVipPrice(), 80);
|
||||
// assertEquals(price.getPayPrice(), 120);
|
||||
// assertNull(result.getCouponId());
|
||||
// // 断言:SKU 1
|
||||
// assertEquals(result.getItems().size(), 2);
|
||||
// TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
|
||||
// assertEquals(orderItem01.getSkuId(), 10L);
|
||||
// assertEquals(orderItem01.getCount(), 2);
|
||||
// assertEquals(orderItem01.getPrice(), 100);
|
||||
// assertEquals(orderItem01.getDiscountPrice(), 0);
|
||||
// assertEquals(orderItem01.getDeliveryPrice(), 0);
|
||||
// assertEquals(orderItem01.getCouponPrice(), 0);
|
||||
// assertEquals(orderItem01.getPointPrice(), 0);
|
||||
// assertEquals(orderItem01.getVipPrice(), 80);
|
||||
// assertEquals(orderItem01.getPayPrice(), 120);
|
||||
// // 断言:SKU 2
|
||||
// TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
|
||||
// assertEquals(orderItem02.getSkuId(), 20L);
|
||||
// assertEquals(orderItem02.getCount(), 3);
|
||||
// assertEquals(orderItem02.getPrice(), 50);
|
||||
// assertEquals(orderItem02.getDiscountPrice(), 0);
|
||||
// assertEquals(orderItem02.getDeliveryPrice(), 0);
|
||||
// assertEquals(orderItem02.getCouponPrice(), 0);
|
||||
// assertEquals(orderItem02.getPointPrice(), 0);
|
||||
// assertEquals(orderItem02.getVipPrice(), 60);
|
||||
// assertEquals(orderItem02.getPayPrice(), 90);
|
||||
// // 断言:Promotion 部分
|
||||
// assertEquals(result.getPromotions().size(), 1);
|
||||
// TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
|
||||
// assertEquals(promotion01.getId(), 2048L);
|
||||
// assertEquals(promotion01.getName(), "VIP 会员");
|
||||
// assertEquals(promotion01.getType(), PromotionTypeEnum.MEMBER_LEVEL.getType());
|
||||
// assertEquals(promotion01.getTotalPrice(), 200);
|
||||
// assertEquals(promotion01.getDiscountPrice(), 80);
|
||||
// assertTrue(promotion01.getMatch());
|
||||
// assertEquals(promotion01.getDescription(), "会员等级折扣:省 0.80 元");
|
||||
// TradePriceCalculateRespBO.PromotionItem promotionItem01 = promotion01.getItems().get(0);
|
||||
// assertEquals(promotion01.getItems().size(), 1);
|
||||
// assertEquals(promotionItem01.getSkuId(), 10L);
|
||||
// assertEquals(promotionItem01.getTotalPrice(), 200);
|
||||
// assertEquals(promotionItem01.getDiscountPrice(), 80);
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
@ -0,0 +1,61 @@
|
||||
package cn.iocoder.yudao.module.system.api.social.dto;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 微信小程序订阅消息发送 Request DTO
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Data
|
||||
public class SocialWxaSubscribeMessageSendReqDTO {
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*
|
||||
* 关联 MemberUserDO 的 id 编号
|
||||
* 关联 AdminUserDO 的 id 编号
|
||||
*/
|
||||
@NotNull(message = "用户编号不能为空")
|
||||
private Long userId;
|
||||
/**
|
||||
* 用户类型
|
||||
*
|
||||
* 关联 {@link UserTypeEnum}
|
||||
*/
|
||||
@NotNull(message = "用户类型不能为空")
|
||||
private Integer userType;
|
||||
|
||||
/**
|
||||
* 消息模版标题
|
||||
*/
|
||||
@NotEmpty(message = "消息模版标题不能为空")
|
||||
private String templateTitle;
|
||||
|
||||
/**
|
||||
* 点击模板卡片后的跳转页面,仅限本小程序内的页面
|
||||
*
|
||||
* 支持带参数,(示例 index?foo=bar )。该字段不填则模板无跳转。
|
||||
*/
|
||||
private String page;
|
||||
|
||||
/**
|
||||
* 模板内容的参数
|
||||
*/
|
||||
private Map<String, String> messages;
|
||||
|
||||
public SocialWxaSubscribeMessageSendReqDTO addMessage(String key, String value) {
|
||||
if (messages == null) {
|
||||
messages = new HashMap<>();
|
||||
}
|
||||
messages.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package cn.iocoder.yudao.module.system.api.social.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
* 小程序订阅消息模版 Response DTO
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Data
|
||||
public class SocialWxaSubscribeTemplateRespDTO {
|
||||
|
||||
/**
|
||||
* 模版编号
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 模版标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 模版内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 模板内容示例
|
||||
*/
|
||||
private String example;
|
||||
|
||||
/**
|
||||
* 模版类型
|
||||
*
|
||||
* 2:为一次性订阅
|
||||
* 3:为长期订阅
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
}
|
@ -77,7 +77,8 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode SMS_CHANNEL_NOT_EXISTS = new ErrorCode(1_002_011_000, "短信渠道不存在");
|
||||
ErrorCode SMS_CHANNEL_DISABLE = new ErrorCode(1_002_011_001, "短信渠道不处于开启状态,不允许选择");
|
||||
ErrorCode SMS_CHANNEL_HAS_CHILDREN = new ErrorCode(1_002_011_002, "无法删除,该短信渠道还有短信模板");
|
||||
|
||||
ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_TEMPLATE_ERROR = new ErrorCode(1_002_018_202, "获得小程序订阅消息模版失败");
|
||||
ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_MESSAGE_ERROR = new ErrorCode(1_002_018_203, "发送小程序订阅消息失败");
|
||||
// ========== 短信模板 1-002-012-000 ==========
|
||||
ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_012_000, "短信模板不存在");
|
||||
ErrorCode SMS_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1_002_012_001, "已经存在编码为【{}】的短信模板");
|
||||
|
Loading…
Reference in New Issue
Block a user