Index: ssts-basedata/src/main/java/com/forgon/disinfectsystem/entity/basedatamanager/holiday/HolidayConfig.java =================================================================== diff -u -r41391 -r41399 --- ssts-basedata/src/main/java/com/forgon/disinfectsystem/entity/basedatamanager/holiday/HolidayConfig.java (.../HolidayConfig.java) (revision 41391) +++ ssts-basedata/src/main/java/com/forgon/disinfectsystem/entity/basedatamanager/holiday/HolidayConfig.java (.../HolidayConfig.java) (revision 41399) @@ -19,6 +19,8 @@ @Table(name = "HolidayConfig", indexes = @Index(name = "HolidayConfig_HolidayDate_IDX", columnList = "holidayDate", unique = true)) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) public class HolidayConfig { + public static final int TYPE_HOLIDAY = 1; + public static final int TYPE_WORK_DAY = 2; /** * 节假日配置表id */ Index: ssts-basedata/src/main/java/com/forgon/disinfectsystem/basedatamanager/holiday/param/JsonImportDateParam.java =================================================================== diff -u --- ssts-basedata/src/main/java/com/forgon/disinfectsystem/basedatamanager/holiday/param/JsonImportDateParam.java (revision 0) +++ ssts-basedata/src/main/java/com/forgon/disinfectsystem/basedatamanager/holiday/param/JsonImportDateParam.java (revision 41399) @@ -0,0 +1,81 @@ +package com.forgon.disinfectsystem.basedatamanager.holiday.param; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.sql.Date; +import java.util.Objects; + +public class JsonImportDateParam { + /** + * 假期名称,比如元旦、春节 + */ + private String name; + /** + * 假期日期,格式为yyyy-MM-dd + */ + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private Date date; + /** + * 是否是假期.true为假期,false为调休补班 + */ + private Boolean isOffDay; + + public JsonImportDateParam() { + } + + public JsonImportDateParam(String name, Date date, Boolean isOffDay) { + this.name = name; + this.date = date; + this.isOffDay = isOffDay; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public Boolean getIsOffDay() { + return isOffDay; + } + + public void setIsOffDay(Boolean offDay) { + isOffDay = offDay; + } + + public boolean offDay() { + return isOffDay != null && isOffDay; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (!(object instanceof JsonImportDateParam)) return false; + JsonImportDateParam that = (JsonImportDateParam) object; + return Objects.equals(name, that.name) && Objects.equals(date, that.date) && Objects.equals(isOffDay, that.isOffDay); + } + + @Override + public int hashCode() { + return Objects.hash(name, date, isOffDay); + } + + @Override + public String toString() { + return "JsonImportDateParam{" + + "name='" + name + '\'' + + ", date='" + date + '\'' + + ", isOffDay=" + isOffDay + + '}'; + } +} Index: ssts-basedata/src/test/java/com/forgon/disinfectsystem/basedatamanager/holiday/controller/HolidayConfigControllerTest.java =================================================================== diff -u -r41391 -r41399 --- ssts-basedata/src/test/java/com/forgon/disinfectsystem/basedatamanager/holiday/controller/HolidayConfigControllerTest.java (.../HolidayConfigControllerTest.java) (revision 41391) +++ ssts-basedata/src/test/java/com/forgon/disinfectsystem/basedatamanager/holiday/controller/HolidayConfigControllerTest.java (.../HolidayConfigControllerTest.java) (revision 41399) @@ -6,12 +6,14 @@ import com.forgon.disinfectsystem.basedatamanager.holiday.service.HolidayConfigManager; import com.forgon.disinfectsystem.common.params.PagerParam; import com.forgon.disinfectsystem.entity.basedatamanager.holiday.HolidayConfig; +import com.forgon.exception.service.ExceptionHandler; import com.forgon.response.OperatorResponse; import com.forgon.response.PagedDataResponse; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.invocation.Invocation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; @@ -21,12 +23,17 @@ import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.sql.Date; import java.util.Collections; +import java.util.List; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.mockito.internal.invocation.InvocationsFinder.findInvocations; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -39,6 +46,8 @@ private HolidayConfigController controller; @Autowired private HolidayConfigManager holidayConfigManager; + @Autowired + private ExceptionHandler exceptionHandler; private final ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build(); private MockMvc mockMvc; @@ -85,7 +94,37 @@ .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); PagedDataResponse or = objectMapper.readValue(res, new TypeReference>() {}); Assert.assertTrue(or.success()); - Assert.assertEquals(vo, or); + assertEquals(vo, or); verify(holidayConfigManager).list(new PagerParam(10, 20), 2025); } + + @Test + public void testImportJson() throws Exception { + String res = mockMvc.perform(fileUpload("/disinfectSystem/baseData/holidayconfig/json") + .file("file", "holiday.json".getBytes()) + .contentType(MediaType.MULTIPART_FORM_DATA)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + OperatorResponse or = objectMapper.readValue(res, OperatorResponse.class); + Assert.assertTrue(or.success()); + verify(holidayConfigManager, data -> { + List actions = findInvocations(data.getAllInvocations(), data.getTarget()); + assertEquals(1, actions.size()); + MultipartFile file = actions.get(0).getArgument(0); + try { + Assert.assertArrayEquals("holiday.json".getBytes(), file.getBytes()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).importJson(any(MultipartFile.class)); + + doThrow(new RuntimeException("导入失败")).when(holidayConfigManager).importJson(any(MultipartFile.class)); + when(exceptionHandler.handleException(any(RuntimeException.class))).thenReturn("导入失败"); + res = mockMvc.perform(fileUpload("/disinfectSystem/baseData/holidayconfig/json") + .file("file", "holiday.json".getBytes()) + .contentType(MediaType.MULTIPART_FORM_DATA)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + or = objectMapper.readValue(res, OperatorResponse.class); + Assert.assertFalse(or.success()); + assertEquals("导入失败", or.getMessage()); + } } Index: ssts-web/src/main/webapp/disinfectsystem/basedatamanager/holiday/holidayListView.js =================================================================== diff -u -r41391 -r41399 --- ssts-web/src/main/webapp/disinfectsystem/basedatamanager/holiday/holidayListView.js (.../holidayListView.js) (revision 41391) +++ ssts-web/src/main/webapp/disinfectsystem/basedatamanager/holiday/holidayListView.js (.../holidayListView.js) (revision 41399) @@ -83,56 +83,143 @@ } } + var importWindow = Ext4.create('Ext4.window.Window', { + title: '导入节假日信息', + items: [{ + xtype: 'form', + bodyPadding: 15, + border: false, + // 关键配置:必须设置这些属性 + fileUpload: true, + standardSubmit: false, + defaults: { + anchor: '100%', + labelWidth: 80, + allowBlank: false + }, + items: [{ + xtype: 'filefield', + name: 'file', + fieldLabel: '导入文件', + buttonText: '选择文件', + listeners: { + change: function(field, newValue) { + } + } + }] + }], + buttons: [{ + text: '导入', + handler: function() { + var form = importWindow.down('form').getForm(); + var fileField = form.findField('file'); + if(!fileField.getValue()){ + showResult('请选择文件'); + return false; + } + // 使用 ExtJS 的表单提交方式 + form.submit({ + url: WWWROOT + '/disinfectSystem/baseData/holidayconfig/json.mhtml', + waitMsg: '正在上传文件...', + success: function(form, action) { + if (action.result && action.result.success) { + showResult('导入成功'); + gridStore.load(); // 重新加载数据 + importWindow.close(); + } else { + showResult(action.result ? action.result.message : '导入失败'); + } + }, + failure: function(form, action) { + var result = action.result; + var errorMsg = '导入失败'; + + if (result) { + if (result.message) { + errorMsg = result.message; + } else if (result.responseText) { + errorMsg = result.responseText; + } + } + showResult(errorMsg); + } + }); + } + }, { + text: '取消', + iconCls: 'icon-cancel', + handler: function() { + importWindow.close(); + } + }] + }); + appGird = Ext4.create('ExtJs.forgon4.Grid',{ title: '节假日设置', enableSearching: false, dataUrl : WWWROOT + '/disinfectSystem/baseData/holidayconfig.mhtml', + fields : recordFields, + columns : recordColumns, + extraParams : {}, + showRightClick : false, dockedItems: [{ xtype: 'toolbar', dock: 'top', items: [{ xtype: 'button', text: '添加', hidden: SSTS_HolidayConfig_Create, - iconCls: 'icon-add', + iconCls: 'btn_ext_add', handler: function() { showHolidayForm(); } }, { xtype: 'button', text: '修改', hidden: SSTS_HolidayConfig_Update, - iconCls: 'icon-edit', + iconCls: 'btn_ext_application_edit', handler: function() { var selection = appGird.getSelectionModel().getSelection(); if (selection.length === 1) { showHolidayForm(selection[0]); + } else if(selection.length > 1) { + showResult('只能选择一条记录进行修改') } else { - showResult('请选择一条记录进行修改') + showResult('请选择要修改的记录!'); } } }, { xtype: 'button', text: '删除', hidden: SSTS_HolidayConfig_Delete, - iconCls: 'icon-remove', + iconCls: 'btn_ext_application_del', handler: function() { var selection = appGird.getSelectionModel().getSelection(); - if (selection.length === 1) { - Ext4.Msg.confirm('确认', '确定要删除选中的记录吗?', function(btn) { + if (selection.length >= 1) { + var len = selection.length; + Ext4.Msg.confirm('确认', '确定要删除选中的'+len+'条记录吗?', function(btn) { if (btn === 'yes') { - deleteHoliday(selection[0]); + for (var i = 0; i < len; i++) { + deleteHoliday(selection[i]); + } } }); } else { - showResult('请选择一条记录进行删除'); + showResult('请至少选择一条记录进行删除'); } } + }, { + xtype: 'button', + text: '导入', + hidden: SSTS_HolidayConfig_Create, + iconCls: 'btn_ext_upload', + handler: function() { + var form = importWindow.down('form'); + form.getForm().reset(); + importWindow.show(); + } }] }], - extraParams : {}, //如果你要自定义参数,这里一定要先给一个空的对象,要不然会报空异常 - fields : recordFields, - columns : recordColumns, listeners: { itemdblclick: function(grid, record) { showHolidayForm(record); Index: ssts-basedata/src/test/java/com/forgon/disinfectsystem/basedatamanager/holiday/service/HolidayConfigManagerImplImportJsonTest.java =================================================================== diff -u --- ssts-basedata/src/test/java/com/forgon/disinfectsystem/basedatamanager/holiday/service/HolidayConfigManagerImplImportJsonTest.java (revision 0) +++ ssts-basedata/src/test/java/com/forgon/disinfectsystem/basedatamanager/holiday/service/HolidayConfigManagerImplImportJsonTest.java (revision 41399) @@ -0,0 +1,167 @@ +package com.forgon.disinfectsystem.basedatamanager.holiday.service; + +import com.forgon.disinfectsystem.basedatamanager.holiday.param.JsonImportDateParam; +import com.forgon.disinfectsystem.basedatamanager.holiday.param.JsonImportParam; +import com.forgon.disinfectsystem.entity.basedatamanager.holiday.HolidayConfig; +import com.forgon.tools.hibernate.ObjectDao; +import com.forgon.tools.json.JacksonUtil; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.springframework.mock.web.MockMultipartFile; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.sql.Date; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +/** + * HolidayConfigManagerImpl#importJson 单元测试类 + */ +public class HolidayConfigManagerImplImportJsonTest { + + @InjectMocks + @Spy + private HolidayConfigManagerImpl holidayConfigManager; + + @Mock + private ObjectDao objectDao; + + private AutoCloseable autoCloseable; + + @Before + public void setUp() { + autoCloseable = MockitoAnnotations.openMocks(this); + } + + @After + public void tearDown() throws Exception { + autoCloseable.close(); + } + + /** + * TC01: 正常情况 - JSON 包含两个新日期,应成功保存两条记录 + */ + @Test + public void testImportJson_NormalInput_SavesNewRecords() throws IOException { + // 准备模拟数据 + List days = new ArrayList<>(); + days.add(new JsonImportDateParam("元旦", Date.valueOf("2025-01-01"), true)); + days.add(new JsonImportDateParam("春节", Date.valueOf("2025-02-10"), false)); + + JsonImportParam param = new JsonImportParam(2025, days); + String jsonContent = JacksonUtil.trans2JsonStr(param); + + MockMultipartFile file = new MockMultipartFile( + "file", + "holidays.json", + "application/json", + jsonContent.getBytes(StandardCharsets.UTF_8) + ); + + // 模拟数据库中无对应日期 + when(holidayConfigManager.getFirst(anyString(), any())).thenReturn(null); + + // 执行方法 + holidayConfigManager.importJson(file); + + // 验证调用了两次 save + verify(holidayConfigManager, times(2)).save(any(HolidayConfig.class)); + verify(holidayConfigManager).save(new HolidayConfig(Date.valueOf("2025-01-01"), HolidayConfig.TYPE_HOLIDAY, "元旦假期")); + verify(holidayConfigManager).save(new HolidayConfig(Date.valueOf("2025-02-10"), HolidayConfig.TYPE_WORK_DAY, "春节调休补班")); + } + + /** + * TC02: 异常情况 - JSON 格式错误导致解析失败,抛出 RuntimeException + */ + @Test + public void testImportJson_InvalidJson_ThrowsRuntimeException() throws IOException { + MockMultipartFile invalidFile = new MockMultipartFile( + "file", + "invalid.json", + "application/json", + "{ invalid json }".getBytes(StandardCharsets.UTF_8) + ); + + assertThrows(RuntimeException.class, () -> { + holidayConfigManager.importJson(invalidFile); + }); + } + + /** + * TC03: 边界情况 - JSON 中 days 为空数组,不应有任何数据库操作 + */ + @Test + public void testImportJson_EmptyDays_NoOperations() throws IOException { + JsonImportParam param = new JsonImportParam(2025, new ArrayList<>()); + String jsonContent = JacksonUtil.trans2JsonStr(param); + + MockMultipartFile file = new MockMultipartFile( + "file", + "empty_days.json", + "application/json", + jsonContent.getBytes(StandardCharsets.UTF_8) + ); + + holidayConfigManager.importJson(file); + + // 验证没有调用 save 或 getFirst + verify(holidayConfigManager, never()).getFirst(anyString(), any()); + verify(holidayConfigManager, never()).save(any(HolidayConfig.class)); + } + + /** + * TC04: 特殊情况 - JSON 中有重复日期(DB 已存在),只保存新日期 + */ + @Test + public void testImportJson_DuplicateDates_OnlySaveNewOnes() throws IOException { + // 构造 JSON 数据 + List days = new ArrayList<>(); + days.add(new JsonImportDateParam("劳动节", Date.valueOf("2025-05-01"), true)); // 新增 + days.add(new JsonImportDateParam("端午节", Date.valueOf("2025-06-01"), true)); // 已存在 + + JsonImportParam param = new JsonImportParam(2025, days); + String jsonContent = JacksonUtil.trans2JsonStr(param); + + MockMultipartFile file = new MockMultipartFile( + "file", + "mixed_dates.json", + "application/json", + jsonContent.getBytes(StandardCharsets.UTF_8) + ); + + // 第一次返回 null(表示不存在) + // 第二次返回实体(表示已存在) + when(holidayConfigManager.getFirst("holidayDate", Date.valueOf("2025-05-01"))) + .thenReturn(null); + when(holidayConfigManager.getFirst("holidayDate", Date.valueOf("2025-06-01"))) + .thenReturn(new HolidayConfig()); + + holidayConfigManager.importJson(file); + + // 应该只调用一次 save(因为有一个已经存在) + verify(holidayConfigManager).save(any(HolidayConfig.class)); + } + + @Test + public void testImportJson_InvalidFile_ThrowsRuntimeException() throws IOException { + MockMultipartFile invalidFile = mock(MockMultipartFile.class); + when(invalidFile.getBytes()).thenThrow(IOException.class); + + RuntimeException e = assertThrows(RuntimeException.class, () -> { + holidayConfigManager.importJson(invalidFile); + }); + assertTrue(e.getCause() instanceof IOException); + } +} Index: ssts-basedata/src/main/java/com/forgon/disinfectsystem/basedatamanager/holiday/service/HolidayConfigManagerImpl.java =================================================================== diff -u -r41391 -r41399 --- ssts-basedata/src/main/java/com/forgon/disinfectsystem/basedatamanager/holiday/service/HolidayConfigManagerImpl.java (.../HolidayConfigManagerImpl.java) (revision 41391) +++ ssts-basedata/src/main/java/com/forgon/disinfectsystem/basedatamanager/holiday/service/HolidayConfigManagerImpl.java (.../HolidayConfigManagerImpl.java) (revision 41399) @@ -2,10 +2,13 @@ import com.forgon.disinfectsystem.basedatamanager.holiday.model.HolidayType; import com.forgon.disinfectsystem.basedatamanager.holiday.param.HolidayConfigParam; +import com.forgon.disinfectsystem.basedatamanager.holiday.param.JsonImportDateParam; +import com.forgon.disinfectsystem.basedatamanager.holiday.param.JsonImportParam; import com.forgon.disinfectsystem.common.params.PagerParam; import com.forgon.disinfectsystem.entity.basedatamanager.holiday.HolidayConfig; import com.forgon.response.PagedDataResponse; import com.forgon.tools.hibernate.BasePoManagerImpl; +import com.forgon.tools.json.JacksonUtil; import com.forgon.tools.util.ForgonDateUtils; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -18,6 +21,8 @@ import java.util.Map; import java.util.stream.Collectors; +import static com.forgon.disinfectsystem.entity.basedatamanager.holiday.HolidayConfig.TYPE_HOLIDAY; +import static com.forgon.disinfectsystem.entity.basedatamanager.holiday.HolidayConfig.TYPE_WORK_DAY; import static com.forgon.tools.util.ForgonDateUtils.DATE_FORMAT_YYYYMMDD; @Service @@ -103,7 +108,19 @@ public void importJson(MultipartFile file) { try { String json = new String(file.getBytes(), StandardCharsets.UTF_8); -// List list = HolidayConfig.fromJson(json); + JsonImportParam param = JacksonUtil.parseObject(json, JsonImportParam.class); + if(!CollectionUtils.isEmpty(param.getDays())){ + for (JsonImportDateParam dateParam : param.getDays()) { + Integer type = dateParam.offDay() ? TYPE_HOLIDAY : TYPE_WORK_DAY; + String remark = dateParam.offDay() ? dateParam.getName() + "假期": dateParam.getName() + "调休补班"; + HolidayConfig entity = getFirst("holidayDate", dateParam.getDate()); + if(entity == null) { + // 只保存不存在的日期 + entity = new HolidayConfig(dateParam.getDate(), type, remark); + save(entity); + } + } + } } catch (IOException e) { throw new RuntimeException(e); } Index: ssts-basedata/src/main/java/com/forgon/disinfectsystem/basedatamanager/holiday/param/JsonImportParam.java =================================================================== diff -u --- ssts-basedata/src/main/java/com/forgon/disinfectsystem/basedatamanager/holiday/param/JsonImportParam.java (revision 0) +++ ssts-basedata/src/main/java/com/forgon/disinfectsystem/basedatamanager/holiday/param/JsonImportParam.java (revision 41399) @@ -0,0 +1,60 @@ +package com.forgon.disinfectsystem.basedatamanager.holiday.param; + +import java.util.List; +import java.util.Objects; + +public class JsonImportParam { + /** + * 年份 + */ + private Integer year; + /** + * 假期配置列表 + */ + private List days; + + public JsonImportParam() { + } + + public JsonImportParam(Integer year, List days) { + this.year = year; + this.days = days; + } + + public List getDays() { + return days; + } + + public void setDays(List days) { + this.days = days; + } + + public Integer getYear() { + return year; + } + + public void setYear(Integer year) { + this.year = year; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (!(object instanceof JsonImportParam)) return false; + JsonImportParam that = (JsonImportParam) object; + return Objects.equals(year, that.year) && Objects.equals(days, that.days); + } + + @Override + public int hashCode() { + return Objects.hash(year, days); + } + + @Override + public String toString() { + return "JsonImportParam{" + + "year=" + year + + ", days=" + days + + '}'; + } +} Index: ssts-basedata/src/main/java/com/forgon/disinfectsystem/basedatamanager/holiday/controller/HolidayConfigController.java =================================================================== diff -u -r41391 -r41399 --- ssts-basedata/src/main/java/com/forgon/disinfectsystem/basedatamanager/holiday/controller/HolidayConfigController.java (.../HolidayConfigController.java) (revision 41391) +++ ssts-basedata/src/main/java/com/forgon/disinfectsystem/basedatamanager/holiday/controller/HolidayConfigController.java (.../HolidayConfigController.java) (revision 41399) @@ -9,6 +9,7 @@ import com.forgon.response.PagedDataResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("/disinfectSystem/baseData/holidayconfig") @@ -63,13 +64,13 @@ } } -// @RequestMapping(value = "json", method = RequestMethod.POST, produces = "application/json; charset=UTF-8") -// public OperatorResponse importJson(MultipartFile file) { -// try { -// holidayConfigManager.importJson(file); -// return new OperatorResponse(); -// }catch (Exception e){ -// return new OperatorResponse(false, exceptionHandler.handleException(e)); -// } -// } + @RequestMapping(value = "json", method = RequestMethod.POST, produces = "application/json; charset=UTF-8") + public OperatorResponse importJson(MultipartFile file) { + try { + holidayConfigManager.importJson(file); + return new OperatorResponse(); + }catch (Exception e){ + return new OperatorResponse(false, exceptionHandler.handleException(e)); + } + } }