完成发票管理

This commit is contained in:
WenC 2024-04-07 17:37:09 +08:00
parent 3e56632e34
commit 8ec42c9d82
12 changed files with 1350 additions and 66 deletions

View File

@ -0,0 +1,354 @@
import {Button, Divider, Flex, Form, Input, Modal, Radio, Space} from "antd";
import {useEffect, useState} from "react";
import {listDictionary, IDictionary, get, put} from "../../util/AjaxUtils.ts";
import useMessage from "antd/es/message/useMessage";
import InvoiceInfoList from "./info/InvoiceInfoList.tsx";
import InvoiceOrderList from "./order/InvoiceOrderList.tsx";
import useModal from "antd/es/modal/useModal";
type FormFieldType = {
invoiceTitle: string;
invoiceNo: string;
invoiceAddress: string;
invoicePhone: string;
invoiceAccount: string;
invoiceBank: string;
content: string;
rate: string;
type: string;
orderIds: string[];
invoiceNote: string;
isSaveInvoiceInfo: boolean;
invoiceAmount: number;
}
type EditProps = {
invoiceId: string;
handleOk: () => void;
handleCancel: () => void;
}
export default function InvoiceEdit(props: EditProps) {
const [messageApi, messageContext] = useMessage();
const [modal, modalContext] = useModal();
const [form] = Form.useForm<FormFieldType>();
const [contentArray, setContentArray] = useState<IDictionary[]>([]);
const [rateArray, setRateArray] = useState<IDictionary[]>([]);
const [typeArray, setTypeArray] = useState<IDictionary[]>([]);
const [isInvoiceInfoListOpen, setIsInvoiceInfoListOpen] = useState(false);
const [isInvoiceOrderListOpen, setIsInvoiceOrderListOpen] = useState(false);
const [orderAmount, setOrderAmount] = useState('0');
useEffect(() => {
if(!props.invoiceId) {
return;
}
// 开票内容
listDictionary('e0251d55-cd52-4f57-be92-b2bef8a6dd62', messageApi).then((data) => {
setContentArray([...data]);
})
// 开票税率
listDictionary('b67d5208-db1d-4d0e-99de-cc22d9d50041', messageApi).then((data) => {
setRateArray([...data]);
})
// 开票类型
listDictionary('e4808c45-64ee-42fa-a413-a470fbdc0aea', messageApi).then((data) => {
setTypeArray([...data]);
})
get<FormFieldType>({
messageApi,
url: `/api/invoice/get/${props.invoiceId}`,
onSuccess({data}) {
form.setFieldsValue(data);
setOrderAmount((data.invoiceAmount / 100).toFixed(2));
}
})
}, []);
return (
<>
{messageContext}
{modalContext}
<Form
name="basic"
initialValues={{isSaveInvoiceInfo: 0}}
form={form}
onFinish={(values) => {
modal.confirm({
title: '提示',
content: '确定提交吗?',
okText: '确定',
cancelText: '取消',
okButtonProps: {
style: {
backgroundColor: 'val(--color-primary)'
}
},
onOk: () => {
put<any>({
messageApi,
url: `/api/invoice/update/${props.invoiceId}`,
body: values,
onSuccess() {
messageApi.success('提交成功');
props.handleOk();
}
})
}
});
}}
>
<Divider orientation="left" plain></Divider>
<table className="pay-table">
<colgroup>
<col width="120"/>
<col/>
</colgroup>
<tbody>
<tr>
<td colSpan={2}>
<Button type="link" onClick={() => {
setIsInvoiceInfoListOpen(true);
}}></Button>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="invoiceTitle"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入公司名称'}]}
>
<Input placeholder="请输入公司名称"/>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="invoiceNo"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入纳税人识别号'}]}
>
<Input placeholder="请输入纳税人识别号"/>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="invoiceAddress"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入公司地址'}]}
>
<Input placeholder="请输入公司地址"/>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="invoicePhone"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入联系电话'}]}
>
<Input placeholder="请输入联系电话"/>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="invoiceAccount"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入开户行'}]}
>
<Input placeholder="请输入开户行"/>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="invoiceBank"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入开户行账号'}]}
>
<Input placeholder="请输入开户行账号"/>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="content"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请选择开票内容'}]}
>
<Radio.Group>
<Space direction="vertical">
{contentArray.map(item => <Radio value={item.dataName}
key={item.dataId}>{item.dataName}</Radio>)}
</Space>
</Radio.Group>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="rate"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请选择开票税率'}]}
>
<Radio.Group>
{rateArray.map(item => <Radio value={item.dataName}
key={item.dataId}>{item.dataName}</Radio>)}
</Radio.Group>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="type"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请选择发票类型'}]}
>
<Radio.Group>
<Space direction="vertical">
{typeArray.map(item => <Radio value={item.dataName}
key={item.dataId}>{item.dataName}</Radio>)}
</Space>
</Radio.Group>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="isSaveInvoiceInfo"
style={{marginBottom: '0'}}
>
<Radio.Group>
<Radio value={0}></Radio>
<Radio value={1}></Radio>
</Radio.Group>
</Form.Item>
</td>
</tr>
</tbody>
</table>
<Divider orientation="left" plain></Divider>
<table className="pay-table">
<colgroup>
<col width="120"/>
<col/>
</colgroup>
<tbody>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="orderIds"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请选择开票金额'}]}
>
<div>
<span></span>
<span style={{fontSize: '20px', fontWeight: 'bold'}}>{orderAmount}</span>
<Button type="link" size="small" onClick={() => {
setIsInvoiceOrderListOpen(true);
}}></Button>
</div>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"></td>
<td>
<Form.Item
name="invoiceNote"
style={{marginBottom: '0'}}
>
<Input.TextArea placeholder="请输入开票备注" rows={4}/>
</Form.Item>
</td>
</tr>
</tbody>
</table>
<Form.Item style={{marginBottom: '0'}}>
<Flex justify="center" style={{marginTop: '15px'}}>
<Space>
<Button type="primary" htmlType="submit" style={{
backgroundColor: 'var(--color-primary)'
}}></Button>
<Button type="default" onClick={() => {
props.handleCancel();
}}></Button>
</Space>
</Flex>
</Form.Item>
</Form>
<Modal open={isInvoiceInfoListOpen}
title="开票信息"
width={1000}
footer={false}
onCancel={() => setIsInvoiceInfoListOpen(false)}
>
<InvoiceInfoList
handleOk={(selectedInvoice) => {
form.setFieldsValue({
invoiceTitle: selectedInvoice.invoiceTitle,
invoiceNo: selectedInvoice.invoiceNo,
invoiceAddress: selectedInvoice.invoiceAddress,
invoicePhone: selectedInvoice.invoicePhone,
invoiceAccount: selectedInvoice.invoiceAccount,
invoiceBank: selectedInvoice.invoiceBank
});
setIsInvoiceInfoListOpen(false);
}}
handleCancel={() => {
setIsInvoiceInfoListOpen(false);
}}
/>
</Modal>
<Modal open={isInvoiceOrderListOpen}
title="开票订单"
width={1000}
footer={false}
onCancel={() => setIsInvoiceOrderListOpen(false)}
>
<InvoiceOrderList
selectedOrderIds={form.getFieldValue('orderIds')}
handleOk={(selectedOrders) => {
let totalAmount = 0;
const orderIds: string[] = [];
selectedOrders.forEach((item) => {
totalAmount += item.totalAmount;
orderIds.push(item.orderId);
});
setOrderAmount((totalAmount / 100).toFixed(2));
setIsInvoiceOrderListOpen(false);
form.setFieldValue('orderIds', orderIds);
}}
handleCancel={() => {
setIsInvoiceOrderListOpen(false);
}}
/>
</Modal>
</>
);
}

View File

@ -1,10 +1,12 @@
import {Button, Dropdown, MenuProps, Modal, Table, TableProps, Tag} from "antd";
import {Button, Dropdown, Flex, MenuProps, Modal, Popconfirm, Space, Table, TableProps, Tag} from "antd";
import {PlusOutlined} from "@ant-design/icons";
import {useEffect, useState} from "react";
import {get} from "../../util/AjaxUtils.ts";
import {useEffect, useRef, useState} from "react";
import {get, put} from "../../util/AjaxUtils.ts";
import {IListPage} from "../../interfaces/listpage/IListPage.ts";
import useMessage from "antd/es/message/useMessage";
import InvoiceSave from "./InvoiceSave.tsx";
import InvoiceEdit from "./InvoiceEdit.tsx";
import InvoiceShow from "./InvoiceShow.tsx";
enum InvoiceStatusEnum {
PENDING = 'PENDING',
@ -39,6 +41,10 @@ export default function InvoiceList() {
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [dataArray, setDataArray] = useState<DataType[]>([]);
const [isSaveModalOpen, setIsSaveModalOpen] = useState(false);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [isShowModalOpen, setIsShowModalOpen] = useState(false);
const invoiceId = useRef('');
const columns: TableProps<DataType>['columns'] = [
{
@ -125,7 +131,55 @@ export default function InvoiceList() {
align: 'center',
width: 120,
fixed: 'right',
render: (value, record) => {
render: (_value, record) => {
if(record.invoiceStatus === InvoiceStatusEnum.PENDING) {
return <Flex justify="center">
<Space>
<Button type="link" style={{
color: 'var(--color-dark)',
padding: '0'
}} onClick={() => {
invoiceId.current = record.invoiceId;
setIsShowModalOpen(true);
}}></Button>
<Popconfirm
title={false}
placement="right"
description="确定取消吗?"
okText="确定"
cancelText="取消"
okButtonProps={{
style: {
backgroundColor: 'var(--color-primary)'
}
}}
onConfirm={() => {
put<any>({
messageApi,
url: `/api/invoice/update-cancel/${record.invoiceId}`,
onSuccess() {
messageApi.success('取消成功');
getData();
}
})
}}
>
<Button type="link" danger style={{
padding: '0'
}}></Button>
</Popconfirm>
</Space>
</Flex>
}
if(record.invoiceStatus === InvoiceStatusEnum.CANCEL) {
return <Button type="link" style={{
padding: '0'
}} onClick={() => {
invoiceId.current = record.invoiceId;
setIsEditModalOpen(true);
}}></Button>
}
if(record.invoiceStatus == InvoiceStatusEnum.COMPLETE) {
const items: MenuProps['items'] = [];
record.invoiceFileList.forEach((item, index) => {
@ -146,7 +200,7 @@ export default function InvoiceList() {
},
]
useEffect(() => {
const getData = () => {
get<IListPage<DataType>>({
messageApi,
url: '/api/invoice/listpage/self',
@ -162,6 +216,10 @@ export default function InvoiceList() {
setDataArray(data.rows);
}
})
}
useEffect(() => {
getData();
}, [page]);
return (
@ -170,24 +228,59 @@ export default function InvoiceList() {
<div className="mod-list">
<div className="table-btn-group" style={{marginBottom: '15px'}}>
<Button value="small" onClick={() => {
setIsSaveModalOpen(true);
}}><PlusOutlined/> </Button>
</div>
<Table columns={columns} dataSource={dataArray} pagination={
{
pageSize: 20,
total: total,
onChange: (currentPage) => {
setPage(currentPage);
}
setPage(currentPage);
},
}
} scroll={{y: 500}} bordered key="dataTable" rowKey="invoiceId"/>
</div>
</div>
<Modal open={true}
<Modal open={isSaveModalOpen}
title="申请开票"
footer={false}
onCancel={() => setIsSaveModalOpen(false)}
>
<InvoiceSave />
<InvoiceSave
handleOk={() => {
getData();
setIsSaveModalOpen(false);
}}
handleCancel={() => {
setIsSaveModalOpen(false);
}}
/>
</Modal>
<Modal open={isEditModalOpen}
title="修改开票信息"
footer={false}
onCancel={() => setIsEditModalOpen(false)}
>
<InvoiceEdit
invoiceId={invoiceId.current}
handleOk={() => {
getData();
setIsEditModalOpen(false);
}}
handleCancel={() => {
setIsEditModalOpen(false);
}}
/>
</Modal>
<Modal open={isShowModalOpen}
title="查看开票信息"
footer={false}
onCancel={() => setIsShowModalOpen(false)}
>
<InvoiceShow
invoiceId={invoiceId.current}
/>
</Modal>
{messageContext}
</>

View File

@ -1,12 +1,14 @@
import {Button, Divider, Flex, Form, Input, Modal, Radio, Space} from "antd";
import {useEffect, useState} from "react";
import {listDictionary, IDictionary} from "../../util/AjaxUtils.ts";
import {listDictionary, IDictionary, post} from "../../util/AjaxUtils.ts";
import useMessage from "antd/es/message/useMessage";
import InvoiceInfoList from "./info/InvoiceInfoList.tsx";
import InvoiceOrderList from "./order/InvoiceOrderList.tsx";
import useModal from "antd/es/modal/useModal";
type FormFieldType = {
invoiceTitle: string;
invoiceNo: number;
invoiceNo: string;
invoiceAddress: string;
invoicePhone: string;
invoiceAccount: string;
@ -14,28 +16,38 @@ type FormFieldType = {
content: string;
rate: string;
type: string;
orderIds: string[];
invoiceNote: string;
isSaveInvoiceInfo: boolean;
}
export default function InvoiceSave() {
const [messageApi, messageContext] = useMessage();
const [form] = Form.useForm<FormFieldType>();
const [isSaveInvoiceInfo, setIsSaveInvoiceInfo] = useState(0);
type SaveProps = {
handleOk: () => void;
handleCancel: () => void;
}
export default function InvoiceSave(props: SaveProps) {
const [messageApi, messageContext] = useMessage();
const [modal, modalContext] = useModal();
const [form] = Form.useForm<FormFieldType>();
const [contentArray, setContentArray] = useState<IDictionary[]>([]);
const [rateArray, setRateArray] = useState<IDictionary[]>([]);
const [typeArray, setTypeArray] = useState<IDictionary[]>([]);
const [isInvoiceInfoListOpen, setIsInvoiceInfoListOpen] = useState(false);
const [isInvoiceOrderListOpen, setIsInvoiceOrderListOpen] = useState(false);
const [orderAmount, setOrderAmount] = useState('0');
useEffect(() => {
// 开票内容
listDictionary('e0251d55-cd52-4f57-be92-b2bef8a6dd62', messageApi).then((data)=>{
listDictionary('e0251d55-cd52-4f57-be92-b2bef8a6dd62', messageApi).then((data) => {
setContentArray([...data]);
})
// 开票税率
listDictionary('b67d5208-db1d-4d0e-99de-cc22d9d50041', messageApi).then((data)=>{
listDictionary('b67d5208-db1d-4d0e-99de-cc22d9d50041', messageApi).then((data) => {
setRateArray([...data]);
})
// 开票类型
listDictionary('e4808c45-64ee-42fa-a413-a470fbdc0aea', messageApi).then((data)=>{
listDictionary('e4808c45-64ee-42fa-a413-a470fbdc0aea', messageApi).then((data) => {
setTypeArray([...data]);
})
}, []);
@ -43,10 +55,35 @@ export default function InvoiceSave() {
return (
<>
{messageContext}
{modalContext}
<Form
name="basic"
initialValues={{remember: true}}
initialValues={{isSaveInvoiceInfo: 0}}
form={form}
onFinish={(values) => {
modal.confirm({
title: '提示',
content: '确定提交吗?',
okText: '确定',
cancelText: '取消',
okButtonProps: {
style: {
backgroundColor: 'val(--color-primary)'
}
},
onOk: () => {
post<any>({
messageApi,
url: '/api/invoice/save',
body: values,
onSuccess() {
messageApi.success('提交成功');
props.handleOk();
}
})
}
});
}}
>
<Divider orientation="left" plain></Divider>
<table className="pay-table">
@ -55,6 +92,13 @@ export default function InvoiceSave() {
<col/>
</colgroup>
<tbody>
<tr>
<td colSpan={2}>
<Button type="link" onClick={() => {
setIsInvoiceInfoListOpen(true);
}}></Button>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
@ -137,7 +181,8 @@ export default function InvoiceSave() {
>
<Radio.Group>
<Space direction="vertical">
{contentArray.map(item => <Radio value={item.dataName}>{item.dataName}</Radio>)}
{contentArray.map(item => <Radio value={item.dataName}
key={item.dataId}>{item.dataName}</Radio>)}
</Space>
</Radio.Group>
</Form.Item>
@ -152,7 +197,8 @@ export default function InvoiceSave() {
rules={[{required: true, message: '请选择开票税率'}]}
>
<Radio.Group>
{rateArray.map(item => <Radio value={item.dataName}>{item.dataName}</Radio>)}
{rateArray.map(item => <Radio value={item.dataName}
key={item.dataId}>{item.dataName}</Radio>)}
</Radio.Group>
</Form.Item>
</td>
@ -167,22 +213,27 @@ export default function InvoiceSave() {
>
<Radio.Group>
<Space direction="vertical">
{typeArray.map(item => <Radio value={item.dataName}>{item.dataName}</Radio>)}
{typeArray.map(item => <Radio value={item.dataName}
key={item.dataId}>{item.dataName}</Radio>)}
</Space>
</Radio.Group>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Radio.Group defaultValue={isSaveInvoiceInfo} onChange={(e) => {
setIsSaveInvoiceInfo(e.target.value)
}}>
<Radio value={0}></Radio>
<Radio value={1}></Radio>
</Radio.Group>
<Form.Item
name="isSaveInvoiceInfo"
style={{marginBottom: '0'}}
>
<Radio.Group>
<Radio value={0}></Radio>
<Radio value={1}></Radio>
</Radio.Group>
</Form.Item>
</td>
</tr>
</tbody>
@ -197,22 +248,27 @@ export default function InvoiceSave() {
<tr>
<td className="table-label"> *</td>
<td>
<div>
<span></span>
<span style={{fontSize: '20px', fontWeight: 'bold'}}>300</span>
<Button type="link" size="small" onClick={() => {
}}></Button>
</div>
<Form.Item
name="orderIds"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请选择开票金额'}]}
>
<div>
<span></span>
<span style={{fontSize: '20px', fontWeight: 'bold'}}>{orderAmount}</span>
<Button type="link" size="small" onClick={() => {
setIsInvoiceOrderListOpen(true);
}}></Button>
</div>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"></td>
<td>
<Form.Item
name="invoiceBank"
name="invoiceNote"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入开票备注'}]}
>
<Input.TextArea placeholder="请输入开票备注" rows={4}/>
</Form.Item>
@ -225,20 +281,61 @@ export default function InvoiceSave() {
<Space>
<Button type="primary" htmlType="submit" style={{
backgroundColor: 'var(--color-primary)'
}} onClick={() => {
}}></Button>
<Button type="default"></Button>
<Button type="default" onClick={() => {
props.handleCancel();
}}></Button>
</Space>
</Flex>
</Form.Item>
</Form>
<Modal open={true}
<Modal open={isInvoiceInfoListOpen}
title="开票信息"
width={1000}
footer={false}
onCancel={() => {
setIsInvoiceInfoListOpen(false);
}}
>
<InvoiceInfoList/>
<InvoiceInfoList
handleOk={(selectedInvoice) => {
form.setFieldsValue({
invoiceTitle: selectedInvoice.invoiceTitle,
invoiceNo: selectedInvoice.invoiceNo,
invoiceAddress: selectedInvoice.invoiceAddress,
invoicePhone: selectedInvoice.invoicePhone,
invoiceAccount: selectedInvoice.invoiceAccount,
invoiceBank: selectedInvoice.invoiceBank
});
setIsInvoiceInfoListOpen(false);
}}
handleCancel={() => {
setIsInvoiceInfoListOpen(false);
}}
/>
</Modal>
<Modal open={isInvoiceOrderListOpen}
title="开票订单"
width={1000}
footer={false}
onCancel={() => setIsInvoiceOrderListOpen(false)}
>
<InvoiceOrderList
handleOk={(selectedOrders) => {
let totalAmount = 0;
const orderIds: string[] = [];
selectedOrders.forEach((item) => {
totalAmount += item.totalAmount;
orderIds.push(item.orderId);
});
setOrderAmount((totalAmount / 100).toFixed(2));
setIsInvoiceOrderListOpen(false);
form.setFieldValue('orderIds', orderIds);
}}
handleCancel={() => {
setIsInvoiceOrderListOpen(false);
}}
/>
</Modal>
</>
);

View File

@ -0,0 +1,132 @@
import {Button, Divider, Modal} from "antd";
import {useEffect, useState} from "react";
import {get} from "../../util/AjaxUtils.ts";
import useMessage from "antd/es/message/useMessage";
import InvoiceInfoSelectedList from "./order/InvoiceOrderSelectedList.tsx"
type DataType = {
invoiceTitle: string;
invoiceNo: string;
invoiceAddress: string;
invoicePhone: string;
invoiceAccount: string;
invoiceBank: string;
content: string;
rate: string;
type: string;
orderIds: string[];
invoiceNote: string;
isSaveInvoiceInfo: boolean;
invoiceAmount: number;
}
type ShowProps = {
invoiceId: string;
}
export default function InvoiceShow(props: ShowProps) {
const [messageApi, messageContext] = useMessage();
const [isInvoiceOrderListOpen, setIsInvoiceOrderListOpen] = useState(false);
const [invoiceData, setInvoiceData] = useState<DataType | null>(null);
const [orderAmount, setOrderAmount] = useState('0');
useEffect(() => {
if (!props.invoiceId) {
return;
}
get<DataType>({
messageApi,
url: `/api/invoice/get/${props.invoiceId}`,
onSuccess({data}) {
setInvoiceData({
...data
});
setOrderAmount((data.invoiceAmount / 100).toFixed(2));
}
})
}, []);
return (
<>
{messageContext}
<Divider orientation="left" plain></Divider>
<table className="pay-table">
<colgroup>
<col width="120"/>
<col/>
</colgroup>
<tbody>
<tr>
<td className="table-label"> *</td>
<td>{invoiceData?.invoiceTitle}</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>{invoiceData?.invoiceNo}</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>{invoiceData?.invoiceAddress}</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>{invoiceData?.invoicePhone}</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>{invoiceData?.invoiceAccount}</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>{invoiceData?.invoiceBank}</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>{invoiceData?.content}</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>{invoiceData?.rate}</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>{invoiceData?.type}</td>
</tr>
</tbody>
</table>
<Divider orientation="left" plain></Divider>
<table className="pay-table">
<colgroup>
<col width="120"/>
<col/>
</colgroup>
<tbody>
<tr>
<td className="table-label"> *</td>
<td>
<div>
<span></span>
<span style={{fontSize: '20px', fontWeight: 'bold'}}>{orderAmount}</span>
<Button type="link" size="small" onClick={() => {
setIsInvoiceOrderListOpen(true);
}}></Button>
</div>
</td>
</tr>
<tr>
<td className="table-label"></td>
<td>{invoiceData?.invoiceNote}</td>
</tr>
</tbody>
</table>
<Modal open={isInvoiceOrderListOpen}
title="开票订单"
width={1000}
footer={false}
onCancel={() => setIsInvoiceOrderListOpen(false)}
>
<InvoiceInfoSelectedList invoiceId={props.invoiceId}/>
</Modal>
</>
);
}

View File

@ -0,0 +1,128 @@
import {Button, Flex, Form, Input, Space} from "antd";
import {useForm} from "antd/es/form/Form";
import useModal from "antd/es/modal/useModal";
import {get, post} from "../../../util/AjaxUtils.ts";
import useMessage from "antd/es/message/useMessage";
import {useEffect} from "react";
type FormDataType = {
invoiceTitle: string;
invoicePhone: string;
invoiceNo: string;
invoiceBank: string;
invoiceAddress: string;
invoiceAccount: string;
}
interface EditProps {
invoiceInfoId: string;
handleOk: () => void;
handleCancel: () => void;
}
export default function InvoiceInfoSave(props: EditProps) {
const [messageApi, messageContext] = useMessage();
const [modal, modalContext] = useModal();
const [form] = useForm<FormDataType>()
useEffect(() => {
if (!props.invoiceInfoId) {
return;
}
get<FormDataType>({
messageApi,
url: `/api/invoice-info/get/${props.invoiceInfoId}`,
onSuccess({data}) {
form.setFieldsValue(data);
}
})
}, [props.invoiceInfoId]);
return (
<>
<Form
name="basic"
layout="vertical"
form={form}
onFinish={(values) => {
modal.confirm({
title: '提示',
content: '确定保存吗',
okText: '确定',
cancelText: '取消',
okButtonProps: {
style: {
backgroundColor: 'var(--color-primary)'
}
},
onOk: () => {
post<any>({
messageApi,
url: `/api/invoice-info/update/${props.invoiceInfoId}`,
body: values,
onSuccess() {
messageApi.success('保存成功');
props.handleOk();
}
})
}
})
}}
>
<Form.Item
label="公司名称"
name="invoiceTitle"
rules={[{required: true, message: '请输入公司名称'}]}
>
<Input placeholder="请输入公司名称"/>
</Form.Item>
<Form.Item
label="纳税人识别号"
name="invoiceNo"
rules={[{required: true, message: '请输入纳税人识别号'}]}
>
<Input placeholder="请输入纳税人识别号"/>
</Form.Item>
<Form.Item
label="公司地址"
name="invoiceAddress"
rules={[{required: true, message: '请输入公司地址'}]}
>
<Input placeholder="请输入公司地址"/>
</Form.Item>
<Form.Item
label="公司电话"
name="invoicePhone"
rules={[{required: true, message: '请输入公司电话'}]}
>
<Input placeholder="请输入公司电话"/>
</Form.Item>
<Form.Item
label="开户行"
name="invoiceBank"
rules={[{required: true, message: '请输入开户行'}]}
>
<Input placeholder="请输入开户行"/>
</Form.Item>
<Form.Item
label="开户行账号"
name="invoiceAccount"
rules={[{required: true, message: '请输入开户行账号'}]}
>
<Input placeholder="请输入开户行账号"/>
</Form.Item>
<Flex justify="center">
<Space size={5}>
<Button type="primary" htmlType="submit"
style={{backgroundColor: 'var(--color-primary)'}}></Button>
<Button type="default" onClick={() => {
props.handleCancel();
}}></Button>
</Space>
</Flex>
</Form>
{messageContext}
{modalContext}
</>
)
}

View File

@ -1,10 +1,11 @@
import {Button, Modal, Space, Table, TableProps} from "antd";
import {Button, Flex, Modal, Popconfirm, Space, Table, TableProps} from "antd";
import {DeleteOutlined, EditOutlined, PlusOutlined} from "@ant-design/icons";
import useMessage from "antd/es/message/useMessage";
import {useEffect, useState} from "react";
import {get} from "../../../util/AjaxUtils.ts";
import {useEffect, useRef, useState} from "react";
import {del, get} from "../../../util/AjaxUtils.ts";
import {IListPage} from "../../../interfaces/listpage/IListPage.ts";
import InvoiceInfoSave from "./InvoiceInfoSave.tsx";
import InvoiceInfoEdit from "./InvoiceInfoEdit.tsx";
type DataType = {
invoiceTitle: string;
@ -18,11 +19,20 @@ type DataType = {
creator: string;
}
export default function InvoiceInfoList() {
interface ListProps {
handleOk: (selectedInvoice: DataType) => void;
handleCancel: () => void;
}
export default function InvoiceInfoList(props: ListProps) {
const [messageApi, messageContext] = useMessage();
const [isInvoiceInfoSaveOpen, setIsInvoiceInfoSaveOpen] = useState(false);
const [isInvoiceInfoEditOpen, setIsInvoiceInfoEditOpen] = useState(false);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [dataArray, setDataArray] = useState<DataType[]>([]);
const editInvoiceInfoId = useRef('');
const selectedInvoice = useRef<DataType | null>(null);
const columns: TableProps<DataType>['columns'] = [
{
@ -68,18 +78,39 @@ export default function InvoiceInfoList() {
align: 'center',
width: 100,
fixed: 'right',
render: () => {
render: (_value, record) => {
return (
<Space size={5}>
<Button type="primary" size="small"><EditOutlined/></Button>
<Button size="small"><DeleteOutlined/></Button>
<Button type="primary" size="small" onClick={() => {
editInvoiceInfoId.current = record.invoiceInfoId;
setIsInvoiceInfoEditOpen(true);
}}><EditOutlined/></Button>
<Popconfirm
placement="right"
title={false}
description="确定删除吗?"
okText="确定"
cancelText="取消"
onConfirm={() => {
del<any>({
messageApi,
url: `/api/invoice-info/remove/${record.invoiceInfoId}`,
onSuccess() {
messageApi.success('删除成功');
getData();
}
})
}}
>
<Button type="primary" size="small" danger><DeleteOutlined/></Button>
</Popconfirm>
</Space>
)
}
},
]
useEffect(() => {
const getData = () => {
get<IListPage<DataType>>({
messageApi,
url: '/api/invoice-info/listpage/self',
@ -95,6 +126,10 @@ export default function InvoiceInfoList() {
setDataArray(data.rows);
}
})
}
useEffect(() => {
getData();
}, [page]);
return (
@ -103,14 +138,14 @@ export default function InvoiceInfoList() {
<div className="mod-list">
<div className="table-btn-group" style={{marginBottom: '15px'}}>
<Button value="small" onClick={() => {
setIsInvoiceInfoSaveOpen(true);
}}><PlusOutlined/> </Button>
</div>
<Table rowSelection={
{
type: 'radio',
onChange: (selectedRowKeys: React.Key[], selectedRows: DataType[]) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
onChange: (_selectedRowKeys, selectedRows) => {
selectedInvoice.current = selectedRows[0];
},
}
} columns={columns} dataSource={dataArray} pagination={
@ -123,12 +158,58 @@ export default function InvoiceInfoList() {
}
} scroll={{y: 500}} bordered key="dataTable" rowKey="invoiceInfoId"/>
</div>
<Flex justify="center">
<Space size={5}>
<Button type="primary"
style={{backgroundColor: 'var(--color-primary)'}}
onClick={() => {
if (!selectedInvoice.current) {
messageApi.error('请选择开票信息');
return;
}
props.handleOk(selectedInvoice.current);
}}
></Button>
<Button type="default" onClick={() => {
props.handleCancel();
}}></Button>
</Space>
</Flex>
</div>
<Modal open={true}
<Modal open={isInvoiceInfoSaveOpen}
title="新增"
footer={false}
onCancel={() => {
setIsInvoiceInfoSaveOpen(false)
}}
>
<InvoiceInfoSave/>
<InvoiceInfoSave
handleOk={() => {
getData();
setIsInvoiceInfoSaveOpen(false);
}}
handleCancel={() => {
setIsInvoiceInfoSaveOpen(false);
}}
/>
</Modal>
<Modal open={isInvoiceInfoEditOpen}
title="编辑"
footer={false}
onCancel={() => {
setIsInvoiceInfoEditOpen(false)
}}
>
<InvoiceInfoEdit
invoiceInfoId={editInvoiceInfoId.current}
handleOk={() => {
getData();
setIsInvoiceInfoEditOpen(false);
}}
handleCancel={() => {
setIsInvoiceInfoEditOpen(false);
}}
/>
</Modal>
{messageContext}
</>

View File

@ -1,5 +1,8 @@
import {Button, Flex, Form, Input, Space} from "antd";
import {useForm} from "antd/es/form/Form";
import useModal from "antd/es/modal/useModal";
import {post} from "../../../util/AjaxUtils.ts";
import useMessage from "antd/es/message/useMessage";
type FormDataType = {
invoiceTitle: string;
@ -10,8 +13,14 @@ type FormDataType = {
invoiceAccount: string;
}
export default function InvoiceInfoSave() {
interface SaveProps {
handleOk: () => void;
handleCancel: () => void;
}
export default function InvoiceInfoSave(props: SaveProps) {
const [messageApi, messageContext] = useMessage();
const [modal, modalContext] = useModal();
const [form] = useForm<FormDataType>()
return (
@ -20,8 +29,29 @@ export default function InvoiceInfoSave() {
name="basic"
layout="vertical"
form={form}
onFinish={() => {
onFinish={(values) => {
modal.confirm({
title: '提示',
content: '确定保存吗',
okText: '确定',
cancelText: '取消',
okButtonProps: {
style: {
backgroundColor: 'var(--color-primary)'
}
},
onOk: () => {
post<any>({
messageApi,
url: '/api/invoice-info/save',
body: values,
onSuccess() {
messageApi.success('保存成功');
props.handleOk();
}
})
}
})
}}
>
<Form.Item
@ -68,11 +98,16 @@ export default function InvoiceInfoSave() {
</Form.Item>
<Flex justify="center">
<Space size={5}>
<Button type="primary" htmlType="submit" style={{backgroundColor: 'var(--color-primary)'}}></Button>
<Button type="default" onClick={() => {}}></Button>
<Button type="primary" htmlType="submit"
style={{backgroundColor: 'var(--color-primary)'}}></Button>
<Button type="default" onClick={() => {
props.handleCancel();
}}></Button>
</Space>
</Flex>
</Form>
{messageContext}
{modalContext}
</>
)
}

View File

@ -0,0 +1,200 @@
import {Button, Flex, Space, Table, TableProps, Tooltip} from "antd";
import useMessage from "antd/es/message/useMessage";
import {useEffect, useRef, useState} from "react";
import {get} from "../../../util/AjaxUtils.ts";
import {IListPage} from "../../../interfaces/listpage/IListPage.ts";
type DetailDataType = {
productType: string;
quantity: number;
unitPrice: number;
notes: string;
productDescription: string;
}
type DataType = {
orderId: string;
orderNo: string;
totalAmount: number;
detail: DetailDataType;
orderStatus: string;
gmtCreate: string;
}
interface ListProps {
selectedOrderIds?: string[];
handleOk: (selectedOrders: DataType[]) => void;
handleCancel: () => void;
}
export default function InvoiceInfoList(props: ListProps) {
const [messageApi, messageContext] = useMessage();
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [dataArray, setDataArray] = useState<DataType[]>([]);
const selectedOrders = useRef<DataType[] | null>(null);
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const columns: TableProps<DataType>['columns'] = [
{
title: '订单号',
dataIndex: 'orderNo',
align: 'center',
width: 250,
fixed: 'left'
},
{
title: '总金额',
dataIndex: 'totalAmount',
align: 'center',
width: 100,
fixed: 'left',
render: (value) => {
return (value / 100).toFixed(2)
}
},
{
title: '订单状态',
dataIndex: 'orderStatus',
align: 'center',
width: 100,
render: (value) => {
if(value === 'COMPLETE') {
return '完成';
}
if(value === 'CHARGEBACK') {
return '已退款';
}
}
},
{
title: '创建时间',
dataIndex: 'gmtCreate',
align: 'center',
width: 180
},
{
title: '产品类型',
dataIndex: 'detail.productType',
align: 'center',
width: 100,
render: (_value, record) => {
if(record.detail.productType === 'PROJ') {
return '项目创建'
}
if(record.detail.productType === 'AGENT') {
return '项目代理'
}
return record.detail.productType
}
},
{
title: '数量',
dataIndex: 'detail.quantity',
align: 'center',
width: 100,
render: (_value, record) => {
return record.detail.quantity
}
},
{
title: '单价',
dataIndex: 'detail.unitPrice',
align: 'center',
width: 100,
render: (_value, record) => {
return (record.detail.unitPrice / 100).toFixed(2)
}
},
{
title: '订单备注',
dataIndex: 'detail.notes',
align: 'center',
width: 120,
render: (_value, record) => {
return record.detail.notes
}
},
{
title: '描述',
dataIndex: 'detail.productDescription',
align: 'center',
width: 200,
ellipsis: {
showTitle: false,
},
render: (_value, record) => {
return <Tooltip placement="top" title={record.detail.productDescription}>{record.detail.productDescription}</Tooltip>
}
},
]
const getData = () => {
get<IListPage<DataType>>({
messageApi,
url: '/api/order/listpage/complete/no-invoiced/self',
config: {
params: {
page: page,
rows: 20
}
},
onSuccess({data}) {
setPage(data.page);
setTotal(data.total);
setDataArray(data.rows);
}
})
}
useEffect(() => {
getData();
if(props.selectedOrderIds && props.selectedOrderIds.length > 0) {
setSelectedRowKeys(props.selectedOrderIds)
}
}, [page]);
return (
<>
<div className="invoice-list-container">
<div className="mod-list">
<Table rowSelection={
{
selectedRowKeys,
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRowKeys(selectedRowKeys);
selectedOrders.current = selectedRows;
},
}
} columns={columns} dataSource={dataArray} pagination={
{
pageSize: 20,
total: total,
onChange: (currentPage) => {
setPage(currentPage);
}
}
} scroll={{y: 500}} bordered key="dataTable" rowKey="orderId"/>
</div>
<Flex justify="center">
<Space size={5}>
<Button type="primary"
style={{backgroundColor: 'var(--color-primary)'}}
onClick={() => {
if (!selectedOrders.current) {
messageApi.error('请选择开票订单');
return;
}
props.handleOk(selectedOrders.current);
}}
></Button>
<Button type="default" onClick={() => {
props.handleCancel();
}}></Button>
</Space>
</Flex>
</div>
{messageContext}
</>
);
}

View File

@ -0,0 +1,164 @@
import {Table, TableProps, Tooltip} from "antd";
import useMessage from "antd/es/message/useMessage";
import {useEffect, useState} from "react";
import {get} from "../../../util/AjaxUtils.ts";
import {IListPage} from "../../../interfaces/listpage/IListPage.ts";
type DetailDataType = {
productType: string;
quantity: number;
unitPrice: number;
notes: string;
productDescription: string;
}
type DataType = {
orderId: string;
orderNo: string;
totalAmount: number;
detail: DetailDataType;
orderStatus: string;
gmtCreate: string;
}
interface ListProps {
invoiceId: string;
}
export default function InvoiceInfoSelectedList(props: ListProps) {
const [messageApi, messageContext] = useMessage();
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [dataArray, setDataArray] = useState<DataType[]>([]);
const columns: TableProps<DataType>['columns'] = [
{
title: '订单号',
dataIndex: 'orderNo',
align: 'center',
width: 250,
fixed: 'left'
},
{
title: '总金额',
dataIndex: 'totalAmount',
align: 'center',
width: 100,
fixed: 'left',
render: (value) => {
return (value / 100).toFixed(2)
}
},
{
title: '订单状态',
dataIndex: 'orderStatus',
align: 'center',
width: 100,
render: (value) => {
if(value === 'COMPLETE') {
return '完成';
}
if(value === 'CHARGEBACK') {
return '已退款';
}
}
},
{
title: '创建时间',
dataIndex: 'gmtCreate',
align: 'center',
width: 180
},
{
title: '产品类型',
dataIndex: 'detail.productType',
align: 'center',
width: 100,
render: (_value, record) => {
if(record.detail.productType === 'PROJ') {
return '项目创建'
}
if(record.detail.productType === 'AGENT') {
return '项目代理'
}
return record.detail.productType
}
},
{
title: '数量',
dataIndex: 'detail.quantity',
align: 'center',
width: 100,
render: (_value, record) => {
return record.detail.quantity
}
},
{
title: '单价',
dataIndex: 'detail.unitPrice',
align: 'center',
width: 100,
render: (_value, record) => {
return (record.detail.unitPrice / 100).toFixed(2)
}
},
{
title: '订单备注',
dataIndex: 'detail.notes',
align: 'center',
width: 120,
render: (_value, record) => {
return record.detail.notes
}
},
{
title: '描述',
dataIndex: 'detail.productDescription',
align: 'center',
width: 200,
ellipsis: {
showTitle: false,
},
render: (_value, record) => {
return <Tooltip placement="top" title={record.detail.productDescription}>{record.detail.productDescription}</Tooltip>
}
},
]
useEffect(() => {
get<IListPage<DataType>>({
messageApi,
url: `/api/order/listpage/complete/self?invoiceId=${props.invoiceId}`,
config: {
params: {
page: page,
rows: 20
}
},
onSuccess({data}) {
setPage(data.page);
setTotal(data.total);
setDataArray(data.rows);
}
})
}, [page]);
return (
<>
{messageContext}
<div className="invoice-list-container">
<div className="mod-list">
<Table columns={columns} dataSource={dataArray} pagination={
{
pageSize: 20,
total: total,
onChange: (currentPage) => {
setPage(currentPage);
}
}
} scroll={{y: 500}} bordered key="dataTable" rowKey="orderId"/>
</div>
</div>
</>
);
}

View File

@ -20,6 +20,7 @@ export default function Head() {
const [loading, setLoading] = useState<boolean>(false);
const [isSelfModalOpen, setIsSelfModalOpen] = useState(false);
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false);
const [isInvoiceModalOpen, setIsInvoiceModalOpen] = useState(false);
useEffect(() => {
reloadUser(messageApi, globalDispatchContext).then((data) => {
@ -63,7 +64,7 @@ export default function Head() {
</div>
),
onClick: () => {
// nav('/invoice-list');
setIsInvoiceModalOpen(true);
}
},
{
@ -181,10 +182,11 @@ export default function Head() {
});
}}/>
</Modal>
<Modal open={true}
<Modal open={isInvoiceModalOpen}
title="发票管理"
width={1100}
footer={false}
onCancel={() => setIsInvoiceModalOpen(false)}
>
<InvoiceList/>
</Modal>

View File

@ -6,7 +6,7 @@ import MenuWithTopButton from "../../components/menu/MenuWithTopButton.tsx";
import MenuTreeWithTopButton from "../../components/menu/MenuTreeWithTopButton.tsx";
import ListProj from "../../components/list/ListProj.tsx";
import ListProjAgent from "../../components/list/ListProjAgent.tsx";
import {Breadcrumb, Button, MenuProps} from 'antd';
import {Breadcrumb, MenuProps} from 'antd';
import {
IndexListContext,
IndexListDataType,
@ -133,7 +133,6 @@ export default function Index() {
list={agentMenu.list}
handleListItem={agentMenu.handleListItem}
/>
<Button></Button>
</div>
<div className="right">
{

View File

@ -94,7 +94,6 @@ export default function ProjNew() {
labelCol={{span: 24}}
wrapperCol={{span: 24}}
style={{width: 500}}
initialValues={{remember: true}}
onFinish={(formData) => {
setIsCreateModalOpen(true);
setProjInfo({