bill coupon detail (#6054)
Some checks are pending
Document deploy / sync-images (push) Waiting to run
Document deploy / generate-timestamp (push) Blocked by required conditions
Document deploy / build-images (map[domain:https://fastgpt.cn suffix:cn]) (push) Blocked by required conditions
Document deploy / build-images (map[domain:https://fastgpt.io suffix:io]) (push) Blocked by required conditions
Document deploy / update-images (map[deployment:fastgpt-docs domain:https://fastgpt.cn kube_config:KUBE_CONFIG_CN suffix:cn]) (push) Blocked by required conditions
Document deploy / update-images (map[deployment:fastgpt-docs domain:https://fastgpt.io kube_config:KUBE_CONFIG_IO suffix:io]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / get-vars (push) Waiting to run
Build FastGPT images in Personal warehouse / build-fastgpt-images (map[arch:amd64 runs-on:ubuntu-24.04]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / build-fastgpt-images (map[arch:arm64 runs-on:ubuntu-24.04-arm]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / release-fastgpt-images (push) Blocked by required conditions

* bill coupon detail

* enum

* fix
This commit is contained in:
heheer 2025-12-08 18:07:31 +08:00 committed by GitHub
parent e979f69cba
commit 44f95038b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 247 additions and 81 deletions

View File

@ -5,9 +5,14 @@ import {
BillStatusEnum,
BillPayWayEnum
} from '../../../../support/wallet/bill/constants';
import { StandardSubLevelEnum, SubModeEnum } from '../../../../support/wallet/sub/constants';
import {
StandardSubLevelEnum,
SubModeEnum,
SubTypeEnum
} from '../../../../support/wallet/sub/constants';
import { PaginationSchema } from '../../../api';
import { BillSchema } from '../../../../support/wallet/bill/type';
import { CouponTypeEnum } from '../../../../support/wallet/sub/coupon/constants';
// Bill list
export const BillListQuerySchema = PaginationSchema.safeExtend({
@ -85,7 +90,23 @@ export type CheckPayResultResponseType = z.infer<typeof CheckPayResultResponseSc
// Bill detail
export const BillDetailResponseSchema = BillSchema.safeExtend({
couponName: z.string().optional()
discountCouponName: z.string().optional(),
couponDetail: z
.object({
key: z.string(),
type: z.enum(CouponTypeEnum),
subscriptions: z.array(
z.object({
type: z.enum(SubTypeEnum),
durationDay: z.number(),
totalPoints: z.number().optional(),
level: z.enum(StandardSubLevelEnum).optional(),
extraDatasetSize: z.number().optional(),
customConfig: z.record(z.string(), z.any()).optional()
})
)
})
.optional()
});
export type BillDetailResponseType = z.infer<typeof BillDetailResponseSchema>;

View File

@ -1,3 +1,5 @@
export const COUPON_PREFIX = 'coupon-';
export enum CouponTypeEnum {
bank = 'bank',
activity = 'activity'

View File

@ -61,20 +61,20 @@ export const getTeamStandPlan = async ({ teamId }: { teamId: string }) => {
standardConstants: standardConstants
? {
...standardConstants,
maxTeamMember: standard?.maxTeamMember || standardConstants.maxTeamMember,
maxAppAmount: standard?.maxApp || standardConstants.maxAppAmount,
maxDatasetAmount: standard?.maxDataset || standardConstants.maxDatasetAmount,
requestsPerMinute: standard?.requestsPerMinute || standardConstants.requestsPerMinute,
maxTeamMember: standard?.maxTeamMember ?? standardConstants.maxTeamMember,
maxAppAmount: standard?.maxApp ?? standardConstants.maxAppAmount,
maxDatasetAmount: standard?.maxDataset ?? standardConstants.maxDatasetAmount,
requestsPerMinute: standard?.requestsPerMinute ?? standardConstants.requestsPerMinute,
chatHistoryStoreDuration:
standard?.chatHistoryStoreDuration || standardConstants.chatHistoryStoreDuration,
maxDatasetSize: standard?.maxDatasetSize || standardConstants.maxDatasetSize,
standard?.chatHistoryStoreDuration ?? standardConstants.chatHistoryStoreDuration,
maxDatasetSize: standard?.maxDatasetSize ?? standardConstants.maxDatasetSize,
websiteSyncPerDataset:
standard?.websiteSyncPerDataset || standardConstants.websiteSyncPerDataset,
standard?.websiteSyncPerDataset ?? standardConstants.websiteSyncPerDataset,
appRegistrationCount:
standard?.appRegistrationCount || standardConstants.appRegistrationCount,
standard?.appRegistrationCount ?? standardConstants.appRegistrationCount,
auditLogStoreDuration:
standard?.auditLogStoreDuration || standardConstants.auditLogStoreDuration,
ticketResponseTime: standard?.ticketResponseTime || standardConstants.ticketResponseTime
standard?.auditLogStoreDuration ?? standardConstants.auditLogStoreDuration,
ticketResponseTime: standard?.ticketResponseTime ?? standardConstants.ticketResponseTime
}
: undefined
};
@ -176,8 +176,8 @@ export const getTeamPlanStatus = async ({
const standardMaxDatasetSize =
standardPlan?.currentSubLevel && standardPlans
? standardPlans[standardPlan.currentSubLevel]?.maxDatasetSize ||
standardPlan?.maxDatasetSize ||
? standardPlan?.maxDatasetSize ||
standardPlans[standardPlan.currentSubLevel]?.maxDatasetSize ||
Infinity
: Infinity;
const totalDatasetSize =
@ -196,21 +196,21 @@ export const getTeamPlanStatus = async ({
standardConstants: standardConstants
? {
...standardConstants,
maxTeamMember: standardPlan?.maxTeamMember || standardConstants.maxTeamMember,
maxAppAmount: standardPlan?.maxApp || standardConstants.maxAppAmount,
maxDatasetAmount: standardPlan?.maxDataset || standardConstants.maxDatasetAmount,
requestsPerMinute: standardPlan?.requestsPerMinute || standardConstants.requestsPerMinute,
maxTeamMember: standardPlan?.maxTeamMember ?? standardConstants.maxTeamMember,
maxAppAmount: standardPlan?.maxApp ?? standardConstants.maxAppAmount,
maxDatasetAmount: standardPlan?.maxDataset ?? standardConstants.maxDatasetAmount,
requestsPerMinute: standardPlan?.requestsPerMinute ?? standardConstants.requestsPerMinute,
chatHistoryStoreDuration:
standardPlan?.chatHistoryStoreDuration || standardConstants.chatHistoryStoreDuration,
maxDatasetSize: standardPlan?.maxDatasetSize || standardConstants.maxDatasetSize,
standardPlan?.chatHistoryStoreDuration ?? standardConstants.chatHistoryStoreDuration,
maxDatasetSize: standardPlan?.maxDatasetSize ?? standardConstants.maxDatasetSize,
websiteSyncPerDataset:
standardPlan?.websiteSyncPerDataset || standardConstants.websiteSyncPerDataset,
appRegistrationCount:
standardPlan?.appRegistrationCount || standardConstants.appRegistrationCount,
standardPlan?.appRegistrationCount ?? standardConstants.appRegistrationCount,
auditLogStoreDuration:
standardPlan?.auditLogStoreDuration || standardConstants.auditLogStoreDuration,
standardPlan?.auditLogStoreDuration ?? standardConstants.auditLogStoreDuration,
ticketResponseTime:
standardPlan?.ticketResponseTime || standardConstants.ticketResponseTime
standardPlan?.ticketResponseTime ?? standardConstants.ticketResponseTime
}
: undefined,

View File

@ -98,7 +98,20 @@
"subscription_period": "Subscription cycle",
"subscription_package": "Subscription package",
"subscription_mode_month": "Duration",
"month": "moon",
"month": "month",
"coupon_included_packages": "Coupon packages",
"day": "day",
"extra_dataset_size": "Additional knowledge base capacity",
"extra_ai_points": "AI points calculation standard"
"extra_ai_points": "AI points calculation standard",
"max_team_member": "Max team members",
"max_app_amount": "Max app amount",
"max_dataset_amount": "Max dataset amount",
"requests_per_minute": "Requests per minute",
"max_dataset_size": "Max dataset size",
"chat_history_store_duration": "Chat history storage duration",
"website_sync_per_dataset": "Website sync per dataset",
"app_registration_count": "App registration count",
"audit_log_store_duration": "Audit log storage duration",
"ticket_response_time": "Ticket response time",
"custom_config_details": "Custom configuration details"
}

View File

@ -3,17 +3,32 @@
"active_model": "可用模型",
"add_default_model": "添加预设模型",
"api_key": "API 密钥",
"app_registration_count": "应用备案数",
"audit_log_store_duration": "团队操作日志记录时长",
"bill_detail": "账单详情",
"bills_and_invoices": "账单与发票",
"channel": "模型渠道",
"chat_history_store_duration": "对话记录保留时长",
"config_model": "模型配置",
"confirm_logout": "确认退出登录?",
"create_channel": "新增渠道",
"create_model": "新增模型",
"custom_config_details": "定制配置详情",
"custom_model": "自定义模型",
"day": "天",
"default_model": "预设模型",
"default_model_config": "默认模型配置",
"extra_ai_points": "额外 AI 积分",
"extra_dataset_size": "额外知识库容量",
"generation_time": "生成时间",
"has_invoice": "是否已开票",
"hour": "小时",
"language": "语言与时区",
"logout": "登出",
"max_app_amount": "Agent 上限",
"max_dataset_amount": "知识库上限",
"max_dataset_size": "知识库索引上限",
"max_team_member": "团队成员上限",
"model.active": "启用",
"model.alias": "别名",
"model.alias_tip": "模型在系统中展示的名字,方便用户理解",
@ -76,29 +91,27 @@
"model.voices": "声音角色",
"model.voices_tip": "通过一个数组配置多个,例如:\n[\n {\n \"label\": \"Alloy\",\n \"value\": \"alloy\"\n },\n {\n \"label\": \"Echo\",\n \"value\": \"echo\"\n }\n]",
"model_provider": "模型提供商",
"month": "月",
"no": "否",
"notifications": "通知",
"order_number": "订单号",
"order_type": "订单类型",
"payment_method": "支付方式",
"personal_information": "个人信息",
"personalization": "个性化",
"promotion_records": "促销记录",
"requests_per_minute": "QPM",
"reset_default": "恢复默认配置",
"status": "状态",
"subscription_mode_month": "时长",
"subscription_package": "订阅套餐",
"subscription_period": "订阅周期",
"support_wallet_amount": "金额",
"team": "团队管理",
"third_party": "第三方账号",
"ticket_response_time": "工单支持响应时间",
"usage_records": "使用记录",
"bill_detail": "账单详情",
"order_number": "订单号",
"generation_time": "生成时间",
"order_type": "订单类型",
"status": "状态",
"payment_method": "支付方式",
"support_wallet_amount": "金额",
"yuan": "{{amount}}元",
"has_invoice": "是否已开票",
"website_sync_per_dataset": "站点同步最大页数",
"yes": "是",
"no": "否",
"subscription_period": "订阅周期",
"subscription_package": "订阅套餐",
"subscription_mode_month": "时长",
"month": "月",
"extra_dataset_size": "额外知识库容量",
"extra_ai_points": "额外 AI 积分"
"yuan": "{{amount}}元"
}

View File

@ -3,17 +3,32 @@
"active_model": "可用模型",
"add_default_model": "新增預設模型",
"api_key": "API 金鑰",
"app_registration_count": "應用備案數",
"audit_log_store_duration": "團隊操作日誌記錄時長",
"bill_detail": "帳單詳細資訊",
"bills_and_invoices": "帳單與發票",
"channel": "模型管道",
"chat_history_store_duration": "對話記錄保留時長",
"config_model": "模型設定",
"confirm_logout": "確認登出登入",
"create_channel": "新增道",
"confirm_logout": "確認登出",
"create_channel": "新增道",
"create_model": "新增模型",
"custom_config_details": "定制配置詳情",
"custom_model": "自訂模型",
"day": "天",
"default_model": "預設模型",
"default_model_config": "預設模型設定",
"extra_ai_points": "額外 AI 積分",
"extra_dataset_size": "額外知識庫容量",
"generation_time": "生成時間",
"has_invoice": "是否已開票",
"hour": "小時",
"language": "語言與時區",
"logout": "登出",
"max_app_amount": "Agent 上限",
"max_dataset_amount": "知識庫上限",
"max_dataset_size": "知識庫索引上限",
"max_team_member": "團隊成員上限",
"model.active": "啟用",
"model.alias": "別名",
"model.alias_tip": "模型在系統中展示的名字,方便使用者理解",
@ -47,9 +62,9 @@
"model.max_quote": "知識庫最大引用",
"model.max_temperature": "最大溫度",
"model.model_id": "模型 ID",
"model.model_id_tip": "模型的唯一標識,也就是實際請求到服務商 model 的值,需要與 OneAPI 道中的模型對應。",
"model.model_id_tip": "模型的唯一標識,也就是實際請求到服務商 model 的值,需要與 OneAPI 道中的模型對應。",
"model.normalization": "歸一化處理",
"model.normalization_tip": "如果 Embedding API 未對向量值進行歸一化,可以啟用該開關,系統會進行歸一化處理。\n\n未歸一化的 API表現為向量檢索得分會大於 1。",
"model.normalization_tip": "如果 Embedding API 未對向量值進行歸一化,可以啟用該開關,系統會進行歸一化處理。\n未歸一化的 API表現為向量檢索得分會大於 1。",
"model.output_price": "模型輸出價格",
"model.output_price_tip": "語言模型輸出價格,如果設定了該項,則模型綜合價格會失效",
"model.param_name": "參數名稱",
@ -58,7 +73,7 @@
"model.request_auth": "自訂請求 Key",
"model.request_auth_tip": "向自訂請求地址發起請求時候攜帶請求頭Authorization: Bearer xxx 進行請求",
"model.request_url": "自訂請求地址",
"model.request_url_tip": "如果填寫該值,則會直接向該地址發起請求,不經過 OneAPI。\n需要遵循 OpenAI 的 API 格式,並填寫完整請求地址,例如:\n\nLLM: {{host}}/v1/chat/completions\n\nEmbedding: {{host}}/v1/embeddings\n\nSTT: {{host}}/v1/audio/transcriptions\n\nTTS: {{host}}/v1/audio/speech\n\nRerank: {{host}}/v1/rerank",
"model.request_url_tip": "如果填寫該值,則會直接向該地址發起請求,不經過 OneAPI。需要遵循 OpenAI 的 API 格式,並填寫完整請求地址,例如:\nLLM: {{host}}/v1/chat/completions\nEmbedding: {{host}}/v1/embeddings\nSTT: {{host}}/v1/audio/transcriptions\nTTS: {{host}}/v1/audio/speech\nRerank: {{host}}/v1/rerank",
"model.response_format": "響應格式",
"model.show_stop_sign": "展示停止序列參數",
"model.show_top_p": "展示 Top-p 參數",
@ -74,31 +89,29 @@
"model.vision_tag": "視覺",
"model.vision_tip": "如果模型支援圖片識別,則開啟該開關。",
"model.voices": "聲音角色",
"model.voices_tip": "透過一個陣列設定多個,例如:\n\n[\n {\n \"label\": \"Alloy\",\n \"value\": \"alloy\"\n },\n {\n \"label\": \"Echo\",\n \"value\": \"echo\"\n }\n]",
"model.voices_tip": "透過一個陣列設定多個,例如:\n[\n {\n \"label\": \"Alloy\",\n \"value\": \"alloy\"\n },\n {\n \"label\": \"Echo\",\n \"value\": \"echo\"\n }\n]",
"model_provider": "模型提供者",
"month": "月",
"no": "否",
"notifications": "通知",
"order_number": "訂單編號",
"order_type": "訂單類型",
"payment_method": "支付方式",
"personal_information": "個人資訊",
"personalization": "個人化",
"promotion_records": "促銷記錄",
"requests_per_minute": "QPM",
"reset_default": "恢復預設設定",
"team": "團隊管理",
"third_party": "第三方賬號",
"usage_records": "使用記錄",
"bill_detail": "帳單詳細資訊",
"order_number": "訂單編號",
"generation_time": "生成時間",
"order_type": "訂單類型",
"status": "狀態",
"payment_method": "支付方式",
"support_wallet_amount": "金額",
"yuan": "{{amount}}元",
"has_invoice": "是否已開票",
"yes": "是",
"no": "否",
"subscription_period": "訂閱週期",
"subscription_package": "訂閱套餐",
"subscription_mode_month": "時長",
"month": "月",
"extra_dataset_size": "額外知識庫容量",
"extra_ai_points": "AI 積分運算標準"
}
"subscription_package": "訂閱套餐",
"subscription_period": "訂閱週期",
"support_wallet_amount": "金額",
"team": "團隊管理",
"third_party": "第三方帳號",
"ticket_response_time": "工單支援響應時間",
"usage_records": "使用記錄",
"website_sync_per_dataset": "站點同步最大頁數",
"yes": "是",
"yuan": "{{amount}}元"
}

View File

@ -123,7 +123,7 @@ const Navbar = ({ unread }: { unread: number }) => {
w={'100%'}
userSelect={'none'}
pb={2}
bg={isDashboardPage ? 'white' : 'transparent'}
bg={isDashboardPage ? 'myGray.50' : 'transparent'}
>
{/* logo */}
<Box flex={'0 0 auto'} mb={3}>

View File

@ -35,18 +35,18 @@ const StandardPlanContentList = ({
level: level as `${StandardSubLevelEnum}`,
...standardSubLevelMap[level as `${StandardSubLevelEnum}`],
totalPoints:
standplan?.totalPoints || plan.totalPoints * (mode === SubModeEnum.month ? 1 : 12),
requestsPerMinute: standplan?.requestsPerMinute || plan.requestsPerMinute || 2000,
maxTeamMember: standplan?.maxTeamMember || plan.maxTeamMember,
maxAppAmount: standplan?.maxApp || plan.maxAppAmount,
maxDatasetAmount: standplan?.maxDataset || plan.maxDatasetAmount,
maxDatasetSize: standplan?.maxDatasetSize || plan.maxDatasetSize,
websiteSyncPerDataset: standplan?.websiteSyncPerDataset || plan.websiteSyncPerDataset,
standplan?.totalPoints ?? plan.totalPoints * (mode === SubModeEnum.month ? 1 : 12),
requestsPerMinute: standplan?.requestsPerMinute ?? plan.requestsPerMinute,
maxTeamMember: standplan?.maxTeamMember ?? plan.maxTeamMember,
maxAppAmount: standplan?.maxApp ?? plan.maxAppAmount,
maxDatasetAmount: standplan?.maxDataset ?? plan.maxDatasetAmount,
maxDatasetSize: standplan?.maxDatasetSize ?? plan.maxDatasetSize,
websiteSyncPerDataset: standplan?.websiteSyncPerDataset ?? plan.websiteSyncPerDataset,
chatHistoryStoreDuration:
standplan?.chatHistoryStoreDuration || plan.chatHistoryStoreDuration,
auditLogStoreDuration: standplan?.auditLogStoreDuration || plan.auditLogStoreDuration,
appRegistrationCount: standplan?.appRegistrationCount || plan.appRegistrationCount,
ticketResponseTime: standplan?.ticketResponseTime || plan.ticketResponseTime
standplan?.chatHistoryStoreDuration ?? plan.chatHistoryStoreDuration,
auditLogStoreDuration: standplan?.auditLogStoreDuration ?? plan.auditLogStoreDuration,
appRegistrationCount: standplan?.appRegistrationCount ?? plan.appRegistrationCount,
ticketResponseTime: standplan?.ticketResponseTime ?? plan.ticketResponseTime
};
}, [
subPlans?.standard,

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { Box, Flex, ModalBody } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
@ -13,6 +13,7 @@ import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tool
import { standardSubLevelMap, subModeMap } from '@fastgpt/global/support/wallet/sub/constants';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { getBillDetail } from '@/web/support/wallet/bill/api';
import { i18nT } from '@fastgpt/web/i18n/utils';
type BillDetailModalProps = {
billId: string;
@ -27,6 +28,91 @@ const BillDetailModal = ({ billId, onClose }: BillDetailModalProps) => {
manual: false
});
const customConfigItems = useMemo(() => {
if (bill?.metadata.standSubLevel !== 'custom') return [];
const customSub = bill?.couponDetail?.subscriptions?.find(
(sub) => sub.type === 'standard' && sub.level === 'custom' && sub.customConfig
);
if (!customSub?.customConfig) return [];
const config = customSub.customConfig;
const items = [];
if (config.maxTeamMember !== undefined) {
items.push({
key: i18nT('account:max_team_member'),
value: config.maxTeamMember,
unit: ''
});
}
if (config.maxAppAmount !== undefined) {
items.push({
key: i18nT('account:max_app_amount'),
value: config.maxAppAmount,
unit: ''
});
}
if (config.maxDatasetAmount !== undefined) {
items.push({
key: i18nT('account:max_dataset_amount'),
value: config.maxDatasetAmount,
unit: ''
});
}
if (config.requestsPerMinute !== undefined) {
items.push({
key: i18nT('account:requests_per_minute'),
value: config.requestsPerMinute,
unit: ''
});
}
if (config.maxDatasetSize !== undefined) {
items.push({
key: i18nT('account:max_dataset_size'),
value: config.maxDatasetSize,
unit: 'GB'
});
}
if (config.chatHistoryStoreDuration !== undefined) {
items.push({
key: i18nT('account:chat_history_store_duration'),
value: config.chatHistoryStoreDuration,
unit: 'day'
});
}
if (config.websiteSyncPerDataset !== undefined) {
items.push({
key: i18nT('account:website_sync_per_dataset'),
value: config.websiteSyncPerDataset,
unit: ''
});
}
if (config.appRegistrationCount !== undefined) {
items.push({
key: i18nT('account:app_registration_count'),
value: config.appRegistrationCount,
unit: ''
});
}
if (config.auditLogStoreDuration !== undefined) {
items.push({
key: i18nT('account:audit_log_store_duration'),
value: config.auditLogStoreDuration,
unit: 'day'
});
}
if (config.ticketResponseTime !== undefined) {
items.push({
key: i18nT('account:ticket_response_time'),
value: config.ticketResponseTime,
unit: 'h'
});
}
return items;
}, [bill?.couponDetail?.subscriptions]);
return (
<MyModal
isOpen={true}
@ -57,10 +143,10 @@ const BillDetailModal = ({ billId, onClose }: BillDetailModalProps) => {
<Box>{t(billStatusMap[bill.status]?.label as any)}</Box>
</Flex>
)}
{!!bill?.couponName && (
{!!bill?.discountCouponName && (
<Flex alignItems={'center'} pb={4}>
<FormLabel flex={'0 0 120px'}>{t('account_info:discount_coupon')}:</FormLabel>
<Box>{t(bill?.couponName as any)}</Box>
<Box>{t(bill?.discountCouponName as any)}</Box>
</Flex>
)}
{!!bill?.metadata?.payWay && (
@ -115,6 +201,24 @@ const BillDetailModal = ({ billId, onClose }: BillDetailModalProps) => {
<Box>{bill.metadata.extraPoints}</Box>
</Flex>
)}
{customConfigItems.length > 0 && (
<Flex alignItems={'flex-start'} pb={4}>
<FormLabel flex={'0 0 120px'}>{t('account:custom_config_details')}:</FormLabel>
<Box flex={1} fontSize="sm" color="gray.600">
{customConfigItems.map((item, idx) => (
<Box key={idx} pb={0.5}>
{t(item.key)}: {item.value}
{item.unit &&
(item.unit === 'day'
? t('account:day')
: item.unit === 'h'
? t('account:hour')
: item.unit)}
</Box>
))}
</Box>
</Flex>
)}
</ModalBody>
</MyModal>
);