* feat: invoice (#2293)

* feat: default voice header

* add i18n

* refactor: 优化代码

* feat: 用户开票

* refactor: 代码优化&&样式联调 (#2384)

* Feat: invoice upload (#2424)

* refactor: 验收问题&&样式调整

* feat: 文件上传

* 小调整

* perf: invoice ui

---------

Co-authored-by: papapatrick <109422393+Patrickill@users.noreply.github.com>
This commit is contained in:
Archer
2024-08-19 17:44:48 +08:00
committed by GitHub
parent 884c2d9553
commit 5fab3734fa
37 changed files with 1093 additions and 31 deletions

View File

@@ -90,3 +90,19 @@ export type LafAccountType = {
appid: string;
pat: string;
};
export type TeamInvoiceHeaderType = {
teamName: string;
unifiedCreditCode: string;
companyAddress: string;
companyPhone: string;
bankName: string;
bankAccount: string;
needSpecialInvoice?: boolean;
emailAddress: string;
};
export type TeamInvoiceHeaderInfoSchemaType = TeamInvoiceHeaderType & {
_id: string;
teamId: string;
};

View File

@@ -44,6 +44,7 @@ export enum BillPayWayEnum {
balance = 'balance',
wx = 'wx'
}
export const billPayWayMap = {
[BillPayWayEnum.balance]: {
label: 'support.wallet.bill.payWay.balance'

View File

@@ -0,0 +1,4 @@
export enum InvoiceStatusEnum {
submitted = 1,
completed = 2
}

View File

@@ -1,6 +1,6 @@
import { StandardSubLevelEnum, SubModeEnum, SubTypeEnum } from '../sub/constants';
import { BillPayWayEnum, BillTypeEnum } from './constants';
import { TeamInvoiceHeaderType } from '../../user/team/type';
export type BillSchemaType = {
_id: string;
userId: string;
@@ -29,3 +29,16 @@ export type ChatNodeUsageType = {
moduleName: string;
model?: string;
};
export type InvoiceType = {
amount: number;
billIdList: string[];
} & TeamInvoiceHeaderType;
export type InvoiceSchemaType = {
teamId: string;
_id: string;
status: 1 | 2;
createTime: Date;
finishTime?: Date;
} & InvoiceType;

View File

@@ -0,0 +1,56 @@
import React, { useRef, useCallback } from 'react';
import { Box } from '@chakra-ui/react';
import { useToast } from '../../../hooks/useToast';
import { useTranslation } from 'next-i18next';
export const useSelectFile = (props?: {
fileType?: string;
multiple?: boolean;
maxCount?: number;
}) => {
const { t } = useTranslation();
const { fileType = '*', multiple = false, maxCount = 10 } = props || {};
const { toast } = useToast();
const SelectFileDom = useRef<HTMLInputElement>(null);
const openSign = useRef<any>();
const File = useCallback(
({ onSelect }: { onSelect: (e: File[], sign?: any) => void }) => (
<Box position={'absolute'} w={0} h={0} overflow={'hidden'}>
<input
ref={SelectFileDom}
type="file"
accept={fileType}
multiple={multiple}
onChange={(e) => {
const files = e.target.files;
if (!files || files?.length === 0) return;
let fileList = Array.from(files);
if (fileList.length > maxCount) {
toast({
status: 'warning',
title: t('file:select_file_amount_limit', { max: maxCount })
});
fileList = fileList.slice(0, maxCount);
}
onSelect(fileList, openSign.current);
e.target.value = '';
}}
/>
</Box>
),
[fileType, maxCount, multiple, toast]
);
const onOpen = useCallback((sign?: any) => {
openSign.current = sign;
SelectFileDom.current && SelectFileDom.current.click();
}, []);
return {
File,
onOpen
};
};

View File

@@ -226,7 +226,10 @@ export const iconPaths = {
delete: () => import('./icons/delete.svg'),
edit: () => import('./icons/edit.svg'),
empty: () => import('./icons/empty.svg'),
paragraph: () => import('./icons/paragraph.svg'),
export: () => import('./icons/export.svg'),
point: () => import('./icons/point.svg'),
infoRounded: () => import('./icons/infoRounded.svg'),
'file/csv': () => import('./icons/file/csv.svg'),
'file/fill/csv': () => import('./icons/file/fill/csv.svg'),
'file/fill/doc': () => import('./icons/file/fill/doc.svg'),

View File

@@ -0,0 +1,10 @@
<svg viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="icon/line/info-rounded" clip-path="url(#clip0_9300_11)">
<path id="Icon (Stroke)" fill-rule="evenodd" clip-rule="evenodd" d="M6.99991 2.05302C4.26775 2.05302 2.0529 4.26787 2.0529 7.00003C2.0529 9.73219 4.26775 11.947 6.99991 11.947C9.73207 11.947 11.9469 9.73219 11.9469 7.00003C11.9469 4.26787 9.73207 2.05302 6.99991 2.05302ZM0.88623 7.00003C0.88623 3.62354 3.62342 0.886353 6.99991 0.886353C10.3764 0.886353 13.1136 3.62354 13.1136 7.00003C13.1136 10.3765 10.3764 13.1137 6.99991 13.1137C3.62342 13.1137 0.88623 10.3765 0.88623 7.00003ZM6.41657 4.78789C6.41657 4.46573 6.67774 4.20456 6.99991 4.20456H7.00544C7.3276 4.20456 7.58877 4.46573 7.58877 4.78789C7.58877 5.11006 7.3276 5.37123 7.00544 5.37123H6.99991C6.67774 5.37123 6.41657 5.11006 6.41657 4.78789ZM6.99991 6.4167C7.32207 6.4167 7.58324 6.67786 7.58324 7.00003V9.21217C7.58324 9.53433 7.32207 9.7955 6.99991 9.7955C6.67774 9.7955 6.41657 9.53433 6.41657 9.21217V7.00003C6.41657 6.67786 6.67774 6.4167 6.99991 6.4167Z" />
</g>
<defs>
<clipPath id="clip0_9300_11">
<rect width="14" height="14" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,3 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.19885 4.05802C1.19885 3.68983 1.51614 3.39136 1.90752 3.39136H14.0924C14.4838 3.39136 14.8011 3.68983 14.8011 4.05802C14.8011 4.42621 14.4838 4.72469 14.0924 4.72469H1.90752C1.51614 4.72469 1.19885 4.42621 1.19885 4.05802ZM1.19885 7.95852C1.19885 7.59033 1.51614 7.29185 1.90752 7.29185H14.0924C14.4838 7.29185 14.8011 7.59033 14.8011 7.95852C14.8011 8.32671 14.4838 8.62518 14.0924 8.62518H1.90752C1.51614 8.62518 1.19885 8.32671 1.19885 7.95852ZM1.19885 11.942C1.19885 11.5738 1.51614 11.2753 1.90752 11.2753H9.71358C10.105 11.2753 10.4222 11.5738 10.4222 11.942C10.4222 12.3102 10.105 12.6087 9.71358 12.6087H1.90752C1.51614 12.6087 1.19885 12.3102 1.19885 11.942Z" />
</svg>

After

Width:  |  Height:  |  Size: 805 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 6 6" fill="none">
<circle cx="3" cy="3" r="3" />
</svg>

After

Width:  |  Height:  |  Size: 114 B

View File

@@ -21,7 +21,9 @@ const PopoverConfirm = ({
Trigger,
placement = 'bottom-start',
offset,
onConfirm
onConfirm,
confirmText,
cancelText
}: {
content: string;
showCancel?: boolean;
@@ -30,6 +32,8 @@ const PopoverConfirm = ({
placement?: PlacementWithLogical;
offset?: [number, number];
onConfirm: () => any;
confirmText?: string;
cancelText?: string;
}) => {
const { t } = useTranslation();
@@ -82,11 +86,11 @@ const PopoverConfirm = ({
<HStack mt={1} justifyContent={'flex-end'}>
{showCancel && (
<Button variant={'whiteBase'} size="sm" onClick={onClose}>
{t('common:common.Cancel')}
{cancelText || t('common:common.Cancel')}
</Button>
)}
<Button isLoading={loading} variant={map.variant} size="sm" onClick={onclickConfirm}>
{t('common:common.Confirm')}
{confirmText || t('common:common.Confirm')}
</Button>
</HStack>
</PopoverContent>

View File

@@ -8,7 +8,7 @@ import { Box, Flex } from '@chakra-ui/react';
import { useBasicTypeaheadTriggerMatch } from '../../utils';
import { EditorVariableLabelPickerType } from '../../type';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'react-i18next';
import { useTranslation } from 'next-i18next';
import Avatar from '../../../../Avatar';
interface EditorVariableItemType {

View File

@@ -1,6 +1,6 @@
import { ChevronRightIcon } from '@chakra-ui/icons';
import { Box } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { useTranslation } from 'next-i18next';
import Avatar from '../../../../../../../components/common/Avatar';
export default function VariableLabel({

View File

@@ -2,7 +2,7 @@ import { useRef, useState, useCallback, useMemo, useEffect } from 'react';
import { IconButton, Flex, Box, Input } from '@chakra-ui/react';
import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons';
import { useMutation } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { useTranslation } from 'next-i18next';
import { throttle } from 'lodash';
import { useToast } from './useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';

View File

@@ -75,6 +75,7 @@
"unit": "Number"
},
"move_app": "Move app",
"no": "no",
"paste_config": "Paste Config",
"plugin_cost_per_times": "{{cost}}/per time",
"plugin_dispatch": "Plugins",
@@ -84,6 +85,27 @@
"search_app": "Search app",
"setting_app": "Settings",
"setting_plugin": "Setting plugin",
"support": {
"wallet": {
"bill_tag": {
"bill": "billing records",
"default_header": "Default header",
"invoice": "Invoicing records"
},
"invoice_data": {
"bank": "Bank of deposit",
"bank_account": "Account opening account",
"company_address": "company address",
"company_phone": "company phone",
"email": "email address",
"in_valid": "There are empty fields or incorrect email formats",
"need_special_invoice": "Do you need a special ticket?",
"organization_name": "name of association",
"unit_code": "same credit code"
},
"invoicing": "Invoicing"
}
},
"template": {
"simple_robot": "Simple Robot"
},
@@ -95,6 +117,7 @@
"templateTags": {
"Image_generation": "Image generation",
"Office_services": "Office searvices",
"Recommendation": "recommend",
"Roleplay": "Roleplay",
"Web_search": "Web search",
"Writing": "Writing"
@@ -115,6 +138,7 @@
"Create plugin bot": "Create plugin bot",
"Create simple bot": "Create simple bot",
"Create simple bot tip": "Create simple AI applications in form form",
"Create template tip": "Explore more ways to play in the template market to help you understand and use various applications",
"Create workflow bot": "Create workflow bot",
"Create workflow tip": "Through the way of low code, build a logically complex multi-round dialogue AI application, recommended for advanced players",
"Http plugin": "Http plugin",
@@ -160,5 +184,6 @@
"user_file_input_desc": "Links to documents and images uploaded by users",
"user_select": "User select",
"user_select_tip": "The module can have multiple options that lead to different workflow branches"
}
},
"yes": "yes"
}

View File

@@ -29,6 +29,7 @@
"UnKnow": "Unknown",
"Warning": "Warning",
"add_new": "Add new",
"back": "return",
"chose_condition": "Selection criteria",
"chosen": "selected",
"classification": "Classification",
@@ -36,6 +37,8 @@
"code_editor": "Code edit",
"code_error": {
"app_error": {
"invalid_app_type": "Wrong application type",
"invalid_owner": "Illegal app owner",
"not_exist": "App does not exist",
"un_auth_app": "No permission to operate this application"
},
@@ -237,6 +240,7 @@
"empty": "This directory has nothing selectable~",
"open_dataset": "Open knowledge base"
},
"have_done": "Completed",
"input": {
"Repeat Value": "Duplicate value"
},
@@ -258,6 +262,9 @@
"error tip": "Speech to text failed",
"not support": "Your browser does not support speech input"
},
"submit_success": "Submitted successfully",
"submitted": "Submitted",
"submitting": "Submitting",
"support": "support",
"system": {
"Commercial version function": "Commercial version feature",
@@ -358,6 +365,14 @@
"logs": {
"Source And Time": "Source & Time"
},
"more": "View more",
"navbar": {
"External": "external use",
"Flow mode": "Advanced orchestration",
"Publish": "release",
"Publish app": "Publish application",
"Simple mode": "Easy configuration"
},
"no_app": "There is no application yet, go and create one!",
"not_published": "Unpublished",
"outLink": {
@@ -600,7 +615,8 @@
"success": "Start syncing"
}
},
"training": {}
"training": {
}
},
"data": {
"Auxiliary Data": "Auxiliary data",
@@ -1061,6 +1077,7 @@
"Tools": "Tools"
},
"new_create": "Create New",
"no": "no",
"no_data": "No data",
"no_laf_env": "The system is not configured with Laf environment",
"not_yet_introduced": "No introduction yet",
@@ -1097,7 +1114,12 @@
"Resume InheritPermission Confirm": "Whether to resume to inherit the parent folder's permission?",
"Resume InheritPermission Failed": "Resume Failed",
"Resume InheritPermission Success": "Resume Success",
"change_owner_tip": "Your permissions will not be retained after transfer"
"change_owner": "transfer ownership",
"change_owner_failed": "Transfer ownership failed",
"change_owner_placeholder": "Enter username to find account",
"change_owner_success": "Successfully transferred ownership",
"change_owner_tip": "Your administrator rights will be retained after the transfer",
"change_owner_to": "transferred to"
},
"plugin": {
"App": "Select app",
@@ -1225,6 +1247,7 @@
"Standard Plan Detail": "Plan details",
"To read plan": "View plan",
"amount_0": "The purchase quantity cannot be 0",
"apply_invoice": "Apply for making an invoice",
"bill": {
"Number": "Order number",
"Status": "Status",
@@ -1241,12 +1264,36 @@
"success": "Payment successful"
}
},
"bill_detail": "Bill details",
"bill_tag": {
"bill": "billing records",
"default_header": "Default header",
"invoice": "Invoicing records"
},
"billable_invoice": "Billable bills",
"buy_resource": "Purchase resource pack",
"has_invoice": "Whether the invoice has been issued",
"invoice_amount": "Invoice amount",
"invoice_data": {
"bank": "Bank of deposit",
"bank_account": "Account opening account",
"company_address": "Company address",
"company_phone": "Company phone number",
"email": "Email address",
"in_valid": "There are empty fields or incorrect email formats",
"need_special_invoice": "Do you need a special ticket?",
"organization_name": "Organization name",
"unit_code": "same credit code"
},
"invoice_detail": "Invoice details",
"invoice_info": "The invoice will be sent to your mailbox within 3-7 working days, please be patient.",
"invoicing": "Invoicing",
"moduleName": {
"index": "Index generation",
"qa": "QA split"
},
"noBill": "No bill records~",
"no_invoice": "No invoicing record yet",
"subscription": {
"AI points": "AI points",
"AI points click to read tip": "Each call to the AI model will consume a certain amount of AI points (similar to Tokens). Click to view detailed calculation rules.",
@@ -1322,7 +1369,8 @@
"Total points": "AI points consumed",
"Usage Detail": "Usage details",
"Whisper": "Voice input"
}
},
"use_default": "Use default header"
}
},
"sync_link": "Sync Link",
@@ -1442,5 +1490,6 @@
"type": "type"
},
"verification": "verify",
"xx_search_result": "{{key}} Search results"
"xx_search_result": "{{key}} Search results",
"yes": "yes"
}

View File

@@ -1,10 +1,16 @@
{
"app_key_tips": "These keys have the current application identification, refer to the document for specific use ",
"basic_info": "Basic information",
"copy_link_hint": "Copy the link below to the specified location",
"create_api_key": "Create new Key",
"create_link": "Create link",
"default_response": "Default Response",
"edit_api_key": "Edit Key information",
"edit_feishu_bot": "Edit Feishu Robot",
"edit_link": "Edit",
"feishu_api": "Feishu interface",
"feishu_bot": "Feishu Robot",
"feishu_bot_desc": "Directly access Feishu Robot through API",
"feishu_name": "Lark",
"key_alias": "key alias, for display only ",
"key_tips": "You can use the API Key to access certain interfaces (you can't access the app, you need to use the API key within the app to access the app)",
@@ -12,9 +18,21 @@
"official_account": {
"params": "Wechat params"
},
"new_feishu_bot": "Added Feishu robot",
"publish_name": "name",
"qpm_is_empty": "QPM cannot be empty",
"qpm_tips": "How many times per minute can each IP ask at most",
"request_address": "Request address",
"show_share_link_modal_title": "Get started",
"token_auth": "Token authentication",
"token_auth_tips": "Identity verification server address, if this value is filled, a request will be sent to the specified server before each conversation to perform identity verification",
"token_auth_use_cases": "View usage instructions for identity verification"
"token_auth_use_cases": "View usage instructions for identity verification",
"wecom": {
"api": "Qiwei API",
"bot": "Enterprise WeChat robot",
"bot_desc": "Directly access enterprise WeChat robots through API",
"create_modal_title": "Create a Qiwei robot",
"edit_modal_title": "Edit Qiwei Robot",
"title": "Publish to enterprise WeChat robot"
}
}

View File

@@ -1,6 +1,12 @@
{
"bind_inform_account_error": "Abnormal binding notification account",
"bind_inform_account_success": "Binding notification account successful",
"code_error": {
"app_error": {
"invalid_app_type": "Wrong application type",
"invalid_owner": "Illegal app owner"
}
},
"delete": {
"admin_failed": "Failed to delete administrator",
"admin_success": "Administrator deleted successfully"
@@ -17,7 +23,8 @@
},
"name": "name",
"notification": {
"Bind Notification Pipe Hint": "Bind the email address or mobile phone number for receiving notifications to ensure that you receive important system notifications in a timely manner."
"Bind Notification Pipe Hint": "Bind the email address or mobile phone number for receiving notifications to ensure that you receive important system notifications in a timely manner.",
"remind_owner_bind": "Please remind the creator to bind the notification account"
},
"operations": "operate",
"password": {
@@ -48,6 +55,12 @@
"Read desc": "Members can only read related resources and cannot create new resources.",
"Write": "Write",
"Write tip": "In addition to readable resources, you can also create new resources",
"change_owner": "transfer ownership",
"change_owner_failed": "Transfer ownership failed",
"change_owner_placeholder": "Enter username to find account",
"change_owner_success": "Successfully transferred ownership",
"change_owner_tip": "Your administrator rights will be retained after the transfer",
"change_owner_to": "transferred to",
"only_collaborators": "Collaborator access only",
"team_read": "Team accessible",
"team_write": "Team editable"

View File

@@ -19,6 +19,9 @@
"package_overlay_a": "可以的。每次购买的资源包都是独立的在其有效期内将会叠加使用。AI积分会优先扣除最先过期的资源包。",
"package_overlay_q": "额外资源包可以叠加么?"
},
"yes": "是",
"no": "否",
"back": "返回",
"Folder": "文件夹",
"Login": "登录",
"Move": "移动",
@@ -135,6 +138,7 @@
"Detail": "详情",
"Documents": "文档",
"Done": "完成",
"have_done": "已完成",
"Edit": "编辑",
"Exit": "退出",
"Expired Time": "过期时间",
@@ -190,7 +194,11 @@
"Set Name": "取个名字",
"Setting": "设置",
"Status": "状态",
"submitting": "提交中",
"submit_success": "提交成功",
"Submit failed": "提交失败",
"submitted": "已提交",
"Success": "成功",
"Sync success": "同步成功",
"Team": "团队",
@@ -1233,8 +1241,17 @@
"wallet": {
"Ai point every thousand tokens": "{{points}} 积分/1K tokens",
"Amount": "金额",
"Bills": "账单",
"Bills": "账单与开票",
"invoicing": "开票",
"invoice_amount": "开票金额",
"bill_detail": "账单详情",
"billable_invoice": "可开票账单",
"apply_invoice": "申请开票",
"Buy": "购买",
"invoice_detail": "发票详情",
"invoice_info": "发票将在 3-7 个工作日内发送至邮箱,请耐心等待",
"no_invoice": "暂无开票记录",
"has_invoice": "是否已开票",
"Confirm pay": "支付确认",
"Not sufficient": "您的 AI 积分不足,请先升级套餐或购买额外 AI 积分后继续使用。",
"Plan expired time": "套餐到期时间",
@@ -1242,6 +1259,23 @@
"Standard Plan Detail": "套餐详情",
"To read plan": "查看套餐",
"amount_0": "购买数量不能为0",
"use_default": "使用默认抬头",
"bill_tag": {
"bill": "账单记录",
"invoice": "开票记录",
"default_header": "默认抬头"
},
"invoice_data": {
"organization_name": "组织名称",
"unit_code": "统一信用代码",
"company_address": "公司地址",
"company_phone": "公司电话",
"bank": "开户银行",
"bank_account": "开户账号",
"need_special_invoice": "是否需要专票",
"email": "邮箱地址",
"in_valid": "存在空字段或邮箱格式错误"
},
"bill": {
"Number": "订单号",
"Status": "状态",