初步完成发票提交

main
wuyize 2023-01-01 16:19:30 +08:00
parent 68431ef6e6
commit 81cafa8010
10 changed files with 276 additions and 197 deletions

View File

@ -9,6 +9,7 @@ import {RouterProvider} from "react-router-dom";
import CustomRouter from "./router/router";
import {PersistGate} from 'redux-persist/integration/react'
import {ConfigProvider} from "antd";
import zhCN from 'antd/locale/zh_CN';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
@ -17,7 +18,7 @@ root.render(
<React.StrictMode>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<ConfigProvider theme={{
<ConfigProvider locale={zhCN} theme={{
token: {
colorPrimary: "#1890ff",
colorSuccess: "#52c41a",

106
src/models/Invoice.ts Normal file
View File

@ -0,0 +1,106 @@
import {Staff} from "./Staff";
import {Reimbursement} from "./Reimbursement";
import {Dayjs} from "dayjs"
export interface Invoice {
invoiceAmount: number;
invoiceCode: string;
invoiceDate: string;
invoiceDeparture?: string;
invoiceDestination?: string;
invoiceFileUri: string;
invoiceId: number;
invoiceKind: number;
invoiceName?: string;
invoiceNo: string;
invoiceNote: string;
/**
* -1: 0: 1: 2:
*/
invoiceState: number;
invoiceThumbnailUri: string;
invoiceUploader: Staff;
invoiceUploadTime: string;
modified: boolean;
reimbursement: Reimbursement;
}
export interface InvoiceIdentifyResponse {
invoiceAmount: number;
invoiceCheckCode?: string;
invoiceCode: string;
invoiceDate: string|Dayjs;
invoiceDeparture?: string;
invoiceDestination?: string;
invoiceExtraInfo: InvoiceExtraInfo[];
invoiceFileName: string;
invoiceKind: number;
invoiceName?: string;
invoiceNo: string;
invoiceNote?: string;
invoiceRegionCode?: string;
invoiceSellerTaxCode?: string;
}
export interface InvoiceExtraInfo {
name: string;
value: string;
}
export const invoiceTypeNameMap = new Map ([
[0, '出租车发票'],
[1, '定额发票'],
[2, '火车票'],
[3, '增值税发票'],
[5, '机票行程单'],
[8, '通用机打发票'],
[9, '汽车票'],
[10, '轮船票'],
[11, '增值税发票(卷票)'],
[12, '购车发票'],
[13, '过路过桥费发票'],
[15, '非税发票'],
[16, '全电发票']
])
export const invoiceItemsMap = new Map([
['invoiceNo', '发票编号'],
['invoiceCode', '发票代码'],
['invoiceAmount', '金额'],
['invoiceDate', '开票日期'],
['invoiceKind', '发票种类'],
['invoiceCheckCode', '校验码'],
['invoiceRegionCode', '区域码'],
['invoiceSellerTaxCode', '卖方税号'],
['invoiceNote', '备注'],
['invoiceDeparture', '出发地'],
['invoiceDestination', '目的地'],
['invoiceName', '发票名称'],
])
export const invoiceTypeItemsMap = new Map ([
[13, ['invoiceName', 'invoiceNo', 'invoiceCode', 'invoiceAmount', 'invoiceDate', 'invoiceDeparture', 'invoiceDestination']],
])
/*
{
"invoiceFileName": "e86d8d76c0507dcfb5364172a4832494.jpg",
"invoiceNo": "35971607",
"invoiceCode": "121011972201",
"invoiceAmount": 1000,
"invoiceDate": "2019-07-11",
"invoiceKind": 13,
"invoiceCheckCode": "",
"invoiceRegionCode": "",
"invoiceSellerTaxCode": "",
"invoiceExtraInfo": {
"时间": "13:01",
"发票消费类型": "交通",
"高速标志": "1"
},
"invoiceNote": null,
"invoiceDeparture": "苏家屯",
"invoiceDestination": "灯塔",
"invoiceName": "辽宁省高速公路通行费专用发票"
}*/

View File

@ -18,83 +18,43 @@ export interface Department {
departmentName: string;
}
export interface Invoice {
invoiceAmount: number;
invoiceCode: string;
invoiceDate: string;
invoiceDeparture?: string;
invoiceDestination?: string;
invoiceFileUri: string;
invoiceId: number;
invoiceKind: number;
invoiceName?: string;
invoiceNo: string;
invoiceNote: string;
/**
* -1: 0: 1: 2:
*/
invoiceState: number;
invoiceThumbnailUri: string;
invoiceUploader: Staff;
invoiceUploadTime: string;
modified: boolean;
reimbursement: Reimbursement;
}
export interface InvoiceIdentifyResponse {
invoiceAmount: number;
invoiceCheckCode?: string;
invoiceCode: string;
invoiceDate: string;
invoiceDeparture?: string;
invoiceDestination?: string;
invoiceExtraInfo: InvoiceExtraInfo[];
invoiceFileName: string;
invoiceKind: number;
invoiceName?: string;
invoiceNo: string;
invoiceNote?: string;
invoiceRegionCode?: string;
invoiceSellerTaxCode?: string;
}
export interface InvoiceExtraInfo {
name: string;
value: string;
}
type Nullable<T> = T | undefined | null;
export class InvoiceSearchOption{
export class InvoiceSearchOption {
invoiceNo: Nullable<string>
invoiceCode: Nullable<string>
invoiceUploadTimeStart: Nullable<string>
invoiceUploadTimeEnd:Nullable<string>
invoiceState:Nullable<number>
invoiceKind:Nullable<string>
invoiceDateStart:Nullable<string>
invoiceDateEnd:Nullable<string>
invoiceNote:Nullable<string>
invoiceUploader:Nullable<string>
pageNum:number
pageSize:number
constructor(){
this.pageNum=0
this.pageSize=20
invoiceUploadTimeEnd: Nullable<string>
invoiceState: Nullable<number>
invoiceKind: Nullable<string>
invoiceDateStart: Nullable<string>
invoiceDateEnd: Nullable<string>
invoiceNote: Nullable<string>
invoiceUploader: Nullable<string>
pageNum: number
pageSize: number
constructor() {
this.pageNum = 0
this.pageSize = 20
this.clear()
}
clear(){
this.invoiceNo=null
this.invoiceCode=null
this.invoiceUploadTimeStart=null
this.invoiceUploadTimeEnd=null
this.invoiceState=null
this.invoiceKind=null
this.invoiceDateStart=null
this.invoiceDateEnd=null
this.invoiceNote=null
this.invoiceUploader=null
this.pageNum=0
this.pageSize=20
clear() {
this.invoiceNo = null
this.invoiceCode = null
this.invoiceUploadTimeStart = null
this.invoiceUploadTimeEnd = null
this.invoiceState = null
this.invoiceKind = null
this.invoiceDateStart = null
this.invoiceDateEnd = null
this.invoiceNote = null
this.invoiceUploader = null
this.pageNum = 0
this.pageSize = 20
}
// toString(){
@ -114,17 +74,18 @@ export class InvoiceSearchOption{
}
export class InvoiceCommit {
invoiceFileName: string
invoiceFileName: string
invoiceNo: string
invoiceCode:string
invoiceKind:string
invoiceDate:dayjs.Dayjs
invoiceAmount:number
invoiceCode: string
invoiceKind: string
invoiceDate: dayjs.Dayjs
invoiceAmount: number
invoiceAmountWithoutTax: number
invoiceCheckCode:string
invoiceCheckCode: string
invoiceRegionCode: Nullable<string>
invoiceSellerTaxCode:Nullable<string>
invoiceExtraInfo:Nullable<string>|[]|Map<any,string>
invoiceSellerTaxCode: Nullable<string>
invoiceExtraInfo: Nullable<string> | [] | Map<any, string>
constructor() {
this.invoiceFileName = ""
this.invoiceNo = ""
@ -138,10 +99,11 @@ export class InvoiceCommit {
this.invoiceSellerTaxCode = null
this.invoiceExtraInfo = null
}
setValue(props: FormData) {
console.log("1111"+props.toString())
console.log("1111" + props.toString())
this.invoiceFileName = props.get("invoiceFileName")!.toString()
this.invoiceNo = props.get("invoiceNo")!.toString()
this.invoiceNo = props.get("invoiceNo")!.toString()
this.invoiceCode = props.get("invoiceCode")!.toString()
this.invoiceKind = props.get("invoiceKind")!.toString()
//this.invoiceDate = new Date(props.get("invoiceDate")!.toString())
@ -153,21 +115,22 @@ export class InvoiceCommit {
//this.invoiceExtraInfo = props.get("invoiceExtraInfo")!.
}
}
export interface Reimbursement {
/**
*
*/
reimbursementAdditionalAmount: number;
reimbursementDepartureInvoiceId: number;
reimbursementDepartureName: string;
reimbursementAdditionalAmount: number;
reimbursementDepartureInvoiceId: number;
reimbursementDepartureName: string;
reimbursementDestinationInvoiceId?: number;
reimbursementDestinationName: string;
reimbursementId: number;
reimbursementDestinationName: string;
reimbursementId: number;
/**
*
*/
reimbursementInvoiceAmount: number;
reimbursementNote?: string;
reimbursementNote?: string;
/**
* 0: success
* 1:
@ -176,10 +139,10 @@ export interface Reimbursement {
* 4:
* 5:
*/
reimbursementStatus: number;
reimbursementStatus: number;
reimbursementSubmitDepartmentId: number;
reimbursementSubmitStaffId: string;
reimbursementSubmitTime: string;
reimbursementSubmitStaffId: string;
reimbursementSubmitTime: string;
/**
*
*/

View File

@ -38,6 +38,7 @@ const staffSlice = createSlice({
state.staffName = action.payload.staffName
state.managingDepartment = action.payload.managingDepartment
state.staffDepartments = action.payload.staffDepartments
},
},
});

View File

@ -71,7 +71,7 @@ function HeaderBar(props: any) {
const departmentToString = (staff: Staff) => {
let result = staff.managingDepartment === null ? '' : staff.managingDepartment.departmentName + '主管'
for (const department of staff.staffDepartments) {
result += ' ' + department.departmentName
result += '\xa0\xa0' + department.departmentName
}
return result
}
@ -85,7 +85,7 @@ function HeaderBar(props: any) {
flexDirection: 'row-reverse',
alignItems: 'center'
}}>
<Dropdown menu={{items, onClick}} placement="bottomRight" arrow>
<Dropdown overlayStyle={{minWidth: '150px'}} menu={{items, onClick}} placement="bottomRight" arrow>
<Button type="text" style={{
marginRight: '12px', height: '100%',
display: 'flex', justifyContent: 'center', alignItems: 'center'
@ -96,7 +96,7 @@ function HeaderBar(props: any) {
width: '24px', height: '24px', backgroundColor: colorPrimary, borderRadius: '12px',
display: 'flex', justifyContent: 'center', alignItems: 'center'
}}>
<span style={{color: 'white'}}>{staff.staffName[0]}</span>
<span style={{color: 'white', fontSize: '10px'}}>{staff.staffName[0]}</span>
</div>
</Button>
</Dropdown>

View File

@ -18,7 +18,7 @@ import {
import type {MenuProps, PaginationProps} from 'antd';
import {DownOutlined, UploadOutlined} from '@ant-design/icons';
import React, {ReactElement, JSXElementConstructor, ReactFragment, ReactPortal, useState} from "react";
import {Invoice, InvoiceCommit, InvoiceSearchOption} from "../../../models/Staff"
import {InvoiceCommit, InvoiceSearchOption} from "../../../models/Staff"
import {Space, Typography} from 'antd';
import {SizeType} from "antd/es/config-provider/SizeContext";
import axios from "axios";
@ -28,6 +28,7 @@ import change = Simulate.change;
import {useNavigate} from "react-router-dom";
import {useAppDispatch} from "../../../models/hooks";
import InvoiceUploadView from "./InvoiceUploadView";
import {Invoice, invoiceTypeNameMap} from "../../../models/Invoice";
const {Meta} = Card;
const {Search} = Input;
@ -238,7 +239,7 @@ class InvoiceItem extends React.Component<any, any> {
>
<div style={{marginTop: -20}}>
<li style={{fontWeight: "bold", fontSize: 20}}>¥{(this.state.invoiceAmount / 100.0).toFixed(2)}</li>
<li>{this.state.invoiceKind}</li>
<li>{invoiceTypeNameMap.get(this.state.invoiceKind)}</li>
<li>{this.state.invoiceNo}</li>
<li>{this.state.invoiceDate}</li>
</div>
@ -262,6 +263,9 @@ class InvoiceListView extends React.Component<any, any> {
totalNum: 2,
PaginationKey: -2
}
}
componentDidMount(){
this.searchInvoiceContent()
}

View File

@ -1,13 +1,19 @@
import React, {useEffect, useRef, useState} from 'react';
import {Button, Modal, UploadProps, message, Upload, UploadFile, Form, Input, DatePicker} from 'antd';
import {Button, Modal, UploadProps, message, Upload, UploadFile, Form, Input, DatePicker, Select, InputNumber} from 'antd';
import {InboxOutlined, UploadOutlined} from '@ant-design/icons';
import axiosInstance from "../../../utils/axiosInstance";
import {InvoiceCommit, InvoiceIdentifyResponse} from "../../../models/Staff";
import dayjs from 'dayjs';
import {InvoiceCommit} from "../../../models/Staff";
import dayjs, {Dayjs} from 'dayjs';
import TextArea from "antd/es/input/TextArea";
import {constants} from "http2";
import type {FormInstance} from 'antd/es/form';
import {useAppDispatch} from "../../../models/hooks";
import {
InvoiceIdentifyResponse,
invoiceItemsMap,
invoiceTypeItemsMap,
invoiceTypeNameMap,
} from "../../../models/Invoice";
const {Dragger} = Upload;
@ -22,17 +28,6 @@ const props: UploadProps = {
console.log(file)
return false
},
onChange(info) {
// const { status } = info.file;
// if (status !== 'uploading') {
// console.log('here---------'+info.file, info.fileList);
// }
// if (status === 'done') {
// message.success(`${info.file.name} file uploaded successfully.`);
// } else if (status === 'error') {
// message.error(`${info.file.name} file upload failed.`);
// }
},
onDrop(e) {
console.log('Dropped files', e.dataTransfer.files);
},
@ -86,7 +81,7 @@ function UpLoadModal(props: any) {
return (
<Modal
open={open}
title="Title"
title="上传发票"
onOk={handleOk}
onCancel={handleCancel}
footer={[
@ -105,104 +100,109 @@ function UpLoadModal(props: any) {
function FormModal(props: any) {
const [open, setOpen] = useState(false)
const [loading, setLoading] = useState(false)
const [invoice, setInvoice] = useState({})
const [form] = Form.useForm();
useEffect(() => {
setOpen(props.open)
console.log(props.invoiceIdentifyResponse)
let i = {...props.invoiceIdentifyResponse}
setInvoice(i)
form.setFieldsValue(i)
}, [props]);
const handleCancel = () => {
setOpen(false);
};
const submit = () => {
let result = form!.getFieldsValue()
result.invoiceDate = result.invoiceDate.format("YYYY-MM-DD")
result.invoiceExtraInfo = new Map(result.invoiceExtraInfo.split("\n").map((entry: string) => entry.split(": ")))
console.log(result.invoiceExtraInfo.split("\n").map((entry: string) => entry.split(": ")))
// let extraInfo=[]
// let line = result.invoiceExtraInfo.split('\n')
// for(let i=0;i<line.length;i++){
// let tmp = line[i].split(": ")
// if(tmp.length===2){
// extraInfo.push({name:tmp[0],value:tmp[1]})
// }
// }
// result.invoiceExtraInfo=extraInfo
console.log(result.invoiceExtraInfo)
setLoading(true)
console.log(form.getFieldsValue())
let result:InvoiceIdentifyResponse = {...invoice , ...form.getFieldsValue()} as InvoiceIdentifyResponse
console.log(result)
if (typeof result.invoiceDate !== "string") {
result.invoiceDate = result.invoiceDate.format("YYYY-MM-DD")
}
result.invoiceAmount*=100
console.log(result)
axiosInstance({
url: 'common/invoice/submit',
method: 'POST',
data: result
}).then(response => {
//TODO
console.log(response.data)
setLoading(false)
setOpen(false)
}).catch(function (error) {
console.log(error)
setLoading(false)
})
}
const inputComponent = (item: string) => {
if (item === 'invoiceDate')
return <DatePicker/>
else if(item === 'invoiceAmount')
return <InputNumber
style={{ width: '100%' }}
formatter={(value) => `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
/>
else
return <Input/>
}
let formItems = invoiceTypeItemsMap.get(props.invoiceIdentifyResponse?.invoiceKind)?.map((item, index) => {
return (
<Form.Item
key={index}
name={item}
label={invoiceItemsMap.get(item)}
>
{inputComponent(item)}
</Form.Item>
)
})
const getInvoiceKindOptions = () => {
let options: { value: number; label: string; }[] = []
invoiceTypeNameMap.forEach(function (value, key, map) {
options.push({
value: key,
label: value,
})
})
return options
}
return (
<Modal
open={open}
title="Title"
title="提交发票"
onOk={submit}
onCancel={handleCancel}
footer={[
<Button key="cancel" onClick={handleCancel}>
</Button>,
<Button key="next" type="primary" loading={false} onClick={submit}
<Button key="next" type="primary" loading={loading} onClick={submit}
>
</Button>
]}
>
<Form form={form}>
<Form.Item
name={`invoiceNo`}
label={`发票编码`}
>
<Input/>
</Form.Item>
<Form.Item
name={`invoiceCode`}
label={`发票代码`}
>
<Input/>
</Form.Item>
<Form form={form} layout="horizontal" labelCol={{span: 4}}
>
<Form.Item
name={`invoiceKind`}
label={`发票类型`}
>
<Input/>
<Select
options={getInvoiceKindOptions()}
/>
</Form.Item>
{formItems}
<Form.Item
name={`invoiceAmount`}
label={`计税总额`}
>
<Input/>
</Form.Item>
<Form.Item
name={`invoiceAmountWithoutTax`}
label={`非税金额`}
>
<Input/>
</Form.Item>
<Form.Item
name={`invoiceDate`}
label={`开票日期`}
>
<DatePicker/>
</Form.Item>
<Form.Item
name={`invoiceCheckCode`}
label={`核验码`}
>
<Input/>
</Form.Item>
<Form.Item
name={`invoiceExtraInfo`}
label={`其他信息`}
name={`invoiceNote`}
label={`备注`}
>
<TextArea/>
</Form.Item>
@ -213,44 +213,32 @@ function FormModal(props: any) {
class InvoiceUploadView extends React.Component<any, any> {
formRef = React.createRef<FormInstance>();
constructor(props: {}) {
super(props);
this.state = {
loading: false,
open: false,
uploadOpen: false,
uploadStep: 0,
invoiceCommit: new InvoiceCommit(),
uploadModalOpen: false,
formModalOpen: false,
invoiceIdentifyResponse: null
}
this.state.invoiceCommit.invoiceNo = "init"
}
setUploadOpen(value: Boolean) {
this.setState({uploadOpen: value})
}
setOpen(value: Boolean) {
this.setState({open: value})
this.setState({uploadModalOpen: value})
}
showUploadView = () => {
this.setOpen(true);
};
handleUploadCanCel = () => {
this.setUploadOpen(false);
}
handleNextStep = (response: InvoiceIdentifyResponse) => {
this.setState({open: false, uploadOpen: true})
response.invoiceDate = dayjs(response.invoiceDate)
response.invoiceAmount /= 100.
this.setState({uploadModalOpen: false, formModalOpen: true, invoiceIdentifyResponse: response})
}
render() {
console.log(this.state.invoiceCommit)
console.log(this.state.uploadStep)
// @ts-ignore
return (
@ -259,8 +247,9 @@ class InvoiceUploadView extends React.Component<any, any> {
size="large">
</Button>
<UpLoadModal open={this.state.open} nextStep={this.handleNextStep}/>
<FormModal open={this.state.uploadOpen}/>
<UpLoadModal open={this.state.uploadModalOpen} nextStep={this.handleNextStep}/>
<FormModal open={this.state.formModalOpen}
invoiceIdentifyResponse={this.state.invoiceIdentifyResponse}/>
</>);
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import {Button, Form, Input} from 'antd';
import React, {useState} from 'react';
import {Button, Form, Input, Alert} from 'antd';
import {LockOutlined, UserOutlined} from '@ant-design/icons';
import './LoginView.css';
import loginImg from '../../assets/login.png'
@ -12,7 +12,8 @@ import {setToken} from "../../models/store";
function LoginView() {
const navigate = useNavigate();
const dispatch = useAppDispatch();
const [showAlert, setShowAlert] = useState(false)
const [alertMessage, setAlertMessage] = useState('')
const onFinish = (values: any) => {
console.log(values)
const data = new FormData();
@ -30,7 +31,8 @@ function LoginView() {
navigate('/invoice/mine')
}).catch(function (error) {
console.log(error);
//showAlert.value = true
setAlertMessage(error.response.data.msg)
setShowAlert(true)
});
console.log('Success:', values);
};
@ -38,6 +40,9 @@ function LoginView() {
console.log('Failed:', errorInfo);
};
const afterClose = () =>{
setShowAlert(false)
}
return (
<div className="background">
@ -93,7 +98,16 @@ function LoginView() {
<Input.Password prefix={<LockOutlined/>}
placeholder="密码"/>
</Form.Item>
{showAlert&&
<Alert
style={{marginBottom: 20, height: 32}}
message={alertMessage}
type="error"
closable
showIcon
afterClose={afterClose}
/>
}
<Form.Item>
<Button type="primary" htmlType="submit" style={{width: '100%'}}>

View File

@ -15,7 +15,7 @@ axiosInstance.interceptors.request.use(
function (config: AxiosRequestConfig) {
// @ts-ignore
config.headers.Authorization = "Bearer " + store.getState().token.accessToken
console.log(config)
//console.log(config)
return config
},
function (error) {

View File

@ -18,7 +18,8 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
"jsx": "react-jsx",
"downlevelIteration": true
},
"include": [
"src"