Java + React を使用して個人所得税を計算するにはどうすればよいですか?

序文

レポートデータ処理において、Excel の数式は強力かつ多彩な機能を備え、さまざまなビジネス分野で広く使用されています。投資収益の計算、財務諸表の作成、保険収入の見積りのいずれにおいても、Excel の数式は不可欠な役割を果たします。従来のアプローチでは、Excel に直接依存して複雑なビジネス ロジックを実装し、対応する Excel ファイルを生成します。したがって、事前設定された場所に対応するパラメータを入力するだけで、Excel の数式がアクティブになり、結果がすぐに計算されて表示されます。このため、この種のシナリオでは、企業は計算に使用される大量の Excel ファイルを蓄積し、それらは貴重な富となります。

ただし、従来の Excel ファイル方式には、管理が難しく、データの安全性が低いという欠点があります。これらの問題を解決するには、B/S アーキテクチャ + Excel コンポーネント ライブラリを使用できます。

この記事では個人所得税の計算を例に、React+Spring Boot+GcExcelを使って実装していきます。まずExcelファイルを用意し、国家税務総局が提供する個人税計算ページに従って作成します。

個人所得税の所得には次の8種類があります。

  • 給与収入
  • 年末賞与収入
  • 労働報酬による収入
  • 個々の工業および商業世帯、生産および運営からの収入
  • 報酬
  • チャンス収入
  • 利息、配当金、賞与収入
  • 不動産譲渡による収益

その中でも、社会保険や特別控除など、給与や給与所得は最も複雑です。税金の種類ごとに計算方法が異なります。わかりやすくするために、種類ごとに計算するためのワークシートを作成しました。

以下は用意したExcelファイルです。青い部分が入力パラメータが必要なセルで、それ以外のセルは自動計算されます。

準備作業が完了したら、フロントエンドプロジェクトとバックエンドプロジェクトの構築を開始します。

練習する

フロントエンド反応

Reactプロジェクトを作成する

TaxCalculator などの新しいフォルダーを作成し、そのフォルダーに入り、リソース マネージャーのアドレス バーに「cmd」と入力し、Enter キーを押してコマンド ライン ウィンドウを開きます。以下のコードを使用して、client-app という名前の反応アプリを作成します。

npx create-react-app salary-client

作成したばかりの給与クライアント フォルダーに入り、Visual Studio Code などの IDE を使用してフォルダーを開きます。

インターフェイス部

個人所得税に関わる所得は8種類あり、そのうち4種類(「報酬所得」、「雑所得」、「利子、配当、賞与所得」、「不動産譲渡所得」)は同様の計算方法、UIとなっています。 React のコンポーネント機能は、最終的に 5 つのフォーム インターフェイスを提供する必要があります。

以下に示すように:

UI の見栄えを良くするには、まず UI フレームワークを導入します。ここでは MUI を使用します。

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

まず、Src/App.js のコードを更新し、DarkMode テーマを追加します。コードは次のとおりです。

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 は主に、ユーザーが収入の種類を選択し、選択した種類に応じてさまざまなコンポーネントをレンダリングできるようにするセレクターを提供します。

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. 残りの4種類 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/calcPersonalTax」) に送信されることがわかります。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);
        }
    }

ご覧のとおり、リクエスト全体は非常に単純になっており、主に状態の値を取り出し、ポストリクエストを通じてサーバーに送信し、戻り値に基づいてデータを状態にリセットすることで更新が完了します。 UIデータの。

リクエスト転送ミドルウェアの構成

リクエスト時にアクセスするのは相対アドレスですが、React自体にはnodeJSがあり、デフォルトのポートは3000、Spring Bootのデフォルトのポートは8080です。フロントエンドに直接アクセスするとクロスドメインの問題が発生するため、プロキシを構成する必要があります。

次のコードを含むファイルを setupProxy.js という名前の src フォルダーの下に追加します。

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 プロジェクトを作成します。コミュニティ バージョンを使用していて、Spring Boot プロジェクトを直接作成できない場合は、最初に空のプロジェクトを作成できます。IDEA でプロジェクトを作成するプロセスはスキップされます。ここでは、Take the 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()
}

依存関係では、Spring Boot に依存することに加えて、GcExcel への依存関係も追加しました。GcExcel は、後でエクスポートするときに使用されます。現在のバージョンは 6.2.0 です。

APIの追加

Application クラスに属性 @RequestMapping("/api") を追加し、calcPersonalTax 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 に基づいて計算に使用するシートを決定していることがわかります。異なるシートの場合は、GcExcel を使用して値を設定し、特定のグリッドから値を取得するだけで済みます。

同時に、CalcParameter と CalcResult という 2 つのクラスも作成する必要があります。CalcParameter はリクエストからの投稿データを解析するために使用され、CalcResult はレスポンスでデータを返すために使用されます。

計算パラメータ:

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

計算結果:

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 の数式計算は自動的に完了するため、ワークブックを使用して Excel ファイルを開いた後は、関連する値を設定するだけです。後で値を取得すると、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 ファイルをオンラインで簡単に移行でき、作業効率が向上します。

また、この記事で共有するコードは実際の作業の要件を最適に満たすものではありませんが、読者は次の観点から独自のコードを最適化することもできます。

  1. 収入タイプを列挙できるため、管理と使用が容易になります。
  2. 現時点では、各 React コンポーネントの冗長性は低くはなく、コードの繰り返し記述を避けるためにコンポーネントを抽象化し続けることができます。
  3. サーバー側では数式計算のロジックが変わらないため、実際のシナリオでは複数の Excel ファイルを同時に読み込むことが可能であり、パフォーマンスを向上させるためにワークブックをメモリ上に常駐させることも検討できます。

拡張リンク:

高度なSQL分析機能~ランキング計算にウィンドウ関数を使う方法~

3D モデル + BI 分析により、新しいインタラクティブ 3D ビジュアライゼーション大画面開発ソリューションを作成

React + Springboot + Quartz、Excelレポート自動化を0から実現

おすすめ

転載: blog.csdn.net/powertoolsteam/article/details/132605300