forked from liweijie/education
课时统计功能提交
This commit is contained in:
+11
@@ -195,6 +195,17 @@ public class KSController {
|
|||||||
return Result.success(ksService.pageHourStatistics(new PageQuery(pageNum, pageSize), cond));
|
return Result.success(ksService.pageHourStatistics(new PageQuery(pageNum, pageSize), cond));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课时核算:按指定自然年的已排课次聚合生成课时统计表(覆盖该年数据)。
|
||||||
|
* 仅教务人员可执行。
|
||||||
|
*
|
||||||
|
* @param year 自然年(可选,默认当前年)
|
||||||
|
*/
|
||||||
|
@PostMapping("/hour-stat/recalculate")
|
||||||
|
public Result<Integer> recalcHourStatistics(@RequestParam(required = false) Integer year) {
|
||||||
|
return Result.success(ksService.recalcHourStatistics(year));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出课时统计 Excel。
|
* 导出课时统计 Excel。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -32,4 +32,13 @@ public interface KSService {
|
|||||||
* 导出课时费统计。
|
* 导出课时费统计。
|
||||||
*/
|
*/
|
||||||
void exportFeeStatistics(HourStatisticsQuery cond, HttpServletResponse response);
|
void exportFeeStatistics(HourStatisticsQuery cond, HttpServletResponse response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课时核算:按已排课次(实施_课程表)聚合指定自然年各教员上下半年课时,
|
||||||
|
* 覆盖写入课时统计表。仅教务人员可执行。
|
||||||
|
*
|
||||||
|
* @param year 自然年(如 2026);为空取当前年
|
||||||
|
* @return 写入统计表的教员行数
|
||||||
|
*/
|
||||||
|
int recalcHourStatistics(Integer year);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,18 @@ import com.roomroot.common.exception.ServiceException;
|
|||||||
import com.roomroot.common.utils.StringUtils;
|
import com.roomroot.common.utils.StringUtils;
|
||||||
import com.roomroot.common.utils.poi.ExcelUtil;
|
import com.roomroot.common.utils.poi.ExcelUtil;
|
||||||
import com.roomroot.jwgl.dto.ks.HourStatisticsQuery;
|
import com.roomroot.jwgl.dto.ks.HourStatisticsQuery;
|
||||||
|
import com.roomroot.jwgl.entity.JYB;
|
||||||
import com.roomroot.jwgl.entity.KSFBZ;
|
import com.roomroot.jwgl.entity.KSFBZ;
|
||||||
|
import com.roomroot.jwgl.entity.KSTJB;
|
||||||
|
import com.roomroot.jwgl.entity.SSKC;
|
||||||
|
import com.roomroot.jwgl.entity.SSKCB;
|
||||||
|
import com.roomroot.jwgl.entity.SSKCBFZJY;
|
||||||
|
import com.roomroot.jwgl.mapper.JYBMapper;
|
||||||
import com.roomroot.jwgl.mapper.KSFBZMapper;
|
import com.roomroot.jwgl.mapper.KSFBZMapper;
|
||||||
import com.roomroot.jwgl.mapper.KSTJBMapper;
|
import com.roomroot.jwgl.mapper.KSTJBMapper;
|
||||||
|
import com.roomroot.jwgl.mapper.SSKCBFZJYMapper;
|
||||||
|
import com.roomroot.jwgl.mapper.SSKCBMapper;
|
||||||
|
import com.roomroot.jwgl.mapper.SSKCMapper;
|
||||||
import com.roomroot.jwgl.mapper.SystemInitializationMapper;
|
import com.roomroot.jwgl.mapper.SystemInitializationMapper;
|
||||||
import com.roomroot.jwgl.service.KSService;
|
import com.roomroot.jwgl.service.KSService;
|
||||||
import com.roomroot.jwgl.unit.PageQuery;
|
import com.roomroot.jwgl.unit.PageQuery;
|
||||||
@@ -18,14 +27,22 @@ import com.roomroot.jwgl.vo.ks.HourStatisticsVO;
|
|||||||
import com.roomroot.jwgl.vo.systeminitialization.SemesterInitializationVO;
|
import com.roomroot.jwgl.vo.systeminitialization.SemesterInitializationVO;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.roomroot.jwgl.utils.BasicManagementConstants.BAD_REQUEST;
|
import static com.roomroot.jwgl.utils.BasicManagementConstants.BAD_REQUEST;
|
||||||
|
import static com.roomroot.jwgl.utils.BasicManagementConstants.FORBIDDEN;
|
||||||
import static com.roomroot.jwgl.utils.BasicManagementConstants.NOT_FOUND;
|
import static com.roomroot.jwgl.utils.BasicManagementConstants.NOT_FOUND;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,6 +66,18 @@ public class KSServiceImpl implements KSService {
|
|||||||
@Resource
|
@Resource
|
||||||
private JwglRoleHelper jwglRoleHelper;
|
private JwglRoleHelper jwglRoleHelper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SSKCBMapper sskcbMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SSKCBFZJYMapper sskcbfzjyMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SSKCMapper sskcMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private JYBMapper jybMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResult<HourStatisticsVO> pageHourStatistics(PageQuery query, HourStatisticsQuery cond) {
|
public PageResult<HourStatisticsVO> pageHourStatistics(PageQuery query, HourStatisticsQuery cond) {
|
||||||
Page<HourStatisticsVO> page = queryHours(query, cond);
|
Page<HourStatisticsVO> page = queryHours(query, cond);
|
||||||
@@ -136,6 +165,66 @@ public class KSServiceImpl implements KSService {
|
|||||||
return systemInitializationMapper.selectCurrentSemester();
|
return systemInitializationMapper.selectCurrentSemester();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 课时核算:把指定自然年已排课次(每课次 2 + 节次调节 学时)按教员聚合到课时统计表。
|
||||||
|
* 归属:课次主讲教员(实施_课程表_辅助教员 zjy=1),无主讲记录回退课程责任教员。
|
||||||
|
* 半年口径:1-6 月上半年、7-12 月下半年。该年原有统计行先整体清掉再重写。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public int recalcHourStatistics(Integer year) {
|
||||||
|
if (!jwglRoleHelper.isDepartmentPersonnel()) {
|
||||||
|
throw new ServiceException("只有教务人员可以执行课时核算", FORBIDDEN);
|
||||||
|
}
|
||||||
|
int y = year != null ? year : LocalDate.now().getYear();
|
||||||
|
List<SSKCB> yearLessons = sskcbMapper.selectList(new LambdaQueryWrapper<SSKCB>()
|
||||||
|
.eq(SSKCB::getSczt, 0).isNotNull(SSKCB::getRq)).stream()
|
||||||
|
.filter(l -> l.getRq().getYear() == y).collect(Collectors.toList());
|
||||||
|
if (yearLessons.isEmpty()) {
|
||||||
|
kstjbMapper.delete(new LambdaQueryWrapper<KSTJB>().eq(KSTJB::getNf, y));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// 课次主讲教员(zjy=1,每课次取一条)
|
||||||
|
List<String> lessonIds = yearLessons.stream().map(SSKCB::getBh).collect(Collectors.toList());
|
||||||
|
Map<String, String> mainTeacher = new HashMap<>();
|
||||||
|
for (SSKCBFZJY f : sskcbfzjyMapper.selectList(new LambdaQueryWrapper<SSKCBFZJY>()
|
||||||
|
.in(SSKCBFZJY::getSskcbbh, lessonIds).eq(SSKCBFZJY::getZjy, 1))) {
|
||||||
|
if (f.getFzjybh() != null && !f.getFzjybh().isEmpty()) {
|
||||||
|
mainTeacher.putIfAbsent(f.getSskcbbh(), f.getFzjybh());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 课程责任教员兜底
|
||||||
|
Set<String> courseIds = yearLessons.stream().map(SSKCB::getSskcbh).collect(Collectors.toSet());
|
||||||
|
Map<String, String> courseTeacher = new HashMap<>();
|
||||||
|
if (!courseIds.isEmpty()) {
|
||||||
|
for (SSKC c : sskcMapper.selectByBhs(new ArrayList<>(courseIds))) {
|
||||||
|
courseTeacher.put(c.getBh(), c.getJybh());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 按教员累计上/下半年课时
|
||||||
|
Map<String, double[]> hours = new HashMap<>();
|
||||||
|
for (SSKCB l : yearLessons) {
|
||||||
|
String jybh = mainTeacher.getOrDefault(l.getBh(), courseTeacher.get(l.getSskcbh()));
|
||||||
|
if (jybh == null || jybh.isEmpty()) continue;
|
||||||
|
double h = 2D + (l.getJcdj() == null ? 0 : l.getJcdj());
|
||||||
|
int half = l.getRq().getMonthValue() <= 6 ? 0 : 1;
|
||||||
|
hours.computeIfAbsent(jybh, k -> new double[2])[half] += h;
|
||||||
|
}
|
||||||
|
kstjbMapper.delete(new LambdaQueryWrapper<KSTJB>().eq(KSTJB::getNf, y));
|
||||||
|
for (Map.Entry<String, double[]> e : hours.entrySet()) {
|
||||||
|
KSTJB row = new KSTJB();
|
||||||
|
row.setNf(y);
|
||||||
|
row.setJybh(e.getKey());
|
||||||
|
row.setSbn(e.getValue()[0]);
|
||||||
|
row.setXbn(e.getValue()[1]);
|
||||||
|
JYB teacher = jybMapper.selectById(e.getKey());
|
||||||
|
// 职称列为 NOT NULL 且达梦空串等同 NULL,无职称须给兜底值
|
||||||
|
row.setZc(teacher != null && StringUtils.isNotEmpty(teacher.getZc()) ? teacher.getZc() : "未定级");
|
||||||
|
kstjbMapper.insert(row);
|
||||||
|
}
|
||||||
|
return hours.size();
|
||||||
|
}
|
||||||
|
|
||||||
private KSFBZ resolveFeeStandard(HourStatisticsQuery cond) {
|
private KSFBZ resolveFeeStandard(HourStatisticsQuery cond) {
|
||||||
if (cond != null && StringUtils.isNotEmpty(cond.getKsfbzbh())) {
|
if (cond != null && StringUtils.isNotEmpty(cond.getKsfbzbh())) {
|
||||||
KSFBZ specified = ksfbzMapper.selectById(cond.getKsfbzbh());
|
KSFBZ specified = ksfbzMapper.selectById(cond.getKsfbzbh());
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ export function listHourStatistics(query) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 课时核算:按自然年聚合已排课次生成课时统计表(覆盖该年,仅教务)
|
||||||
|
export function recalcHourStatistics(year) {
|
||||||
|
return request({
|
||||||
|
url: '/ks/hour-stat/recalculate',
|
||||||
|
method: 'post',
|
||||||
|
params: year != null ? { year } : {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 导出课时统计 Excel(返回 Blob,文件名 课时统计.xlsx)
|
// 导出课时统计 Excel(返回 Blob,文件名 课时统计.xlsx)
|
||||||
export function exportHourStatistics(query) {
|
export function exportHourStatistics(query) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
<el-col :xs="24" :sm="12" :md="6" class="search-actions-col">
|
<el-col :xs="24" :sm="12" :md="6" class="search-actions-col">
|
||||||
<div class="search-actions">
|
<div class="search-actions">
|
||||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||||
|
<el-button type="primary" plain @click="handleRecalc">课时核算</el-button>
|
||||||
<el-button type="primary" plain @click="handleExport">导出 Excel</el-button>
|
<el-button type="primary" plain @click="handleExport">导出 Excel</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
@@ -130,6 +131,7 @@ import { saveAs } from 'file-saver'
|
|||||||
import {
|
import {
|
||||||
listHourStatistics,
|
listHourStatistics,
|
||||||
exportHourStatistics,
|
exportHourStatistics,
|
||||||
|
recalcHourStatistics,
|
||||||
listFeeStatistics,
|
listFeeStatistics,
|
||||||
exportFeeStatistics
|
exportFeeStatistics
|
||||||
} from '@/api/classHour/hourStat'
|
} from '@/api/classHour/hourStat'
|
||||||
@@ -230,6 +232,21 @@ export default {
|
|||||||
this.fetchList()
|
this.fetchList()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 课时核算:按学期年度(缺省当前年)聚合已排课次,覆盖该年统计 */
|
||||||
|
handleRecalc() {
|
||||||
|
const nd = (this.hourForm.nd || '').trim()
|
||||||
|
const year = nd ? Number(nd) : null
|
||||||
|
const tip = year
|
||||||
|
? `将按 ${year} 年已排课次重新核算课时统计,该年现有统计将被覆盖,是否继续?`
|
||||||
|
: '将按当前年已排课次重新核算课时统计,该年现有统计将被覆盖,是否继续?'
|
||||||
|
this.$confirm(tip, '课时核算', { type: 'warning' }).then(() => {
|
||||||
|
recalcHourStatistics(year).then(res => {
|
||||||
|
this.$message.success(`核算完成,共生成 ${(res && res.data) || 0} 名教员的课时统计`)
|
||||||
|
this.fetchList()
|
||||||
|
}).catch(() => {})
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
|
||||||
// ==================== 导出 ====================
|
// ==================== 导出 ====================
|
||||||
handleExport() {
|
handleExport() {
|
||||||
const clean = this.buildParams(this.currentForm)
|
const clean = this.buildParams(this.currentForm)
|
||||||
|
|||||||
Reference in New Issue
Block a user