Compare commits

..

No commits in common. "main-new" and "main" have entirely different histories.

16 changed files with 58 additions and 143 deletions

View File

@ -32,13 +32,13 @@ WORKDIR /app
ENV APP_API_URL localhost:50000
# nginx配置文件
#COPY nginx.conf /etc/nginx/conf.d/default.conf.template
COPY nginx.conf /etc/nginx/conf.d/default.conf.template
# 编译文件
COPY --from=builder /app/dist ./
# RUN /bin/sh envsubst /etc/nginx/templates/*.template /etc/nginx/conf.d
#WORKDIR /etc/nginx/conf.d/
#ENTRYPOINT sed -i "s~<!--app_url-->~<script>const APP_SITE_URL='${APP_SITE_URL}';</script>~" /app/index.html && envsubst '$APP_API_URL' < default.conf.template > default.conf && cat default.conf && nginx -g 'daemon off;'
WORKDIR /etc/nginx/conf.d/
ENTRYPOINT sed -i "s~<!--app_url-->~<script>const APP_SITE_URL='${APP_SITE_URL}';</script>~" /app/index.html && envsubst '$APP_API_URL' < default.conf.template > default.conf && cat default.conf && nginx -g 'daemon off;'
# 暴露80端口
EXPOSE 80
# 启动Nginx服务
CMD ["nginx", "-g", "daemon off;"]
# CMD ["nginx", "-g", "daemon off;"]

View File

@ -15,12 +15,13 @@ services:
hkchc-payment-frontend-server:
image: registry.hkchc.team/hkchc-payment-frontend:latest
container_name: hkchc-payment-frontend
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
environment:
APP_API_URL: "10.10.0.152:50000" # payment backend service
working_dir: /etc/nginx/conf.d/
ports:
- "50001:80"
command: [
"sed -i \"s~<!--app_url-->~<script>const APP_SITE_URL='${APP_SITE_URL}';</script>~\" /app/index.html && envsubst '$APP_API_URL' < default.conf.template > default.conf && cat default.conf",
"nginx -g daemon off;"
]
<<: *common

View File

@ -1,12 +1,15 @@
export const AppConfig: {
[key:string]: {
ldapApiUrl: string,
ldapApiKey: string
}
} = {
default:{
ldapApiUrl: 'https://test-api.hkchc.team',
ldapApiKey: 'MPCbsNa6l2RJ7D1Zo6D03qtVF1P93st3'
},
production:{
ldapApiKey: 'NFIgLIzvmL0ENQeeIDJu5Z7MEp5TjhlE'
ldapApiUrl: 'https://test-api.hkchc.team',
ldapApiKey: 'MPCbsNa6l2RJ7D1Zo6D03qtVF1P93st3'
}
}

View File

@ -1,3 +1,7 @@
upstream payment_backend {
server $APP_API_URL;
}
server {
listen 80;
listen [::]:80;
@ -22,7 +26,7 @@ server {
}
location ^~/api {
proxy_pass http://localhost:30000;
proxy_pass http://payment_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -45,6 +49,7 @@ server {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
# proxy_hide_header Upgrade;

View File

@ -1,6 +1,6 @@
import {Button, Checkbox, CheckboxGroup, Space, Table, Tag, Typography} from "@douyinfe/semi-ui";
import {ColumnProps} from "@douyinfe/semi-ui/lib/es/table";
import React, {ReactNode, useEffect, useMemo, useState} from "react";
import React, {ReactNode, useMemo, useState} from "react";
import {useTranslation} from "react-i18next";
import dayjs from "dayjs";
import {IconCheckCircleStroked, IconSetting, IconTickCircle} from "@douyinfe/semi-icons";
@ -20,7 +20,6 @@ type BillListProps = {
rowSelectionDisabled?: (record: BillModel) => boolean;
source?: RecordList<BillModel>;
onPageChange: (pageIndex: number) => void;
onPageSizeChange: (pageSize:number) => void;
tableFooter?: React.ReactNode;
loading?: boolean;
beforeTotalAmount?: React.ReactNode;
@ -41,15 +40,13 @@ export const BillList: React.FC<BillListProps> = (props) => {
const [currentTotalAmount, setCurrentTotalAmount] = useState(0)
const [state, setState] = useSetState<{
showColumnsConfig?: boolean;
showCols: string[];
selectedKeys: string[];
showCols: string[]
}>({
showCols: [
"id", "merchant_ref", "student_number", "application_number", 'confirm_status', "initiated_paid_at", "delivered_at",
"paid_at", "student_english_name", "student_email", "programme_english_name","department_english_name",
"intake_year", "detail", "detail_confirms", "amount", "pay_amount", "actual_payment_amount", "pay_method", "status", "apply_status"
],
selectedKeys:[]
]
})
const billStatusText = (billStatus: string) => {
@ -89,7 +86,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
title: 'Merchant Ref',
dataIndex: 'merchant_ref',
width: 200,
render: (value: string) => (value || 'N/A')
// render: (_) => (<MoneyFormat money={_}/>),
},
{
title: t('base.student_number'),
@ -146,7 +143,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
title: t('bill.title_student_name'),
dataIndex: 'student_english_name',
width: 180,
render: (_, record) => _?(<div>{record.student_english_name}<br/>{record.student_chinese_name}</div>):'N/A'
render: (_, record) => (<div>{record.student_english_name}<br/>{record.student_chinese_name}</div>)
},
{
title: 'Email',
@ -159,14 +156,14 @@ export const BillList: React.FC<BillListProps> = (props) => {
title: t('bill.title_program_name'),
dataIndex: 'programme_english_name',
width: 250,
render: (_, record) => _?(i18n.language == 'en-US' ? record.programme_english_name : record.programme_chinese_name):'N/A',
render: (_, record) => (i18n.language == 'en-US' ? record.programme_english_name : record.programme_chinese_name),
// dataIndex: i18n.language == 'en-US' ? 'programme_english_name' : 'programme_chinese_name',
},
{
title: t('bill.title_department'),
width: 200,
dataIndex: 'department_english_name',
render: (_, record) => _?(i18n.language == 'en-US' ? record.department_english_name : record.department_chinese_name):'N/A',
render: (_, record) => (i18n.language == 'en-US' ? record.department_english_name : record.department_chinese_name),
},
{
title: t('bill.title_year'),
@ -201,7 +198,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
dataIndex: 'detail_confirms',
ellipsis: {showTitle: true},
width: 220,
render: (_, record) => record.detail_confirms?(<div style={{
render: (_, record) => (<div style={{
fontSize: 13,
lineHeight: 1.2,
wordBreak: 'break-all',
@ -210,7 +207,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
}}>
{record.detail_confirms?.map((it) => (
<div key={it.id}>{it.bill_type}: <MoneyFormat money={it.amount}/></div>))}
</div>):'N/A',
</div>),
},
{
title: t('bill.title_amount'),
@ -304,11 +301,6 @@ export const BillList: React.FC<BillListProps> = (props) => {
setCurrentTotalAmount(_total)
return originList;
}, [props.source])
useEffect(()=>{
setState({
selectedKeys:[]
})
},[currentList])
return <Card
title={<Space>
@ -364,10 +356,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
currentPage: props.source?.pagination.current,
pageSize: props.source?.pagination.pageSize,
total: props.source?.pagination.total,
pageSizeOpts:[10,20,50],
showSizeChanger:true,
onPageChange: props.onPageChange,
onPageSizeChange: props.onPageSizeChange,
formatPageText: (params) => (
<div className="bill-list-pagination">
{props.tableFooter}
@ -379,9 +368,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
loading={props.loading}
rowSelection={props.onRowSelection ? {
fixed: true,
selectedRowKeys: state.selectedKeys,
onChange: (selectedRowKeys) => {
setState({selectedKeys: selectedRowKeys as string[]})
selectedRowKeys && props.onRowSelection?.(selectedRowKeys)
},
getCheckboxProps: (record) => {

View File

@ -138,11 +138,11 @@ const SearchForm: React.FC<SearchFormProps> = (props) => {
<Form.Select.Option value="student_number asc">{t('base.student_number')} {t('bill.sort_asc')}</Form.Select.Option>
<Form.Select.Option value="application_number desc">{t('base.bill_number')} {t('bill.sort_desc')}</Form.Select.Option>
<Form.Select.Option value="application_number asc">{t('base.bill_number')} {t('bill.sort_asc')}</Form.Select.Option>
<Form.Select.Option value="initiated_paid_at desc">{t('bill.title_initiated_paid_at')} {t('bill.sort_desc')}</Form.Select.Option>
<Form.Select.Option value="initiated_paid_at asc">{t('bill.title_initiated_paid_at')} {t('bill.sort_asc')}</Form.Select.Option>
<Form.Select.Option value="paid_at desc">{t('bill.title_paid_at')} {t('bill.sort_desc')}</Form.Select.Option>
<Form.Select.Option value="paid_at asc">{t('bill.title_paid_at')} {t('bill.sort_asc')}</Form.Select.Option>
<Form.Select.Option value="delivered_at desc">{t('bill.title_delivered_at')} {t('bill.sort_desc')}</Form.Select.Option>
<Form.Select.Option value="delivered_at asc">{t('bill.title_delivered_at')} {t('bill.sort_asc')}</Form.Select.Option>
<Form.Select.Option value="create_at desc">{t('bill.title_create_at')} {t('bill.sort_desc')}</Form.Select.Option>
<Form.Select.Option value="create_at asc">{t('bill.title_create_at')} {t('bill.sort_asc')}</Form.Select.Option>
</Form.Select>
</Col>
<Col xxl={4} xl={6} md={8}>
@ -153,16 +153,8 @@ const SearchForm: React.FC<SearchFormProps> = (props) => {
<Form.Select.Option value="PPS">PPS</Form.Select.Option>
</Form.Select>
</Col>
<Col xxl={4} xl={6} md={8}>
<Form.Select showClear field="is_delivered" label={t('bill.delivered_status')}
placeholder={t('base.please_select')} style={{width: '100%'}}>
<Form.Select.Option value="false">{t('bill.delivered_status_no')}</Form.Select.Option>
<Form.Select.Option value="true">{t('bill.delivered_status_yes')}</Form.Select.Option>
</Form.Select>
</Col>
<Col xxl={4} xl={6} md={8}>
<Form.Select
showClear
field="confirm_bill_type" style={{width: '100%'}}
label={t('manual.bill_type')}
placeholder={t('manual.bill_type')}

View File

@ -61,6 +61,6 @@ const formatCurrency = (currency = 'HKD') => {
const MoneyFormat: React.FC<MoneyFormatProps> = ({money, currency = 'HKD'}) => {
// 将货币数字转换为千分位格式且带2位小数
return (money || money == 0 || money == '0') ?
<span className={'money-format'}>{formatCurrency(currency)} {formatMoneyNumber(money)}</span> : 'N/A';
<span className={'money-format'}>{formatCurrency(currency)} {formatMoneyNumber(money)}</span> : null;
}
export default MoneyFormat;

View File

@ -17,8 +17,6 @@ export function useBillTypes(){
const types = ret.filter(it=>!it.description.toUpperCase().startsWith('ADJUSTMENT'))
.map(it=>({value: it.type, label: it.description}))
setBillTypes(types)
// 避免出现多次
BillTypesCache.length = 0;
BillTypesCache.push(...types)
})
}

View File

@ -35,15 +35,11 @@
"confirm_bill_type": "Confirm Bill",
"confirm_bill_type_batch": "Batch confirm Bill Type",
"confirm_bill_warning_amount": "The bill amount and actual payment amount are inconsistent",
"confirm_bill_warning_amount_id": "The bill id({{id}}) confirmed amount and actual payment amount are inconsistent",
"confirm_confirm_title": "Confirm check and sync the Bill?",
"confirm_select_empty": "Require confirm bill data",
"confirm_student_number": "Confirm Student Number",
"confirm_success": "Confirm success!",
"confirmed": "Confirmed",
"delivered_status": "Delivered Status",
"delivered_status_no": "Undivided",
"delivered_status_yes": "Delivered",
"download-qr-code": "Download QR Code",
"download_receipt": "Download receipt",
"export_excel": "Export Transaction Excel",

View File

@ -35,15 +35,11 @@
"confirm_bill_type": "确认账单",
"confirm_bill_type_batch": "批量确认账单",
"confirm_bill_warning_amount": "账单金额和实付金额不一致",
"confirm_bill_warning_amount_id": "账单ID{{id}})确认金额和实付金额不一致",
"confirm_confirm_title": "请确定对账并同步此账单?",
"confirm_select_empty": "对账账单为空",
"confirm_student_number": "确认学号",
"confirm_success": "对账成功!",
"confirmed": "已对账",
"delivered_status": "分账状态",
"delivered_status_no": "未分账",
"delivered_status_yes": "已分账",
"download-qr-code": "下载二维码",
"download_receipt": "下载收据",
"export_excel": "导出交易记录",

View File

@ -34,16 +34,12 @@
"confirm_bill_number": "確認帳單編號",
"confirm_bill_type": "確認賬單",
"confirm_bill_type_batch": "批次確認帳單",
"confirm_bill_warning_amount": "帳單金額和實付金額不一致",
"confirm_bill_warning_amount_id": "帳單ID{{id}})確認金額和實付金額不一致",
"confirm_bill_warning_amount": "账单金额和实付金额不一致",
"confirm_confirm_title": "請確定對帳并同步此帳單?",
"confirm_select_empty": "對帳帳單為空",
"confirm_student_number": "確認學號",
"confirm_success": "對帳成功!",
"confirmed": "已對帳",
"delivered_status": "分帳狀態",
"delivered_status_no": "未分帳",
"delivered_status_yes": "已分賬",
"download-qr-code": "下載二維碼",
"download_receipt": "下載收據",
"export_excel": "導出交易记录",

View File

@ -1,4 +1,4 @@
import {Button, Select, Space, Divider, InputNumber, Modal} from "@douyinfe/semi-ui";
import {Button, Select, Space, Divider, InputNumber, Modal,} from "@douyinfe/semi-ui";
import React, {useEffect} from "react";
import {useSetState} from "ahooks";
import MoneyFormat from "@/components/money-format.tsx";
@ -103,7 +103,7 @@ export const BillTypeConfirmModal: React.FC<BillTypeConfirmProps> = (props) => {
const onBillConfirm = () => {
// 判断confirm的总金额是否和实付金额相等
const total = state.detail_confirms.reduce((total, item) => {
return total + Number(item.amount)
return total + item.amount
}, 0)
if(total != props.bill.actual_payment_amount){
Modal.warning({
@ -121,11 +121,6 @@ export const BillTypeConfirmModal: React.FC<BillTypeConfirmProps> = (props) => {
...state
}]).then(() => {
props.onClose?.(true)
}).catch(e=>{
Modal.error({
title: 'Error',
content: `Confirmed Fail: ${e.message}`
})
}).finally(() => {
setState({loading: false})
})

View File

@ -1,5 +1,5 @@
import React from "react";
import {Button, Modal, Popconfirm} from "@douyinfe/semi-ui";
import {Button, Popconfirm} from "@douyinfe/semi-ui";
import {useTranslation} from "react-i18next";
import {confirmBillType} from "@/service/api/bill.ts";
import {useSetState} from "ahooks";
@ -21,24 +21,7 @@ export const BillTypeConfirmBatch: React.FC<BillTypeConfirmBatchProps> = (props)
}
const confirmBillTypeBatch = () => {
const bills: BillConfirmParams[] = [];
const arr = props.data?.list.filter(item => props.selectKeys.includes(item.id));
if(!arr) return;
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
if(item.confirm_status != 'UNCONFIRMED'){
continue;
}
// 判断confirm的总金额是否和实付金额相等
const total = item.details.reduce((total, item) => {
return total + Number(item.amount)
}, 0)
if(total != item.actual_payment_amount){
Modal.warning({
title: 'Warning',
content: t('bill.confirm_bill_warning_amount_id',{id: item.id})
})
return;
}
props.data?.list.filter(item => props.selectKeys.includes(item.id)).forEach(item => {
bills.push({
id: item.id,
confirm_application_number: String(item.application_number),
@ -51,7 +34,7 @@ export const BillTypeConfirmBatch: React.FC<BillTypeConfirmBatchProps> = (props)
}
})
})
}
})
if (bills.length == 0) return;
confirm(bills)
}

View File

@ -14,7 +14,6 @@ import {BillTypeConfirmModal} from "@/pages/bill/components/bill_type_confirm.ts
import {saveAs} from "file-saver";
// import {AddBillModal} from "@/pages/bill/components/add_bill_modal.tsx";
import {BillTypeConfirmBatch} from "@/pages/bill/components/bill_type_confirm_batch.tsx";
import {AddBillModal} from "@/pages/bill/components/add_bill_modal.tsx";
const DownloadButton = ({bill, text}: { bill: BillModel; text: string }) => {
@ -42,9 +41,6 @@ const BillQuery = () => {
});
const {data, loading, refresh} = useRequest(() => billList(queryParams), {
refreshDeps: [queryParams],
onSuccess:()=>{
document.documentElement.scrollTo({top:0});
},
onError: (e) => {
Notification.error({title: 'Error', content: e.message})
}
@ -129,7 +125,7 @@ const BillQuery = () => {
operationRender={operation} operationRenderWidth={180}
beforeTotalAmount={<Space>
<BillTypeConfirmBatch data={data} selectKeys={selectKeys} onConfirm={refresh}/>
<AddBillModal onConfirm={refresh}/>
{/*<AddBillModal onConfirm={refresh}/>*/}
<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>
@ -144,12 +140,6 @@ const BillQuery = () => {
page_number
})
}}
onPageSizeChange={(page_size) => {
setBillQueryParams({
...queryParams,
page_size
})
}}
/>
<Modal
title="Bill Detail"

View File

@ -1,4 +1,4 @@
import {Button, Space, TabPane, Tabs, Popconfirm, Toast, Modal} from "@douyinfe/semi-ui";
import {Button, Space, TabPane, Tabs, Notification, Popconfirm, Toast} from "@douyinfe/semi-ui";
import {useRequest, useSetState} from "ahooks";
import {useTranslation} from "react-i18next";
import {useState} from "react";
@ -6,23 +6,22 @@ import {useState} from "react";
import SearchForm from "@/components/bill/search-form.tsx";
import {BillList} from "@/components/bill/list.tsx";
import {billList, BillQueryParams, confirmBills} from "@/service/api/bill.ts";
import useAuth from "@/hooks/useAuth.ts";
import {BizError} from "@/service/types.ts";
const BillReconciliation = () => {
const {t} = useTranslation()
const {user} = useAuth();
const [queryParams, setBillQueryParams] = useState<BillQueryParams>({
apply_status: 'UNCHECKED'
});
const {data, loading, refresh} = useRequest(() => billList({
...queryParams,
status: 'PAID',
confirm_status: 'CONFIRMED'
confirm_status: 'CONFIRMED',
department: user?.department == 'RO' ? 'RO' : 'FO',
}), {
refreshDeps: [queryParams],
onSuccess: () => {
document.documentElement.scrollTo({top:0});
setState({checkingId: -1})
},
onError: (e: Error) => {
Toast.error({
content: `${t('base.query_bill')}:${e.message}`,
@ -37,31 +36,12 @@ const BillReconciliation = () => {
})
const confirmBill = (records: number[]) => {
if (records.length == 0) {
Toast.error({content: t('bill.confirm_select_empty')})
Notification.error({title: 'Notice', content: t('bill.confirm_select_empty')})
return
}
const arr = data?.list.filter(item => records.includes(item.id));
if (!arr) return;
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
// 判断confirm的总金额是否和实付金额相等
const total = item.detail_confirms ? item.detail_confirms.reduce((total, item) => {
return total + Number(item.amount)
}, 0) : 0;
if (total != item.actual_payment_amount) {
Modal.warning({
title: 'Warning',
content: t('bill.confirm_bill_warning_amount_id', {id: item.id})
})
return;
}
}
setState({checkingId: records.length > 1 ? 0 : Number(records[0])})
setState({checkingId: records.length > 1 ? 0 : records[0]})
confirmBills(records).then(() => {
Toast.success({content: t('bill.confirm_success')})
Notification.success({title: 'Notice', content: t('bill.confirm_success')})
refresh()
}).catch((e: BizError) => {
Toast.error({
@ -80,7 +60,6 @@ const BillReconciliation = () => {
onConfirm={() => confirmBill([_record.id])}
okText={t('base.confirm')}
cancelText={t('base.cancel')}
disabled={state.checkingId == _record.id}
>
<Button
loading={state.checkingId == _record.id} size={'small'} theme={'solid'}
@ -110,18 +89,18 @@ const BillReconciliation = () => {
source={data} type={'reconciliation'}
operationRender={queryParams.apply_status == 'CHECKED' ? undefined : operation}
beforeTotalAmount={<div>{queryParams.apply_status != 'CHECKED' && (
(selectKeys.length == 0) ? <Button theme={'solid'} disabled style={{marginRight: 10}}>
(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
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>
</Button>
</Popconfirm>
)}</div>}
onRowSelection={queryParams.apply_status == 'CHECKED' ? undefined : (keys: (number | string)[]) => {
setSelectedKeys(keys);
@ -131,12 +110,6 @@ const BillReconciliation = () => {
...queryParams,
page_number
})}
onPageSizeChange={(page_size) => {
setBillQueryParams({
...queryParams,
page_size
})
}}
/>
</div>)
}

View File

@ -74,7 +74,7 @@ export function confirmBillType(bills:BillConfirmParams[]) {
return post<BillModel>(`/bills/confirm`, {bills})
}
export function confirmBills(bill_ids: number[] | string[]) {
export function confirmBills(bill_ids: number[]) {
return post(`/bills/apply`, {bill_ids})
}