前言
在报表数据处理中,Excel 公式领有弱小而多样的性能,广泛应用于各个业务畛域。无论是投资收益计算、财务报表编制还是保险收益估算,Excel 公式都扮演着不可或缺的角色。传统的做法是间接依赖 Excel 来实现简单的业务逻辑,并生成相应的 Excel 文件。因而只需在预设地位输出相应参数,Excel 公式即可被激活,迅速计算并出现后果。正因如此,在这类场景中,企业积攒了大量用于计算的 Excel 文件,它们曾经成为了无价的财产。
然而,传统的 Excel 文件形式存在难以治理和数据不平安的毛病。为了解决这些问题,能够采纳 B / S 架构 +Excel 组件库的形式。
本文将以个人所得税的计算为例,应用 React+Spring Boot+GcExcel 来实现。首先筹备好 Excel 文件,依照国家税务总局提供的个税计算页面进行创立。
个人所得税的支出类型有 8 种:
- 工资薪金所得
- 年终奖所得
- 劳务报酬所得
- 个体工商户、生产经营所得
- 酬劳所得
- 偶尔所得
- 利息、股息、红利所得
- 财产转让所得
其中,工资薪金所得最为简单,包含社会保险和专项扣除。每种类型的计税形式都不同,为了便于了解,咱们为每个类型创立了一个工作表进行计算。
以下是筹备好的 Excel 文件,其中蓝色局部为须要输出参数的单元格,其余单元格将主动计算。
实现筹备工作后,上面开始前后端工程的搭建。
实际
前端 React
创立 React 工程
新建一个文件夹,如 TaxCalculator,进入文件夹,在资源管理器的地址栏里输出 cmd,而后回车,关上命令行窗口。应用上面的代码创立名为 client-app 的 react app。
npx create-react-app salary-client
进入刚创立的 salary-client 文件夹,应用 IDE,比方 VisualStudio Code 关上文件夹。
界面局部
个人所得税波及的支出类型一共有 8 种,其中(“酬劳所得”,“偶尔所得”,“利息、股息、红利所得”,“财产转让所得”)四种的计算形式靠近,UI 布局类似,借助 React 的 component 个性,最终须要提供 5 种表单界面。
如下图所示:
为了让 UI 看起来更好看一些,能够先引入一个 UI 框架,这里咱们应用了 MUI。
npm install @mui/material @emotion/react @emotion/styled
首先,更新 Src/App.js 的代码,其中增加了 DarkMode 的 Theme, 代码如下:
import './App.css';
import {ThemeProvider} from '@emotion/react';
import {createTheme} from '@mui/material';
import {FormContainer} from './Component/FormContainer';
const darkTheme = createTheme({
palette: {mode: 'dark',},
});
function App() {
return (<ThemeProvider theme={darkTheme}>
<div className="App-header">
<h2> 个人所得税计算器 </h2>
<FormContainer></FormContainer>
</div>
</ThemeProvider>
);
}
export default App;
能够看到,App.js 中援用了 FormContainer,下来增加 ./Component/FormContainer.js。
FormContainer 次要是提供一个 Selector,让用户抉择支出类型,依据抉择的类型渲染不同的组件。
import React, {useState} from 'react';
import {SalaryIncome} from "./SalaryIncome"
import {NativeSelect, FormControl} from '@mui/material';
import {BounsIncome} from './BounsIncome';
import {CommercialIncome} from './CommercialIncome';
import {LaborIncome} from './LaborIncome';
import {OtherIncome} from './OtherIncome';
export const FormContainer = () => {const [calcType, setCalcType] = useState("工资薪金所得");
const GetIncomeControl = () => {switch (calcType) {
case "工资薪金所得":
return <SalaryIncome calcType={calcType}></SalaryIncome>;
case "年终奖所得":
return <BounsIncome calcType={calcType}></BounsIncome>;
case "劳务报酬所得":
return <LaborIncome calcType={calcType}></LaborIncome>;
case "个体工商户、生产经营所得":
return <CommercialIncome calcType={calcType}></CommercialIncome>;
default:
return <OtherIncome calcType={calcType}></OtherIncome>;
}
}
return (<div style={{ width: "60vw", marginTop: "5vh"}}>
<FormControl fullWidth sx={{marginBottom: 2}}>
<NativeSelect labelId="demo-simple-select-label" id="demo-simple-select"
value={calcType} label="类型" onChange={e => setCalcType(e.target.value)} >
<option value="工资薪金所得"> 工资薪金所得 </option>
<option value="年终奖所得"> 年终奖所得 </option>
<option Item value="劳务报酬所得"> 劳务报酬所得 </option>
<option value="个体工商户、生产经营所得"> 个体工商户、生产经营所得 </option>
<option value="酬劳所得"> 酬劳所得 </option>
<option value="偶尔所得"> 偶尔所得 </option>
<option value="利息、股息、红利所得"> 利息、股息、红利所得 </option>
</NativeSelect>
</FormControl>
{GetIncomeControl()}
</div>);
}
例如:\<SalaryIncome calcType={calcType}\>\</SalaryIncome\>; 同时会将 calcType 传递进去。
接下来,别离创立几个 xxxIncome 组件。
1. 工资薪金所得 SalaryIncome.js
import React, {useState} from 'react';
import {TextField, Button, Stack} from '@mui/material';
import axios from 'axios';
export const SalaryIncome = (props) => {const [income, setIncome] = useState("");
const [insurance, setInsurance] = useState("");
const [childEdu, setChildEdu] = useState("");
const [selfEdu, setSelfEdu] = useState("");
const [treatment, setTreatment] = useState("");
const [loans, setLoans] = useState("");
const [rent, setRent] = useState("");
const [elder, setElder] = useState("");
const [taxableIncome, setTaxableIncome] = useState("");
const [taxRate, setTaxRate] = useState("");
const [deduction, setDeduction] = useState("");
const [tax, setTax] = useState("");
const [takeHomeSalary, setTakeHomeSalary] = useState("");
async function calculateTax(event) {event.preventDefault();
let res = await axios.post("api/calcPersonTax", {
calcType: props.calcType,
income: income,
insurance: insurance,
childEdu: childEdu,
selfEdu: selfEdu,
treatment: treatment,
loans: loans,
rent: rent,
elder: elder,
});
if (res != null) {
let data = res.data;
setTaxableIncome(data.taxableIncome);
setTaxRate(data.taxRate);
setDeduction(data.deduction);
setTax(data.tax);
setTakeHomeSalary(data.takeHomeSalary);
}
}
function reset(event) {event.preventDefault();
setIncome("");
setInsurance("");
setChildEdu("");
setSelfEdu("");
setTreatment("");
setLoans("");
setRent("");
setElder("");
setTaxableIncome("");
setTaxRate("");
setDeduction("");
setTax("");
setTakeHomeSalary("");
}
return (
<div>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='primary'
label="税前工资" onChange={e => setIncome(e.target.value)}
value={income} fullWidth required size="small"/>
<TextField type="text" variant='outlined' color='secondary'
label="社会保险 / 公积金" onChange={e => setInsurance(e.target.value)}
value={insurance} fullWidth size="small"/>
</Stack>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary'
label="子女教育专项扣除" onChange={e => setChildEdu(e.target.value)}
value={childEdu} fullWidth size="small"/>
<TextField type="text" variant='outlined' color='secondary'
label="持续教育专项扣除" onChange={e => setSelfEdu(e.target.value)}
value={selfEdu} fullWidth size="small"/>
</Stack>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary'
label="大病医疗专项扣除" onChange={e => setTreatment(e.target.value)}
value={treatment} fullWidth size="small"/>
<TextField type="text" variant='outlined' color='secondary'
label="住房贷款利息专项扣除" onChange={e => setLoans(e.target.value)}
value={loans} fullWidth size="small"/>
</Stack>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary'
label="住房租金专项扣除" onChange={e => setRent(e.target.value)}
value={rent} fullWidth size="small"/>
<TextField type="text" variant='outlined' color='secondary'
label="奉养老人专项扣除" onChange={e => setElder(e.target.value)}
value={elder} fullWidth size="small"/>
</Stack>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary'
label="起征点" value="5000 元 / 月" fullWidth disabled size="small"/>
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<Button variant="outlined" color="primary" onClick={calculateTax} fullWidth size="large"> 计算 </Button>
<Button variant="outlined" color="secondary" onClick={reset} fullWidth size="large"> 重置 </Button>
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary'
label="应征税所得额" value={taxableIncome} fullWidth disabled size="small"/>
<TextField type="text" variant='outlined' color='secondary'
label="税率" value={taxRate} fullWidth disabled size="small"/>
</Stack>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary'
label="速算扣除数" value={deduction} fullWidth disabled size="small"/>
<TextField type="text" variant='outlined' color='secondary'
label="应纳税额" value={tax} fullWidth disabled size="small"/>
</Stack>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary'
label="税后工资" value={takeHomeSalary} fullWidth disabled size="small"/>
</Stack>
</div>
)
}
2. 年终奖金所得 BounsIncome.js
import React, {useState} from 'react';
import {TextField, Button, Stack} from '@mui/material';
import axios from 'axios';
export const BounsIncome = (props) => {const [income, setIncome] = useState("");
const [taxableIncome, setTaxableIncome] = useState("");
const [taxRate, setTaxRate] = useState("");
const [deduction, setDeduction] = useState("");
const [monthlyWage, setMonthlyWage] = useState("");
const [tax, setTax] = useState("");
const [takeHomeSalary, setTakeHomeSalary] = useState("");
async function calculateTax(event) {event.preventDefault();
let res = await axios.post("api/calcPersonTax", {
calcType: props.calcType,
income: income,
});
if (res != null) {
let data = res.data;
setTaxableIncome(data.taxableIncome);
setTaxRate(data.taxRate);
setDeduction(data.deduction);
setMonthlyWage(data.monthlyWage);
setTax(data.tax);
setTakeHomeSalary(data.takeHomeSalary);
}
}
function reset(event) {event.preventDefault();
setIncome("");
setTaxableIncome("");
setTaxRate("");
setDeduction("");
setMonthlyWage("");
setTax("");
setTakeHomeSalary("");
}
return (
<div>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='primary' size="small"
label="税前工资" onChange={e => setIncome(e.target.value)}
value={income} fullWidth required />
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<Button variant="outlined" color="primary" onClick={calculateTax} fullWidth size="large"> 计算 </Button>
<Button variant="outlined" color="secondary" onClick={reset} fullWidth size="large"> 重置 </Button>
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="应征税所得额" value={taxableIncome} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="税率" value={taxRate} fullWidth disabled />
</Stack>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="速算扣除数" value={deduction} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="均匀每月工资" value={monthlyWage} fullWidth disabled />
</Stack>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="应纳税额" value={tax} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="税后工资" value={takeHomeSalary} fullWidth disabled />
</Stack>
</div>
)
}
3. 劳务报酬所得 LaborIncome.js
import React, {useState} from 'react';
import {TextField, Button, Stack} from '@mui/material';
import axios from 'axios';
export const LaborIncome = (props) => {const [income, setIncome] = useState("");
const [taxableIncome, setTaxableIncome] = useState("");
const [taxRate, setTaxRate] = useState("");
const [deduction, setDeduction] = useState("");
const [nonTaxablePart, setNonTaxablePart] = useState("");
const [tax, setTax] = useState("");
const [takeHomeSalary, setTakeHomeSalary] = useState("");
async function calculateTax(event) {event.preventDefault();
let res = await axios.post("api/calcPersonTax", {
calcType: props.calcType,
income: income,
});
if (res != null) {
let data = res.data;
setTaxableIncome(data.taxableIncome);
setTaxRate(data.taxRate);
setDeduction(data.deduction);
setNonTaxablePart(data.nonTaxablePart);
setTax(data.tax);
setTakeHomeSalary(data.takeHomeSalary);
}
}
function reset(event) {event.preventDefault();
setIncome("");
setTaxableIncome("");
setTaxRate("");
setDeduction("");
setNonTaxablePart("");
setTax("");
setTakeHomeSalary("");
}
return (
<div>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='primary' size="small"
label="税前工资" onChange={e => setIncome(e.target.value)}
value={income} fullWidth required />
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<Button variant="outlined" color="primary" onClick={calculateTax} fullWidth size="large"> 计算 </Button>
<Button variant="outlined" color="secondary" onClick={reset} fullWidth size="large"> 重置 </Button>
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="应征税所得额" value={taxableIncome} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="税率" value={taxRate} fullWidth disabled />
</Stack>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="速算扣除数" value={deduction} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="减除费用" value={nonTaxablePart} fullWidth disabled />
</Stack>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="应纳税额" value={tax} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="税后工资" value={takeHomeSalary} fullWidth disabled />
</Stack>
</div>
)
}
4. 个体工商户、生产经营所得 CommercialIncome.js
import React, {useState} from 'react';
import {TextField, Button, Stack} from '@mui/material';
import axios from 'axios';
export const CommercialIncome = (props) => {const [income, setIncome] = useState("");
const [taxableIncome, setTaxableIncome] = useState("");
const [taxRate, setTaxRate] = useState("");
const [deduction, setDeduction] = useState("");
const [tax, setTax] = useState("");
const [takeHomeSalary, setTakeHomeSalary] = useState("");
async function calculateTax(event) {event.preventDefault();
let res = await axios.post("api/calcPersonTax", {
calcType: props.calcType,
income: income,
});
if (res != null) {
let data = res.data;
setTaxableIncome(data.taxableIncome);
setTaxRate(data.taxRate);
setDeduction(data.deduction);
setTax(data.tax);
setTakeHomeSalary(data.takeHomeSalary);
}
}
function reset(event) {event.preventDefault();
setIncome("");
setTaxableIncome("");
setTaxRate("");
setDeduction("");
setTax("");
setTakeHomeSalary("");
}
return (
<div>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='primary' size="small"
label="税前工资" onChange={e => setIncome(e.target.value)}
value={income} fullWidth required />
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<Button variant="outlined" color="primary" onClick={calculateTax} fullWidth size="large"> 计算 </Button>
<Button variant="outlined" color="secondary" onClick={reset} fullWidth size="large"> 重置 </Button>
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="应征税所得额" value={taxableIncome} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="税率" value={taxRate} fullWidth disabled />
</Stack>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="速算扣除数" value={deduction} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="应纳税额" value={tax} fullWidth disabled />
</Stack>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="税后工资" value={takeHomeSalary} fullWidth disabled />
</Stack>
</div>
)
}
5. 余下四种类型 OtherIncome.js
import React, {useState} from 'react';
import {TextField, Button, Stack} from '@mui/material';
import axios from 'axios';
export const OtherIncome = (props) => {const [income, setIncome] = useState("");
const [taxableIncome, setTaxableIncome] = useState("");
const [taxRate, setTaxRate] = useState("");
const [tax, setTax] = useState("");
const [takeHomeSalary, setTakeHomeSalary] = useState("");
async function calculateTax(event) {event.preventDefault();
let res = await axios.post("api/calcPersonTax", {
calcType: props.calcType,
income: income,
});
if (res != null) {
let data = res.data;
setTaxableIncome(data.taxableIncome);
setTaxRate(data.taxRate);
setTax(data.tax);
setTakeHomeSalary(data.takeHomeSalary);
}
}
function reset(event) {event.preventDefault();
setIncome("");
setTaxableIncome("");
setTaxRate("");
setTax("");
setTakeHomeSalary("");
}
return (
<div>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='primary' size="small"
label={props.calcType} onChange={e => setIncome(e.target.value)}
value={income} fullWidth required />
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<Button variant="outlined" color="primary" onClick={calculateTax} fullWidth size="large"> 计算 </Button>
<Button variant="outlined" color="secondary" onClick={reset} fullWidth size="large"> 重置 </Button>
</Stack>
<hr></hr>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="应征税所得额" value={taxableIncome} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="税率" value={taxRate} fullWidth disabled />
</Stack>
<Stack spacing={2} direction="row" sx={{marginBottom: 2}}>
<TextField type="text" variant='outlined' color='secondary' size="small"
label="应纳税额" value={tax} fullWidth disabled />
<TextField type="text" variant='outlined' color='secondary' size="small"
label="税后工资" value={takeHomeSalary} fullWidth disabled />
</Stack>
</div>
)
}
此时,实现 UI 局部后,能够尝试运行起来,成果如下:
// 通过代码运行 React app
npm start
能够试着填一些数据,然而当咱们点击计算时会报错,这是因为服务端还没有筹备好。
前端申请局部
相熟 Axios 的同学能够跳过这部分,后面的代码里,曾经给出了 Axois 发送申请的代码。
能够看到无论是哪一种类型的组件,申请都发送到了雷同的 url(”api/calcPersonTax”),以 SalaryIncome 为例,代码如下:
async function calculateTax(event) {event.preventDefault();
let res = await axios.post("api/calcPersonTax", {
calcType: props.calcType,
income: income,
insurance: insurance,
childEdu: childEdu,
selfEdu: selfEdu,
treatment: treatment,
loans: loans,
rent: rent,
elder: elder,
});
if (res != null) {
let data = res.data;
setTaxableIncome(data.taxableIncome);
setTaxRate(data.taxRate);
setDeduction(data.deduction);
setTax(data.tax);
setTakeHomeSalary(data.takeHomeSalary);
}
}
能够看到,整个申请变得非常简单,次要是把 state 的值取出来,通过 post 申请发送到服务端,而后依据返回值,把数据从新设给 state,这样就实现 UI 数据的更新了。
配置申请转发中间件
咱们在申请时拜访的是绝对地址,React 自身有一个 nodeJS,默认的端口是 3000,而 Spring Boot 的默认端口是 8080。前端间接拜访会有跨域的问题,因而咱们要做一个代理的配置。
在 src 文件夹上面增加文件,名为 setupProxy.js,代码如下:
const {createProxyMiddleware} = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:8080',
changeOrigin: true,
})
);
};
服务端 Spring Boot
创立工程及增加依赖
应用 IDEA 创立一个 Spring Boot 工程,如果应用的是社区(community)版本,不能间接创立 Spring Boot 我的项目,那能够先创立一个空我的项目,idea 创立 project 的过程,就跳过了,这里咱们以创立了一个 gradle 我的项目为例。
plugins {
id 'org.springframework.boot' version '3.0.0'
id 'io.spring.dependency-management' version '1.1.0'
id 'java'
id 'war'
}
group = 'org.example'
version = '1.0-SNAPSHOT'
repositories {mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.grapecity.documents:gcexcel:6.2.0'
implementation 'javax.json:javax.json-api:1.1.4'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation('org.springframework.boot:spring-boot-starter-test')
}
test {useJUnitPlatform()
}
在 dependencies 中,咱们除了依赖 Spring Boot 之外,还增加了 GcExcel 的依赖,前面导出时会用到 GcExcel,目前的版本是 6.2.0。
增加 API
在 Application 类上,增加属性 @RequestMapping(“/api”).,并增加 calcPersonTax API。
@Spring BootApplication
@RestController
@RequestMapping("/api")
public class SalaryTaxCalculator {public static void main(String[] args) {SpringApplication.run(SalaryTaxCalculator.class, args);
}
@PostMapping("/calcPersonTax")
public CalcResult calcTax(@RequestBody CalcParameter par) {Workbook workbook = new Workbook();
workbook.open(GetResourcePath());
return CalcInternal(workbook, par);
}
private String GetResourcePath(){return Objects.requireNonNull(SalaryTaxCalculator.class.getClassLoader().getResource("PersonalTaxCalcEngine.xlsx")).getPath();}
private CalcResult CalcInternal(Workbook workbook, CalcParameter par) {//todo}
}
能够看到在 CalcInternal 办法内,咱们应用 GcExcel,依据 calcType 来判断应用哪一个 sheet 来进行计算。对不同 Sheet 只须要通过 GcExcel 设值,并从特定的格子里取值即可。
同时,咱们还须要创立两个类,CalcParameter 和 CalcResult。CalcParameter 用于从 request 中把 post 的 data 解析进去,CalcResult 用于在 response 中返回的数据。
CalcParameter:
public class CalcParameter {
public String calcType;
public double income;
public double insurance;
public double childEdu;
public double selfEdu;
public double treatment;
public double loans;
public double rent;
public double elder;
}
CalcResult:
public class CalcResult {
public double taxableIncome;
public double taxRate;
public double deduction;
public double tax;
public double takeHomeSalary;
public double monthlyWage;
public double nonTaxablePart;
}
应用 GcExcel 实现公式计算
后面咱们定义了 CalcInternal,在 CalcInternal 中,咱们须要应用 GcExcel 来实现公式计算。
GcExcel 的公式计算是主动实现的,咱们应用 workbook 关上 Excel 文件后,只须要 set 相干的 value。之后在取值时,GcExcel 会主动计算响应公式的值。
private CalcResult CalcInternal(Workbook workbook, CalcParameter par) {var result = new CalcResult();
var sheet = workbook.getWorksheets().get(par.calcType);
switch (par.calcType) {
case "工资薪金所得" -> {sheet.getRange("B1").setValue(par.income);
sheet.getRange("D1").setValue(par.insurance);
sheet.getRange("B2").setValue(par.childEdu);
sheet.getRange("D2").setValue(par.selfEdu);
sheet.getRange("B3").setValue(par.treatment);
sheet.getRange("D3").setValue(par.loans);
sheet.getRange("B4").setValue(par.rent);
sheet.getRange("D4").setValue(par.elder);
result.taxableIncome = (double) sheet.getRange("B9").getValue();
result.taxRate = (double) sheet.getRange("D9").getValue();
result.deduction = (double) sheet.getRange("B10").getValue();
result.tax = (double) sheet.getRange("D10").getValue();
result.takeHomeSalary = (double) sheet.getRange("B11").getValue();}
case "年终奖所得" -> {sheet.getRange("B1").setValue(par.income);
result.taxableIncome = (double) sheet.getRange("B3").getValue();
result.taxRate = (double) sheet.getRange("D3").getValue();
result.deduction = (double) sheet.getRange("B4").getValue();
result.monthlyWage = (double) sheet.getRange("D4").getValue();
result.tax = (double) sheet.getRange("B5").getValue();
result.takeHomeSalary = (double) sheet.getRange("D5").getValue();}
case "劳务报酬所得" -> {sheet.getRange("B1").setValue(par.income);
result.taxableIncome = (double) sheet.getRange("B3").getValue();
result.taxRate = (double) sheet.getRange("D3").getValue();
result.deduction = (double) sheet.getRange("B4").getValue();
result.nonTaxablePart = (double) sheet.getRange("D4").getValue();
result.tax = (double) sheet.getRange("B5").getValue();
result.takeHomeSalary = (double) sheet.getRange("D5").getValue();}
case "个体工商户、生产经营所得" -> {sheet.getRange("B1").setValue(par.income);
result.taxableIncome = (double) sheet.getRange("B3").getValue();
result.taxRate = (double) sheet.getRange("D3").getValue();
result.deduction = (double) sheet.getRange("B4").getValue();
result.tax = (double) sheet.getRange("D4").getValue();
result.takeHomeSalary = (double) sheet.getRange("B5").getValue();}
default -> {sheet.getRange("B1").setValue(par.income);
result.taxableIncome = (double) sheet.getRange("B3").getValue();
result.taxRate = (double) sheet.getRange("D3").getValue();
result.tax = (double) sheet.getRange("B4").getValue();
result.takeHomeSalary = (double) sheet.getRange("D4").getValue();}
}
return result;
}
这样就实现了服务端的代码。
最终成果
咱们能够应用工资薪金所得试验一下,能够看到数据被计算出来了。因为目标是为了分享服务端公式计算的计划,所以计算的后果是否正确,就不做粗疏思考。
总结
个税计算的场景并不简单,次要是通过 Excel 实现公式计算即可,在服务端应用 GcExcel 能够大幅度降低前后端的开发难度,零碎的搭建过程能够齐全不须要思考计算的逻辑。
在理论的公式计算场景中,可能往往会比个税计算的场景简单,借助 GcExcel 这样 Excel 组件库,能够很容易的把已有的 Excel 文件迁徙到线上,进步工作效率。
另外,本文中分享的代码并不是最符合实际工作中的要求,读者还能够从以下角度去优化本人的代码。
- 支出类型能够抽成枚举,这样保护和应用起来更容易。
- 目前每一个 react 组件里的冗余度还不低,还能够持续形象组件,防止反复写代码。
- 在服务端,因为公式计算的逻辑是不会变的,在理论场景中,也有可能同一时间要加载复数个 Excel 文件,能够思考把 workbook 常驻内存,来进步性能。
扩大链接:
高级 SQL 剖析函数 - 如何用窗口函数进行排名计算
3D 模型 +BI 剖析,打造全新的交互式 3D 可视化大屏开发计划
React + Springboot + Quartz,从 0 实现 Excel 报表自动化