How to calculate personal income tax using Java + React?

Preface

In report data processing, Excel formulas have powerful and diverse functions and are widely used in various business fields. Whether it is investment income calculation, financial statement preparation or insurance income estimation, Excel formulas play an indispensable role. The traditional approach is to rely directly on Excel to implement complex business logic and generate corresponding Excel files. Therefore, just enter the corresponding parameters at the preset location, and the Excel formula can be activated, quickly calculating and presenting the results. Because of this, in this type of scenario, companies have accumulated a large number of Excel files used for calculations, and they have become priceless wealth.

However, the traditional Excel file method has the disadvantages of being difficult to manage and data insecurity. In order to solve these problems, the B/S architecture + Excel component library can be used.

This article will take the calculation of personal income tax as an example and use React+Spring Boot+GcExcel to implement it. First prepare the Excel file and create it according to the individual tax calculation page provided by the State Administration of Taxation.

There are 8 types of income for personal income tax:

  • salary income
  • Year-end bonus income
  • Income from labor remuneration
  • Individual industrial and commercial households, income from production and operations
  • Remuneration
  • chance income
  • Interest, dividends, bonus income
  • Proceeds from property transfer

Among them, wages and salary income are the most complicated, including social insurance and special deductions. Each type of tax is calculated differently, and to make it easier to understand, we've created a worksheet for each type to calculate.

The following is the prepared Excel file, in which the blue parts are cells that require input parameters, and other cells will be automatically calculated.

After completing the preparation work, start building the front-end and back-end projects.

practice

FrontendReact

Create React project

Create a new folder, such as TaxCalculator, enter the folder, enter cmd in the address bar of the resource manager, and then press Enter to open the command line window. Use the code below to create a react app named client-app.

npx create-react-app salary-client

Enter the salary-client folder you just created and use an IDE, such as Visual Studio Code, to open the folder.

Interface part

There are 8 types of income involved in personal income tax. Four of them ("remuneration income", "accidental income", "interest, dividend, bonus income", "property transfer income") are calculated in similar ways and have similar UI layouts. With the help of The component feature of React ultimately needs to provide 5 form interfaces.

As shown below:

In order to make the UI look better, you can first introduce a UI framework. Here we use MUI.

npm install @mui/material @emotion/react @emotion/styled

First, update the code of Src/App.js and add the DarkMode Theme. The code is as follows:

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;

You can see that FormContainer is referenced in App.js, and add ./Component/FormContainer.js.

FormContainer mainly provides a Selector to allow users to select the income type and render different components according to the selected type.

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>);
}

For example: <SalaryIncome calcType={calcType}></SalaryIncome>; calcType will be passed in at the same time.

Next, create several xxxIncome components respectively.

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. Year-end bonus income 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. Income from labor remuneration 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.Income from individual industrial and commercial households and production and operations 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. The remaining four types 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>
    )
}

At this point, after completing the UI part, you can try to run it. The effect is as follows:

//通过代码运行React app
npm start

You can try to fill in some data, but when we click Calculate, an error will be reported. This is because the server is not ready yet.

Front-end request part

Students who are familiar with Axios can skip this part. In the previous code, the code for Axois to send the request has been given.

You can see that no matter which type of component it is, the request is sent to the same url ("api/calcPersonTax"). Taking SalaryIncome as an example, the code is as follows:

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);
        }
    }

As you can see, the entire request becomes very simple. It mainly takes out the value of the state, sends it to the server through a post request, and then resets the data to the state based on the return value, thus completing the update of the UI data.

Configure request forwarding middleware

What we access when requesting is a relative address. React itself has a nodeJS, the default port is 3000, and the default port of Spring Boot is 8080. Direct front-end access will cause cross-domain problems, so we need to configure a proxy.

Add a file under the src folder named setupProxy.js with the following code:

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:8080',
      changeOrigin: true,
    })
  );
};

Server-side Spring Boot

Create a project and add dependencies

Use IDEA to create a Spring Boot project. If you are using the community version and cannot create a Spring Boot project directly, you can create an empty project first. The process of creating the project in IDEA is skipped. Here we create a Take the gradle project as an example.

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()
}

In dependencies, in addition to relying on Spring Boot, we also added a dependency on GcExcel. GcExcel will be used when exporting later. The current version is 6.2.0.

Add API

On the Application class, add the attribute @RequestMapping("/api")., and add the 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
    }
}

You can see that in the CalcInternal method, we use GcExcel to determine which sheet to use for calculation based on calcType. For different Sheets, you only need to set the value through GcExcel and get the value from a specific grid.

At the same time, we also need to create two classes, CalcParameter and CalcResult. CalcParameter is used to parse the post data from the request, and CalcResult is used to return the data in the 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;
}

Use GcExcel to complete formula calculations

We defined CalcInternal earlier. In CalcInternal, we need to use GcExcel to complete formula calculations.

GcExcel's formula calculation is completed automatically. After we use the workbook to open the Excel file, we only need to set the relevant value. Later, when taking the value, GcExcel will automatically calculate the value of the response formula.

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;
    }

This completes the server-side code.

final effect

We can experiment using wages and salaries and see that the data is calculated. Because the purpose is to share the server-side formula calculation solution, we will not consider in detail whether the calculation results are correct.

Summarize

The scenario of individual tax calculation is not complicated. It mainly requires completing the formula calculation through Excel. Using GcExcel on the server side can greatly reduce the difficulty of front-end and back-end development. The system construction process does not need to consider the calculation logic at all.

In actual formula calculation scenarios, it may often be more complicated than personal tax calculation scenarios. With the help of Excel component libraries such as GcExcel, existing Excel files can be easily migrated online to improve work efficiency.

In addition, the code shared in this article does not best meet the requirements of actual work. Readers can also optimize their own code from the following perspectives.

  1. Income types can be enumerated, making them easier to maintain and use.
  2. At present, the redundancy in each react component is not low, and we can continue to abstract components to avoid repeated code writing.
  3. On the server side, because the logic of formula calculation will not change, in actual scenarios, it is possible to load multiple Excel files at the same time. You can consider making the workbook resident in memory to improve performance.

Extension link:

Advanced SQL analysis functions-how to use window functions for ranking calculations

3D model + BI analysis to create a new interactive 3D visualization large-screen development solution

React + Springboot + Quartz, realize Excel report automation from 0

Guess you like

Origin blog.csdn.net/powertoolsteam/article/details/132605300