ClassTimeStatisticsServiceImpl.java 68 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313
  1. package com.xjrsoft.module.classtime.service.impl;
  2. import cn.hutool.core.bean.BeanUtil;
  3. import cn.hutool.core.util.StrUtil;
  4. import com.alibaba.excel.EasyExcel;
  5. import com.alibaba.excel.support.ExcelTypeEnum;
  6. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  7. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  8. import com.github.yulichang.base.MPJBaseServiceImpl;
  9. import com.github.yulichang.wrapper.MPJLambdaWrapper;
  10. import com.google.gson.Gson;
  11. import com.google.gson.JsonArray;
  12. import com.google.gson.JsonElement;
  13. import com.google.gson.JsonObject;
  14. import com.google.gson.JsonParser;
  15. import com.xjrsoft.common.enums.CourseAdjustTypeEnum;
  16. import com.xjrsoft.common.enums.CourseTimeTypeEnum;
  17. import com.xjrsoft.common.enums.DeleteMark;
  18. import com.xjrsoft.common.enums.EnabledMark;
  19. import com.xjrsoft.common.exception.MyException;
  20. import com.xjrsoft.common.mybatis.SqlRunnerAdapter;
  21. import com.xjrsoft.common.utils.VoToColumnUtil;
  22. import com.xjrsoft.module.classtime.dto.AddClassTimeStatisticsDto;
  23. import com.xjrsoft.module.classtime.dto.CourseRecordDto;
  24. import com.xjrsoft.module.classtime.entity.ClassTimeDelete;
  25. import com.xjrsoft.module.classtime.entity.ClassTimeStatistics;
  26. import com.xjrsoft.module.classtime.entity.ClassTimeStatisticsAdministration;
  27. import com.xjrsoft.module.classtime.entity.ClassTimeStatisticsRecord;
  28. import com.xjrsoft.module.classtime.entity.ClassTimeStatisticsSet;
  29. import com.xjrsoft.module.classtime.mapper.ClassTimeStatisticsAdministrationMapper;
  30. import com.xjrsoft.module.classtime.mapper.ClassTimeStatisticsMapper;
  31. import com.xjrsoft.module.classtime.mapper.ClassTimeStatisticsRecordMapper;
  32. import com.xjrsoft.module.classtime.service.IClassTimeDeleteService;
  33. import com.xjrsoft.module.classtime.service.IClassTimeStatisticsService;
  34. import com.xjrsoft.module.classtime.service.IClassTimeStatisticsSetService;
  35. import com.xjrsoft.module.classtime.vo.ClassTimeStatisticsAdministrationExcelVo;
  36. import com.xjrsoft.module.classtime.vo.ClassTimeStatisticsRecordVo;
  37. import com.xjrsoft.module.classtime.vo.CourseClassTimeStatisticsRecordVo;
  38. import com.xjrsoft.module.classtime.vo.CourseListVo;
  39. import com.xjrsoft.module.classtime.vo.TeacherListVo;
  40. import com.xjrsoft.module.classtime.vo.WeekTimeRangeVo;
  41. import com.xjrsoft.module.oa.entity.WfTeacherCourseTime;
  42. import com.xjrsoft.module.organization.entity.User;
  43. import com.xjrsoft.module.organization.service.IUserService;
  44. import com.xjrsoft.module.system.entity.DictionaryDetail;
  45. import com.xjrsoft.module.system.service.IDictionarydetailService;
  46. import com.xjrsoft.module.teacher.entity.BaseTeacher;
  47. import com.xjrsoft.module.teacher.entity.XjrUser;
  48. import lombok.AllArgsConstructor;
  49. import me.zhyd.oauth.log.Log;
  50. import org.apache.poi.ss.usermodel.Cell;
  51. import org.apache.poi.ss.usermodel.CellStyle;
  52. import org.apache.poi.ss.usermodel.Font;
  53. import org.apache.poi.ss.usermodel.HorizontalAlignment;
  54. import org.apache.poi.ss.usermodel.Row;
  55. import org.apache.poi.ss.usermodel.Sheet;
  56. import org.apache.poi.ss.usermodel.VerticalAlignment;
  57. import org.apache.poi.ss.usermodel.Workbook;
  58. import org.apache.poi.ss.util.CellRangeAddress;
  59. import org.apache.poi.xssf.usermodel.XSSFWorkbook;
  60. import org.springframework.stereotype.Service;
  61. import org.springframework.transaction.annotation.Transactional;
  62. import org.springframework.web.multipart.MultipartFile;
  63. import java.io.ByteArrayOutputStream;
  64. import java.math.BigDecimal;
  65. import java.math.RoundingMode;
  66. import java.time.DayOfWeek;
  67. import java.time.LocalDate;
  68. import java.time.format.DateTimeFormatter;
  69. import java.time.temporal.ChronoUnit;
  70. import java.util.ArrayList;
  71. import java.util.Arrays;
  72. import java.util.Collection;
  73. import java.util.Date;
  74. import java.util.HashMap;
  75. import java.util.LinkedHashMap;
  76. import java.util.List;
  77. import java.util.Map;
  78. import java.util.Set;
  79. import java.util.concurrent.CompletableFuture;
  80. import java.util.stream.Collectors;
  81. /**
  82. * @title: 课时统计
  83. * @Author dzx
  84. * @Date: 2024-09-26
  85. * @Version 1.0
  86. */
  87. @Service
  88. @AllArgsConstructor
  89. public class ClassTimeStatisticsServiceImpl extends MPJBaseServiceImpl<ClassTimeStatisticsMapper, ClassTimeStatistics> implements IClassTimeStatisticsService {
  90. private final ClassTimeStatisticsMapper statisticsMapper;
  91. private final ClassTimeStatisticsRecordMapper recordMapper;
  92. private final IClassTimeStatisticsSetService statisticsSetService;
  93. private final IClassTimeDeleteService deleteService;
  94. private final IDictionarydetailService dictionaryService;
  95. private final ClassTimeStatisticsAdministrationMapper administrationMapper;
  96. private final IUserService userService;
  97. @Override
  98. @Transactional(rollbackFor = Exception.class)
  99. public Boolean add(ClassTimeStatistics classTimeStatistics) {
  100. statisticsMapper.insert(classTimeStatistics);
  101. return true;
  102. }
  103. @Override
  104. @Transactional(rollbackFor = Exception.class)
  105. public Boolean addCourse(AddClassTimeStatisticsDto dto) {
  106. ClassTimeStatistics classTimeStatistics = BeanUtil.toBean(dto, ClassTimeStatistics.class);
  107. classTimeStatistics.setStatus(0);
  108. classTimeStatistics.setCategory(2);
  109. classTimeStatistics.setOverWorkloadNumberStatus(0);
  110. classTimeStatistics.setCreateDate(new Date());
  111. List<WeekTimeRangeVo> weekTimeRangeVos = calculateNaturalWeeks(classTimeStatistics.getStartDate(), classTimeStatistics.getEndDate());
  112. classTimeStatistics.setWeeks(weekTimeRangeVos.size());
  113. //查询最新权重并保存到统计中
  114. Integer category = 3;
  115. List<ClassTimeStatisticsSet> list = statisticsSetService.list(
  116. new QueryWrapper<ClassTimeStatisticsSet>().lambda()
  117. .eq(ClassTimeStatisticsSet::getCategory, category)
  118. .orderByDesc(ClassTimeStatisticsSet::getCreateDate)
  119. );
  120. if(list.isEmpty()){
  121. throw new MyException("请先设置权重并进行保存");
  122. }
  123. classTimeStatistics.setWeightSetJson(list.get(0).getJsonContent());
  124. this.save(classTimeStatistics);
  125. refreshCourseRecord(classTimeStatistics);
  126. return true;
  127. }
  128. @Override
  129. @Transactional(rollbackFor = Exception.class)
  130. public Boolean update(ClassTimeStatistics classTimeStatistics) {
  131. statisticsMapper.updateById(classTimeStatistics);
  132. return true;
  133. }
  134. @Override
  135. @Transactional(rollbackFor = Exception.class)
  136. public Boolean updateCourse(ClassTimeStatistics classTimeStatistics) {
  137. List<WeekTimeRangeVo> weekTimeRangeVos = calculateNaturalWeeks(classTimeStatistics.getStartDate(), classTimeStatistics.getEndDate());
  138. classTimeStatistics.setWeeks(weekTimeRangeVos.size());
  139. statisticsMapper.updateById(classTimeStatistics);
  140. return true;
  141. }
  142. @Override
  143. @Transactional(rollbackFor = Exception.class)
  144. public Boolean delete(List<Long> ids) {
  145. statisticsMapper.deleteBatchIds(ids);
  146. recordMapper.delete(Wrappers.lambdaQuery(ClassTimeStatisticsRecord.class).in(ClassTimeStatisticsRecord::getClassTimeStatisticsId, ids));
  147. return true;
  148. }
  149. @Override
  150. @Transactional(rollbackFor = Exception.class)
  151. public Boolean add(AddClassTimeStatisticsDto dto) {
  152. ClassTimeStatistics classTimeStatistics = BeanUtil.toBean(dto, ClassTimeStatistics.class);
  153. classTimeStatistics.setStatus(0);
  154. classTimeStatistics.setCategory(1);
  155. classTimeStatistics.setCreateDate(new Date());
  156. //查询最新权重并保存到统计中
  157. Integer category = 1;
  158. List<ClassTimeStatisticsSet> list = statisticsSetService.list(
  159. new QueryWrapper<ClassTimeStatisticsSet>().lambda()
  160. .eq(ClassTimeStatisticsSet::getCategory, category)
  161. .orderByDesc(ClassTimeStatisticsSet::getCreateDate)
  162. );
  163. if(list.isEmpty()){
  164. throw new MyException("请先设置权重并进行保存");
  165. }
  166. classTimeStatistics.setWeightSetJson(list.get(0).getJsonContent());
  167. //查询最新费用并保存到统计中
  168. category = 2;
  169. list = statisticsSetService.list(
  170. new QueryWrapper<ClassTimeStatisticsSet>().lambda()
  171. .eq(ClassTimeStatisticsSet::getCategory, category)
  172. .orderByDesc(ClassTimeStatisticsSet::getCreateDate)
  173. );
  174. if(list.isEmpty()){
  175. throw new MyException("请先设置费用并进行保存");
  176. }
  177. classTimeStatistics.setCostSetJson(list.get(0).getJsonContent());
  178. this.save(classTimeStatistics);
  179. CompletableFuture.runAsync(() -> {
  180. refreshRecord(classTimeStatistics);
  181. });
  182. return true;
  183. }
  184. /**
  185. * 1、通过xjr_user和base_teacher查询所有教师,查询聘用类型为:正式聘用和外聘的
  186. * 2、根据统计的开始日期和结束日期查询课时补充(wf_teacher_course_time)中的教研会、督导听课、临近三年退休政策、出题、阅卷、周末培优
  187. * 3、根绝统计的开始日期和结束日期查询课程表(course_table)中的所有课程数据
  188. * 4、查询补班日期下的所有课程
  189. * 5、属于节假日的课程不查询
  190. */
  191. @Override
  192. @Transactional(rollbackFor = Exception.class)
  193. public Boolean refreshRecord(ClassTimeStatistics statistics) {
  194. try {
  195. // 1、查询教师
  196. List<TeacherListVo> teacherList = this.baseMapper.getTeacherList();
  197. // 2、查询补课课时
  198. List<DictionaryDetail> CourseTimeTypeList = dictionaryService.list(
  199. new QueryWrapper<DictionaryDetail>().lambda()
  200. .eq(DictionaryDetail::getItemId, 1833772737004875778L)
  201. .eq(DictionaryDetail::getDeleteMark, DeleteMark.NODELETE.getCode())
  202. .eq(DictionaryDetail::getEnabledMark, EnabledMark.ENABLED.getCode())
  203. .orderByAsc(DictionaryDetail::getCode)
  204. );
  205. Map<String, String> CourseTimeTypeMap = CourseTimeTypeList.stream().collect(Collectors.toMap(DictionaryDetail::getCode, DictionaryDetail::getName));
  206. List<WfTeacherCourseTime> courseTimeList = this.baseMapper.getWfTeacherCourseTimeList(statistics);
  207. //按照课时补充类型分类统计
  208. Map<String, List<WfTeacherCourseTime>> courseTimeMap = courseTimeList.stream().collect(Collectors.groupingBy(WfTeacherCourseTime::getCourseTimeType));
  209. JsonParser parser = new JsonParser();
  210. //费用设置jsonArray
  211. JsonArray costSetArray = parser.parse(statistics.getCostSetJson()).getAsJsonArray();
  212. Map<String, Double> costSetMap = new LinkedHashMap<>();
  213. for (JsonElement jsonElement : costSetArray) {
  214. JsonObject object = jsonElement.getAsJsonObject();
  215. costSetMap.put(object.get("field").getAsString(), object.get("value").getAsDouble());
  216. }
  217. //权重设置jsonArray
  218. JsonArray weightSetArray = parser.parse(statistics.getWeightSetJson()).getAsJsonArray();
  219. Map<String, Double> weightSetMap = new LinkedHashMap<>();
  220. for (JsonElement jsonElement : weightSetArray) {
  221. JsonObject object = jsonElement.getAsJsonObject();
  222. weightSetMap.put(object.get("label").getAsString(), object.get("value").getAsDouble());
  223. }
  224. //计算出这个时间段内一共多少周
  225. List<WeekTimeRangeVo> weekTimeRangeVos = calculateNaturalWeeks(statistics.getStartDate(), statistics.getEndDate());
  226. //取出所有的日期
  227. List<LocalDate> allDateList = getDatesBetween(statistics.getStartDate(), statistics.getEndDate());
  228. //查询课程数据,排除开节假日的数据
  229. List<CourseListVo> allCourseList = this.baseMapper.getCourseList(statistics);
  230. allCourseList.addAll(this.baseMapper.getHolidayReplaceCourseList(statistics));
  231. //查询删除课时的数据,将每个班删除的具体日期统计出来
  232. List<ClassTimeDelete> deleteList = deleteService.list(
  233. new QueryWrapper<ClassTimeDelete>().lambda()
  234. .ne(ClassTimeDelete::getStatus, 2)
  235. .eq(ClassTimeDelete::getEnabledMark, EnabledMark.ENABLED.getCode())
  236. .eq(ClassTimeDelete::getDeleteMark, DeleteMark.NODELETE.getCode())
  237. );
  238. Map<Long, List<ClassTimeDelete>> deleteDataMap = deleteList.stream().collect(Collectors.groupingBy(ClassTimeDelete::getClassId));
  239. Map<Long, Map<LocalDate, String>> deleteMap = new HashMap<>();//将每个班级计算出来所有被删除的日期存入map
  240. for (Long classId : deleteDataMap.keySet()) {
  241. Map<LocalDate, String> dateMap = new HashMap<>();
  242. for (ClassTimeDelete classTimeDelete : deleteDataMap.get(classId)) {
  243. LocalDate currentDate = classTimeDelete.getStartDate();
  244. while (!currentDate.isAfter(classTimeDelete.getEndDate())) {
  245. dateMap.put(currentDate, classTimeDelete.getTimePeriod());
  246. currentDate = currentDate.plusDays(1); // 增加一天
  247. }
  248. }
  249. //去重并存到map中
  250. deleteMap.put(classId, dateMap);
  251. }
  252. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
  253. //查询所有老师发起顶课通过的数量,也要分别计算早自习、晚自习、正课、如果日期出入课时删除中,也需要跳过
  254. List<CourseListVo> allSubstituteList = this.baseMapper.getSubstituteList(statistics);
  255. List<ClassTimeStatisticsRecord> insertList = new ArrayList<>();
  256. //循环教师,准备开始计算
  257. for (TeacherListVo teacher : teacherList) {
  258. ClassTimeStatisticsRecord record = new ClassTimeStatisticsRecord();
  259. record.setUserId(teacher.getId());
  260. record.setClassTimeStatisticsId(statistics.getId());
  261. record.setEmployType(teacher.getEmployType());
  262. JsonArray allClassTimeDataArray = new JsonArray();
  263. Double allClassTime = 0D;
  264. //计算补充课时,算作正课课时,但是计算超出课时时不计算这部分
  265. JsonObject courseTimeTypeJson = new JsonObject();
  266. Double courseTimeTypeTime = 0D;
  267. for (String courseTimeType : CourseTimeTypeMap.keySet()) {
  268. List<WfTeacherCourseTime> courseTimes = courseTimeMap.get(courseTimeType);
  269. if(courseTimes == null){
  270. courseTimeTypeJson.addProperty(CourseTimeTypeMap.get(courseTimeType), 0);
  271. continue;
  272. }
  273. double sum = courseTimes.stream()
  274. .filter(x -> x.getTeacherIds().contains(teacher.getId().toString()))
  275. .mapToDouble(WfTeacherCourseTime::getCourseTime).sum();
  276. allClassTime = allClassTime + sum;
  277. courseTimeTypeTime = courseTimeTypeTime + sum;
  278. courseTimeTypeJson.addProperty(CourseTimeTypeMap.get(courseTimeType), sum);
  279. for (LocalDate localDate : allDateList) {
  280. double sum1 = courseTimes.stream()
  281. .filter(x -> x.getTeacherIds().contains(teacher.getId().toString()) && localDate.equals(x.getScheduleDate()))
  282. .mapToDouble(WfTeacherCourseTime::getCourseTime).sum();
  283. JsonObject courseJson = new JsonObject();
  284. courseJson.addProperty("type", CourseTimeTypeMap.get(courseTimeType));
  285. courseJson.addProperty("scheduleDate", localDate.format(formatter));
  286. courseJson.addProperty("content", sum1);
  287. courseJson.addProperty("adjustType", "");
  288. allClassTimeDataArray.add(courseJson);
  289. }
  290. }
  291. record.setCourseTimeTypeData(courseTimeTypeJson.toString());
  292. //早自习、正课、晚辅
  293. Double classTime7 = 0D,classTime8 = 0D,classTime9 = 0D,classTime11 = 0D;
  294. List<String> zkList = Arrays.asList("上1","上2","上3","上4","下1","下2","下3","下4");
  295. List<String> wzxList = Arrays.asList("晚1","晚2","晚3");
  296. //查询出老师的课程
  297. List<CourseListVo> courseList = allCourseList.stream()
  298. .filter(x -> x.getTeacherId().contains(teacher.getId().toString()))
  299. .collect(Collectors.toList());
  300. //循环,统计出各项数据
  301. for (CourseListVo courseListVo : courseList) {
  302. //如果这个课程数据包含在被删除的课时中,跳过不计算
  303. Map<LocalDate, String> deleteDates = deleteMap.get(courseListVo.getClassId());
  304. JsonObject courseJson = new JsonObject();
  305. if(deleteDates != null && deleteDates.containsKey(courseListVo.getScheduleDate()) && deleteDates.get(courseListVo.getScheduleDate()) != null
  306. && deleteDates.get(courseListVo.getScheduleDate()).contains(courseListVo.getTimeNumber()) ){
  307. courseJson.addProperty("type", courseListVo.getShortName());
  308. courseJson.addProperty("scheduleDate", courseListVo.getScheduleDate().format(formatter));
  309. courseJson.addProperty("content", "");
  310. courseJson.addProperty("adjustType", "course_delete");
  311. allClassTimeDataArray.add(courseJson);
  312. continue;
  313. }
  314. if("早自习".equals(courseListVo.getShortName())){
  315. classTime7 += weightSetMap.get(courseListVo.getShortName()) == null ? 0d : weightSetMap.get(courseListVo.getShortName());
  316. }else if(zkList.contains(courseListVo.getShortName())){
  317. classTime8 += weightSetMap.get(courseListVo.getShortName()) == null ? 0d : weightSetMap.get(courseListVo.getShortName());
  318. }else if(wzxList.contains(courseListVo.getShortName())){
  319. classTime9 += weightSetMap.get(courseListVo.getShortName()) == null ? 0d : weightSetMap.get(courseListVo.getShortName());
  320. }
  321. if(courseListVo.getAdjustType() != null && !courseListVo.getAdjustType().isEmpty()){
  322. if(CourseAdjustTypeEnum.courseExchange.getCode().equals(courseListVo.getAdjustType())){
  323. classTime11 += weightSetMap.get(courseListVo.getShortName());
  324. }
  325. }
  326. courseJson.addProperty("type", courseListVo.getShortName());
  327. courseJson.addProperty("scheduleDate", courseListVo.getScheduleDate().format(formatter));
  328. courseJson.addProperty("content", courseListVo.getClassName() + "," + courseListVo.getCourseName());
  329. courseJson.addProperty("adjustType", courseListVo.getAdjustType() == null?"":courseListVo.getAdjustType());
  330. allClassTimeDataArray.add(courseJson);
  331. }
  332. record.setClassTime7(classTime7);
  333. record.setClassTime8(classTime8);
  334. record.setClassTime9(classTime9);
  335. record.setClassTime11(classTime11);
  336. JsonObject weekDataJson = new JsonObject();
  337. Double allTimes = 0d;
  338. Double allCcksTime = 0d;//总的超出课时,从每周的正课课时中计算
  339. BigDecimal ccksTime = BigDecimal.ZERO;//超出课时标准(每周)
  340. if("FB1601".equals(teacher.getEmployType())){
  341. ccksTime = BigDecimal.valueOf(costSetMap.get("cost7"));
  342. }else{//外聘FB1605、合作人员FB1609
  343. ccksTime = BigDecimal.valueOf(costSetMap.get("cost8"));
  344. }
  345. //存每周的数据
  346. for (WeekTimeRangeVo timeRangeVo : weekTimeRangeVos) {
  347. Double zkTimes = 0d;
  348. Double wzxTimes = 0d;
  349. Double tkTimes = 0d;
  350. for (CourseListVo courseListVo : courseList) {
  351. LocalDate scheduleDate = courseListVo.getScheduleDate();
  352. //如果这个课程数据包含在被删除的课时中,跳过不计算
  353. Map<LocalDate, String> deleteDates = deleteMap.get(courseListVo.getClassId());
  354. if(deleteDates != null && deleteDates.containsKey(scheduleDate) && deleteDates.get(courseListVo.getScheduleDate()) != null
  355. && deleteDates.get(courseListVo.getScheduleDate()).contains(courseListVo.getTimeNumber())){
  356. continue;
  357. }
  358. if(!( (scheduleDate.equals(timeRangeVo.getMondayDate()) || scheduleDate.isAfter(timeRangeVo.getMondayDate()))
  359. && (scheduleDate.equals(timeRangeVo.getSundayDate()) || scheduleDate.isBefore(timeRangeVo.getSundayDate())))){
  360. continue;
  361. }
  362. if("早自习".equals(courseListVo.getShortName())){
  363. zkTimes += weightSetMap.get(courseListVo.getShortName()) == null ? 0d : weightSetMap.get(courseListVo.getShortName());
  364. }else if(zkList.contains(courseListVo.getShortName())){
  365. zkTimes += weightSetMap.get(courseListVo.getShortName()) == null ? 0d : weightSetMap.get(courseListVo.getShortName());
  366. }else if(wzxList.contains(courseListVo.getShortName())){
  367. wzxTimes += weightSetMap.get(courseListVo.getShortName()) == null ? 0d : weightSetMap.get(courseListVo.getShortName());
  368. }
  369. if(courseListVo.getAdjustType() != null && !courseListVo.getAdjustType().isEmpty()){
  370. if(CourseAdjustTypeEnum.courseExchange.getCode().equals(courseListVo.getAdjustType())){
  371. tkTimes += weightSetMap.get(courseListVo.getShortName());
  372. }
  373. }
  374. }
  375. //计算该老师发起的事假、病假顶课数据
  376. List<CourseListVo> substituteList = allSubstituteList.stream().filter(x -> x.getExchangeTeacherId().equals(teacher.getId()))
  377. .collect(Collectors.toList());
  378. Double reduceTime = 0d;//顶课课时
  379. for (CourseListVo courseListVo : substituteList) {
  380. Map<LocalDate, String> deleteDates = deleteMap.get(courseListVo.getClassId());
  381. if(deleteDates != null && deleteDates.get(courseListVo.getScheduleDate()) != null && deleteDates.get(courseListVo.getScheduleDate()).contains(courseListVo.getTimeNumber())){
  382. continue;
  383. }
  384. if(!( (courseListVo.getScheduleDate().equals(timeRangeVo.getMondayDate()) || courseListVo.getScheduleDate().isAfter(timeRangeVo.getMondayDate()))
  385. && (courseListVo.getScheduleDate().equals(timeRangeVo.getSundayDate()) || courseListVo.getScheduleDate().isBefore(timeRangeVo.getSundayDate())))){
  386. continue;
  387. }
  388. reduceTime += weightSetMap.get(courseListVo.getShortName());
  389. }
  390. substituteList = allSubstituteList.stream().filter(x -> x.getTeacherId().equals(teacher.getId().toString()))
  391. .collect(Collectors.toList());
  392. for (CourseListVo courseListVo : substituteList) {
  393. Map<LocalDate, String> deleteDates = deleteMap.get(courseListVo.getClassId());
  394. if(deleteDates != null && deleteDates.get(courseListVo.getScheduleDate()) != null
  395. ){
  396. String timeNumbers = deleteDates.get(courseListVo.getScheduleDate());
  397. if(StrUtil.isNotEmpty(timeNumbers) && timeNumbers.contains(courseListVo.getTimeNumber())){
  398. continue;
  399. }
  400. }
  401. if(!( (courseListVo.getScheduleDate().equals(timeRangeVo.getMondayDate()) || courseListVo.getScheduleDate().isAfter(timeRangeVo.getMondayDate()))
  402. && (courseListVo.getScheduleDate().equals(timeRangeVo.getSundayDate()) || courseListVo.getScheduleDate().isBefore(timeRangeVo.getSundayDate())))){
  403. continue;
  404. }
  405. reduceTime = reduceTime - weightSetMap.get(courseListVo.getShortName());
  406. }
  407. timeRangeVo.setZkTimes(zkTimes);
  408. timeRangeVo.setWzxTimes(wzxTimes);
  409. timeRangeVo.setDkTimes(reduceTime);
  410. timeRangeVo.setTkTimes(tkTimes);
  411. allTimes += (zkTimes + wzxTimes);
  412. if(zkTimes > ccksTime.doubleValue()){
  413. allCcksTime += zkTimes - ccksTime.doubleValue();
  414. }
  415. }
  416. weekDataJson.add("weekData", new Gson().toJsonTree(weekTimeRangeVos));
  417. weekDataJson.addProperty("allTimes", allTimes);
  418. record.setWeekData(weekDataJson.toString());
  419. //计算总课时
  420. allClassTime = allClassTime + classTime7 + classTime8 + classTime9;
  421. record.setAllClassTime(allClassTime);
  422. //计算费用,根据聘用类型判断费用问题
  423. Double classTimeAmount = 0d;
  424. Double beyondClassTimeAmount = 0d;
  425. BigDecimal zzxCost = BigDecimal.ZERO;//早自习费用
  426. if("FB1601".equals(teacher.getEmployType())){
  427. zzxCost = BigDecimal.valueOf(costSetMap.get("cost1"));
  428. }else{//外聘FB1605、合作人员FB1609
  429. zzxCost = BigDecimal.valueOf(costSetMap.get("cost2"));
  430. }
  431. classTimeAmount += BigDecimal.valueOf(classTime7).multiply(zzxCost).doubleValue();
  432. BigDecimal zkCost = BigDecimal.ZERO;//正课费用
  433. if("FB1601".equals(teacher.getEmployType())){
  434. zkCost = BigDecimal.valueOf(costSetMap.get("cost3"));
  435. }else{//外聘FB1605、合作人员FB1609
  436. zkCost = BigDecimal.valueOf(costSetMap.get("cost4"));
  437. }
  438. double classTime = classTime8
  439. + courseTimeTypeTime;
  440. classTimeAmount += BigDecimal.valueOf(classTime).multiply(zkCost).doubleValue();
  441. BigDecimal wzxCost = BigDecimal.ZERO;//晚自习费用
  442. if("FB1601".equals(teacher.getEmployType())){
  443. wzxCost = BigDecimal.valueOf(costSetMap.get("cost5"));
  444. }else{//外聘FB1605、合作人员FB1609
  445. wzxCost = BigDecimal.valueOf(costSetMap.get("cost6"));
  446. }
  447. classTimeAmount += BigDecimal.valueOf(classTime9).multiply(wzxCost).doubleValue();
  448. /**
  449. * 顶课:
  450. * 1、流程发起人(被顶老师)的课被顶课老师上了,那么被顶老师的课时就会-1,顶课老师课时就会+1
  451. * 2、顶课老师会根据规则额外加一笔费用,根据设置1课时额外加4元
  452. * 3、顶课类型为事假和病假的时候会扣除被顶老师的费用,根据设置为扣除4元
  453. */
  454. BigDecimal reduceTimeAmount = BigDecimal.ZERO;
  455. {
  456. //计算该老师发起的事假、病假顶课数据
  457. List<CourseListVo> substituteList = allSubstituteList.stream().filter(x -> x.getExchangeTeacherId().equals(teacher.getId()))
  458. .collect(Collectors.toList());
  459. Double reduceTime = 0d;//顶课课时
  460. Double dkClassTime = 0d;//去顶课的数量,所有顶课数量
  461. for (CourseListVo courseListVo : substituteList) {
  462. Map<LocalDate, String> deleteDates = deleteMap.get(courseListVo.getClassId());
  463. if(deleteDates != null && deleteDates.get(courseListVo.getScheduleDate()) != null && deleteDates.get(courseListVo.getScheduleDate()).contains(courseListVo.getTimeNumber())){
  464. continue;
  465. }
  466. reduceTime += weightSetMap.get(courseListVo.getShortName());
  467. dkClassTime += weightSetMap.get(courseListVo.getShortName());
  468. }
  469. Double bdkClassTime = 0d;//发起顶课的数量,只统计事假和病假的数量
  470. substituteList = allSubstituteList.stream().filter(x -> x.getTeacherId().equals(teacher.getId().toString()))
  471. .collect(Collectors.toList());
  472. for (CourseListVo courseListVo : substituteList) {
  473. Map<LocalDate, String> deleteDates = deleteMap.get(courseListVo.getClassId());
  474. if(deleteDates != null && deleteDates.get(courseListVo.getScheduleDate()) != null && deleteDates.get(courseListVo.getScheduleDate()).contains(courseListVo.getTimeNumber())){
  475. continue;
  476. }
  477. reduceTime = reduceTime - weightSetMap.get(courseListVo.getShortName());
  478. if("sick _leave".equals(courseListVo.getReason()) || "leave_absence".equals(courseListVo.getReason())){
  479. bdkClassTime += weightSetMap.get(courseListVo.getShortName());
  480. }
  481. }
  482. record.setClassTime10(reduceTime);
  483. BigDecimal dkCost = BigDecimal.ZERO;//顶课费用,顶课老师加钱
  484. if("FB1601".equals(teacher.getEmployType())){
  485. dkCost = BigDecimal.valueOf(costSetMap.get("cost11"));
  486. }else{//外聘FB1605、合作人员FB1609
  487. dkCost = BigDecimal.valueOf(costSetMap.get("cost12"));
  488. }
  489. reduceTimeAmount = BigDecimal.valueOf(dkClassTime).multiply(dkCost);
  490. BigDecimal bdkCost = BigDecimal.ZERO;//顶课费用,被顶课老师扣钱
  491. if("FB1601".equals(teacher.getEmployType())){
  492. bdkCost = BigDecimal.valueOf(costSetMap.get("cost13")==null?0:costSetMap.get("cost13"));
  493. }else{//外聘FB1605、合作人员FB1609
  494. bdkCost = BigDecimal.valueOf(costSetMap.get("cost14")==null?0:costSetMap.get("cost14"));
  495. }
  496. //计算被扣除的费用
  497. reduceTimeAmount = reduceTimeAmount.subtract(bdkCost.multiply(BigDecimal.valueOf(bdkClassTime)));
  498. }
  499. record.setClassTimeAmount(classTimeAmount);
  500. //计算超出的课时,需要先计算出每个周的开始结束时间
  501. BigDecimal ccksCost = BigDecimal.ZERO;
  502. if("FB1601".equals(teacher.getEmployType())){
  503. ccksCost = BigDecimal.valueOf(costSetMap.get("cost9"));
  504. }else{//外聘FB1605、合作人员FB1609
  505. ccksCost = BigDecimal.valueOf(costSetMap.get("cost10"));
  506. }
  507. //计算出总的超出课时并计算超课时费
  508. beyondClassTimeAmount = BigDecimal.valueOf(allCcksTime).multiply(ccksCost).doubleValue();
  509. if(!"FB1601".equals(teacher.getEmployType())){
  510. record.setBeyondClassTimeAmount(beyondClassTimeAmount);
  511. }
  512. //计算总金额,课时费+超课时费
  513. Double totalAmount = classTimeAmount + beyondClassTimeAmount + reduceTimeAmount.doubleValue();
  514. record.setTotalAmount(totalAmount);
  515. record.setAllClassTimeData(calculateClassTime(allClassTimeDataArray, weightSetMap, allDateList, new ArrayList<>(CourseTimeTypeMap.values())));
  516. record.setCreateDate(new Date());
  517. insertList.add(record);
  518. }
  519. if(!insertList.isEmpty()){
  520. //先删除
  521. String sql = "delete from class_time_statistics_record where class_time_statistics_id = " + statistics.getId();
  522. SqlRunnerAdapter.db().delete(sql);
  523. for (ClassTimeStatisticsRecord record : insertList) {
  524. recordMapper.insert(record);
  525. }
  526. }
  527. statistics.setAllClassTime(insertList.stream().filter(x -> x.getAllClassTime() != null).mapToDouble(ClassTimeStatisticsRecord::getAllClassTime).sum());
  528. statistics.setTotalAmount(insertList.stream().filter(x -> x.getTotalAmount() != null).mapToDouble(ClassTimeStatisticsRecord::getTotalAmount).sum());
  529. statistics.setBeyondClassTimeAmount(insertList.stream().filter(x -> x.getBeyondClassTimeAmount() != null).mapToDouble(ClassTimeStatisticsRecord::getBeyondClassTimeAmount).sum());
  530. statistics.setClassTimeAmount(insertList.stream().filter(x -> x.getClassTimeAmount() != null).mapToDouble(ClassTimeStatisticsRecord::getClassTimeAmount).sum());
  531. }catch (Exception e){
  532. Log.error(e.getMessage(), e);
  533. throw new MyException("统计出错,请联系管理员");
  534. }finally {
  535. statistics.setStatus(1);
  536. this.updateById(statistics);
  537. }
  538. return true;
  539. }
  540. @Override
  541. public Boolean lockData(Long id) {
  542. ClassTimeStatistics statistics = this.getById(id);
  543. return null;
  544. }
  545. @Override
  546. public List<ClassTimeStatisticsRecordVo> getRecordList(Long id) {
  547. return this.baseMapper.getRecordList(id);
  548. }
  549. @Override
  550. public List<CourseClassTimeStatisticsRecordVo> getCourseRecordList(CourseRecordDto dto) {
  551. return this.baseMapper.getCourseRecordList(dto);
  552. }
  553. /**
  554. * 导出课时统计明细
  555. */
  556. @Override
  557. public byte[] recordExport(Long id) {
  558. try {
  559. //1、查询数据
  560. List<ClassTimeStatisticsRecordVo> recordList = this.baseMapper.getRecordList(id);
  561. //计算出一共有多少周
  562. ClassTimeStatistics statistics = this.getById(id);
  563. List<WeekTimeRangeVo> weekTimeRangeVos = calculateNaturalWeeks(statistics.getStartDate(), statistics.getEndDate());
  564. List<DictionaryDetail> CourseTimeTypeList = dictionaryService.list(
  565. new QueryWrapper<DictionaryDetail>().lambda()
  566. .eq(DictionaryDetail::getItemId, 1833772737004875778L)
  567. .eq(DictionaryDetail::getDeleteMark, DeleteMark.NODELETE.getCode())
  568. .orderByAsc(DictionaryDetail::getCode)
  569. );
  570. /**
  571. * 2、将查询出来的数据按照表头转换成二维数组
  572. * 前端固定表头:序号、工号、姓名、教研会、督导听课、临近三年退休政策、出题、阅卷、周末培优、早自习、正课(含调顶课时)、晚辅(含调顶课时)、顶课、调课
  573. * 中间动态表头:每周,总课时(含调顶课节)、顶课(节)、调课(节)、晚辅(节)......课时晚辅合计
  574. * 后端固定表头:总课时、课时费(元)、外聘教师超课时费(元)、总金额(元)
  575. */
  576. //计算一共多少列
  577. int column = 12 + CourseTimeTypeList.size() + (4 * weekTimeRangeVos.size());
  578. List<ArrayList<String>> dataList = new ArrayList<>();
  579. Integer sortCode = 1;
  580. JsonParser parser = new JsonParser();
  581. for (ClassTimeStatisticsRecordVo recordVo : recordList) {
  582. ArrayList<String> rowData = new ArrayList<>();
  583. rowData.add(sortCode.toString());//序号
  584. rowData.add(recordVo.getUserName());//工号
  585. rowData.add(recordVo.getName());//姓名
  586. JsonObject courseTimeTypeJson = parser.parse(recordVo.getCourseTimeTypeData()).getAsJsonObject();
  587. for (DictionaryDetail dictionaryDetail : CourseTimeTypeList) {
  588. if (courseTimeTypeJson.get(dictionaryDetail.getName()) == null) {
  589. continue;
  590. }
  591. Double courseTimeTypeTime = courseTimeTypeJson.get(dictionaryDetail.getName()).getAsDouble();
  592. rowData.add(courseTimeTypeTime.toString());//教研会
  593. }
  594. // rowData.add(recordVo.getClassTime1()==null?"":recordVo.getClassTime1().toString());//教研会
  595. // rowData.add(recordVo.getClassTime2()==null?"":recordVo.getClassTime2().toString());//督导听课
  596. // rowData.add(recordVo.getClassTime3()==null?"":recordVo.getClassTime3().toString());//临近三年退休政策
  597. // rowData.add(recordVo.getClassTime4()==null?"":recordVo.getClassTime4().toString());//出题
  598. // rowData.add(recordVo.getClassTime5()==null?"":recordVo.getClassTime5().toString());//阅卷
  599. // rowData.add(recordVo.getClassTime6()==null?"":recordVo.getClassTime6().toString());//周末培优
  600. rowData.add(recordVo.getClassTime7()==null?"":recordVo.getClassTime7().toString());//早自习
  601. rowData.add(recordVo.getClassTime8()==null?"":recordVo.getClassTime8().toString());//正课(含调顶课时)
  602. rowData.add(recordVo.getClassTime9()==null?"":recordVo.getClassTime9().toString());//晚辅(含调顶课时)
  603. rowData.add(recordVo.getClassTime10()==null?"":recordVo.getClassTime10().toString());//顶课
  604. rowData.add(recordVo.getClassTime11()==null?"":recordVo.getClassTime11().toString());//调课
  605. //每周数据
  606. JsonObject weekDataJson = parser.parse(recordVo.getWeekData()).getAsJsonObject();
  607. JsonArray weekDataArray = weekDataJson.getAsJsonArray("weekData");
  608. for (int i = 0; i < weekDataArray.size(); i ++){
  609. JsonObject jsonElement = weekDataArray.get(i).getAsJsonObject();
  610. rowData.add(jsonElement.get("zkTimes").getAsString());//总课时(含调顶课节)
  611. rowData.add(jsonElement.get("dkTimes").getAsString());//顶课(节)
  612. rowData.add(jsonElement.get("tkTimes").getAsString());//调课(节)
  613. rowData.add(jsonElement.get("wzxTimes").getAsString());//晚辅(节)
  614. }
  615. rowData.add(weekDataJson.get("allTimes").getAsString());//课时晚辅合计
  616. rowData.add(recordVo.getAllClassTime().toString());//总课时
  617. rowData.add(recordVo.getClassTimeAmount().toString());//课时费(元)
  618. rowData.add(recordVo.getBeyondClassTimeAmount()==null?"":recordVo.getBeyondClassTimeAmount().toString());//外聘教师超课时费(元)
  619. rowData.add(recordVo.getTotalAmount().toString());//总金额(元)
  620. dataList.add(rowData);
  621. sortCode ++;
  622. }
  623. // 创建一个新的工作簿
  624. Workbook workbook = new XSSFWorkbook();
  625. // 创建一个工作表(sheet)
  626. String sheetName = "数据";
  627. Sheet sheet = workbook.createSheet(sheetName);
  628. // 创建一个字体对象
  629. Font font = workbook.createFont();
  630. font.setBold(true);// 设置为粗体
  631. font.setFontName("宋体");
  632. //font.setColor(IndexedColors.RED.getIndex()); // 设置字体颜色为红色
  633. font.setFontHeightInPoints((short)24);
  634. // 创建一个单元格样式对象
  635. CellStyle cellStyle = workbook.createCellStyle();
  636. cellStyle.setFont(font); // 将字体应用到样式
  637. cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
  638. cellStyle.setAlignment(HorizontalAlignment.CENTER);
  639. // 第一行表头
  640. {
  641. Row row = sheet.createRow(0);
  642. //合并第一行的列
  643. sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, column - 1));
  644. //3、处理表头
  645. String title1 = statistics.getYear() + "年" + statistics.getMonth() + "月课时统计";
  646. // 创建单元格并设置值
  647. Cell cell = row.createCell(0);
  648. cell.setCellValue(title1);
  649. cell.setCellStyle(cellStyle);
  650. }
  651. //第二行表头
  652. createSecondTitle(workbook, sheet, statistics, weekTimeRangeVos.size() * 4 + 1, CourseTimeTypeList);
  653. //第三行表头
  654. createThirdTitle(workbook, sheet, statistics.getMonth(), weekTimeRangeVos, 7 + CourseTimeTypeList.size());
  655. //第四行表头
  656. createFourthTitle(workbook, sheet, weekTimeRangeVos, 7 + CourseTimeTypeList.size());
  657. int dataRowNumber = 4;
  658. //设置样式
  659. font = workbook.createFont();
  660. font.setBold(false);// 设置为粗体
  661. font.setFontName("宋体");
  662. //font.setColor(IndexedColors.RED.getIndex()); // 设置字体颜色为红色
  663. font.setFontHeightInPoints((short)12);
  664. // 创建一个单元格样式对象
  665. cellStyle = workbook.createCellStyle();
  666. cellStyle.setFont(font); // 将字体应用到样式
  667. cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
  668. cellStyle.setAlignment(HorizontalAlignment.CENTER);
  669. for (ArrayList<String> rowData : dataList) {
  670. Row dataRow = sheet.createRow(dataRowNumber);
  671. for (int i = 0; i < rowData.size(); i ++){
  672. Cell row1cell2 = dataRow.createCell(i);
  673. String value = rowData.get(i);
  674. if("0".equals(value) || "0.0".equals(value)){
  675. value = "";
  676. }
  677. row1cell2.setCellValue(value);
  678. row1cell2.setCellStyle(cellStyle);
  679. }
  680. dataRowNumber ++;
  681. }
  682. //写入文件
  683. ByteArrayOutputStream bot = new ByteArrayOutputStream();
  684. workbook.write(bot);
  685. return bot.toByteArray();
  686. }catch (Exception e){
  687. Log.error(e.getMessage(), e);
  688. throw new MyException("导出出错,请联系管理员");
  689. }
  690. }
  691. @Override
  692. @Transactional(rollbackFor = Exception.class)
  693. public Boolean refreshCourseRecord(ClassTimeStatistics statistics) {
  694. try {
  695. // 1、查询教师
  696. List<TeacherListVo> teacherList = this.baseMapper.getTeacherList();
  697. //查询课程数据,排除开节假日的数据
  698. List<CourseListVo> allCourseList = this.baseMapper.getCourseList(statistics);
  699. allCourseList.addAll(this.baseMapper.getHolidayReplaceCourseList(statistics));
  700. //查询删除课时的数据,将每个班删除的具体日期统计出来
  701. List<ClassTimeDelete> deleteList = deleteService.list(
  702. new QueryWrapper<ClassTimeDelete>().lambda()
  703. .ne(ClassTimeDelete::getStatus, 2)
  704. .eq(ClassTimeDelete::getDeleteMark, DeleteMark.NODELETE.getCode())
  705. .eq(ClassTimeDelete::getEnabledMark, EnabledMark.ENABLED.getCode())
  706. );
  707. Map<Long, List<ClassTimeDelete>> deleteDataMap = deleteList.stream().collect(Collectors.groupingBy(ClassTimeDelete::getClassId));
  708. Map<Long, Map<LocalDate, String>> deleteMap = new HashMap<>();//将每个班级计算出来所有被删除的日期存入map
  709. for (Long classId : deleteDataMap.keySet()) {
  710. Map<LocalDate, String> dateMap = new HashMap<>();
  711. for (ClassTimeDelete classTimeDelete : deleteDataMap.get(classId)) {
  712. LocalDate currentDate = classTimeDelete.getStartDate();
  713. while (!currentDate.isAfter(classTimeDelete.getEndDate())) {
  714. dateMap.put(currentDate, classTimeDelete.getTimePeriod());
  715. currentDate = currentDate.plusDays(1); // 增加一天
  716. }
  717. }
  718. //去重并存到map中
  719. deleteMap.put(classId, dateMap);
  720. }
  721. JsonParser parser = new JsonParser();
  722. //权重设置jsonArray
  723. JsonArray weightSetArray = parser.parse(statistics.getWeightSetJson()).getAsJsonArray();
  724. Map<String, Double> weightLabelMap = new LinkedHashMap<>();
  725. Map<String, Double> weightFieldMap = new LinkedHashMap<>();
  726. for (JsonElement jsonElement : weightSetArray) {
  727. JsonObject object = jsonElement.getAsJsonObject();
  728. weightLabelMap.put(object.get("label").getAsString(), object.get("value").getAsDouble());
  729. weightFieldMap.put(object.get("field").getAsString(), object.get("value").getAsDouble());
  730. }
  731. //查询行政工作量并转成map
  732. List<ClassTimeStatisticsAdministration> administrationList = administrationMapper.selectList(
  733. new QueryWrapper<ClassTimeStatisticsAdministration>().lambda()
  734. .eq(ClassTimeStatisticsAdministration::getClassTimeStatisticsId, statistics.getId())
  735. .eq(ClassTimeStatisticsAdministration::getDeleteMark, DeleteMark.NODELETE.getCode())
  736. .eq(ClassTimeStatisticsAdministration::getEnabledMark, EnabledMark.ENABLED.getCode())
  737. );
  738. Map<Long, Double> administrationMap = administrationList.stream().collect(Collectors.toMap(ClassTimeStatisticsAdministration::getUserId, ClassTimeStatisticsAdministration::getWorkload));
  739. List<ClassTimeStatisticsRecord> insertList = new ArrayList<>();
  740. for (TeacherListVo teacher : teacherList) {
  741. ClassTimeStatisticsRecord record = new ClassTimeStatisticsRecord();
  742. record.setUserId(teacher.getId());
  743. record.setClassTimeStatisticsId(statistics.getId());
  744. record.setEmployType(teacher.getEmployType());
  745. record.setClassTime1(statistics.getWeeks());
  746. //早自习、正课、晚辅、顶课、调课
  747. Double classTime7 = 0D,classTime8 = 0D,classTime9 = 0D;
  748. List<String> zkList = Arrays.asList("上1","上2","上3","上4","下1","下2","下3","下4");
  749. List<String> wzxList = Arrays.asList("晚1","晚2","晚3");
  750. //查询出老师的课程
  751. List<CourseListVo> courseList = allCourseList.stream()
  752. .filter(x -> x.getTeacherId().contains(teacher.getId().toString()))
  753. .collect(Collectors.toList());
  754. //循环,统计出各项数据
  755. for (CourseListVo courseListVo : courseList) {
  756. Map<LocalDate, String> deleteDates = deleteMap.get(courseListVo.getClassId());
  757. if(deleteDates != null && deleteDates.containsKey(courseListVo.getScheduleDate()) && deleteDates.get(courseListVo.getScheduleDate()) != null
  758. && deleteDates.get(courseListVo.getScheduleDate()).contains(courseListVo.getTimeNumber()) ){
  759. continue;
  760. }
  761. if("早自习".equals(courseListVo.getShortName())){
  762. classTime8 += weightLabelMap.get(courseListVo.getShortName()) == null ? 0d : weightLabelMap.get(courseListVo.getShortName());
  763. }else if(zkList.contains(courseListVo.getShortName())){
  764. classTime8 += weightLabelMap.get(courseListVo.getShortName()) == null ? 0d : weightLabelMap.get(courseListVo.getShortName());
  765. }else if(wzxList.contains(courseListVo.getShortName())){
  766. classTime9 += weightLabelMap.get(courseListVo.getShortName()) == null ? 0d : weightLabelMap.get(courseListVo.getShortName());
  767. }
  768. }
  769. record.setClassTime7(classTime7);
  770. record.setClassTime8(classTime8);
  771. record.setClassTime9(classTime9);
  772. //计算总课时:正课课时+晚辅
  773. Double allTimes = classTime8 + classTime9;
  774. record.setAllClassTime(allTimes);
  775. //计算周平均课时:总课时÷总周次
  776. double classTime2 = BigDecimal.valueOf(allTimes).divide(BigDecimal.valueOf(record.getClassTime1()),2, RoundingMode.HALF_UP).doubleValue();
  777. record.setClassTime2(classTime2);
  778. //行政工作量
  779. record.setClassTime3(administrationMap.get(record.getUserId()));
  780. //总平均课时
  781. Double classTime4 = (record.getClassTime3()==null?0D:record.getClassTime3()) + (record.getClassTime2()==null?0D:record.getClassTime2());
  782. record.setClassTime4(classTime4);
  783. Double time13 = weightFieldMap.get("time13");
  784. if(classTime4 > time13){
  785. record.setClassTime5(time13);
  786. }else{
  787. record.setClassTime5(classTime4);
  788. }
  789. insertList.add(record);
  790. }
  791. if(!insertList.isEmpty()){
  792. Double overWorkloadNumber = statistics.getOverWorkloadNumber() == null ? 0D : statistics.getOverWorkloadNumber();
  793. Double time14 = weightFieldMap.get("time14");//工作量得分
  794. Double time13 = weightFieldMap.get("time13");//超工作量得分
  795. if(statistics.getOverWorkloadNumberStatus() != 1){
  796. //取出最大总平均课时
  797. Double maxClassTime4 = insertList.stream().mapToDouble(ClassTimeStatisticsRecord::getClassTime4).max().getAsDouble();
  798. //计算超工作量基数
  799. if(!maxClassTime4.equals(time13)){
  800. overWorkloadNumber = BigDecimal.valueOf(time14).divide((BigDecimal.valueOf(maxClassTime4).subtract(BigDecimal.valueOf(time13))),2, RoundingMode.HALF_UP).doubleValue();
  801. }
  802. }
  803. double allClassTime = insertList.stream().filter(x -> x.getAllClassTime() != null).mapToDouble(ClassTimeStatisticsRecord::getAllClassTime).sum();
  804. statistics.setAllClassTime(allClassTime);
  805. statistics.setOverWorkloadNumber(overWorkloadNumber);
  806. //先删除
  807. String sql = "delete from class_time_statistics_record where class_time_statistics_id = " + statistics.getId();
  808. SqlRunnerAdapter.db().delete(sql);
  809. for (ClassTimeStatisticsRecord record : insertList) {
  810. if(overWorkloadNumber != 0D && time13 < record.getClassTime4()){
  811. double classTime6 = BigDecimal.valueOf(record.getClassTime4())
  812. .subtract(BigDecimal.valueOf(time13))
  813. .multiply(BigDecimal.valueOf(overWorkloadNumber)).doubleValue();
  814. if(classTime6 > 0d){
  815. record.setClassTime6(classTime6);
  816. }
  817. }
  818. recordMapper.insert(record);
  819. }
  820. }
  821. }catch (Exception e){
  822. Log.error(e.getMessage(), e);
  823. throw new MyException("刷新统计出错,请联系管理员");
  824. }finally {
  825. statistics.setStatus(1);
  826. this.updateById(statistics);
  827. }
  828. return true;
  829. }
  830. @Override
  831. public Boolean importAdministration(Long classTimeStatisticsId, MultipartFile file){
  832. try {
  833. List<Map<Integer, Object>> excelDataList = EasyExcel.read(file.getInputStream()).sheet().headRowNumber(2).doReadSync();
  834. List<User> teacherList = userService.list(
  835. new MPJLambdaWrapper<User>()
  836. .select(User::getId)
  837. .select(XjrUser.class, x -> VoToColumnUtil.fieldsToColumns(XjrUser.class).contains(x.getProperty()))
  838. .innerJoin(BaseTeacher.class, BaseTeacher::getUserId, User::getId)
  839. );
  840. List<ClassTimeStatisticsAdministration> insertList = new ArrayList<>();
  841. Map<String, Long> userMap = teacherList.stream().collect(Collectors.toMap(User::getUserName, User::getId));
  842. for (Map<Integer, Object> rowData : excelDataList) {
  843. String userName = rowData.get(1).toString();
  844. ClassTimeStatisticsAdministration data = new ClassTimeStatisticsAdministration(){{
  845. setClassTimeStatisticsId(classTimeStatisticsId);
  846. setUserId(userMap.get(userName));
  847. setWorkload(Double.parseDouble(rowData.get(3).toString()));
  848. }};
  849. insertList.add(data);
  850. }
  851. if(!insertList.isEmpty()){
  852. String sql = "delete from class_time_statistics_administration where class_time_statistics_id = " + classTimeStatisticsId;
  853. SqlRunnerAdapter.db().delete(sql);
  854. for (ClassTimeStatisticsAdministration administration : insertList) {
  855. administrationMapper.insert(administration);
  856. }
  857. CompletableFuture.runAsync(() -> {
  858. ClassTimeStatistics statistics = this.getById(classTimeStatisticsId);
  859. refreshCourseRecord(statistics);
  860. });
  861. }
  862. return true;
  863. }catch (Exception e){
  864. Log.error(e.getMessage(), e);
  865. throw new MyException("导入行政工作量报错,请联系管理员");
  866. }
  867. }
  868. @Override
  869. public byte[] exportAdministration(Long classTimeStatisticsId) {
  870. CourseRecordDto dto = new CourseRecordDto() {{
  871. setId(classTimeStatisticsId);
  872. }};
  873. List<CourseClassTimeStatisticsRecordVo> recordList = this.getCourseRecordList(dto);
  874. List<ClassTimeStatisticsAdministrationExcelVo> list = new ArrayList<>();
  875. int sortCode = 1;
  876. for (CourseClassTimeStatisticsRecordVo el : recordList) {
  877. ClassTimeStatisticsAdministrationExcelVo excelVo = BeanUtil.toBean(el, ClassTimeStatisticsAdministrationExcelVo.class);
  878. excelVo.setSortCode(sortCode);
  879. list.add(excelVo);
  880. sortCode ++;
  881. }
  882. ByteArrayOutputStream bot = new ByteArrayOutputStream();
  883. EasyExcel.write(bot, ClassTimeStatisticsAdministrationExcelVo.class).automaticMergeHead(false).excelType(ExcelTypeEnum.XLSX).sheet().doWrite(list);
  884. return bot.toByteArray();
  885. }
  886. /**
  887. * 生成第二行表头
  888. * 前端固定表头:序号、工号、姓名、教研会、督导听课、临近三年退休政策、出题、阅卷、周末培优、早自习、正课(含调顶课时)、晚辅(含调顶课时)、顶课、调课
  889. * 中间动态表头:每周,总课时(含调顶课节)、顶课(节)、调课(节)、晚辅(节)......课时晚辅合计
  890. * 后端固定表头:总课时、课时费(元)、外聘教师超课时费(元)、总金额(元)
  891. */
  892. void createSecondTitle(Workbook workbook, Sheet sheet, ClassTimeStatistics statistics, int mergeCoulmn, List<DictionaryDetail> CourseTimeTypeList){
  893. Font font = workbook.createFont();
  894. font.setFontName("宋体");
  895. font.setFontHeightInPoints((short)12);
  896. CellStyle cellStyle = workbook.createCellStyle();
  897. cellStyle.setFont(font); // 将字体应用到样式
  898. cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
  899. cellStyle.setAlignment(HorizontalAlignment.CENTER);
  900. Row row1 = sheet.createRow(1);
  901. int cellNumber = 0;
  902. Cell row1cell1 = row1.createCell(cellNumber);
  903. row1cell1.setCellValue("序号");
  904. row1cell1.setCellStyle(cellStyle);
  905. cellNumber ++;
  906. Cell row1cell2 = row1.createCell(cellNumber);
  907. row1cell2.setCellValue("工号");
  908. row1cell2.setCellStyle(cellStyle);
  909. cellNumber ++;
  910. Cell row1cell3 = row1.createCell(cellNumber);
  911. row1cell3.setCellValue("姓名");
  912. row1cell3.setCellStyle(cellStyle);
  913. cellNumber ++;
  914. for (DictionaryDetail detail : CourseTimeTypeList) {
  915. Cell row1cell4 = row1.createCell(cellNumber);
  916. row1cell4.setCellValue(detail.getName());
  917. row1cell4.setCellStyle(cellStyle);
  918. cellNumber ++;
  919. }
  920. Cell row1cell10 = row1.createCell(cellNumber);
  921. row1cell10.setCellValue("早自习");
  922. row1cell10.setCellStyle(cellStyle);
  923. cellNumber ++;
  924. Cell row1cell11 = row1.createCell(cellNumber);
  925. row1cell11.setCellValue("正课(含调顶课时)");
  926. row1cell11.setCellStyle(cellStyle);
  927. cellNumber ++;
  928. Cell row1cell12 = row1.createCell(cellNumber);
  929. row1cell12.setCellValue("晚辅(含调顶课时)");
  930. row1cell12.setCellStyle(cellStyle);
  931. cellNumber ++;
  932. Cell row1cell13 = row1.createCell(cellNumber);
  933. row1cell13.setCellValue("顶课");
  934. row1cell13.setCellStyle(cellStyle);
  935. cellNumber ++;
  936. Cell row1cell14 = row1.createCell(cellNumber);
  937. row1cell14.setCellValue("调课");
  938. row1cell14.setCellStyle(cellStyle);
  939. cellNumber ++;
  940. Cell row1cell15 = row1.createCell(cellNumber);
  941. row1cell15.setCellValue("正课课时数(平台导出" + statistics.getMonth() + "月)");
  942. row1cell15.setCellStyle(cellStyle);
  943. cellNumber ++;
  944. int index = cellNumber + mergeCoulmn - 1;
  945. Cell row1cell16 = row1.createCell(index);
  946. row1cell16.setCellValue("总课时");
  947. row1cell16.setCellStyle(cellStyle);
  948. Cell row1cell17 = row1.createCell(index + 1);
  949. row1cell17.setCellValue("课时费(元)");
  950. row1cell17.setCellStyle(cellStyle);
  951. Cell row1cell18 = row1.createCell(index + 2);
  952. row1cell18.setCellValue("外聘教师超课时费(元)");
  953. row1cell18.setCellStyle(cellStyle);
  954. Cell row1cell19 = row1.createCell(index + 3);
  955. row1cell19.setCellValue("总金额(元)");
  956. row1cell19.setCellStyle(cellStyle);
  957. //合并表头
  958. for(int i = 0; i < 8 + CourseTimeTypeList.size(); i ++){
  959. sheet.addMergedRegion(new CellRangeAddress(1, 3, i, i));
  960. }
  961. //合并中间动态表头
  962. sheet.addMergedRegion(new CellRangeAddress(1, 1, 8 + CourseTimeTypeList.size(), index - 1));
  963. //合并前端表头
  964. for(int i = index; i < index + 4; i ++){
  965. sheet.addMergedRegion(new CellRangeAddress(1, 3, i, i));
  966. }
  967. }
  968. /**
  969. * 生成第三行表头
  970. * 关于周的表头,周一日期到周二日期,第几周,最后拼接一个单独的月份
  971. *
  972. * @param month 月份
  973. * @param weekTimeRangeVos 周的数据,包含周一的日期周二的日期
  974. * @param lastIndex 前一个表头的单元格
  975. */
  976. void createThirdTitle(Workbook workbook, Sheet sheet, int month, List<WeekTimeRangeVo> weekTimeRangeVos, int lastIndex){
  977. Font font = workbook.createFont();
  978. font.setFontName("宋体");
  979. font.setFontHeightInPoints((short)12);
  980. CellStyle cellStyle = workbook.createCellStyle();
  981. cellStyle.setFont(font); // 将字体应用到样式
  982. cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
  983. cellStyle.setAlignment(HorizontalAlignment.CENTER);
  984. //设置内容
  985. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM.dd");
  986. int rowNumber = 2;
  987. Row row2 = sheet.createRow(rowNumber);
  988. for(int i = 0; i < weekTimeRangeVos.size(); i ++){
  989. WeekTimeRangeVo rangeVo = weekTimeRangeVos.get(i);
  990. String cellValue = rangeVo.getMondayDate().format(formatter) + "-"
  991. + rangeVo.getSundayDate().format(formatter) + "(第" + rangeVo.getWeeks() + "周)";
  992. Cell row1cell1 = row2.createCell(lastIndex + 1 + (i * 4));
  993. row1cell1.setCellValue(cellValue);
  994. row1cell1.setCellStyle(cellStyle);
  995. }
  996. //合并单元格
  997. int monthIndex = 0;
  998. for(int i = 0; i < weekTimeRangeVos.size(); i ++){
  999. int startCoulmn = lastIndex + 1 + (i * 4);
  1000. int endCoulmn = startCoulmn + 3;
  1001. sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber, startCoulmn, endCoulmn));
  1002. monthIndex = endCoulmn;
  1003. }
  1004. Cell row1cell2 = row2.createCell(monthIndex + 1);
  1005. row1cell2.setCellValue(month + "月");
  1006. row1cell2.setCellStyle(cellStyle);
  1007. }
  1008. /**
  1009. * 生成第四行表头
  1010. * 表头内容:总课时(含调顶课节)、顶课(节)、调课(节)、晚辅(节)、课时晚辅合计
  1011. *
  1012. * @param weekTimeRangeVos 周的数据,包含周一的日期周二的日期
  1013. * @param lastIndex 前一个表头的单元格
  1014. */
  1015. void createFourthTitle(Workbook workbook, Sheet sheet, List<WeekTimeRangeVo> weekTimeRangeVos, int lastIndex){
  1016. Font font = workbook.createFont();
  1017. font.setFontName("宋体");
  1018. font.setFontHeightInPoints((short)12);
  1019. CellStyle cellStyle = workbook.createCellStyle();
  1020. cellStyle.setFont(font); // 将字体应用到样式
  1021. cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
  1022. cellStyle.setAlignment(HorizontalAlignment.CENTER);
  1023. int rowNumber = 3;
  1024. Row row2 = sheet.createRow(rowNumber);
  1025. int index = lastIndex + 1;
  1026. for(int i = 0; i < weekTimeRangeVos.size(); i ++){
  1027. Cell row1cell1 = row2.createCell(index);
  1028. row1cell1.setCellValue("总课时(含调顶课节)");
  1029. row1cell1.setCellStyle(cellStyle);
  1030. index ++;
  1031. Cell row1cell2 = row2.createCell(index);
  1032. row1cell2.setCellValue("顶课(节)");
  1033. row1cell2.setCellStyle(cellStyle);
  1034. index ++;
  1035. Cell row1cell3 = row2.createCell(index);
  1036. row1cell3.setCellValue("调课(节)");
  1037. row1cell3.setCellStyle(cellStyle);
  1038. index ++;
  1039. Cell row1cell4 = row2.createCell(index);
  1040. row1cell4.setCellValue("晚辅(节)");
  1041. row1cell4.setCellStyle(cellStyle);
  1042. index ++;
  1043. }
  1044. Cell row1cell5 = row2.createCell(index);
  1045. row1cell5.setCellValue("课时晚辅合计");
  1046. row1cell5.setCellStyle(cellStyle);
  1047. }
  1048. //计算日期内一共几个周,并返回每周的周一日期和周日日期
  1049. private static List<WeekTimeRangeVo> calculateNaturalWeeks(LocalDate startDate, LocalDate endDate) {
  1050. List<WeekTimeRangeVo> result = new ArrayList<>();
  1051. // 获取第一个周一
  1052. LocalDate currentMonday = startDate.with(DayOfWeek.MONDAY);
  1053. // 如果 startDate 不是周一,则用下一个周一
  1054. if (!startDate.isEqual(currentMonday) && startDate.isAfter(currentMonday)) {
  1055. currentMonday = currentMonday.plusWeeks(1);
  1056. }
  1057. int weeks = 1;
  1058. // 遍历每个周
  1059. while (!currentMonday.isAfter(endDate)) {
  1060. // 计算当前周的周日
  1061. LocalDate currentSunday = currentMonday.plusDays(6);
  1062. // 限制周日的范围
  1063. if (currentSunday.isAfter(endDate)) {
  1064. currentSunday = endDate;
  1065. }
  1066. // 添加结果
  1067. WeekTimeRangeVo timeRangeVo = new WeekTimeRangeVo();
  1068. timeRangeVo.setMondayDate(currentMonday);
  1069. timeRangeVo.setSundayDate(currentSunday);
  1070. timeRangeVo .setWeeks(weeks);
  1071. result.add(timeRangeVo);
  1072. // 移动到下一个周的周一
  1073. currentMonday = currentMonday.plusWeeks(1);
  1074. weeks ++;
  1075. }
  1076. return result;
  1077. }
  1078. /**
  1079. * 取出所有日期
  1080. * @param startDate 开始时间
  1081. * @param endDate 结束时间
  1082. * @return 返回所有日期的集合
  1083. */
  1084. private static List<LocalDate> getDatesBetween(LocalDate startDate, LocalDate endDate) {
  1085. List<LocalDate> dates = new ArrayList<>();
  1086. long numOfDaysBetween = ChronoUnit.DAYS.between(startDate, endDate) + 1; // +1 包含结束日期
  1087. for (long i = 0; i < numOfDaysBetween; i++) {
  1088. dates.add(startDate.plusDays(i));
  1089. }
  1090. return dates;
  1091. }
  1092. String calculateClassTime(JsonArray allClassTimeDataArray, Map<String, Double> weightSetMap, List<LocalDate> allDateList, List<String> CourseTimeTypeSet){
  1093. JsonObject result = new JsonObject();
  1094. //计算纵向的合计
  1095. Double allClassTime = 0d;
  1096. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM.dd");
  1097. JsonArray rowTitle = new JsonArray();
  1098. for (LocalDate localDate : allDateList) {
  1099. JsonObject rowTitleJson = new JsonObject();
  1100. rowTitleJson.addProperty("scheduleDate", localDate.format(formatter));
  1101. Double sum = 0d;
  1102. for (JsonElement jsonElement : allClassTimeDataArray) {
  1103. JsonObject object = jsonElement.getAsJsonObject();
  1104. LocalDate scheduleDate = LocalDate.parse(object.get("scheduleDate").getAsString());
  1105. if(!scheduleDate.equals(localDate)){
  1106. continue;
  1107. }
  1108. String adjustType = object.get("adjustType").getAsString();
  1109. if("course_delete".equals(adjustType)){
  1110. continue;
  1111. }
  1112. if(weightSetMap.get(object.get("type").getAsString()) == null){
  1113. sum += object.get("content").getAsDouble();
  1114. allClassTime += object.get("content").getAsDouble();
  1115. }else{
  1116. sum += weightSetMap.get(object.get("type").getAsString());
  1117. allClassTime += weightSetMap.get(object.get("type").getAsString());
  1118. }
  1119. }
  1120. rowTitleJson.addProperty("content", sum);
  1121. rowTitleJson.addProperty("type", "");
  1122. rowTitleJson.addProperty("adjustType", "");
  1123. rowTitle.add(rowTitleJson);
  1124. }
  1125. result.add("rowTitle", rowTitle);
  1126. //计算横向的合计
  1127. JsonArray columnTitle = new JsonArray();
  1128. for (String type : weightSetMap.keySet()) {
  1129. Double sum = 0d;
  1130. JsonObject columnTitleJson = new JsonObject();
  1131. for (JsonElement jsonElement : allClassTimeDataArray) {
  1132. JsonObject object = jsonElement.getAsJsonObject();
  1133. String typeStr = object.get("type").getAsString();
  1134. if(!type.equals(typeStr)){
  1135. continue;
  1136. }
  1137. String adjustType = object.get("adjustType").getAsString();
  1138. if("course_delete".equals(adjustType)){
  1139. continue;
  1140. }
  1141. sum += weightSetMap.get(typeStr);
  1142. }
  1143. columnTitleJson.addProperty("type", type);
  1144. columnTitleJson.addProperty("content", sum);
  1145. columnTitleJson.addProperty("scheduleDate", "");
  1146. columnTitleJson.addProperty("adjustType", "");
  1147. columnTitle.add(columnTitleJson);
  1148. }
  1149. for (String courseTimeType : CourseTimeTypeSet) {
  1150. double sum = 0d;
  1151. JsonObject columnTitleJson = new JsonObject();
  1152. for (JsonElement jsonElement : allClassTimeDataArray) {
  1153. JsonObject object = jsonElement.getAsJsonObject();
  1154. String typeStr = object.get("type").getAsString();
  1155. if (!courseTimeType.equals(typeStr)) {
  1156. continue;
  1157. }
  1158. String adjustType = object.get("adjustType").getAsString();
  1159. if("course_delete".equals(adjustType)){
  1160. continue;
  1161. }
  1162. sum += object.get("content").getAsDouble();
  1163. }
  1164. columnTitleJson.addProperty("type", courseTimeType);
  1165. columnTitleJson.addProperty("content", sum);
  1166. columnTitleJson.addProperty("scheduleDate", "");
  1167. columnTitleJson.addProperty("adjustType", "");
  1168. columnTitle.add(columnTitleJson);
  1169. }
  1170. result.add("columnTitle", columnTitle);
  1171. result.add("data", allClassTimeDataArray);
  1172. result.addProperty("allClassTime", allClassTime);
  1173. return result.toString();
  1174. }
  1175. }