Commit 97c1bdaf3d7af4bfe9347fb332bed0e577db83ff
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 './product-form.vue' |
| 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
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&ProductId=xxx&UsageBatchId=xxx&UsageStartTime=2025-01-01&UsageEndTime=2025-12-31&ApprovalStatus=已通过&IsReceived=true&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 | + | ... | ... |