kpi分析

This commit is contained in:
liuhanqing
2025-07-25 16:08:43 +08:00
parent 1840464bd8
commit 528af35039
10 changed files with 474 additions and 37 deletions

View File

@@ -193,7 +193,7 @@ public class CheckDifferenceRecordController extends BaseController {
//查询是否存在空料箱库存数
BasStock queryStock = new BasStock();
queryStock.setTrayCode(record.getTrayCode());
queryStock.setProductId("KTP000001");
queryStock.setProductId("KTP0001");
List<BasStock> stocks = basStockService.selectBasStockList(queryStock);
if (!stocks.isEmpty()){
basStockService.deleteBasStockByStockId(stocks.get(0).getStockId());
@@ -221,8 +221,8 @@ public class CheckDifferenceRecordController extends BaseController {
BasWarehouse basWarehouse = basWarehouseService.selectBasWarehouseByWarehouseId(record.getWarehouseId());
if (stocks2.isEmpty() && ("1".equals(basWarehouse.getWarehouseType()) || "2".equals(basWarehouse.getWarehouseType()))) {
BasStock basStock = stocks.get(0);
basStock.setProductId("KTP000001");
basStock.setProductGuid("KTP000001");
basStock.setProductId("KTP0001");
basStock.setProductGuid("KTP0001");
basStock.setQuantity("1");
basStock.setStockStatus("1");
basStock.setOutQuantity("0");

View File

@@ -131,6 +131,26 @@ public class MydoJob extends BaseEntity
private List<String> inOutIds;
private String createLeftTime;
private String createRightTime;
public String getCreateLeftTime() {
return createLeftTime;
}
public void setCreateLeftTime(String createLeftTime) {
this.createLeftTime = createLeftTime;
}
public String getCreateRightTime() {
return createRightTime;
}
public void setCreateRightTime(String createRightTime) {
this.createRightTime = createRightTime;
}
public List<String> getInOutIds() {
return inOutIds;
}

View File

@@ -6,9 +6,11 @@ import com.ruoyi.kpi.service.KpiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* KPI 统计分析
*
*
* @author gw
* @date 2025-05-21
*/
@@ -20,7 +22,7 @@ public class KpiController extends BaseController {
/**
* 获取入库均匀分析数据
*
*
* @param warehouseId 仓库ID
* @param yearMonth 年月,格式:yyyy-MM
* @return 入库均匀分析数据
@@ -31,6 +33,19 @@ public class KpiController extends BaseController {
return success(kpiService.getInAnalysis(warehouseId, yearMonth));
}
/**
* 按时间维度获取入库均匀分析数据
* @param warehouseId
* @return
*/
@GetMapping("/inAnalysisByData")
public AjaxResult getInAnalysisByData(@RequestParam(required = false) String warehouseId,
@RequestParam String startTime,
@RequestParam String endTime,
@RequestParam String inOut) {
return kpiService.getInAnalysisByData(warehouseId, startTime,endTime,inOut);
}
/**
* 获取出库均匀分析数据
*
@@ -126,4 +141,4 @@ public class KpiController extends BaseController {
return success(kpiService.getChartMag(warehouseId,theColumn,theFloor,thePlatoon));
}
}
}

View File

@@ -1,17 +1,20 @@
package com.ruoyi.kpi.service;
import com.ruoyi.common.core.domain.AjaxResult;
import java.util.List;
import java.util.Map;
/**
* KPI 统计分析服务
*
*
* @author gw
* @date 2025-05-21
*/
public interface KpiService {
/**
* 获取入库均匀分析数据
*
*
* @param warehouseId 仓库ID
* @param yearMonth 年月,格式:yyyy-MM
* @return 入库均匀分析数据,包含每个巷道的入库数量
@@ -22,7 +25,7 @@ public interface KpiService {
/**
* 获取巷道均匀分析数据
*
*
* @param warehouseId 仓库ID
* @param startTime 开始时间,格式:yyyy-MM-dd HH:mm:ss
* @param endTime 结束时间,格式:yyyy-MM-dd HH:mm:ss
@@ -44,4 +47,5 @@ public interface KpiService {
Map<String,Object> getChartMag(String warehouseId, String theColumn, String theFloor, String thePlatoon);
}
AjaxResult getInAnalysisByData(String warehouseId, String startTime, String endTime,String inOut);
}

View File

@@ -2,8 +2,14 @@ package com.ruoyi.kpi.service.impl;
import java.util.*;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.ruoyi.bas.cargoSpace.domain.BasCargoSpace;
import com.ruoyi.bas.cargoSpace.mapper.BasCargoSpaceMapper;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.job.mydojob.domain.MydoJob;
import com.ruoyi.job.mydojobfinish.domain.MydoJobFinish;
import com.ruoyi.job.mydojobfinish.mapper.MydoJobFinishMapper;
import org.springframework.beans.factory.annotation.Autowired;
@@ -35,13 +41,13 @@ public class KpiServiceImpl implements KpiService
@Override
public Map<String, Object> getInAnalysis(String warehouseId, String yearMonth)
{
MydoJobFinish mydoJobFinish = new MydoJobFinish();
MydoJob mydoJob = new MydoJob();
List<MydoJobFinish> list = mydoJobFinishMapper.selectMydoJobFinishList(mydoJobFinish);
List<MydoJob> list = mydoJobFinishMapper.selectMydoJobFinishListByMydoJob(mydoJob);
Map<String, Integer> roadwayCount = new HashMap<>();
for (MydoJobFinish job : list) {
for (MydoJob job : list) {
// 检查入库作业标识是否小于10
if (job.getInOrOut() == null || Integer.parseInt(job.getInOrOut()) >= 20) {
continue;
@@ -281,12 +287,12 @@ public class KpiServiceImpl implements KpiService
@Override
public Map<String, Object> getRoadways(String warehouseId){
Map<String, Object> response = new HashMap<>();
// 查询指定仓库的巷道信息
BasCargoSpace queryParams = new BasCargoSpace();
queryParams.setWarehouseId(warehouseId);
List<BasCargoSpace> list = basCargoSpaceMapper.selectBasCargoSpaceList(queryParams);
// 使用Set去重并排序巷道ID
Set<String> roadwaySet = new TreeSet<>();
for (BasCargoSpace space : list) {
@@ -294,7 +300,7 @@ public class KpiServiceImpl implements KpiService
roadwaySet.add(space.getRoadwayId());
}
}
// 转换为前端下拉框所需的格式
List<Map<String, String>> roadways = new ArrayList<>();
for (String roadwayId : roadwaySet) {
@@ -303,7 +309,7 @@ public class KpiServiceImpl implements KpiService
roadway.put("label", "巷道" + roadwayId);
roadways.add(roadway);
}
response.put("data", roadways);
return response;
}
@@ -311,13 +317,13 @@ public class KpiServiceImpl implements KpiService
@Override
public Map<String,Object> getPlatoons(String warehouseId, String roadwayId){
Map<String, Object> response = new HashMap<>();
// 查询指定仓库和巷道的排号信息
BasCargoSpace queryParams = new BasCargoSpace();
queryParams.setWarehouseId(warehouseId);
queryParams.setRoadwayId(roadwayId);
List<BasCargoSpace> list = basCargoSpaceMapper.selectBasCargoSpaceList(queryParams);
// 使用TreeSet去重并排序排号
Set<Long> platoonSet = new TreeSet<>();
for (BasCargoSpace space : list) {
@@ -325,7 +331,7 @@ public class KpiServiceImpl implements KpiService
platoonSet.add(space.getThePlatoon());
}
}
// 转换为前端下拉框所需的格式
List<Map<String, String>> platoons = new ArrayList<>();
for (Long platoon : platoonSet) {
@@ -334,7 +340,7 @@ public class KpiServiceImpl implements KpiService
platoonMap.put("label", platoon + "排");
platoons.add(platoonMap);
}
response.put("data", platoons);
return response;
}
@@ -349,7 +355,7 @@ public class KpiServiceImpl implements KpiService
queryParams.setThePlatoon(Long.parseLong(thePlatoon));
}
List<BasCargoSpace> list = basCargoSpaceMapper.selectBasCargoSpaceList(queryParams);
// 用于存储不同状态的货位坐标
String empty = "";
String full = "";
@@ -357,17 +363,17 @@ public class KpiServiceImpl implements KpiService
String outAllot = "";
String inCheck = "";
String outCheck = "";
// 用于存储列和层的集合
Set<Long> columnSet = new TreeSet<>();
Set<Long> floorSet = new TreeSet<>();
// 处理每个货位的状态
for (BasCargoSpace space : list) {
// 收集列和层信息
columnSet.add(space.getTheColumn());
floorSet.add(space.getTheFloor());
// 根据状态分类处理
if (space.getStatus() != null) {
String coordinate = "[" + space.getTheColumn() + "," + space.getTheFloor() + "," + space.getStatus() + "],";
@@ -381,20 +387,20 @@ public class KpiServiceImpl implements KpiService
}
}
}
// 构建列和层的标签
StringBuilder columnLabels = new StringBuilder();
for (Long col : columnSet) {
columnLabels.append("'").append(col).append("列',");
}
String column = "[" + columnLabels.toString() + "]";
StringBuilder floorLabels = new StringBuilder();
for (Long floor : floorSet) {
floorLabels.append("'").append(floor).append("层',");
}
String floor = "[" + floorLabels.toString() + "]";
// 返回结果
Map<String, Object> map = new HashMap<>();
map.put("empty", "[" + empty + "]");
@@ -412,20 +418,20 @@ public class KpiServiceImpl implements KpiService
@Override
public Map<String,Object> getChartMag(String warehouseId,String theColumn,String theFloor,String thePlatoon){
Map<String, Object> response = new HashMap<>();
// 查询指定货位信息
BasCargoSpace queryParams = new BasCargoSpace();
queryParams.setWarehouseId(warehouseId);
queryParams.setTheColumn(Long.parseLong(theColumn));
queryParams.setTheFloor(Long.parseLong(theFloor));
queryParams.setThePlatoon(Long.parseLong(thePlatoon));
List<BasCargoSpace> list = basCargoSpaceMapper.selectBasCargoSpaceList(queryParams);
if (!list.isEmpty()) {
BasCargoSpace space = list.get(0);
StringBuilder info = new StringBuilder();
// 拼接货位信息
info.append("货位名称:").append(space.getCargoSpaceName()).append("<br>");
info.append("货位类型:").append(space.getTheTypeName()).append("<br>");
@@ -433,14 +439,36 @@ public class KpiServiceImpl implements KpiService
info.append("托盘条码:").append(space.getTrayCode() != null ? space.getTrayCode() : "无").append("<br>");
info.append("货位高度:").append(space.getCargoHight() != null ? space.getCargoHight() : "无").append("<br>");
info.append("是否双伸:").append("Y".equals(space.getIssDouble()) ? "是" : "否").append("<br>");
response.put("msg", info.toString());
} else {
response.put("msg", "未找到货位信息");
}
return response;
}
@Override
public AjaxResult getInAnalysisByData(String warehouseId, String startTime, String endTime,String inOut) {
Map<String,Object> res = new HashMap<>();
MydoJob mydoJobParam = new MydoJob();
mydoJobParam.setWarehouseId(warehouseId);
mydoJobParam.setCreateLeftTime(startTime);
mydoJobParam.setCreateRightTime(endTime);
mydoJobParam.setInOut(inOut);
List<DateTime> datesBetween = DateUtil.rangeToList(DateUtil.parseDate(mydoJobParam.getCreateLeftTime()), DateUtil.parseDate(mydoJobParam.getCreateRightTime()), DateField.DAY_OF_MONTH);
List<MydoJob> list = mydoJobFinishMapper.selectMydoJobFinishListByMydoJob(mydoJobParam);
List<String> x = new ArrayList<>();
List<Long> y = new ArrayList<>();
for (DateTime dateTime : datesBetween) {
//筛选每一日的数量
x.add(DateUtil.format(dateTime, "yyyy-MM-dd"));
y.add(list.stream().filter(mydoJob -> DateUtil.isIn(mydoJob.getCreateTime(), DateUtil.beginOfDay(dateTime),DateUtil.endOfDay(dateTime))).count());
}
res.put("xAxis",x);
res.put("series",y);
return AjaxResult.success(res);
}
}

View File

@@ -415,6 +415,7 @@ public class PdaServiceImp implements PdaService {
checkCommitRecord.setStockId(mydoJobDetail.getStockId());
checkCommitRecord.setSpare1(mydoJobDetail.getInOrOutDId());
checkCommitRecord.setSpare2(mydoJobDetail.getProductId());
checkCommitRecord.setSpare3(mydoJobDetail.getOutQuantity());
checkCommitRecordService.insertCheckCommitRecord(checkCommitRecord);
mydoJobDetail.setSpare3("1");

View File

@@ -7,3 +7,12 @@ export function getInAnalysisList(query) {
params: query
})
}
// 获取入库数量统计数据
export function getInAnalysisData(query) {
return request({
url: '/kpi/inAnalysisByData',
method: 'get',
params: query
})
}

View File

@@ -332,12 +332,12 @@ export default {
/** 确认选择物料 */
confirmSelectProduct() {
// 检查选中的物料是否已存在于表单中
const duplicateProducts = this.selectedProducts.filter(selected =>
const duplicateProducts = this.selectedProducts.filter(selected =>
this.productList.some(existing => existing.productId === selected.productId)
);
if (duplicateProducts.length > 0) {
this.$modal.msgError('以下物料已存在,请修改数量:\n' + duplicateProducts.map(p => p.productName).join('\n'));
this.$modal.msgError(this.$t('receiveStockinAdd.addProductError')+'\n' + duplicateProducts.map(p => p.productName).join('\n'));
return;
}

View File

@@ -0,0 +1,180 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true">
<el-form-item label="仓库" prop="warehouseId">
<el-select v-model="queryParams.warehouseId" placeholder="请选择仓库" clearable>
<el-option
v-for="item in warehouseOptions"
:key="item.warehouseId"
:label="item.warehouseName"
:value="item.warehouseId"
/>
</el-select>
</el-form-item>
<el-form-item label="时间范围" prop="dateRange">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
@change="handleDateRangeChange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
</el-form-item>
</el-form>
<div class="chart-wrapper">
<div class="chart-scroll-container">
<div ref="chartRef" style="height: 500px; min-width: 800px"></div>
</div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts'
import { listWarehouse } from '@/api/bas/warehouse'
import { getInAnalysisData } from '@/api/kpi/inAnalysis'
export default {
name: 'InAnalysisByTime',
data() {
return {
// 查询参数
queryParams: {
warehouseId: undefined,
startTime: '',
endTime: '',
inOut:'1'
},
dateRange: [],
// 仓库选项
warehouseOptions: [],
// 图表实例
chart: null,
// 图表数据
chartData: {
xAxis: [],
series: []
}
}
},
mounted() {
this.getWarehouseList()
this.initChart()
this.setDefaultDateRange()
},
beforeDestroy() {
if (this.chart) {
this.chart.dispose()
this.chart = null
}
},
methods: {
/** 获取仓库列表 */
async getWarehouseList() {
try {
const response = await listWarehouse()
this.warehouseOptions = response.rows
} catch (error) {
console.error('获取仓库列表失败:', error)
}
},
/** 设置最近七天时间范围 */
setDefaultDateRange() {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 6)
this.dateRange = [this.formatDate(start), this.formatDate(end)]
this.handleDateRangeChange(this.dateRange)
this.handleQuery()
},
/** 格式化日期 */
formatDate(date) {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
/** 初始化图表 */
initChart() {
this.chart = echarts.init(this.$refs.chartRef)
this.updateChart()
},
/** 更新图表数据 */
updateChart() {
const option = {
title: {
text: '入库数量统计'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: this.chartData.xAxis
},
yAxis: {
type: 'value',
name: '入库数量'
},
series: [{
name: '入库数量',
type: 'line',
data: this.chartData.series,
smooth: true
}]
}
this.chart.setOption(option)
},
/** 处理日期范围变化 */
handleDateRangeChange(val) {
if (val) {
this.queryParams.startTime = val[0]
this.queryParams.endTime = val[1]
} else {
this.queryParams.startTime = ''
this.queryParams.endTime = ''
}
},
/** 处理查询按钮点击 */
async handleQuery() {
try {
const response = await getInAnalysisData(this.queryParams)
if (response.code === 200) {
this.chartData.xAxis = response.data.xAxis
this.chartData.series = response.data.series
this.updateChart()
} else {
this.$message.error(response.msg)
}
} catch (error) {
console.error('获取统计数据失败:', error)
this.$message.error('获取统计数据失败')
}
},
}
}
</script>
<style scoped>
.chart-wrapper {
margin-top: 20px;
background: #fff;
padding: 20px;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.chart-scroll-container {
width: 100%;
overflow-x: auto;
overflow-y: hidden;
}
</style>

View File

@@ -0,0 +1,180 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true">
<el-form-item label="仓库" prop="warehouseId">
<el-select v-model="queryParams.warehouseId" placeholder="请选择仓库" clearable>
<el-option
v-for="item in warehouseOptions"
:key="item.warehouseId"
:label="item.warehouseName"
:value="item.warehouseId"
/>
</el-select>
</el-form-item>
<el-form-item label="时间范围" prop="dateRange">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
@change="handleDateRangeChange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
</el-form-item>
</el-form>
<div class="chart-wrapper">
<div class="chart-scroll-container">
<div ref="chartRef" style="height: 500px; min-width: 800px"></div>
</div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts'
import { listWarehouse } from '@/api/bas/warehouse'
import { getInAnalysisData } from '@/api/kpi/inAnalysis'
export default {
name: 'InAnalysisByTime',
data() {
return {
// 查询参数
queryParams: {
warehouseId: undefined,
startTime: '',
endTime: '',
inOut:'2'
},
dateRange: [],
// 仓库选项
warehouseOptions: [],
// 图表实例
chart: null,
// 图表数据
chartData: {
xAxis: [],
series: []
}
}
},
mounted() {
this.getWarehouseList()
this.initChart()
this.setDefaultDateRange()
},
beforeDestroy() {
if (this.chart) {
this.chart.dispose()
this.chart = null
}
},
methods: {
/** 获取仓库列表 */
async getWarehouseList() {
try {
const response = await listWarehouse()
this.warehouseOptions = response.rows
} catch (error) {
console.error('获取仓库列表失败:', error)
}
},
/** 设置最近七天时间范围 */
setDefaultDateRange() {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 6)
this.dateRange = [this.formatDate(start), this.formatDate(end)]
this.handleDateRangeChange(this.dateRange)
this.handleQuery()
},
/** 格式化日期 */
formatDate(date) {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
/** 初始化图表 */
initChart() {
this.chart = echarts.init(this.$refs.chartRef)
this.updateChart()
},
/** 更新图表数据 */
updateChart() {
const option = {
title: {
text: '出库数量统计'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: this.chartData.xAxis
},
yAxis: {
type: 'value',
name: '出库数量'
},
series: [{
name: '出库数量',
type: 'line',
data: this.chartData.series,
smooth: true
}]
}
this.chart.setOption(option)
},
/** 处理日期范围变化 */
handleDateRangeChange(val) {
if (val) {
this.queryParams.startTime = val[0]
this.queryParams.endTime = val[1]
} else {
this.queryParams.startTime = ''
this.queryParams.endTime = ''
}
},
/** 处理查询按钮点击 */
async handleQuery() {
try {
const response = await getInAnalysisData(this.queryParams)
if (response.code === 200) {
this.chartData.xAxis = response.data.xAxis
this.chartData.series = response.data.series
this.updateChart()
} else {
this.$message.error(response.msg)
}
} catch (error) {
console.error('获取统计数据失败:', error)
this.$message.error('获取统计数据失败')
}
},
}
}
</script>
<style scoped>
.chart-wrapper {
margin-top: 20px;
background: #fff;
padding: 20px;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.chart-scroll-container {
width: 100%;
overflow-x: auto;
overflow-y: hidden;
}
</style>