浏览代码

可支配收入数据填报

wwh 2 周之前
父节点
当前提交
d4eb5f1d6e
共有 14 个文件被更改,包括 802 次插入0 次删除
  1. 81 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/controller/BizDisposableIncomeController.java
  2. 90 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/domain/BizDisposableIncome.java
  3. 25 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/mapper/BizDisposableIncomeMapper.java
  4. 22 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/service/IBizDisposableIncomeService.java
  5. 126 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/service/impl/BizDisposableIncomeServiceImpl.java
  6. 40 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/DisposableIncomeGrowthSupport.java
  7. 52 0
      baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/DisposableIncomeValidation.java
  8. 73 0
      baqing-admin/src/main/resources/mapper/industryservice/BizDisposableIncomeMapper.xml
  9. 53 0
      baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/support/DisposableIncomeGrowthSupportTest.java
  10. 78 0
      baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/support/DisposableIncomeValidationTest.java
  11. 44 0
      doc/产业数据模型及服务/可支配收入数据填报/可支配收入数据填报功能需求.md
  12. 31 0
      doc/产业数据模型及服务/可支配收入数据填报/可支配收入数据填报技术方案.md
  13. 72 0
      doc/产业数据模型及服务/可支配收入数据填报/可支配收入数据填报测试用例.md
  14. 15 0
      sql/biz_disposable_income.sql

+ 81 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/controller/BizDisposableIncomeController.java

@@ -0,0 +1,81 @@
1
+package com.ruoyi.web.modules.industryservice.controller;
2
+
3
+import java.util.List;
4
+import org.springframework.beans.factory.annotation.Autowired;
5
+import org.springframework.security.access.prepost.PreAuthorize;
6
+import org.springframework.validation.annotation.Validated;
7
+import org.springframework.web.bind.annotation.DeleteMapping;
8
+import org.springframework.web.bind.annotation.GetMapping;
9
+import org.springframework.web.bind.annotation.PathVariable;
10
+import org.springframework.web.bind.annotation.PostMapping;
11
+import org.springframework.web.bind.annotation.PutMapping;
12
+import org.springframework.web.bind.annotation.RequestBody;
13
+import org.springframework.web.bind.annotation.RequestMapping;
14
+import org.springframework.web.bind.annotation.RestController;
15
+import com.ruoyi.common.annotation.Log;
16
+import com.ruoyi.common.core.controller.BaseController;
17
+import com.ruoyi.common.core.domain.AjaxResult;
18
+import com.ruoyi.common.core.page.TableDataInfo;
19
+import com.ruoyi.common.enums.BusinessType;
20
+import com.ruoyi.web.modules.industryservice.domain.BizDisposableIncome;
21
+import com.ruoyi.web.modules.industryservice.service.IBizDisposableIncomeService;
22
+
23
+/**
24
+ * 可支配收入数据填报
25
+ */
26
+@RestController
27
+@RequestMapping("/dataModel/disposableIncome")
28
+public class BizDisposableIncomeController extends BaseController
29
+{
30
+    @Autowired
31
+    private IBizDisposableIncomeService bizDisposableIncomeService;
32
+
33
+    @PreAuthorize("@ss.hasPermi('dataModel:disposableIncome:list')")
34
+    @GetMapping("/list")
35
+    public TableDataInfo list(BizDisposableIncome query)
36
+    {
37
+        startPage();
38
+        List<BizDisposableIncome> list = bizDisposableIncomeService.selectBizDisposableIncomeList(query);
39
+        return getDataTable(list);
40
+    }
41
+
42
+    @PreAuthorize("@ss.hasPermi('dataModel:disposableIncome:query')")
43
+    @GetMapping("/{id}")
44
+    public AjaxResult getInfo(@PathVariable("id") Long id)
45
+    {
46
+        return success(bizDisposableIncomeService.selectBizDisposableIncomeById(id));
47
+    }
48
+
49
+    @PreAuthorize("@ss.hasPermi('dataModel:disposableIncome:add')")
50
+    @Log(title = "可支配收入数据填报", businessType = BusinessType.INSERT)
51
+    @PostMapping
52
+    public AjaxResult add(@Validated @RequestBody BizDisposableIncome row)
53
+    {
54
+        row.setCreateBy(getUsername());
55
+        return toAjax(bizDisposableIncomeService.insertBizDisposableIncome(row));
56
+    }
57
+
58
+    @PreAuthorize("@ss.hasPermi('dataModel:disposableIncome:edit')")
59
+    @Log(title = "可支配收入数据填报", businessType = BusinessType.UPDATE)
60
+    @PutMapping
61
+    public AjaxResult edit(@Validated @RequestBody BizDisposableIncome row)
62
+    {
63
+        row.setUpdateBy(getUsername());
64
+        return toAjax(bizDisposableIncomeService.updateBizDisposableIncome(row));
65
+    }
66
+
67
+    @PreAuthorize("@ss.hasPermi('dataModel:disposableIncome:remove')")
68
+    @Log(title = "可支配收入数据填报", businessType = BusinessType.DELETE)
69
+    @DeleteMapping("/{ids}")
70
+    public AjaxResult remove(@PathVariable Long[] ids)
71
+    {
72
+        return toAjax(bizDisposableIncomeService.deleteBizDisposableIncomeByIds(ids, getUsername()));
73
+    }
74
+
75
+    @PreAuthorize("@ss.hasPermi('dataModel:disposableIncome:list')")
76
+    @GetMapping("/yearOptions")
77
+    public AjaxResult yearOptions()
78
+    {
79
+        return success(bizDisposableIncomeService.listYearOptions());
80
+    }
81
+}

+ 90 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/domain/BizDisposableIncome.java

@@ -0,0 +1,90 @@
1
+package com.ruoyi.web.modules.industryservice.domain;
2
+
3
+import java.math.BigDecimal;
4
+import javax.validation.constraints.NotNull;
5
+import com.ruoyi.common.core.domain.BaseEntity;
6
+
7
+/**
8
+ * 可支配收入数据填报 biz_disposable_income
9
+ */
10
+public class BizDisposableIncome extends BaseEntity
11
+{
12
+    private static final long serialVersionUID = 1L;
13
+
14
+    private Long id;
15
+
16
+    @NotNull(message = "所属年份不能为空")
17
+    private Integer statYear;
18
+
19
+    /** 农村居民人均可支配收入(元) */
20
+    private BigDecimal ruralDisposableIncome;
21
+
22
+    /** 脱贫人口人均纯收入(元) */
23
+    private BigDecimal povertyNetIncome;
24
+
25
+    /** 增幅(元)= 本年可支配收入 - 上年可支配收入,非落库 */
26
+    private BigDecimal incomeIncrease;
27
+
28
+    /** 增速 = 增幅 / 上年可支配收入,非落库 */
29
+    private BigDecimal incomeGrowthRate;
30
+
31
+    public Long getId()
32
+    {
33
+        return id;
34
+    }
35
+
36
+    public void setId(Long id)
37
+    {
38
+        this.id = id;
39
+    }
40
+
41
+    public Integer getStatYear()
42
+    {
43
+        return statYear;
44
+    }
45
+
46
+    public void setStatYear(Integer statYear)
47
+    {
48
+        this.statYear = statYear;
49
+    }
50
+
51
+    public BigDecimal getRuralDisposableIncome()
52
+    {
53
+        return ruralDisposableIncome;
54
+    }
55
+
56
+    public void setRuralDisposableIncome(BigDecimal ruralDisposableIncome)
57
+    {
58
+        this.ruralDisposableIncome = ruralDisposableIncome;
59
+    }
60
+
61
+    public BigDecimal getPovertyNetIncome()
62
+    {
63
+        return povertyNetIncome;
64
+    }
65
+
66
+    public void setPovertyNetIncome(BigDecimal povertyNetIncome)
67
+    {
68
+        this.povertyNetIncome = povertyNetIncome;
69
+    }
70
+
71
+    public BigDecimal getIncomeIncrease()
72
+    {
73
+        return incomeIncrease;
74
+    }
75
+
76
+    public void setIncomeIncrease(BigDecimal incomeIncrease)
77
+    {
78
+        this.incomeIncrease = incomeIncrease;
79
+    }
80
+
81
+    public BigDecimal getIncomeGrowthRate()
82
+    {
83
+        return incomeGrowthRate;
84
+    }
85
+
86
+    public void setIncomeGrowthRate(BigDecimal incomeGrowthRate)
87
+    {
88
+        this.incomeGrowthRate = incomeGrowthRate;
89
+    }
90
+}

+ 25 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/mapper/BizDisposableIncomeMapper.java

@@ -0,0 +1,25 @@
1
+package com.ruoyi.web.modules.industryservice.mapper;
2
+
3
+import java.util.List;
4
+import org.apache.ibatis.annotations.Param;
5
+import com.ruoyi.web.modules.industryservice.domain.BizDisposableIncome;
6
+
7
+/**
8
+ * 可支配收入数据填报 Mapper
9
+ */
10
+public interface BizDisposableIncomeMapper
11
+{
12
+    BizDisposableIncome selectBizDisposableIncomeById(Long id);
13
+
14
+    BizDisposableIncome selectByStatYear(@Param("statYear") Integer statYear);
15
+
16
+    int countByStatYearExcludeId(@Param("statYear") Integer statYear, @Param("excludeId") Long excludeId);
17
+
18
+    List<BizDisposableIncome> selectBizDisposableIncomeList(BizDisposableIncome query);
19
+
20
+    int insertBizDisposableIncome(BizDisposableIncome row);
21
+
22
+    int updateBizDisposableIncome(BizDisposableIncome row);
23
+
24
+    int deleteBizDisposableIncomeByIds(@Param("ids") Long[] ids);
25
+}

+ 22 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/service/IBizDisposableIncomeService.java

@@ -0,0 +1,22 @@
1
+package com.ruoyi.web.modules.industryservice.service;
2
+
3
+import java.util.List;
4
+import com.ruoyi.web.modules.industryservice.domain.BizDisposableIncome;
5
+
6
+/**
7
+ * 可支配收入数据填报
8
+ */
9
+public interface IBizDisposableIncomeService
10
+{
11
+    BizDisposableIncome selectBizDisposableIncomeById(Long id);
12
+
13
+    List<BizDisposableIncome> selectBizDisposableIncomeList(BizDisposableIncome query);
14
+
15
+    int insertBizDisposableIncome(BizDisposableIncome row);
16
+
17
+    int updateBizDisposableIncome(BizDisposableIncome row);
18
+
19
+    int deleteBizDisposableIncomeByIds(Long[] ids, String updateBy);
20
+
21
+    List<Integer> listYearOptions();
22
+}

+ 126 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/service/impl/BizDisposableIncomeServiceImpl.java

@@ -0,0 +1,126 @@
1
+package com.ruoyi.web.modules.industryservice.service.impl;
2
+
3
+import java.util.ArrayList;
4
+import java.util.List;
5
+import org.springframework.beans.factory.annotation.Autowired;
6
+import org.springframework.stereotype.Service;
7
+import org.springframework.transaction.annotation.Transactional;
8
+import com.ruoyi.common.exception.ServiceException;
9
+import com.ruoyi.web.modules.industryservice.domain.BizDisposableIncome;
10
+import com.ruoyi.web.modules.industryservice.mapper.BizDisposableIncomeMapper;
11
+import com.ruoyi.web.modules.industryservice.service.IBizDisposableIncomeService;
12
+import com.ruoyi.web.modules.industryservice.support.DisposableIncomeGrowthSupport;
13
+import com.ruoyi.web.modules.industryservice.support.DisposableIncomeValidation;
14
+import com.ruoyi.web.modules.industryservice.support.YakHerdInventoryYearSupport;
15
+
16
+/**
17
+ * 可支配收入数据填报服务实现
18
+ */
19
+@Service
20
+public class BizDisposableIncomeServiceImpl implements IBizDisposableIncomeService
21
+{
22
+    @Autowired
23
+    private BizDisposableIncomeMapper bizDisposableIncomeMapper;
24
+
25
+    @Override
26
+    public BizDisposableIncome selectBizDisposableIncomeById(Long id)
27
+    {
28
+        BizDisposableIncome row = bizDisposableIncomeMapper.selectBizDisposableIncomeById(id);
29
+        fillGrowth(row);
30
+        return row;
31
+    }
32
+
33
+    @Override
34
+    public List<BizDisposableIncome> selectBizDisposableIncomeList(BizDisposableIncome query)
35
+    {
36
+        List<BizDisposableIncome> list = bizDisposableIncomeMapper.selectBizDisposableIncomeList(query);
37
+        if (list != null)
38
+        {
39
+            for (BizDisposableIncome row : list)
40
+            {
41
+                fillGrowth(row);
42
+            }
43
+        }
44
+        return list;
45
+    }
46
+
47
+    @Override
48
+    @Transactional(rollbackFor = Exception.class)
49
+    public int insertBizDisposableIncome(BizDisposableIncome row)
50
+    {
51
+        DisposableIncomeValidation.validateRowForSave(row);
52
+        assertUniqueYear(row.getStatYear(), null);
53
+        return bizDisposableIncomeMapper.insertBizDisposableIncome(row);
54
+    }
55
+
56
+    @Override
57
+    @Transactional(rollbackFor = Exception.class)
58
+    public int updateBizDisposableIncome(BizDisposableIncome row)
59
+    {
60
+        if (row.getId() == null)
61
+        {
62
+            throw new ServiceException("主键不能为空");
63
+        }
64
+        DisposableIncomeValidation.validateRowForSave(row);
65
+        requireExistingRow(row.getId());
66
+        assertUniqueYear(row.getStatYear(), row.getId());
67
+        return bizDisposableIncomeMapper.updateBizDisposableIncome(row);
68
+    }
69
+
70
+    @Override
71
+    @Transactional(rollbackFor = Exception.class)
72
+    public int deleteBizDisposableIncomeByIds(Long[] ids, String updateBy)
73
+    {
74
+        if (ids == null || ids.length == 0)
75
+        {
76
+            return 0;
77
+        }
78
+        List<Long> cleaned = new ArrayList<>();
79
+        for (Long id : ids)
80
+        {
81
+            if (id != null)
82
+            {
83
+                cleaned.add(id);
84
+            }
85
+        }
86
+        if (cleaned.isEmpty())
87
+        {
88
+            return 0;
89
+        }
90
+        return bizDisposableIncomeMapper.deleteBizDisposableIncomeByIds(cleaned.toArray(new Long[0]));
91
+    }
92
+
93
+    @Override
94
+    public List<Integer> listYearOptions()
95
+    {
96
+        return YakHerdInventoryYearSupport.resolveYearOptions();
97
+    }
98
+
99
+    private void fillGrowth(BizDisposableIncome row)
100
+    {
101
+        if (row == null || row.getStatYear() == null)
102
+        {
103
+            return;
104
+        }
105
+        BizDisposableIncome previous = bizDisposableIncomeMapper.selectByStatYear(row.getStatYear() - 1);
106
+        DisposableIncomeGrowthSupport.fillGrowth(row, previous);
107
+    }
108
+
109
+    private void assertUniqueYear(Integer statYear, Long excludeId)
110
+    {
111
+        int count = bizDisposableIncomeMapper.countByStatYearExcludeId(statYear, excludeId);
112
+        if (count > 0)
113
+        {
114
+            throw new ServiceException("该年份数据已存在");
115
+        }
116
+    }
117
+
118
+    private void requireExistingRow(Long id)
119
+    {
120
+        BizDisposableIncome existing = bizDisposableIncomeMapper.selectBizDisposableIncomeById(id);
121
+        if (existing == null)
122
+        {
123
+            throw new ServiceException("记录不存在或已删除");
124
+        }
125
+    }
126
+}

+ 40 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/DisposableIncomeGrowthSupport.java

@@ -0,0 +1,40 @@
1
+package com.ruoyi.web.modules.industryservice.support;
2
+
3
+import java.math.BigDecimal;
4
+import java.math.RoundingMode;
5
+import com.ruoyi.web.modules.industryservice.domain.BizDisposableIncome;
6
+
7
+/**
8
+ * 可支配收入增幅、增速计算:增幅=本年-上年;增速=增幅/上年
9
+ */
10
+public final class DisposableIncomeGrowthSupport
11
+{
12
+    private static final int RATE_SCALE = 4;
13
+
14
+    private DisposableIncomeGrowthSupport()
15
+    {
16
+    }
17
+
18
+    public static void fillGrowth(BizDisposableIncome current, BizDisposableIncome previousYear)
19
+    {
20
+        if (current == null)
21
+        {
22
+            return;
23
+        }
24
+        current.setIncomeIncrease(null);
25
+        current.setIncomeGrowthRate(null);
26
+        if (current.getRuralDisposableIncome() == null || previousYear == null
27
+                || previousYear.getRuralDisposableIncome() == null)
28
+        {
29
+            return;
30
+        }
31
+        BigDecimal prev = previousYear.getRuralDisposableIncome();
32
+        BigDecimal increase = current.getRuralDisposableIncome().subtract(prev);
33
+        current.setIncomeIncrease(increase);
34
+        if (prev.compareTo(BigDecimal.ZERO) == 0)
35
+        {
36
+            return;
37
+        }
38
+        current.setIncomeGrowthRate(increase.divide(prev, RATE_SCALE, RoundingMode.HALF_UP));
39
+    }
40
+}

+ 52 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/DisposableIncomeValidation.java

@@ -0,0 +1,52 @@
1
+package com.ruoyi.web.modules.industryservice.support;
2
+
3
+import java.math.BigDecimal;
4
+import com.ruoyi.common.exception.ServiceException;
5
+import com.ruoyi.common.utils.StringUtils;
6
+import com.ruoyi.web.modules.industryservice.domain.BizDisposableIncome;
7
+
8
+/**
9
+ * 可支配收入数据填报校验
10
+ */
11
+public final class DisposableIncomeValidation
12
+{
13
+    private DisposableIncomeValidation()
14
+    {
15
+    }
16
+
17
+    public static void validateRowForSave(BizDisposableIncome row)
18
+    {
19
+        if (row == null)
20
+        {
21
+            throw new ServiceException("参数不能为空");
22
+        }
23
+        if (row.getStatYear() == null)
24
+        {
25
+            throw new ServiceException("所属年份不能为空");
26
+        }
27
+        if (!YakHerdInventoryYearSupport.isAllowedYear(row.getStatYear()))
28
+        {
29
+            throw new ServiceException("所属年份不在可选范围内");
30
+        }
31
+        if (row.getRuralDisposableIncome() == null)
32
+        {
33
+            throw new ServiceException("农村居民人均可支配收入不能为空");
34
+        }
35
+        if (row.getRuralDisposableIncome().compareTo(BigDecimal.ZERO) < 0)
36
+        {
37
+            throw new ServiceException("农村居民人均可支配收入不能为负数");
38
+        }
39
+        if (row.getPovertyNetIncome() == null)
40
+        {
41
+            throw new ServiceException("脱贫人口人均纯收入不能为空");
42
+        }
43
+        if (row.getPovertyNetIncome().compareTo(BigDecimal.ZERO) < 0)
44
+        {
45
+            throw new ServiceException("脱贫人口人均纯收入不能为负数");
46
+        }
47
+        if (StringUtils.isNotEmpty(row.getRemark()) && row.getRemark().length() > 500)
48
+        {
49
+            throw new ServiceException("备注不能超过500字");
50
+        }
51
+    }
52
+}

+ 73 - 0
baqing-admin/src/main/resources/mapper/industryservice/BizDisposableIncomeMapper.xml

@@ -0,0 +1,73 @@
1
+<?xml version="1.0" encoding="UTF-8" ?>
2
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3
+<mapper namespace="com.ruoyi.web.modules.industryservice.mapper.BizDisposableIncomeMapper">
4
+
5
+    <resultMap type="com.ruoyi.web.modules.industryservice.domain.BizDisposableIncome" id="BizDisposableIncomeResult">
6
+        <id property="id" column="id"/>
7
+        <result property="statYear" column="stat_year"/>
8
+        <result property="ruralDisposableIncome" column="rural_disposable_income"/>
9
+        <result property="povertyNetIncome" column="poverty_net_income"/>
10
+        <result property="createBy" column="create_by"/>
11
+        <result property="createTime" column="create_time"/>
12
+        <result property="updateBy" column="update_by"/>
13
+        <result property="updateTime" column="update_time"/>
14
+        <result property="remark" column="remark"/>
15
+    </resultMap>
16
+
17
+    <sql id="selectVo">
18
+        select r.id, r.stat_year, r.rural_disposable_income, r.poverty_net_income,
19
+               r.create_by, r.create_time, r.update_by, r.update_time, r.remark
20
+        from biz_disposable_income r
21
+    </sql>
22
+
23
+    <select id="selectBizDisposableIncomeById" parameterType="long" resultMap="BizDisposableIncomeResult">
24
+        <include refid="selectVo"/>
25
+        where r.id = #{id}
26
+    </select>
27
+
28
+    <select id="selectByStatYear" resultMap="BizDisposableIncomeResult">
29
+        <include refid="selectVo"/>
30
+        where r.stat_year = #{statYear}
31
+        limit 1
32
+    </select>
33
+
34
+    <select id="countByStatYearExcludeId" resultType="int">
35
+        select count(1) from biz_disposable_income
36
+        where stat_year = #{statYear}
37
+        <if test="excludeId != null">and id != #{excludeId}</if>
38
+    </select>
39
+
40
+    <select id="selectBizDisposableIncomeList" parameterType="com.ruoyi.web.modules.industryservice.domain.BizDisposableIncome"
41
+            resultMap="BizDisposableIncomeResult">
42
+        <include refid="selectVo"/>
43
+        <where>
44
+            <if test="statYear != null">and r.stat_year = #{statYear}</if>
45
+        </where>
46
+        order by r.stat_year desc, r.id desc
47
+    </select>
48
+
49
+    <insert id="insertBizDisposableIncome" parameterType="com.ruoyi.web.modules.industryservice.domain.BizDisposableIncome"
50
+            useGeneratedKeys="true" keyProperty="id">
51
+        insert into biz_disposable_income (
52
+            stat_year, rural_disposable_income, poverty_net_income, create_by, create_time, remark
53
+        ) values (
54
+            #{statYear}, #{ruralDisposableIncome}, #{povertyNetIncome}, #{createBy}, sysdate(), #{remark}
55
+        )
56
+    </insert>
57
+
58
+    <update id="updateBizDisposableIncome" parameterType="com.ruoyi.web.modules.industryservice.domain.BizDisposableIncome">
59
+        update biz_disposable_income set
60
+            stat_year = #{statYear},
61
+            rural_disposable_income = #{ruralDisposableIncome},
62
+            poverty_net_income = #{povertyNetIncome},
63
+            update_by = #{updateBy},
64
+            update_time = sysdate(),
65
+            remark = #{remark}
66
+        where id = #{id}
67
+    </update>
68
+
69
+    <delete id="deleteBizDisposableIncomeByIds">
70
+        delete from biz_disposable_income where id in
71
+        <foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>
72
+    </delete>
73
+</mapper>

+ 53 - 0
baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/support/DisposableIncomeGrowthSupportTest.java

@@ -0,0 +1,53 @@
1
+package com.ruoyi.web.modules.industryservice.support;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertEquals;
4
+import static org.junit.jupiter.api.Assertions.assertNull;
5
+
6
+import java.math.BigDecimal;
7
+import org.junit.jupiter.api.DisplayName;
8
+import org.junit.jupiter.api.Test;
9
+import com.ruoyi.web.modules.industryservice.domain.BizDisposableIncome;
10
+
11
+@DisplayName("DisposableIncomeGrowthSupport")
12
+class DisposableIncomeGrowthSupportTest
13
+{
14
+    @Test
15
+    @DisplayName("有上年数据时计算增幅与增速")
16
+    void fillGrowthWithPrevious()
17
+    {
18
+        BizDisposableIncome current = row(2020, "13346");
19
+        BizDisposableIncome previous = row(2019, "11886");
20
+        DisposableIncomeGrowthSupport.fillGrowth(current, previous);
21
+        assertEquals(new BigDecimal("1460"), current.getIncomeIncrease());
22
+        assertEquals(new BigDecimal("0.1228"), current.getIncomeGrowthRate());
23
+    }
24
+
25
+    @Test
26
+    @DisplayName("无上年数据时增幅增速为空")
27
+    void fillGrowthWithoutPrevious()
28
+    {
29
+        BizDisposableIncome current = row(2019, "11886");
30
+        DisposableIncomeGrowthSupport.fillGrowth(current, null);
31
+        assertNull(current.getIncomeIncrease());
32
+        assertNull(current.getIncomeGrowthRate());
33
+    }
34
+
35
+    @Test
36
+    @DisplayName("上年可支配收入为0时仅填增幅")
37
+    void fillGrowthWhenPreviousZero()
38
+    {
39
+        BizDisposableIncome current = row(2021, "100");
40
+        BizDisposableIncome previous = row(2020, "0");
41
+        DisposableIncomeGrowthSupport.fillGrowth(current, previous);
42
+        assertEquals(new BigDecimal("100"), current.getIncomeIncrease());
43
+        assertNull(current.getIncomeGrowthRate());
44
+    }
45
+
46
+    private static BizDisposableIncome row(int year, String income)
47
+    {
48
+        BizDisposableIncome row = new BizDisposableIncome();
49
+        row.setStatYear(year);
50
+        row.setRuralDisposableIncome(new BigDecimal(income));
51
+        return row;
52
+    }
53
+}

+ 78 - 0
baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/support/DisposableIncomeValidationTest.java

@@ -0,0 +1,78 @@
1
+package com.ruoyi.web.modules.industryservice.support;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
4
+import static org.junit.jupiter.api.Assertions.assertThrows;
5
+import static org.junit.jupiter.api.Assertions.assertTrue;
6
+
7
+import java.math.BigDecimal;
8
+import java.time.Year;
9
+import org.junit.jupiter.api.DisplayName;
10
+import org.junit.jupiter.api.Test;
11
+import com.ruoyi.common.exception.ServiceException;
12
+import com.ruoyi.web.modules.industryservice.domain.BizDisposableIncome;
13
+
14
+@DisplayName("DisposableIncomeValidation")
15
+class DisposableIncomeValidationTest
16
+{
17
+    @Test
18
+    @DisplayName("缺年份失败")
19
+    void missingYear()
20
+    {
21
+        BizDisposableIncome row = validRow();
22
+        row.setStatYear(null);
23
+        ServiceException ex = assertThrows(ServiceException.class, () -> DisposableIncomeValidation.validateRowForSave(row));
24
+        assertTrue(ex.getMessage().contains("所属年份"));
25
+    }
26
+
27
+    @Test
28
+    @DisplayName("非法年份失败")
29
+    void illegalYear()
30
+    {
31
+        BizDisposableIncome row = validRow();
32
+        row.setStatYear(1999);
33
+        ServiceException ex = assertThrows(ServiceException.class, () -> DisposableIncomeValidation.validateRowForSave(row));
34
+        assertTrue(ex.getMessage().contains("可选范围"));
35
+    }
36
+
37
+    @Test
38
+    @DisplayName("负可支配收入失败")
39
+    void negativeRuralIncome()
40
+    {
41
+        BizDisposableIncome row = validRow();
42
+        row.setRuralDisposableIncome(new BigDecimal("-1"));
43
+        ServiceException ex = assertThrows(ServiceException.class, () -> DisposableIncomeValidation.validateRowForSave(row));
44
+        assertTrue(ex.getMessage().contains("不能为负数"));
45
+    }
46
+
47
+    @Test
48
+    @DisplayName("备注超长失败")
49
+    void remarkTooLong()
50
+    {
51
+        BizDisposableIncome row = validRow();
52
+        StringBuilder sb = new StringBuilder();
53
+        for (int i = 0; i < 501; i++)
54
+        {
55
+            sb.append('a');
56
+        }
57
+        row.setRemark(sb.toString());
58
+        ServiceException ex = assertThrows(ServiceException.class, () -> DisposableIncomeValidation.validateRowForSave(row));
59
+        assertTrue(ex.getMessage().contains("500"));
60
+    }
61
+
62
+    @Test
63
+    @DisplayName("合法数据通过")
64
+    void validRowPasses()
65
+    {
66
+        assertDoesNotThrow(() -> DisposableIncomeValidation.validateRowForSave(validRow()));
67
+    }
68
+
69
+    private static BizDisposableIncome validRow()
70
+    {
71
+        BizDisposableIncome row = new BizDisposableIncome();
72
+        row.setStatYear(Year.now().getValue());
73
+        row.setRuralDisposableIncome(new BigDecimal("22190"));
74
+        row.setPovertyNetIncome(new BigDecimal("16965"));
75
+        row.setRemark("ok");
76
+        return row;
77
+    }
78
+}

+ 44 - 0
doc/产业数据模型及服务/可支配收入数据填报/可支配收入数据填报功能需求.md

@@ -0,0 +1,44 @@
1
+# 可支配收入数据填报 — 功能需求
2
+
3
+## 1. 文档说明
4
+
5
+| 项 | 说明 |
6
+| --- | --- |
7
+| 模块名称 | 可支配收入数据填报 |
8
+| 目标 | 按**所属年份**登记农村居民人均可支配收入、脱贫人口人均纯收入;列表展示增幅与增速 |
9
+| 关联系统 | 业务后台 |
10
+| 修订依据 | 交接表 `可支配收入数据填报.xlsx` |
11
+
12
+---
13
+
14
+## 2. 填报粒度
15
+
16
+- 一条记录:某一自然年的县级收入快照(无乡镇/村维度)。
17
+- 唯一键:所属年份。
18
+- 增幅、增速不落库:增幅 = 本年可支配收入 − 上年可支配收入;增速 = 增幅 / 上年可支配收入。
19
+- 无导入 / 导出。
20
+
21
+---
22
+
23
+## 3. 功能范围
24
+
25
+| 能力 | 说明 |
26
+| --- | --- |
27
+| 分页列表 | 筛选:所属年份;列:年份、农村居民人均可支配收入、增幅、增速、脱贫人口人均纯收入 |
28
+| 新增 / 修改 | 年份、人均可支配收入、人均纯收入、备注;新增默认本年 |
29
+| 详情 | 全部字段含增幅、增速 |
30
+| 删除 | 物理删除 |
31
+
32
+---
33
+
34
+## 4. 权限
35
+
36
+`dataModel:disposableIncome:list|query|add|edit|remove`
37
+
38
+---
39
+
40
+## 5. 修订记录
41
+
42
+| 版本 | 说明 |
43
+| --- | --- |
44
+| 1.0 | 首版:县级年度 CRUD;增幅/增速计算展示 |

+ 31 - 0
doc/产业数据模型及服务/可支配收入数据填报/可支配收入数据填报技术方案.md

@@ -0,0 +1,31 @@
1
+# 可支配收入数据填报 — 技术方案
2
+
3
+## 1. 接口
4
+
5
+| 方法 | 路径 | 权限 |
6
+| --- | --- | --- |
7
+| GET | `/dataModel/disposableIncome/list` | list |
8
+| GET | `/dataModel/disposableIncome/{id}` | query |
9
+| POST | `/dataModel/disposableIncome` | add |
10
+| PUT | `/dataModel/disposableIncome` | edit |
11
+| DELETE | `/dataModel/disposableIncome/{ids}` | remove |
12
+| GET | `/dataModel/disposableIncome/yearOptions` | list |
13
+
14
+## 2. 表结构
15
+
16
+表名:`biz_disposable_income`(见 `sql/biz_disposable_income.sql`)
17
+
18
+| 字段 | 说明 |
19
+| --- | --- |
20
+| stat_year | 所属年份,唯一 |
21
+| rural_disposable_income | 农村居民人均可支配收入(元) |
22
+| poverty_net_income | 脱贫人口人均纯收入(元) |
23
+| remark | 备注 |
24
+
25
+非落库:`incomeIncrease`、`incomeGrowthRate`(列表/详情按上年记录计算)。
26
+
27
+## 3. 前端
28
+
29
+- 页面:`ruoyi-ui/src/views/dataModel/disposableIncome/index.vue`
30
+- API:`ruoyi-ui/src/api/dataModel/disposableIncome.js`
31
+- 菜单组件路径建议:`dataModel/disposableIncome/index`

+ 72 - 0
doc/产业数据模型及服务/可支配收入数据填报/可支配收入数据填报测试用例.md

@@ -0,0 +1,72 @@
1
+# 可支配收入数据填报 — 测试用例
2
+
3
+> 依据:`可支配收入数据填报功能需求.md`、`可支配收入数据填报技术方案.md`  
4
+> **接口 Base Path**:`/dataModel/disposableIncome`  
5
+> **建表**:执行 `sql/biz_disposable_income.sql`
6
+
7
+**通用前置**:已登录且具备模块权限;测试库无冲突年份数据或已隔离。
8
+
9
+---
10
+
11
+## 一、单元测试用例
12
+
13
+| 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
14
+| --- | --- | --- | --- | --- | --- | --- | --- | --- |
15
+| ZCZX-KZPMSR-UT-001 | 增幅增速 | 有上年数据 | 单元测试 | JUnit5 | 计算逻辑 | 本年 13346、上年 11886 | `DisposableIncomeGrowthSupport.fillGrowth` | 增幅=1460;增速≈0.1228 |
16
+| ZCZX-KZPMSR-UT-002 | 增幅增速 | 无上年数据 | 单元测试 | JUnit5 | 首年无对比 | previous=null | `fillGrowth` | 增幅、增速均为 null |
17
+| ZCZX-KZPMSR-UT-003 | 增幅增速 | 上年为 0 | 单元测试 | JUnit5 | 除零保护 | 上年可支配收入=0 | `fillGrowth` | 增幅有值;增速为 null |
18
+| ZCZX-KZPMSR-UT-004 | 校验 | 必填年份 | 单元测试 | JUnit5 | 保存校验 | 缺 `statYear` | `validateRowForSave` | 抛 `ServiceException` |
19
+| ZCZX-KZPMSR-UT-005 | 校验 | 非法年份 | 单元测试 | JUnit5 | 年份范围 | `statYear=1999` | `validateRowForSave` | 抛「所属年份不在可选范围内」 |
20
+| ZCZX-KZPMSR-UT-006 | 校验 | 负收入拒绝 | 单元测试 | JUnit5 | 非负约束 | 可支配收入=-1 | `validateRowForSave` | 抛 `ServiceException` |
21
+| ZCZX-KZPMSR-UT-007 | 校验 | 备注超长 | 单元测试 | JUnit5 | 长度限制 | remark 长度>500 | `validateRowForSave` | 抛「备注不能超过500字」 |
22
+| ZCZX-KZPMSR-UT-008 | 校验 | 合法保存 | 单元测试 | JUnit5 | 正向 | 本年合法数据 | `validateRowForSave` | 不抛异常 |
23
+
24
+对应代码:`DisposableIncomeGrowthSupportTest`、`DisposableIncomeValidationTest`
25
+
26
+---
27
+
28
+## 二、接口测试用例
29
+
30
+| 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
31
+| --- | --- | --- | --- | --- | --- | --- | --- | --- |
32
+| ZCZX-KZPMSR-API-001 | 列表 | 默认分页 | 接口测试 | Postman | 列表 | 有样本 | `GET /list` | `code=200`;含年份、可支配收入、增幅、增速、人均纯收入 |
33
+| ZCZX-KZPMSR-API-002 | 列表 | 按年份筛选 | 接口测试 | Postman | 搜索 | 多年数据 | `statYear=2025` | 仅 2025 |
34
+| ZCZX-KZPMSR-API-003 | 列表 | 增幅增速回填 | 接口测试 | Postman | 计算展示 | 连续两年有数据 | `GET /list` | 较新年份带增幅、增速;最早年可为空 |
35
+| ZCZX-KZPMSR-API-004 | 详情 | 全字段 | 接口测试 | Postman | 详情 | 有效 id | `GET /{id}` | 含收入字段与增幅、增速 |
36
+| ZCZX-KZPMSR-API-005 | 新增 | 必填成功 | 接口测试 | Postman | 新增 | 该年无记录 | `POST` 含年份+两项收入 | 成功 |
37
+| ZCZX-KZPMSR-API-006 | 新增 | 缺年份 | 接口测试 | Postman | 校验 | 已登录 | 缺 `statYear` | 失败 |
38
+| ZCZX-KZPMSR-API-007 | 新增 | 缺可支配收入 | 接口测试 | Postman | 校验 | 已登录 | 缺 `ruralDisposableIncome` | 失败 |
39
+| ZCZX-KZPMSR-API-008 | 新增 | 重复年份 | 接口测试 | Postman | 唯一键 | 已有同年记录 | 再 `POST` 同年 | 失败「该年份数据已存在」 |
40
+| ZCZX-KZPMSR-API-009 | 修改 | 更新收入 | 接口测试 | Postman | 修改 | 有效 id | `PUT` 改可支配收入 | 成功;列表增幅随之变化 |
41
+| ZCZX-KZPMSR-API-010 | 修改 | 改到已有年份 | 接口测试 | Postman | 唯一键 | 存在 2024、2025 | 将 2025 的年份改为 2024 | 失败 |
42
+| ZCZX-KZPMSR-API-011 | 删除 | 物理删除 | 接口测试 | Postman | 删除 | 有效 id | `DELETE /{id}` | 记录移除;再查 404/空 |
43
+| ZCZX-KZPMSR-API-012 | 下拉 | 年份选项 | 接口测试 | Postman | 下拉 | 已登录 | `GET /yearOptions` | 当前年起向前 5 年共 6 个 |
44
+| ZCZX-KZPMSR-API-013 | 权限 | 无 add | 接口测试 | Postman | 权限 | 仅 list | `POST /` | 403 |
45
+| ZCZX-KZPMSR-API-014 | 权限 | 无 remove | 接口测试 | Postman | 权限 | 无 remove | `DELETE /{id}` | 403 |
46
+| ZCZX-KZPMSR-API-015 | 端到端 | 增-查-改-删 | 接口测试 | Postman | 闭环 | 空库该年 | 新增→list→详情→修改→删除 | 各步数据一致 |
47
+
48
+---
49
+
50
+## 三、界面测试用例(UI)
51
+
52
+**工具**:Playwright / 手工均可。
53
+
54
+| 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
55
+| --- | --- | --- | --- | --- | --- | --- | --- | --- |
56
+| ZCZX-KZPMSR-UI-001 | 列表 | 列展示 | 界面测试 | Chrome | 列表 | 有数据 | 打开页面 | 显示年份、可支配收入、增幅、增速、人均纯收入 |
57
+| ZCZX-KZPMSR-UI-002 | 列表 | 年份筛选 | 界面测试 | Chrome | 搜索 | 有多年数据 | 选年份→搜索 | 仅该年 |
58
+| ZCZX-KZPMSR-UI-003 | 新增 | 默认本年 | 界面测试 | Chrome | 默认值 | 已登录 | 点新增 | 年份默认当前年 |
59
+| ZCZX-KZPMSR-UI-004 | 新增 | 保存成功 | 界面测试 | Chrome | 新增 | 该年无数据 | 填两项收入→确定 | 提示成功;列表出现 |
60
+| ZCZX-KZPMSR-UI-005 | 新增 | 必填校验 | 界面测试 | Chrome | 校验 | 打开新增 | 不填收入直接确定 | 表单校验提示 |
61
+| ZCZX-KZPMSR-UI-006 | 详情 | 只读含增幅增速 | 界面测试 | Chrome | 详情 | 有上年数据 | 点查看 | 展示增幅、增速(百分比) |
62
+| ZCZX-KZPMSR-UI-007 | 修改 | 编辑回填 | 界面测试 | Chrome | 修改 | 有效行 | 点编辑 | 表单回填;可保存 |
63
+| ZCZX-KZPMSR-UI-008 | 删除 | 确认删除 | 界面测试 | Chrome | 删除 | 有效行 | 点删除→确认 | 行消失 |
64
+| ZCZX-KZPMSR-UI-009 | 范围 | 无导入导出按钮 | 界面测试 | Chrome | 需求边界 | 有权限 | 查看工具栏 | 无导入、导出入口 |
65
+
66
+---
67
+
68
+## 四、修订记录
69
+
70
+| 版本 | 说明 |
71
+| --- | --- |
72
+| 1.0 | 首版:CRUD + 增幅/增速计算;无导入导出用例 |

+ 15 - 0
sql/biz_disposable_income.sql

@@ -0,0 +1,15 @@
1
+-- 可支配收入数据填报(物理删除;县级年度;全量覆盖建表)
2
+DROP TABLE IF EXISTS `biz_disposable_income`;
3
+CREATE TABLE `biz_disposable_income` (
4
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
5
+  `stat_year` int(11) NOT NULL COMMENT '所属年份',
6
+  `rural_disposable_income` decimal(18,2) DEFAULT NULL COMMENT '农村居民人均可支配收入(元)',
7
+  `poverty_net_income` decimal(18,2) DEFAULT NULL COMMENT '脱贫人口人均纯收入(元)',
8
+  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
9
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
10
+  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
11
+  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
12
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
13
+  PRIMARY KEY (`id`),
14
+  UNIQUE KEY `uk_stat_year` (`stat_year`)
15
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='可支配收入数据填报';