Procházet zdrojové kódy

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

xsh_1997 před 1 měsícem
rodič
revize
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 27
 import com.ruoyi.web.modules.industryservice.support.BreedingIndustryPastureScope;
28 28
 import com.ruoyi.web.modules.industryservice.support.BreedingIndustryRules;
29 29
 import com.ruoyi.web.modules.industryservice.support.GrasslandRules;
30
+import com.ruoyi.web.modules.screen.mapper.HomeScreenStatsMapper;
30 31
 
31 32
 @Service
32 33
 public class BreedingIndustryDashboardServiceImpl implements IBreedingIndustryDashboardService
@@ -43,6 +44,9 @@ public class BreedingIndustryDashboardServiceImpl implements IBreedingIndustryDa
43 44
     @Autowired
44 45
     private BreedingIndustryPastureScope breedingIndustryPastureScope;
45 46
 
47
+    @Autowired
48
+    private HomeScreenStatsMapper homeScreenStatsMapper;
49
+
46 50
     @Override
47 51
     public BreedingIndustryDashboardVo loadDashboard(boolean forceRefresh)
48 52
     {
@@ -58,14 +62,20 @@ public class BreedingIndustryDashboardServiceImpl implements IBreedingIndustryDa
58 62
             fillGrasslandOverview(overview);
59 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 74
             BreedingIndustryInventoryChangeVo inventoryChange = new BreedingIndustryInventoryChangeVo();
62 75
             List<BreedingIndustryAgeBandVo> ageStructure = buildEmptyAgeStructure();
63 76
             if (allowedPastureIds.isEmpty())
64 77
             {
65
-                overview.setInventoryTotal(0);
66
-                overview.setAnnualOutbound(0);
67 78
                 overview.setWarningYakCount(0);
68
-                overview.setPastureCount(0);
69 79
                 inventoryChange.setBullCount(0);
70 80
                 inventoryChange.setCowCount(0);
71 81
                 inventoryChange.setUnknownGenderCount(0);
@@ -75,12 +85,12 @@ public class BreedingIndustryDashboardServiceImpl implements IBreedingIndustryDa
75 85
             {
76 86
                 Date yearStart = toDate(statDate.withDayOfYear(1));
77 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 94
                 overview.setWarningYakCount(
85 95
                         breedingIndustryStatsMapper.countWarningYak(allowedPastureIds, yearStart, statDateEnd));
86 96
 
@@ -108,14 +118,13 @@ public class BreedingIndustryDashboardServiceImpl implements IBreedingIndustryDa
108 118
                         cow++;
109 119
                     }
110 120
                 }
111
-                int total = overview.getInventoryTotal();
112 121
                 inventoryChange.setBullCount(bull);
113 122
                 inventoryChange.setCowCount(cow);
114
-                inventoryChange.setUnknownGenderCount(Math.max(0, total - bull - cow));
123
+                inventoryChange.setUnknownGenderCount(Math.max(0, legacyInventoryTotal - bull - cow));
115 124
                 inventoryChange.setMonthlyTrend(BreedingIndustryCalculator.buildMonthlyTrend(snapshots, statDate));
116 125
 
117 126
                 Map<String, Integer> bandCounts = BreedingIndustryCalculator.countAgeBands(snapshots, statDate);
118
-                ageStructure = BreedingIndustryCalculator.buildAgeStructure(bandCounts, total);
127
+                ageStructure = BreedingIndustryCalculator.buildAgeStructure(bandCounts, legacyInventoryTotal);
119 128
             }
120 129
             dashboard.setInventoryChange(inventoryChange);
121 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 7
 public class HomeScreenIndustryOverviewVo
8 8
 {
9
-    /** 截止 statDate 牦牛存栏总头数 */
9
+    /** 产业总览「牦牛存栏总量」(头):上一年全县存栏填报 `yak_total` 合计;字段名沿用 `inventoryTotal` */
10 10
     private int inventoryTotal;
11
-    /** 当年出栏总头数 */
11
+    /** 产业总览「牦牛年出栏量」(头):当年全县出栏填报 `yak_outbound_count` 合计;字段名沿用 `annualOutbound` */
12 12
     private int annualOutbound;
13
-    /** 有效牧场数 */
13
+    /** 产业总览「农牧户」展示位(户):当年全县出栏填报 `farmer_household_count` 合计;字段名沿用 `pastureCount` */
14 14
     private int pastureCount;
15 15
     /** 草场块数 */
16 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 70
     List<HomeScreenTownMapMarkerVo> selectTownMapMarkers(
71 71
             @Param("herdStatYear") int herdStatYear,
72 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 68
         overview.setSupplierCount(homeScreenStatsMapper.countSupplierForScreen());
69 69
         overview.setDistributorCount(homeScreenStatsMapper.countDistributorForScreen());
70 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 76
         overview.setGrasslandCount(breedingIndustryStatsMapper.countGrassland());
75 77
         BigDecimal area = breedingIndustryStatsMapper.sumGrasslandAreaMu();
76 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 87
         HomeScreenInventoryChangeVo inventoryChange = new HomeScreenInventoryChangeVo();
79 88
         if (allowedPastureIds.isEmpty())
80 89
         {
81 90
             // 无有效牧场时牦牛相关区块全部置零
82
-            overview.setInventoryTotal(0);
83
-            overview.setAnnualOutbound(0);
84 91
             inventoryChange.setBullCount(0);
85 92
             inventoryChange.setCowCount(0);
86 93
             inventoryChange.setUnknownGenderCount(0);
@@ -91,14 +98,14 @@ public class HomeScreenServiceImpl implements IHomeScreenService
91 98
         }
92 99
         else
93 100
         {
94
-            int inventoryTotal = HomeScreenSupport.countInventoryAtDate(snapshots, statDate);
101
+            // 原时点存栏口径(暂保留,供存栏变动/年龄结构等区块使用)
102
+            int inventoryTotalAtDate = HomeScreenSupport.countInventoryAtDate(snapshots, statDate);
95 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 106
             inventoryChange.setBullCount(gender[0]);
100 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 109
             inventoryChange.setMonthlyTrend(HomeScreenSupport.buildBullCowMonthlyTrend(snapshots, statDate));
103 110
             vo.setAgeStructure(HomeScreenSupport.buildAgeStructureAtDate(snapshots, statDate));
104 111
             vo.setOutboundMonthly(HomeScreenSupport.buildOutboundMonthly(

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

@@ -163,4 +163,22 @@
163 163
           and d.status = '0'
164 164
         order by d.order_num asc, d.dept_id asc
165 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 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 3
 import static org.junit.jupiter.api.Assertions.assertEquals;
4 4
 import static org.junit.jupiter.api.Assertions.assertThrows;
5
+import static org.mockito.ArgumentMatchers.eq;
5 6
 import static org.mockito.ArgumentMatchers.any;
6 7
 import static org.mockito.ArgumentMatchers.anyList;
8
+import static org.mockito.Mockito.lenient;
7 9
 import static org.mockito.Mockito.when;
8 10
 
9 11
 import java.math.BigDecimal;
12
+import java.time.Year;
10 13
 import java.util.Arrays;
11 14
 import java.util.Collections;
12 15
 import org.junit.jupiter.api.DisplayName;
@@ -21,6 +24,7 @@ import com.ruoyi.web.modules.industryservice.domain.vo.BreedingIndustryDashboard
21 24
 import com.ruoyi.web.modules.industryservice.mapper.BreedingIndustryStatsMapper;
22 25
 import com.ruoyi.web.modules.industryservice.support.BreedingIndustryPastureScope;
23 26
 import com.ruoyi.web.modules.industryservice.support.BreedingIndustryRules;
27
+import com.ruoyi.web.modules.screen.mapper.HomeScreenStatsMapper;
24 28
 
25 29
 @ExtendWith(MockitoExtension.class)
26 30
 @DisplayName("BreedingIndustryDashboardServiceImpl")
@@ -32,6 +36,9 @@ class BreedingIndustryDashboardServiceImplTest
32 36
     @Mock
33 37
     private BreedingIndustryPastureScope breedingIndustryPastureScope;
34 38
 
39
+    @Mock
40
+    private HomeScreenStatsMapper homeScreenStatsMapper;
41
+
35 42
     @InjectMocks
36 43
     private BreedingIndustryDashboardServiceImpl service;
37 44
 
@@ -39,20 +46,48 @@ class BreedingIndustryDashboardServiceImplTest
39 46
     @DisplayName("ZCZX-YZCYSJ-UT-002 可见牧场为空")
40 47
     void ut002_emptyScope()
41 48
     {
49
+        int currentYear = Year.now().getValue();
42 50
         stubGrassland();
51
+        stubReportOverviewSums(currentYear, 100, 50, 20);
43 52
         when(breedingIndustryPastureScope.resolveAllowedPastureIds()).thenReturn(Collections.emptyList());
44 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 57
         assertEquals(3, vo.getOverview().getGrasslandCount());
48 58
         assertEquals(0, vo.getInventoryChange().getMonthlyTrend().size());
49 59
     }
50 60
 
51 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 86
     void ut009_genderSum()
54 87
     {
88
+        int currentYear = Year.now().getValue();
55 89
         stubGrassland();
90
+        stubReportOverviewSums(currentYear, 0, 0, 0);
56 91
         when(breedingIndustryPastureScope.resolveAllowedPastureIds())
57 92
                 .thenReturn(Collections.singletonList(BreedingIndustryTestSamples.PASTURE_P1));
58 93
         when(breedingIndustryStatsMapper.countPastureInScope(anyList())).thenReturn(1);
@@ -64,7 +99,6 @@ class BreedingIndustryDashboardServiceImplTest
64 99
                 BreedingIndustryTestSamples.normalCow(),
65 100
                 BreedingIndustryTestSamples.unknownGender()));
66 101
         BreedingIndustryDashboardVo vo = service.loadDashboard(false);
67
-        assertEquals(3, vo.getOverview().getInventoryTotal());
68 102
         assertEquals(1, vo.getInventoryChange().getBullCount());
69 103
         assertEquals(1, vo.getInventoryChange().getCowCount());
70 104
         assertEquals(1, vo.getInventoryChange().getUnknownGenderCount());
@@ -88,4 +122,14 @@ class BreedingIndustryDashboardServiceImplTest
88 122
         when(breedingIndustryStatsMapper.countInUseGrassland(any())).thenReturn(1);
89 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 26
         assertTrue(xml.contains("biz_subsidy_grant_record"));
27 27
         assertTrue(xml.contains("biz_yak_herd_inventory"));
28 28
         assertTrue(xml.contains("biz_yak_outbound_report"));
29
+        assertTrue(xml.contains("sumHerdInventoryYakTotalByStatYear"));
30
+        assertTrue(xml.contains("sumOutboundReportYakCountByStatYear"));
31
+        assertTrue(xml.contains("sumOutboundReportFarmerHouseholdCountByStatYear"));
29 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 5
 import static org.mockito.ArgumentMatchers.eq;
6 6
 import static org.mockito.ArgumentMatchers.any;
7 7
 import static org.mockito.ArgumentMatchers.anyList;
8
+import static org.mockito.Mockito.lenient;
8 9
 import static org.mockito.Mockito.when;
9 10
 
10 11
 import java.math.BigDecimal;
@@ -109,6 +110,37 @@ class HomeScreenServiceImplTest
109 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 144
     private void stubBreedingEmpty()
113 145
     {
114 146
         when(breedingIndustryStatsMapper.selectActivePastureIds()).thenReturn(Collections.singletonList(1L));
@@ -122,6 +154,11 @@ class HomeScreenServiceImplTest
122 154
         when(breedingIndustryStatsMapper.selectDegradationGroupCounts()).thenReturn(Collections.emptyList());
123 155
         when(homeScreenStatsMapper.selectOutboundCountByMonth(anyList(), any(), any()))
124 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 164
     private void stubBreedingWithOneYak()
@@ -143,6 +180,11 @@ class HomeScreenServiceImplTest
143 180
         when(breedingIndustryStatsMapper.selectDegradationGroupCounts()).thenReturn(Collections.emptyList());
144 181
         when(homeScreenStatsMapper.selectOutboundCountByMonth(anyList(), any(), any()))
145 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 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 217
 | 5 | 草场数量 | 个 | **§2.6** |
218 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 239
 #### 5.3.2 存栏趋势(折线图)
240 240
 
@@ -248,7 +248,7 @@ flowchart LR
248 248
 | 当前月 | 以 **统计日** 作为时点代入 **§2.10**(代替月末日) |
249 249
 | 图例 | 「存栏数量」(或与原型一致) |
250 250
 
251
-**与总览联动:** 当前月折线点宜与 **§5.2**「牦牛存栏总量」一致(同一统计日、同一纳入范围);已结束月份各点可小于或等于总览值
251
+**与总览联动:** 当前月折线点与 **§2.10** 月末还原存栏一致;**§5.2** 第 1 项已改为全县填报合计,**不要求**与折线当前月点数值相同
252 252
 
253 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 225
 | `grasslandCount` | int | 草场数量(个) |
226 226
 | `grasslandAreaMu` | decimal | 草场面积(亩) |
227 227
 
@@ -328,8 +328,9 @@ return success(dashboardService.loadDashboard(allowed, forceRefresh));
328 328
 | --- | --- |
329 329
 | **月龄** | `COALESCE(age_months, TIMESTAMPDIFF(MONTH, birth_date, statDate))`,`birth_date` 空则 0 |
330 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 334
 | **比率** | `ratio = count * 100.0 / NULLIF(total, 0)`,分母为 0 时 ratio=0 |
334 335
 | **月末性能** | 优先 1 条 SQL + `CASE WHEN` 按月末分组,或 Java 循环 12 次;超过 3s 启用 **§2.3** 缓存 |
335 336
 | **日志** | 看板接口可 `@Log(title="养殖产业数据", businessType=OTHER)` 或仅记录慢查询 |

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

@@ -49,7 +49,7 @@
49 49
 | 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
50 50
 | --- | --- | --- | --- | --- | --- | --- | --- | --- |
51 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 53
 | ZCZX-YZCYSJ-UT-003 | 数据权限 | 部分牧场 IN 过滤 | 单元测试 | JUnit5 | §2.9 | 造数 P1、P2 | `allowed=[101]` 统计牦牛 | 仅 P1 档案计入 |
54 54
 | ZCZX-YZCYSJ-UT-004 | 存栏 | 仅正常状态 | 单元测试 | JUnit5 | §2.3 | 含 status 1/2/3/4 | `countInventory` | 仅 `asset_status=1` |
55 55
 | ZCZX-YZCYSJ-UT-005 | 存栏 | 逻辑删除排除 | 单元测试 | JUnit5 | §2.3 | `del_flag=2` 档案 | 统计 | 不计入 |
@@ -60,7 +60,8 @@
60 60
 | ZCZX-YZCYSJ-UT-010 | 预警 | 周期下界 1/1 0:00 | 单元测试 | JUnit5 | §2.5 | `alert_time=当年1/1 00:00:00` | 统计 | 计入 |
61 61
 | ZCZX-YZCYSJ-UT-011 | 预警 | 周期外排除 | 单元测试 | JUnit5 | §2.5 | W-out | 统计 | 0 |
62 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 65
 | ZCZX-YZCYSJ-UT-014 | 性别 | trim 后匹配 | 单元测试 | JUnit5 | 技术§4 | `gender=' 母 '` | `normalizeGender` | 计母牛 |
65 66
 | ZCZX-YZCYSJ-UT-015 | 月龄 | birth_date 回退 | 单元测试 | JUnit5 | §2.3 | `age_months` 空、有生日 | `resolveAgeMonths(statDate)` | 与 `TIMESTAMPDIFF` 一致;无生日为 0 |
66 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 71
 | ZCZX-YZCYSJ-UT-020 | 月末趋势 | 非正常且变更日空 | 单元测试 | JUnit5 | §2.10 | status=2、date 空 | 各月 | 恒 0 |
71 72
 | ZCZX-YZCYSJ-UT-021 | 月末趋势 | 当前月用统计日 | 单元测试 | JUnit5 | §2.10、技术§4 | statDate=5-20 | 5 月点 vs `countInventory` | 相等 |
72 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 75
 | ZCZX-YZCYSJ-UT-024 | 草场 | 面积空不参与求和 | 单元测试 | JUnit5 | §2.6 | 一条 `area_mu` null | `sumArea` | 不计该条 |
75 76
 | ZCZX-YZCYSJ-UT-025 | 草场 | 可用=未退化+轻度 | 单元测试 | JUnit5 | §2.7 | 退化 1、2、3 各 1 | `availableCount` | 2 |
76 77
 | ZCZX-YZCYSJ-UT-026 | 草场 | 禁牧闭区间 | 单元测试 | JUnit5 | §2.7 | 禁牧期内/外;统计日在期内 | `inUseCount` | 仅期外+轮牧/全年计入 |
@@ -87,7 +88,7 @@
87 88
 | ZCZX-YZCYSJ-API-001 | 看板 | 首次加载全量结构 | 接口测试 | Postman/curl | §5.1 正常流程 | UA 登录;造数完整 | `GET /dataModel/breedingIndustry/dashboard` | `code=200`;`data` 含 `statDate`、`statYear`、`overview`、`inventoryChange`、`ageStructure`、`grassland` |
88 89
 | ZCZX-YZCYSJ-API-002 | 看板 | 统计日与自然年 | 接口测试 | Postman | §2.2 | 已登录 | 同上 | `statDate`=`yyyy-MM-dd` 服务器当日;`statYear` 为公历年 |
89 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 92
 | ZCZX-YZCYSJ-API-005 | 存栏变动 | 月趋势仅至当前月 | 接口测试 | Postman | §5.3.2 | 统计日 5 月 | `monthlyTrend` | 长度=5;`month`∈{1..5};无 6~12 |
92 93
 | ZCZX-YZCYSJ-API-006 | 存栏变动 | 当前月=总览存栏 | 接口测试 | Postman | §5.3.2、技术§4 | 已登录 | 比 `monthlyTrend` 最后项与 `inventoryTotal` | 相等 |
93 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 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 123
 | ZCZX-YZCYSJ-UI-003 | 存栏变动 | 公母与未标注文案 | UI | Playwright+Chrome | §5.3.1 | `unknownGenderCount>0` | 查看存栏区文案 | 显示公牛、母牛数量;显示「未标注性别:N 头」 |
123 124
 | ZCZX-YZCYSJ-UI-004 | 存栏变动 | 未标注为 0 隐藏 | UI | Playwright+Chrome | §5.3.1、前端§4 | 造数全公母 | 打开页 | 不展示「未标注性别」文案 |
124 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 272
 | 4 | 草场数量 | 个 | **§2.5** |
273 273
 | 5 | 草场面积 | 亩 | **§2.5** |
274 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 229
 | `supplierCount` / `distributorCount` / `tradeMarketCount` | 流通三项**实时**快照 |
229 230
 
230 231
 #### 3.1.2 `inventoryChange`
@@ -344,7 +345,8 @@
344 345
 7. agriClassroom = tech video count + training publish count by year
345 346
 8. trainingTrend = training by month; hasTrainingData
346 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 77
 | DP-SY-UT-024 | 约束 | 无标准详情接口 | 单元测试 | JUnit5 | §5.9 | 检查 Controller 映射 | 无 `GET .../standards/{id}` | 仅列表分页 |
78 78
 | DP-SY-UT-025 | 缓存 | forceRefresh | 单元测试 | JUnit5 | 技术§2.3 | 启用缓存未过期 | `loadDashboard(2026,true)` | 跳过缓存重新聚合 |
79 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