feat:export query bill to excel; payment channel only flywire

This commit is contained in:
LittleBoy 2024-07-29 13:07:02 +08:00
parent 9c7bf8c1cb
commit 6741e61e13
11 changed files with 76 additions and 48 deletions

View File

@ -1,4 +1,4 @@
import {Table, Typography} from "@douyinfe/semi-ui"; import {Space, Table, Typography} from "@douyinfe/semi-ui";
import {ColumnProps} from "@douyinfe/semi-ui/lib/es/table"; import {ColumnProps} from "@douyinfe/semi-ui/lib/es/table";
import React, {useMemo, useState} from "react"; import React, {useMemo, useState} from "react";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
@ -18,6 +18,7 @@ type BillListProps = {
onPageChange: (pageIndex:number) => void; onPageChange: (pageIndex:number) => void;
tableFooter?: React.ReactNode; tableFooter?: React.ReactNode;
loading?: boolean; loading?: boolean;
beforeTotalAmount?: React.ReactNode;
} }
export const BillList: React.FC<BillListProps> = (props) => { export const BillList: React.FC<BillListProps> = (props) => {
@ -213,7 +214,9 @@ export const BillList: React.FC<BillListProps> = (props) => {
return <Card return <Card
title={t('bill.title_bill_list')} title={t('bill.title_bill_list')}
headerRight={<div className="bill-info"> headerRight={<Space spacing={20}>
{props.beforeTotalAmount}
<div className="bill-info">
<div className="bill-info-item"> <div className="bill-info-item">
<span className="bill-info-title">{t('bill.query_amount_total')} :</span> <span className="bill-info-title">{t('bill.query_amount_total')} :</span>
<MoneyFormat money={props.source?.pagination.recordTotal || 0}/> <MoneyFormat money={props.source?.pagination.recordTotal || 0}/>
@ -222,7 +225,8 @@ export const BillList: React.FC<BillListProps> = (props) => {
<span className="bill-info-title current-amount">{t('bill.query_amount_current_page')} :</span> <span className="bill-info-title current-amount">{t('bill.query_amount_current_page')} :</span>
<MoneyFormat money={currentTotalAmount || 0}/> <MoneyFormat money={currentTotalAmount || 0}/>
</div> </div>
</div>} </div>
</Space>}
> >
<div className="bill-list-table"> <div className="bill-list-table">
<Table<BillModel> <Table<BillModel>

View File

@ -104,7 +104,7 @@ const SearchForm: React.FC<SearchFormProps> = (props) => {
placeholder={t('base.please_enter')}/> placeholder={t('base.please_enter')}/>
</Col> </Col>
<Col xxl={6} xl={6} md={8}> <Col xxl={6} xl={6} md={8}>
<Form.Select showClear field="payment_channel" label={t('bill.title_pay_method')} <Form.Select showClear field="payment_channel" label={t('bill.title_pay_channel')}
placeholder={t('base.please_select')} style={{width: '100%'}}> placeholder={t('base.please_select')} style={{width: '100%'}}>
<Form.Select.Option value="ASIAPAY">ASIAPAY</Form.Select.Option> <Form.Select.Option value="ASIAPAY">ASIAPAY</Form.Select.Option>
<Form.Select.Option value="FLYWIRE">FLYWIRE</Form.Select.Option> <Form.Select.Option value="FLYWIRE">FLYWIRE</Form.Select.Option>

View File

@ -30,6 +30,7 @@
"confirmed": "Confirmed", "confirmed": "Confirmed",
"download-qr-code": "Download QR Code", "download-qr-code": "Download QR Code",
"download_receipt": "Download receipt", "download_receipt": "Download receipt",
"export_excel": "Export Excel",
"paid": "Paid", "paid": "Paid",
"paid_confirm": "Please confirm the order status is set to paid", "paid_confirm": "Please confirm the order status is set to paid",
"pay_status": "Bill Status", "pay_status": "Bill Status",

View File

@ -30,6 +30,7 @@
"confirmed": "已对账", "confirmed": "已对账",
"download-qr-code": "下载二维码", "download-qr-code": "下载二维码",
"download_receipt": "下载收据", "download_receipt": "下载收据",
"export_excel": "导出账单列表",
"paid": "已支付", "paid": "已支付",
"paid_confirm": "是否将此订单状态设为已支付", "paid_confirm": "是否将此订单状态设为已支付",
"pay_status": "账单状态", "pay_status": "账单状态",

View File

@ -30,6 +30,7 @@
"confirmed": "已對帳", "confirmed": "已對帳",
"download-qr-code": "下載二維碼", "download-qr-code": "下載二維碼",
"download_receipt": "下載收據", "download_receipt": "下載收據",
"export_excel": "Export Excel",
"paid": "已支付", "paid": "已支付",
"paid_confirm": "是否將此訂單狀態設為已支付", "paid_confirm": "是否將此訂單狀態設為已支付",
"pay_status": "帳單狀態", "pay_status": "帳單狀態",

View File

@ -37,7 +37,7 @@ export const BillPaidModal: React.FC<BillPaidModalProps> = (props) => {
} }
const onSubmit = (values: BillUpdateParams) => { const onSubmit = (values: BillUpdateParams) => {
if (!props.bill) return; if (!props.bill) return;
finishBill({bill:props.bill,param:values}).then(props.onConfirm) finishBill({bill: props.bill, param: values}).then(props.onConfirm)
// setState({ // setState({
// loading: true // loading: true
// }) // })
@ -69,7 +69,7 @@ export const BillPaidModal: React.FC<BillPaidModalProps> = (props) => {
<Form<BillUpdateParams> onSubmit={onSubmit} initValues={{ <Form<BillUpdateParams> onSubmit={onSubmit} initValues={{
payment_channel: 'FLYWIRE', payment_channel: 'FLYWIRE',
payment_method: 'card', payment_method: '',
merchant_ref: props.bill?.merchant_ref, merchant_ref: props.bill?.merchant_ref,
payment_amount: props.bill?.amount, payment_amount: props.bill?.amount,
actual_payment_amount: props.bill?.amount actual_payment_amount: props.bill?.amount
@ -88,19 +88,34 @@ export const BillPaidModal: React.FC<BillPaidModalProps> = (props) => {
</Form.Select> </Form.Select>
</Col> </Col>
<Col span={12}> <Col span={12}>
{/*<Form.AutoComplete*/}
{/* rules={[*/}
{/* {required: true, message: 'required error'},*/}
{/* ]}*/}
{/* placeholder={t('base.please_select')} style={{width: '100%'}}*/}
{/* data={[*/}
{/* {value:'card',label:'Card(VISA,MasterCard,UnionPay,JCB...)'},*/}
{/* {value:'wechat',label:'Wechat'},*/}
{/* {value:'alipay',label:'Alipay'},*/}
{/* {value:'other',label:'Other'},*/}
{/* ]}*/}
{/* // renderItem={()=>{*/}
{/* //*/}
{/* // }}*/}
{/* showClear field="payment_method" label={t('bill.title_pay_method')}*/}
{/*/>*/}
<Form.Select <Form.Select
rules={[ rules={[
{required: true, message: 'required error'}, {required: true, message: 'required error'},
]} ]}
showClear field="payment_method" label={t('bill.title_pay_method')} optionList={[
placeholder={t('base.please_select')} style={{width: '100%'}}> {value: 'card', label: 'Card(VISA,MasterCard,UnionPay,JCB...)'},
<Form.Select.Option value="card">Card(VISA,MasterCard,UnionPay,JCB...)</Form.Select.Option> {value: 'wechat', label: 'Wechat'},
<Form.Select.Option value="wechat">Wechat</Form.Select.Option> {value: 'alipay', label: 'Alipay'},
<Form.Select.Option value="alipay">Alipay</Form.Select.Option> {value: 'other', label: 'Other'},
<Form.Select.Option value="other">Other</Form.Select.Option> ]}
{/*<Form.Select.Option value="ASIAPAY">ASIAPAY</Form.Select.Option>*/} allowCreate filter showClear field="payment_method" label={t('bill.title_pay_method')}
{/*<Form.Select.Option value="PPS">PPS</Form.Select.Option>*/} placeholder={t('base.please_select')} style={{width: '100%'}}/>
</Form.Select>
</Col> </Col>
</Row> </Row>
<Row gutter={20}> <Row gutter={20}>
@ -139,7 +154,9 @@ export const BillPaidModal: React.FC<BillPaidModalProps> = (props) => {
<div className={'text-right'} style={{margin: '10px 0 20px'}}> <div className={'text-right'} style={{margin: '10px 0 20px'}}>
<Space spacing={12}> <Space spacing={12}>
<Button onClick={props.onCancel} type={'tertiary'}>{t('base.cancel')}</Button> <Button onClick={props.onCancel} type={'tertiary'}>{t('base.cancel')}</Button>
<Button loading={state.loading} htmlType={'submit'} theme={'solid'} type={'primary'}>{t('base.confirm_paid')}</Button> <Button
loading={state.loading} htmlType={'submit'} theme={'solid'}
type={'primary'}>{t('base.confirm_paid')}</Button>
</Space> </Space>
</div> </div>
</Form> </Form>

View File

@ -2,6 +2,7 @@ import {Button, Divider, Modal, Notification, Popconfirm, Toast} from "@douyinfe
import {useState} from "react"; import {useState} from "react";
import {useRequest, useSetState} from "ahooks"; import {useRequest, useSetState} from "ahooks";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import {stringify} from "qs"
import {BillList} from "@/components/bill/list.tsx"; import {BillList} from "@/components/bill/list.tsx";
import SearchForm from "@/components/bill/search-form.tsx"; import SearchForm from "@/components/bill/search-form.tsx";
@ -12,6 +13,7 @@ import {useDownloadReceiptPDF} from "@/service/generate-pdf.ts";
import {BillDetailItems} from "@/components/bill"; import {BillDetailItems} from "@/components/bill";
import {BillPaidModal} from "@/pages/bill/components/bill_paid_modal.tsx"; import {BillPaidModal} from "@/pages/bill/components/bill_paid_modal.tsx";
import {BillTypeConfirm} from "@/pages/bill/components/bill_type_confirm.tsx"; import {BillTypeConfirm} from "@/pages/bill/components/bill_type_confirm.tsx";
import {saveAs} from "file-saver";
const DownloadButton = ({bill, text}: { bill: BillModel; text: string }) => { const DownloadButton = ({bill, text}: { bill: BillModel; text: string }) => {
@ -84,11 +86,17 @@ const BillQuery = () => {
} }
</div>) </div>)
} }
const onExportExcel = ()=>{
const downloadUrl = `${AppConfig.API_PREFIX || '/api'}/bills/export?${stringify(queryParams)}`
saveAs(downloadUrl, 'bill-result-excel.xlsx')
}
return (<div> return (<div>
<SearchForm showApply loading={loading} onSearch={setBillQueryParams}/> <SearchForm showApply loading={loading} onSearch={setBillQueryParams}/>
<BillList <BillList
type={'query'} loading={loading} source={data} type={'query'} loading={loading} source={data}
operationRender={operation} operationRenderWidth={180} operationRender={operation} operationRenderWidth={180}
beforeTotalAmount={<Button onClick={onExportExcel} theme={'solid'} type={'secondary'}>{t('bill.export_excel')}</Button>}
onPageChange={(page_number) => { onPageChange={(page_number) => {
setBillQueryParams({ setBillQueryParams({
...queryParams, ...queryParams,

View File

@ -107,7 +107,7 @@ export default function Index() {
<div className={styles.QRCodeContainer}> <div className={styles.QRCodeContainer}>
<QRCode size={250} className={styles.qrCode} bill={billInfo}/> <QRCode size={250} className={styles.qrCode} bill={billInfo}/>
</div> </div>
{billInfo && <div {billInfo && billInfo.expiration_time && <div
className={styles.billExpTime}> {t('manual.exp_time')} {dayjs(billInfo.expiration_time).format('YYYY-MM-DD HH:mm')} </div>} className={styles.billExpTime}> {t('manual.exp_time')} {dayjs(billInfo.expiration_time).format('YYYY-MM-DD HH:mm')} </div>}
</div> </div>
</Space> </Space>

View File

@ -1,16 +1,12 @@
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import {useNavigate, useSearchParams} from "react-router-dom"; import {useNavigate, useSearchParams} from "react-router-dom";
import {Radio, RadioGroup} from '@douyinfe/semi-ui';
import styles from './pay.module.less' import styles from './pay.module.less'
import {PayLogo} from "@/pages/pay/component"; import {PayLogo} from "@/pages/pay/component";
import MoneyFormat from "@/components/money-format.tsx"; import MoneyFormat from "@/components/money-format.tsx";
import FlywireLogo from "@/assets/images/pay/flywire.tsx";
import AsiaPayLogo from "@/assets/images/pay/asia_pay.tsx";
import {getBillDetail} from "@/service/api/bill.ts"; import {getBillDetail} from "@/service/api/bill.ts";
import {BillStatus} from "@/service/types.ts"; import {BillStatus} from "@/service/types.ts";
import {StartAsiaPay} from "@/pages/pay/component/start-asia-pay.tsx";
import {StartFlyWire} from "@/pages/pay/component/start-fly-wire.tsx"; import {StartFlyWire} from "@/pages/pay/component/start-fly-wire.tsx";
@ -28,7 +24,7 @@ const PayIndex = () => {
return; return;
} }
const payChannel = search.get('pay_channel') || 'asia_pay' const payChannel = search.get('pay_channel') || 'flywire'
setPayChannel(payChannel) setPayChannel(payChannel)
getBillDetail(Number(billId)).then((bill) => { getBillDetail(Number(billId)).then((bill) => {
// 判断bill状态 // 判断bill状态
@ -59,19 +55,19 @@ const PayIndex = () => {
</div>} </div>}
</div> </div>
</div> </div>
<div className={styles.payChanel}> {/*<div className={styles.payChanel}>*/}
<RadioGroup style={{width: '100%'}} type={'card'} onChange={(e) => setPayChannel(e.target.value)} {/* <RadioGroup style={{width: '100%'}} type={'card'} onChange={(e) => setPayChannel(e.target.value)}*/}
value={payChannel}> {/* value={payChannel}>*/}
<Radio value={'asia_pay'} style={{flex: 1}}> {/* <Radio value={'asia_pay'} style={{flex: 1}}>*/}
<AsiaPayLogo/> {/* <AsiaPayLogo/>*/}
<span style={{marginLeft: 5}}>AsiaPay</span> {/* <span style={{marginLeft: 5}}>AsiaPay</span>*/}
</Radio> {/* </Radio>*/}
<Radio value={'flywire'} style={{flex: 1}}> {/* <Radio value={'flywire'} style={{flex: 1}}>*/}
<FlywireLogo/> {/* <FlywireLogo/>*/}
<span style={{marginLeft: 5}}>Flywire</span> {/* <span style={{marginLeft: 5}}>Flywire</span>*/}
</Radio> {/* </Radio>*/}
</RadioGroup> {/* </RadioGroup>*/}
</div> {/*</div>*/}
<div className={styles.payDetail}> <div className={styles.payDetail}>
{bill.details.length == 1 ? <div className="pay-item"> {bill.details.length == 1 ? <div className="pay-item">
<div className="title">Payment Type:</div> <div className="title">Payment Type:</div>
@ -96,7 +92,7 @@ const PayIndex = () => {
<div className={styles.payConfirm}> <div className={styles.payConfirm}>
{bill.student_email && <div className="student-email">Your Email: {bill.student_email}</div> } {bill.student_email && <div className="student-email">Your Email: {bill.student_email}</div> }
<div className="pay-submit"> <div className="pay-submit">
{ payChannel == 'asia_pay' && <StartAsiaPay bill={bill} />} {/*{ payChannel == 'asia_pay' && <StartAsiaPay bill={bill} />}*/}
<StartFlyWire bill={bill} open={payChannel == 'flywire'} /> <StartFlyWire bill={bill} open={payChannel == 'flywire'} />
</div> </div>
</div> </div>

View File

@ -89,16 +89,16 @@ export async function finishFlywire({bill,param}: BillUpdateFormParams){
"data": { "data": {
"remark": param.remark, "remark": param.remark,
"payment_id": param.merchant_ref, "payment_id": param.merchant_ref,
"amount_from": param.actual_payment_amount, "amount_from": Number(param.actual_payment_amount) * 100,
"currency_from": "HKD", "currency_from": "HKD",
"amount_to": param.payment_amount, "amount_to": Number(param.payment_amount) * 100,
"currency_to": "HKD", "currency_to": "HKD",
"status": "guaranteed", "status": "guaranteed",
"expiration_date": dayjs().format('YYYY-MM-DDTHH:mm:ss[Z]'), "expiration_date": dayjs().format('YYYY-MM-DDTHH:mm:ss[Z]'),
"external_reference": bill.id, "external_reference": bill.id,
"country": "CN", "country": "CN",
"payment_method": { "payment_method": {
"type": param.payment_channel "type": param.payment_method
}, },
"fields": { "fields": {
"student_email": bill.student_email, "student_email": bill.student_email,

View File

@ -35,7 +35,7 @@ export function GeneratePdf(bill: BillModel) {
title: 'Programme:', title: 'Programme:',
content: bill.programme_english_name content: bill.programme_english_name
}, 56) }, 56)
drawItem(doc, {title: 'Mode of Study:', content: bill.attendance_mode}, bill.programme_english_name.length > 70?70:64) drawItem(doc, {title: 'Mode of Study:', content: bill.attendance_mode == 'FT' ? 'FULL-TIME': bill.attendance_mode}, bill.programme_english_name.length > 70?70:64)
// draw table // draw table
autoTable(doc, { autoTable(doc, {
startY: 80, startY: 80,
@ -61,7 +61,7 @@ export function GeneratePdf(bill: BillModel) {
`#${it.id}`, `#${it.id}`,
dayjs(bill.paid_at).format('YYYY-MM-DD'), dayjs(bill.paid_at).format('YYYY-MM-DD'),
it.bill_type, it.bill_type,
`${bill.payment_channel}` + bill.payment_channel != bill.payment_method ? `(${bill.payment_method})` : '', `${bill.payment_channel}` + (bill.payment_channel != bill.payment_method ? `(${bill.payment_method})` : ''),
`${it.amount}` `${it.amount}`
]; ];
})), })),