Commit 97c1bdaf3d7af4bfe9347fb332bed0e577db83ff

Authored by “wangming”
1 parent 80757fb6

feat: 优化库存扣减逻辑和报销导出功能

- 修复库存扣减逻辑:只有已领取状态才扣减库存
  - 修改LqInventoryService、LqProductService、LqInventoryUsageService中所有计算已使用数量的地方
  - 只统计IsReceived=1的使用记录,确保库存扣减准确
  - 添加System.Collections.Generic命名空间引用

- 优化报销导出功能
  - 添加购买门店字段(purchaseStoreId、purchaseStoreName)
  - 区分申请门店和购买门店两个字段
  - 修复文件路径问题:使用项目根目录下的ExportFiles文件夹
  - 修复FromQuery参数导致的BadImageFormatException错误

- 修复编译错误
  - 添加System.IO命名空间引用
  - 修复List<dynamic>类型转换问题
Showing 25 changed files with 1334 additions and 652 deletions
ExportFiles/仓库使用记录_20251226210512.xls 0 → 100644
No preview for this file type
ExportFiles/报销表明细_2025年12月.xls 0 → 100644
No preview for this file type
antis-ncc-admin/src/views/extend/annualSummary/dashboard/index.vue
... ... @@ -8,33 +8,14 @@
8 8 <div class="header-right">
9 9 <el-form :inline="true" :model="query" class="search-form-compact">
10 10 <el-form-item label="年度">
11   - <el-date-picker
12   - v-model="query.year"
13   - type="year"
14   - value-format="yyyy"
15   - placeholder="选择年度"
16   - clearable
17   - size="mini"
18   - @change="handleQueryChange"
19   - style="width: 150px"
20   - />
  11 + <el-date-picker v-model="query.year" type="year" value-format="yyyy" placeholder="选择年度" clearable
  12 + size="mini" @change="handleQueryChange" style="width: 150px" />
21 13 </el-form-item>
22 14 <el-form-item label="门店名称">
23   - <el-select
24   - v-model="query.storeName"
25   - placeholder="请选择门店"
26   - clearable
27   - filterable
28   - size="mini"
29   - @change="handleQueryChange"
30   - style="width: 200px"
31   - >
32   - <el-option
33   - v-for="store in storeOptions"
34   - :key="store.id"
35   - :label="store.fullName"
36   - :value="store.fullName"
37   - />
  15 + <el-select v-model="query.storeName" placeholder="请选择门店" clearable filterable size="mini"
  16 + @change="handleQueryChange" style="width: 200px">
  17 + <el-option v-for="store in storeOptions" :key="store.id" :label="store.fullName"
  18 + :value="store.fullName" />
38 19 </el-select>
39 20 </el-form-item>
40 21 <el-form-item>
... ... @@ -78,478 +59,356 @@
78 59  
79 60 <!-- 内容区域 -->
80 61 <div class="dashboard-content">
81   - <!-- 月度趋势分析 -->
82   - <div v-show="activeMenu === 'monthly-trend'" class="content-panel">
83   - <div class="panel-body">
84   - <el-row :gutter="16">
85   - <el-col :span="12">
86   - <el-card shadow="hover" class="dashboard-card chart-card">
87   - <div slot="header" class="card-title">
88   - <i class="el-icon-trophy"></i>
89   - <span>业绩走势对比</span>
90   - </div>
91   - <div id="perfTrend" class="chart-box"></div>
92   - </el-card>
93   - </el-col>
94   - <el-col :span="12">
95   - <el-card shadow="hover" class="dashboard-card chart-card">
96   - <div slot="header" class="card-title">
97   - <i class="el-icon-shopping-cart-2"></i>
98   - <span>消耗走势对比</span>
99   - </div>
100   - <div id="consumeTrend" class="chart-box"></div>
101   - </el-card>
102   - </el-col>
103   - </el-row>
104   - <el-row :gutter="16" style="margin-top: 16px">
105   - <el-col :span="8">
106   - <el-card shadow="hover" class="dashboard-card chart-card">
107   - <div slot="header" class="card-title">
108   - <i class="el-icon-user"></i>
109   - <span>客头数走势</span>
110   - </div>
111   - <div id="headCountTrend" class="chart-box"></div>
112   - </el-card>
113   - </el-col>
114   - <el-col :span="8">
115   - <el-card shadow="hover" class="dashboard-card chart-card">
116   - <div slot="header" class="card-title">
117   - <i class="el-icon-user-solid"></i>
118   - <span>客次数走势</span>
119   - </div>
120   - <div id="personTimeTrend" class="chart-box"></div>
121   - </el-card>
122   - </el-col>
123   - <el-col :span="8">
124   - <el-card shadow="hover" class="dashboard-card chart-card">
125   - <div slot="header" class="card-title">
126   - <i class="el-icon-goods"></i>
127   - <span>项目数走势</span>
128   - </div>
129   - <div id="projectCountTrend" class="chart-box"></div>
130   - </el-card>
131   - </el-col>
132   - </el-row>
133   - <div class="table-section" style="margin-top: 16px">
134   - <el-card shadow="hover" class="dashboard-card table-card">
  62 + <!-- 月度趋势分析 -->
  63 + <div v-show="activeMenu === 'monthly-trend'" class="content-panel">
  64 + <div class="panel-body">
  65 + <el-row :gutter="16">
  66 + <el-col :span="12">
  67 + <el-card shadow="hover" class="dashboard-card chart-card">
135 68 <div slot="header" class="card-title">
136   - <i class="el-icon-s-grid"></i>
137   - <span>月度趋势数据列表</span>
  69 + <i class="el-icon-trophy"></i>
  70 + <span>业绩走势对比</span>
138 71 </div>
139   - <NCC-table
140   - v-loading="trendTableLoading"
141   - :data="trendTableData"
142   - border
143   - stripe
144   - style="width: 100%"
145   - >
146   - <el-table-column
147   - v-for="col in trendTableColumns"
148   - :key="col.prop"
149   - :prop="col.prop"
150   - :label="col.label"
151   - :width="col.width"
152   - :align="col.align || 'center'"
153   - >
154   - <template slot-scope="scope">
155   - <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
156   - <span v-else>{{ scope.row[col.prop] || '0' }}</span>
157   - </template>
158   - </el-table-column>
159   - </NCC-table>
  72 + <div id="perfTrend" class="chart-box"></div>
160 73 </el-card>
161   - </div>
  74 + </el-col>
  75 + <el-col :span="12">
  76 + <el-card shadow="hover" class="dashboard-card chart-card">
  77 + <div slot="header" class="card-title">
  78 + <i class="el-icon-shopping-cart-2"></i>
  79 + <span>消耗走势对比</span>
  80 + </div>
  81 + <div id="consumeTrend" class="chart-box"></div>
  82 + </el-card>
  83 + </el-col>
  84 + </el-row>
  85 + <el-row :gutter="16" style="margin-top: 16px">
  86 + <el-col :span="8">
  87 + <el-card shadow="hover" class="dashboard-card chart-card">
  88 + <div slot="header" class="card-title">
  89 + <i class="el-icon-user"></i>
  90 + <span>客头数走势</span>
  91 + </div>
  92 + <div id="headCountTrend" class="chart-box"></div>
  93 + </el-card>
  94 + </el-col>
  95 + <el-col :span="8">
  96 + <el-card shadow="hover" class="dashboard-card chart-card">
  97 + <div slot="header" class="card-title">
  98 + <i class="el-icon-user-solid"></i>
  99 + <span>客次数走势</span>
  100 + </div>
  101 + <div id="personTimeTrend" class="chart-box"></div>
  102 + </el-card>
  103 + </el-col>
  104 + <el-col :span="8">
  105 + <el-card shadow="hover" class="dashboard-card chart-card">
  106 + <div slot="header" class="card-title">
  107 + <i class="el-icon-goods"></i>
  108 + <span>项目数走势</span>
  109 + </div>
  110 + <div id="projectCountTrend" class="chart-box"></div>
  111 + </el-card>
  112 + </el-col>
  113 + </el-row>
  114 + <div class="table-section" style="margin-top: 16px">
  115 + <el-card shadow="hover" class="dashboard-card table-card">
  116 + <div slot="header" class="card-title">
  117 + <i class="el-icon-s-grid"></i>
  118 + <span>月度趋势数据列表</span>
  119 + </div>
  120 + <NCC-table v-loading="trendTableLoading" :data="trendTableData" border stripe style="width: 100%">
  121 + <el-table-column v-for="col in trendTableColumns" :key="col.prop" :prop="col.prop" :label="col.label"
  122 + :width="col.width" :align="col.align || 'center'">
  123 + <template slot-scope="scope">
  124 + <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
  125 + <span v-else>{{ scope.row[col.prop] || '0' }}</span>
  126 + </template>
  127 + </el-table-column>
  128 + </NCC-table>
  129 + </el-card>
162 130 </div>
163 131 </div>
  132 + </div>
164 133  
165   - <!-- 全年门店业绩表 -->
166   - <div v-show="activeMenu === 'performance-stat'" class="content-panel">
167   - <div class="panel-body">
168   - <el-card shadow="hover" class="dashboard-card chart-card">
  134 + <!-- 全年门店业绩表 -->
  135 + <div v-show="activeMenu === 'performance-stat'" class="content-panel">
  136 + <div class="panel-body">
  137 + <el-card shadow="hover" class="dashboard-card chart-card">
  138 + <div slot="header" class="card-title">
  139 + <i class="el-icon-data-line"></i>
  140 + <span>业绩走势图</span>
  141 + </div>
  142 + <div id="performanceChart" class="chart-box-large"></div>
  143 + </el-card>
  144 + <div class="table-section" style="margin-top: 16px">
  145 + <el-card shadow="hover" class="dashboard-card table-card">
169 146 <div slot="header" class="card-title">
170   - <i class="el-icon-data-line"></i>
171   - <span>业绩走势图</span>
  147 + <i class="el-icon-s-grid"></i>
  148 + <span>业绩数据列表</span>
172 149 </div>
173   - <div id="performanceChart" class="chart-box-large"></div>
  150 + <NCC-table v-loading="performanceTableLoading" :data="performanceTableData" border stripe
  151 + style="width: 100%">
  152 + <el-table-column v-for="col in performanceTableColumns" :key="col.prop" :prop="col.prop"
  153 + :label="col.label" :width="col.width" :align="col.align || 'center'">
  154 + <template slot-scope="scope">
  155 + <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
  156 + <span v-else>{{ scope.row[col.prop] || '0' }}</span>
  157 + </template>
  158 + </el-table-column>
  159 + </NCC-table>
174 160 </el-card>
175   - <div class="table-section" style="margin-top: 16px">
176   - <el-card shadow="hover" class="dashboard-card table-card">
177   - <div slot="header" class="card-title">
178   - <i class="el-icon-s-grid"></i>
179   - <span>业绩数据列表</span>
180   - </div>
181   - <NCC-table
182   - v-loading="performanceTableLoading"
183   - :data="performanceTableData"
184   - border
185   - stripe
186   - style="width: 100%"
187   - >
188   - <el-table-column
189   - v-for="col in performanceTableColumns"
190   - :key="col.prop"
191   - :prop="col.prop"
192   - :label="col.label"
193   - :width="col.width"
194   - :align="col.align || 'center'"
195   - >
196   - <template slot-scope="scope">
197   - <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
198   - <span v-else>{{ scope.row[col.prop] || '0' }}</span>
199   - </template>
200   - </el-table-column>
201   - </NCC-table>
202   - </el-card>
203   - </div>
204 161 </div>
205 162 </div>
  163 + </div>
206 164  
207   - <!-- 全年门店消耗表 -->
208   - <div v-show="activeMenu === 'consume-stat'" class="content-panel">
209   - <div class="panel-body">
210   - <el-card shadow="hover" class="dashboard-card chart-card">
  165 + <!-- 全年门店消耗表 -->
  166 + <div v-show="activeMenu === 'consume-stat'" class="content-panel">
  167 + <div class="panel-body">
  168 + <el-card shadow="hover" class="dashboard-card chart-card">
  169 + <div slot="header" class="card-title">
  170 + <i class="el-icon-data-line"></i>
  171 + <span>消耗走势图</span>
  172 + </div>
  173 + <div id="consumeChart" class="chart-box-large"></div>
  174 + </el-card>
  175 + <div class="table-section" style="margin-top: 16px">
  176 + <el-card shadow="hover" class="dashboard-card table-card">
211 177 <div slot="header" class="card-title">
212   - <i class="el-icon-data-line"></i>
213   - <span>消耗走势图</span>
  178 + <i class="el-icon-s-grid"></i>
  179 + <span>消耗数据列表</span>
214 180 </div>
215   - <div id="consumeChart" class="chart-box-large"></div>
  181 + <NCC-table v-loading="consumeTableLoading" :data="consumeTableData" border stripe style="width: 100%">
  182 + <el-table-column v-for="col in consumeTableColumns" :key="col.prop" :prop="col.prop" :label="col.label"
  183 + :width="col.width" :align="col.align || 'center'">
  184 + <template slot-scope="scope">
  185 + <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
  186 + <span v-else>{{ scope.row[col.prop] || '0' }}</span>
  187 + </template>
  188 + </el-table-column>
  189 + </NCC-table>
216 190 </el-card>
217   - <div class="table-section" style="margin-top: 16px">
218   - <el-card shadow="hover" class="dashboard-card table-card">
219   - <div slot="header" class="card-title">
220   - <i class="el-icon-s-grid"></i>
221   - <span>消耗数据列表</span>
222   - </div>
223   - <NCC-table
224   - v-loading="consumeTableLoading"
225   - :data="consumeTableData"
226   - border
227   - stripe
228   - style="width: 100%"
229   - >
230   - <el-table-column
231   - v-for="col in consumeTableColumns"
232   - :key="col.prop"
233   - :prop="col.prop"
234   - :label="col.label"
235   - :width="col.width"
236   - :align="col.align || 'center'"
237   - >
238   - <template slot-scope="scope">
239   - <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
240   - <span v-else>{{ scope.row[col.prop] || '0' }}</span>
241   - </template>
242   - </el-table-column>
243   - </NCC-table>
244   - </el-card>
245   - </div>
246 191 </div>
247 192 </div>
  193 + </div>
248 194  
249   - <!-- 年度门店人头表 -->
250   - <div v-show="activeMenu === 'headcount-stat'" class="content-panel">
251   - <div class="panel-body">
252   - <el-card shadow="hover" class="dashboard-card chart-card">
  195 + <!-- 年度门店人头表 -->
  196 + <div v-show="activeMenu === 'headcount-stat'" class="content-panel">
  197 + <div class="panel-body">
  198 + <el-card shadow="hover" class="dashboard-card chart-card">
  199 + <div slot="header" class="card-title">
  200 + <i class="el-icon-data-line"></i>
  201 + <span>人头数走势图</span>
  202 + </div>
  203 + <div id="headCountChart" class="chart-box-large"></div>
  204 + </el-card>
  205 + <div class="table-section" style="margin-top: 16px">
  206 + <el-card shadow="hover" class="dashboard-card table-card">
253 207 <div slot="header" class="card-title">
254   - <i class="el-icon-data-line"></i>
255   - <span>人头数走势图</span>
  208 + <i class="el-icon-s-grid"></i>
  209 + <span>人头数据列表</span>
256 210 </div>
257   - <div id="headCountChart" class="chart-box-large"></div>
  211 + <NCC-table v-loading="headCountTableLoading" :data="headCountTableData" border stripe style="width: 100%">
  212 + <el-table-column v-for="col in headCountTableColumns" :key="col.prop" :prop="col.prop"
  213 + :label="col.label" :width="col.width" :align="col.align || 'center'">
  214 + <template slot-scope="scope">
  215 + <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
  216 + <span v-else>{{ scope.row[col.prop] || '0' }}</span>
  217 + </template>
  218 + </el-table-column>
  219 + </NCC-table>
258 220 </el-card>
259   - <div class="table-section" style="margin-top: 16px">
260   - <el-card shadow="hover" class="dashboard-card table-card">
261   - <div slot="header" class="card-title">
262   - <i class="el-icon-s-grid"></i>
263   - <span>人头数据列表</span>
264   - </div>
265   - <NCC-table
266   - v-loading="headCountTableLoading"
267   - :data="headCountTableData"
268   - border
269   - stripe
270   - style="width: 100%"
271   - >
272   - <el-table-column
273   - v-for="col in headCountTableColumns"
274   - :key="col.prop"
275   - :prop="col.prop"
276   - :label="col.label"
277   - :width="col.width"
278   - :align="col.align || 'center'"
279   - >
280   - <template slot-scope="scope">
281   - <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
282   - <span v-else>{{ scope.row[col.prop] || '0' }}</span>
283   - </template>
284   - </el-table-column>
285   - </NCC-table>
286   - </el-card>
287   - </div>
288 221 </div>
289 222 </div>
  223 + </div>
290 224  
291   - <!-- 年度门店人次表 -->
292   - <div v-show="activeMenu === 'persontime-stat'" class="content-panel">
293   - <div class="panel-body">
294   - <el-card shadow="hover" class="dashboard-card chart-card">
  225 + <!-- 年度门店人次表 -->
  226 + <div v-show="activeMenu === 'persontime-stat'" class="content-panel">
  227 + <div class="panel-body">
  228 + <el-card shadow="hover" class="dashboard-card chart-card">
  229 + <div slot="header" class="card-title">
  230 + <i class="el-icon-data-line"></i>
  231 + <span>人次走势图</span>
  232 + </div>
  233 + <div id="personTimeChart" class="chart-box-large"></div>
  234 + </el-card>
  235 + <div class="table-section" style="margin-top: 16px">
  236 + <el-card shadow="hover" class="dashboard-card table-card">
295 237 <div slot="header" class="card-title">
296   - <i class="el-icon-data-line"></i>
297   - <span>人次走势图</span>
  238 + <i class="el-icon-s-grid"></i>
  239 + <span>人次数据列表</span>
298 240 </div>
299   - <div id="personTimeChart" class="chart-box-large"></div>
  241 + <NCC-table v-loading="personTimeTableLoading" :data="personTimeTableData" border stripe
  242 + style="width: 100%">
  243 + <el-table-column v-for="col in personTimeTableColumns" :key="col.prop" :prop="col.prop"
  244 + :label="col.label" :width="col.width" :align="col.align || 'center'">
  245 + <template slot-scope="scope">
  246 + <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
  247 + <span v-else>{{ scope.row[col.prop] || '0' }}</span>
  248 + </template>
  249 + </el-table-column>
  250 + </NCC-table>
300 251 </el-card>
301   - <div class="table-section" style="margin-top: 16px">
302   - <el-card shadow="hover" class="dashboard-card table-card">
303   - <div slot="header" class="card-title">
304   - <i class="el-icon-s-grid"></i>
305   - <span>人次数据列表</span>
306   - </div>
307   - <NCC-table
308   - v-loading="personTimeTableLoading"
309   - :data="personTimeTableData"
310   - border
311   - stripe
312   - style="width: 100%"
313   - >
314   - <el-table-column
315   - v-for="col in personTimeTableColumns"
316   - :key="col.prop"
317   - :prop="col.prop"
318   - :label="col.label"
319   - :width="col.width"
320   - :align="col.align || 'center'"
321   - >
322   - <template slot-scope="scope">
323   - <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
324   - <span v-else>{{ scope.row[col.prop] || '0' }}</span>
325   - </template>
326   - </el-table-column>
327   - </NCC-table>
328   - </el-card>
329   - </div>
330 252 </div>
331 253 </div>
  254 + </div>
332 255  
333   - <!-- 年度门店项目数表 -->
334   - <div v-show="activeMenu === 'project-stat'" class="content-panel">
335   - <div class="panel-body">
336   - <el-card shadow="hover" class="dashboard-card chart-card">
  256 + <!-- 年度门店项目数表 -->
  257 + <div v-show="activeMenu === 'project-stat'" class="content-panel">
  258 + <div class="panel-body">
  259 + <el-card shadow="hover" class="dashboard-card chart-card">
  260 + <div slot="header" class="card-title">
  261 + <i class="el-icon-data-line"></i>
  262 + <span>项目数走势图</span>
  263 + </div>
  264 + <div id="projectCountChart" class="chart-box-large"></div>
  265 + </el-card>
  266 + <div class="table-section" style="margin-top: 16px">
  267 + <el-card shadow="hover" class="dashboard-card table-card">
337 268 <div slot="header" class="card-title">
338   - <i class="el-icon-data-line"></i>
339   - <span>项目数走势图</span>
  269 + <i class="el-icon-s-grid"></i>
  270 + <span>项目数据列表</span>
340 271 </div>
341   - <div id="projectCountChart" class="chart-box-large"></div>
  272 + <NCC-table v-loading="projectCountTableLoading" :data="projectCountTableData" border stripe
  273 + style="width: 100%">
  274 + <el-table-column v-for="col in projectCountTableColumns" :key="col.prop" :prop="col.prop"
  275 + :label="col.label" :width="col.width" :align="col.align || 'center'">
  276 + <template slot-scope="scope">
  277 + <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
  278 + <span v-else>{{ scope.row[col.prop] || '0' }}</span>
  279 + </template>
  280 + </el-table-column>
  281 + </NCC-table>
342 282 </el-card>
343   - <div class="table-section" style="margin-top: 16px">
344   - <el-card shadow="hover" class="dashboard-card table-card">
345   - <div slot="header" class="card-title">
346   - <i class="el-icon-s-grid"></i>
347   - <span>项目数据列表</span>
348   - </div>
349   - <NCC-table
350   - v-loading="projectCountTableLoading"
351   - :data="projectCountTableData"
352   - border
353   - stripe
354   - style="width: 100%"
355   - >
356   - <el-table-column
357   - v-for="col in projectCountTableColumns"
358   - :key="col.prop"
359   - :prop="col.prop"
360   - :label="col.label"
361   - :width="col.width"
362   - :align="col.align || 'center'"
363   - >
364   - <template slot-scope="scope">
365   - <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
366   - <span v-else>{{ scope.row[col.prop] || '0' }}</span>
367   - </template>
368   - </el-table-column>
369   - </NCC-table>
370   - </el-card>
371   - </div>
372 283 </div>
373 284 </div>
  285 + </div>
374 286  
375   - <!-- 门店五项指标统计 -->
376   - <div v-show="activeMenu === 'store-indicators'" class="content-panel">
377   - <div class="panel-body">
378   - <div class="indicator-controls">
379   - <el-radio-group v-model="indicatorField" size="mini" @change="loadStoreIndicators">
380   - <el-radio-button label="totalperformance">
381   - <i class="el-icon-trophy"></i>
382   - 总业绩
383   - </el-radio-button>
384   - <el-radio-button label="totalconsume">
385   - <i class="el-icon-shopping-cart-2"></i>
386   - 总消耗
387   - </el-radio-button>
388   - <el-radio-button label="headcount">
389   - <i class="el-icon-user"></i>
390   - 客头数
391   - </el-radio-button>
392   - <el-radio-button label="persontime">
393   - <i class="el-icon-user-solid"></i>
394   - 客次数
395   - </el-radio-button>
396   - <el-radio-button label="projectcount">
397   - <i class="el-icon-goods"></i>
398   - 项目数
399   - </el-radio-button>
400   - </el-radio-group>
401   - </div>
402   - <el-row :gutter="16" style="margin-top: 16px">
403   - <el-col :span="16">
404   - <el-card shadow="hover" class="dashboard-card chart-card">
405   - <div slot="header" class="card-title">
406   - <i class="el-icon-s-data"></i>
407   - <span>各门店指标对比</span>
408   - </div>
409   - <div id="storeIndicatorChart" class="chart-box-large"></div>
410   - </el-card>
411   - </el-col>
412   - <el-col :span="8">
413   - <el-card shadow="hover" class="dashboard-card chart-card">
414   - <div slot="header" class="card-title">
415   - <i class="el-icon-pie-chart"></i>
416   - <span>门店占比分析</span>
417   - </div>
418   - <div id="storePieChart" class="chart-box-large"></div>
419   - </el-card>
420   - </el-col>
421   - </el-row>
422   - <div class="table-section" style="margin-top: 16px">
423   - <el-card shadow="hover" class="dashboard-card table-card">
  287 + <!-- 门店五项指标统计 -->
  288 + <div v-show="activeMenu === 'store-indicators'" class="content-panel">
  289 + <div class="panel-body">
  290 + <div class="indicator-controls">
  291 + <el-radio-group v-model="indicatorField" size="mini" @change="loadStoreIndicators">
  292 + <el-radio-button label="totalperformance">
  293 + <i class="el-icon-trophy"></i>
  294 + 总业绩
  295 + </el-radio-button>
  296 + <el-radio-button label="totalconsume">
  297 + <i class="el-icon-shopping-cart-2"></i>
  298 + 总消耗
  299 + </el-radio-button>
  300 + <el-radio-button label="headcount">
  301 + <i class="el-icon-user"></i>
  302 + 客头数
  303 + </el-radio-button>
  304 + <el-radio-button label="persontime">
  305 + <i class="el-icon-user-solid"></i>
  306 + 客次数
  307 + </el-radio-button>
  308 + <el-radio-button label="projectcount">
  309 + <i class="el-icon-goods"></i>
  310 + 项目数
  311 + </el-radio-button>
  312 + </el-radio-group>
  313 + </div>
  314 + <el-row :gutter="16" style="margin-top: 16px">
  315 + <el-col :span="16">
  316 + <el-card shadow="hover" class="dashboard-card chart-card">
424 317 <div slot="header" class="card-title">
425   - <i class="el-icon-s-grid"></i>
426   - <span>门店指标数据列表</span>
  318 + <i class="el-icon-s-data"></i>
  319 + <span>各门店指标对比</span>
427 320 </div>
428   - <NCC-table
429   - v-loading="storeIndicatorTableLoading"
430   - :data="storeIndicatorTableData"
431   - border
432   - stripe
433   - style="width: 100%"
434   - >
435   - <el-table-column
436   - v-for="col in storeIndicatorTableColumns"
437   - :key="col.prop"
438   - :prop="col.prop"
439   - :label="col.label"
440   -
441   - >
442   - <template slot-scope="scope">
443   - <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
444   - <span v-else>{{ scope.row[col.prop] || '0' }}</span>
445   - </template>
446   - </el-table-column>
447   - </NCC-table>
  321 + <div id="storeIndicatorChart" class="chart-box-large"></div>
448 322 </el-card>
449   - </div>
450   - </div>
451   - </div>
452   -
453   - <!-- 事业部五项指标统计 -->
454   - <div v-show="activeMenu === 'bu-indicators'" class="content-panel">
455   - <div class="panel-body">
456   - <div class="indicator-controls">
457   - <el-radio-group v-model="buIndicatorField" size="mini" @change="loadBuIndicators">
458   - <el-radio-button label="totalperformance">
459   - <i class="el-icon-trophy"></i>
460   - 总业绩
461   - </el-radio-button>
462   - <el-radio-button label="totalconsume">
463   - <i class="el-icon-shopping-cart-2"></i>
464   - 总消耗
465   - </el-radio-button>
466   - <el-radio-button label="headcount">
467   - <i class="el-icon-user"></i>
468   - 客头数
469   - </el-radio-button>
470   - <el-radio-button label="persontime">
471   - <i class="el-icon-user-solid"></i>
472   - 客次数
473   - </el-radio-button>
474   - <el-radio-button label="projectcount">
475   - <i class="el-icon-goods"></i>
476   - 项目数
477   - </el-radio-button>
478   - </el-radio-group>
479   - </div>
480   - <el-row :gutter="16" style="margin-top: 16px">
481   - <el-col :span="12">
482   - <el-card shadow="hover" class="dashboard-card chart-card">
483   - <div slot="header" class="card-title">
484   - <i class="el-icon-pie-chart"></i>
485   - <span>事业部贡献占比</span>
486   - </div>
487   - <div id="buPieChart" class="chart-box"></div>
488   - </el-card>
489   - </el-col>
490   - <el-col :span="12">
491   - <el-card shadow="hover" class="dashboard-card chart-card">
492   - <div slot="header" class="card-title">
493   - <i class="el-icon-data-line"></i>
494   - <span>事业部增长率分析</span>
495   - </div>
496   - <div id="buGrowthChart" class="chart-box"></div>
497   - </el-card>
498   - </el-col>
499   - </el-row>
500   - <div class="table-section" style="margin-top: 16px">
501   - <el-card shadow="hover" class="dashboard-card table-card">
  323 + </el-col>
  324 + <el-col :span="8">
  325 + <el-card shadow="hover" class="dashboard-card chart-card">
502 326 <div slot="header" class="card-title">
503   - <i class="el-icon-s-grid"></i>
504   - <span>事业部指标数据列表</span>
  327 + <i class="el-icon-pie-chart"></i>
  328 + <span>门店占比分析</span>
505 329 </div>
506   - <NCC-table
507   - v-loading="buIndicatorTableLoading"
508   - :data="buIndicatorTableData"
509   - border
510   - stripe
511   - style="width: 100%"
512   - >
513   - <el-table-column
514   - v-for="col in buIndicatorTableColumns"
515   - :key="col.prop"
516   - :prop="col.prop"
517   - :label="col.label"
518   - >
519   - <template slot-scope="scope">
520   - <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
521   - <span v-else>{{ scope.row[col.prop] || '0' }}</span>
522   - </template>
523   - </el-table-column>
524   - </NCC-table>
  330 + <div id="storePieChart" class="chart-box-large"></div>
525 331 </el-card>
526   - </div>
  332 + </el-col>
  333 + </el-row>
  334 + <div class="table-section" style="margin-top: 16px">
  335 + <el-card shadow="hover" class="dashboard-card table-card">
  336 + <div slot="header" class="card-title">
  337 + <i class="el-icon-s-grid"></i>
  338 + <span>门店指标数据列表</span>
  339 + </div>
  340 + <NCC-table v-loading="storeIndicatorTableLoading" :data="storeIndicatorTableData" border stripe
  341 + style="width: 100%">
  342 + <el-table-column v-for="col in storeIndicatorTableColumns" :key="col.prop" :prop="col.prop"
  343 + :label="col.label">
  344 + <template slot-scope="scope">
  345 + <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
  346 + <span v-else>{{ scope.row[col.prop] || '0' }}</span>
  347 + </template>
  348 + </el-table-column>
  349 + </NCC-table>
  350 + </el-card>
527 351 </div>
528 352 </div>
  353 + </div>
529 354  
530   - <!-- 事业部内部汇总 -->
531   - <div v-show="activeMenu === 'bu-summary'" class="content-panel">
532   - <div class="panel-body">
  355 + <!-- 事业部五项指标统计 -->
  356 + <div v-show="activeMenu === 'bu-indicators'" class="content-panel">
  357 + <div class="panel-body">
  358 + <div class="indicator-controls">
  359 + <el-radio-group v-model="buIndicatorField" size="mini" @change="loadBuIndicators">
  360 + <el-radio-button label="totalperformance">
  361 + <i class="el-icon-trophy"></i>
  362 + 总业绩
  363 + </el-radio-button>
  364 + <el-radio-button label="totalconsume">
  365 + <i class="el-icon-shopping-cart-2"></i>
  366 + 总消耗
  367 + </el-radio-button>
  368 + <el-radio-button label="headcount">
  369 + <i class="el-icon-user"></i>
  370 + 客头数
  371 + </el-radio-button>
  372 + <el-radio-button label="persontime">
  373 + <i class="el-icon-user-solid"></i>
  374 + 客次数
  375 + </el-radio-button>
  376 + <el-radio-button label="projectcount">
  377 + <i class="el-icon-goods"></i>
  378 + 项目数
  379 + </el-radio-button>
  380 + </el-radio-group>
  381 + </div>
  382 + <el-row :gutter="16" style="margin-top: 16px">
  383 + <el-col :span="12">
  384 + <el-card shadow="hover" class="dashboard-card chart-card">
  385 + <div slot="header" class="card-title">
  386 + <i class="el-icon-pie-chart"></i>
  387 + <span>事业部贡献占比</span>
  388 + </div>
  389 + <div id="buPieChart" class="chart-box"></div>
  390 + </el-card>
  391 + </el-col>
  392 + <el-col :span="12">
  393 + <el-card shadow="hover" class="dashboard-card chart-card">
  394 + <div slot="header" class="card-title">
  395 + <i class="el-icon-data-line"></i>
  396 + <span>事业部增长率分析</span>
  397 + </div>
  398 + <div id="buGrowthChart" class="chart-box"></div>
  399 + </el-card>
  400 + </el-col>
  401 + </el-row>
  402 + <div class="table-section" style="margin-top: 16px">
533 403 <el-card shadow="hover" class="dashboard-card table-card">
534 404 <div slot="header" class="card-title">
535 405 <i class="el-icon-s-grid"></i>
536   - <span>事业部汇总数据列表</span>
  406 + <span>事业部指标数据列表</span>
537 407 </div>
538   - <NCC-table
539   - v-loading="buSummaryTableLoading"
540   - :data="buSummaryTableData"
541   - border
542   - stripe
543   - style="width: 100%"
544   - >
545   - <el-table-column
546   - v-for="col in buSummaryTableColumns"
547   - :key="col.prop"
548   - :prop="col.prop"
549   - :label="col.label"
550   - :width="col.width"
551   - :align="col.align || 'center'"
552   - >
  408 + <NCC-table v-loading="buIndicatorTableLoading" :data="buIndicatorTableData" border stripe
  409 + style="width: 100%">
  410 + <el-table-column v-for="col in buIndicatorTableColumns" :key="col.prop" :prop="col.prop"
  411 + :label="col.label">
553 412 <template slot-scope="scope">
554 413 <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
555 414 <span v-else>{{ scope.row[col.prop] || '0' }}</span>
... ... @@ -559,6 +418,29 @@
559 418 </el-card>
560 419 </div>
561 420 </div>
  421 + </div>
  422 +
  423 + <!-- 事业部内部汇总 -->
  424 + <div v-show="activeMenu === 'bu-summary'" class="content-panel">
  425 + <div class="panel-body">
  426 + <el-card shadow="hover" class="dashboard-card table-card bu-summary-card">
  427 + <div slot="header" class="card-title">
  428 + <i class="el-icon-s-grid"></i>
  429 + <span>事业部汇总数据列表</span>
  430 + </div>
  431 + <NCC-table v-loading="buSummaryTableLoading" :data="buSummaryTableData" border stripe style="width: 100%"
  432 + :height="null" class="bu-summary-table">
  433 + <el-table-column v-for="col in buSummaryTableColumns" :key="col.prop" :prop="col.prop" :label="col.label"
  434 + :width="col.width" :align="col.align || 'center'">
  435 + <template slot-scope="scope">
  436 + <span v-if="col.formatter" v-html="col.formatter(scope.row)"></span>
  437 + <span v-else>{{ scope.row[col.prop] || '0' }}</span>
  438 + </template>
  439 + </el-table-column>
  440 + </NCC-table>
  441 + </el-card>
  442 + </div>
  443 + </div>
562 444 </div>
563 445 </div>
564 446 </template>
... ... @@ -722,7 +604,7 @@ export default {
722 604 storeName: this.query.storeName || '',
723 605 type: item.field // type 作为单独的查询参数传递
724 606 }
725   -
  607 +
726 608 const res = await getMonthlyTrend(params)
727 609 if (res && res.data) {
728 610 // 立即渲染,避免数据被覆盖
... ... @@ -735,10 +617,10 @@ export default {
735 617  
736 618 // 加载列表数据(使用当前选中的趋势类型对应的数据)
737 619 try {
738   - const res = await getMonthlyTrend({
739   - year: this.query.year,
740   - storeName: this.query.storeName,
741   - type: this.trendField || 'totalperformance'
  620 + const res = await getMonthlyTrend({
  621 + year: this.query.year,
  622 + storeName: this.query.storeName,
  623 + type: this.trendField || 'totalperformance'
742 624 })
743 625 if (res && res.data && res.data.rows) {
744 626 this.trendTableData = res.data.rows
... ... @@ -870,7 +752,7 @@ export default {
870 752 async loadBuSummary() {
871 753 this.buSummaryTableLoading = true
872 754 try {
873   - const res = await getBusinessUnitSummaryStat(this.query)
  755 + const res = await getBusinessUnitSummaryStat(this.query)
874 756 if (res.data && res.data.list) {
875 757 this.buSummaryTableData = res.data.list
876 758 this.buildBuSummaryTableColumns()
... ... @@ -927,13 +809,13 @@ export default {
927 809 if (oldChart) {
928 810 oldChart.dispose()
929 811 }
930   -
  812 +
931 813 const chart = echarts.init(chartDom)
932   - const months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
933   -
  814 + const months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
  815 +
934 816 // 确保使用传入的 data,而不是共享的数据
935 817 const rows = data && data.rows ? data.rows : []
936   -
  818 +
937 819 const currentYearValues = months.map((m) => {
938 820 return rows.reduce((sum, row) => {
939 821 const value = row['month' + m] || 0
... ... @@ -946,22 +828,22 @@ export default {
946 828 const value = row['lastMonth' + m] || 0
947 829 return sum + (typeof value === 'number' ? value : 0)
948 830 }, 0)
949   - })
  831 + })
950 832  
951   - // 获取颜色配置
952   - const colors = this.getChartColors(name)
953   -
954   - const option = {
955   - tooltip: {
956   - trigger: 'axis',
  833 + // 获取颜色配置
  834 + const colors = this.getChartColors(name)
  835 +
  836 + const option = {
  837 + tooltip: {
  838 + trigger: 'axis',
957 839 formatter: function (params) {
958   - let res = params[0].name + '<br/>'
  840 + let res = params[0].name + '<br/>'
959 841 params.forEach((item) => {
960   - res += item.marker + item.seriesName + ': ' + item.value.toLocaleString() + '<br/>'
961   - })
962   - return res
963   - }
964   - },
  842 + res += item.marker + item.seriesName + ': ' + item.value.toLocaleString() + '<br/>'
  843 + })
  844 + return res
  845 + }
  846 + },
965 847 legend: {
966 848 data: ['本年走势', '上年走势'],
967 849 top: 10
... ... @@ -981,7 +863,7 @@ export default {
981 863 yAxis: {
982 864 type: 'value'
983 865 },
984   - series: [
  866 + series: [
985 867 {
986 868 name: '本年走势',
987 869 type: 'line',
... ... @@ -999,9 +881,9 @@ export default {
999 881 itemStyle: { color: colors.last },
1000 882 lineStyle: { type: 'dashed', color: colors.last, width: 2 }
1001 883 }
1002   - ]
1003   - }
1004   - chart.setOption(option)
  884 + ]
  885 + }
  886 + chart.setOption(option)
1005 887 })
1006 888 },
1007 889 // 渲染月度统计图表
... ... @@ -1021,10 +903,10 @@ export default {
1021 903 return (data.rows || []).reduce((sum, row) => sum + (row['lastMonth' + m] || 0), 0)
1022 904 })
1023 905  
1024   - // 获取颜色配置
1025   - const colors = this.getChartColors(name)
  906 + // 获取颜色配置
  907 + const colors = this.getChartColors(name)
1026 908  
1027   - const option = {
  909 + const option = {
1028 910 tooltip: {
1029 911 trigger: 'axis',
1030 912 formatter: function (params) {
... ... @@ -1325,7 +1207,7 @@ export default {
1325 1207 buildTrendTableColumns(data) {
1326 1208 const columns = [
1327 1209 { label: '事业部', prop: 'businessUnitName', width: 120, align: 'left' },
1328   - { label: '门店', prop: 'storeName', width: 150, align: 'left' }
  1210 + { label: '门店', prop: 'storeName', align: 'left' }
1329 1211 ]
1330 1212  
1331 1213 for (let i = 1; i <= 12; i++) {
... ... @@ -1378,7 +1260,7 @@ export default {
1378 1260 buildMonthlyStatTableColumns(data, name) {
1379 1261 const columns = [
1380 1262 { label: '事业部', prop: 'businessUnitName', width: 120, align: 'left' },
1381   - { label: '门店', prop: 'storeName', width: 150, align: 'left' }
  1263 + { label: '门店', prop: 'storeName', align: 'left' }
1382 1264 ]
1383 1265  
1384 1266 for (let i = 1; i <= 12; i++) {
... ... @@ -1459,7 +1341,7 @@ export default {
1459 1341 buildStoreIndicatorTableColumns() {
1460 1342 this.storeIndicatorTableColumns = [
1461 1343 { label: '事业部', prop: 'businessUnitName', width: 120, align: 'left' },
1462   - { label: '门店', prop: 'storeName', width: 150, align: 'left' },
  1344 + { label: '门店', prop: 'storeName', align: 'left' },
1463 1345 {
1464 1346 label: '本年数值',
1465 1347 prop: 'currentYearValue',
... ... @@ -1530,7 +1412,7 @@ export default {
1530 1412 buildBuSummaryTableColumns() {
1531 1413 this.buSummaryTableColumns = [
1532 1414 { label: '事业部', prop: 'businessUnitName', width: 120, align: 'left' },
1533   - { label: '门店', prop: 'storeName', width: 150, align: 'left' },
  1415 + { label: '门店', prop: 'storeName', align: 'left' },
1534 1416 {
1535 1417 label: '本年业绩',
1536 1418 prop: 'currentPerformance',
... ... @@ -1858,6 +1740,33 @@ export default {
1858 1740 margin-right: 8px;
1859 1741 }
1860 1742 }
  1743 +
  1744 + // 事业部汇总表格特殊样式
  1745 + &.bu-summary-card {
  1746 + &::v-deep .el-card__body {
  1747 + height: auto !important;
  1748 + max-height: none !important;
  1749 + overflow: visible !important;
  1750 + }
  1751 + }
  1752 + }
  1753 +
  1754 + // 事业部汇总表格样式
  1755 + .bu-summary-table {
  1756 + height: auto !important;
  1757 + max-height: none !important;
  1758 + overflow: visible !important;
  1759 +
  1760 + &::v-deep .el-table__body-wrapper {
  1761 + height: auto !important;
  1762 + max-height: none !important;
  1763 + overflow: visible !important;
  1764 + }
  1765 +
  1766 + &::v-deep .el-table {
  1767 + height: auto !important;
  1768 + max-height: none !important;
  1769 + }
1861 1770 }
1862 1771  
1863 1772 .table-section {
... ... @@ -1868,7 +1777,7 @@ export default {
1868 1777 font-weight: 600;
1869 1778 }
1870 1779  
1871   - .el-table__body tr:hover > td {
  1780 + .el-table__body tr:hover>td {
1872 1781 background-color: #f5f7fa;
1873 1782 }
1874 1783 }
... ...
antis-ncc-admin/src/views/lqInventory/export-usage-dialog.vue
... ... @@ -7,8 +7,10 @@
7 7 <el-option v-for="store in storeOptions" :key="store.id" :label="store.dm" :value="store.id" />
8 8 </el-select>
9 9 </el-form-item>
10   - <el-form-item label="产品ID">
11   - <el-input v-model="exportQuery.productId" placeholder="请输入产品ID(可选)" clearable />
  10 + <el-form-item label="产品">
  11 + <el-select v-model="exportQuery.productId" placeholder="请选择产品(可选)" clearable filterable style="width: 100%">
  12 + <el-option v-for="product in productOptions" :key="product.id" :label="product.productName" :value="product.id" />
  13 + </el-select>
12 14 </el-form-item>
13 15 <el-form-item label="批次号">
14 16 <el-input v-model="exportQuery.usageBatchId" placeholder="请输入批次号(可选)" clearable />
... ... @@ -19,10 +21,13 @@
19 21 format="yyyy-MM-dd HH:mm:ss" style="width: 100%" />
20 22 </el-form-item>
21 23 <el-form-item label="审批状态">
22   - <el-select v-model="exportQuery.isApproved" placeholder="请选择审批状态(可选)" clearable style="width: 100%">
23   - <el-option label="全部" :value="null" />
24   - <el-option label="已通过" :value="true" />
25   - <el-option label="未通过" :value="false" />
  24 + <el-select v-model="exportQuery.approvalStatus" placeholder="请选择审批状态(可选)" clearable style="width: 100%">
  25 + <el-option label="全部" value="" />
  26 + <el-option label="待审批" value="待审批" />
  27 + <el-option label="审批中" value="审批中" />
  28 + <el-option label="已通过" value="已通过" />
  29 + <el-option label="未通过" value="未通过" />
  30 + <el-option label="已退回" value="已退回" />
26 31 </el-select>
27 32 </el-form-item>
28 33 <el-form-item label="是否已领取">
... ... @@ -75,11 +80,12 @@ export default {
75 80 usageBatchId: undefined,
76 81 usageStartTime: undefined,
77 82 usageEndTime: undefined,
78   - isApproved: undefined,
  83 + approvalStatus: undefined,
79 84 isReceived: undefined,
80 85 isEffective: undefined
81 86 },
82   - usageTimeRange: null
  87 + usageTimeRange: null,
  88 + productOptions: [] // 产品选项
83 89 }
84 90 },
85 91 methods: {
... ... @@ -92,11 +98,33 @@ export default {
92 98 usageBatchId: undefined,
93 99 usageStartTime: undefined,
94 100 usageEndTime: undefined,
95   - isApproved: undefined,
  101 + approvalStatus: undefined,
96 102 isReceived: undefined,
97 103 isEffective: undefined
98 104 }
99 105 this.usageTimeRange = null
  106 + // 加载产品选项
  107 + this.initProductOptions()
  108 + },
  109 + // 加载产品选项
  110 + initProductOptions() {
  111 + request({
  112 + url: '/api/Extend/LqProduct/GetList',
  113 + method: 'GET',
  114 + data: {
  115 + currentPage: 1,
  116 + pageSize: 1000,
  117 + onShelfStatus: 1 // 只获取上架的产品
  118 + }
  119 + }).then(res => {
  120 + if (res.code == 200 && res.data && res.data.list) {
  121 + this.productOptions = res.data.list.filter(product => product.onShelfStatus === 1)
  122 + } else {
  123 + this.productOptions = []
  124 + }
  125 + }).catch(() => {
  126 + this.productOptions = []
  127 + })
100 128 },
101 129 handleExport() {
102 130 this.btnLoading = true
... ... @@ -109,6 +137,14 @@ export default {
109 137 exportParams.usageEndTime = this.usageTimeRange[1]
110 138 }
111 139  
  140 + // 处理布尔值参数(转换为字符串true/false)
  141 + if (exportParams.isReceived !== undefined && exportParams.isReceived !== null) {
  142 + exportParams.isReceived = exportParams.isReceived === true || exportParams.isReceived === 'true'
  143 + }
  144 + if (exportParams.isEffective !== undefined && exportParams.isEffective !== null) {
  145 + exportParams.isEffective = exportParams.isEffective === true || exportParams.isEffective === 'true'
  146 + }
  147 +
112 148 // 移除空值
113 149 Object.keys(exportParams).forEach(key => {
114 150 if (exportParams[key] === undefined || exportParams[key] === null || exportParams[key] === '') {
... ... @@ -118,7 +154,14 @@ export default {
118 154  
119 155 // 构建查询字符串
120 156 const queryString = Object.keys(exportParams)
121   - .map(key => `${key}=${encodeURIComponent(exportParams[key])}`)
  157 + .map(key => {
  158 + const value = exportParams[key]
  159 + // 布尔值转换为字符串
  160 + if (typeof value === 'boolean') {
  161 + return `${key}=${value}`
  162 + }
  163 + return `${key}=${encodeURIComponent(value)}`
  164 + })
122 165 .join('&')
123 166  
124 167 // 调用导出接口
... ...
antis-ncc-admin/src/views/lqInventory/index.vue
... ... @@ -40,6 +40,7 @@
40 40 <el-button type="success" icon="el-icon-document" @click="viewApplicationList()">申请列表</el-button>
41 41 <el-button type="info" icon="el-icon-data-analysis" @click="viewStatistics()">门店领取统计</el-button>
42 42 <el-button type="primary" icon="el-icon-printer" @click="viewPendingDelivery()">待领取统计</el-button>
  43 + <el-button type="warning" icon="el-icon-download" @click="exportUsageRecords()">导出使用记录</el-button>
43 44 </div>
44 45 <div class="NCC-common-head-right">
45 46 <el-tooltip effect="dark" content="刷新" placement="top">
... ... @@ -131,6 +132,8 @@
131 132 <UsageMultiForm v-if="usageMultiFormVisible" ref="UsageMultiForm" @refresh="refreshProduct" />
132 133 <!-- 添加库存弹窗 -->
133 134 <InventoryForm v-if="inventoryFormVisible" ref="InventoryForm" @refresh="refreshProduct" />
  135 + <!-- 导出使用记录弹窗 -->
  136 + <ExportUsageDialog ref="ExportUsageDialog" :store-options="storeOptions" />
134 137 </div>
135 138 </template>
136 139  
... ... @@ -140,9 +143,10 @@ import ProductForm from &#39;./product-form.vue&#39;
140 143 import ProductDetailDialog from './product-detail-dialog.vue'
141 144 import UsageMultiForm from './usage-multi-form.vue'
142 145 import InventoryForm from './inventory-form.vue'
  146 +import ExportUsageDialog from './export-usage-dialog.vue'
143 147  
144 148 export default {
145   - components: { ProductForm, ProductDetailDialog, UsageMultiForm, InventoryForm },
  149 + components: { ProductForm, ProductDetailDialog, UsageMultiForm, InventoryForm, ExportUsageDialog },
146 150 data() {
147 151 return {
148 152 // 产品相关
... ... @@ -159,11 +163,13 @@ export default {
159 163 productFormVisible: false,
160 164 detailDialogVisible: false,
161 165 usageMultiFormVisible: false,
162   - inventoryFormVisible: false
  166 + inventoryFormVisible: false,
  167 + storeOptions: [] // 门店选项
163 168 }
164 169 },
165 170 created() {
166 171 this.initProductData()
  172 + this.initStoreOptions()
167 173 },
168 174 methods: {
169 175 // 产品相关方法
... ... @@ -289,6 +295,31 @@ export default {
289 295 query: {}
290 296 })
291 297 },
  298 + // 导出使用记录
  299 + exportUsageRecords() {
  300 + this.$nextTick(() => {
  301 + this.$refs.ExportUsageDialog.init()
  302 + })
  303 + },
  304 + // 初始化门店选项
  305 + initStoreOptions() {
  306 + request({
  307 + url: '/api/Extend/LqMdxx',
  308 + method: 'GET',
  309 + data: {
  310 + currentPage: 1,
  311 + pageSize: 1000
  312 + }
  313 + }).then(res => {
  314 + if (res.data && res.data.list) {
  315 + this.storeOptions = res.data.list
  316 + } else {
  317 + this.storeOptions = []
  318 + }
  319 + }).catch(() => {
  320 + this.storeOptions = []
  321 + })
  322 + },
292 323 // 工具方法
293 324 formatMoney(amount) {
294 325 if (!amount && amount !== 0) return '0.00'
... ...
antis-ncc-admin/src/views/statisticsList/form20.vue
... ... @@ -27,58 +27,53 @@
27 27 </el-row>
28 28  
29 29 <!-- 统计卡片 -->
30   - <el-row :gutter="16" class="statistics-cards" v-if="summaryData">
31   - <el-col :span="4">
32   - <div class="stat-card">
33   - <div class="stat-content">
34   - <div class="stat-label">
35   - 开单业绩
36   - </div>
37   - <div class="stat-value">¥{{ formatMoney(summaryData.totalOrderAchievement) }}</div>
38   - </div>
  30 + <div class="statistics-cards" v-if="summaryData">
  31 + <div class="stat-card order-card">
  32 + <div class="stat-icon">
  33 + <i class="el-icon-shopping-cart-full"></i>
39 34 </div>
40   - </el-col>
41   - <el-col :span="4">
42   - <div class="stat-card">
43   - <div class="stat-content">
44   - <div class="stat-label">
45   - 消耗业绩
46   - </div>
47   - <div class="stat-value">¥{{ formatMoney(summaryData.totalConsumeAchievement) }}</div>
48   - </div>
  35 + <div class="stat-content">
  36 + <div class="stat-label">开单业绩</div>
  37 + <div class="stat-value">¥{{ formatMoney(summaryData.totalOrderAchievement) }}</div>
49 38 </div>
50   - </el-col>
51   - <el-col :span="4">
52   - <div class="stat-card">
53   - <div class="stat-content">
54   - <div class="stat-label">
55   - 退卡业绩
56   - </div>
57   - <div class="stat-value">¥{{ formatMoney(summaryData.totalRefundAchievement) }}</div>
58   - </div>
  39 + </div>
  40 + <div class="stat-card consume-card">
  41 + <div class="stat-icon">
  42 + <i class="el-icon-goods"></i>
59 43 </div>
60   - </el-col>
61   - <el-col :span="4">
62   - <div class="stat-card">
63   - <div class="stat-content">
64   - <div class="stat-label">
65   - 总人头
66   - </div>
67   - <div class="stat-value">{{ summaryData.totalPersonCount || 0 }}</div>
68   - </div>
  44 + <div class="stat-content">
  45 + <div class="stat-label">消耗业绩</div>
  46 + <div class="stat-value">¥{{ formatMoney(summaryData.totalConsumeAchievement) }}</div>
69 47 </div>
70   - </el-col>
71   - <el-col :span="4">
72   - <div class="stat-card">
73   - <div class="stat-content">
74   - <div class="stat-label">
75   - 手工费
76   - </div>
77   - <div class="stat-value">¥{{ formatMoney(summaryData.totalLaborCost) }}</div>
78   - </div>
  48 + </div>
  49 + <div class="stat-card refund-card">
  50 + <div class="stat-icon">
  51 + <i class="el-icon-refresh-left"></i>
79 52 </div>
80   - </el-col>
81   - </el-row>
  53 + <div class="stat-content">
  54 + <div class="stat-label">退卡业绩</div>
  55 + <div class="stat-value">¥{{ formatMoney(summaryData.totalRefundAchievement) }}</div>
  56 + </div>
  57 + </div>
  58 + <div class="stat-card person-card">
  59 + <div class="stat-icon">
  60 + <i class="el-icon-user-solid"></i>
  61 + </div>
  62 + <div class="stat-content">
  63 + <div class="stat-label">总人头</div>
  64 + <div class="stat-value">{{ summaryData.totalPersonCount || 0 }}</div>
  65 + </div>
  66 + </div>
  67 + <div class="stat-card labor-card">
  68 + <div class="stat-icon">
  69 + <i class="el-icon-coin"></i>
  70 + </div>
  71 + <div class="stat-content">
  72 + <div class="stat-label">手工费</div>
  73 + <div class="stat-value">¥{{ formatMoney(summaryData.totalLaborCost) }}</div>
  74 + </div>
  75 + </div>
  76 + </div>
82 77  
83 78 <!-- 数据表格 -->
84 79 <div class="NCC-common-layout-main NCC-flex-main table-wrapper">
... ... @@ -99,6 +94,11 @@
99 94 <span>{{ scope.row.EmployeeName || '无' }}</span>
100 95 </template>
101 96 </el-table-column>
  97 + <el-table-column prop="DepartmentName" label="部门" min-width="120" sortable="custom">
  98 + <template slot-scope="scope">
  99 + <span>{{ scope.row.DepartmentName || '无' }}</span>
  100 + </template>
  101 + </el-table-column>
102 102 <el-table-column prop="OrderAchievement" label="开单业绩" min-width="130" sortable="custom">
103 103 <template slot-scope="scope">
104 104 <span class="amount-value">¥{{ formatMoney(scope.row.OrderAchievement) }}</span>
... ... @@ -345,6 +345,7 @@ export default {
345 345 const headers = [
346 346 '员工ID',
347 347 '员工姓名',
  348 + '部门',
348 349 '开单业绩',
349 350 '消耗业绩',
350 351 '退卡业绩',
... ... @@ -356,6 +357,7 @@ export default {
356 357 const dataRows = this.list.map(row => [
357 358 row.EmployeeId || '无',
358 359 row.EmployeeName || '无',
  360 + row.DepartmentName || '无',
359 361 this.formatMoney(row.OrderAchievement),
360 362 this.formatMoney(row.ConsumeAchievement),
361 363 this.formatMoney(row.RefundAchievement),
... ... @@ -373,6 +375,7 @@ export default {
373 375 ws['!cols'] = [
374 376 { wch: 18 }, // 员工ID
375 377 { wch: 12 }, // 员工姓名
  378 + { wch: 12 }, // 部门
376 379 { wch: 15 }, // 订单业绩
377 380 { wch: 15 }, // 耗卡业绩
378 381 { wch: 15 }, // 退款业绩
... ... @@ -420,33 +423,77 @@ export default {
420 423  
421 424 <style lang="scss" scoped>
422 425 .statistics-cards {
423   - margin-bottom: 16px;
  426 + margin-bottom: 20px;
  427 + display: flex;
  428 + gap: 20px;
  429 + justify-content: space-between;
  430 + width: 100%;
424 431  
425 432 .stat-card {
426   - height: 100px;
427   - padding: 12px;
428 433 background: #fff;
429 434 border-radius: 12px;
430   - border: 1px solid #e4e7ed;
431   - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  435 + padding: 24px;
  436 + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  437 + display: flex;
  438 + align-items: center;
  439 + transition: all 0.3s ease;
  440 + height: 120px;
  441 + flex: 1;
  442 + min-width: 0;
432 443  
433   - .stat-content {
434   - height: 100%;
  444 + &:hover {
  445 + transform: translateY(-2px);
  446 + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
  447 + }
  448 +
  449 + .stat-icon {
  450 + width: 60px;
  451 + height: 60px;
  452 + border-radius: 50%;
435 453 display: flex;
436   - flex-direction: column;
  454 + align-items: center;
437 455 justify-content: center;
438   - align-items: flex-start;
  456 + margin-right: 16px;
  457 + font-size: 24px;
  458 + color: #fff;
  459 + flex-shrink: 0;
  460 + }
  461 +
  462 + &.order-card .stat-icon {
  463 + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  464 + }
  465 +
  466 + &.consume-card .stat-icon {
  467 + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
  468 + }
  469 +
  470 + &.refund-card .stat-icon {
  471 + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
  472 + }
  473 +
  474 + &.person-card .stat-icon {
  475 + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
  476 + }
  477 +
  478 + &.labor-card .stat-icon {
  479 + background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
  480 + }
  481 +
  482 + .stat-content {
  483 + flex: 1;
  484 + min-width: 0;
439 485  
440 486 .stat-label {
441   - font-size: 13px;
442   - color: #606266;
  487 + font-size: 14px;
  488 + color: #7f8c8d;
443 489 margin-bottom: 8px;
444 490 }
445 491  
446 492 .stat-value {
447   - font-size: 20px;
448   - font-weight: 600;
449   - color: #303133;
  493 + font-size: 28px;
  494 + font-weight: 700;
  495 + color: #2c3e50;
  496 + line-height: 1.2;
450 497 }
451 498 }
452 499 }
... ...
netcore/ExportFiles/仓库使用记录_20251226201711.xls 0 → 100644
No preview for this file type
netcore/ExportFiles/仓库使用记录_20251226201736.xls 0 → 100644
No preview for this file type
netcore/ExportFiles/仓库使用记录_20251226201759.xls 0 → 100644
No preview for this file type
netcore/ExportFiles/仓库使用记录_20251226201815.xls 0 → 100644
No preview for this file type
netcore/ExportFiles/仓库使用记录_20251226204041.xls 0 → 100644
No preview for this file type
netcore/ExportFiles/仓库使用记录_20251226204106.xls 0 → 100644
No preview for this file type
netcore/ExportFiles/仓库使用记录_20251226204243.xls 0 → 100644
No preview for this file type
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageExportInput.cs
... ... @@ -33,9 +33,9 @@ namespace NCC.Extend.Entitys.Dto.LqInventoryUsage
33 33 public DateTime? UsageEndTime { get; set; }
34 34  
35 35 /// <summary>
36   - /// 是否审核通过(可选,true-已通过,false-未通过,null-全部
  36 + /// 审批状态(可选:待审批/审批中/已通过/未通过/已退回
37 37 /// </summary>
38   - public bool? IsApproved { get; set; }
  38 + public string ApprovalStatus { get; set; }
39 39  
40 40 /// <summary>
41 41 /// 是否已领取(可选,true-已领取,false-未领取,null-全部)
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqProduct/LqProductInventoryDetailOutput.cs
... ... @@ -129,6 +129,11 @@ namespace NCC.Extend.Entitys.Dto.LqProduct
129 129 /// 是否有效
130 130 /// </summary>
131 131 public int isEffective { get; set; }
  132 +
  133 + /// <summary>
  134 + /// 使用批次ID(用于过滤已领取的记录)
  135 + /// </summary>
  136 + public string usageBatchId { get; set; }
132 137 }
133 138 }
134 139  
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqTechTeacherSalary/TechTeacherStatisticsInput.cs
... ... @@ -14,5 +14,10 @@ namespace NCC.Extend.Entitys.Dto.LqTechTeacherSalary
14 14 /// 月份(1-12)
15 15 /// </summary>
16 16 public int Month { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 部门ID(科技部组织ID,用于筛选)
  20 + /// </summary>
  21 + public string DepartmentId { get; set; }
17 22 }
18 23 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqTechTeacherSalary/TechTeacherStatisticsOutput.cs
... ... @@ -46,6 +46,16 @@ namespace NCC.Extend.Entitys.Dto.LqTechTeacherSalary
46 46 /// 手工费(仅统计耗卡中的手工费)
47 47 /// </summary>
48 48 public decimal LaborCost { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 部门ID(科技部组织ID)
  52 + /// </summary>
  53 + public string DepartmentId { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 部门名称(科技一部/科技二部)
  57 + /// </summary>
  58 + public string DepartmentName { get; set; }
49 59 }
50 60 }
51 61  
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqAnnualSummaryService.cs
... ... @@ -867,14 +867,23 @@ namespace NCC.Extend
867 867 .WhereIF(!string.IsNullOrEmpty(input.StoreName), x => x.StoreName.Contains(input.StoreName))
868 868 .ToListAsync();
869 869  
870   - // 聚合数据:按 (BusinessUnitName, StoreName) - 其实就是按 StoreName,因为 Store 属于 BU
871   - // 先获取所有涉及的门店
872   - var allStores = currentData.Select(x => new { x.StoreId, x.StoreName, x.BusinessUnitName, x.BusinessUnitId })
  870 + // 聚合数据:按 StoreId 分组,解决同一门店对应多个事业部的问题
  871 + // 先获取所有涉及的门店,按 StoreId 分组,优先选择有 BusinessUnitId 且数据更多的记录
  872 + var allStoreGroups = currentData.Select(x => new { x.StoreId, x.StoreName, x.BusinessUnitName, x.BusinessUnitId })
873 873 .Union(lastYearData.Select(x => new { x.StoreId, x.StoreName, x.BusinessUnitName, x.BusinessUnitId }))
874   - .Distinct()
  874 + .GroupBy(x => x.StoreId)
  875 + .Select(g => {
  876 + // 优先选择有 BusinessUnitId 的记录,如果都有或都没有,选择第一条
  877 + var preferred = g.OrderByDescending(x => !string.IsNullOrEmpty(x.BusinessUnitId))
  878 + .ThenByDescending(x => x.BusinessUnitName)
  879 + .First();
  880 + return new { preferred.StoreId, preferred.StoreName, preferred.BusinessUnitName, preferred.BusinessUnitId };
  881 + })
875 882 .OrderBy(x => x.BusinessUnitName)
876 883 .ThenBy(x => x.StoreName)
877 884 .ToList();
  885 +
  886 + var allStores = allStoreGroups;
878 887  
879 888 // 批量获取门店开业时间
880 889 var storeIds = allStores.Select(x => x.StoreId).Distinct().ToList();
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
... ... @@ -135,7 +135,7 @@ namespace NCC.Extend
135 135 SELECT
136 136 consume.Md as StoreId,
137 137 MAX(store.dm) as StoreName,
138   - -- 在职人数(从门店表获取)
  138 + -- 在职人数(从门店表zzrs字段获取)
139 139 MAX(COALESCE(store.zzrs, 0)) as EmployeeCount,
140 140 -- 人头数(去重后的消费会员数)
141 141 COALESCE(COUNT(DISTINCT consume.Hy), 0) as HeadCount,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryService.cs
1 1 using System;
  2 +using System.Collections.Generic;
2 3 using System.Linq;
3 4 using System.Threading.Tasks;
4 5 using Microsoft.AspNetCore.Mvc;
... ... @@ -12,6 +13,7 @@ using NCC.Extend.Entitys.Dto.LqInventory;
12 13 using NCC.Extend.Entitys.Enum;
13 14 using NCC.Extend.Entitys.lq_inventory;
14 15 using NCC.Extend.Entitys.lq_inventory_usage;
  16 +using NCC.Extend.Entitys.lq_inventory_usage_application;
15 17 using NCC.Extend.Entitys.lq_product;
16 18 using NCC.Extend.Interfaces.LqInventory;
17 19 using NCC.FriendlyException;
... ... @@ -233,9 +235,21 @@ namespace NCC.Extend
233 235 .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode())
234 236 .SumAsync(x => (int?)x.Quantity) ?? 0;
235 237  
236   - var totalUsageQuantity = await _db.Queryable<LqInventoryUsageEntity>()
237   - .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode())
238   - .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  238 + // 只统计已领取的使用记录(通过关联申请表的IsReceived字段)
  239 + var receivedBatchIds = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  240 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && x.IsReceived == 1)
  241 + .Select(x => x.UsageBatchId)
  242 + .ToListAsync();
  243 +
  244 + var totalUsageQuantity = 0;
  245 + if (receivedBatchIds != null && receivedBatchIds.Any())
  246 + {
  247 + totalUsageQuantity = await _db.Queryable<LqInventoryUsageEntity>()
  248 + .Where(x => x.ProductId == productId
  249 + && x.IsEffective == StatusEnum.有效.GetHashCode()
  250 + && receivedBatchIds.Contains(x.UsageBatchId)) // 只统计已领取的批次
  251 + .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  252 + }
239 253  
240 254 // 计算入库前的可用库存数量(需要减去本次入库的数量)
241 255 var currentAvailableQuantity = (totalInventoryQuantity - incomingQuantity) - totalUsageQuantity;
... ... @@ -442,9 +456,21 @@ namespace NCC.Extend
442 456 .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode())
443 457 .SumAsync(x => (int?)x.Quantity) ?? 0;
444 458  
445   - var totalUsageQuantity = await _db.Queryable<LqInventoryUsageEntity>()
446   - .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode())
447   - .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  459 + // 只统计已领取的使用记录(通过关联申请表的IsReceived字段)
  460 + var receivedBatchIds = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  461 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && x.IsReceived == 1)
  462 + .Select(x => x.UsageBatchId)
  463 + .ToListAsync();
  464 +
  465 + var totalUsageQuantity = 0;
  466 + if (receivedBatchIds != null && receivedBatchIds.Any())
  467 + {
  468 + totalUsageQuantity = await _db.Queryable<LqInventoryUsageEntity>()
  469 + .Where(x => x.ProductId == productId
  470 + && x.IsEffective == StatusEnum.有效.GetHashCode()
  471 + && receivedBatchIds.Contains(x.UsageBatchId)) // 只统计已领取的批次
  472 + .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  473 + }
448 474  
449 475 var currentAvailableQuantity = totalInventoryQuantity - totalUsageQuantity;
450 476  
... ... @@ -568,15 +594,27 @@ namespace NCC.Extend
568 594 {
569 595 // 注意:库存使用记录表中存储的是产品ID,不是库存ID
570 596 // 这里需要根据业务逻辑调整,如果使用记录需要关联到具体库存批次,需要修改使用记录表结构
571   - // 暂时按产品ID统计已使用数量
  597 + // 暂时按产品ID统计已使用数量(只统计已领取的记录)
572 598 var productIds = data.list.Select(x => x.productId).Distinct().ToList();
573   - var usageDict = await _db.Queryable<LqInventoryUsageEntity>()
574   - .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
575   - .GroupBy(x => x.ProductId)
576   - .Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum((decimal?)x.UsageQuantity) })
  599 + // 先获取已领取的批次ID列表
  600 + var receivedBatchIds = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  601 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && x.IsReceived == 1)
  602 + .Select(x => x.UsageBatchId)
577 603 .ToListAsync();
578   -
579   - var usageDictMap = usageDict.ToDictionary(k => k.ProductId, v => v.TotalUsage.HasValue ? (int)v.TotalUsage.Value : 0);
  604 +
  605 + Dictionary<string, int> usageDictMap = new Dictionary<string, int>();
  606 + if (receivedBatchIds != null && receivedBatchIds.Any())
  607 + {
  608 + var usageList = await _db.Queryable<LqInventoryUsageEntity>()
  609 + .Where(x => productIds.Contains(x.ProductId)
  610 + && x.IsEffective == StatusEnum.有效.GetHashCode()
  611 + && receivedBatchIds.Contains(x.UsageBatchId)) // 只统计已领取的批次
  612 + .GroupBy(x => x.ProductId)
  613 + .Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum((decimal?)x.UsageQuantity) })
  614 + .ToListAsync();
  615 +
  616 + usageDictMap = usageList.ToDictionary(k => k.ProductId, v => v.TotalUsage.HasValue ? (int)v.TotalUsage.Value : 0);
  617 + }
580 618  
581 619 // 计算每个库存的已使用数量和可用数量
582 620 // 注意:这里假设同一产品的所有库存共享使用记录,如果需要按批次区分,需要修改使用记录表
... ... @@ -679,10 +717,21 @@ namespace NCC.Extend
679 717 throw NCCException.Oh("库存记录不存在");
680 718 }
681 719  
682   - // 计算已使用数量
683   - var totalUsage = await _db.Queryable<LqInventoryUsageEntity>()
684   - .Where(x => x.ProductId == inventory.productId && x.IsEffective == StatusEnum.有效.GetHashCode())
685   - .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  720 + // 计算已使用数量(只统计已领取的记录)
  721 + var receivedBatchIds = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  722 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && x.IsReceived == 1)
  723 + .Select(x => x.UsageBatchId)
  724 + .ToListAsync();
  725 +
  726 + var totalUsage = 0;
  727 + if (receivedBatchIds != null && receivedBatchIds.Any())
  728 + {
  729 + totalUsage = await _db.Queryable<LqInventoryUsageEntity>()
  730 + .Where(x => x.ProductId == inventory.productId
  731 + && x.IsEffective == StatusEnum.有效.GetHashCode()
  732 + && receivedBatchIds.Contains(x.UsageBatchId)) // 只统计已领取的批次
  733 + .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  734 + }
686 735  
687 736 // 计算该产品的总库存
688 737 var totalInventory = await _db.Queryable<LqInventoryEntity>()
... ... @@ -766,10 +815,21 @@ namespace NCC.Extend
766 815 .Where(x => x.ProductId == inventory.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode())
767 816 .SumAsync(x => (int?)x.Quantity) ?? 0;
768 817  
769   - // 计算该产品的已使用数量
770   - var totalUsage = await _db.Queryable<LqInventoryUsageEntity>()
771   - .Where(x => x.ProductId == inventory.ProductId && x.IsEffective == StatusEnum.有效.GetHashCode())
772   - .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  818 + // 计算该产品的已使用数量(只统计已领取的记录)
  819 + var receivedBatchIds = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  820 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && x.IsReceived == 1)
  821 + .Select(x => x.UsageBatchId)
  822 + .ToListAsync();
  823 +
  824 + var totalUsage = 0;
  825 + if (receivedBatchIds != null && receivedBatchIds.Any())
  826 + {
  827 + totalUsage = await _db.Queryable<LqInventoryUsageEntity>()
  828 + .Where(x => x.ProductId == inventory.ProductId
  829 + && x.IsEffective == StatusEnum.有效.GetHashCode()
  830 + && receivedBatchIds.Contains(x.UsageBatchId)) // 只统计已领取的批次
  831 + .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  832 + }
773 833  
774 834 // 作废后的总数量 = 当前总库存 - 当前库存数量
775 835 var inventoryAfterCancel = totalInventory - inventory.Quantity;
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs
1 1 using System;
2 2 using System.Collections.Generic;
  3 +using System.IO;
3 4 using System.Linq;
4 5 using System.Reflection;
5 6 using System.Threading.Tasks;
... ... @@ -24,6 +25,10 @@ using NCC.FriendlyException;
24 25 using NCC.System.Entitys.Permission;
25 26 using SqlSugar;
26 27 using Yitter.IdGenerator;
  28 +using NCC.Common.Helper;
  29 +using NCC.Common.Model.NPOI;
  30 +using NCC.DataEncryption;
  31 +using NCC.Common.Configuration;
27 32  
28 33 namespace NCC.Extend
29 34 {
... ... @@ -52,6 +57,19 @@ namespace NCC.Extend
52 57 }
53 58  
54 59 /// <summary>
  60 + /// 获取已领取的批次ID列表(用于过滤已领取的使用记录)
  61 + /// </summary>
  62 + /// <returns>已领取的批次ID列表</returns>
  63 + private async Task<List<string>> GetReceivedBatchIdsAsync()
  64 + {
  65 + var receivedBatchIds = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  66 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && x.IsReceived == 1)
  67 + .Select(x => x.UsageBatchId)
  68 + .ToListAsync();
  69 + return receivedBatchIds ?? new List<string>();
  70 + }
  71 +
  72 + /// <summary>
55 73 /// 根据商品现存的库存计算平均价格(加权平均)
56 74 /// 使用实际可用库存(总数量 - 已领用数量)来计算
57 75 /// </summary>
... ... @@ -79,10 +97,17 @@ namespace NCC.Extend
79 97 return defaultPrice;
80 98 }
81 99  
82   - // 计算该产品的总已使用数量
83   - var totalUsageQuantity = await _db.Queryable<LqInventoryUsageEntity>()
84   - .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode())
85   - .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  100 + // 计算该产品的总已使用数量(只统计已领取的记录)
  101 + var receivedBatchIds = await GetReceivedBatchIdsAsync();
  102 + var totalUsageQuantity = 0;
  103 + if (receivedBatchIds != null && receivedBatchIds.Any())
  104 + {
  105 + totalUsageQuantity = await _db.Queryable<LqInventoryUsageEntity>()
  106 + .Where(x => x.ProductId == productId
  107 + && x.IsEffective == StatusEnum.有效.GetHashCode()
  108 + && receivedBatchIds.Contains(x.UsageBatchId)) // 只统计已领取的批次
  109 + .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  110 + }
86 111  
87 112 // 计算实际可用库存数量
88 113 var availableInventoryQuantity = totalInventoryQuantity - totalUsageQuantity;
... ... @@ -2529,5 +2554,284 @@ namespace NCC.Extend
2529 2554 }
2530 2555 #endregion
2531 2556  
  2557 + #region 导出Excel
  2558 +
  2559 + /// <summary>
  2560 + /// 导出仓库使用记录到Excel
  2561 + /// </summary>
  2562 + /// <remarks>
  2563 + /// 导出仓库使用记录到Excel文件,支持多种筛选条件
  2564 + ///
  2565 + /// 示例请求:
  2566 + /// ```http
  2567 + /// GET /api/Extend/LqInventoryUsage/ExportExcel?StoreId=xxx&amp;ProductId=xxx&amp;UsageBatchId=xxx&amp;UsageStartTime=2025-01-01&amp;UsageEndTime=2025-12-31&amp;ApprovalStatus=已通过&amp;IsReceived=true&amp;IsEffective=true
  2568 + /// ```
  2569 + ///
  2570 + /// 参数说明:
  2571 + /// - StoreId: 门店ID(可选)
  2572 + /// - ProductId: 产品ID(可选)
  2573 + /// - UsageBatchId: 批次号(可选)
  2574 + /// - UsageStartTime: 使用开始时间(可选)
  2575 + /// - UsageEndTime: 使用结束时间(可选)
  2576 + /// - ApprovalStatus: 审批状态(可选:待审批/审批中/已通过/未通过/已退回)
  2577 + /// - IsReceived: 是否已领取(可选,true-已领取,false-未领取,null-全部)
  2578 + /// - IsEffective: 是否有效(可选,true-有效,false-无效,null-全部)
  2579 + /// </remarks>
  2580 + /// <param name="input">查询参数</param>
  2581 + /// <returns>Excel文件下载信息</returns>
  2582 + /// <response code="200">成功返回下载链接</response>
  2583 + /// <response code="400">参数错误</response>
  2584 + /// <response code="500">服务器内部错误</response>
  2585 + [HttpGet("ExportExcel")]
  2586 + public async Task<dynamic> ExportExcel([FromQuery] LqInventoryUsageExportInput input)
  2587 + {
  2588 + try
  2589 + {
  2590 + // 1. 构建查询条件(使用LEFT JOIN关联申请表,因为可能有些使用记录没有申请记录)
  2591 + // 使用 JoinQueryInfos 明确指定 JOIN 类型,避免别名冲突
  2592 + // 注意:LEFT JOIN 申请表时,只关联有效的申请记录,如果没有申请记录或申请记录无效,application 为 null
  2593 + // 确保导出所有使用记录,不管有没有申请记录
  2594 + // 使用子查询方式获取有效的申请记录,避免 LEFT JOIN 条件中的 IsEffective 筛选导致的问题
  2595 + var query = _db.Queryable<LqInventoryUsageEntity, LqProductEntity, LqMdxxEntity>(
  2596 + (u, product, store) => new JoinQueryInfos(
  2597 + JoinType.Left, u.ProductId == product.Id,
  2598 + JoinType.Left, u.StoreId == store.Id
  2599 + ))
  2600 + .Where((u, product, store) => true);
  2601 +
  2602 + // 2. 应用筛选条件
  2603 + if (!string.IsNullOrWhiteSpace(input.StoreId))
  2604 + {
  2605 + query = query.Where((u, product, store) => u.StoreId == input.StoreId);
  2606 + }
  2607 +
  2608 + if (!string.IsNullOrWhiteSpace(input.ProductId))
  2609 + {
  2610 + query = query.Where((u, product, store) => u.ProductId == input.ProductId);
  2611 + }
  2612 +
  2613 + if (!string.IsNullOrWhiteSpace(input.UsageBatchId))
  2614 + {
  2615 + query = query.Where((u, product, store) => u.UsageBatchId == input.UsageBatchId);
  2616 + }
  2617 +
  2618 + if (input.UsageStartTime.HasValue)
  2619 + {
  2620 + query = query.Where((u, product, store) => u.UsageTime >= input.UsageStartTime.Value);
  2621 + }
  2622 +
  2623 + if (input.UsageEndTime.HasValue)
  2624 + {
  2625 + query = query.Where((u, product, store) => u.UsageTime <= input.UsageEndTime.Value.AddDays(1));
  2626 + }
  2627 +
  2628 + // 是否有效筛选
  2629 + if (input.IsEffective.HasValue)
  2630 + {
  2631 + var isEffectiveValue = input.IsEffective.Value ? StatusEnum.有效.GetHashCode() : StatusEnum.无效.GetHashCode();
  2632 + query = query.Where((u, product, store) => u.IsEffective == isEffectiveValue);
  2633 + }
  2634 +
  2635 + // 审批状态和是否领取的筛选需要在子查询中处理
  2636 + // 先查询所有使用记录,然后在内存中或使用子查询获取申请信息
  2637 +
  2638 + // 3. 查询数据并转换为导出格式(使用子查询获取申请信息)
  2639 + var exportDataList = await query
  2640 + .Select((u, product, store) => new
  2641 + {
  2642 + BatchId = u.UsageBatchId ?? "",
  2643 + ProductName = product.ProductName ?? "",
  2644 + StoreName = store.Dm ?? "",
  2645 + UsageQuantity = u.UsageQuantity,
  2646 + UnitPrice = u.UnitPrice,
  2647 + TotalAmount = u.TotalAmount,
  2648 + UsageTime = u.UsageTime,
  2649 + Warehouse = product.Warehouse ?? "",
  2650 + UsageBatchId = u.UsageBatchId ?? "",
  2651 + IsEffective = u.IsEffective == StatusEnum.有效.GetHashCode() ? "有效" : "无效"
  2652 + })
  2653 + .MergeTable()
  2654 + .OrderBy(x => x.UsageTime, OrderByType.Desc)
  2655 + .ToListAsync();
  2656 +
  2657 + // 4. 批量查询申请记录信息(按批次ID分组)
  2658 + var batchIds = exportDataList.Select(x => x.UsageBatchId).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
  2659 + var applicationDict = new Dictionary<string, LqInventoryUsageApplicationEntity>();
  2660 +
  2661 + if (batchIds.Any())
  2662 + {
  2663 + var applications = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  2664 + .Where(a => batchIds.Contains(a.UsageBatchId) && a.IsEffective == StatusEnum.有效.GetHashCode())
  2665 + .ToListAsync();
  2666 +
  2667 + applicationDict = applications.ToDictionary(a => a.UsageBatchId, a => a);
  2668 + }
  2669 +
  2670 + // 5. 处理申请信息的筛选(在内存中筛选)
  2671 + var filteredData = exportDataList.AsEnumerable();
  2672 +
  2673 + // 审批状态筛选
  2674 + if (!string.IsNullOrWhiteSpace(input.ApprovalStatus))
  2675 + {
  2676 + if (input.ApprovalStatus == "无")
  2677 + {
  2678 + filteredData = filteredData.Where(x => !applicationDict.ContainsKey(x.UsageBatchId) ||
  2679 + string.IsNullOrEmpty(applicationDict[x.UsageBatchId].ApprovalStatus));
  2680 + }
  2681 + else
  2682 + {
  2683 + filteredData = filteredData.Where(x => applicationDict.ContainsKey(x.UsageBatchId) &&
  2684 + applicationDict[x.UsageBatchId].ApprovalStatus == input.ApprovalStatus);
  2685 + }
  2686 + }
  2687 +
  2688 + // 是否已领取筛选
  2689 + if (input.IsReceived.HasValue)
  2690 + {
  2691 + if (input.IsReceived.Value)
  2692 + {
  2693 + filteredData = filteredData.Where(x => applicationDict.ContainsKey(x.UsageBatchId) &&
  2694 + applicationDict[x.UsageBatchId].IsReceived == 1);
  2695 + }
  2696 + else
  2697 + {
  2698 + filteredData = filteredData.Where(x => !applicationDict.ContainsKey(x.UsageBatchId) ||
  2699 + applicationDict[x.UsageBatchId].IsReceived == 0);
  2700 + }
  2701 + }
  2702 +
  2703 + var finalDataList = filteredData.ToList();
  2704 +
  2705 + var exportData = finalDataList.Select(x =>
  2706 + {
  2707 + var application = applicationDict.ContainsKey(x.UsageBatchId) ? applicationDict[x.UsageBatchId] : null;
  2708 + return new LqInventoryUsageExportOutput
  2709 + {
  2710 + BatchId = x.BatchId,
  2711 + ProductName = x.ProductName,
  2712 + StoreName = x.StoreName,
  2713 + UsageQuantity = x.UsageQuantity,
  2714 + UnitPrice = x.UnitPrice,
  2715 + TotalAmount = x.TotalAmount,
  2716 + UsageTime = x.UsageTime,
  2717 + Warehouse = x.Warehouse,
  2718 + ApprovalStatus = application != null && !string.IsNullOrEmpty(application.ApprovalStatus)
  2719 + ? application.ApprovalStatus
  2720 + : "无",
  2721 + IsReceived = application != null && application.IsReceived == 1
  2722 + ? "已领取"
  2723 + : "未领取",
  2724 + IsEffective = x.IsEffective
  2725 + };
  2726 + }).ToList();
  2727 +
  2728 + if (exportData == null || !exportData.Any())
  2729 + {
  2730 + throw NCCException.Oh("没有符合条件的数据可导出");
  2731 + }
  2732 +
  2733 + // 记录导出数据统计信息(用于调试)
  2734 + var approvalStatusStats = exportData.GroupBy(x => x.ApprovalStatus).ToDictionary(g => g.Key, g => g.Count());
  2735 + var isReceivedStats = exportData.GroupBy(x => x.IsReceived).ToDictionary(g => g.Key, g => g.Count());
  2736 + _logger.LogInformation($"导出数据统计:总记录数={exportData.Count}, 审批状态分布={string.Join(", ", approvalStatusStats.Select(kv => $"{kv.Key}:{kv.Value}"))}, 是否领取分布={string.Join(", ", isReceivedStats.Select(kv => $"{kv.Key}:{kv.Value}"))}");
  2737 +
  2738 + // 4. 配置Excel导出
  2739 + var excelConfig = new ExcelConfig
  2740 + {
  2741 + FileName = $"仓库使用记录_{DateTime.Now:yyyyMMddHHmmss}.xls",
  2742 + HeadFont = "微软雅黑",
  2743 + HeadPoint = 10,
  2744 + IsAllSizeColumn = true,
  2745 + ColumnModel = new List<ExcelColumnModel>
  2746 + {
  2747 + new ExcelColumnModel { Column = "BatchId", ExcelColumn = "批次号" },
  2748 + new ExcelColumnModel { Column = "ProductName", ExcelColumn = "产品名称" },
  2749 + new ExcelColumnModel { Column = "StoreName", ExcelColumn = "门店名称" },
  2750 + new ExcelColumnModel { Column = "UsageQuantity", ExcelColumn = "使用数量" },
  2751 + new ExcelColumnModel { Column = "UnitPrice", ExcelColumn = "单价" },
  2752 + new ExcelColumnModel { Column = "TotalAmount", ExcelColumn = "总金额" },
  2753 + new ExcelColumnModel { Column = "UsageTime", ExcelColumn = "使用时间" },
  2754 + new ExcelColumnModel { Column = "Warehouse", ExcelColumn = "归属仓库" },
  2755 + new ExcelColumnModel { Column = "ApprovalStatus", ExcelColumn = "审批状态" },
  2756 + new ExcelColumnModel { Column = "IsReceived", ExcelColumn = "是否已领取" },
  2757 + new ExcelColumnModel { Column = "IsEffective", ExcelColumn = "是否有效" }
  2758 + }
  2759 + };
  2760 +
  2761 + // 5. 导出Excel文件(保存到项目根目录下的 ExportFiles 文件夹)
  2762 + // 注意:KeyVariable.SystemPath 可能指向 OSS,所以需要直接查找项目根目录
  2763 + // 从 AppContext.BaseDirectory 向上查找包含 .git 目录的目录作为项目根目录(优先)
  2764 + var baseDir = AppContext.BaseDirectory;
  2765 + var projectRoot = baseDir;
  2766 + var dir = new DirectoryInfo(baseDir);
  2767 +
  2768 + // 优先查找包含 .git 目录的目录(真正的项目根目录)
  2769 + while (dir != null && dir.Parent != null)
  2770 + {
  2771 + try
  2772 + {
  2773 + if (dir.GetDirectories(".git").Any())
  2774 + {
  2775 + projectRoot = dir.FullName;
  2776 + break;
  2777 + }
  2778 + }
  2779 + catch
  2780 + {
  2781 + // 忽略访问错误,继续向上查找
  2782 + }
  2783 + dir = dir.Parent;
  2784 + }
  2785 +
  2786 + // 如果没找到 .git 目录,再查找包含 .sln 文件的目录
  2787 + if (projectRoot == baseDir)
  2788 + {
  2789 + dir = new DirectoryInfo(baseDir);
  2790 + while (dir != null && dir.Parent != null)
  2791 + {
  2792 + try
  2793 + {
  2794 + if (dir.GetFiles("*.sln").Any())
  2795 + {
  2796 + projectRoot = dir.FullName;
  2797 + break;
  2798 + }
  2799 + }
  2800 + catch
  2801 + {
  2802 + // 忽略访问错误,继续向上查找
  2803 + }
  2804 + dir = dir.Parent;
  2805 + }
  2806 + }
  2807 +
  2808 + // 在项目根目录下创建 ExportFiles 文件夹
  2809 + var exportFilesPath = Path.Combine(projectRoot, "ExportFiles");
  2810 + if (!Directory.Exists(exportFilesPath))
  2811 + {
  2812 + Directory.CreateDirectory(exportFilesPath);
  2813 + }
  2814 + var addPath = Path.Combine(exportFilesPath, excelConfig.FileName);
  2815 + ExcelExportHelper<LqInventoryUsageExportOutput>.Export(exportData, excelConfig, addPath);
  2816 + var fileName = _userManager.UserId + "|" + addPath + "|xls";
  2817 +
  2818 + // 6. 返回下载链接
  2819 + var output = new
  2820 + {
  2821 + name = excelConfig.FileName,
  2822 + url = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC")
  2823 + };
  2824 +
  2825 + return output;
  2826 + }
  2827 + catch (Exception ex)
  2828 + {
  2829 + _logger.LogError(ex, "导出仓库使用记录失败");
  2830 + throw NCCException.Oh($"导出失败:{ex.Message}");
  2831 + }
  2832 + }
  2833 +
  2834 + #endregion
  2835 +
2532 2836 }
2533 2837 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs
1 1 using System;
  2 +using System.Collections.Generic;
2 3 using System.Linq;
3 4 using System.Threading.Tasks;
4 5 using Microsoft.AspNetCore.Mvc;
... ... @@ -12,6 +13,7 @@ using NCC.Extend.Entitys.Dto.LqProduct;
12 13 using NCC.Extend.Entitys.Enum;
13 14 using NCC.Extend.Entitys.lq_inventory;
14 15 using NCC.Extend.Entitys.lq_inventory_usage;
  16 +using NCC.Extend.Entitys.lq_inventory_usage_application;
15 17 using NCC.Extend.Entitys.lq_mdxx;
16 18 using NCC.Extend.Entitys.lq_product;
17 19 using NCC.Extend.Interfaces.LqProduct;
... ... @@ -264,14 +266,26 @@ namespace NCC.Extend
264 266  
265 267 var inventoryDictMap = inventoryDict.ToDictionary(k => k.ProductId, v => v.TotalQuantity.HasValue ? (int)v.TotalQuantity.Value : 0);
266 268  
267   - // 查询已使用数量
268   - var usageDict = await _db.Queryable<LqInventoryUsageEntity>()
269   - .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
270   - .GroupBy(x => x.ProductId)
271   - .Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum((decimal?)x.UsageQuantity) })
  269 + // 查询已使用数量(只统计已领取的记录)
  270 + // 先获取已领取的批次ID列表
  271 + var receivedBatchIds = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  272 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && x.IsReceived == 1)
  273 + .Select(x => x.UsageBatchId)
272 274 .ToListAsync();
273   -
274   - var usageDictMap = usageDict.ToDictionary(k => k.ProductId, v => v.TotalUsage.HasValue ? (int)v.TotalUsage.Value : 0);
  275 +
  276 + Dictionary<string, int> usageDictMap = new Dictionary<string, int>();
  277 + if (receivedBatchIds != null && receivedBatchIds.Any())
  278 + {
  279 + var usageList = await _db.Queryable<LqInventoryUsageEntity>()
  280 + .Where(x => productIds.Contains(x.ProductId)
  281 + && x.IsEffective == StatusEnum.有效.GetHashCode()
  282 + && receivedBatchIds.Contains(x.UsageBatchId)) // 只统计已领取的批次
  283 + .GroupBy(x => x.ProductId)
  284 + .Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum((decimal?)x.UsageQuantity) })
  285 + .ToListAsync();
  286 +
  287 + usageDictMap = usageList.ToDictionary(k => k.ProductId, v => v.TotalUsage.HasValue ? (int)v.TotalUsage.Value : 0);
  288 + }
275 289  
276 290 // 填充库存数量(总库存减去已使用数量)
277 291 foreach (var item in data.list)
... ... @@ -450,14 +464,26 @@ namespace NCC.Extend
450 464  
451 465 var inventoryDictMap = inventoryDict.ToDictionary(k => k.ProductId, v => v.TotalQuantity.HasValue ? (int)v.TotalQuantity.Value : 0);
452 466  
453   - // 查询已使用数量
454   - var usageDict = await _db.Queryable<LqInventoryUsageEntity>()
455   - .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode())
456   - .GroupBy(x => x.ProductId)
457   - .Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum((decimal?)x.UsageQuantity) })
  467 + // 查询已使用数量(只统计已领取的记录)
  468 + // 先获取已领取的批次ID列表
  469 + var receivedBatchIds = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  470 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && x.IsReceived == 1)
  471 + .Select(x => x.UsageBatchId)
458 472 .ToListAsync();
459   -
460   - var usageDictMap = usageDict.ToDictionary(k => k.ProductId, v => v.TotalUsage.HasValue ? (int)v.TotalUsage.Value : 0);
  473 +
  474 + Dictionary<string, int> usageDictMap = new Dictionary<string, int>();
  475 + if (receivedBatchIds != null && receivedBatchIds.Any())
  476 + {
  477 + var usageList = await _db.Queryable<LqInventoryUsageEntity>()
  478 + .Where(x => productIds.Contains(x.ProductId)
  479 + && x.IsEffective == StatusEnum.有效.GetHashCode()
  480 + && receivedBatchIds.Contains(x.UsageBatchId)) // 只统计已领取的批次
  481 + .GroupBy(x => x.ProductId)
  482 + .Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum((decimal?)x.UsageQuantity) })
  483 + .ToListAsync();
  484 +
  485 + usageDictMap = usageList.ToDictionary(k => k.ProductId, v => v.TotalUsage.HasValue ? (int)v.TotalUsage.Value : 0);
  486 + }
461 487  
462 488 // 填充库存数量(总库存减去已使用数量)
463 489 foreach (var item in products)
... ... @@ -516,10 +542,21 @@ namespace NCC.Extend
516 542 .Where(x => x.ProductId == id && x.IsEffective == StatusEnum.有效.GetHashCode())
517 543 .SumAsync(x => (int?)x.Quantity) ?? 0;
518 544  
519   - // 计算该产品的已使用数量
520   - var totalUsage = await _db.Queryable<LqInventoryUsageEntity>()
521   - .Where(x => x.ProductId == id && x.IsEffective == StatusEnum.有效.GetHashCode())
522   - .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  545 + // 计算该产品的已使用数量(只统计已领取的记录)
  546 + var receivedBatchIds = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  547 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && x.IsReceived == 1)
  548 + .Select(x => x.UsageBatchId)
  549 + .ToListAsync();
  550 +
  551 + var totalUsage = 0;
  552 + if (receivedBatchIds != null && receivedBatchIds.Any())
  553 + {
  554 + totalUsage = await _db.Queryable<LqInventoryUsageEntity>()
  555 + .Where(x => x.ProductId == id
  556 + && x.IsEffective == StatusEnum.有效.GetHashCode()
  557 + && receivedBatchIds.Contains(x.UsageBatchId)) // 只统计已领取的批次
  558 + .SumAsync(x => (int?)x.UsageQuantity) ?? 0;
  559 + }
523 560  
524 561 // 作废后的总库存(作废后库存为0)
525 562 var inventoryAfterCancel = 0;
... ... @@ -617,13 +654,27 @@ namespace NCC.Extend
617 654 usageQuantity = usage.UsageQuantity,
618 655 relatedConsumeId = usage.RelatedConsumeId,
619 656 createTime = usage.CreateTime,
620   - isEffective = usage.IsEffective
  657 + isEffective = usage.IsEffective,
  658 + usageBatchId = usage.UsageBatchId // 添加批次ID用于过滤
621 659 })
622 660 .ToListAsync();
623 661  
624   - // 计算总库存、已使用数量、可用库存
  662 + // 计算总库存、已使用数量(只统计已领取的记录)、可用库存
625 663 var totalInventory = inventoryList.Where(x => x.isEffective == StatusEnum.有效.GetHashCode()).Sum(x => x.quantity);
626   - var totalUsage = usageList.Where(x => x.isEffective == StatusEnum.有效.GetHashCode()).Sum(x => x.usageQuantity);
  664 +
  665 + // 获取已领取的批次ID列表
  666 + var receivedBatchIds = await _db.Queryable<LqInventoryUsageApplicationEntity>()
  667 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode() && x.IsReceived == 1)
  668 + .Select(x => x.UsageBatchId)
  669 + .ToListAsync();
  670 +
  671 + // 只统计已领取的使用记录
  672 + var totalUsage = usageList
  673 + .Where(x => x.isEffective == StatusEnum.有效.GetHashCode()
  674 + && receivedBatchIds != null
  675 + && receivedBatchIds.Contains(x.usageBatchId)) // 只统计已领取的批次
  676 + .Sum(x => x.usageQuantity);
  677 +
627 678 var availableInventory = totalInventory - totalUsage;
628 679  
629 680 var result = new LqProductInventoryDetailOutput
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs
... ... @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc;
11 11 using SqlSugar;
12 12 using System;
13 13 using System.Collections.Generic;
  14 +using System.IO;
14 15 using System.Linq;
15 16 using System.Threading.Tasks;
16 17 using NCC.Extend.Entitys;
... ... @@ -1725,7 +1726,7 @@ namespace NCC.Extend.LqReimbursementApplication
1725 1726 /// <response code="200">导出成功</response>
1726 1727 /// <response code="500">服务器错误</response>
1727 1728 [HttpGet("Actions/ExportApprovedDetails")]
1728   - public async Task<dynamic> ExportApprovedDetails([FromQuery] int? year = null, [FromQuery] string month = null)
  1729 + public async Task<dynamic> ExportApprovedDetails(int? year = null, string month = null)
1729 1730 {
1730 1731 try
1731 1732 {
... ... @@ -1761,19 +1762,34 @@ namespace NCC.Extend.LqReimbursementApplication
1761 1762 .ToListAsync();
1762 1763 }
1763 1764  
1764   - // 获取门店信息
1765   - var storeIds = applications.Where(x => !string.IsNullOrEmpty(x.ApplicationStoreId))
  1765 + // 获取申请门店信息
  1766 + var applicationStoreIds = applications.Where(x => !string.IsNullOrEmpty(x.ApplicationStoreId))
1766 1767 .Select(x => x.ApplicationStoreId)
1767 1768 .Distinct()
1768 1769 .ToList();
1769   - var stores = new Dictionary<string, string>();
1770   - if (storeIds.Any())
  1770 + var applicationStores = new Dictionary<string, string>();
  1771 + if (applicationStoreIds.Any())
1771 1772 {
1772 1773 var storeList = await _db.Queryable<LqMdxxEntity>()
1773   - .Where(x => storeIds.Contains(x.Id))
  1774 + .Where(x => applicationStoreIds.Contains(x.Id))
1774 1775 .Select(x => new { x.Id, x.Dm })
1775 1776 .ToListAsync();
1776   - stores = storeList.ToDictionary(x => x.Id, x => x.Dm ?? "");
  1777 + applicationStores = storeList.ToDictionary(x => x.Id, x => x.Dm ?? "");
  1778 + }
  1779 +
  1780 + // 获取购买门店信息(从购买记录的CreateUserStoreId)
  1781 + var purchaseStoreIds = purchaseRecords.Where(x => !string.IsNullOrEmpty(x.CreateUserStoreId))
  1782 + .Select(x => x.CreateUserStoreId)
  1783 + .Distinct()
  1784 + .ToList();
  1785 + var purchaseStores = new Dictionary<string, string>();
  1786 + if (purchaseStoreIds.Any())
  1787 + {
  1788 + var purchaseStoreList = await _db.Queryable<LqMdxxEntity>()
  1789 + .Where(x => purchaseStoreIds.Contains(x.Id))
  1790 + .Select(x => new { x.Id, x.Dm })
  1791 + .ToListAsync();
  1792 + purchaseStores = purchaseStoreList.ToDictionary(x => x.Id, x => x.Dm ?? "");
1777 1793 }
1778 1794  
1779 1795 // 组装导出数据(包含报销申请和购买记录明细)
... ... @@ -1792,8 +1808,12 @@ namespace NCC.Extend.LqReimbursementApplication
1792 1808 applicationId = app.Id,
1793 1809 applicationUserName = app.ApplicationUserName,
1794 1810 applicationStoreId = app.ApplicationStoreId,
1795   - applicationStoreName = !string.IsNullOrEmpty(app.ApplicationStoreId) && stores.ContainsKey(app.ApplicationStoreId)
1796   - ? stores[app.ApplicationStoreId]
  1811 + applicationStoreName = !string.IsNullOrEmpty(app.ApplicationStoreId) && applicationStores.ContainsKey(app.ApplicationStoreId)
  1812 + ? applicationStores[app.ApplicationStoreId]
  1813 + : "",
  1814 + purchaseStoreId = pr.CreateUserStoreId,
  1815 + purchaseStoreName = !string.IsNullOrEmpty(pr.CreateUserStoreId) && purchaseStores.ContainsKey(pr.CreateUserStoreId)
  1816 + ? purchaseStores[pr.CreateUserStoreId]
1797 1817 : "",
1798 1818 applicationTime = app.ApplicationTime,
1799 1819 applicationAmount = !string.IsNullOrEmpty(app.Amount) ? decimal.Parse(app.Amount) : 0m,
... ... @@ -1816,9 +1836,11 @@ namespace NCC.Extend.LqReimbursementApplication
1816 1836 applicationId = app.Id,
1817 1837 applicationUserName = app.ApplicationUserName,
1818 1838 applicationStoreId = app.ApplicationStoreId,
1819   - applicationStoreName = !string.IsNullOrEmpty(app.ApplicationStoreId) && stores.ContainsKey(app.ApplicationStoreId)
1820   - ? stores[app.ApplicationStoreId]
  1839 + applicationStoreName = !string.IsNullOrEmpty(app.ApplicationStoreId) && applicationStores.ContainsKey(app.ApplicationStoreId)
  1840 + ? applicationStores[app.ApplicationStoreId]
1821 1841 : "",
  1842 + purchaseStoreId = "",
  1843 + purchaseStoreName = "",
1822 1844 applicationTime = app.ApplicationTime,
1823 1845 applicationAmount = !string.IsNullOrEmpty(app.Amount) ? decimal.Parse(app.Amount) : 0m,
1824 1846 purchaseRecordId = "",
... ... @@ -1834,7 +1856,7 @@ namespace NCC.Extend.LqReimbursementApplication
1834 1856 }
1835 1857  
1836 1858 // 导出Excel
1837   - List<ParamsModel> paramList = "[{\"value\":\"报销申请ID\",\"field\":\"applicationId\"},{\"value\":\"申请人姓名\",\"field\":\"applicationUserName\"},{\"value\":\"门店ID\",\"field\":\"applicationStoreId\"},{\"value\":\"门店名称\",\"field\":\"applicationStoreName\"},{\"value\":\"申请时间\",\"field\":\"applicationTime\"},{\"value\":\"申请金额\",\"field\":\"applicationAmount\"},{\"value\":\"购买记录ID\",\"field\":\"purchaseRecordId\"},{\"value\":\"支出分类ID\",\"field\":\"reimbursementCategoryId\"},{\"value\":\"支出分类名称\",\"field\":\"reimbursementCategoryName\"},{\"value\":\"单价\",\"field\":\"unitPrice\"},{\"value\":\"数量\",\"field\":\"quantity\"},{\"value\":\"金额\",\"field\":\"amount\"},{\"value\":\"备注说明\",\"field\":\"memo\"},{\"value\":\"购买时间\",\"field\":\"purchaseTime\"},]".ToList<ParamsModel>();
  1859 + List<ParamsModel> paramList = "[{\"value\":\"报销申请ID\",\"field\":\"applicationId\"},{\"value\":\"申请人姓名\",\"field\":\"applicationUserName\"},{\"value\":\"申请门店ID\",\"field\":\"applicationStoreId\"},{\"value\":\"申请门店\",\"field\":\"applicationStoreName\"},{\"value\":\"购买门店ID\",\"field\":\"purchaseStoreId\"},{\"value\":\"购买门店\",\"field\":\"purchaseStoreName\"},{\"value\":\"申请时间\",\"field\":\"applicationTime\"},{\"value\":\"申请金额\",\"field\":\"applicationAmount\"},{\"value\":\"购买记录ID\",\"field\":\"purchaseRecordId\"},{\"value\":\"支出分类ID\",\"field\":\"reimbursementCategoryId\"},{\"value\":\"支出分类名称\",\"field\":\"reimbursementCategoryName\"},{\"value\":\"单价\",\"field\":\"unitPrice\"},{\"value\":\"数量\",\"field\":\"quantity\"},{\"value\":\"金额\",\"field\":\"amount\"},{\"value\":\"备注说明\",\"field\":\"memo\"},{\"value\":\"购买时间\",\"field\":\"purchaseTime\"},]".ToList<ParamsModel>();
1838 1860 ExcelConfig excelconfig = new ExcelConfig();
1839 1861 excelconfig.FileName = $"报销表明细_{queryYear}年{queryMonth}月.xls";
1840 1862 excelconfig.HeadFont = "微软雅黑";
... ... @@ -1845,7 +1867,61 @@ namespace NCC.Extend.LqReimbursementApplication
1845 1867 {
1846 1868 excelconfig.ColumnModel.Add(new ExcelColumnModel() { Column = param.field, ExcelColumn = param.value });
1847 1869 }
1848   - var addPath = FileVariable.TemporaryFilePath + excelconfig.FileName;
  1870 +
  1871 + // 导出Excel文件(保存到项目根目录下的 ExportFiles 文件夹)
  1872 + // 注意:KeyVariable.SystemPath 可能指向 OSS,所以需要直接查找项目根目录
  1873 + // 从 AppContext.BaseDirectory 向上查找包含 .git 目录的目录作为项目根目录(优先)
  1874 + var baseDir = AppContext.BaseDirectory;
  1875 + var projectRoot = baseDir;
  1876 + var dir = new DirectoryInfo(baseDir);
  1877 +
  1878 + // 优先查找包含 .git 目录的目录(真正的项目根目录)
  1879 + while (dir != null && dir.Parent != null)
  1880 + {
  1881 + try
  1882 + {
  1883 + if (dir.GetDirectories(".git").Any())
  1884 + {
  1885 + projectRoot = dir.FullName;
  1886 + break;
  1887 + }
  1888 + }
  1889 + catch
  1890 + {
  1891 + // 忽略访问错误,继续向上查找
  1892 + }
  1893 + dir = dir.Parent;
  1894 + }
  1895 +
  1896 + // 如果没找到 .git 目录,再查找包含 .sln 文件的目录
  1897 + if (projectRoot == baseDir)
  1898 + {
  1899 + dir = new DirectoryInfo(baseDir);
  1900 + while (dir != null && dir.Parent != null)
  1901 + {
  1902 + try
  1903 + {
  1904 + if (dir.GetFiles("*.sln").Any())
  1905 + {
  1906 + projectRoot = dir.FullName;
  1907 + break;
  1908 + }
  1909 + }
  1910 + catch
  1911 + {
  1912 + // 忽略访问错误,继续向上查找
  1913 + }
  1914 + dir = dir.Parent;
  1915 + }
  1916 + }
  1917 +
  1918 + // 在项目根目录下创建 ExportFiles 文件夹
  1919 + var exportFilesPath = Path.Combine(projectRoot, "ExportFiles");
  1920 + if (!Directory.Exists(exportFilesPath))
  1921 + {
  1922 + Directory.CreateDirectory(exportFilesPath);
  1923 + }
  1924 + var addPath = Path.Combine(exportFilesPath, excelconfig.FileName);
1849 1925 ExcelExportHelper<ReimbursementDetailExportOutput>.Export(exportData, excelconfig, addPath);
1850 1926 var fileName = _userManager.UserId + "|" + addPath + "|xls";
1851 1927 var output = new
... ... @@ -1883,11 +1959,21 @@ namespace NCC.Extend.LqReimbursementApplication
1883 1959 public string applicationStoreId { get; set; }
1884 1960  
1885 1961 /// <summary>
1886   - /// 门店名称
  1962 + /// 申请门店名称
1887 1963 /// </summary>
1888 1964 public string applicationStoreName { get; set; }
1889 1965  
1890 1966 /// <summary>
  1967 + /// 购买门店ID
  1968 + /// </summary>
  1969 + public string purchaseStoreId { get; set; }
  1970 +
  1971 + /// <summary>
  1972 + /// 购买门店名称
  1973 + /// </summary>
  1974 + public string purchaseStoreName { get; set; }
  1975 +
  1976 + /// <summary>
1891 1977 /// 申请时间
1892 1978 /// </summary>
1893 1979 public DateTime? applicationTime { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqTechTeacherSalaryService.cs
... ... @@ -591,14 +591,38 @@ namespace NCC.Extend
591 591 var endDate = startDate.AddMonths(1).AddDays(-1);
592 592 var monthStr = $"{input.Year}{input.Month:D2}";
593 593  
594   - // 3. 获取所有科技部老师(岗位为"科技老师")
595   - var techTeacherList = await _db.Queryable<UserEntity>()
596   - .Where(x => x.Gw == "科技老师")
597   - .Select(x => new
  594 + // 3. 获取科技部组织列表(科技一部/科技二部)
  595 + var techOrganizeList = await _db.Queryable<OrganizeEntity>()
  596 + .Where(x => x.FullName != null && (x.FullName.Contains("科技一部") || x.FullName.Contains("科技二部"))
  597 + && x.DeleteMark == null && x.EnabledMark == 1)
  598 + .Select(x => new { x.Id, x.FullName })
  599 + .ToListAsync();
  600 +
  601 + var techOrganizeIds = techOrganizeList?.Select(x => x.Id).ToList() ?? new List<string>();
  602 + var techOrganizeDict = techOrganizeList?.ToDictionary(x => x.Id, x => x.FullName) ?? new Dictionary<string, string>();
  603 +
  604 + // 4. 获取所有科技部老师(岗位为"科技老师"),并关联部门信息
  605 + // 先筛选科技部组织,只查询属于科技一部或科技二部的老师
  606 + var techTeacherQuery = _db.Queryable<UserEntity>()
  607 + .LeftJoin<OrganizeEntity>((user, org) => user.OrganizeId == org.Id)
  608 + .Where((user, org) => user.Gw == "科技老师"
  609 + && user.DeleteMark == null && user.EnabledMark == 1
  610 + && (techOrganizeIds.Count == 0 || techOrganizeIds.Contains(user.OrganizeId)));
  611 +
  612 + // 如果指定了部门ID,则进行筛选
  613 + if (!string.IsNullOrEmpty(input.DepartmentId))
  614 + {
  615 + techTeacherQuery = techTeacherQuery.Where((user, org) => user.OrganizeId == input.DepartmentId);
  616 + }
  617 +
  618 + var techTeacherList = await techTeacherQuery
  619 + .Select((user, org) => new
598 620 {
599   - EmployeeId = x.Id,
600   - EmployeeName = x.RealName,
601   - EmployeeAccount = x.Account
  621 + EmployeeId = user.Id,
  622 + EmployeeName = user.RealName,
  623 + EmployeeAccount = user.Account,
  624 + OrganizeId = user.OrganizeId,
  625 + DepartmentName = org.FullName ?? ""
602 626 })
603 627 .ToListAsync();
604 628  
... ... @@ -610,7 +634,7 @@ namespace NCC.Extend
610 634 var teacherIds = techTeacherList.Select(x => x.EmployeeId).ToList();
611 635 var teacherAccounts = techTeacherList.Where(x => !string.IsNullOrEmpty(x.EmployeeAccount)).Select(x => x.EmployeeAccount).ToList();
612 636  
613   - // 4. 使用聚合查询统计开单业绩和手工费(优化性能)
  637 + // 5. 使用聚合查询统计开单业绩和手工费(优化性能)
614 638 // 注意:kjblsyj字段是varchar类型,需要转换
615 639 var orderStatsList = await _db.Queryable<LqKdKjbsyjEntity>()
616 640 .Where(x => x.Yjsj >= startDate && x.Yjsj <= endDate.AddDays(1) && x.IsEffective == 1)
... ... @@ -627,7 +651,7 @@ namespace NCC.Extend
627 651 })
628 652 .ToList();
629 653  
630   - // 5. 使用聚合查询统计消耗业绩和手工费(关联耗卡主表获取时间)
  654 + // 6. 使用聚合查询统计消耗业绩和手工费(关联耗卡主表获取时间)
631 655 var consumeStatsList = await _db.Queryable<LqXhKjbsyjEntity, LqXhHyhkEntity>(
632 656 (kjbsyj, hyhk) => kjbsyj.Glkdbh == hyhk.Id && hyhk.IsEffective == 1)
633 657 .Where((kjbsyj, hyhk) => kjbsyj.IsEffective == 1
... ... @@ -652,7 +676,7 @@ namespace NCC.Extend
652 676 })
653 677 .ToList();
654 678  
655   - // 6. 使用聚合查询统计退卡业绩和手工费
  679 + // 7. 使用聚合查询统计退卡业绩和手工费
656 680 var refundStatsList = await _db.Queryable<LqHytkKjbsyjEntity>()
657 681 .Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1)
658 682 .Where(x => teacherIds.Contains(x.Kjbls) || teacherAccounts.Contains(x.Kjblszh))
... ... @@ -668,28 +692,35 @@ namespace NCC.Extend
668 692 })
669 693 .ToList();
670 694  
671   - // 7. 统计人头(按月份+客户去重)
  695 + // 8. 统计人头(按月份+客户去重)
  696 + // 注意:数据库中可能存在"科技老师"和"科技部老师"两种PersonType值,需要同时查询
672 697 var personCountRecords = await _db.Queryable<LqPersonTimesRecordEntity>()
673   - .Where(x => x.PersonType == "科技老师"
  698 + .Where(x => (x.PersonType == "科技老师" || x.PersonType == "科技部老师")
674 699 && x.WorkMonth == monthStr
675 700 && x.IsEffective == 1
676   - && teacherIds.Contains(x.PersonId))
  701 + && teacherIds.Contains(x.PersonId)
  702 + && !string.IsNullOrEmpty(x.PersonId)
  703 + && !string.IsNullOrEmpty(x.MemberId))
  704 + .Select(x => new
  705 + {
  706 + PersonId = x.PersonId,
  707 + MemberId = x.MemberId
  708 + })
677 709 .ToListAsync();
678 710  
679   - var personCountStats = personCountRecords
  711 + // 按PersonId和MemberId去重,然后按PersonId分组统计不同MemberId的数量
  712 + var personCountDict = personCountRecords
680 713 .GroupBy(x => new { x.PersonId, x.MemberId })
681   - .Select(g => new { TeacherId = g.Key.PersonId })
  714 + .Select(g => new { TeacherId = g.Key.PersonId, MemberId = g.Key.MemberId })
682 715 .GroupBy(x => x.TeacherId)
683   - .Select(g => new
684   - {
685   - TeacherId = g.Key,
686   - PersonCount = g.Count()
687   - })
688   - .ToList();
  716 + .ToDictionary(
  717 + g => g.Key ?? "",
  718 + g => g.Select(x => x.MemberId).Distinct().Count());
689 719  
690   - // 8. 统计人次(按日期+客户去重,汇总数量)
  720 + // 9. 统计人次(按日期+客户去重,汇总数量)
  721 + // 注意:数据库中可能存在"科技老师"和"科技部老师"两种PersonType值,需要同时查询
691 722 var personTimesRecords = await _db.Queryable<LqPersonTimesRecordEntity>()
692   - .Where(x => x.PersonType == "科技老师"
  723 + .Where(x => (x.PersonType == "科技老师" || x.PersonType == "科技部老师")
693 724 && x.WorkMonth == monthStr
694 725 && x.IsEffective == 1
695 726 && teacherIds.Contains(x.PersonId))
... ... @@ -711,21 +742,21 @@ namespace NCC.Extend
711 742 })
712 743 .ToList();
713 744  
714   - // 9. 构建结果字典(优化查找性能)
  745 + // 10. 构建结果字典(优化查找性能)
715 746 var orderDict = orderStats.ToDictionary(x => x.TeacherId, x => x);
716 747 var consumeDict = consumeStats.ToDictionary(x => x.TeacherId, x => x);
717 748 var refundDict = refundStats.ToDictionary(x => x.TeacherId, x => x);
718   - var personCountDict = personCountStats.ToDictionary(x => x.TeacherId, x => x);
  749 + // personCountDict 已经在上面构建了
719 750 var personTimesDict = personTimesStats.ToDictionary(x => x.TeacherId, x => x);
720 751  
721   - // 10. 组装结果
  752 + // 11. 组装结果
722 753 var result = new List<TechTeacherStatisticsOutput>();
723 754 foreach (var teacher in techTeacherList)
724 755 {
725 756 var orderStat = orderDict.ContainsKey(teacher.EmployeeId) ? orderDict[teacher.EmployeeId] : null;
726 757 var consumeStat = consumeDict.ContainsKey(teacher.EmployeeId) ? consumeDict[teacher.EmployeeId] : null;
727 758 var refundStat = refundDict.ContainsKey(teacher.EmployeeId) ? refundDict[teacher.EmployeeId] : null;
728   - var personCountStat = personCountDict.ContainsKey(teacher.EmployeeId) ? personCountDict[teacher.EmployeeId] : null;
  759 + var personCount = personCountDict.ContainsKey(teacher.EmployeeId) ? personCountDict[teacher.EmployeeId] : 0;
729 760 var personTimesStat = personTimesDict.ContainsKey(teacher.EmployeeId) ? personTimesDict[teacher.EmployeeId] : null;
730 761  
731 762 result.Add(new TechTeacherStatisticsOutput
... ... @@ -735,9 +766,13 @@ namespace NCC.Extend
735 766 OrderAchievement = orderStat?.OrderAchievement ?? 0m,
736 767 ConsumeAchievement = consumeStat?.ConsumeAchievement ?? 0m,
737 768 RefundAchievement = refundStat?.RefundAchievement ?? 0m,
738   - PersonCount = personCountStat?.PersonCount ?? 0,
  769 + PersonCount = personCount,
739 770 PersonTimes = personTimesStat?.PersonTimes ?? 0m,
740   - LaborCost = consumeStat?.LaborCost ?? 0m // 手工费只统计耗卡中的手工费
  771 + LaborCost = consumeStat?.LaborCost ?? 0m, // 手工费只统计耗卡中的手工费
  772 + DepartmentId = teacher.OrganizeId,
  773 + DepartmentName = !string.IsNullOrEmpty(teacher.DepartmentName)
  774 + ? teacher.DepartmentName
  775 + : (techOrganizeDict.ContainsKey(teacher.OrganizeId) ? techOrganizeDict[teacher.OrganizeId] : "")
741 776 });
742 777 }
743 778  
... ...
test_tech_teacher_statistics.sh 0 → 100755
  1 +#!/bin/bash
  2 +
  3 +# 测试科技老师统计数据接口
  4 +# 测试2025年12月的人头数是否正确返回
  5 +
  6 +echo "=== 测试科技老师统计数据接口 ==="
  7 +echo ""
  8 +
  9 +# 1. 获取Token
  10 +echo "=== 1. 获取Token ==="
  11 +TOKEN_RESPONSE=$(curl -s -X POST "http://localhost:2011/api/oauth/Login" \
  12 + -H "Content-Type: application/x-www-form-urlencoded" \
  13 + -d "account=admin&password=e10adc3949ba59abbe56e057f20f883e")
  14 +
  15 +TOKEN=$(echo "$TOKEN_RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data['data']['token'])" 2>/dev/null)
  16 +
  17 +if [ -z "$TOKEN" ]; then
  18 + echo "❌ Token获取失败"
  19 + echo "$TOKEN_RESPONSE"
  20 + exit 1
  21 +fi
  22 +
  23 +echo "✅ Token获取成功"
  24 +echo ""
  25 +
  26 +# 2. 测试接口 - 2025年12月
  27 +echo "=== 2. 测试接口 - 2025年12月 ==="
  28 +echo "请求参数: Year=2025, Month=12"
  29 +echo ""
  30 +
  31 +RESPONSE=$(curl -s -X GET "http://localhost:2011/api/Extend/lqtechteachersalary/statistics?Year=2025&Month=12" \
  32 + -H "Authorization: $TOKEN")
  33 +
  34 +echo "响应结果:"
  35 +echo "$RESPONSE" | python3 -m json.tool 2>/dev/null | head -100
  36 +
  37 +echo ""
  38 +echo "=== 3. 检查人头数数据 ==="
  39 +
  40 +# 检查返回结果
  41 +PERSON_COUNT_DATA=$(echo "$RESPONSE" | python3 << 'PYTHON_SCRIPT'
  42 +import sys, json
  43 +try:
  44 + data = json.load(sys.stdin)
  45 + if data.get('code') == 200:
  46 + result = data.get('data', [])
  47 + if isinstance(result, list) and len(result) > 0:
  48 + # 统计有头数的记录
  49 + has_person_count = [item for item in result if item.get('PersonCount', 0) > 0]
  50 + print(f'总记录数: {len(result)}')
  51 + print(f'有人头数的记录数: {len(has_person_count)}')
  52 + if len(has_person_count) > 0:
  53 + print('\n前10条有人头数的记录:')
  54 + for item in has_person_count[:10]:
  55 + print(f" - {item.get('EmployeeName', 'N/A')} (ID: {item.get('EmployeeId', 'N/A')}): {item.get('PersonCount', 0)}人")
  56 + # 计算总人头数
  57 + total = sum(item.get('PersonCount', 0) for item in result)
  58 + print(f'\n总人头数: {total}')
  59 + sys.exit(0)
  60 + else:
  61 + print('❌ 没有人头数数据')
  62 + sys.exit(1)
  63 + else:
  64 + print('❌ 返回数据为空或格式不正确')
  65 + sys.exit(1)
  66 + else:
  67 + print(f'❌ 接口返回错误: {data.get("msg", "未知错误")}')
  68 + sys.exit(1)
  69 +except Exception as e:
  70 + print(f'❌ 解析响应失败: {e}')
  71 + import traceback
  72 + traceback.print_exc()
  73 + sys.exit(1)
  74 +PYTHON_SCRIPT
  75 +)
  76 +
  77 +if [ $? -eq 0 ]; then
  78 + echo "$PERSON_COUNT_DATA"
  79 + echo ""
  80 + echo "✅ 接口测试通过,2025年12月的人头数数据已正确返回"
  81 +else
  82 + echo "$PERSON_COUNT_DATA"
  83 + echo ""
  84 + echo "❌ 接口测试失败"
  85 + exit 1
  86 +fi
  87 +
... ...