查看: 143|回复: 0

Python Flask工资计算器累计预扣法与接口实现

[复制链接]
发表于 半小时前 | 显示全部楼层 |阅读模式
项目结构与计算难点
这个示例由两个 Python 文件组成:salary_funcs.py 负责五险一金和个税计算,calc_salary_ui.py 负责 Flask 接口与前端页面。工资计算并不只是“税前收入乘比例”,核心是累计预扣法:截至当月的累计收入先减去累计五险一金、累计专项附加扣除和每月5000元乘以月份数的累计免征额,再套年度税率表算出累计应缴个税,最后减去已预缴税额,得到当月应补税。

五险一金个人缴纳函数
个人缴费常见比例为养老8%、医疗2%、失业0.5%(部分城市0.2%),公积金在5%到12%之间。函数 calc_social_insurance 接收税前月薪、社保基数、公积金比例;若 base_salary 为0,则退化为使用当月工资作为基数。社保基数在真实业务中常取上年度月平均工资,所以要外部传入而不能写死。
  1. def calc_social_insurance(salary, base_salary, housing_fund_rate=0.12):
  2.     if base_salary == 0:
  3.         base_salary = salary
  4.     pension = base_salary * 0.08
  5.     medical = base_salary * 0.02
  6.     unemployment = base_salary * 0.005
  7.     housing_fund = base_salary * housing_fund_rate
  8.     total = pension + medical + unemployment + housing_fund
  9.     return {
  10.         'pension': round(pension, 2),
  11.         'medical': round(medical, 2),
  12.         'unemployment': round(unemployment, 2),
  13.         'housing_fund': round(housing_fund, 2),
  14.         'total': round(total, 2)
  15.     }
复制代码

这里 housing_fund_rate 默认0.12,可按公司实际改为0.05、0.07或0.10。返回字典包含养老、医疗、失业、公积金和合计,便于前端展示。

累计预扣法个税函数
个税函数最容易出错的地方是免征额。原文先给出一个按月免征额参数的设计,随后指出:如果每次调用都传5000,累计到第二个月仍然只减5000,结果就错了。正确做法是传入 cumulative_months,在函数内部计算 5000 * cumulative_months,或者由调用方传入 5000 乘当前月份数。
  1. def calc_monthly_tax(cumulative_income, cumulative_insurance, cumulative_deduction,
  2.                      pre_paid_tax, cumulative_months):
  3.     tax_income = cumulative_income - cumulative_insurance - cumulative_deduction - 5000 * cumulative_months
  4.     tax_income = max(tax_income, 0)
  5.     brackets = [
  6.         (36000, 0.03, 0),
  7.         (144000, 0.10, 2520),
  8.         (300000, 0.20, 16920),
  9.         (420000, 0.25, 31920),
  10.         (660000, 0.30, 52920),
  11.         (960000, 0.35, 85920),
  12.         (float('inf'), 0.45, 181920)
  13.     ]
  14.     payable_tax = 0
  15.     for limit, rate, quick_deduction in brackets:
  16.         if tax_income <= limit:
  17.             payable_tax = tax_income * rate - quick_deduction
  18.             break
  19.     monthly_tax = max(payable_tax - pre_paid_tax, 0)
  20.     return round(monthly_tax, 2)
复制代码

税率表按年度累计所得分七档:36000以内3%、144000以内10%、300000以内20%、420000以内25%、660000以内30%、960000以内35%,超过部分45%,并分别对应速算扣除数0、2520、16920、31920、52920、85920、181920。cumulative_deduction 表示截至当月累计的专项附加扣除和其他扣除,不是单月值;若中途变更,必须传入累计后的值。

用数据类收拢收入参数
收入不止基本工资,还可能包括绩效奖金、加班费、津贴、补发工资等。为了避免函数参数膨胀,可以用 dataclass 定义 SalaryInput,把字段集中在 API 层映射。
  1. from dataclasses import dataclass
  2. @dataclass
  3. class SalaryInput:
  4.     basic: float
  5.     bonus: float = 0
  6.     overtime: float = 0
  7.     allowance: float = 0
  8.     social_base: float = 0
  9.     housing_rate: float = 0.12
  10.     deductions: float = 0
复制代码

基本工资 basic 必填,其余字段都有默认值。这样单元测试只需写关键字段,后续增加“年终奖单独计税”时,也可以在另一个函数中处理 bonus,而不影响综合所得逻辑。

Flask 接口设计
后端把计算函数暴露成 POST /api/calculate,请求体用 JSON,字段名与 SalaryInput 一致。pre_data 保存截至上月的累计月份和已预缴税。示例中为了演示没有接数据库,所以每次调用需要前端把已缴税额传回来;生产环境应改为数据库或会话存储。
  1. from flask import Flask, request, jsonify, render_template_string
  2. from salary_funcs import calc_social_insurance, calc_monthly_tax
  3. app = Flask(__name__)
  4. def compute_month(salary_input, pre_data):
  5.     insurance = calc_social_insurance(
  6.         salary_input.basic + salary_input.bonus,
  7.         salary_input.social_base,
  8.         salary_input.housing_rate
  9.     )
  10.     taxable_income = (salary_input.basic + salary_input.bonus + salary_input.overtime
  11.                       + salary_input.allowance) - insurance['total']
  12.     monthly_tax = calc_monthly_tax(
  13.         cumulative_income=taxable_income * pre_data['months'],
  14.         cumulative_insurance=insurance['total'] * pre_data['months'],
  15.         cumulative_deduction=salary_input.deductions * pre_data['months'],
  16.         pre_paid_tax=pre_data['paid_tax'],
  17.         cumulative_months=pre_data['months']
  18.     )
  19.     net_salary = taxable_income - monthly_tax
  20.     return {
  21.         'insurance': insurance['total'],
  22.         'tax': monthly_tax,
  23.         'net_salary': round(net_salary, 2)
  24.     }
  25. @app.route('/api/calculate', methods=['POST'])
  26. def calculate():
  27.     data = request.get_json()
  28.     salary_input = SalaryInput(
  29.         basic=float(data.get('basic', 0)),
  30.         bonus=float(data.get('bonus', 0)),
  31.         overtime=float(data.get('overtime', 0)),
  32.         allowance=float(data.get('allowance', 0)),
  33.         social_base=float(data.get('social_base', 0)),
  34.         housing_rate=float(data.get('housing_rate', 0.12)),
  35.         deductions=float(data.get('deductions', 0))
  36.     )
  37.     pre_data = {
  38.         'months': int(data.get('months', 1)),
  39.         'paid_tax': float(data.get('paid_tax', 0))
  40.     }
  41.     result = compute_month(salary_input, pre_data)
  42.     return jsonify(result)
复制代码

注意示例中的 compute_month 先算 insurance,再把 taxable_income 传入 calc_monthly_tax,同时又把 insurance 作为 cumulative_insurance 传入。如果 taxable_income 已经扣过五险一金,就会重复扣除;更清晰的划分是让 cumulative_income 传累计总收入,累计五险一金单独传,或者把已扣保险的金额与累计保险参数二选一。这个细节在二次开发时要特别检查。

前端页面与 fetch 交互
calc_salary_ui.py 可以用 render_template_string 直接在 Python 字符串里维护 HTML,不需要单独创建 templates 目录。表单字段建议分三组:收入信息(基本工资、绩效奖金、加班费、津贴)、社保公积金(社保基数、公积金比例)、累计信息(当前月份、当年已缴个税、专项附加扣除)。公积金比例用下拉框限制为5%、7%、10%、12%,减少无效输入。

提交时不要整页刷新,用 fetch 发 POST,然后把 JSON 渲染成表格。renderResult 中可以把五险一金个人缴纳、当月个税、到手工资、累计个税逐行放入 table,比直接拼 innerHTML 更清晰。
  1. form.addEventListener('submit', async (e) => {
  2.   e.preventDefault();
  3.   const data = Object.fromEntries(new FormData(form));
  4.   try {
  5.     const res = await fetch('/api/calculate', {
  6.       method: 'POST',
  7.       body: JSON.stringify(data),
  8.       headers: {'Content-Type': 'application/json'}
  9.     });
  10.     if (!res.ok) {
  11.       const error = await res.json();
  12.       throw new Error(error.message || '接口错误');
  13.     }
  14.     const result = await res.json();
  15.     renderResult(result);
  16.   } catch (err) {
  17.     document.getElementById('errorBox').textContent = '计算失败: ' + err.message;
  18.   }
  19. });
复制代码

参数校验与常见排错
前端能拦空值和类型错误,但后端必须做最终检查。月份必须在1到12之间,公积金比例必须在0.05到0.12之间,请求体必须是 JSON。不符合时返回 HTTP 400 和中文错误信息,前端根据 res.ok 捕获并显示。
  1. months = int(data.get('months', 1))
  2. if months < 1 or months > 12:
  3.     return jsonify({'message': '月份必须在1到12之间'}), 400
  4. housing_rate = float(data.get('housing_rate', 0.12))
  5. if housing_rate < 0.05 or housing_rate > 0.12:
  6.     return jsonify({'message': '公积金比例需在0.05到0.12之间'}), 400
复制代码

常见问题包括:FormData 取出的值都是字符串,后端要用 float 或 int 转换;已缴税字段首次使用填0,连续计算时要把上月输出中的累计个税回填;专项附加扣除如果每月不同,不能简单用 deductions * months,而要传累计值;社保基数上下限、失业保险比例、公积金比例各地不同,需要在 calc_social_insurance 外先确定缴费基数。

适用场景与局限
这个工资计算器适合学习累计预扣法、做 HR/财务薪酬测算原型,或嵌入已有管理系统做二次开发。它没有数据库,状态需要手动传递;税率表、五险一金比例和基数上下限属于示例参数,真实使用要按当地政策和公司口径替换。Flask 在这里承担轻量 API 和页面渲染,不引入额外前端框架,也方便继续改造成前后端分离接口。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-14 13:40 , Processed in 0.023329 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部