Просмотр исходного кода

Merge branch 'master' of http://192.168.1.25:3000/lbyzx123/huimv-baqing

xsh_1997 1 месяц назад
Родитель
Сommit
2de8c25b88

+ 20 - 11
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/service/impl/BreedingIndustryDashboardServiceImpl.java

@@ -27,6 +27,7 @@ import com.ruoyi.web.modules.industryservice.support.BreedingIndustryCalculator;
27
 import com.ruoyi.web.modules.industryservice.support.BreedingIndustryPastureScope;
27
 import com.ruoyi.web.modules.industryservice.support.BreedingIndustryPastureScope;
28
 import com.ruoyi.web.modules.industryservice.support.BreedingIndustryRules;
28
 import com.ruoyi.web.modules.industryservice.support.BreedingIndustryRules;
29
 import com.ruoyi.web.modules.industryservice.support.GrasslandRules;
29
 import com.ruoyi.web.modules.industryservice.support.GrasslandRules;
30
+import com.ruoyi.web.modules.screen.mapper.HomeScreenStatsMapper;
30
 
31
 
31
 @Service
32
 @Service
32
 public class BreedingIndustryDashboardServiceImpl implements IBreedingIndustryDashboardService
33
 public class BreedingIndustryDashboardServiceImpl implements IBreedingIndustryDashboardService
@@ -43,6 +44,9 @@ public class BreedingIndustryDashboardServiceImpl implements IBreedingIndustryDa
43
     @Autowired
44
     @Autowired
44
     private BreedingIndustryPastureScope breedingIndustryPastureScope;
45
     private BreedingIndustryPastureScope breedingIndustryPastureScope;
45
 
46
 
47
+    @Autowired
48
+    private HomeScreenStatsMapper homeScreenStatsMapper;
49
+
46
     @Override
50
     @Override
47
     public BreedingIndustryDashboardVo loadDashboard(boolean forceRefresh)
51
     public BreedingIndustryDashboardVo loadDashboard(boolean forceRefresh)
48
     {
52
     {
@@ -58,14 +62,20 @@ public class BreedingIndustryDashboardServiceImpl implements IBreedingIndustryDa
58
             fillGrasslandOverview(overview);
62
             fillGrasslandOverview(overview);
59
             dashboard.setOverview(overview);
63
             dashboard.setOverview(overview);
60
 
64
 
65
+            int currentCalendarYear = statDate.getYear();
66
+            // 产业总览三项:填报合计填入既有字段(与大屏首页一致);原快照口径见下方暂保留调用
67
+            overview.setInventoryTotal(
68
+                    homeScreenStatsMapper.sumHerdInventoryYakTotalByStatYear(currentCalendarYear - 1));
69
+            overview.setAnnualOutbound(
70
+                    homeScreenStatsMapper.sumOutboundReportYakCountByStatYear(currentCalendarYear));
71
+            overview.setPastureCount(
72
+                    homeScreenStatsMapper.sumOutboundReportFarmerHouseholdCountByStatYear(currentCalendarYear));
73
+
61
             BreedingIndustryInventoryChangeVo inventoryChange = new BreedingIndustryInventoryChangeVo();
74
             BreedingIndustryInventoryChangeVo inventoryChange = new BreedingIndustryInventoryChangeVo();
62
             List<BreedingIndustryAgeBandVo> ageStructure = buildEmptyAgeStructure();
75
             List<BreedingIndustryAgeBandVo> ageStructure = buildEmptyAgeStructure();
63
             if (allowedPastureIds.isEmpty())
76
             if (allowedPastureIds.isEmpty())
64
             {
77
             {
65
-                overview.setInventoryTotal(0);
66
-                overview.setAnnualOutbound(0);
67
                 overview.setWarningYakCount(0);
78
                 overview.setWarningYakCount(0);
68
-                overview.setPastureCount(0);
69
                 inventoryChange.setBullCount(0);
79
                 inventoryChange.setBullCount(0);
70
                 inventoryChange.setCowCount(0);
80
                 inventoryChange.setCowCount(0);
71
                 inventoryChange.setUnknownGenderCount(0);
81
                 inventoryChange.setUnknownGenderCount(0);
@@ -75,12 +85,12 @@ public class BreedingIndustryDashboardServiceImpl implements IBreedingIndustryDa
75
             {
85
             {
76
                 Date yearStart = toDate(statDate.withDayOfYear(1));
86
                 Date yearStart = toDate(statDate.withDayOfYear(1));
77
                 Date statDateEnd = toDateEndOfDay(statDate);
87
                 Date statDateEnd = toDateEndOfDay(statDate);
78
-                Date statDateOnly = toDate(statDate);
79
 
88
 
80
-                overview.setPastureCount(breedingIndustryStatsMapper.countPastureInScope(allowedPastureIds));
81
-                overview.setInventoryTotal(breedingIndustryStatsMapper.countInventory(allowedPastureIds));
82
-                overview.setAnnualOutbound(
83
-                        breedingIndustryStatsMapper.countAnnualOutbound(allowedPastureIds, yearStart, statDateEnd));
89
+                // 原牧场/存栏/出栏口径(暂保留调用)
90
+                breedingIndustryStatsMapper.countPastureInScope(allowedPastureIds);
91
+                int legacyInventoryTotal = breedingIndustryStatsMapper.countInventory(allowedPastureIds);
92
+                breedingIndustryStatsMapper.countAnnualOutbound(allowedPastureIds, yearStart, statDateEnd);
93
+
84
                 overview.setWarningYakCount(
94
                 overview.setWarningYakCount(
85
                         breedingIndustryStatsMapper.countWarningYak(allowedPastureIds, yearStart, statDateEnd));
95
                         breedingIndustryStatsMapper.countWarningYak(allowedPastureIds, yearStart, statDateEnd));
86
 
96
 
@@ -108,14 +118,13 @@ public class BreedingIndustryDashboardServiceImpl implements IBreedingIndustryDa
108
                         cow++;
118
                         cow++;
109
                     }
119
                     }
110
                 }
120
                 }
111
-                int total = overview.getInventoryTotal();
112
                 inventoryChange.setBullCount(bull);
121
                 inventoryChange.setBullCount(bull);
113
                 inventoryChange.setCowCount(cow);
122
                 inventoryChange.setCowCount(cow);
114
-                inventoryChange.setUnknownGenderCount(Math.max(0, total - bull - cow));
123
+                inventoryChange.setUnknownGenderCount(Math.max(0, legacyInventoryTotal - bull - cow));
115
                 inventoryChange.setMonthlyTrend(BreedingIndustryCalculator.buildMonthlyTrend(snapshots, statDate));
124
                 inventoryChange.setMonthlyTrend(BreedingIndustryCalculator.buildMonthlyTrend(snapshots, statDate));
116
 
125
 
117
                 Map<String, Integer> bandCounts = BreedingIndustryCalculator.countAgeBands(snapshots, statDate);
126
                 Map<String, Integer> bandCounts = BreedingIndustryCalculator.countAgeBands(snapshots, statDate);
118
-                ageStructure = BreedingIndustryCalculator.buildAgeStructure(bandCounts, total);
127
+                ageStructure = BreedingIndustryCalculator.buildAgeStructure(bandCounts, legacyInventoryTotal);
119
             }
128
             }
120
             dashboard.setInventoryChange(inventoryChange);
129
             dashboard.setInventoryChange(inventoryChange);
121
             dashboard.setAgeStructure(ageStructure);
130
             dashboard.setAgeStructure(ageStructure);

+ 3 - 3
baqing-admin/src/main/java/com/ruoyi/web/modules/screen/domain/vo/HomeScreenIndustryOverviewVo.java

@@ -6,11 +6,11 @@ import java.math.BigDecimal;
6
  */
6
  */
7
 public class HomeScreenIndustryOverviewVo
7
 public class HomeScreenIndustryOverviewVo
8
 {
8
 {
9
-    /** 截止 statDate 牦牛存栏总头数 */
9
+    /** 产业总览「牦牛存栏总量」(头):上一年全县存栏填报 `yak_total` 合计;字段名沿用 `inventoryTotal` */
10
     private int inventoryTotal;
10
     private int inventoryTotal;
11
-    /** 当年出栏总头数 */
11
+    /** 产业总览「牦牛年出栏量」(头):当年全县出栏填报 `yak_outbound_count` 合计;字段名沿用 `annualOutbound` */
12
     private int annualOutbound;
12
     private int annualOutbound;
13
-    /** 有效牧场数 */
13
+    /** 产业总览「农牧户」展示位(户):当年全县出栏填报 `farmer_household_count` 合计;字段名沿用 `pastureCount` */
14
     private int pastureCount;
14
     private int pastureCount;
15
     /** 草场块数 */
15
     /** 草场块数 */
16
     private int grasslandCount;
16
     private int grasslandCount;

+ 9 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/screen/mapper/HomeScreenStatsMapper.java

@@ -70,4 +70,13 @@ public interface HomeScreenStatsMapper
70
     List<HomeScreenTownMapMarkerVo> selectTownMapMarkers(
70
     List<HomeScreenTownMapMarkerVo> selectTownMapMarkers(
71
             @Param("herdStatYear") int herdStatYear,
71
             @Param("herdStatYear") int herdStatYear,
72
             @Param("outboundStatYear") int outboundStatYear);
72
             @Param("outboundStatYear") int outboundStatYear);
73
+
74
+    /** 指定年份全县乡镇牦牛存栏填报合计({@code biz_yak_herd_inventory.yak_total}) */
75
+    int sumHerdInventoryYakTotalByStatYear(@Param("statYear") int statYear);
76
+
77
+    /** 指定年份全县乡镇牦牛出栏填报合计({@code biz_yak_outbound_report.yak_outbound_count}) */
78
+    int sumOutboundReportYakCountByStatYear(@Param("statYear") int statYear);
79
+
80
+    /** 指定年份全县乡镇农牧户户数填报合计({@code biz_yak_outbound_report.farmer_household_count}) */
81
+    int sumOutboundReportFarmerHouseholdCountByStatYear(@Param("statYear") int statYear);
73
 }
82
 }

+ 17 - 10
baqing-admin/src/main/java/com/ruoyi/web/modules/screen/service/impl/HomeScreenServiceImpl.java

@@ -68,19 +68,26 @@ public class HomeScreenServiceImpl implements IHomeScreenService
68
         overview.setSupplierCount(homeScreenStatsMapper.countSupplierForScreen());
68
         overview.setSupplierCount(homeScreenStatsMapper.countSupplierForScreen());
69
         overview.setDistributorCount(homeScreenStatsMapper.countDistributorForScreen());
69
         overview.setDistributorCount(homeScreenStatsMapper.countDistributorForScreen());
70
         overview.setTradeMarketCount(homeScreenStatsMapper.countTradeMarketForScreen());
70
         overview.setTradeMarketCount(homeScreenStatsMapper.countTradeMarketForScreen());
71
-        overview.setPastureCount(allowedPastureIds.isEmpty()
72
-                ? 0
73
-                : breedingIndustryStatsMapper.countPastureInScope(allowedPastureIds));
71
+        // 原有效牧场数量口径(暂保留调用,产业总览 pastureCount 已改用农牧户填报合计)
72
+        if (!allowedPastureIds.isEmpty())
73
+        {
74
+            breedingIndustryStatsMapper.countPastureInScope(allowedPastureIds);
75
+        }
74
         overview.setGrasslandCount(breedingIndustryStatsMapper.countGrassland());
76
         overview.setGrasslandCount(breedingIndustryStatsMapper.countGrassland());
75
         BigDecimal area = breedingIndustryStatsMapper.sumGrasslandAreaMu();
77
         BigDecimal area = breedingIndustryStatsMapper.sumGrasslandAreaMu();
76
         overview.setGrasslandAreaMu(area != null ? area.setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
78
         overview.setGrasslandAreaMu(area != null ? area.setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
77
 
79
 
80
+        // 产业总览三项:填报合计填入既有字段(字段名不变);原快照/资产口径见下方暂保留调用
81
+        int currentCalendarYear = Year.now().getValue();
82
+        overview.setInventoryTotal(homeScreenStatsMapper.sumHerdInventoryYakTotalByStatYear(currentCalendarYear - 1));
83
+        overview.setAnnualOutbound(homeScreenStatsMapper.sumOutboundReportYakCountByStatYear(currentCalendarYear));
84
+        overview.setPastureCount(
85
+                homeScreenStatsMapper.sumOutboundReportFarmerHouseholdCountByStatYear(currentCalendarYear));
86
+
78
         HomeScreenInventoryChangeVo inventoryChange = new HomeScreenInventoryChangeVo();
87
         HomeScreenInventoryChangeVo inventoryChange = new HomeScreenInventoryChangeVo();
79
         if (allowedPastureIds.isEmpty())
88
         if (allowedPastureIds.isEmpty())
80
         {
89
         {
81
             // 无有效牧场时牦牛相关区块全部置零
90
             // 无有效牧场时牦牛相关区块全部置零
82
-            overview.setInventoryTotal(0);
83
-            overview.setAnnualOutbound(0);
84
             inventoryChange.setBullCount(0);
91
             inventoryChange.setBullCount(0);
85
             inventoryChange.setCowCount(0);
92
             inventoryChange.setCowCount(0);
86
             inventoryChange.setUnknownGenderCount(0);
93
             inventoryChange.setUnknownGenderCount(0);
@@ -91,14 +98,14 @@ public class HomeScreenServiceImpl implements IHomeScreenService
91
         }
98
         }
92
         else
99
         else
93
         {
100
         {
94
-            int inventoryTotal = HomeScreenSupport.countInventoryAtDate(snapshots, statDate);
101
+            // 原时点存栏口径(暂保留,供存栏变动/年龄结构等区块使用)
102
+            int inventoryTotalAtDate = HomeScreenSupport.countInventoryAtDate(snapshots, statDate);
95
             int[] gender = HomeScreenSupport.countGenderAtDate(snapshots, statDate);
103
             int[] gender = HomeScreenSupport.countGenderAtDate(snapshots, statDate);
96
-            overview.setInventoryTotal(inventoryTotal);
97
-            overview.setAnnualOutbound(breedingIndustryStatsMapper.countAnnualOutbound(
98
-                    allowedPastureIds, yearStart, yearEnd));
104
+            // 原资产出栏口径(暂保留调用)
105
+            breedingIndustryStatsMapper.countAnnualOutbound(allowedPastureIds, yearStart, yearEnd);
99
             inventoryChange.setBullCount(gender[0]);
106
             inventoryChange.setBullCount(gender[0]);
100
             inventoryChange.setCowCount(gender[1]);
107
             inventoryChange.setCowCount(gender[1]);
101
-            inventoryChange.setUnknownGenderCount(Math.max(0, inventoryTotal - gender[0] - gender[1]));
108
+            inventoryChange.setUnknownGenderCount(Math.max(0, inventoryTotalAtDate - gender[0] - gender[1]));
102
             inventoryChange.setMonthlyTrend(HomeScreenSupport.buildBullCowMonthlyTrend(snapshots, statDate));
109
             inventoryChange.setMonthlyTrend(HomeScreenSupport.buildBullCowMonthlyTrend(snapshots, statDate));
103
             vo.setAgeStructure(HomeScreenSupport.buildAgeStructureAtDate(snapshots, statDate));
110
             vo.setAgeStructure(HomeScreenSupport.buildAgeStructureAtDate(snapshots, statDate));
104
             vo.setOutboundMonthly(HomeScreenSupport.buildOutboundMonthly(
111
             vo.setOutboundMonthly(HomeScreenSupport.buildOutboundMonthly(

+ 18 - 0
baqing-admin/src/main/resources/mapper/screen/HomeScreenStatsMapper.xml

@@ -163,4 +163,22 @@
163
           and d.status = '0'
163
           and d.status = '0'
164
         order by d.order_num asc, d.dept_id asc
164
         order by d.order_num asc, d.dept_id asc
165
     </select>
165
     </select>
166
+
167
+    <select id="sumHerdInventoryYakTotalByStatYear" resultType="int">
168
+        select coalesce(sum(yak_total), 0)
169
+        from biz_yak_herd_inventory
170
+        where stat_year = #{statYear}
171
+    </select>
172
+
173
+    <select id="sumOutboundReportYakCountByStatYear" resultType="int">
174
+        select coalesce(sum(yak_outbound_count), 0)
175
+        from biz_yak_outbound_report
176
+        where stat_year = #{statYear}
177
+    </select>
178
+
179
+    <select id="sumOutboundReportFarmerHouseholdCountByStatYear" resultType="int">
180
+        select coalesce(sum(farmer_household_count), 0)
181
+        from biz_yak_outbound_report
182
+        where stat_year = #{statYear}
183
+    </select>
166
 </mapper>
184
 </mapper>

+ 48 - 4
baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/service/impl/BreedingIndustryDashboardServiceImplTest.java

@@ -2,11 +2,14 @@ package com.ruoyi.web.modules.industryservice.service.impl;
2
 
2
 
3
 import static org.junit.jupiter.api.Assertions.assertEquals;
3
 import static org.junit.jupiter.api.Assertions.assertEquals;
4
 import static org.junit.jupiter.api.Assertions.assertThrows;
4
 import static org.junit.jupiter.api.Assertions.assertThrows;
5
+import static org.mockito.ArgumentMatchers.eq;
5
 import static org.mockito.ArgumentMatchers.any;
6
 import static org.mockito.ArgumentMatchers.any;
6
 import static org.mockito.ArgumentMatchers.anyList;
7
 import static org.mockito.ArgumentMatchers.anyList;
8
+import static org.mockito.Mockito.lenient;
7
 import static org.mockito.Mockito.when;
9
 import static org.mockito.Mockito.when;
8
 
10
 
9
 import java.math.BigDecimal;
11
 import java.math.BigDecimal;
12
+import java.time.Year;
10
 import java.util.Arrays;
13
 import java.util.Arrays;
11
 import java.util.Collections;
14
 import java.util.Collections;
12
 import org.junit.jupiter.api.DisplayName;
15
 import org.junit.jupiter.api.DisplayName;
@@ -21,6 +24,7 @@ import com.ruoyi.web.modules.industryservice.domain.vo.BreedingIndustryDashboard
21
 import com.ruoyi.web.modules.industryservice.mapper.BreedingIndustryStatsMapper;
24
 import com.ruoyi.web.modules.industryservice.mapper.BreedingIndustryStatsMapper;
22
 import com.ruoyi.web.modules.industryservice.support.BreedingIndustryPastureScope;
25
 import com.ruoyi.web.modules.industryservice.support.BreedingIndustryPastureScope;
23
 import com.ruoyi.web.modules.industryservice.support.BreedingIndustryRules;
26
 import com.ruoyi.web.modules.industryservice.support.BreedingIndustryRules;
27
+import com.ruoyi.web.modules.screen.mapper.HomeScreenStatsMapper;
24
 
28
 
25
 @ExtendWith(MockitoExtension.class)
29
 @ExtendWith(MockitoExtension.class)
26
 @DisplayName("BreedingIndustryDashboardServiceImpl")
30
 @DisplayName("BreedingIndustryDashboardServiceImpl")
@@ -32,6 +36,9 @@ class BreedingIndustryDashboardServiceImplTest
32
     @Mock
36
     @Mock
33
     private BreedingIndustryPastureScope breedingIndustryPastureScope;
37
     private BreedingIndustryPastureScope breedingIndustryPastureScope;
34
 
38
 
39
+    @Mock
40
+    private HomeScreenStatsMapper homeScreenStatsMapper;
41
+
35
     @InjectMocks
42
     @InjectMocks
36
     private BreedingIndustryDashboardServiceImpl service;
43
     private BreedingIndustryDashboardServiceImpl service;
37
 
44
 
@@ -39,20 +46,48 @@ class BreedingIndustryDashboardServiceImplTest
39
     @DisplayName("ZCZX-YZCYSJ-UT-002 可见牧场为空")
46
     @DisplayName("ZCZX-YZCYSJ-UT-002 可见牧场为空")
40
     void ut002_emptyScope()
47
     void ut002_emptyScope()
41
     {
48
     {
49
+        int currentYear = Year.now().getValue();
42
         stubGrassland();
50
         stubGrassland();
51
+        stubReportOverviewSums(currentYear, 100, 50, 20);
43
         when(breedingIndustryPastureScope.resolveAllowedPastureIds()).thenReturn(Collections.emptyList());
52
         when(breedingIndustryPastureScope.resolveAllowedPastureIds()).thenReturn(Collections.emptyList());
44
         BreedingIndustryDashboardVo vo = service.loadDashboard(false);
53
         BreedingIndustryDashboardVo vo = service.loadDashboard(false);
45
-        assertEquals(0, vo.getOverview().getInventoryTotal());
46
-        assertEquals(0, vo.getOverview().getPastureCount());
54
+        assertEquals(100, vo.getOverview().getInventoryTotal());
55
+        assertEquals(50, vo.getOverview().getAnnualOutbound());
56
+        assertEquals(20, vo.getOverview().getPastureCount());
47
         assertEquals(3, vo.getOverview().getGrasslandCount());
57
         assertEquals(3, vo.getOverview().getGrasslandCount());
48
         assertEquals(0, vo.getInventoryChange().getMonthlyTrend().size());
58
         assertEquals(0, vo.getInventoryChange().getMonthlyTrend().size());
49
     }
59
     }
50
 
60
 
51
     @Test
61
     @Test
52
-    @DisplayName("ZCZX-YZCYSJ-UT-009 公母未标=总量")
62
+    @DisplayName("ZCZX-YZCYSJ-UT-028 产业总览三项与大屏填报口径一致")
63
+    void ut028_overviewFromReportTables()
64
+    {
65
+        int currentYear = Year.now().getValue();
66
+        stubGrassland();
67
+        stubReportOverviewSums(currentYear, 50000, 3200, 8988);
68
+        when(breedingIndustryPastureScope.resolveAllowedPastureIds())
69
+                .thenReturn(Collections.singletonList(BreedingIndustryTestSamples.PASTURE_P1));
70
+        when(breedingIndustryStatsMapper.countPastureInScope(anyList())).thenReturn(12);
71
+        when(breedingIndustryStatsMapper.countInventory(anyList())).thenReturn(3);
72
+        when(breedingIndustryStatsMapper.countAnnualOutbound(anyList(), any(), any())).thenReturn(8);
73
+        when(breedingIndustryStatsMapper.countWarningYak(anyList(), any(), any())).thenReturn(0);
74
+        when(breedingIndustryStatsMapper.selectYakSnapshotsInScope(anyList()))
75
+                .thenReturn(Collections.singletonList(BreedingIndustryTestSamples.normalBull()));
76
+
77
+        BreedingIndustryDashboardVo vo = service.loadDashboard(false);
78
+
79
+        assertEquals(50000, vo.getOverview().getInventoryTotal());
80
+        assertEquals(3200, vo.getOverview().getAnnualOutbound());
81
+        assertEquals(8988, vo.getOverview().getPastureCount());
82
+    }
83
+
84
+    @Test
85
+    @DisplayName("ZCZX-YZCYSJ-UT-009 公母未标=原存栏时点总量")
53
     void ut009_genderSum()
86
     void ut009_genderSum()
54
     {
87
     {
88
+        int currentYear = Year.now().getValue();
55
         stubGrassland();
89
         stubGrassland();
90
+        stubReportOverviewSums(currentYear, 0, 0, 0);
56
         when(breedingIndustryPastureScope.resolveAllowedPastureIds())
91
         when(breedingIndustryPastureScope.resolveAllowedPastureIds())
57
                 .thenReturn(Collections.singletonList(BreedingIndustryTestSamples.PASTURE_P1));
92
                 .thenReturn(Collections.singletonList(BreedingIndustryTestSamples.PASTURE_P1));
58
         when(breedingIndustryStatsMapper.countPastureInScope(anyList())).thenReturn(1);
93
         when(breedingIndustryStatsMapper.countPastureInScope(anyList())).thenReturn(1);
@@ -64,7 +99,6 @@ class BreedingIndustryDashboardServiceImplTest
64
                 BreedingIndustryTestSamples.normalCow(),
99
                 BreedingIndustryTestSamples.normalCow(),
65
                 BreedingIndustryTestSamples.unknownGender()));
100
                 BreedingIndustryTestSamples.unknownGender()));
66
         BreedingIndustryDashboardVo vo = service.loadDashboard(false);
101
         BreedingIndustryDashboardVo vo = service.loadDashboard(false);
67
-        assertEquals(3, vo.getOverview().getInventoryTotal());
68
         assertEquals(1, vo.getInventoryChange().getBullCount());
102
         assertEquals(1, vo.getInventoryChange().getBullCount());
69
         assertEquals(1, vo.getInventoryChange().getCowCount());
103
         assertEquals(1, vo.getInventoryChange().getCowCount());
70
         assertEquals(1, vo.getInventoryChange().getUnknownGenderCount());
104
         assertEquals(1, vo.getInventoryChange().getUnknownGenderCount());
@@ -88,4 +122,14 @@ class BreedingIndustryDashboardServiceImplTest
88
         when(breedingIndustryStatsMapper.countInUseGrassland(any())).thenReturn(1);
122
         when(breedingIndustryStatsMapper.countInUseGrassland(any())).thenReturn(1);
89
         when(breedingIndustryStatsMapper.selectDegradationGroupCounts()).thenReturn(Collections.emptyList());
123
         when(breedingIndustryStatsMapper.selectDegradationGroupCounts()).thenReturn(Collections.emptyList());
90
     }
124
     }
125
+
126
+    private void stubReportOverviewSums(int currentYear, int inventory, int outbound, int farmerHousehold)
127
+    {
128
+        lenient().when(homeScreenStatsMapper.sumHerdInventoryYakTotalByStatYear(eq(currentYear - 1)))
129
+                .thenReturn(inventory);
130
+        lenient().when(homeScreenStatsMapper.sumOutboundReportYakCountByStatYear(eq(currentYear)))
131
+                .thenReturn(outbound);
132
+        lenient().when(homeScreenStatsMapper.sumOutboundReportFarmerHouseholdCountByStatYear(eq(currentYear)))
133
+                .thenReturn(farmerHousehold);
134
+    }
91
 }
135
 }

+ 3 - 0
baqing-admin/src/test/java/com/ruoyi/web/modules/screen/mapper/HomeScreenStatsMapperXmlTest.java

@@ -26,6 +26,9 @@ class HomeScreenStatsMapperXmlTest
26
         assertTrue(xml.contains("biz_subsidy_grant_record"));
26
         assertTrue(xml.contains("biz_subsidy_grant_record"));
27
         assertTrue(xml.contains("biz_yak_herd_inventory"));
27
         assertTrue(xml.contains("biz_yak_herd_inventory"));
28
         assertTrue(xml.contains("biz_yak_outbound_report"));
28
         assertTrue(xml.contains("biz_yak_outbound_report"));
29
+        assertTrue(xml.contains("sumHerdInventoryYakTotalByStatYear"));
30
+        assertTrue(xml.contains("sumOutboundReportYakCountByStatYear"));
31
+        assertTrue(xml.contains("sumOutboundReportFarmerHouseholdCountByStatYear"));
29
         assertTrue(xml.contains("status not in (2, 3)"));
32
         assertTrue(xml.contains("status not in (2, 3)"));
30
     }
33
     }
31
 }
34
 }

+ 42 - 0
baqing-admin/src/test/java/com/ruoyi/web/modules/screen/service/impl/HomeScreenServiceImplTest.java

@@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
5
 import static org.mockito.ArgumentMatchers.eq;
5
 import static org.mockito.ArgumentMatchers.eq;
6
 import static org.mockito.ArgumentMatchers.any;
6
 import static org.mockito.ArgumentMatchers.any;
7
 import static org.mockito.ArgumentMatchers.anyList;
7
 import static org.mockito.ArgumentMatchers.anyList;
8
+import static org.mockito.Mockito.lenient;
8
 import static org.mockito.Mockito.when;
9
 import static org.mockito.Mockito.when;
9
 
10
 
10
 import java.math.BigDecimal;
11
 import java.math.BigDecimal;
@@ -109,6 +110,37 @@ class HomeScreenServiceImplTest
109
         assertEquals(50, vo.getMarkers().get(0).getYakOutboundCount().intValue());
110
         assertEquals(50, vo.getMarkers().get(0).getYakOutboundCount().intValue());
110
     }
111
     }
111
 
112
 
113
+    @Test
114
+    @DisplayName("DP-SY-UT-027 产业总览存栏出栏取自填报合计")
115
+    void ut027_overviewFromReportTables()
116
+    {
117
+        int currentYear = Year.now().getValue();
118
+        stubBreedingWithOneYak();
119
+        stubOtherBlocks();
120
+        when(homeScreenStatsMapper.sumHerdInventoryYakTotalByStatYear(eq(currentYear - 1))).thenReturn(50000);
121
+        when(homeScreenStatsMapper.sumOutboundReportYakCountByStatYear(eq(currentYear))).thenReturn(3200);
122
+
123
+        HomeScreenDashboardVo vo = service.loadDashboard("2024");
124
+
125
+        assertEquals(50000, vo.getIndustryOverview().getInventoryTotal());
126
+        assertEquals(3200, vo.getIndustryOverview().getAnnualOutbound());
127
+    }
128
+
129
+    @Test
130
+    @DisplayName("DP-SY-UT-028 产业总览农牧户取自出栏填报合计")
131
+    void ut028_farmerHouseholdFromReportTable()
132
+    {
133
+        int currentYear = Year.now().getValue();
134
+        stubBreedingWithOneYak();
135
+        stubOtherBlocks();
136
+        when(homeScreenStatsMapper.sumOutboundReportFarmerHouseholdCountByStatYear(eq(currentYear)))
137
+                .thenReturn(8988);
138
+
139
+        HomeScreenDashboardVo vo = service.loadDashboard("2025");
140
+
141
+        assertEquals(8988, vo.getIndustryOverview().getPastureCount());
142
+    }
143
+
112
     private void stubBreedingEmpty()
144
     private void stubBreedingEmpty()
113
     {
145
     {
114
         when(breedingIndustryStatsMapper.selectActivePastureIds()).thenReturn(Collections.singletonList(1L));
146
         when(breedingIndustryStatsMapper.selectActivePastureIds()).thenReturn(Collections.singletonList(1L));
@@ -122,6 +154,11 @@ class HomeScreenServiceImplTest
122
         when(breedingIndustryStatsMapper.selectDegradationGroupCounts()).thenReturn(Collections.emptyList());
154
         when(breedingIndustryStatsMapper.selectDegradationGroupCounts()).thenReturn(Collections.emptyList());
123
         when(homeScreenStatsMapper.selectOutboundCountByMonth(anyList(), any(), any()))
155
         when(homeScreenStatsMapper.selectOutboundCountByMonth(anyList(), any(), any()))
124
                 .thenReturn(Collections.emptyList());
156
                 .thenReturn(Collections.emptyList());
157
+        int currentYear = Year.now().getValue();
158
+        lenient().when(homeScreenStatsMapper.sumHerdInventoryYakTotalByStatYear(eq(currentYear - 1))).thenReturn(0);
159
+        lenient().when(homeScreenStatsMapper.sumOutboundReportYakCountByStatYear(eq(currentYear))).thenReturn(0);
160
+        lenient().when(homeScreenStatsMapper.sumOutboundReportFarmerHouseholdCountByStatYear(eq(currentYear)))
161
+                .thenReturn(0);
125
     }
162
     }
126
 
163
 
127
     private void stubBreedingWithOneYak()
164
     private void stubBreedingWithOneYak()
@@ -143,6 +180,11 @@ class HomeScreenServiceImplTest
143
         when(breedingIndustryStatsMapper.selectDegradationGroupCounts()).thenReturn(Collections.emptyList());
180
         when(breedingIndustryStatsMapper.selectDegradationGroupCounts()).thenReturn(Collections.emptyList());
144
         when(homeScreenStatsMapper.selectOutboundCountByMonth(anyList(), any(), any()))
181
         when(homeScreenStatsMapper.selectOutboundCountByMonth(anyList(), any(), any()))
145
                 .thenReturn(Collections.emptyList());
182
                 .thenReturn(Collections.emptyList());
183
+        int currentYear = Year.now().getValue();
184
+        lenient().when(homeScreenStatsMapper.sumHerdInventoryYakTotalByStatYear(eq(currentYear - 1))).thenReturn(0);
185
+        lenient().when(homeScreenStatsMapper.sumOutboundReportYakCountByStatYear(eq(currentYear))).thenReturn(0);
186
+        lenient().when(homeScreenStatsMapper.sumOutboundReportFarmerHouseholdCountByStatYear(eq(currentYear)))
187
+                .thenReturn(0);
146
     }
188
     }
147
 
189
 
148
     private void stubOtherBlocks()
190
     private void stubOtherBlocks()

+ 6 - 6
doc/产业数据模型及服务/养殖产业数据/养殖产业数据功能需求.md

@@ -210,10 +210,10 @@ flowchart LR
210
 
210
 
211
 | 序号 | 指标名称 | 单位 | 统计口径 |
211
 | 序号 | 指标名称 | 单位 | 统计口径 |
212
 | --- | --- | --- | --- |
212
 | --- | --- | --- | --- |
213
-| 1 | 牦牛存栏总量 | 头 | **§2.3** 当前存栏头数 |
214
-| 2 | 牦牛年出栏量 | 头 | **§2.4** |
215
-| 3 | 预警牦牛数量 | 头 | **§2.5** |
216
-| 4 | 牧场数量 | 个 | **§2.6** |
213
+| 1 | 牦牛存栏总量 | 头 | `biz_yak_herd_inventory.yak_total` 全县乡镇合计;统计年 = **当前自然年 − 1**;接口字段 **`inventoryTotal`**(与大屏一致) |
214
+| 2 | 牦牛年出栏量 | 头 | `biz_yak_outbound_report.yak_outbound_count` 全县乡镇合计;统计年 = **当前自然年**;接口字段 **`annualOutbound`** |
215
+| 3 | 预警牦牛数量 | 头 | **§2.5**(仍为档案时点口径) |
216
+| 4 | 农牧户 | 户 | `biz_yak_outbound_report.farmer_household_count` 全县乡镇合计;统计年 = **当前自然年**;接口字段沿用 **`pastureCount`**(与大屏一致) |
217
 | 5 | 草场数量 | 个 | **§2.6** |
217
 | 5 | 草场数量 | 个 | **§2.6** |
218
 | 6 | 草场面积 | 亩 | **§2.6** |
218
 | 6 | 草场面积 | 亩 | **§2.6** |
219
 
219
 
@@ -234,7 +234,7 @@ flowchart LR
234
 | 公牛数量 | 头 | 当前存栏中性别为「公」的头数 |
234
 | 公牛数量 | 头 | 当前存栏中性别为「公」的头数 |
235
 | 母牛数量 | 头 | 当前存栏中性别为「母」的头数 |
235
 | 母牛数量 | 头 | 当前存栏中性别为「母」的头数 |
236
 
236
 
237
-**数量关系:** 公牛数量 + 母牛数量 + 未标注性别数量 = 牦牛存栏总量(**§5.2** 第 1 项)。当未标注性别数量 > 0 时,在公、母牛数量旁展示 **「未标注性别:N 头」**(N 为整数)。
237
+**数量关系:** 公牛数量 + 母牛数量 + 未标注性别数量 = **§2.3** 当前存栏时点总量(原档案口径,**不等同于** **§5.2** 第 1 项填报合计)。当未标注性别数量 > 0 时,在公、母牛数量旁展示 **「未标注性别:N 头」**(N 为整数)。
238
 
238
 
239
 #### 5.3.2 存栏趋势(折线图)
239
 #### 5.3.2 存栏趋势(折线图)
240
 
240
 
@@ -248,7 +248,7 @@ flowchart LR
248
 | 当前月 | 以 **统计日** 作为时点代入 **§2.10**(代替月末日) |
248
 | 当前月 | 以 **统计日** 作为时点代入 **§2.10**(代替月末日) |
249
 | 图例 | 「存栏数量」(或与原型一致) |
249
 | 图例 | 「存栏数量」(或与原型一致) |
250
 
250
 
251
-**与总览联动:** 当前月折线点宜与 **§5.2**「牦牛存栏总量」一致(同一统计日、同一纳入范围);已结束月份各点可小于或等于总览值
251
+**与总览联动:** 当前月折线点与 **§2.10** 月末还原存栏一致;**§5.2** 第 1 项已改为全县填报合计,**不要求**与折线当前月点数值相同
252
 
252
 
253
 ### 5.4 牦牛年龄结构
253
 ### 5.4 牦牛年龄结构
254
 
254
 

+ 7 - 6
doc/产业数据模型及服务/养殖产业数据/养殖产业数据技术方案.md

@@ -218,10 +218,10 @@ return success(dashboardService.loadDashboard(allowed, forceRefresh));
218
 
218
 
219
 | 字段 | 类型 | 说明 |
219
 | 字段 | 类型 | 说明 |
220
 | --- | --- | --- |
220
 | --- | --- | --- |
221
-| `inventoryTotal` | int | 牦牛存栏总量(头) |
222
-| `annualOutbound` | int | 牦牛年出栏量(头) |
223
-| `warningYakCount` | int | 预警牦牛数量(头) |
224
-| `pastureCount` | int | 牧场数量(个) |
221
+| `inventoryTotal` | int | 牦牛存栏总量(头);`biz_yak_herd_inventory` 全县合计,年份 = 当前自然年 **− 1**(与大屏 `inventoryTotal` 一致) |
222
+| `annualOutbound` | int | 牦牛年出栏量(头);`biz_yak_outbound_report` 全县 `yak_outbound_count` 合计,年份 = 当前自然年 |
223
+| `warningYakCount` | int | 预警牦牛数量(头);**§2.5** 档案口径 |
224
+| `pastureCount` | int | 农牧户(户);`biz_yak_outbound_report` 全县 `farmer_household_count` 合计,年份 = 当前自然年(与大屏 `pastureCount` 一致) |
225
 | `grasslandCount` | int | 草场数量(个) |
225
 | `grasslandCount` | int | 草场数量(个) |
226
 | `grasslandAreaMu` | decimal | 草场面积(亩) |
226
 | `grasslandAreaMu` | decimal | 草场面积(亩) |
227
 
227
 
@@ -328,8 +328,9 @@ return success(dashboardService.loadDashboard(allowed, forceRefresh));
328
 | --- | --- |
328
 | --- | --- |
329
 | **月龄** | `COALESCE(age_months, TIMESTAMPDIFF(MONTH, birth_date, statDate))`,`birth_date` 空则 0 |
329
 | **月龄** | `COALESCE(age_months, TIMESTAMPDIFF(MONTH, birth_date, statDate))`,`birth_date` 空则 0 |
330
 | **性别** | 入库值 `trim` 后精确匹配 `公`/`母`;第三方若写「公牛」「母牛」等,Service 层归一后再统计 |
330
 | **性别** | 入库值 `trim` 后精确匹配 `公`/`母`;第三方若写「公牛」「母牛」等,Service 层归一后再统计 |
331
-| **公母 vs 趋势** | `bullCount`/`cowCount`/`inventoryTotal`/`ageStructure` 用 **§2.3**(`asset_status=1`);`monthlyTrend` 用 **§2.10** 月末判定 |
332
-| **一致性** | `monthlyTrend` 最后一月 `inventoryCount` 应等于 `overview.inventoryTotal`(同统计日);实现后 UT 断言 |
331
+| **公母 vs 趋势** | `bullCount`/`cowCount`/`unknownGenderCount`/`ageStructure` 用 **§2.3** 原档案时点口径;`monthlyTrend` 用 **§2.10** 月末判定 |
332
+| **总览填报三项** | `overview.inventoryTotal` / `annualOutbound` / `pastureCount` 复用大屏 `HomeScreenStatsMapper` 填报合计 SQL;原 `countInventory` / `countAnnualOutbound` / `countPastureInScope` 在 Service 内暂保留调用 |
333
+| **一致性** | `monthlyTrend` 当前月点与 **§2.3** 时点存栏一致;**不要求**与 `overview.inventoryTotal`(填报合计)相同 |
333
 | **比率** | `ratio = count * 100.0 / NULLIF(total, 0)`,分母为 0 时 ratio=0 |
334
 | **比率** | `ratio = count * 100.0 / NULLIF(total, 0)`,分母为 0 时 ratio=0 |
334
 | **月末性能** | 优先 1 条 SQL + `CASE WHEN` 按月末分组,或 Java 循环 12 次;超过 3s 启用 **§2.3** 缓存 |
335
 | **月末性能** | 优先 1 条 SQL + `CASE WHEN` 按月末分组,或 Java 循环 12 次;超过 3s 启用 **§2.3** 缓存 |
335
 | **日志** | 看板接口可 `@Log(title="养殖产业数据", businessType=OTHER)` 或仅记录慢查询 |
336
 | **日志** | 看板接口可 `@Log(title="养殖产业数据", businessType=OTHER)` 或仅记录慢查询 |

+ 6 - 5
doc/产业数据模型及服务/养殖产业数据/养殖产业数据测试用例.md

@@ -49,7 +49,7 @@
49
 | 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
49
 | 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
50
 | --- | --- | --- | --- | --- | --- | --- | --- | --- |
50
 | --- | --- | --- | --- | --- | --- | --- | --- | --- |
51
 | ZCZX-YZCYSJ-UT-001 | 数据权限 | 全量可见牧场 ID 列表 | 单元测试 | JUnit5 | §2.6、§2.9 | Mock ADMIN 数据范围 | 调用 `BreedingIndustryPastureScope.resolve()` | 返回全部 `biz_status=1` 且未删牧场 ID;非 null 非空 |
51
 | ZCZX-YZCYSJ-UT-001 | 数据权限 | 全量可见牧场 ID 列表 | 单元测试 | JUnit5 | §2.6、§2.9 | Mock ADMIN 数据范围 | 调用 `BreedingIndustryPastureScope.resolve()` | 返回全部 `biz_status=1` 且未删牧场 ID;非 null 非空 |
52
-| ZCZX-YZCYSJ-UT-002 | 数据权限 | 可见牧场为空列表 | 单元测试 | JUnit5 | §2.6 空 `[]` | Mock U0 | `loadDashboard([])` | 牦牛/预警/牧场数=0;草场仍统计 |
52
+| ZCZX-YZCYSJ-UT-002 | 数据权限 | 可见牧场为空列表 | 单元测试 | JUnit5 | §2.6 空 `[]` | Mock U0 | `loadDashboard([])` | 填报三项仍返回;预警/图表=0;草场仍统计 |
53
 | ZCZX-YZCYSJ-UT-003 | 数据权限 | 部分牧场 IN 过滤 | 单元测试 | JUnit5 | §2.9 | 造数 P1、P2 | `allowed=[101]` 统计牦牛 | 仅 P1 档案计入 |
53
 | ZCZX-YZCYSJ-UT-003 | 数据权限 | 部分牧场 IN 过滤 | 单元测试 | JUnit5 | §2.9 | 造数 P1、P2 | `allowed=[101]` 统计牦牛 | 仅 P1 档案计入 |
54
 | ZCZX-YZCYSJ-UT-004 | 存栏 | 仅正常状态 | 单元测试 | JUnit5 | §2.3 | 含 status 1/2/3/4 | `countInventory` | 仅 `asset_status=1` |
54
 | ZCZX-YZCYSJ-UT-004 | 存栏 | 仅正常状态 | 单元测试 | JUnit5 | §2.3 | 含 status 1/2/3/4 | `countInventory` | 仅 `asset_status=1` |
55
 | ZCZX-YZCYSJ-UT-005 | 存栏 | 逻辑删除排除 | 单元测试 | JUnit5 | §2.3 | `del_flag=2` 档案 | 统计 | 不计入 |
55
 | ZCZX-YZCYSJ-UT-005 | 存栏 | 逻辑删除排除 | 单元测试 | JUnit5 | §2.3 | `del_flag=2` 档案 | 统计 | 不计入 |
@@ -60,7 +60,8 @@
60
 | ZCZX-YZCYSJ-UT-010 | 预警 | 周期下界 1/1 0:00 | 单元测试 | JUnit5 | §2.5 | `alert_time=当年1/1 00:00:00` | 统计 | 计入 |
60
 | ZCZX-YZCYSJ-UT-010 | 预警 | 周期下界 1/1 0:00 | 单元测试 | JUnit5 | §2.5 | `alert_time=当年1/1 00:00:00` | 统计 | 计入 |
61
 | ZCZX-YZCYSJ-UT-011 | 预警 | 周期外排除 | 单元测试 | JUnit5 | §2.5 | W-out | 统计 | 0 |
61
 | ZCZX-YZCYSJ-UT-011 | 预警 | 周期外排除 | 单元测试 | JUnit5 | §2.5 | W-out | 统计 | 0 |
62
 | ZCZX-YZCYSJ-UT-012 | 预警 | 须关联纳入档案 | 单元测试 | JUnit5 | §2.9 | 预警牛仅 P2、范围 P1 | `countWarning` | 0 |
62
 | ZCZX-YZCYSJ-UT-012 | 预警 | 须关联纳入档案 | 单元测试 | JUnit5 | §2.9 | 预警牛仅 P2、范围 P1 | `countWarning` | 0 |
63
-| ZCZX-YZCYSJ-UT-013 | 性别 | 公+母+未标=总量 | 单元测试 | JUnit5 | §5.3.1 | Y-N 公母 + Y-U | 分项求和 | 等于 `inventoryTotal` |
63
+| ZCZX-YZCYSJ-UT-013 | 性别 | 公+母+未标=原时点总量 | 单元测试 | JUnit5 | §5.3.1 | Y-N 公母 + Y-U | 分项求和 | 等于原 `countInventory` 口径,**不等同** `overview.inventoryTotal` |
64
+| ZCZX-YZCYSJ-UT-028 | 产业总览 | 填报三项与大屏一致 | 单元测试 | JUnit5 | §5.2 | Mock 填报合计 | `loadDashboard` | `inventoryTotal`/`annualOutbound`/`pastureCount` 与 `HomeScreenStatsMapper` 合计一致 |
64
 | ZCZX-YZCYSJ-UT-014 | 性别 | trim 后匹配 | 单元测试 | JUnit5 | 技术§4 | `gender=' 母 '` | `normalizeGender` | 计母牛 |
65
 | ZCZX-YZCYSJ-UT-014 | 性别 | trim 后匹配 | 单元测试 | JUnit5 | 技术§4 | `gender=' 母 '` | `normalizeGender` | 计母牛 |
65
 | ZCZX-YZCYSJ-UT-015 | 月龄 | birth_date 回退 | 单元测试 | JUnit5 | §2.3 | `age_months` 空、有生日 | `resolveAgeMonths(statDate)` | 与 `TIMESTAMPDIFF` 一致;无生日为 0 |
66
 | ZCZX-YZCYSJ-UT-015 | 月龄 | birth_date 回退 | 单元测试 | JUnit5 | §2.3 | `age_months` 空、有生日 | `resolveAgeMonths(statDate)` | 与 `TIMESTAMPDIFF` 一致;无生日为 0 |
66
 | ZCZX-YZCYSJ-UT-016 | 年龄分档 | 边界 0/6/7/12/13 | 单元测试 | JUnit5 | §5.4 | 各月龄一头 | `bandOf` | M1_6、M7_12(含12)、M13_PLUS |
67
 | ZCZX-YZCYSJ-UT-016 | 年龄分档 | 边界 0/6/7/12/13 | 单元测试 | JUnit5 | §5.4 | 各月龄一头 | `bandOf` | M1_6、M7_12(含12)、M13_PLUS |
@@ -70,7 +71,7 @@
70
 | ZCZX-YZCYSJ-UT-020 | 月末趋势 | 非正常且变更日空 | 单元测试 | JUnit5 | §2.10 | status=2、date 空 | 各月 | 恒 0 |
71
 | ZCZX-YZCYSJ-UT-020 | 月末趋势 | 非正常且变更日空 | 单元测试 | JUnit5 | §2.10 | status=2、date 空 | 各月 | 恒 0 |
71
 | ZCZX-YZCYSJ-UT-021 | 月末趋势 | 当前月用统计日 | 单元测试 | JUnit5 | §2.10、技术§4 | statDate=5-20 | 5 月点 vs `countInventory` | 相等 |
72
 | ZCZX-YZCYSJ-UT-021 | 月末趋势 | 当前月用统计日 | 单元测试 | JUnit5 | §2.10、技术§4 | statDate=5-20 | 5 月点 vs `countInventory` | 相等 |
72
 | ZCZX-YZCYSJ-UT-022 | 月末趋势 | 不生成未来月 | 单元测试 | JUnit5 | §2.2、§5.3.2 | 5 月环境 | `buildMonthlyTrend` | 仅 month 1~5 |
73
 | ZCZX-YZCYSJ-UT-022 | 月末趋势 | 不生成未来月 | 单元测试 | JUnit5 | §2.2、§5.3.2 | 5 月环境 | `buildMonthlyTrend` | 仅 month 1~5 |
73
-| ZCZX-YZCYSJ-UT-023 | 牧场 | 仅正常经营 | 单元测试 | JUnit5 | §2.6 | P-停、注销 | `pastureCount` | 不含停业/注销 |
74
+| ZCZX-YZCYSJ-UT-023 | 牧场 | 原口径暂保留 | 单元测试 | JUnit5 | §2.6 | P-停、注销 | `countPastureInScope` 仍调用 | 结果**不**写入 `pastureCount` |
74
 | ZCZX-YZCYSJ-UT-024 | 草场 | 面积空不参与求和 | 单元测试 | JUnit5 | §2.6 | 一条 `area_mu` null | `sumArea` | 不计该条 |
75
 | ZCZX-YZCYSJ-UT-024 | 草场 | 面积空不参与求和 | 单元测试 | JUnit5 | §2.6 | 一条 `area_mu` null | `sumArea` | 不计该条 |
75
 | ZCZX-YZCYSJ-UT-025 | 草场 | 可用=未退化+轻度 | 单元测试 | JUnit5 | §2.7 | 退化 1、2、3 各 1 | `availableCount` | 2 |
76
 | ZCZX-YZCYSJ-UT-025 | 草场 | 可用=未退化+轻度 | 单元测试 | JUnit5 | §2.7 | 退化 1、2、3 各 1 | `availableCount` | 2 |
76
 | ZCZX-YZCYSJ-UT-026 | 草场 | 禁牧闭区间 | 单元测试 | JUnit5 | §2.7 | 禁牧期内/外;统计日在期内 | `inUseCount` | 仅期外+轮牧/全年计入 |
77
 | ZCZX-YZCYSJ-UT-026 | 草场 | 禁牧闭区间 | 单元测试 | JUnit5 | §2.7 | 禁牧期内/外;统计日在期内 | `inUseCount` | 仅期外+轮牧/全年计入 |
@@ -87,7 +88,7 @@
87
 | ZCZX-YZCYSJ-API-001 | 看板 | 首次加载全量结构 | 接口测试 | Postman/curl | §5.1 正常流程 | UA 登录;造数完整 | `GET /dataModel/breedingIndustry/dashboard` | `code=200`;`data` 含 `statDate`、`statYear`、`overview`、`inventoryChange`、`ageStructure`、`grassland` |
88
 | ZCZX-YZCYSJ-API-001 | 看板 | 首次加载全量结构 | 接口测试 | Postman/curl | §5.1 正常流程 | UA 登录;造数完整 | `GET /dataModel/breedingIndustry/dashboard` | `code=200`;`data` 含 `statDate`、`statYear`、`overview`、`inventoryChange`、`ageStructure`、`grassland` |
88
 | ZCZX-YZCYSJ-API-002 | 看板 | 统计日与自然年 | 接口测试 | Postman | §2.2 | 已登录 | 同上 | `statDate`=`yyyy-MM-dd` 服务器当日;`statYear` 为公历年 |
89
 | ZCZX-YZCYSJ-API-002 | 看板 | 统计日与自然年 | 接口测试 | Postman | §2.2 | 已登录 | 同上 | `statDate`=`yyyy-MM-dd` 服务器当日;`statYear` 为公历年 |
89
 | ZCZX-YZCYSJ-API-003 | 产业总览 | 六项指标非负 | 接口测试 | Postman | §5.2 | 已知样本 | 读 `overview` 六字段 | 与 UT-004~012、023~024 手工值一致;`grasslandAreaMu`≥0 |
90
 | ZCZX-YZCYSJ-API-003 | 产业总览 | 六项指标非负 | 接口测试 | Postman | §5.2 | 已知样本 | 读 `overview` 六字段 | 与 UT-004~012、023~024 手工值一致;`grasslandAreaMu`≥0 |
90
-| ZCZX-YZCYSJ-API-004 | 存栏变动 | 公母未标关系 | 接口测试 | Postman | §5.3.1 | 含 Y-U | `bullCount+cowCount+unknownGenderCount=inventoryTotal` | 成立 |
91
+| ZCZX-YZCYSJ-API-004 | 存栏变动 | 公母未标关系 | 接口测试 | Postman | §5.3.1 | 含 Y-U | `bullCount+cowCount+unknownGenderCount` | 等于原档案时点存栏,**不要求**等于 `overview.inventoryTotal` |
91
 | ZCZX-YZCYSJ-API-005 | 存栏变动 | 月趋势仅至当前月 | 接口测试 | Postman | §5.3.2 | 统计日 5 月 | `monthlyTrend` | 长度=5;`month`∈{1..5};无 6~12 |
92
 | ZCZX-YZCYSJ-API-005 | 存栏变动 | 月趋势仅至当前月 | 接口测试 | Postman | §5.3.2 | 统计日 5 月 | `monthlyTrend` | 长度=5;`month`∈{1..5};无 6~12 |
92
 | ZCZX-YZCYSJ-API-006 | 存栏变动 | 当前月=总览存栏 | 接口测试 | Postman | §5.3.2、技术§4 | 已登录 | 比 `monthlyTrend` 最后项与 `inventoryTotal` | 相等 |
93
 | ZCZX-YZCYSJ-API-006 | 存栏变动 | 当前月=总览存栏 | 接口测试 | Postman | §5.3.2、技术§4 | 已登录 | 比 `monthlyTrend` 最后项与 `inventoryTotal` | 相等 |
93
 | ZCZX-YZCYSJ-API-007 | 年龄结构 | 固定三档 | 接口测试 | Postman | §5.4 | 已登录 | `ageStructure` | 长度=3;`bandCode` 为 M1_6/M7_12/M13_PLUS;count=0 也返回 |
94
 | ZCZX-YZCYSJ-API-007 | 年龄结构 | 固定三档 | 接口测试 | Postman | §5.4 | 已登录 | `ageStructure` | 长度=3;`bandCode` 为 M1_6/M7_12/M13_PLUS;count=0 也返回 |
@@ -118,7 +119,7 @@
118
 | 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
119
 | 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
119
 | --- | --- | --- | --- | --- | --- | --- | --- | --- |
120
 | --- | --- | --- | --- | --- | --- | --- | --- | --- |
120
 | ZCZX-YZCYSJ-UI-001 | 正常流程 | 菜单进入自动加载 | UI | Playwright+Chrome | §4.2、§5.1 | UA 有菜单;后端正常 | 登录→点击「养殖产业数据」 | 出现四块标题;`v-loading` 结束后六项卡片有数;无报错弹窗 |
121
 | ZCZX-YZCYSJ-UI-001 | 正常流程 | 菜单进入自动加载 | UI | Playwright+Chrome | §4.2、§5.1 | UA 有菜单;后端正常 | 登录→点击「养殖产业数据」 | 出现四块标题;`v-loading` 结束后六项卡片有数;无报错弹窗 |
121
-| ZCZX-YZCYSJ-UI-002 | 产业总览 | 六项单位与只读 | UI | Playwright+Chrome | §5.2 | 有数据 | 检查首屏卡片 | 名称含存栏/出栏/预警/牧场/草场/面积;单位头/个/亩;卡片不可点击跳转 |
122
+| ZCZX-YZCYSJ-UI-002 | 产业总览 | 六项单位与只读 | UI | Playwright+Chrome | §5.2 | 有数据 | 检查首屏卡片 | 名称含存栏/出栏/预警/农牧户/草场/面积;单位头/户/个/亩;卡片不可点击跳转 |
122
 | ZCZX-YZCYSJ-UI-003 | 存栏变动 | 公母与未标注文案 | UI | Playwright+Chrome | §5.3.1 | `unknownGenderCount>0` | 查看存栏区文案 | 显示公牛、母牛数量;显示「未标注性别:N 头」 |
123
 | ZCZX-YZCYSJ-UI-003 | 存栏变动 | 公母与未标注文案 | UI | Playwright+Chrome | §5.3.1 | `unknownGenderCount>0` | 查看存栏区文案 | 显示公牛、母牛数量;显示「未标注性别:N 头」 |
123
 | ZCZX-YZCYSJ-UI-004 | 存栏变动 | 未标注为 0 隐藏 | UI | Playwright+Chrome | §5.3.1、前端§4 | 造数全公母 | 打开页 | 不展示「未标注性别」文案 |
124
 | ZCZX-YZCYSJ-UI-004 | 存栏变动 | 未标注为 0 隐藏 | UI | Playwright+Chrome | §5.3.1、前端§4 | 造数全公母 | 打开页 | 不展示「未标注性别」文案 |
124
 | ZCZX-YZCYSJ-UI-005 | 存栏变动 | 折线止于当前月 | UI | Playwright+Chrome | §5.3.2 | 5 月环境 | 检查 ECharts 折线 X 轴与数据点 | 1~5 月有值;6~12 月无数据点/折线不延伸 |
125
 | ZCZX-YZCYSJ-UI-005 | 存栏变动 | 折线止于当前月 | UI | Playwright+Chrome | §5.3.2 | 5 月环境 | 检查 ECharts 折线 X 轴与数据点 | 1~5 月有值;6~12 月无数据点/折线不延伸 |

+ 3 - 3
doc/大屏/首页/大屏首页功能需求.md

@@ -266,9 +266,9 @@ flowchart TB
266
 
266
 
267
 | 序号 | 指标名称 | 单位 | 统计口径 |
267
 | 序号 | 指标名称 | 单位 | 统计口径 |
268
 | --- | --- | --- | --- |
268
 | --- | --- | --- | --- |
269
-| 1 | 牦牛存栏总量 | 头 | **§2.5** 时点存栏 |
270
-| 2 | 牦牛年出栏量 | 头 | **§2.5** 年内出栏 |
271
-| 3 | 牧场数量 | 个 | **§2.5** |
269
+| 1 | 牦牛存栏总量 | 头 | `biz_yak_herd_inventory.yak_total` 全县乡镇合计;统计年 = **当前自然年 − 1**;接口字段沿用 **`inventoryTotal`** |
270
+| 2 | 牦牛年出栏量 | 头 | `biz_yak_outbound_report.yak_outbound_count` 全县乡镇合计;统计年 = **当前自然年**;接口字段沿用 **`annualOutbound`** |
271
+| 3 | 农牧户 | 户 | `biz_yak_outbound_report.farmer_household_count` 全县乡镇合计;统计年 = **当前自然年**;接口字段沿用 **`pastureCount`** |
272
 | 4 | 草场数量 | 个 | **§2.5** |
272
 | 4 | 草场数量 | 个 | **§2.5** |
273
 | 5 | 草场面积 | 亩 | **§2.5** |
273
 | 5 | 草场面积 | 亩 | **§2.5** |
274
 | 6 | 供应商数量 | 个 | **§2.6** 实时快照 |
274
 | 6 | 供应商数量 | 个 | **§2.6** 实时快照 |

+ 6 - 4
doc/大屏/首页/大屏首页技术方案.md

@@ -222,9 +222,10 @@
222
 
222
 
223
 | 字段 | 说明 |
223
 | 字段 | 说明 |
224
 | --- | --- |
224
 | --- | --- |
225
-| `inventoryTotal` | 牦牛存栏(头),时点 |
226
-| `annualOutbound` | 年出栏(头),**Y** 年内 |
227
-| `pastureCount` / `grasslandCount` / `grasslandAreaMu` | 牧场、草场、面积 |
225
+| `inventoryTotal` | 牦牛存栏(头),`biz_yak_herd_inventory` 全县 `yak_total` 合计;年份 = 当前自然年 **− 1**;**字段名不变** |
226
+| `annualOutbound` | 年出栏(头),`biz_yak_outbound_report` 全县 `yak_outbound_count` 合计;年份 = 当前自然年;**字段名不变** |
227
+| `pastureCount` | 农牧户(户),`biz_yak_outbound_report` 全县 `farmer_household_count` 合计;年份 = 当前自然年;**字段名不变** |
228
+| `grasslandCount` / `grasslandAreaMu` | 草场、面积 |
228
 | `supplierCount` / `distributorCount` / `tradeMarketCount` | 流通三项**实时**快照 |
229
 | `supplierCount` / `distributorCount` / `tradeMarketCount` | 流通三项**实时**快照 |
229
 
230
 
230
 #### 3.1.2 `inventoryChange`
231
 #### 3.1.2 `inventoryChange`
@@ -344,7 +345,8 @@
344
 7. agriClassroom = tech video count + training publish count by year
345
 7. agriClassroom = tech video count + training publish count by year
345
 8. trainingTrend = training by month; hasTrainingData
346
 8. trainingTrend = training by month; hasTrainingData
346
 9. availableYears = union(distinct years from各源表, current year)
347
 9. availableYears = union(distinct years from各源表, current year)
347
-10. return AjaxResult.success(vo)
348
+10. industryOverview 填报合计填入既有字段:`inventoryTotal` / `annualOutbound` / `pastureCount`(农牧户);原牧场/快照口径在 Service 内暂保留
349
+11. return AjaxResult.success(vo)
348
 ```
350
 ```
349
 
351
 
350
 ---
352
 ---

+ 2 - 0
doc/大屏/首页/大屏首页测试用例.md

@@ -77,6 +77,8 @@
77
 | DP-SY-UT-024 | 约束 | 无标准详情接口 | 单元测试 | JUnit5 | §5.9 | 检查 Controller 映射 | 无 `GET .../standards/{id}` | 仅列表分页 |
77
 | DP-SY-UT-024 | 约束 | 无标准详情接口 | 单元测试 | JUnit5 | §5.9 | 检查 Controller 映射 | 无 `GET .../standards/{id}` | 仅列表分页 |
78
 | DP-SY-UT-025 | 缓存 | forceRefresh | 单元测试 | JUnit5 | 技术§2.3 | 启用缓存未过期 | `loadDashboard(2026,true)` | 跳过缓存重新聚合 |
78
 | DP-SY-UT-025 | 缓存 | forceRefresh | 单元测试 | JUnit5 | 技术§2.3 | 启用缓存未过期 | `loadDashboard(2026,true)` | 跳过缓存重新聚合 |
79
 | DP-SY-UT-026 | 地图标注 | 年份与字段 | 单元测试 | JUnit5 | §5.12 | Mock Mapper 返回 1 乡镇 | `loadTownMapMarkers()` | `herdStatYear=当前年-1`;`outboundStatYear=当前年`;含 `townDeptId`/`townName`/存栏/出栏 |
79
 | DP-SY-UT-026 | 地图标注 | 年份与字段 | 单元测试 | JUnit5 | §5.12 | Mock Mapper 返回 1 乡镇 | `loadTownMapMarkers()` | `herdStatYear=当前年-1`;`outboundStatYear=当前年`;含 `townDeptId`/`townName`/存栏/出栏 |
80
+| DP-SY-UT-027 | 产业总览 | 填报合计 | 单元测试 | JUnit5 | §5.2 | Mock 填报合计 | `loadDashboard` | `inventoryTotal`/`annualOutbound` 取自填报表;与 **Y** 无关 |
81
+| DP-SY-UT-028 | 产业总览 | 农牧户合计 | 单元测试 | JUnit5 | §5.2 | Mock 出栏填报农牧户户数 | `loadDashboard` | `pastureCount` 为当年全县农牧户合计(字段名不变) |
80
 
82
 
81
 ---
83
 ---
82
 
84