| | |
| | | <template> |
| | | <view class="page"> |
| | | <view class="top-area"> |
| | | <view class="custom-nav"> |
| | | <view class="safe-space" :style="'height:' + navTop + 'px'"></view> |
| | | <view class="nav-row" :style="'height:' + navHeight + 'px;padding-right:' + navRight + 'px'"> |
| | | <view class="back-button" @tap="goBack"><image src="/static/news-assets/arrow-left.svg" mode="aspectFit"></image></view> |
| | | <text class="nav-title">{{merchantName}}</text> |
| | | <view class="capsule-space" :style="'width:' + navWidth + 'px'"></view> |
| | | </view> |
| | | <view class="page-header"> |
| | | <view class="safe-space" :style="'height:' + navTop + 'px'"></view> |
| | | <view class="nav-row" :style="'height:' + navHeight + 'px;padding-right:' + navRight + 'px'"> |
| | | <view class="back-button" hover-class="back-button-active" @tap="goBack"><image src="/static/news-assets/arrow-left.svg" mode="aspectFit"></image></view> |
| | | <text class="nav-title">{{merchantName}}</text> |
| | | <view class="capsule-space" :style="'width:' + navWidth + 'px'"></view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-if="merchantLoading" class="detail-state loading-state"><view class="loading-spinner"></view><text>正在加载洗车服务</text></view> |
| | | <view v-else-if="merchantError" class="detail-state error-state"><text>洗车服务加载失败</text><text class="retry-text" @tap="loadMerchantDetail">重新加载</text></view> |
| | | <view v-else class="detail-content"> |
| | | <view v-if="merchantLoading" class="state-block"><view class="loading-spinner"></view><text>正在加载洗车服务</text></view> |
| | | <view v-else-if="merchantError" class="state-block error-state"><text>洗车服务加载失败</text><text class="retry-text" @tap="loadMerchantDetail">重新加载</text></view> |
| | | <view v-else class="content"> |
| | | <view class="service-card"> |
| | | <view class="service-title-row"><view class="service-icon"><image src="/static/merchant-assets/car-wash-orange.svg" mode="aspectFit"></image></view><text class="service-title">{{merchantName}}</text></view> |
| | | <view class="service-title-row"> |
| | | <view class="service-icon"><image src="/static/merchant-assets/car-wash-orange.svg" mode="aspectFit"></image></view> |
| | | <text class="service-title">{{merchantName}}</text> |
| | | </view> |
| | | <view class="talent-tag"><text>{{applicableCards}}</text></view> |
| | | <text class="service-desc">{{servicePointText}}</text> |
| | | <text class="service-tip">{{tipText}}</text> |
| | | <view v-if="!available" class="unavailable-note"><text>{{unavailableReason}}</text></view> |
| | | </view> |
| | | <text class="service-tip">{{tipText}}</text> |
| | | <view v-if="!available" class="unavailable-note"><text>{{unavailableReason}}</text></view> |
| | | |
| | | |
| | | </view> |
| | | |
| | | <view class="action-bar"> |
| | | <view class="wash-button" :class="!available || merchantLoading || merchantError ? 'is-disabled' : ''" @tap="openExternalService"><image src="/static/merchant-assets/car-wash-white.svg" mode="aspectFit"></image><text>去洗车</text></view> |
| | | <view class="wash-button" :class="!available || merchantLoading || merchantError ? 'is-disabled' : ''" @tap="openExternalService"> |
| | | <image src="/static/merchant-assets/car-wash-white.svg" mode="aspectFit"></image> |
| | | <text v-if="externalLinkType == 'miniProgram' || externalLinkType == 'web'">去洗车</text> |
| | | <text v-else>提交手机号</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-if="showPhoneForm" class="phone-mask" @tap="closePhoneForm"> |
| | | <view class="phone-dialog" @tap.stop> |
| | | <view class="phone-dialog-close" @tap="closePhoneForm"><text>×</text></view> |
| | | <text class="phone-title">填写手机号</text> |
| | | <text class="phone-desc">提交后商家将根据手机号与您联系</text> |
| | | <view class="phone-input-box"><input v-model="phone" type="number" maxlength="11" placeholder="请输入手机号" placeholder-class="phone-placeholder" /></view> |
| | | <text v-if="phoneError != ''" class="phone-error">{{phoneError}}</text> |
| | | <view class="phone-submit" @tap="submitPhone"><text>提交</text></view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup lang="uts"> |
| | | import { isApiConfigured } from '../../config/api.uts' |
| | | import { getCarWashMerchantDetail } from '../../services/portal.uts' |
| | | import { getCarWashMerchantDetail, getTalentCardProfile, submitCarWashCredential } from '../../services/portal.uts' |
| | | import { getSession } from '../../services/session.uts' |
| | | import { safeBack } from '../../utils/navigation.uts' |
| | | |
| | | const navTop = ref(28) |
| | |
| | | const externalMiniProgramAppId = ref('') |
| | | const externalMiniProgramPath = ref('') |
| | | const externalWebUrl = ref('') |
| | | const talentId = ref('') |
| | | const cardId = ref('') |
| | | const showPhoneForm = ref(false) |
| | | const phone = ref('') |
| | | const phoneSubmitting = ref(false) |
| | | const phoneError = ref('') |
| | | const merchantLoading = ref(true) |
| | | const merchantError = ref(false) |
| | | |
| | |
| | | // #endif |
| | | if (options != null && options['merchantId'] != null) merchantId.value = options['merchantId'] as string |
| | | if (options != null && options['name'] != null && merchantName.value == '自助洗车服务') merchantName.value = options['name'] as string |
| | | const session = getSession() |
| | | if (session != null && session.talentId != null) talentId.value = session.talentId as string |
| | | if (talentId.value != '') getTalentCardProfile(talentId.value, (data: any) => { const card = data != null ? data.card : null; if (card != null && card.id != null) cardId.value = '' + card.id }, () => {}) |
| | | loadMerchantDetail() |
| | | }) |
| | | |
| | |
| | | getCarWashMerchantDetail(merchantId.value, (data: any) => { |
| | | if (data == null) { merchantLoading.value = false; merchantError.value = true; return } |
| | | merchantName.value = data.merchantName != null && data.merchantName != '' ? data.merchantName as string : merchantName.value |
| | | applicableCards.value = data.applicableCards != null ? data.applicableCards as string : '请咨询商户' |
| | | servicePointText.value = data.servicePointText != null ? data.servicePointText as string : '请咨询商户' |
| | | applicableCards.value = data.applicableCards != null ? data.applicableCards as string : '请咨询商家' |
| | | servicePointText.value = data.servicePointText != null ? data.servicePointText as string : '请咨询商家' |
| | | tipText.value = data.tipText != null ? data.tipText as string : '请按现场指引操作' |
| | | available.value = data.available === true |
| | | unavailableReason.value = data.unavailableReason != null && data.unavailableReason != '' ? data.unavailableReason as string : '洗车服务当前不可用' |
| | |
| | | externalWebUrl.value = data.externalWebUrl != null ? data.externalWebUrl as string : '' |
| | | merchantLoading.value = false |
| | | }, () => { merchantLoading.value = false; merchantError.value = true }) |
| | | } |
| | | const closePhoneForm = () => { showPhoneForm.value = false; phoneError.value = '' } |
| | | const submitPhone = () => { |
| | | if (phoneSubmitting.value) return |
| | | const value = phone.value.trim() |
| | | if (!/^1[3-9][0-9]{9}$/.test(value)) { |
| | | phoneError.value = '请输入正确的 11 位手机号' |
| | | return |
| | | } |
| | | if (merchantId.value == '' || talentId.value == '' || cardId.value == '') { |
| | | phoneError.value = '请登录后提交手机号' |
| | | return |
| | | } |
| | | phoneSubmitting.value = true |
| | | phoneError.value = '' |
| | | const key = 'car-wash-credential-' + merchantId.value + '-' + talentId.value + '-' + Date.now() |
| | | submitCarWashCredential(merchantId.value, talentId.value, cardId.value, value, key, (data: any) => { |
| | | phoneSubmitting.value = false |
| | | showPhoneForm.value = false |
| | | phone.value = '' |
| | | uni.showToast({ title: '手机号已提交', icon: 'success' }) |
| | | setTimeout(() => safeBack('/pages/transport-benefits/transport-benefits'), 800) |
| | | }, (error) => { |
| | | phoneSubmitting.value = false |
| | | phoneError.value = error.message |
| | | }) |
| | | } |
| | | const openExternalService = () => { |
| | | if (merchantLoading.value || merchantError.value) return |
| | |
| | | uni.navigateTo({ url: '/pages/external-webview/external-webview?source=carWash&merchantId=' + encodeURIComponent(merchantId.value) }) |
| | | return |
| | | } |
| | | uni.showToast({ title: '洗车服务链接暂未配置', icon: 'none' }) |
| | | showPhoneForm.value = true |
| | | } |
| | | </script> |
| | | |
| | | <style> |
| | | .page { width: 100%; height: 100vh; position: relative; overflow: hidden; background-color: #F4F7FB; } |
| | | .top-area { height: 317rpx; background-image: linear-gradient(180deg, #FFE6BD 0%, #FFF0D9 100%); } |
| | | .custom-nav { position: relative; z-index: 5; } |
| | | .page { display: flex; width: 100%; height: 100vh; background-color: #F4F7FB; flex-direction: column; } |
| | | .page-header { position: relative; z-index: 10; flex-shrink: 0; padding-bottom: 20rpx; background-image: linear-gradient(180deg, #FFE6BD 0%, #FFF0D9 100%); } |
| | | .safe-space { height: 28px; } |
| | | .nav-row { display: flex; box-sizing: border-box; padding-left: 20rpx; flex-direction: row; align-items: center; justify-content: space-between; } |
| | | .nav-row { display: flex; height: 32px; box-sizing: border-box; padding-left: 20rpx; padding-right: 10px; position: relative; flex-direction: row; align-items: center; justify-content: space-between; } |
| | | .back-button { display: flex; width: 70rpx; height: 64rpx; margin-left: -4rpx; justify-content: center; align-items: center; } |
| | | .back-button image { width: 36rpx; height: 36rpx; } |
| | | .nav-title { position: absolute; left: 156rpx; right: 156rpx; color: #171B22; font-size: 31rpx; line-height: 48rpx; font-weight: 700; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } |
| | | .capsule-space { height: 100%; flex-shrink: 0; } |
| | | .detail-content { display: block; } |
| | | .service-card { position: absolute; z-index: 6; left: 40rpx; right: 40rpx; top: 242rpx; min-height: 318rpx; box-sizing: border-box; padding: 31rpx 34rpx 27rpx; border-radius: 28rpx; background-color: #FFFFFF; box-shadow: 0 13rpx 30rpx rgba(63,74,89,.07); } |
| | | .back-button-active { opacity: .55; } |
| | | .nav-title { position: absolute; left: 50%; top: 50%; color: #171B22; font-size: 37rpx; line-height: 48rpx; font-weight: 700; transform: translate(-50%, -50%); white-space: nowrap; } |
| | | .capsule-space { width: 88px; height: 100%; flex-shrink: 0; } |
| | | .state-block { display: flex; min-height: 360rpx; color: #8E98A6; font-size: 26rpx; flex-direction: column; justify-content: center; align-items: center; } |
| | | .error-state { flex-direction: column; } |
| | | .retry-text { margin-top: 22rpx; color: #F26A38; } |
| | | .loading-spinner { width: 28rpx; height: 28rpx; margin-right: 14rpx; box-sizing: border-box; border-width: 3rpx; border-style: solid; border-color: #FFD6C4; border-top-color: #FF6C2B; border-radius: 50%; animation: wash-loading 720ms linear infinite; } |
| | | @keyframes wash-loading { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } |
| | | .content { min-height: 0; box-sizing: border-box; padding: 30rpx 40rpx 30rpx; flex: 1; overflow-y: auto; } |
| | | .service-card { box-sizing: border-box; padding: 31rpx 34rpx 27rpx; border-radius: 28rpx; background-color: #FFFFFF; box-shadow: 0 13rpx 30rpx rgba(63,74,89,.07); } |
| | | .service-title-row { display: flex; flex-direction: row; align-items: center; } |
| | | .service-icon { display: flex; width: 80rpx; height: 80rpx; border-radius: 50%; background-color: #FFF3E6; justify-content: center; align-items: center; flex-shrink: 0; } |
| | | .service-icon image { width: 48rpx; height: 48rpx; } |
| | | .service-title { min-width: 0; flex: 1; margin-left: 21rpx; color: #202631; font-size: 34rpx; line-height: 47rpx; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } |
| | | .talent-tag { display: flex; max-width: 100%; width: fit-content; min-height: 47rpx; margin-top: 25rpx; box-sizing: border-box; padding: 8rpx 17rpx; border-radius: 24rpx; background-image: linear-gradient(90deg, #FFF0E5 0%, #FFE8D7 100%); justify-content: center; align-items: center; } |
| | | .talent-tag text { max-width: 540rpx; color: #D86B35; font-size: 23rpx; line-height: 31rpx; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } |
| | | .service-desc { display: block; max-height: 78rpx; margin-top: 24rpx; overflow: hidden; color: #6F7887; font-size: 25rpx; line-height: 39rpx; } |
| | | .service-tip { position: absolute; left: 80rpx; right: 80rpx; top: 650rpx; max-height: 68rpx; overflow: hidden; color: #9CA6B5; font-size: 22rpx; line-height: 34rpx; text-align: center; } |
| | | .unavailable-note { position: absolute; left: 80rpx; right: 80rpx; top: 748rpx; text-align: center; } |
| | | .service-desc { display: block; margin-top: 24rpx; color: #6F7887; font-size: 25rpx; line-height: 39rpx; } |
| | | .service-tip { display: block; margin-top: 16rpx; color: #9CA6B5; font-size: 22rpx; line-height: 34rpx; } |
| | | .unavailable-note { margin-top: 16rpx; text-align: center; } |
| | | .unavailable-note text { color: #D86B35; font-size: 23rpx; line-height: 34rpx; } |
| | | .detail-state { display: flex; position: absolute; z-index: 6; left: 40rpx; right: 40rpx; top: 242rpx; height: 318rpx; border-radius: 28rpx; background-color: #FFFFFF; box-shadow: 0 13rpx 30rpx rgba(63,74,89,.07); flex-direction: column; justify-content: center; align-items: center; } |
| | | .detail-state text { color: #8E98A6; font-size: 26rpx; } |
| | | .error-state .retry-text { margin-top: 22rpx; color: #F26A38; } |
| | | .loading-state { flex-direction: row; } |
| | | .loading-spinner { width: 28rpx; height: 28rpx; margin-right: 14rpx; box-sizing: border-box; border-width: 3rpx; border-style: solid; border-color: #FFD6C4; border-top-color: #FF6C2B; border-radius: 50%; animation: wash-loading 720ms linear infinite; } |
| | | .action-bar { display: flex; position: absolute; left: 0; right: 0; bottom: 0; height: 151rpx; height: calc(151rpx + env(safe-area-inset-bottom)); box-sizing: border-box; padding: 17rpx 40rpx 13rpx; padding-bottom: calc(13rpx + env(safe-area-inset-bottom)); background-color: #FFFFFF; box-shadow: 0 -5rpx 16rpx rgba(0,0,0,.04); align-items: flex-start; } |
| | | .phone-mask { display: flex; position: fixed; z-index: 50; left: 0; right: 0; top: 0; bottom: 0; background-color: rgba(0,0,0,.45); justify-content: center; align-items: center; } |
| | | .phone-dialog { display: flex; width: 590rpx; box-sizing: border-box; padding: 44rpx 40rpx 36rpx; border-radius: 26rpx; background-color: #FFFFFF; position: relative; flex-direction: column; align-items: center; } |
| | | .phone-dialog-close { display: flex; width: 60rpx; height: 60rpx; position: absolute; top: 12rpx; right: 12rpx; justify-content: center; align-items: center; } |
| | | .phone-dialog-close text { color: #9AA3AE; font-size: 36rpx; line-height: 60rpx; } |
| | | .phone-title { color: #262D36; font-size: 31rpx; font-weight: 700; } |
| | | .phone-desc { margin-top: 12rpx; color: #8A94A1; font-size: 24rpx; } |
| | | .phone-input-box { display: flex; width: 100%; height: 88rpx; margin-top: 28rpx; box-sizing: border-box; padding: 0 24rpx; border-radius: 16rpx; background-color: #F5F7FA; justify-content: center; align-items: center; } |
| | | .phone-input-box input { width: 100%; color: #2D333C; font-size: 28rpx; } |
| | | .phone-placeholder { color: #BBC1CA; } |
| | | .phone-error { margin-top: 12rpx; color: #F05C3A; font-size: 22rpx; } |
| | | .phone-submit { display: flex; width: 100%; height: 84rpx; margin-top: 28rpx; border-radius: 42rpx; background-image: linear-gradient(90deg, #FF4B32 0%, #FF8A1E 100%); justify-content: center; align-items: center; } |
| | | .phone-submit text { color: #FFFFFF; font-size: 28rpx; font-weight: 600; } |
| | | .action-bar { display: flex; flex-shrink: 0; box-sizing: border-box; padding: 17rpx 40rpx calc(17rpx + env(safe-area-inset-bottom)); background-color: #FFFFFF; box-shadow: 0 -5rpx 16rpx rgba(0,0,0,.04); } |
| | | .wash-button { display: flex; width: 100%; height: 88rpx; border-radius: 45rpx; background-image: linear-gradient(90deg, #FF4B32 0%, #FF8A1E 100%); flex-direction: row; justify-content: center; align-items: center; } |
| | | .wash-button.is-disabled { background-image: none; background-color: #C7CDD5; } |
| | | .wash-button image { width: 36rpx; height: 36rpx; margin-right: 18rpx; } |
| | | .wash-button text { color: #FFFFFF; font-size: 30rpx; font-weight: 600; } |
| | | @keyframes wash-loading { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } |
| | | </style> |
| | | </style> |
| | |
| | | uni.showToast({ title: '请输入姓名', icon: 'none' }) |
| | | return |
| | | } |
| | | if (!/^1[3-9][0-9]{9}$/.test(userMobile.value.trim())) { |
| | | uni.showToast({ title: '请输入正确的 11 位手机号', icon: 'none' }) |
| | | return |
| | | if (!/^1[2-9][0-9]{9}$/.test(userMobile.value.trim())) { |
| | | uni.showToast({ title: '请输入正确的 11 位手机号', icon: 'none' }) |
| | | return |
| | | } |
| | | if (!/^[0-9]{17}[0-9Xx]$/.test(userIdCard.value.trim())) { |
| | | uni.showToast({ title: '请输入正确的 18 位身份证号', icon: 'none' }) |
| | |
| | | |
| | | <script setup lang="uts"> |
| | | import { apiEnvironment, isApiConfigured } from '../../config/api.uts' |
| | | import { getBenefitQuota, getFuelMerchantDetail, getMedicalPackages, getMerchantDetail, getParkingMerchantDetail, getProducts, submitFuelMobileRegistration } from '../../services/portal.uts' |
| | | import { getFuelMerchantDetail, getMedicalPackages, getMerchantDetail, getParkingMerchantDetail, getProducts, submitFuelMobileRegistration } from '../../services/portal.uts' |
| | | import { resolveApiUrl } from '../../services/http.uts' |
| | | import { getSession } from '../../services/session.uts' |
| | | import { safeBack } from '../../utils/navigation.uts' |
| | |
| | | merchantServiceLabel.value = '加油服务' |
| | | merchantRightsText.value = '持卓越人才卡到站加油并出示人才码,可享指定油品 9.4 折优惠,具体适用油品、优惠上限、可用时段和活动库存以加油站现场说明为准。' |
| | | } |
| | | } else if (routedMerchantType == 'hotel' || routedMerchantType == '酒店服务' || name.indexOf('酒店') >= 0) { |
| | | } else if (routedMerchantType == 'hotel' || name.indexOf('酒店') >= 0) { |
| | | isHotelMerchant.value = true |
| | | merchantType.value = 'hotel' |
| | | merchantName.value = name |
| | |
| | | merchantRightsText.value = data != null && data.rightsText != null && data.rightsText != '' ? data.rightsText as string : merchantRightsText.value |
| | | usageInstructions.value = data != null && data.usageInstructions != null && data.usageInstructions != '' ? data.usageInstructions as string : usageInstructions.value |
| | | groupBuyingEnabled.value = data == null || data.groupBuyingEnabled == null ? groupBuyingEnabled.value : data.groupBuyingEnabled === true || Number(data.groupBuyingEnabled) == 1 |
| | | isHotelMerchant.value = merchantType.value == 'hotel' || merchantType.value == '酒店服务' |
| | | isHotelMerchant.value = merchantType.value == 'hotel' |
| | | hasFreeVoucher.value = merchantDiscount.value.indexOf('免费') >= 0 |
| | | if (isHotelMerchant.value && /^\d+$/.test(merchantId.value) && talentId.value != '') { |
| | | getBenefitQuota('HOTEL', talentId.value, '', merchantId.value, (quota: any) => { |
| | | hasFreeVoucher.value = quota != null && Number(quota.quotaTotal) > 0 |
| | | }, () => {}) |
| | | } |
| | | merchantLocation.value = { |
| | | latitude: data != null && data.latitude != null ? Number(data.latitude) : 0, |
| | | longitude: data != null && data.longitude != null ? Number(data.longitude) : 0, |
| | |
| | | const loadMoreServices = () => { if (initialLoading.value || loadingMore.value || !hasMore.value) return; loadMoreError.value = false; loadingMore.value = true; loadServices(true) } |
| | | const replaceHeroImage = () => { currentScene.value.image = fallbackImage }; const replaceSceneImage = (index: number) => { if (index >= 0 && index < scenes.value.length) scenes.value[index].image = fallbackImage }; const replaceServiceImage = (item: any) => { item.image = fallbackImage } |
| | | const goBack = () => safeBack('/pages/benefits/benefits') |
| | | const openMerchant = (item: any) => { if (!item.available) { uni.showToast({ title: '该服务已下架', icon: 'none' }); return }; if (item.merchantType == 'carWash') { uni.navigateTo({ url: '/pages/car-wash-merchant/car-wash-merchant?merchantId=' + encodeURIComponent(item.id) + '&name=' + encodeURIComponent(item.name) }); return }; const action = item.merchantType == 'parking' && item.discount.indexOf('人才码') >= 0 ? 'talentCode' : ''; uni.navigateTo({ url: '/pages/merchant/merchant?type=transport&merchantType=' + encodeURIComponent(item.merchantType) + '&merchantId=' + encodeURIComponent(item.id) + '&name=' + encodeURIComponent(item.name) + '&image=' + encodeURIComponent(item.image) + '&address=' + encodeURIComponent(item.address) + '&discount=' + encodeURIComponent(item.discount) + '&action=' + encodeURIComponent(action) }) } |
| | | const openMerchant = (item: any) => { if (!item.available) { uni.showToast({ title: '该服务已下架', icon: 'none' }); return }; if (item.merchantType == 'carWash' || item.merchantType == '自助洗车') { uni.navigateTo({ url: '/pages/car-wash-merchant/car-wash-merchant?merchantId=' + encodeURIComponent(item.id) + '&name=' + encodeURIComponent(item.name) }); return }; const action = item.merchantType == 'parking' && item.discount.indexOf('人才码') >= 0 ? 'talentCode' : ''; uni.navigateTo({ url: '/pages/merchant/merchant?type=transport&merchantType=' + encodeURIComponent(item.merchantType) + '&merchantId=' + encodeURIComponent(item.id) + '&name=' + encodeURIComponent(item.name) + '&image=' + encodeURIComponent(item.image) + '&address=' + encodeURIComponent(item.address) + '&discount=' + encodeURIComponent(item.discount) + '&action=' + encodeURIComponent(action) }) } |
| | | </script> |
| | | |
| | | <style> |
| | |
| | | export const submitFuelMobileRegistration = (merchantId: string, talentId: string, benefitId: string, mobile: string, idempotencyKey: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appFuelMerchantDetail') + encodeURIComponent(merchantId) + '/registrations', method: 'POST', skipAuth: true, data: { talentId: Number(talentId), benefitId: Number(benefitId), mobile: mobile }, idempotencyKey: idempotencyKey }, onSuccess, onError) |
| | | export const getParkingMerchantDetail = (merchantId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appParkingMerchantDetail') + encodeURIComponent(merchantId), method: 'GET', skipAuth: true }, onSuccess, onError) |
| | | export const getCarWashMerchantDetail = (merchantId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appCarWashMerchantDetail') + encodeURIComponent(merchantId), method: 'GET', skipAuth: true }, onSuccess, onError) |
| | | export const submitCarWashCredential = (merchantId: string, talentId: string, cardId: string, phone: string, idempotencyKey: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => { |
| | | const data: any = { talentId: Number(talentId), cardId: Number(cardId), proofFiles: [], description: '手机号:' + phone } |
| | | return request<any>({ path: getApiEndpoint('appCarWashMerchantDetail') + encodeURIComponent(merchantId) + '/credentials', method: 'POST', skipAuth: true, data: data, idempotencyKey: idempotencyKey }, onSuccess, onError) |
| | | } |
| | | export const getShoppingMerchantDetail = (merchantId: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appShoppingMerchantDetail') + encodeURIComponent(merchantId), method: 'GET', skipAuth: true }, onSuccess, onError) |
| | | export const searchParkingLots = (merchantId: string, keyword: string, page: number, size: number, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => request<any>({ path: getApiEndpoint('appParkingMerchantDetail') + encodeURIComponent(merchantId) + '/parking-lots', method: 'GET', skipAuth: true, data: { keyword: keyword, page: page, size: size } }, onSuccess, onError) |
| | | export const submitParkingPlateRegistration = (merchantId: string, talentId: string, benefitId: string, parkingLotId: string, plateNumber: string, idempotencyKey: string, onSuccess: (data: any) => void, onError: (error: HttpError) => void): (() => void) => { |
| | |
| | | |
| | | @RestController |
| | | @RequestMapping("/api/admin/workbench") |
| | | @Tag(name = "\u540e\u53f0\u5de5\u4f5c\u53f0", description = "\u540e\u53f0\u9996\u9875\u6838\u5fc3\u6307\u6807\u548c\u5f02\u5e38\u544a\u8b66") |
| | | @Tag(name = "后台工作台", description = "后台首页核心指标和异常告警") |
| | | public class AdminWorkbenchStatisticsController { |
| | | private final AdminWorkbenchStatisticsService service; |
| | | |
| | |
| | | } |
| | | |
| | | @GetMapping({"/statistics", "/dashboard"}) |
| | | @Operation(summary = "\u67e5\u8be2\u5de5\u4f5c\u53f0\u7edf\u8ba1", description = "\u8fd4\u56de\u7d2f\u8ba1\u8865\u8d34\u603b\u989d\u3001\u672c\u6708\u6838\u9500\u5355\u6570\u3001\u6301\u5361\u4eba\u603b\u6570\u3001\u9884\u7b97\u4f7f\u7528\u7387\u548c\u5f02\u5e38\u544a\u8b66") |
| | | @Operation(summary = "查询工作台统计", description = "返回累计补贴总额、本月核销单数、持卡人总数、预算使用率和异常告警") |
| | | public ApiResponse<Map<String, Object>> statistics() { |
| | | return ApiResponse.success(service.summary()); |
| | | } |
| | |
| | | |
| | | @RestController |
| | | @RequestMapping("/api/admin/benefit-service-config/coupons") |
| | | @Tag(name = "\u540e\u53f0-\u6743\u76ca\u670d\u52a1\u914d\u7f6e-\u4f18\u60e0\u5238\u7ba1\u7406", description = "\u7ba1\u7406\u4f18\u60e0\u5238\u7c7b\u578b\u3001\u9762\u989d\u3001\u9002\u7528\u5546\u6237\u3001\u6709\u6548\u671f\u3001\u53d1\u653e\u6279\u6b21\u53ca\u9886\u53d6/\u4f7f\u7528\u72b6\u6001") |
| | | @Tag(name = "后台-权益服务配置-优惠券管理", description = "管理优惠券类型、面额、适用商户、有效期、发放批次及领取/使用状态") |
| | | public class BenefitServiceCouponAdminController { |
| | | private final MerchantCouponManagementService service; |
| | | |
| | |
| | | } |
| | | |
| | | @GetMapping |
| | | @Operation(summary = "\u5206\u9875\u67e5\u8be2\u4f18\u60e0\u5238\u914d\u7f6e") |
| | | @Operation(summary = "分页查询优惠券配置") |
| | | public ApiResponse<PageResult<Map<String, Object>>> page( |
| | | @Parameter(description = "\u9002\u7528\u5546\u6237ID") @RequestParam(required = false) Long merchantId, |
| | | @Parameter(description = "\u4f18\u60e0\u5238\u7c7b\u578b") @RequestParam(required = false) String couponType, |
| | | @Parameter(description = "\u53d1\u653e\u6279\u6b21\u53f7") @RequestParam(required = false) String batchNo, |
| | | @Parameter(description = "\u72b6\u6001\uff1a0\u4e0b\u67b6\u30011\u4e0a\u67b6") @RequestParam(required = false) Integer status, |
| | | @Parameter(description = "\u4f18\u60e0\u5238\u6216\u5546\u6237\u540d\u79f0\u5173\u952e\u8bcd") @RequestParam(required = false) String keyword, |
| | | @Parameter(description = "\u67e5\u8be2\u6307\u5b9a\u65e5\u671f\u6709\u6548\u7684\u4f18\u60e0\u5238") @RequestParam(required = false) LocalDate validOn, |
| | | @Parameter(description = "适用商户ID") @RequestParam(required = false) Long merchantId, |
| | | @Parameter(description = "优惠券类型") @RequestParam(required = false) String couponType, |
| | | @Parameter(description = "发放批次号") @RequestParam(required = false) String batchNo, |
| | | @Parameter(description = "状态:0下架、1上架") @RequestParam(required = false) Integer status, |
| | | @Parameter(description = "优惠券或商户名称关键词") @RequestParam(required = false) String keyword, |
| | | @Parameter(description = "查询指定日期有效的优惠券") @RequestParam(required = false) LocalDate validOn, |
| | | @RequestParam(defaultValue = "1") int page, |
| | | @RequestParam(defaultValue = "10") int size) { |
| | | return ApiResponse.success(service.coupons(merchantId, couponType, batchNo, status, keyword, validOn, page, size)); |
| | | } |
| | | |
| | | @GetMapping("/{id}") |
| | | @Operation(summary = "\u67e5\u8be2\u4f18\u60e0\u5238\u914d\u7f6e\u8be6\u60c5") |
| | | @Operation(summary = "查询优惠券配置详情") |
| | | public ApiResponse<Map<String, Object>> detail(@PathVariable Long id) { |
| | | return ApiResponse.success(service.coupon(id)); |
| | | } |
| | | |
| | | @PostMapping |
| | | @Operation(summary = "\u65b0\u589e\u4f18\u60e0\u5238\u914d\u7f6e") |
| | | @Operation(summary = "新增优惠券配置") |
| | | public ApiResponse<Map<String, Object>> create(@Valid @RequestBody CouponManagementSaveRequest request) { |
| | | return ApiResponse.success(service.createCoupon(request)); |
| | | } |
| | | |
| | | @PutMapping("/{id}") |
| | | @Operation(summary = "\u4fee\u6539\u4f18\u60e0\u5238\u914d\u7f6e") |
| | | @Operation(summary = "修改优惠券配置") |
| | | public ApiResponse<Map<String, Object>> update(@PathVariable Long id, |
| | | @Valid @RequestBody CouponManagementSaveRequest request) { |
| | | return ApiResponse.success(service.updateCoupon(id, request)); |
| | | } |
| | | |
| | | @PatchMapping("/{id}/status") |
| | | @Operation(summary = "\u4f18\u60e0\u5238\u4e0a\u67b6\u6216\u4e0b\u67b6") |
| | | @Operation(summary = "优惠券上架或下架") |
| | | public ApiResponse<Map<String, Object>> status(@PathVariable Long id, |
| | | @Valid @RequestBody CouponManagementStatusRequest request) { |
| | | return ApiResponse.success(service.updateCouponStatus(id, request.status())); |
| | | } |
| | | |
| | | @PostMapping("/{id}/claims") |
| | | @Operation(summary = "\u540e\u53f0\u53d1\u653e\u4f18\u60e0\u5238", description = "\u6309\u53d1\u653e\u6279\u6b21\u548c\u53d1\u653e\u603b\u91cf\u4e3a\u6307\u5b9a\u6301\u5361\u4eba\u751f\u6210\u9886\u53d6\u8bb0\u5f55") |
| | | @Operation(summary = "后台发放优惠券", description = "按发放批次和发放总量为指定持卡人生成领取记录") |
| | | public ApiResponse<Map<String, Object>> issue(@PathVariable Long id, |
| | | @Valid @RequestBody CouponClaimIssueRequest request) { |
| | | return ApiResponse.success(service.issue(id, request)); |
| | | } |
| | | |
| | | @GetMapping("/claims") |
| | | @Operation(summary = "\u5206\u9875\u67e5\u8be2\u4f18\u60e0\u5238\u9886\u53d6\u548c\u4f7f\u7528\u8bb0\u5f55") |
| | | @Operation(summary = "分页查询优惠券领取和使用记录") |
| | | public ApiResponse<PageResult<Map<String, Object>>> claims( |
| | | @RequestParam(required = false) Long couponId, |
| | | @RequestParam(required = false) Long merchantId, |
| | | @RequestParam(required = false) String batchNo, |
| | | @Parameter(description = "\u9886\u53d6\u72b6\u6001\uff1a0\u5f85\u9886\u53d6\u30011\u5df2\u9886\u53d6\u30012\u5df2\u53d6\u6d88") @RequestParam(required = false) Integer claimStatus, |
| | | @Parameter(description = "\u4f7f\u7528\u72b6\u6001\uff1a0\u672a\u4f7f\u7528\u30011\u5df2\u4f7f\u7528\u30012\u5df2\u8fc7\u671f\u30013\u5df2\u4f5c\u5e9f") @RequestParam(required = false) Integer useStatus, |
| | | @Parameter(description = "\u9886\u53d6\u7f16\u53f7/\u6301\u5361\u4eba/\u624b\u673a\u53f7/\u5361\u53f7\u5173\u952e\u8bcd") @RequestParam(required = false) String keyword, |
| | | @Parameter(description = "领取状态:0待领取、1已领取、2已取消") @RequestParam(required = false) Integer claimStatus, |
| | | @Parameter(description = "使用状态:0未使用、1已使用、2已过期、3已作废") @RequestParam(required = false) Integer useStatus, |
| | | @Parameter(description = "领取编号/持卡人/手机号/卡号关键词") @RequestParam(required = false) String keyword, |
| | | @RequestParam(required = false) LocalDate startDate, |
| | | @RequestParam(required = false) LocalDate endDate, |
| | | @RequestParam(defaultValue = "1") int page, |
| | |
| | | } |
| | | |
| | | @GetMapping("/claims/{claimId}") |
| | | @Operation(summary = "\u67e5\u8be2\u4f18\u60e0\u5238\u9886\u53d6\u8bb0\u5f55\u8be6\u60c5") |
| | | @Operation(summary = "查询优惠券领取记录详情") |
| | | public ApiResponse<Map<String, Object>> claim(@PathVariable Long claimId) { |
| | | return ApiResponse.success(service.claim(claimId)); |
| | | } |
| | | |
| | | @PatchMapping("/claims/{claimId}/status") |
| | | @Operation(summary = "\u7ef4\u62a4\u4f18\u60e0\u5238\u9886\u53d6\u548c\u4f7f\u7528\u72b6\u6001") |
| | | @Operation(summary = "维护优惠券领取和使用状态") |
| | | public ApiResponse<Map<String, Object>> claimStatus(@PathVariable Long claimId, |
| | | @Valid @RequestBody CouponClaimStatusRequest request) { |
| | | return ApiResponse.success(service.updateClaimStatus(claimId, request)); |
| | |
| | | |
| | | @RestController |
| | | @RequestMapping("/api/admin/car-wash-benefits") |
| | | @Tag(name = "\u540e\u53f0-\u6d17\u8f66\u6743\u76ca\u7ba1\u7406", description = "\u6d17\u8f66\u670d\u52a1\u914d\u7f6e\u3001\u51ed\u8bc1\u4eba\u5de5\u5ba1\u6838\u3001\u6743\u76ca\u6b21\u6570\u6263\u51cf\u4e0e\u56de\u6eda") |
| | | @Tag(name = "后台-洗车权益管理", description = "洗车服务配置、凭证人工审核、权益次数扣减与回滚") |
| | | public class CarWashBenefitAdminController { |
| | | private final CarWashBenefitManagementService service; |
| | | public CarWashBenefitAdminController(CarWashBenefitManagementService service) { this.service = service; } |
| | | |
| | | @GetMapping("/merchants/{merchantId}/rules") |
| | | @Operation(summary = "\u67e5\u8be2\u6d17\u8f66\u6743\u76ca\u6b21\u6570\u914d\u7f6e") |
| | | @Operation(summary = "查询洗车权益次数配置") |
| | | public ApiResponse<Map<String, Object>> rules(@PathVariable Long merchantId) { return ApiResponse.success(service.rules(merchantId)); } |
| | | |
| | | @PutMapping("/merchants/{merchantId}/rules") |
| | | @Operation(summary = "\u4fdd\u5b58\u6d17\u8f66\u6743\u76ca\u6b21\u6570\u914d\u7f6e", description = "\u6309\u4eba\u624d\u5361\u7b49\u7ea7\u914d\u7f6e\u603b\u6b21\u6570\u3001\u91cd\u7f6e\u5468\u671f\u548c\u4eb2\u5c5e\u9644\u5c5e\u5361\u5171\u4eab") |
| | | @Operation(summary = "保存洗车权益次数配置", description = "按人才卡等级配置总次数、重置周期和亲属附属卡共享") |
| | | public ApiResponse<Map<String, Object>> saveRules(@PathVariable Long merchantId, @Valid @RequestBody CarWashBenefitRuleRequest request) { return ApiResponse.success(service.saveRules(merchantId, request)); } |
| | | |
| | | @GetMapping("/credentials") |
| | | @Operation(summary = "\u67e5\u8be2\u6d17\u8f66\u51ed\u8bc1\u5217\u8868") |
| | | @Operation(summary = "查询洗车凭证列表") |
| | | public ApiResponse<PageResult<Map<String, Object>>> credentials( |
| | | @RequestParam(required = false) Long merchantId, |
| | | @Parameter(description = "\u72b6\u6001\uff1a0\u5f85\u5ba1\u6838\u30011\u5df2\u901a\u8fc7\u30012\u5df2\u9a73\u56de\u30013\u5df2\u56de\u6eda") @RequestParam(required = false) Integer status, |
| | | @Parameter(description = "\u51ed\u8bc1\u7f16\u53f7/\u59d3\u540d/\u624b\u673a\u53f7/\u5361\u53f7") @RequestParam(required = false) String keyword, |
| | | @Parameter(description = "状态:0待审核、1已通过、2已驳回、3已回滚") @RequestParam(required = false) Integer status, |
| | | @Parameter(description = "凭证编号/姓名/手机号/卡号") @RequestParam(required = false) String keyword, |
| | | @RequestParam(required = false) LocalDate startDate, |
| | | @RequestParam(required = false) LocalDate endDate, |
| | | @RequestParam(defaultValue = "1") int page, |
| | |
| | | } |
| | | |
| | | @GetMapping("/credentials/{id}") |
| | | @Operation(summary = "\u67e5\u8be2\u6d17\u8f66\u51ed\u8bc1\u8be6\u60c5") |
| | | @Operation(summary = "查询洗车凭证详情") |
| | | public ApiResponse<Map<String, Object>> detail(@PathVariable Long id) { return ApiResponse.success(service.detail(id)); } |
| | | |
| | | @PatchMapping("/credentials/{id}/audit") |
| | | @Operation(summary = "\u4eba\u5de5\u5ba1\u6838\u6d17\u8f66\u51ed\u8bc1", description = "\u901a\u8fc7\u65f6\u6821\u9a8c\u6743\u76ca\u5269\u4f59\u6b21\u6570\u5e76\u751f\u6210\u6838\u9500\u6263\u51cf\u6d41\u6c34") |
| | | @Operation(summary = "人工审核洗车凭证", description = "通过时校验权益剩余次数并生成核销扣减流水") |
| | | public ApiResponse<Map<String, Object>> audit(@PathVariable Long id, |
| | | @Valid @RequestBody CarWashCredentialAuditRequest request, |
| | | @RequestAttribute(AdminAuthInterceptor.OPERATOR_ID_ATTRIBUTE) Long operatorId) { |
| | |
| | | } |
| | | |
| | | @PostMapping("/credentials/{id}/rollback") |
| | | @Operation(summary = "\u56de\u6eda\u6d17\u8f66\u6743\u76ca\u6263\u51cf", description = "\u64a4\u9500\u5bf9\u5e94\u6838\u9500\u6d41\u6c34\uff0c\u6743\u76ca\u5269\u4f59\u6b21\u6570\u81ea\u52a8\u6062\u590d") |
| | | @Operation(summary = "回滚洗车权益扣减", description = "撤销对应核销流水,权益剩余次数自动恢复") |
| | | public ApiResponse<Map<String, Object>> rollback(@PathVariable Long id, |
| | | @Valid @RequestBody CarWashCredentialRollbackRequest request, |
| | | @RequestAttribute(AdminAuthInterceptor.OPERATOR_ID_ATTRIBUTE) Long operatorId) { |
| | |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | @Tag(name = "\u7ba1\u7406\u540e\u53f0-\u5bb6\u5c5e\u9644\u5c5e\u5361\u5ba1\u6838", description = "\u5bb6\u5c5e\u9644\u5c5e\u5361\u7533\u8bf7\u67e5\u8be2\u3001\u5ba1\u6838\u548c\u72b6\u6001\u8bb0\u5f55") |
| | | @Tag(name = "管理后台-家属附属卡审核", description = "家属附属卡申请查询、审核和状态记录") |
| | | @RestController |
| | | @RequestMapping("/api/admin/dependent-card-applies") |
| | | public class DependentCardApplyAdminController { |
| | |
| | | this.service = service; |
| | | } |
| | | |
| | | @Operation(summary = "\u5206\u9875\u67e5\u8be2\u5bb6\u5c5e\u9644\u5c5e\u5361\u7533\u8bf7") |
| | | @Operation(summary = "分页查询家属附属卡申请") |
| | | @GetMapping |
| | | public ApiResponse<PageResult<DependentCardApplyAdminResponse>> page( |
| | | @Parameter(description = "\u4e3b\u5361ID") @RequestParam(required = false) Long talentCardId, |
| | | @Parameter(description = "\u72b6\u6001\uff1a0\u5f85\u5ba1\u6838\u30011\u5df2\u901a\u8fc7\u30012\u5df2\u9a73\u56de\u30013\u5df2\u89e3\u7ed1") @RequestParam(required = false) Integer status, |
| | | @Parameter(description = "\u9875\u7801") @RequestParam(defaultValue = "1") int page, |
| | | @Parameter(description = "\u6bcf\u9875\u6570\u91cf\uff0c\u6700\u5927100") @RequestParam(defaultValue = "10") int size |
| | | @Parameter(description = "主卡ID") @RequestParam(required = false) Long talentCardId, |
| | | @Parameter(description = "状态:0待审核、1已通过、2已驳回、3已解绑") @RequestParam(required = false) Integer status, |
| | | @Parameter(description = "页码") @RequestParam(defaultValue = "1") int page, |
| | | @Parameter(description = "每页数量,最大100") @RequestParam(defaultValue = "10") int size |
| | | ) { |
| | | return ApiResponse.success(service.page(talentCardId, status, page, size)); |
| | | } |
| | | |
| | | @Operation(summary = "\u67e5\u8be2\u5bb6\u5c5e\u9644\u5c5e\u5361\u7533\u8bf7\u8be6\u60c5") |
| | | @Operation(summary = "查询家属附属卡申请详情") |
| | | @GetMapping("/{id}") |
| | | public ApiResponse<DependentCardApplyAdminResponse> detail(@PathVariable Long id) { |
| | | return ApiResponse.success(service.detail(id)); |
| | | } |
| | | |
| | | @Operation(summary = "\u5ba1\u6838\u5bb6\u5c5e\u9644\u5c5e\u5361\u7533\u8bf7", description = "\u4ec5\u5f85\u5ba1\u6838\u7533\u8bf7\u53ef\u4ee5\u53d8\u66f4\u4e3a\u901a\u8fc7\u6216\u9a73\u56de\uff1b\u901a\u8fc7\u65f6\u6821\u9a8c\u4e3b\u5361\u6743\u9650\u548c\u5bb6\u5c5e\u5361\u540d\u989d") |
| | | @Operation(summary = "审核家属附属卡申请", description = "仅待审核申请可以变更为通过或驳回;通过时校验主卡权限和家属卡名额") |
| | | @ApiResponses({ |
| | | @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "\u5ba1\u6838\u6210\u529f"), |
| | | @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "\u5ba1\u6838\u5931\u8d25\u3001\u7533\u8bf7\u4e0d\u5b58\u5728\u3001\u72b6\u6001\u51b2\u7a81\u6216\u7ed1\u5b9a\u540d\u989d\u5df2\u6ee1") |
| | | @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "审核成功"), |
| | | @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "审核失败、申请不存在、状态冲突或绑定名额已满") |
| | | }) |
| | | @PatchMapping("/{id}/audit") |
| | | public ApiResponse<DependentCardApplyAdminResponse> audit( |
| | |
| | | return ApiResponse.success(service.audit(id, request, operatorId)); |
| | | } |
| | | |
| | | @Operation(summary = "\u67e5\u8be2\u5bb6\u5c5e\u9644\u5c5e\u5361\u7533\u8bf7\u72b6\u6001\u8bb0\u5f55") |
| | | @Operation(summary = "查询家属附属卡申请状态记录") |
| | | @GetMapping("/{id}/status-logs") |
| | | public ApiResponse<List<DependentCardApplyStatusLogResponse>> statusLogs(@PathVariable Long id) { |
| | | return ApiResponse.success(service.statusLogs(id)); |
| | |
| | | |
| | | @RestController |
| | | @RequestMapping("/api/app/car-wash-merchants") |
| | | @Tag(name = "\u5c0f\u7a0b\u5e8f-\u6d17\u8f66\u51ed\u8bc1", description = "\u7528\u6237\u63d0\u4ea4\u6d17\u8f66\u51ed\u8bc1\u6216\u6d88\u8d39\u622a\u56fe") |
| | | @Tag(name = "小程序-洗车凭证", description = "用户提交洗车凭证或消费截图") |
| | | public class CarWashCredentialPortalController { |
| | | private final CarWashBenefitManagementService service; |
| | | public CarWashCredentialPortalController(CarWashBenefitManagementService service) { this.service = service; } |
| | | |
| | | @PostMapping("/{merchantId}/credentials") |
| | | @Operation(summary = "\u63d0\u4ea4\u6d17\u8f66\u51ed\u8bc1", description = "\u5148\u8c03\u7528\u5c0f\u7a0b\u5e8f\u56fe\u7247\u4e0a\u4f20\u63a5\u53e3\uff0c\u518d\u5c06\u8fd4\u56de\u7684URL\u5217\u8868\u4f5c\u4e3a\u51ed\u8bc1\u63d0\u4ea4") |
| | | @Operation(summary = "提交洗车凭证", description = "先调用小程序图片上传接口,再将返回的URL列表作为凭证提交") |
| | | public ApiResponse<Map<String, Object>> submit(@PathVariable Long merchantId, |
| | | @Valid @RequestBody CarWashCredentialSubmitRequest request, |
| | | @Parameter(description = "\u5e42\u7b49\u63d0\u4ea4\u51ed\u8bc1") @RequestHeader(value = "X-Idempotency-Key", required = false) String idempotencyKey) { |
| | | @Parameter(description = "幂等提交凭证") @RequestHeader(value = "X-Idempotency-Key", required = false) String idempotencyKey) { |
| | | return ApiResponse.success(service.submit(merchantId, request, idempotencyKey)); |
| | | } |
| | | |
| | | @GetMapping("/credentials/{id}") |
| | | @Operation(summary = "\u67e5\u8be2\u6d17\u8f66\u51ed\u8bc1\u5ba1\u6838\u7ed3\u679c") |
| | | @Operation(summary = "查询洗车凭证审核结果") |
| | | public ApiResponse<Map<String, Object>> detail(@PathVariable Long id) { return ApiResponse.success(service.detail(id)); } |
| | | } |
| | |
| | | public ApiResponse<Map<String, Object>> detail(@PathVariable Long merchantId) { |
| | | return ApiResponse.success(service.detail(merchantId)); |
| | | } |
| | | } |
| | | } |
| | |
| | | Map<String, Object> merchant = one(""" |
| | | select id, merchant_name, contact_mobile, address, cover_url, service_label, status |
| | | from merchant |
| | | where id = ? and merchant_type = 'carWash' and deleted = 0 |
| | | where id = ? and merchant_type in ('carWash', '自助洗车') and deleted = 0 |
| | | """, merchantId); |
| | | Map<String, Object> service = one(""" |
| | | select id, talent_levels, service_point_text, tip_text, external_link_type, |
| | |
| | | return jdbc.queryForList(""" |
| | | select f.id, f.family_name, f.family_mobile, f.relationship, f.status |
| | | from family_card_application f |
| | | where f.deleted = 0 and f.talent_id = ? and f.status <> '\u5df2\u89e3\u7ed1' |
| | | where f.deleted = 0 and f.talent_id = ? and f.status <> '已解绑' |
| | | order by f.apply_time desc, f.id desc |
| | | """, talentId).stream().map(DatabaseRows::normalize).map(this::dependentCardItem).toList(); |
| | | } |
| | |
| | | where f.id = ? and f.talent_id = ? and f.deleted = 0 |
| | | limit 1 |
| | | """, id, talentId).stream().map(DatabaseRows::normalize).toList(); |
| | | if (rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "\u5bb6\u5c5e\u5361\u7533\u8bf7\u4e0d\u5b58\u5728"); |
| | | if (rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "家属卡申请不存在"); |
| | | return dependentCardItem(rows.get(0)); |
| | | } |
| | | @Transactional |
| | |
| | | if (!"卓越人才卡".equals(string(owner.get("cardLevel")))) throw new BusinessException(ErrorCode.CONFLICT, "家庭附属卡仅向卓越人才卡用户开放"); |
| | | if (!List.of("配偶", "父母", "子女", "其他").contains(request.relationship().trim())) throw new BusinessException(ErrorCode.BAD_REQUEST, "不支持的家属关系"); |
| | | if (!validIdCard(request.idCard().trim())) throw new BusinessException(ErrorCode.BAD_REQUEST, "身份证号格式不正确"); |
| | | Integer submitted = jdbc.queryForObject("select count(*) from family_card_application where deleted = 0 and talent_id = ? and status in ('\u5f85\u5ba1\u6838', '\u5df2\u901a\u8fc7', '\u5df2\u9a73\u56de')", Integer.class, request.talentId()); |
| | | if (submitted != null && submitted >= 3) throw new BusinessException(ErrorCode.CONFLICT, "\u540c\u4e00\u5f20\u4e3b\u5361\u6700\u591a\u63d0\u4ea43\u4e2a\u5bb6\u5c5e\u9644\u5c5e\u5361\u7533\u8bf7"); |
| | | Integer occupied = jdbc.queryForObject("select count(*) from family_card_application where deleted = 0 and talent_id = ? and status in ('\u5f85\u5ba1\u6838', '\u5df2\u901a\u8fc7')", Integer.class, request.talentId()); |
| | | if (occupied != null && occupied >= 2) throw new BusinessException(ErrorCode.CONFLICT, "\u8be5\u4e3b\u5361\u5bb6\u5c5e\u9644\u5c5e\u5361\u540d\u989d\u5df2\u6ee1\uff0c\u6700\u591a\u7ed1\u5b9a2\u4e2a"); |
| | | Integer submitted = jdbc.queryForObject("select count(*) from family_card_application where deleted = 0 and talent_id = ? and status in ('待审核', '已通过', '已驳回')", Integer.class, request.talentId()); |
| | | if (submitted != null && submitted >= 3) throw new BusinessException(ErrorCode.CONFLICT, "同一张主卡最多提交3个家属附属卡申请"); |
| | | Integer occupied = jdbc.queryForObject("select count(*) from family_card_application where deleted = 0 and talent_id = ? and status in ('待审核', '已通过')", Integer.class, request.talentId()); |
| | | if (occupied != null && occupied >= 2) throw new BusinessException(ErrorCode.CONFLICT, "该主卡家属附属卡名额已满,最多绑定2个"); |
| | | Integer duplicate = jdbc.queryForObject(""" |
| | | select count(*) from family_card_application |
| | | where deleted = 0 and talent_id = ? and id_card_no = ? and status in ('待审核', '已通过') |
| | |
| | | } |
| | | |
| | | private Long talentIdByCardId(Long talentCardId) { |
| | | if (talentCardId == null || talentCardId <= 0) throw new BusinessException(ErrorCode.NOT_FOUND, "\u4e3b\u5361\u4e0d\u5b58\u5728\u6216\u5df2\u5931\u6548"); |
| | | if (talentCardId == null || talentCardId <= 0) throw new BusinessException(ErrorCode.NOT_FOUND, "主卡不存在或已失效"); |
| | | List<Map<String, Object>> rows = jdbc.queryForList(""" |
| | | select talent_id from talent_card |
| | | where id = ? and deleted = 0 and card_type = 'PERSONAL' and status = 1 |
| | | limit 1 |
| | | """, talentCardId).stream().map(DatabaseRows::normalize).toList(); |
| | | if (rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "\u4e3b\u5361\u4e0d\u5b58\u5728\u6216\u5df2\u5931\u6548"); |
| | | if (rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "主卡不存在或已失效"); |
| | | return number(rows.get(0).get("talentId")); |
| | | } |
| | | |
| | | private DependentCardApplyItemResponse dependentCardItem(Map<String, Object> row) { |
| | | Integer status = switch (string(row.get("status"))) { |
| | | case "\u5f85\u5ba1\u6838" -> 0; |
| | | case "\u5df2\u901a\u8fc7" -> 1; |
| | | case "\u5df2\u9a73\u56de" -> 2; |
| | | case "\u5df2\u89e3\u7ed1" -> 3; |
| | | case "待审核" -> 0; |
| | | case "已通过" -> 1; |
| | | case "已驳回" -> 2; |
| | | case "已解绑" -> 3; |
| | | default -> -1; |
| | | }; |
| | | String statusText = switch (status) { |
| | | case 0 -> "\u5f85\u5ba1\u6838"; |
| | | case 1 -> "\u5df2\u901a\u8fc7"; |
| | | case 2 -> "\u5df2\u9a73\u56de"; |
| | | case 3 -> "\u5df2\u89e3\u7ed1"; |
| | | default -> "\u672a\u77e5"; |
| | | case 0 -> "待审核"; |
| | | case 1 -> "已通过"; |
| | | case 2 -> "已驳回"; |
| | | case 3 -> "已解绑"; |
| | | default -> "未知"; |
| | | }; |
| | | return new DependentCardApplyItemResponse( |
| | | number(row.get("id")), string(row.get("familyName")), |
| | |
| | | return one(""" |
| | | select id, merchant_name, contact_mobile, address, cover_url, service_label, status, latitude, longitude |
| | | from merchant |
| | | where id = ? and merchant_type = 'parking' and deleted = 0 |
| | | where id = ? and merchant_type in ('parking', '停车服务') and deleted = 0 |
| | | """, merchantId); |
| | | } |
| | | |
| | |
| | | String merchantWhere = " where deleted = 0 and merchant_type = ?"; |
| | | if (type.equals("parking")) { |
| | | type = "停车服务"; |
| | | }if (type.equals("carWash")) { |
| | | type = "自助洗车"; |
| | | } |
| | | PageResult<Map<String, Object>> result = page(""" |
| | | select id, merchant_name, merchant_type, contact_name, contact_mobile, logo_url, cover_url, address, |
| | |
| | | if (value == null) return ""; |
| | | String raw = String.valueOf(value).trim(); |
| | | String upper = raw.toUpperCase(Locale.ROOT); |
| | | if (upper.contains("EXCELLENT") || raw.contains("\u5353\u8d8a")) return "EXCELLENT"; |
| | | if (upper.contains("OUTSTANDING") || raw.contains("\u6770\u51fa")) return "OUTSTANDING"; |
| | | if (upper.contains("ELITE") || raw.contains("\u83c1\u82f1") || raw.contains("\u7cbe\u82f1")) return "ELITE"; |
| | | if (upper.contains("EXCELLENT") || raw.contains("卓越")) return "EXCELLENT"; |
| | | if (upper.contains("OUTSTANDING") || raw.contains("杰出")) return "OUTSTANDING"; |
| | | if (upper.contains("ELITE") || raw.contains("菁英") || raw.contains("精英")) return "ELITE"; |
| | | return upper; |
| | | } |
| | | |
| | |
| | | |
| | | @ExceptionHandler(Exception.class) |
| | | public ResponseEntity<ApiResponse<Void>> exception(Exception exception) { |
| | | return error("\u4f18\u60e0\u5238\u914d\u7f6e\u5904\u7406\u5931\u8d25"); |
| | | return error("优惠券配置处理失败"); |
| | | } |
| | | |
| | | private ResponseEntity<ApiResponse<Void>> error(String message) { |
| | | String fallback = "\u4f18\u60e0\u5238\u914d\u7f6e\u5904\u7406\u5931\u8d25"; |
| | | String fallback = "优惠券配置处理失败"; |
| | | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) |
| | | .body(ApiResponse.failure(500, message == null || message.isBlank() ? fallback : message)); |
| | | } |
| | |
| | | @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class, |
| | | ConstraintViolationException.class, HttpMessageNotReadableException.class}) |
| | | public ResponseEntity<ApiResponse<Void>> handleRequestException(Exception exception) { |
| | | return error("\u5ba1\u6838\u53c2\u6570\u4e0d\u6b63\u786e"); |
| | | return error("审核参数不正确"); |
| | | } |
| | | |
| | | @ExceptionHandler(Exception.class) |
| | | public ResponseEntity<ApiResponse<Void>> handleException(Exception exception) { |
| | | return error("\u5bb6\u5c5e\u9644\u5c5e\u5361\u7533\u8bf7\u5904\u7406\u5931\u8d25"); |
| | | return error("家属附属卡申请处理失败"); |
| | | } |
| | | |
| | | private ResponseEntity<ApiResponse<Void>> error(String message) { |
| | | String fallback = "\u5bb6\u5c5e\u9644\u5c5e\u5361\u7533\u8bf7\u5904\u7406\u5931\u8d25"; |
| | | String fallback = "家属附属卡申请处理失败"; |
| | | String safeMessage = message == null || message.isBlank() ? fallback : message; |
| | | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) |
| | | .body(ApiResponse.failure(500, safeMessage)); |
| | |
| | | u.name as talentName, u.mobile as talentMobile |
| | | from talent_card c join talent_user u on u.id = c.talent_id and u.deleted = 0 |
| | | where c.id = #{cardId} and c.talent_id = #{talentId} and c.deleted = 0 and c.status = 1 |
| | | and (c.valid_start is null or c.valid_start <= current_date) |
| | | and (c.valid_end is null or c.valid_end >= current_date) |
| | | and (c.expire_time is null or c.expire_time > now()) |
| | | and (c.valid_start is null or c.valid_start <= current_date) |
| | | and (c.valid_end is null or c.valid_end >= current_date) |
| | | and (c.expire_time is null or c.expire_time > now()) |
| | | limit 1 |
| | | """) |
| | | Map<String, Object> selectActiveCard(@Param("talentId") Long talentId, @Param("cardId") Long cardId); |
| | |
| | | from merchant m |
| | | left join transport_car_wash_service s on s.merchant_id = m.id and s.deleted = 0 |
| | | where m.deleted = 0 |
| | | and m.merchant_type = 'carWash' |
| | | and m.merchant_type = '自助洗车' |
| | | and (#{keyword} is null or #{keyword} = '' or m.merchant_name like concat('%', #{keyword}, '%') |
| | | or m.address like concat('%', #{keyword}, '%') or m.contact_mobile like concat('%', #{keyword}, '%')) |
| | | and (#{merchantId} is null or m.id = #{merchantId}) |
| | |
| | | select count(*) |
| | | from merchant m |
| | | where m.deleted = 0 |
| | | and m.merchant_type = 'carWash' |
| | | and m.merchant_type = '自助洗车' |
| | | and (#{keyword} is null or #{keyword} = '' or m.merchant_name like concat('%', #{keyword}, '%') |
| | | or m.address like concat('%', #{keyword}, '%') or m.contact_mobile like concat('%', #{keyword}, '%')) |
| | | and (#{merchantId} is null or m.id = #{merchantId}) |
| | |
| | | from merchant m |
| | | left join transport_car_wash_service s on s.merchant_id = m.id and s.deleted = 0 |
| | | where m.deleted = 0 |
| | | and m.merchant_type = 'carWash' |
| | | and m.merchant_type = '自助洗车' |
| | | and m.id = #{merchantId} |
| | | limit 1 |
| | | """) |
| | |
| | | from merchant m |
| | | left join transport_car_wash_service s on s.merchant_id = m.id and s.deleted = 0 |
| | | where m.deleted = 0 |
| | | and m.merchant_type = 'carWash' |
| | | and m.merchant_type = '自助洗车' |
| | | and m.id = #{merchantId} |
| | | limit 1 |
| | | """) |
| | |
| | | @Select(""" |
| | | select count(*) |
| | | from merchant |
| | | where deleted = 0 and merchant_type = 'carWash' and id = #{merchantId} |
| | | where deleted = 0 and merchant_type = '自助洗车' and id = #{merchantId} |
| | | """) |
| | | Long countCarWashMerchant(@Param("merchantId") Long merchantId); |
| | | } |
| | |
| | | b.action_mode as actionMode, |
| | | b.status as benefitStatus |
| | | from transport_parking_card_level_right r |
| | | join merchant m on m.id = r.merchant_id and m.deleted = 0 and m.merchant_type = 'parking' |
| | | join merchant m on m.id = r.merchant_id and m.deleted = 0 and m.merchant_type in ('parking', '停车服务') |
| | | left join transport_parking_benefit b on b.merchant_id = m.id and b.deleted = 0 |
| | | where r.deleted = 0 |
| | | and (#{merchantId} is null or r.merchant_id = #{merchantId}) |
| | |
| | | @Select(""" |
| | | select count(*) |
| | | from transport_parking_card_level_right r |
| | | join merchant m on m.id = r.merchant_id and m.deleted = 0 and m.merchant_type = 'parking' |
| | | join merchant m on m.id = r.merchant_id and m.deleted = 0 and m.merchant_type in ('parking', '停车服务') |
| | | where r.deleted = 0 |
| | | and (#{merchantId} is null or r.merchant_id = #{merchantId}) |
| | | and (#{cardLevel} is null or #{cardLevel} = '' or r.card_level = #{cardLevel}) |
| | |
| | | b.action_mode as actionMode, |
| | | b.status as benefitStatus |
| | | from transport_parking_card_level_right r |
| | | join merchant m on m.id = r.merchant_id and m.deleted = 0 and m.merchant_type = 'parking' |
| | | join merchant m on m.id = r.merchant_id and m.deleted = 0 and m.merchant_type in ('parking', '停车服务') |
| | | left join transport_parking_benefit b on b.merchant_id = m.id and b.deleted = 0 |
| | | where r.deleted = 0 |
| | | and r.merchant_id = #{merchantId} |
| | |
| | | b.action_mode as actionMode, |
| | | b.status as benefitStatus |
| | | from transport_parking_card_level_right r |
| | | join merchant m on m.id = r.merchant_id and m.deleted = 0 and m.merchant_type = 'parking' and m.status = 1 |
| | | join merchant m on m.id = r.merchant_id and m.deleted = 0 and m.merchant_type in ('parking', '停车服务') and m.status = 1 |
| | | join transport_parking_benefit b on b.merchant_id = m.id and b.deleted = 0 and b.status = 1 |
| | | where r.deleted = 0 |
| | | and r.enabled = 1 |
| | |
| | | left join transport_parking_benefit b on b.merchant_id = m.id and b.deleted = 0 |
| | | where m.id = #{merchantId} |
| | | and m.deleted = 0 |
| | | and m.merchant_type = 'parking' |
| | | and m.merchant_type in ('parking', '停车服务') |
| | | limit 1 |
| | | """) |
| | | Map<String, Object> selectParkingMerchant(@Param("merchantId") Long merchantId); |
| | |
| | | |
| | | import java.util.List; |
| | | |
| | | @Schema(name = "CarWashBenefitRuleRequest", description = "\u6d17\u8f66\u6743\u76ca\u6b21\u6570\u914d\u7f6e") |
| | | @Schema(name = "CarWashBenefitRuleRequest", description = "洗车权益次数配置") |
| | | public record CarWashBenefitRuleRequest( |
| | | @NotEmpty List<@Valid Rule> rules |
| | | ) { |
| | | public record Rule( |
| | | @NotBlank String cardLevel, |
| | | @NotNull @Min(1) Integer totalCount, |
| | | @NotBlank @Schema(description = "\u91cd\u7f6e\u5468\u671f\uff1aDAY\u3001WEEK\u3001MONTH\u3001YEAR\u3001NONE") String resetCycle, |
| | | @NotBlank @Schema(description = "重置周期:DAY、WEEK、MONTH、YEAR、NONE") String resetCycle, |
| | | Boolean familyCardShared |
| | | ) {} |
| | | } |
| | |
| | | import jakarta.validation.constraints.NotNull; |
| | | import jakarta.validation.constraints.Size; |
| | | |
| | | @Schema(name = "CarWashCredentialAuditRequest", description = "\u6d17\u8f66\u51ed\u8bc1\u4eba\u5de5\u5ba1\u6838\u53c2\u6570") |
| | | @Schema(name = "CarWashCredentialAuditRequest", description = "洗车凭证人工审核参数") |
| | | public record CarWashCredentialAuditRequest( |
| | | @NotNull @Schema(description = "\u5ba1\u6838\u72b6\u6001\uff1a1\u901a\u8fc7\u30012\u9a73\u56de") Integer status, |
| | | @Schema(description = "\u6263\u51cf\u6b21\u6570\uff0c\u901a\u8fc7\u65f6\u9ed8\u8ba41") Integer quantity, |
| | | @NotNull @Schema(description = "审核状态:1通过、2驳回") Integer status, |
| | | @Schema(description = "扣减次数,通过时默认1") Integer quantity, |
| | | @Size(max = 500) String auditRemark |
| | | ) {} |
| | |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import jakarta.validation.constraints.Size; |
| | | |
| | | @Schema(name = "CarWashCredentialRollbackRequest", description = "\u6d17\u8f66\u6743\u76ca\u6263\u51cf\u56de\u6eda\u53c2\u6570") |
| | | @Schema(name = "CarWashCredentialRollbackRequest", description = "洗车权益扣减回滚参数") |
| | | public record CarWashCredentialRollbackRequest( |
| | | @NotBlank @Size(max = 500) @Schema(description = "\u56de\u6eda\u539f\u56e0") String reason |
| | | @NotBlank @Size(max = 500) @Schema(description = "回滚原因") String reason |
| | | ) {} |
| | |
| | | package com.talentcard.server.request; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import jakarta.validation.constraints.Size; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | @Schema(name = "CarWashCredentialSubmitRequest", description = "\u6d17\u8f66\u51ed\u8bc1\u63d0\u4ea4\u53c2\u6570") |
| | | @Schema(name = "CarWashCredentialSubmitRequest", description = "洗车凭证提交参数") |
| | | public record CarWashCredentialSubmitRequest( |
| | | @NotNull Long talentId, |
| | | @NotNull Long cardId, |
| | | @NotEmpty @Size(max = 9) @Schema(description = "\u6d17\u8f66\u51ed\u8bc1\u6216\u6d88\u8d39\u622a\u56feURL\u5217\u8868") List<@Size(max = 500) String> proofFiles, |
| | | @Size(max = 9) @Schema(description = "洗车凭证或消费截图URL列表,普通商家可不传,手机号写入备注") List<@Size(max = 500) String> proofFiles, |
| | | @Size(max = 500) String description, |
| | | @Schema(description = "\u6d17\u8f66\u6d88\u8d39\u65f6\u95f4") LocalDateTime consumeTime, |
| | | @Schema(description = "洗车消费时间") LocalDateTime consumeTime, |
| | | @Size(max = 128) String idempotencyKey |
| | | ) {} |
| | |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Schema(name = "CouponClaimIssueRequest", description = "\u540e\u53f0\u53d1\u653e\u4f18\u60e0\u5238\u53c2\u6570") |
| | | @Schema(name = "CouponClaimIssueRequest", description = "后台发放优惠券参数") |
| | | public record CouponClaimIssueRequest( |
| | | @NotNull @Schema(description = "\u9886\u53d6\u4eba\u624dID") Long talentId, |
| | | @Schema(description = "\u4eba\u624d\u5361ID") Long cardId, |
| | | @Schema(description = "\u9886\u53d6\u5238\u8fc7\u671f\u65f6\u95f4\uff0c\u4e0d\u4f20\u4f7f\u7528\u4f18\u60e0\u5238\u6709\u6548\u671f") LocalDateTime expireTime, |
| | | @Size(max = 500) @Schema(description = "\u53d1\u653e\u5907\u6ce8") String remark |
| | | @NotNull @Schema(description = "领取人才ID") Long talentId, |
| | | @Schema(description = "人才卡ID") Long cardId, |
| | | @Schema(description = "领取券过期时间,不传使用优惠券有效期") LocalDateTime expireTime, |
| | | @Size(max = 500) @Schema(description = "发放备注") String remark |
| | | ) {} |
| | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.Size; |
| | | |
| | | @Schema(name = "CouponClaimStatusRequest", description = "\u4f18\u60e0\u5238\u9886\u53d6\u548c\u4f7f\u7528\u72b6\u6001\u53c2\u6570") |
| | | @Schema(name = "CouponClaimStatusRequest", description = "优惠券领取和使用状态参数") |
| | | public record CouponClaimStatusRequest( |
| | | @Schema(description = "\u9886\u53d6\u72b6\u6001\uff1a0\u5f85\u9886\u53d6\u30011\u5df2\u9886\u53d6\u30012\u5df2\u53d6\u6d88") Integer claimStatus, |
| | | @Schema(description = "\u4f7f\u7528\u72b6\u6001\uff1a0\u672a\u4f7f\u7528\u30011\u5df2\u4f7f\u7528\u30012\u5df2\u8fc7\u671f\u30013\u5df2\u4f5c\u5e9f") Integer useStatus, |
| | | @Schema(description = "\u4f7f\u7528\u8ba2\u5355ID") Long orderId, |
| | | @Size(max = 500) @Schema(description = "\u72b6\u6001\u5907\u6ce8") String remark |
| | | @Schema(description = "领取状态:0待领取、1已领取、2已取消") Integer claimStatus, |
| | | @Schema(description = "使用状态:0未使用、1已使用、2已过期、3已作废") Integer useStatus, |
| | | @Schema(description = "使用订单ID") Long orderId, |
| | | @Size(max = 500) @Schema(description = "状态备注") String remark |
| | | ) {} |
| | |
| | | import java.time.LocalDate; |
| | | import java.util.List; |
| | | |
| | | @Schema(name = "CouponManagementSaveRequest", description = "\u4f18\u60e0\u5238\u914d\u7f6e\u4fdd\u5b58\u53c2\u6570") |
| | | @Schema(name = "CouponManagementSaveRequest", description = "优惠券配置保存参数") |
| | | public record CouponManagementSaveRequest( |
| | | @NotNull @Schema(description = "\u9002\u7528\u5546\u6237ID", example = "1") Long merchantId, |
| | | @NotBlank @Size(max = 128) @Schema(description = "\u4f18\u60e0\u5238\u540d\u79f0") String couponName, |
| | | @NotBlank @Schema(description = "\u7c7b\u578b\uff1aCASH\u4ee3\u91d1\u5238\u3001FULL_REDUCTION\u6ee1\u51cf\u5238\u3001DISCOUNT\u6298\u6263\u5238\u3001EXCHANGE\u5151\u6362\u5238") String couponType, |
| | | @NotNull @DecimalMin("0.00") @Schema(description = "\u9762\u989d\uff0c\u5355\u4f4d\u5143", example = "50.00") BigDecimal couponAmount, |
| | | @Schema(description = "\u4f7f\u7528\u6761\u4ef6\uff1aNONE\u65e0\u95e8\u69db\u3001FULL\u6ee1\u51cf") String conditionType, |
| | | @DecimalMin("0.00") @Schema(description = "\u6ee1\u51cf\u95e8\u69db\u91d1\u989d") BigDecimal conditionAmount, |
| | | @Schema(description = "\u9002\u7528\u4eba\u624d\u5361\u7b49\u7ea7") List<String> talentLevels, |
| | | @Schema(description = "\u6709\u6548\u5f00\u59cb\u65e5\u671f") LocalDate validStart, |
| | | @Schema(description = "\u6709\u6548\u7ed3\u675f\u65e5\u671f") LocalDate validEnd, |
| | | @Size(max = 64) @Schema(description = "\u53d1\u653e\u6279\u6b21\u53f7\uff0c\u4e0d\u4f20\u81ea\u52a8\u751f\u6210") String batchNo, |
| | | @NotNull @Min(-1) @Schema(description = "\u53d1\u653e\u603b\u91cf\uff0c-1\u8868\u793a\u4e0d\u9650\u91cf", example = "1000") Integer totalQuantity, |
| | | @Schema(description = "\u662f\u5426\u653f\u5e9c\u8865\u8d34") Boolean governmentSubsidy, |
| | | @Schema(description = "\u72b6\u6001\uff1a0\u4e0b\u67b6\u30011\u4e0a\u67b6", example = "1") Integer status |
| | | @NotNull @Schema(description = "适用商户ID", example = "1") Long merchantId, |
| | | @NotBlank @Size(max = 128) @Schema(description = "优惠券名称") String couponName, |
| | | @NotBlank @Schema(description = "类型:CASH代金券、FULL_REDUCTION满减券、DISCOUNT折扣券、EXCHANGE兑换券") String couponType, |
| | | @NotNull @DecimalMin("0.00") @Schema(description = "面额,单位元", example = "50.00") BigDecimal couponAmount, |
| | | @Schema(description = "使用条件:NONE无门槛、FULL满减") String conditionType, |
| | | @DecimalMin("0.00") @Schema(description = "满减门槛金额") BigDecimal conditionAmount, |
| | | @Schema(description = "适用人才卡等级") List<String> talentLevels, |
| | | @Schema(description = "有效开始日期") LocalDate validStart, |
| | | @Schema(description = "有效结束日期") LocalDate validEnd, |
| | | @Size(max = 64) @Schema(description = "发放批次号,不传自动生成") String batchNo, |
| | | @NotNull @Min(-1) @Schema(description = "发放总量,-1表示不限量", example = "1000") Integer totalQuantity, |
| | | @Schema(description = "是否政府补贴") Boolean governmentSubsidy, |
| | | @Schema(description = "状态:0下架、1上架", example = "1") Integer status |
| | | ) {} |
| | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | |
| | | @Schema(name = "CouponManagementStatusRequest", description = "\u4f18\u60e0\u5238\u4e0a\u4e0b\u67b6\u53c2\u6570") |
| | | @Schema(name = "CouponManagementStatusRequest", description = "优惠券上下架参数") |
| | | public record CouponManagementStatusRequest( |
| | | @NotNull @Schema(description = "\u72b6\u6001\uff1a0\u4e0b\u67b6\u30011\u4e0a\u67b6") Integer status |
| | | @NotNull @Schema(description = "状态:0下架、1上架") Integer status |
| | | ) {} |
| | |
| | | import com.talentcard.server.entity.DependentCardApply; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | |
| | | @Schema(name = "DependentCardApplyAdminResponse", description = "\u7ba1\u7406\u540e\u53f0\u5bb6\u5c5e\u9644\u5c5e\u5361\u7533\u8bf7\u4fe1\u606f") |
| | | @Schema(name = "DependentCardApplyAdminResponse", description = "管理后台家属附属卡申请信息") |
| | | public record DependentCardApplyAdminResponse( |
| | | Long id, |
| | | String applyNo, |
| | |
| | | } |
| | | |
| | | private static String statusText(Integer status) { |
| | | if (status == null) return "\u672a\u77e5"; |
| | | if (status == null) return "未知"; |
| | | return switch (status) { |
| | | case DependentCardApply.STATUS_PENDING_REVIEW -> "\u5f85\u5ba1\u6838"; |
| | | case DependentCardApply.STATUS_APPROVED -> "\u5df2\u901a\u8fc7"; |
| | | case DependentCardApply.STATUS_REJECTED -> "\u5df2\u9a73\u56de"; |
| | | case DependentCardApply.STATUS_CANCELLED -> "\u5df2\u89e3\u7ed1"; |
| | | default -> "\u672a\u77e5"; |
| | | case DependentCardApply.STATUS_PENDING_REVIEW -> "待审核"; |
| | | case DependentCardApply.STATUS_APPROVED -> "已通过"; |
| | | case DependentCardApply.STATUS_REJECTED -> "已驳回"; |
| | | case DependentCardApply.STATUS_CANCELLED -> "已解绑"; |
| | | default -> "未知"; |
| | | }; |
| | | } |
| | | } |
| | |
| | | public Map<String, Object> summary() { |
| | | Map<String, Object> row = mapper.selectSummary(); |
| | | if (row == null) { |
| | | throw new BusinessException(ErrorCode.INTERNAL_ERROR, "\u5de5\u4f5c\u53f0\u7edf\u8ba1\u67e5\u8be2\u5931\u8d25"); |
| | | throw new BusinessException(ErrorCode.INTERNAL_ERROR, "工作台统计查询失败"); |
| | | } |
| | | long subsidyCents = number(row.get("subsidyAmountCents")); |
| | | long monthlyVerification = number(row.get("monthlyVerificationCount")); |
| | |
| | | result.put("budgetUsageRateText", "0.00%"); |
| | | |
| | | List<Map<String, Object>> alerts = new ArrayList<>(); |
| | | addAlert(alerts, "\u5f85\u5904\u7406\u8ba2\u5355", number(row.get("pendingOrderCount"))); |
| | | addAlert(alerts, "\u5f85\u5ba1\u6838\u5546\u54c1", number(row.get("pendingProductCount"))); |
| | | addAlert(alerts, "\u5f85\u5904\u7406\u9884\u7ea6", number(row.get("pendingApplicationCount"))); |
| | | addAlert(alerts, "待处理订单", number(row.get("pendingOrderCount"))); |
| | | addAlert(alerts, "待审核商品", number(row.get("pendingProductCount"))); |
| | | addAlert(alerts, "待处理预约", number(row.get("pendingApplicationCount"))); |
| | | result.put("alertCount", alerts.stream().mapToLong(a -> number(a.get("count"))).sum()); |
| | | result.put("alerts", alerts); |
| | | result.put("statisticsBasis", "\u8865\u8d34\u4ec5\u7edf\u8ba1\u653f\u5e9c\u627f\u62c5\u7684\u5546\u54c1\u8865\u8d34\u548c\u653f\u5e9c\u8865\u8d34\u5238\u91d1\u989d\uff0c\u5546\u5bb6\u81ea\u884c\u8ba9\u5229\u4e0d\u8ba1\u5165\u653f\u5e9c\u8865\u8d34"); |
| | | result.put("statisticsBasis", "补贴仅统计政府承担的商品补贴和政府补贴券金额,商家自行让利不计入政府补贴"); |
| | | return result; |
| | | } |
| | | |
| | |
| | | aliases.add(raw); |
| | | } |
| | | String upper = raw.toUpperCase(Locale.ROOT); |
| | | if (upper.contains("EXCELLENT") || raw.contains("\u5353\u8d8a")) { |
| | | if (upper.contains("EXCELLENT") || raw.contains("卓越")) { |
| | | aliases.add("EXCELLENT"); |
| | | aliases.add("\u5353\u8d8a\u4eba\u624d\u5361"); |
| | | aliases.add("\u5353\u8d8a"); |
| | | aliases.add("卓越人才卡"); |
| | | aliases.add("卓越"); |
| | | } |
| | | if (upper.contains("OUTSTANDING") || raw.contains("\u6770\u51fa")) { |
| | | if (upper.contains("OUTSTANDING") || raw.contains("杰出")) { |
| | | aliases.add("OUTSTANDING"); |
| | | aliases.add("\u6770\u51fa\u4eba\u624d\u5361"); |
| | | aliases.add("\u6770\u51fa"); |
| | | aliases.add("杰出人才卡"); |
| | | aliases.add("杰出"); |
| | | } |
| | | if (upper.contains("ELITE") || raw.contains("\u83c1\u82f1") || raw.contains("\u7cbe\u82f1")) { |
| | | if (upper.contains("ELITE") || raw.contains("菁英") || raw.contains("精英")) { |
| | | aliases.add("ELITE"); |
| | | aliases.add("\u83c1\u82f1\u4eba\u624d\u5361"); |
| | | aliases.add("\u83c1\u82f1"); |
| | | aliases.add("菁英人才卡"); |
| | | aliases.add("菁英"); |
| | | } |
| | | return aliases.isEmpty() ? List.of(raw) : List.copyOf(aliases); |
| | | } |
| | |
| | | private String normalizeCardLevel(Object value) { |
| | | String raw = text(value); |
| | | String upper = raw.toUpperCase(Locale.ROOT); |
| | | if (upper.contains("EXCELLENT") || raw.contains("\u5353\u8d8a")) { |
| | | if (upper.contains("EXCELLENT") || raw.contains("卓越")) { |
| | | return "EXCELLENT"; |
| | | } |
| | | if (upper.contains("OUTSTANDING") || raw.contains("\u6770\u51fa")) { |
| | | if (upper.contains("OUTSTANDING") || raw.contains("杰出")) { |
| | | return "OUTSTANDING"; |
| | | } |
| | | if (upper.contains("ELITE") || raw.contains("\u83c1\u82f1") || raw.contains("\u7cbe\u82f1")) { |
| | | if (upper.contains("ELITE") || raw.contains("菁英") || raw.contains("精英")) { |
| | | return "ELITE"; |
| | | } |
| | | return upper; |
| | |
| | | if (cycle.isBlank() || "NONE".equals(cycle)) { |
| | | return LocalDate.of(1970, 1, 1).atStartOfDay(); |
| | | } |
| | | if (cycle.contains("DAY") || cycle.contains("DAILY") || cycle.contains("\u5929") || cycle.contains("\u65e5")) { |
| | | if (cycle.contains("DAY") || cycle.contains("DAILY") || cycle.contains("天") || cycle.contains("日")) { |
| | | return today.atStartOfDay(); |
| | | } |
| | | if (cycle.contains("WEEK") || cycle.contains("\u5468") || cycle.contains("\u661f\u671f")) { |
| | | if (cycle.contains("WEEK") || cycle.contains("周") || cycle.contains("星期")) { |
| | | return today.minusDays(today.getDayOfWeek().getValue() - 1L).atStartOfDay(); |
| | | } |
| | | if (cycle.contains("YEAR") || cycle.contains("ANNUAL") || cycle.contains("\u5e74")) { |
| | | if (cycle.contains("YEAR") || cycle.contains("ANNUAL") || cycle.contains("年")) { |
| | | return today.with(TemporalAdjusters.firstDayOfYear()).atStartOfDay(); |
| | | } |
| | | if (cycle.contains("MONTH") || cycle.contains("\u6708") || monthlyDefault) { |
| | | if (cycle.contains("MONTH") || cycle.contains("月") || monthlyDefault) { |
| | | return today.withDayOfMonth(1).atStartOfDay(); |
| | | } |
| | | return today.with(TemporalAdjusters.firstDayOfYear()).atStartOfDay(); |
| | |
| | | primaryCardId = longValue(primary, "primaryCardId"); |
| | | cardLevel = text(primary, "cardLevel"); |
| | | } |
| | | List<String> files = request.proofFiles().stream().map(this::clean).filter(v -> !v.isBlank()).distinct().toList(); |
| | | if (files.isEmpty()) throw new BusinessException(ErrorCode.BAD_REQUEST, "请上传洗车凭证或消费截图"); |
| | | List<String> files = request.proofFiles() == null ? List.of() : request.proofFiles().stream().map(this::clean).filter(v -> !v.isBlank()).distinct().toList(); |
| | | for (String file : files) { |
| | | if (!(file.startsWith("/uploads/") || file.startsWith("https://"))) { |
| | | throw new BusinessException(ErrorCode.BAD_REQUEST, "凭证文件地址不正确"); |
| | |
| | | } |
| | | |
| | | private void requireMerchant(Long merchantId) { |
| | | if (merchantId == null || merchantId <= 0 || number(configMapper.countCarWashMerchant(merchantId)).longValue() == 0) throw new BusinessException(ErrorCode.NOT_FOUND, "洗车商家不存在"); |
| | | if (merchantId == null || merchantId <= 0 || number(configMapper.countCarWashMerchant(merchantId)).longValue() == 0) |
| | | throw new BusinessException(ErrorCode.NOT_FOUND, "洗车商家不存在"); |
| | | } |
| | | private void validateStatus(Integer status) { if (status != null && (status < 0 || status > 3)) throw new BusinessException(ErrorCode.BAD_REQUEST, "洗车凭证状态不正确"); } |
| | | private Number number(Object value) { return value instanceof Number number ? number : 0L; } |
| | |
| | | max-file-size: 10MB |
| | | max-request-size: 10MB |
| | | datasource: |
| | | url: ${MYSQL_URL:jdbc:mysql://localhost:3306/talent_card?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false} |
| | | url: ${MYSQL_URL:jdbc:mysql://192.168.110.13:3306/talent_card?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false} |
| | | username: ${MYSQL_USERNAME:root} |
| | | password: ${MYSQL_PASSWORD:123456} |
| | | driver-class-name: com.mysql.cj.jdbc.Driver |
| | |
| | | repositories: |
| | | enabled: false |
| | | flyway: |
| | | enabled: true |
| | | enabled: false |
| | | locations: classpath:db/migration |
| | | baseline-on-migrate: true |
| | | jpa: |