feat add bill/student number confirm

This commit is contained in:
LittleBoy 2024-08-05 12:37:06 +08:00
parent 1f16e05c01
commit f50cc00d84
14 changed files with 249 additions and 81 deletions

View File

@ -130,6 +130,9 @@ body #root{
} }
} }
} }
.text-nowrap{
white-space: nowrap;
}
// input // input
.semi-input-wrapper, .semi-select,.semi-datepicker-range-input,.semi-input-textarea-wrapper { .semi-input-wrapper, .semi-select,.semi-datepicker-range-input,.semi-input-textarea-wrapper {

View File

@ -1,25 +1,29 @@
import React, {useMemo} from "react"; import React, {useMemo} from "react";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import {IconBillType, IconMoney, IconStudentEmail, IconStudentId} from "@/components/icons"; import {IconBillType, IconMoney, IconStudentEmail, IconStudentId, IconStudentName} from "@/components/icons";
import MoneyFormat from "@/components/money-format.tsx"; import MoneyFormat from "@/components/money-format.tsx";
import './bill.less' import './bill.less'
const BillDetailItem = (item: { title: React.ReactNode; value: React.ReactNode, icon?: React.ReactNode }) => { export const BillDetailItem = (item: { title: React.ReactNode; value: React.ReactNode, icon?: React.ReactNode }) => {
return <div className={'bill-detail-item'}> return <div className={'bill-detail-item'}>
<div className={'detail-item-title'}>{item.icon} <span className={'item-title'}>{item.title}</span> :</div> <div className={'detail-item-title'}>{item.icon} <span className={'item-title'}>{item.title}</span> :</div>
<div className={'detail-item-value'}>{item.value}</div> <div className={'detail-item-value'}>{item.value}</div>
</div> </div>
} }
const BillDetailItems = (prop: { bill: BillModel }) => { type BillDetailItemsProps = {
bill: BillModel;
studentNumberRender?: React.ReactNode;
}
const BillDetailItems = (prop: BillDetailItemsProps) => {
const {t} = useTranslation(); const {t} = useTranslation();
const billType = useMemo(()=>{ const billType = useMemo(()=>{
return prop.bill.details[0].bill_type return prop.bill.details[0].bill_type
},[prop.bill]) },[prop.bill])
return (<> return (<>
<BillDetailItem icon={<IconBillType/>} title={t('manual.bill_type')} value={billType}/> <BillDetailItem icon={<IconBillType/>} title={t('manual.bill_type')} value={billType}/>
<BillDetailItem icon={<IconStudentId/>} title={t('manual.student_number')} value={prop.bill.student_number || prop.bill.application_number}/> {prop.studentNumberRender?prop.studentNumberRender:<BillDetailItem icon={<IconStudentId/>} title={t('manual.student_number')} value={prop.bill.student_number || prop.bill.application_number || '-'}/>}
<BillDetailItem icon={<IconStudentId/>} title={t('bill.title_student_name')} <BillDetailItem icon={<IconStudentName/>} title={t('bill.title_student_name')}
value={`${prop.bill.student_english_name||'-'}${prop.bill.student_chinese_name?' / '+ prop.bill.student_chinese_name : ''}`}/> value={`${prop.bill.student_english_name||'-'}${prop.bill.student_chinese_name?' / '+ prop.bill.student_chinese_name : ''}`}/>
<BillDetailItem icon={<IconStudentEmail/>} title={'Email'} value={prop.bill.student_email||'-'}/> <BillDetailItem icon={<IconStudentEmail/>} title={'Email'} value={prop.bill.student_email||'-'}/>
<BillDetailItem icon={<IconMoney/>} title={t('manual.amount')} value={<MoneyFormat money={prop.bill.amount}/>}/> <BillDetailItem icon={<IconMoney/>} title={t('manual.amount')} value={<MoneyFormat money={prop.bill.amount}/>}/>

View File

@ -32,7 +32,10 @@ export const BillList: React.FC<BillListProps> = (props) => {
return t('bill.pay_status_pending') return t('bill.pay_status_pending')
case 'PAID': case 'PAID':
return t('bill.pay_status_paid') return t('bill.pay_status_paid')
case 'EXPIRED':
return t('bill.pay_status_expired')
case 'CANCELED': case 'CANCELED':
case 'CANCELLED':
return t('bill.pay_status_canceled') return t('bill.pay_status_canceled')
default: default:
return billStatus return billStatus
@ -56,6 +59,12 @@ export const BillList: React.FC<BillListProps> = (props) => {
dataIndex: 'id', dataIndex: 'id',
width: 120, width: 120,
}, },
{
title: 'Merchant Ref',
dataIndex: 'merchant_ref',
width: 200,
// render: (_) => (<MoneyFormat money={_}/>),
},
{ {
title: t('base.student_number'), title: t('base.student_number'),
dataIndex: 'student_number', dataIndex: 'student_number',
@ -68,33 +77,33 @@ export const BillList: React.FC<BillListProps> = (props) => {
width: 150, width: 150,
}, },
{ {
title: '开始支付时间', title: t('bill.title_initiated_paid_at'),
dataIndex: 'initiated_paid_at', dataIndex: 'initiated_paid_at',
width: 150, width: 180,
render: (value) => value?.length ?value: 'N/A' render: (value) => value?.length ?value: 'N/A'
}, },
{ {
title: '到账时间', title: t('bill.title_delivered_at'),
dataIndex: 'delivered_at', dataIndex: 'delivered_at',
width: 150, width: 180,
render: (value) => value?.length ?value: 'N/A' render: (value) => value?.length ?value: 'N/A'
}, },
{ {
title: t('bill.title_paid_at'), title: t('bill.title_paid_at'),
dataIndex: 'paid_at', dataIndex: 'paid_at',
width: 150, width: 180,
render: (value) => value?.length ?value: 'N/A' render: (value) => value?.length ?value: 'N/A'
}, },
{ {
title: t('bill.title_create_at'), title: t('bill.title_create_at'),
dataIndex: 'create_at', dataIndex: 'create_at',
width: 150, width: 180,
render: (value) => value?.length ?value: 'N/A' render: (value) => value?.length ?value: 'N/A'
}, },
{ {
title: t('bill.title_student_name'), title: t('bill.title_student_name'),
dataIndex: 'student_english_name', dataIndex: 'student_english_name',
width: 150, width: 180,
render: (_, record) => (<div>{record.student_english_name}<br/>{record.student_chinese_name}</div>) render: (_, record) => (<div>{record.student_english_name}<br/>{record.student_chinese_name}</div>)
}, },
{ {
@ -118,7 +127,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
title: t('bill.title_year'), title: t('bill.title_year'),
dataIndex: 'intake_year', dataIndex: 'intake_year',
width: 120, width: 120,
render: (_, record) => (<div>{record.intake_year}/{String(record.intake_semester).length == 1 ? '0':''}{record.intake_semester}</div>) render: (_, record) => record.intake_year?(<div>{record.intake_year}/{String(record.intake_semester).length == 1 ? '0':''}{record.intake_semester}</div>):"N/A"
}, },
// { // {
// title: t('bill.title_semester'), // title: t('bill.title_semester'),
@ -187,12 +196,6 @@ export const BillList: React.FC<BillListProps> = (props) => {
</div> </div>
):'N/A'), ):'N/A'),
}, },
{
title: 'Merchant Ref',
dataIndex: 'merchant_ref',
width: 250,
// render: (_) => (<MoneyFormat money={_}/>),
},
{ {
title: t('bill.title_bill_status'), title: t('bill.title_bill_status'),
dataIndex: 'status', dataIndex: 'status',

View File

@ -4,6 +4,7 @@ import dayjs from "dayjs";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import {Card} from "@/components/card"; import {Card} from "@/components/card";
import {BillQueryParams} from "@/service/api/bill.ts"; import {BillQueryParams} from "@/service/api/bill.ts";
import {BillTypes} from "@/service/bill-types.ts";
type SearchFormProps = { type SearchFormProps = {
onSearch?: (params: BillQueryParams) => void; onSearch?: (params: BillQueryParams) => void;
@ -20,6 +21,7 @@ type SearchFormFields = {
application_number?: string; application_number?: string;
bill_number?: string; bill_number?: string;
payment_channel?: string; payment_channel?: string;
confirm_status?: ConfirmStatus;
bill_status?: string; bill_status?: string;
apply_status?: string; apply_status?: string;
sort_by?: string; sort_by?: string;
@ -35,9 +37,6 @@ const SearchForm: React.FC<SearchFormProps> = (props) => {
if (value.id) { if (value.id) {
params.id = value.id; params.id = value.id;
} }
if (value.merchant_ref) {
params.merchant_ref = value.merchant_ref;
}
if (value.student_number) { if (value.student_number) {
params.student_number = value.student_number; params.student_number = value.student_number;
} }
@ -48,6 +47,10 @@ const SearchForm: React.FC<SearchFormProps> = (props) => {
if (value.apply_status) { if (value.apply_status) {
params.apply_status = value.apply_status; params.apply_status = value.apply_status;
} }
// 确认状态
if (value.confirm_status) {
params.confirm_status = value.confirm_status;
}
// 账单状态 // 账单状态
if (value.bill_status) { if (value.bill_status) {
params.status = value.bill_status; params.status = value.bill_status;
@ -91,17 +94,19 @@ const SearchForm: React.FC<SearchFormProps> = (props) => {
<div className="bill-search-form"> <div className="bill-search-form">
<Form<SearchFormFields> onSubmit={formSubmit}> <Form<SearchFormFields> onSubmit={formSubmit}>
<Row type={'flex'} gutter={16}> <Row type={'flex'} gutter={16}>
<Col xxl={3} xl={6} md={8}>
<Form.Input showClear field='id' label="ID" trigger='blur' placeholder={t('base.please_enter')}/>
</Col>
<Col xxl={3} xl={6} md={8}>
<Form.Input showClear field='merchant_ref' label="Merchant Ref" trigger='blur' placeholder={t('base.please_enter')}/>
</Col>
<Col xxl={6} xl={6} md={8}> <Col xxl={6} xl={6} md={8}>
<Form.DatePicker showClear type={'dateRange'} field="dateRange" label={t('bill.bill_date')} <Form.DatePicker showClear type={'dateRange'} field="dateRange" label={t('bill.title_initiated_paid_at')}
style={{width: '100%'}}> style={{width: '100%'}}>
</Form.DatePicker> </Form.DatePicker>
</Col> </Col>
<Col xxl={6} xl={6} md={8}>
<Form.DatePicker showClear type={'dateRange'} field="delivered_at" label={t('bill.title_delivered_at')}
style={{width: '100%'}}>
</Form.DatePicker>
</Col>
<Col xxl={4} xl={6} md={8}>
<Form.Input showClear field='id' label="ID / Merchant Ref" trigger='blur' placeholder={t('base.please_enter')}/>
</Col>
<Col xxl={4} xl={6} md={8}> <Col xxl={4} xl={6} md={8}>
<Form.Input showClear field='student_number' label={t('base.student_number')} trigger='blur' <Form.Input showClear field='student_number' label={t('base.student_number')} trigger='blur'
placeholder={t('base.please_enter')}/> placeholder={t('base.please_enter')}/>
@ -126,7 +131,7 @@ const SearchForm: React.FC<SearchFormProps> = (props) => {
<Form.Select.Option value="create_at asc">{t('bill.title_create_at')} {t('bill.sort_asc')}</Form.Select.Option> <Form.Select.Option value="create_at asc">{t('bill.title_create_at')} {t('bill.sort_asc')}</Form.Select.Option>
</Form.Select> </Form.Select>
</Col> </Col>
<Col xxl={6} xl={6} md={8}> <Col xxl={4} xl={6} md={8}>
<Form.Select showClear field="payment_channel" label={t('bill.title_pay_channel')} <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="FLYWIRE">FLYWIRE</Form.Select.Option> <Form.Select.Option value="FLYWIRE">FLYWIRE</Form.Select.Option>
@ -134,23 +139,34 @@ const SearchForm: React.FC<SearchFormProps> = (props) => {
<Form.Select.Option value="PPS">PPS</Form.Select.Option> <Form.Select.Option value="PPS">PPS</Form.Select.Option>
</Form.Select> </Form.Select>
</Col> </Col>
<Col xxl={6} xl={6} md={8}> <Col xxl={4} xl={6} md={8}>
<Form.Select showClear field="bill_type" label={t('bill.title_bill_type')} <Form.Select
placeholder={t('base.please_select')} style={{width: '100%'}}> field="bill_type" style={{width: '100%'}}
<Form.Select.Option value="FLYWIRE">FLYWIRE</Form.Select.Option> label={t('manual.bill_type')}
<Form.Select.Option value="CBP">CBP</Form.Select.Option> placeholder={t('manual.bill_type')}
<Form.Select.Option value="PPS">PPS</Form.Select.Option> >
{
BillTypes.map((it, idx) => (
<Form.Select.Option key={idx} value={it.label}>{it.label}</Form.Select.Option>))
}
</Form.Select> </Form.Select>
</Col> </Col>
{props.showApply && <> {props.showApply && <>
<Col xxl={6} xl={6} md={8}> <Col xxl={4} xl={6} md={8}>
<Form.Select showClear field="confirm_status" label={t('bill.title_confirm_status')}
placeholder={t('base.please_select')} style={{width: '100%'}}>
<Form.Select.Option value="CONFIRMED">{t('bill.status_confirmed')}</Form.Select.Option>
<Form.Select.Option value="UNCONFIRMED">{t('bill.status_unconfirmed')}</Form.Select.Option>
</Form.Select>
</Col>
<Col xxl={4} xl={6} md={8}>
<Form.Select showClear field="bill_status" label={t('bill.pay_status')} <Form.Select showClear field="bill_status" label={t('bill.pay_status')}
placeholder={t('base.please_select')} style={{width: '100%'}}> placeholder={t('base.please_select')} style={{width: '100%'}}>
{billStatusOptions.map((item, index) => ( {billStatusOptions.map((item, index) => (
<Form.Select.Option key={index} value={item.value}>{item.label}</Form.Select.Option>))} <Form.Select.Option key={index} value={item.value}>{item.label}</Form.Select.Option>))}
</Form.Select> </Form.Select>
</Col> </Col>
<Col xxl={6} xl={6} md={8}> <Col xxl={4} xl={6} md={8}>
<Form.Select showClear field="apply_status" label={t('bill.title_reconciliation_status')} <Form.Select showClear field="apply_status" label={t('bill.title_reconciliation_status')}
placeholder={t('base.please_select')} style={{width: '100%'}}> placeholder={t('base.please_select')} style={{width: '100%'}}>
{applyStatusOptions.map((item, index) => ( {applyStatusOptions.map((item, index) => (
@ -158,7 +174,7 @@ const SearchForm: React.FC<SearchFormProps> = (props) => {
</Form.Select> </Form.Select>
</Col> </Col>
</>} </>}
<Col xxl={6} xl={6} md={8} style={{display: 'flex', alignItems: 'flex-end', paddingBottom: 12}}> <Col xxl={4} xl={6} md={8} style={{display: 'flex', alignItems: 'flex-end', paddingBottom: 12}}>
<Button loading={props.loading} style={{width: 100}} htmlType={'submit'} theme={'solid'} <Button loading={props.loading} style={{width: 100}} htmlType={'submit'} theme={'solid'}
type={'primary'}>{t('base.btn_search_submit')}</Button> type={'primary'}>{t('base.btn_search_submit')}</Button>
</Col> </Col>

View File

@ -60,6 +60,22 @@ export const IconMoney = ({style}: IconProps) => {
} }
export const IconStudentId = ({style}: IconProps) => { export const IconStudentId = ({style}: IconProps) => {
return (
<svg
className="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" style={style}>
<path
d="M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32z m-40 632H136V232h752v560z"
fill="#00C479"
></path>
<path
d="M610.3 476h123.4c1.3 0 2.3-3.6 2.3-8v-48c0-4.4-1-8-2.3-8H610.3c-1.3 0-2.3 3.6-2.3 8v48c0 4.4 1 8 2.3 8zM615.1 620h185.7c3.9 0 7.1-3.6 7.1-8v-48c0-4.4-3.2-8-7.1-8H615.1c-3.9 0-7.1 3.6-7.1 8v48c0 4.4 3.2 8 7.1 8zM224 673h43.9c4.2 0 7.6-3.3 7.9-7.5 3.8-50.5 46-90.5 97.2-90.5s93.4 40 97.2 90.5c0.3 4.2 3.7 7.5 7.9 7.5H522c4.6 0 8.2-3.8 8-8.4-2.8-53.3-32-99.7-74.6-126.1 18.1-19.9 29.1-46.4 29.1-75.5 0-61.9-49.9-112-111.4-112s-111.4 50.1-111.4 112c0 29.1 11 55.5 29.1 75.5-42.7 26.5-71.8 72.8-74.6 126.1-0.4 4.6 3.2 8.4 7.8 8.4z m149-262c28.5 0 51.7 23.3 51.7 52s-23.2 52-51.7 52-51.7-23.3-51.7-52 23.2-52 51.7-52z"
></path>
</svg>
)
}
export const IconStudentName = ({style}: IconProps) => {
return ( return (
<svg className="icon" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" <svg className="icon" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" style={style}> width="1em" height="1em" style={style}>
@ -88,6 +104,7 @@ export const IconStudentId = ({style}: IconProps) => {
</g> </g>
</svg>) </svg>)
} }
export const IconStudentEmail = ({style}: IconProps) => { export const IconStudentEmail = ({style}: IconProps) => {
return ( return (
<svg className="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" <svg className="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"

View File

@ -15,7 +15,7 @@
"student_number": "Student Number" "student_number": "Student Number"
}, },
"bill": { "bill": {
"bill_date": "Date", "bill_date": "In",
"bill_number": "Bill Number", "bill_number": "Bill Number",
"cancel": "Cancel", "cancel": "Cancel",
"cancel_confirm": "Please make sure to cancel the bill", "cancel_confirm": "Please make sure to cancel the bill",
@ -23,9 +23,13 @@
"cancel_success": "Successful cancel bill", "cancel_success": "Successful cancel bill",
"confirm": "Check", "confirm": "Check",
"confirm_batch": "Batch Confirm", "confirm_batch": "Batch Confirm",
"confirm_bill": "Confirm Bill Information",
"confirm_bill_number": "Confirm Bill Number",
"confirm_bill_type": "Confirm Bill", "confirm_bill_type": "Confirm Bill",
"confirm_bill_type_batch": "Batch confirm Bill Type",
"confirm_confirm_title": "Confirm check the Bill?", "confirm_confirm_title": "Confirm check the Bill?",
"confirm_select_empty": "Require confirm bill data", "confirm_select_empty": "Require confirm bill data",
"confirm_student_number": "Confirm Student Number",
"confirm_success": "Confirm success!", "confirm_success": "Confirm success!",
"confirmed": "Confirmed", "confirmed": "Confirmed",
"download-qr-code": "Download QR Code", "download-qr-code": "Download QR Code",
@ -36,6 +40,7 @@
"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",
"pay_status_canceled": "CANCELED", "pay_status_canceled": "CANCELED",
"pay_status_expired": "EXPIRED",
"pay_status_paid": "PAID", "pay_status_paid": "PAID",
"pay_status_pending": "PENDING", "pay_status_pending": "PENDING",
"query_amount_current_page": "The total amount of current page", "query_amount_current_page": "The total amount of current page",
@ -46,6 +51,8 @@
"set_bill_paid": "Set Bill Paid", "set_bill_paid": "Set Bill Paid",
"sort_asc": "ASC", "sort_asc": "ASC",
"sort_desc": "DESC", "sort_desc": "DESC",
"status_confirmed": "CONFIRMED",
"status_unconfirmed": "UNCONFIRMED",
"title_actual_payment_amount": "Actually Paid", "title_actual_payment_amount": "Actually Paid",
"title_amount": "Amount", "title_amount": "Amount",
"title_bill_detail": "Bill Detail", "title_bill_detail": "Bill Detail",
@ -53,17 +60,20 @@
"title_bill_status": "Bill Status", "title_bill_status": "Bill Status",
"title_bill_type": "Bill Type", "title_bill_type": "Bill Type",
"title_bill_type_confirm": "Confirm Bill Type", "title_bill_type_confirm": "Confirm Bill Type",
"title_create_at": "Input Date", "title_confirm_status": "Confirm Status",
"title_create_at": "Input Time",
"title_delivered_at": "Delivered Time",
"title_department": "Department", "title_department": "Department",
"title_initiated_paid_at": "Initiated Time",
"title_operate": "Operation", "title_operate": "Operation",
"title_paid_at": "Transaction Date", "title_paid_at": "Transaction Time",
"title_pay_amount": "Pay Amount", "title_pay_amount": "Pay Amount",
"title_pay_channel": "Payment Channel", "title_pay_channel": "Payment Channel",
"title_pay_method": "Pay Method", "title_pay_method": "Pay Method",
"title_pay_sort": "Sort By", "title_pay_sort": "Sort By",
"title_program_id": "Program ID", "title_program_id": "Program ID",
"title_program_name": "Program", "title_program_name": "Program",
"title_reconciliation_status": "Reconciliation", "title_reconciliation_status": "Reconciliation Status",
"title_remark": "Remark", "title_remark": "Remark",
"title_semester": "Semester", "title_semester": "Semester",
"title_service_charge": "Service Charge", "title_service_charge": "Service Charge",

View File

@ -15,7 +15,7 @@
"student_number": "学号" "student_number": "学号"
}, },
"bill": { "bill": {
"bill_date": "支付日期", "bill_date": "开始支付时间",
"bill_number": "账单编号", "bill_number": "账单编号",
"cancel": "作废", "cancel": "作废",
"cancel_confirm": "确定作废此账单", "cancel_confirm": "确定作废此账单",
@ -23,9 +23,13 @@
"cancel_success": "作废账单成功", "cancel_success": "作废账单成功",
"confirm": "对账", "confirm": "对账",
"confirm_batch": "批量对账", "confirm_batch": "批量对账",
"confirm_bill": "确认账单信息",
"confirm_bill_number": "确认账单编号",
"confirm_bill_type": "确认账单", "confirm_bill_type": "确认账单",
"confirm_bill_type_batch": "批量确认账单",
"confirm_confirm_title": "请确定对账此账单?", "confirm_confirm_title": "请确定对账此账单?",
"confirm_select_empty": "对账账单为空", "confirm_select_empty": "对账账单为空",
"confirm_student_number": "确认学号",
"confirm_success": "对账成功!", "confirm_success": "对账成功!",
"confirmed": "已对账", "confirmed": "已对账",
"download-qr-code": "下载二维码", "download-qr-code": "下载二维码",
@ -36,6 +40,7 @@
"paid_confirm": "是否将此订单状态设为已支付", "paid_confirm": "是否将此订单状态设为已支付",
"pay_status": "账单状态", "pay_status": "账单状态",
"pay_status_canceled": "已作废", "pay_status_canceled": "已作废",
"pay_status_expired": "已过期",
"pay_status_paid": "已支付", "pay_status_paid": "已支付",
"pay_status_pending": "未支付", "pay_status_pending": "未支付",
"query_amount_current_page": "当前页总金额", "query_amount_current_page": "当前页总金额",
@ -46,6 +51,8 @@
"set_bill_paid": "设置账单支付完成", "set_bill_paid": "设置账单支付完成",
"sort_asc": "升序", "sort_asc": "升序",
"sort_desc": "降序", "sort_desc": "降序",
"status_confirmed": "已确认",
"status_unconfirmed": "未确认",
"title_actual_payment_amount": "实付金额", "title_actual_payment_amount": "实付金额",
"title_amount": "账单金额", "title_amount": "账单金额",
"title_bill_detail": "账单详情", "title_bill_detail": "账单详情",
@ -53,8 +60,11 @@
"title_bill_status": "账单状态", "title_bill_status": "账单状态",
"title_bill_type": "账单类型", "title_bill_type": "账单类型",
"title_bill_type_confirm": "确认账单", "title_bill_type_confirm": "确认账单",
"title_confirm_status": "确认状态",
"title_create_at": "创建时间", "title_create_at": "创建时间",
"title_delivered_at": "到账时间",
"title_department": "学系", "title_department": "学系",
"title_initiated_paid_at": "开始支付时间",
"title_operate": "操作", "title_operate": "操作",
"title_paid_at": "支付时间", "title_paid_at": "支付时间",
"title_pay_amount": "应付金额", "title_pay_amount": "应付金额",

View File

@ -15,7 +15,7 @@
"student_number": "學號" "student_number": "學號"
}, },
"bill": { "bill": {
"bill_date": "支付日期", "bill_date": "開始支付時間",
"bill_number": "帳單編號", "bill_number": "帳單編號",
"cancel": "作廢", "cancel": "作廢",
"cancel_confirm": "確定作廢此帳單", "cancel_confirm": "確定作廢此帳單",
@ -23,9 +23,13 @@
"cancel_success": "作廢帳單成功", "cancel_success": "作廢帳單成功",
"confirm": "對帳", "confirm": "對帳",
"confirm_batch": "批次對帳", "confirm_batch": "批次對帳",
"confirm_bill": "確認帳單資訊",
"confirm_bill_number": "確認帳單編號",
"confirm_bill_type": "確認賬單", "confirm_bill_type": "確認賬單",
"confirm_bill_type_batch": "批次確認帳單",
"confirm_confirm_title": "請確定對帳此帳單?", "confirm_confirm_title": "請確定對帳此帳單?",
"confirm_select_empty": "對帳帳單為空", "confirm_select_empty": "對帳帳單為空",
"confirm_student_number": "確認學號",
"confirm_success": "對帳成功!", "confirm_success": "對帳成功!",
"confirmed": "已對帳", "confirmed": "已對帳",
"download-qr-code": "下載二維碼", "download-qr-code": "下載二維碼",
@ -36,6 +40,7 @@
"paid_confirm": "是否將此訂單狀態設為已支付", "paid_confirm": "是否將此訂單狀態設為已支付",
"pay_status": "帳單狀態", "pay_status": "帳單狀態",
"pay_status_canceled": "已作廢", "pay_status_canceled": "已作廢",
"pay_status_expired": "已過期",
"pay_status_paid": "已付款", "pay_status_paid": "已付款",
"pay_status_pending": "未付款", "pay_status_pending": "未付款",
"query_amount_current_page": "目前頁總金額", "query_amount_current_page": "目前頁總金額",
@ -46,6 +51,8 @@
"set_bill_paid": "設定帳單支付完成", "set_bill_paid": "設定帳單支付完成",
"sort_asc": "升序", "sort_asc": "升序",
"sort_desc": "降序", "sort_desc": "降序",
"status_confirmed": "已確認",
"status_unconfirmed": "未確認",
"title_actual_payment_amount": "實付金額", "title_actual_payment_amount": "實付金額",
"title_amount": "帳單金額", "title_amount": "帳單金額",
"title_bill_detail": "帳單詳情", "title_bill_detail": "帳單詳情",
@ -53,8 +60,11 @@
"title_bill_status": "帳單狀態", "title_bill_status": "帳單狀態",
"title_bill_type": "帳單類型", "title_bill_type": "帳單類型",
"title_bill_type_confirm": "確認帳單", "title_bill_type_confirm": "確認帳單",
"title_confirm_status": "確認狀態",
"title_create_at": "創建時間", "title_create_at": "創建時間",
"title_delivered_at": "到帳時間",
"title_department": "學系", "title_department": "學系",
"title_initiated_paid_at": "開始支付時間",
"title_operate": "操作", "title_operate": "操作",
"title_paid_at": "付款時間", "title_paid_at": "付款時間",
"title_pay_amount": "應付金額", "title_pay_amount": "應付金額",

View File

@ -27,15 +27,15 @@ export const BillTypeConfirm: React.FC<BillTypeConfirmProps> = (props) => {
}) })
} }
return <div className="confirm-item align-center space-between" return <div className="confirm-item align-center space-between"
style={{marginBottom: 10}}> style={{marginBottom: 20}}>
<div> <div style={{lineHeight:1.1}}>
<div>{it.bill_type}</div> <div>{it.bill_type}</div>
<div> <div style={{fontSize:12}}>
<MoneyFormat money={it.amount}/> <MoneyFormat money={it.amount}/>
</div> </div>
</div> </div>
<div className="confirm-item-btn"> <div className="confirm-item-btn">
<Space spacing={20}> <Space spacing={15}>
{it.confirm_status != 'CONFIRMED' && <Select onChange={v=>setState({bill_type:String(v)})} defaultValue={it.bill_type} style={{width:180}} placeholder={t('manual.bill_type')}> {it.confirm_status != 'CONFIRMED' && <Select onChange={v=>setState({bill_type:String(v)})} defaultValue={it.bill_type} style={{width:180}} placeholder={t('manual.bill_type')}>
{ {
BillTypes.map((it, idx) => ( BillTypes.map((it, idx) => (
@ -43,12 +43,12 @@ export const BillTypeConfirm: React.FC<BillTypeConfirmProps> = (props) => {
} }
</Select>} </Select>}
{ {
it.confirm_status == 'CONFIRMED' ? <Tag color='light-blue'>{state.bill_type}</Tag> : <> it.confirm_status == 'CONFIRMED' ? <Tag size={'large'} color='light-blue'>{state.bill_type}</Tag> : <>
<Popconfirm <Popconfirm
title={'Notice'} onConfirm={() => onConfirmBill()} title={'Notice'} onConfirm={() => onConfirmBill()}
position={'topRight'} position={'topRight'}
content={`${t('bill.confirm_bill_type')}?`} content={`${t('bill.confirm_bill_type')}?`}
><Button loading={state.loading} theme={'solid'}>{t('base.confirm')}</Button></Popconfirm> ><Button style={{width:80}} loading={state.loading} theme={'solid'}>{t('base.confirm')}</Button></Popconfirm>
</> </>
} }
</Space> </Space>

View File

@ -0,0 +1,56 @@
import {Button, Space, Tag, Input} from "@douyinfe/semi-ui";
import React from "react";
import {useSetState} from "ahooks";
import {useTranslation} from "react-i18next";
type NumberConfirmProps = {
bill: BillModel;
type: 'student_number' | 'application_number'
}
export const NumberConfirm: React.FC<NumberConfirmProps> = (props) => {
const {t} = useTranslation()
const [state, setState] = useSetState({
loading: false,
confirmed: false,
confirmNumber: (props.type == 'application_number' ? props.bill.application_number : props.bill.student_number) || '',
})
const onConfirm = () => {
if (!state.confirmNumber.length) return
setState({loading: true})
setTimeout(() => {
setState({loading: false, confirmed: true})
}, 500)
// confirmBillType({id:it.id,type:state.bill_type}).then(() => {
// setState({loading:false})
// setItem({...it,confirm_status:'CONFIRMED'})
// }).catch(() => {
// setState({loading:false})
// })
}
return <div
className="confirm-item align-center space-between"
style={{marginBottom: 15}}>
<div>
<div>{t(props.type == 'student_number' ? 'bill.confirm_student_number' : 'bill.confirm_bill_number')}</div>
</div>
<div className="confirm-item-btn">
<Space spacing={15}>
{!state.confirmed && <Input
onChange={v => setState({confirmNumber: String(v)})}
defaultValue={state.confirmNumber}
style={{width: 180}} placeholder={t('base.please_enter')}/>}
{
state.confirmed ? <Space>
<div>{state.confirmNumber}</div>
<Tag size={'large'} color='light-blue'>CONFIRMED</Tag>
</Space> : <Button
style={{width: 80}} disabled={!state.confirmNumber} onClick={onConfirm}
loading={state.loading} theme={'solid'}>{t('base.confirm')}</Button>
}
</Space>
</div>
</div>
}

View File

@ -13,6 +13,9 @@ 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"; import {saveAs} from "file-saver";
import {IconStudentId} from "@/components/icons";
import {BillDetailItem} from "@/components/bill/bill-detail-items.tsx";
import {NumberConfirm} from "@/pages/bill/components/number_confirm.tsx";
const DownloadButton = ({bill, text}: { bill: BillModel; text: string }) => { const DownloadButton = ({bill, text}: { bill: BillModel; text: string }) => {
@ -86,27 +89,31 @@ const BillQuery = () => {
} }
</div>) </div>)
} }
const onExportExcel = ()=>{ const onExportExcel = () => {
// const downloadUrl = `${AppConfig.API_PREFIX || '/api'}/bills/export?${stringify(queryParams)}` // const downloadUrl = `${AppConfig.API_PREFIX || '/api'}/bills/export?${stringify(queryParams)}`
// //
// //
// saveAs(downloadUrl, 'bill-result-excel.xlsx') // saveAs(downloadUrl, 'bill-result-excel.xlsx')
setState({ setState({
exporting:true exporting: true
}) })
exportBillList(queryParams).then(ret=>{ exportBillList(queryParams).then(ret => {
console.log(ret) console.log(ret)
const blob = new Blob([ret], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}); const blob = new Blob([ret], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
saveAs(blob, 'bill-result-excel.xlsx') saveAs(blob, 'bill-result-excel.xlsx')
}).finally(()=>{ }).finally(() => {
setState({ setState({
exporting:false exporting: false
}) })
}) })
} }
const onImportExcel = ()=>{ const onImportExcel = () => {
Toast.warning({content:'Not implemented'}) Toast.warning({content: 'Not implemented'})
}
const [selectKeys, setSelectedKeys] = useState<(string | number)[]>([])
const confirmBillTypeBatch = () => {
console.log(selectKeys)
} }
return (<div> return (<div>
@ -114,10 +121,22 @@ const BillQuery = () => {
<BillList <BillList
type={'query'} loading={loading} source={data} type={'query'} loading={loading} source={data}
operationRender={operation} operationRenderWidth={180} operationRender={operation} operationRenderWidth={180}
beforeTotalAmount={<ButtonGroup style={{marginRight:20}} theme={'solid'}> beforeTotalAmount={<ButtonGroup style={{marginRight: 20}} theme={'solid'}>
<Button onClick={onImportExcel}>{t('bill.import_excel')}</Button> {
<Button loading={state.exporting} onClick={onExportExcel}>{t('bill.export_excel')}</Button> (selectKeys.length == 0) ? <Button disabled>{t('bill.confirm_bill_type_batch')}</Button> :
</ButtonGroup>} <Popconfirm
title={'Notice'}
content={`${t('bill.cancel_confirm_bills')}?`}
onConfirm={() => confirmBillTypeBatch()}
>
<Button theme={'solid'}>{t('bill.confirm_bill_type_batch')}</Button>
</Popconfirm>
}
<Button onClick={onImportExcel}>{t('bill.import_excel')}</Button>
<Button loading={state.exporting} onClick={onExportExcel}>{t('bill.export_excel')}</Button>
</ButtonGroup>}
onRowSelection={setSelectedKeys}
onPageChange={(page_number) => { onPageChange={(page_number) => {
setBillQueryParams({ setBillQueryParams({
...queryParams, ...queryParams,
@ -146,19 +165,34 @@ const BillQuery = () => {
}} }}
/> />
<Modal <Modal
title="Confirm Bill Type" title={t('bill.confirm_bill')}
visible={!!state.confirmBill} visible={!!state.confirmBill}
closeOnEsc={true} closeOnEsc={true}
onCancel={() => { onCancel={() => {
refresh() refresh()
setState({confirmBill: undefined}) setState({confirmBill: undefined})
}} }}
width={500} width={550}
footer={null} footer={null}
> >
{state.confirmBill && <> {state.confirmBill && <>
<div><BillDetailItems bill={state.confirmBill}/></div> <div><BillDetailItems bill={state.confirmBill} studentNumberRender={<>
<div className="confirm-container" style={{padding: '15px 0'}}> <BillDetailItem
icon={<IconStudentId/>} title={t('manual.student_number')}
value={state.confirmBill.student_number || '-'}/>
<BillDetailItem
icon={<IconStudentId/>} title={t('base.bill_number')}
value={state.confirmBill.application_number || '-'}/>
</>}/></div>
<div className="confirm-container" style={{padding: '15px 0',marginTop:20}}>
{
!state.confirmBill.student_number_confirm &&
<NumberConfirm bill={state.confirmBill} type={'student_number'}/>
}
{
!state.confirmBill.application_number_confirm &&
<NumberConfirm bill={state.confirmBill} type={'application_number'}/>
}
{ {
state.confirmBill.details.map((it, idx) => (<div key={idx}> state.confirmBill.details.map((it, idx) => (<div key={idx}>
<Divider margin='12px'/> <Divider margin='12px'/>

View File

@ -86,6 +86,20 @@ const BillReconciliation = () => {
<BillList <BillList
source={data} type={'reconciliation'} source={data} type={'reconciliation'}
operationRender={queryParams.apply_status == 'CHECKED' ? undefined : operation} operationRender={queryParams.apply_status == 'CHECKED' ? undefined : operation}
beforeTotalAmount={<div>{queryParams.apply_status != 'CHECKED' && (
(selectKeys.length == 0 )? <Button theme={'solid'} disabled style={{marginRight: 10}}>
{t('bill.confirm_batch')}
</Button> :
<Popconfirm
title={'Notice'}
content={`${t('bill.cancel_confirm_bills')}?`}
onConfirm={() => confirmBill(selectKeys as number[])}
>
<Button theme={'solid'} style={{marginRight: 10}}>
{t('bill.confirm_batch')}
</Button>
</Popconfirm>
)}</div>}
onRowSelection={queryParams.apply_status == 'CHECKED' ? undefined : (keys: (number | string)[]) => { onRowSelection={queryParams.apply_status == 'CHECKED' ? undefined : (keys: (number | string)[]) => {
setSelectedKeys(keys); setSelectedKeys(keys);
}} }}
@ -94,17 +108,6 @@ const BillReconciliation = () => {
...queryParams, ...queryParams,
page_number page_number
})} })}
tableFooter={queryParams.apply_status != 'CHECKED' && selectKeys?.length > 0 && (
<Popconfirm
title={'Notice'}
content={`${t('bill.cancel_confirm_bills')}?`}
onConfirm={() => confirmBill(selectKeys as number[])}
>
<Button style={{marginRight: 10}}>
{t('bill.confirm_batch')}
</Button>
</Popconfirm>
)}
/> />
</div>) </div>)
} }

4
src/types/bill.d.ts vendored
View File

@ -44,7 +44,9 @@ declare type BillQueryParam = {
declare type BillModel = { declare type BillModel = {
id: number; id: number;
student_number: string; student_number: string;
application_number?: null | string | number; student_number_confirm?: string;
application_number: null | string;
application_number_confirm?: null | string;
student_email: string; student_email: string;
student_tc_name?: string; student_tc_name?: string;
student_sc_name?: string; student_sc_name?: string;

View File

@ -28,8 +28,8 @@ export default defineConfig(({mode}) => {
port:10086, port:10086,
proxy: { proxy: {
'/api': { '/api': {
target: 'https://test-payment-be.hkchc.team', // // target: 'https://test-payment-be.hkchc.team', //
// target: 'http://127.0.0.1:50000', // target: 'http://127.0.0.1:50000', //
changeOrigin: true, changeOrigin: true,
//rewrite: (path) => path.replace(/^\/api/, '') //rewrite: (path) => path.replace(/^\/api/, '')
} }