Index: ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplSaveAndSplitRecyclingRecordTest.java =================================================================== diff -u --- ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplSaveAndSplitRecyclingRecordTest.java (revision 0) +++ ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplSaveAndSplitRecyclingRecordTest.java (revision 41453) @@ -0,0 +1,299 @@ +package com.forgon.disinfectsystem.recyclingrecord.service; + +import com.forgon.Constants; +import com.forgon.disinfectsystem.entity.recyclingapplication.RecyclingApplication; +import com.forgon.disinfectsystem.entity.recyclingrecord.RecyclingRecord; +import com.forgon.disinfectsystem.entity.tousseitem.TousseItem; +import com.forgon.disinfectsystem.recyclingapplication.service.InvoicePlanManager; +import com.forgon.disinfectsystem.recyclingapplication.service.RecyclingApplicationManager; +import com.forgon.disinfectsystem.recyclingrecord.vo.RecyclingContext; +import com.forgon.exception.SystemException; +import com.forgon.tools.hibernate.ObjectDao; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; +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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; + +public class RecyclingRecordManagerImplSaveAndSplitRecyclingRecordTest { + + @InjectMocks + @Spy + private RecyclingRecordManagerImpl recyclingRecordManager; + + @Mock + private RecyclingApplicationManager recyclingApplicationManager; + + @Mock + private InvoicePlanManager invoicePlanManager; + + @Mock + private ObjectDao objectDao; + + private AutoCloseable closeable; + + @Before + public void setUp() throws Exception { + closeable = MockitoAnnotations.openMocks(this); + } + + @After + public void tearDown() throws Exception { + closeable.close(); + } + + /** + * TC1: recyclingRecord 为 null,存在拆分物品 + * 验证是否正确获取申请单并执行拆分逻辑 + */ + @Test + public void testSaveAndSplitRecyclingRecord_RecyclingRecordNull_WithSplitItems() { + // 准备数据 + RecyclingContext recyclingContext = new RecyclingContext(); + recyclingContext.setRecyclingApplicationId("1"); + JSONObject jsonParamObject = new JSONObject(); + JSONArray tousseJson = new JSONArray(); + JSONObject item1 = createTousseItem(5); + tousseJson.add(item1); + jsonParamObject.put("tousseJson", tousseJson); + JSONArray splitTousseJson = new JSONArray(); + JSONObject splitItem1 = createSplitItem(5); // 全部拆分 + splitTousseJson.add(splitItem1); + jsonParamObject.put("splitTousseJson", splitTousseJson); + JSONArray recyclingItemArray = new JSONArray(); + JSONObject recyclingItem = createRecyclingItem(5); + recyclingItemArray.add(recyclingItem); + jsonParamObject.put("recyclingItemArray", recyclingItemArray); + recyclingContext.setJsonParamObject(jsonParamObject); + recyclingContext.setJsonParam(jsonParamObject.toString()); + + RecyclingApplication invoicePlan = mock(RecyclingApplication.class); + when(recyclingApplicationManager.get("1")).thenReturn(invoicePlan); + + List applicationItems = new ArrayList<>(); + TousseItem item = new TousseItem(); + item.setId(1L); + item.setTousseDefinitionId(100L); + item.setAmount(5); + item.setRecyclingAmount(0); + item.setUrgent(Constants.STR_NO); + applicationItems.add(item); + when(invoicePlan.getApplicationItems()).thenReturn(applicationItems); + + doNothing().when(objectDao).saveOrUpdate(any()); + doNothing().when(invoicePlanManager).terminateTousseItemByIds(anyList(), anyString()); + doNothing().when(recyclingRecordManager).save(any(RecyclingContext.class)); + + // 执行测试 + recyclingRecordManager.saveAndSplitRecyclingRecord(recyclingContext, null); + + // 验证结果 + verify(recyclingApplicationManager).get("1"); + verify(invoicePlanManager).terminateTousseItemByIds(Collections.singletonList(1L), "申请单上的物品被拆分"); + verify(recyclingRecordManager, times(2)).save(any(RecyclingContext.class)); + assertEquals(5, item.getAmount().intValue()); // 全部拆分,原申请项已经终止,这种情况下在实现上并没有修改申请数量 + } + + /** + * TC2: recyclingRecord 不为 null,存在拆分物品 + * 验证是否正确从 recyclingRecord 获取申请单并执行拆分逻辑 + */ + @Test + public void testSaveAndSplitRecyclingRecord_RecyclingRecordNotNull_WithSplitItems() { + // 准备数据 + RecyclingContext recyclingContext = new RecyclingContext(); + recyclingContext.setRecyclingApplicationId("1"); + JSONObject jsonParamObject = new JSONObject(); + JSONArray tousseJson = new JSONArray(); + JSONObject item1 = createTousseItem(5); + tousseJson.add(item1); + jsonParamObject.put("tousseJson", tousseJson); + JSONArray splitTousseJson = new JSONArray(); + JSONObject splitItem1 = createSplitItem(3); + splitTousseJson.add(splitItem1); + jsonParamObject.put("splitTousseJson", splitTousseJson); + JSONArray recyclingItemArray = new JSONArray(); + JSONObject recyclingItem = createRecyclingItem(5); + recyclingItemArray.add(recyclingItem); + jsonParamObject.put("recyclingItemArray", recyclingItemArray); + recyclingContext.setJsonParamObject(jsonParamObject); + recyclingContext.setJsonParam(jsonParamObject.toString()); + + RecyclingRecord recyclingRecord = mock(RecyclingRecord.class); + RecyclingApplication invoicePlan = mock(RecyclingApplication.class); + when(recyclingRecord.getRecyclingApplication()).thenReturn(invoicePlan); + + List applicationItems = new ArrayList<>(); + TousseItem item = new TousseItem(); + item.setId(1L); + item.setTousseDefinitionId(100L); + item.setAmount(5); + item.setRecyclingAmount(0); + item.setUrgent(Constants.STR_NO); + applicationItems.add(item); + when(invoicePlan.getApplicationItems()).thenReturn(applicationItems); + + doNothing().when(objectDao).saveOrUpdate(any()); + doNothing().when(invoicePlanManager).terminateTousseItemByIds(anyList(), anyString()); + doNothing().when(recyclingRecordManager).save(any(RecyclingRecord.class), any(RecyclingContext.class)); + + // 执行测试 + recyclingRecordManager.saveAndSplitRecyclingRecord(recyclingContext, recyclingRecord); + + // 验证结果 + verify(recyclingRecord).getRecyclingApplication(); + verify(invoicePlanManager, never()).terminateTousseItemByIds(anyList(), anyString()); + // 拆分回收和 + verify(recyclingRecordManager, times(2)).save(any(RecyclingRecord.class), any(RecyclingContext.class)); + verify(recyclingRecordManager).save(any(RecyclingContext.class)); + assertEquals(2, item.getAmount().intValue()); // 原申请数量减少 + } + + /** + * TC3: 不存在拆分物品 + * 验证是否直接保存回收记录 + */ + @Test + public void testSaveAndSplitRecyclingRecord_NoSplitItems() { + // 准备数据 + RecyclingContext recyclingContext = new RecyclingContext(); + recyclingContext.setRecyclingApplicationId("1"); + JSONObject jsonParamObject = new JSONObject(); + JSONArray tousseJson = new JSONArray(); + JSONObject item1 = createTousseItem(5); + tousseJson.add(item1); + jsonParamObject.put("tousseJson", tousseJson); + jsonParamObject.put("splitTousseJson", new JSONArray()); + recyclingContext.setJsonParamObject(jsonParamObject); + recyclingContext.setJsonParam(jsonParamObject.toString()); + + doNothing().when(recyclingRecordManager).save(any(RecyclingContext.class)); + + // 执行测试 + recyclingRecordManager.saveAndSplitRecyclingRecord(recyclingContext, null); + + // 验证结果 + verify(recyclingRecordManager).save(any(RecyclingContext.class)); + } + + /** + * TC4: 拆分数量大于申请数量 + * 验证是否抛出异常 + */ + @Test(expected = SystemException.class) + public void testSaveAndSplitRecyclingRecord_SplitAmountGreaterThanApplicationAmount() { + // 准备数据 + RecyclingContext recyclingContext = new RecyclingContext(); + recyclingContext.setRecyclingApplicationId("1"); + JSONObject jsonParamObject = new JSONObject(); + JSONArray tousseJson = new JSONArray(); + JSONObject item1 = createTousseItem(5); + tousseJson.add(item1); + jsonParamObject.put("tousseJson", tousseJson); + JSONArray splitTousseJson = new JSONArray(); + JSONObject splitItem1 = createSplitItem(6); + splitTousseJson.add(splitItem1); + jsonParamObject.put("splitTousseJson", splitTousseJson); + JSONArray recyclingItemArray = new JSONArray(); + JSONObject recyclingItem = createRecyclingItem(5); + recyclingItemArray.add(recyclingItem); + jsonParamObject.put("recyclingItemArray", recyclingItemArray); + recyclingContext.setJsonParamObject(jsonParamObject); + recyclingContext.setJsonParam(jsonParamObject.toString()); + + RecyclingApplication invoicePlan = mock(RecyclingApplication.class); + when(recyclingApplicationManager.get("1")).thenReturn(invoicePlan); + + List applicationItems = new ArrayList<>(); + TousseItem item = new TousseItem(); + item.setId(1L); + item.setTousseDefinitionId(100L); + item.setAmount(5); + item.setRecyclingAmount(0); + item.setUrgent(Constants.STR_NO); + applicationItems.add(item); + when(invoicePlan.getApplicationItems()).thenReturn(applicationItems); + + // 执行测试 + recyclingRecordManager.saveAndSplitRecyclingRecord(recyclingContext, null); + } + + /** + * TC5: 拆分数量大于待回收数量 + * 验证是否抛出异常 + */ + @Test(expected = SystemException.class) + public void testSaveAndSplitRecyclingRecord_SplitAmountGreaterThanWaitRecyclingAmount() { + // 准备数据 + RecyclingContext recyclingContext = new RecyclingContext(); + recyclingContext.setRecyclingApplicationId("1"); + JSONObject jsonParamObject = new JSONObject(); + JSONArray tousseJson = new JSONArray(); + JSONObject item1 = createTousseItem(5); + tousseJson.add(item1); + jsonParamObject.put("tousseJson", tousseJson); + JSONArray splitTousseJson = new JSONArray(); + JSONObject splitItem1 = createSplitItem(4); + splitTousseJson.add(splitItem1); + jsonParamObject.put("splitTousseJson", splitTousseJson); + JSONArray recyclingItemArray = new JSONArray(); + JSONObject recyclingItem = createRecyclingItem(5); + recyclingItemArray.add(recyclingItem); + jsonParamObject.put("recyclingItemArray", recyclingItemArray); + recyclingContext.setJsonParamObject(jsonParamObject); + recyclingContext.setJsonParam(jsonParamObject.toString()); + + RecyclingApplication invoicePlan = mock(RecyclingApplication.class); + when(recyclingApplicationManager.get("1")).thenReturn(invoicePlan); + + List applicationItems = new ArrayList<>(); + TousseItem item = new TousseItem(); + item.setId(1L); + item.setTousseDefinitionId(100L); + item.setAmount(5); + item.setRecyclingAmount(2); // 已回收2个 + item.setUrgent(Constants.STR_NO); + applicationItems.add(item); + when(invoicePlan.getApplicationItems()).thenReturn(applicationItems); + + // 执行测试 + recyclingRecordManager.saveAndSplitRecyclingRecord(recyclingContext, null); + } + + private static JSONObject createRecyclingItem(int value) { + JSONObject recyclingItem = new JSONObject(); + recyclingItem.put("tousseDefinitionID", "100"); + recyclingItem.put("tousseName", "Test Tousse"); + recyclingItem.put("amount", value); + return recyclingItem; + } + + private static JSONObject createTousseItem(int value) { + JSONObject tousseItem = new JSONObject(); + tousseItem.put("tousseDefinitionID", "100"); + tousseItem.put("tousseName", "Test Tousse"); + tousseItem.put("amount", value); + return tousseItem; + } + + private static JSONObject createSplitItem(int value) { + JSONObject splitItem1 = new JSONObject(); + splitItem1.put("tousseDefinitionID", "100"); + splitItem1.put("tousseName", "Test Tousse"); + splitItem1.put("amount", value); + splitItem1.put("depart", "Test Dept"); + splitItem1.put("departCoding", "001"); + return splitItem1; + } +} Index: ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManager.java =================================================================== diff -u -r41401 -r41453 --- ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManager.java (.../RecyclingRecordManager.java) (revision 41401) +++ ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManager.java (.../RecyclingRecordManager.java) (revision 41453) @@ -36,7 +36,6 @@ * 保存回收记录 * @param record 要保存的回收记录 * @param recyclingContext 页面的参数 - * @param autoReturnTheBorrowingTousse 是否自动归还。如果为true,那就要处理自动归还 */ public void save(RecyclingRecord record,RecyclingContext recyclingContext); /** @@ -275,10 +274,10 @@ /** * 根据申请单ID获取申请单物品,并按照科室分组(只加载使用记录转换的申请单中的物品)(PYQZYY-173 多手术室器械包流转功能改造) - * @param recyclingRecordId + * @param invoicePlanId 申请单id * @return */ - public Map loadApplicationTousseSplitByOrgUnit(Long recyclingRecordId); + public Map loadApplicationTousseSplitByOrgUnit(Long invoicePlanId); /** * 保存并拆分回收记录(PYQZYY-173 多手术室器械包流转功能改造) Index: ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/vo/SplitDepartTousseVo.java =================================================================== diff -u -r38182 -r41453 --- ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/vo/SplitDepartTousseVo.java (.../SplitDepartTousseVo.java) (revision 38182) +++ ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/vo/SplitDepartTousseVo.java (.../SplitDepartTousseVo.java) (revision 41453) @@ -18,7 +18,7 @@ private Integer amount; - private List splitTousseBarcodes = new ArrayList(); + private List splitTousseBarcodes = new ArrayList<>(); public Long getTousseDefinitionID() { return tousseDefinitionID; Index: ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImpl.java =================================================================== diff -u -r41401 -r41453 --- ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImpl.java (.../RecyclingRecordManagerImpl.java) (revision 41401) +++ ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImpl.java (.../RecyclingRecordManagerImpl.java) (revision 41453) @@ -10110,7 +10110,7 @@ // 当配置项值为1时,根据申请单中物品的发货科室与使用记录录入科室进行拆分 // 当配置项值为2时,根据申请单中物品的包定义的【资产归属】进行拆分 // 配置项未配置时不启用本拆单功能 - Map splitDepartTousseVoMap = new HashMap(); + Map splitDepartTousseVoMap = new HashMap<>(); Integer methodOfSplitRecyclingApplication = ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0); if(methodOfSplitRecyclingApplication.intValue() == 0){ @@ -10121,8 +10121,11 @@ } // 获取申请单 RecyclingApplication app = (RecyclingApplication) objectDao.getById(RecyclingApplication.class.getSimpleName(), invoicePlanId); + if(app == null){ + return splitDepartTousseVoMap; + } UseRecord useRecord = app.getUseRecord(); - if(app == null || StringUtils.isBlank(app.getTousseBarcodes()) || useRecord == null){ + if(StringUtils.isBlank(app.getTousseBarcodes()) || useRecord == null){ return splitDepartTousseVoMap; } @@ -10166,123 +10169,140 @@ // 当配置项值为1时,根据申请单中物品的发货科室与使用记录录入科室进行拆分 if(methodOfSplitRecyclingApplication.intValue() == 1){ - //查询器械包实例的发货单map - Map invoiceMap = loadTousseInstanceInvoiceMap(waiteRecyclingTousseInstanceList); - - for (TousseInstance ti : waiteRecyclingTousseInstanceList) { - TousseDefinition td = ti.getTousseDefinition(); + splitByDepartmentOfInvoiceAndUseRecord(waiteRecyclingTousseInstanceList, tdIds, waiteRecyclingAmountMap, app, splitDepartTousseVoMap); + } else if (methodOfSplitRecyclingApplication.intValue() == 2){ + // 当配置项值为2时,根据申请单中物品的包定义的【资产归属】进行拆分 + // 遍历申请单申请单所有物品,并根据【资产归属】进行分组 + splitByAssetOfTousseDefinition(applicationItems, waiteRecyclingTousseInstanceList, tdIds, app, waiteRecyclingAmountMap, splitDepartTousseVoMap); + } + return splitDepartTousseVoMap; + } - if(!tdIds.contains(td.getId())){ - continue; + /** + * 根据申请单中物品的包定义的【资产归属】进行拆分 + * @param applicationItems + * @param waiteRecyclingTousseInstanceList + * @param tdIds + * @param app + * @param waiteRecyclingAmountMap + * @param splitDepartTousseVoMap + */ + private void splitByAssetOfTousseDefinition(List applicationItems, List waiteRecyclingTousseInstanceList, List tdIds, RecyclingApplication app, Map waiteRecyclingAmountMap, Map splitDepartTousseVoMap) { + if(CollectionUtils.isNotEmpty(applicationItems)){ + Map> tousseInstanceMap = new HashMap<>(); + for (TousseInstance tousseInstance : waiteRecyclingTousseInstanceList) { + Long tousseDefinitionId = tousseInstance.getTousseDefinition().getId(); + List list = tousseInstanceMap.get(tousseDefinitionId); + if(list == null){ + list = new ArrayList<>(); } - - //部分回收时,返回的可拆分数量需要减去已经回收数量 - Integer waiteRecyclingAmount = waiteRecyclingAmountMap.get(td.getId()); - if(waiteRecyclingAmount == null || waiteRecyclingAmount.intValue() <= 0){ + list.add(tousseInstance); + tousseInstanceMap.put(tousseDefinitionId, list); + } + List tdList = objectDao.findByIds(TousseDefinition.class.getSimpleName(), tdIds); + Map idToTdMap = new HashMap<>(); + for (TousseDefinition td : tdList) { + idToTdMap.put(td.getId(), td); + } + for (TousseItem item : applicationItems) { + TousseDefinition td = idToTdMap.get(item.getTousseDefinitionId()); + if(td == null){//可能重复拆单 被拆出去的不在本次回收物品里了 continue; } + String assetsBelong = td.getAssetsBelong(objectDao); + if(StringUtils.isNotBlank(td.getAssetsBelongCode()) && !td.getAssetsBelongCode().equals(app.getDepartCoding())){ + Integer waiteRecyclingAmount = waiteRecyclingAmountMap.get(td.getId()); + if(waiteRecyclingAmount <= 0){ + continue; + } + String key = td.getId() + "_" + td.getAssetsBelongCode(); + SplitDepartTousseVo splitDepartTousseVo = new SplitDepartTousseVo(); + splitDepartTousseVo.setTousseDefinitionID(td.getId()); + splitDepartTousseVo.setTousseName(td.getName()); + splitDepartTousseVo.setAmount(waiteRecyclingAmount); + splitDepartTousseVo.setDepart(assetsBelong); + splitDepartTousseVo.setDepartCoding(td.getAssetsBelongCode()); - Long lastInvoiceId = ti.getLastInvoiceId(); - if(!DatabaseUtil.isPoIdValid(lastInvoiceId)){ - continue; - } - - Invoice invoice = invoiceMap.get(lastInvoiceId); - if(invoice == null){ - continue; - } - - //申请科室编码(发货目的地科室编码) - String departCoding = invoice.getDepartCoding(); - //申请科室 - String depart = invoice.getDepart(); - - if(StringUtils.isNotBlank(departCoding) && !app.getDepartCoding().equals(departCoding)){ - String key = td.getId() + "_" + departCoding; - SplitDepartTousseVo splitDepartTousseVo = splitDepartTousseVoMap.get(key); - if(splitDepartTousseVo == null){ - splitDepartTousseVo = new SplitDepartTousseVo(); - splitDepartTousseVo.setTousseDefinitionID(td.getId()); - splitDepartTousseVo.setTousseName(td.getName()); - splitDepartTousseVo.setDepart(depart); - splitDepartTousseVo.setDepartCoding(departCoding); - splitDepartTousseVo.setAmount(1); - }else{ - Integer amount = splitDepartTousseVo.getAmount(); - if(amount == null){ - amount = 0; + //关联的包实例条码 + List list = tousseInstanceMap.get(td.getId()); + if(CollectionUtils.isEmpty(list)){ + continue; + } + for (TousseInstance ti : list) { + if(waiteRecyclingAmount <= 0){ + break; } - splitDepartTousseVo.setAmount(amount + 1); + //关联的包实例条码及标识牌条码 + setSplitTousseBarcodes(ti, splitDepartTousseVo); + waiteRecyclingAmount--; + waiteRecyclingAmountMap.put(td.getId(), waiteRecyclingAmount); } - //关联的包实例条码及标识牌条码 - setSplitTousseBarcodes(ti, splitDepartTousseVo); splitDepartTousseVoMap.put(key, splitDepartTousseVo); - - //部分回收时,返回的可拆分数量需要减去已经回收数量 - waiteRecyclingAmount--; - waiteRecyclingAmountMap.put(td.getId(), waiteRecyclingAmount); } } - } else if (methodOfSplitRecyclingApplication.intValue() == 2){ - // 当配置项值为2时,根据申请单中物品的包定义的【资产归属】进行拆分 - // 遍历申请单申请单所有物品,并根据【资产归属】进行分组 - if(CollectionUtils.isNotEmpty(applicationItems)){ - Map> tousseInstanceMap = new HashMap>(); - for (TousseInstance tousseInstance : waiteRecyclingTousseInstanceList) { - Long tousseDefinitionId = tousseInstance.getTousseDefinition().getId(); - List list = tousseInstanceMap.get(tousseDefinitionId); - if(list == null){ - list = new ArrayList(); + } + } + + private void splitByDepartmentOfInvoiceAndUseRecord(List waiteRecyclingTousseInstanceList, List tdIds, Map waiteRecyclingAmountMap, RecyclingApplication app, Map splitDepartTousseVoMap) { + //查询器械包实例的发货单map + Map invoiceMap = loadTousseInstanceInvoiceMap(waiteRecyclingTousseInstanceList); + + for (TousseInstance ti : waiteRecyclingTousseInstanceList) { + TousseDefinition td = ti.getTousseDefinition(); + + if(!tdIds.contains(td.getId())){ + continue; + } + + //部分回收时,返回的可拆分数量需要减去已经回收数量 + Integer waiteRecyclingAmount = waiteRecyclingAmountMap.get(td.getId()); + if(waiteRecyclingAmount == null || waiteRecyclingAmount.intValue() <= 0){ + continue; + } + + Long lastInvoiceId = ti.getLastInvoiceId(); + if(!DatabaseUtil.isPoIdValid(lastInvoiceId)){ + continue; + } + + Invoice invoice = invoiceMap.get(lastInvoiceId); + if(invoice == null){ + continue; + } + + //申请科室编码(发货目的地科室编码) + String departCoding = invoice.getDepartCoding(); + //申请科室 + String depart = invoice.getDepart(); + + if(StringUtils.isNotBlank(departCoding) && !app.getDepartCoding().equals(departCoding)){ + String key = td.getId() + "_" + departCoding; + SplitDepartTousseVo splitDepartTousseVo = splitDepartTousseVoMap.get(key); + if(splitDepartTousseVo == null){ + splitDepartTousseVo = new SplitDepartTousseVo(); + splitDepartTousseVo.setTousseDefinitionID(td.getId()); + splitDepartTousseVo.setTousseName(td.getName()); + splitDepartTousseVo.setDepart(depart); + splitDepartTousseVo.setDepartCoding(departCoding); + splitDepartTousseVo.setAmount(1); + }else{ + Integer amount = splitDepartTousseVo.getAmount(); + if(amount == null){ + amount = 0; } - list.add(tousseInstance); - tousseInstanceMap.put(tousseDefinitionId, list); + splitDepartTousseVo.setAmount(amount + 1); } - List tdList = objectDao.findByIds(TousseDefinition.class.getSimpleName(), tdIds); - Map idToTdMap = new HashMap(); - for (TousseDefinition td : tdList) { - idToTdMap.put(td.getId(), td); - } - for (TousseItem item : applicationItems) { - TousseDefinition td = idToTdMap.get(item.getTousseDefinitionId()); - if(td == null){//可能重复拆单 被拆出去的不在本次回收物品里了 - continue; - } - String assetsBelong = td.getAssetsBelong(objectDao); - if(StringUtils.isNotBlank(td.getAssetsBelongCode()) && !td.getAssetsBelongCode().equals(app.getDepartCoding())){ - Integer waiteRecyclingAmount = waiteRecyclingAmountMap.get(td.getId()); - if(waiteRecyclingAmount <= 0){ - continue; - } - String key = td.getId() + "_" + td.getAssetsBelongCode(); - SplitDepartTousseVo splitDepartTousseVo = new SplitDepartTousseVo(); - splitDepartTousseVo.setTousseDefinitionID(td.getId()); - splitDepartTousseVo.setTousseName(td.getName()); - splitDepartTousseVo.setAmount(waiteRecyclingAmount); - splitDepartTousseVo.setDepart(assetsBelong); - splitDepartTousseVo.setDepartCoding(td.getAssetsBelongCode()); - - //关联的包实例条码 - List list = tousseInstanceMap.get(td.getId()); - if(CollectionUtils.isEmpty(list)){ - continue; - } - for (TousseInstance ti : list) { - if(waiteRecyclingAmount <= 0){ - break; - } - //关联的包实例条码及标识牌条码 - setSplitTousseBarcodes(ti, splitDepartTousseVo); - waiteRecyclingAmount--; - waiteRecyclingAmountMap.put(td.getId(), waiteRecyclingAmount); - } - splitDepartTousseVoMap.put(key, splitDepartTousseVo); - } - } + //关联的包实例条码及标识牌条码 + setSplitTousseBarcodes(ti, splitDepartTousseVo); + splitDepartTousseVoMap.put(key, splitDepartTousseVo); + + //部分回收时,返回的可拆分数量需要减去已经回收数量 + waiteRecyclingAmount--; + waiteRecyclingAmountMap.put(td.getId(), waiteRecyclingAmount); } } - return splitDepartTousseVoMap; } - + /** * 设置包实例条码及标识牌条码 * @param ti @@ -10294,7 +10314,7 @@ vo.setIdCardInstanceBarcode(StringTools.defaultString(ti.getIdCardInstanceBarcode())); List splitTousseBarcodes = splitDepartTousseVo.getSplitTousseBarcodes(); if(splitTousseBarcodes == null){ - splitTousseBarcodes = new ArrayList(); + splitTousseBarcodes = new ArrayList<>(); } splitTousseBarcodes.add(vo); } @@ -10305,9 +10325,9 @@ * @return */ @SuppressWarnings("unchecked") - private List loadInvoicePlanUseRecordWaiteRecyclingTousseInstance(RecyclingApplication app) { + List loadInvoicePlanUseRecordWaiteRecyclingTousseInstance(RecyclingApplication app) { - List tousseInstanceList = new ArrayList(); + List tousseInstanceList = new ArrayList<>(); // 使用记录转申请单时,记住的器械包条码 String tousseBarcodes = app.getTousseBarcodes(); if(StringUtils.isBlank(tousseBarcodes)){ @@ -10344,12 +10364,12 @@ /** * 查询返回申请单上已经被拆分出去的包实例ID - * @param app - * @return + * @param app 申请单 + * @return 已经回收的器械包实例ID */ - private List loadRecyclingTousseInstanceIdList(RecyclingApplication app) { - List splitTousseInstanceIdList = new ArrayList(); - StringBuffer splitTousseSql = new StringBuffer(); + List loadRecyclingTousseInstanceIdList(RecyclingApplication app) { + List splitTousseInstanceIdList = new ArrayList<>(); + StringBuilder splitTousseSql = new StringBuilder(); splitTousseSql.append("select rt.tousseInstanceId from "); splitTousseSql.append(RecyclingTousseInstance.class.getSimpleName()); splitTousseSql.append(" rt where rt.invoicePlanId = "); @@ -10376,7 +10396,7 @@ * @return */ @SuppressWarnings("unchecked") - private Map loadTousseInstanceInvoiceMap(List tiList) { + Map loadTousseInstanceInvoiceMap(List tiList) { Map invoiceMap = new HashMap(); if(CollectionUtils.isEmpty(tiList)){ return invoiceMap; @@ -10408,15 +10428,16 @@ public void saveAndSplitRecyclingRecord(RecyclingContext recyclingContext, RecyclingRecord recyclingRecord) { //被拆分回收的申请单 - RecyclingApplication invoicePlan = recyclingApplicationManager.get(recyclingContext.getRecyclingApplicationId()); + RecyclingApplication invoicePlan = null; if(recyclingRecord == null){ invoicePlan = recyclingApplicationManager.get(recyclingContext.getRecyclingApplicationId()); }else{ InvoicePlan ip = recyclingRecord.getRecyclingApplication(); if(ip instanceof RecyclingApplication){ invoicePlan = (RecyclingApplication)ip; + }else{ + invoicePlan = recyclingApplicationManager.get(recyclingContext.getRecyclingApplicationId()); } - } //参数拆分 splitRecyclingContext(recyclingContext, invoicePlan); Index: ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadInvoicePlanUseRecordWaiteRecyclingTousseInstanceTest.java =================================================================== diff -u --- ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadInvoicePlanUseRecordWaiteRecyclingTousseInstanceTest.java (revision 0) +++ ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadInvoicePlanUseRecordWaiteRecyclingTousseInstanceTest.java (revision 41453) @@ -0,0 +1,230 @@ +package com.forgon.disinfectsystem.recyclingrecord.service; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.forgon.disinfectsystem.entity.basedatamanager.toussedefinition.TousseInstance; +import com.forgon.disinfectsystem.entity.recyclingapplication.RecyclingApplication; +import com.forgon.disinfectsystem.entity.useRecord.UseRecord; +import com.forgon.tools.hibernate.ObjectDao; +import org.apache.commons.collections.CollectionUtils; +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; + +/** + * RecyclingRecordManagerImpl unit test + * 回收记录管理器实现类单元测试 + */ +public class RecyclingRecordManagerImplLoadInvoicePlanUseRecordWaiteRecyclingTousseInstanceTest { + + @InjectMocks + @Spy + private RecyclingRecordManagerImpl recyclingRecordManager; + + @Mock + private ObjectDao objectDao; + + @Mock + private RecyclingApplication mockApp; + + @Mock + private UseRecord mockUseRecord; + + private AutoCloseable closeable; + + @Before + public void setUp() { + closeable = MockitoAnnotations.openMocks(this); + } + + @After + public void tearDown() throws Exception { + closeable.close(); + } + + /** + * Test case TC001: app为null + * 测试场景:传入null参数 + * 预期结果:抛出NullPointerException + */ + @Test(expected = NullPointerException.class) + public void testLoadInvoicePlanUseRecordWaiteRecyclingTousseInstance_AppIsNull() { + // Act + recyclingRecordManager.loadInvoicePlanUseRecordWaiteRecyclingTousseInstance(null); + } + + /** + * Test case TC002: tousseBarcodes为空(null或空白字符串) + * 测试场景:tousseBarcodes为空字符串 + * 预期结果:返回空列表 + */ + @Test + public void testLoadInvoicePlanUseRecordWaiteRecyclingTousseInstance_TousseBarcodesIsEmpty() { + // Arrange + when(mockApp.getTousseBarcodes()).thenReturn(""); + when(mockApp.getUseRecord()).thenReturn(mockUseRecord); + + // Act + List result = recyclingRecordManager.loadInvoicePlanUseRecordWaiteRecyclingTousseInstance(mockApp); + + // Assert + assertTrue("Result should be empty list when tousseBarcodes is empty", CollectionUtils.isEmpty(result)); + } + + /** + * Test case TC002: tousseBarcodes为null + * 测试场景:tousseBarcodes为null + * 预期结果:返回空列表 + */ + @Test + public void testLoadInvoicePlanUseRecordWaiteRecyclingTousseInstance_TousseBarcodesIsNull() { + // Arrange + when(mockApp.getTousseBarcodes()).thenReturn(null); + when(mockApp.getUseRecord()).thenReturn(mockUseRecord); + + // Act + List result = recyclingRecordManager.loadInvoicePlanUseRecordWaiteRecyclingTousseInstance(mockApp); + + // Assert + assertTrue("Result should be empty list when tousseBarcodes is null", CollectionUtils.isEmpty(result)); + } + + /** + * Test case TC003: useRecord为null + * 测试场景:useRecord为null + * 预期结果:返回空列表 + */ + @Test + public void testLoadInvoicePlanUseRecordWaiteRecyclingTousseInstance_UseRecordIsNull() { + // Arrange + when(mockApp.getTousseBarcodes()).thenReturn("[barcode1;barcode2]"); + when(mockApp.getUseRecord()).thenReturn(null); + + // Act + List result = recyclingRecordManager.loadInvoicePlanUseRecordWaiteRecyclingTousseInstance(mockApp); + + // Assert + assertTrue("Result should be empty list when useRecord is null", CollectionUtils.isEmpty(result)); + } + + /** + * Test case TC004: 数据库查询结果为空 + * 测试场景:数据库查询不到任何数据 + * 预期结果:返回空列表 + */ + @Test + public void testLoadInvoicePlanUseRecordWaiteRecyclingTousseInstance_DbQueryReturnsEmpty() { + // Arrange + when(mockApp.getTousseBarcodes()).thenReturn("[barcode1;barcode2]"); + when(mockApp.getUseRecord()).thenReturn(mockUseRecord); + when(objectDao.findBySql(anyString(), anyString())).thenReturn(new ArrayList<>()); + + // Act + List result = recyclingRecordManager.loadInvoicePlanUseRecordWaiteRecyclingTousseInstance(mockApp); + + // Assert + assertTrue("Result should be empty list when db query returns empty", CollectionUtils.isEmpty(result)); + verify(objectDao).findBySql(eq("TousseInstance"), anyString()); + } + + /** + * Test case TC005: 拆分实例列表为空 + * 测试场景:没有拆分的实例 + * 预期结果:返回所有查询到的实例 + */ + @Test + public void testLoadInvoicePlanUseRecordWaiteRecyclingTousseInstance_SplitListIsEmpty() { + // Arrange + TousseInstance instance1 = new TousseInstance(); + instance1.setId(1L); + TousseInstance instance2 = new TousseInstance(); + instance2.setId(2L); + List mockInstances = Arrays.asList(instance1, instance2); + + when(mockApp.getTousseBarcodes()).thenReturn(";barcode1;barcode2;"); + when(mockApp.getUseRecord()).thenReturn(mockUseRecord); + when(objectDao.findBySql(anyString(), anyString())).thenReturn(mockInstances); + + // Mock the method to return empty split list + doReturn(new ArrayList()).when(recyclingRecordManager).loadRecyclingTousseInstanceIdList(mockApp); + + // Act + List result = recyclingRecordManager.loadInvoicePlanUseRecordWaiteRecyclingTousseInstance(mockApp); + + // Assert + assertEquals("Should return all instances when no split instances", 2, result.size()); + assertTrue("Should contain instance1", result.contains(instance1)); + assertTrue("Should contain instance2", result.contains(instance2)); + } + + /** + * Test case TC006: 正常情况-有过滤操作 + * 测试场景:需要过滤掉已拆分的实例 + * 预期结果:返回过滤后的实例列表 + */ + @Test + public void testLoadInvoicePlanUseRecordWaiteRecyclingTousseInstance_NormalCaseWithFiltering() { + // Arrange + TousseInstance instance1 = new TousseInstance(); + instance1.setId(1L); + TousseInstance instance2 = new TousseInstance(); + instance2.setId(2L); + TousseInstance instance3 = new TousseInstance(); + instance3.setId(3L); + List mockInstances = Arrays.asList(instance1, instance2, instance3); + + when(mockApp.getTousseBarcodes()).thenReturn(";barcode1;barcode2;barcode3;"); + when(mockApp.getUseRecord()).thenReturn(mockUseRecord); + when(objectDao.findBySql(anyString(), anyString())).thenReturn(mockInstances); + + // Mock the method to return split instance IDs (instance2 is split) + List splitIds = Collections.singletonList(2L); + doReturn(splitIds).when(recyclingRecordManager).loadRecyclingTousseInstanceIdList(mockApp); + + // Act + List result = recyclingRecordManager.loadInvoicePlanUseRecordWaiteRecyclingTousseInstance(mockApp); + + // Assert + assertEquals("Should return filtered instances", 2, result.size()); + assertTrue("Should contain instance1", result.contains(instance1)); + assertFalse("Should not contain instance2 (split)", result.contains(instance2)); + assertTrue("Should contain instance3", result.contains(instance3)); + } + + /** + * Test case: 边界情况测试 - 只有一个实例且被拆分 + * 测试场景:只有一个实例但该实例已被拆分 + * 预期结果:返回空列表 + */ + @Test + public void testLoadInvoicePlanUseRecordWaiteRecyclingTousseInstance_SingleSplitInstance() { + // Arrange + TousseInstance instance1 = new TousseInstance(); + instance1.setId(1L); + List mockInstances = Collections.singletonList(instance1); + + when(mockApp.getTousseBarcodes()).thenReturn("[barcode1]"); + when(mockApp.getUseRecord()).thenReturn(mockUseRecord); + when(objectDao.findBySql(anyString(), anyString())).thenReturn(mockInstances); + + // Mock the method to return split instance IDs + List splitIds = Collections.singletonList(1L); + doReturn(splitIds).when(recyclingRecordManager).loadRecyclingTousseInstanceIdList(mockApp); + + // Act + List result = recyclingRecordManager.loadInvoicePlanUseRecordWaiteRecyclingTousseInstance(mockApp); + + // Assert + assertTrue("Should return empty list when all instances are split", CollectionUtils.isEmpty(result)); + } +} Index: ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadApplicationTousseSplitByOrgUnitTest.java =================================================================== diff -u --- ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadApplicationTousseSplitByOrgUnitTest.java (revision 0) +++ ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadApplicationTousseSplitByOrgUnitTest.java (revision 41453) @@ -0,0 +1,328 @@ +package com.forgon.disinfectsystem.recyclingrecord.service; + +import com.forgon.disinfectsystem.entity.basedatamanager.toussedefinition.TousseDefinition; +import com.forgon.disinfectsystem.entity.basedatamanager.toussedefinition.TousseInstance; +import com.forgon.disinfectsystem.entity.invoicemanager.Invoice; +import com.forgon.disinfectsystem.entity.recyclingapplication.RecyclingApplication; +import com.forgon.disinfectsystem.entity.tousseitem.TousseItem; +import com.forgon.disinfectsystem.entity.useRecord.UseRecord; +import com.forgon.disinfectsystem.recyclingrecord.vo.SplitDepartTousseVo; +import com.forgon.tools.hibernate.ObjectDao; +import com.forgon.tools.util.ConfigUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.*; + +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class RecyclingRecordManagerImplLoadApplicationTousseSplitByOrgUnitTest { + + @InjectMocks + @Spy + private RecyclingRecordManagerImpl recyclingRecordManager; + + @Mock + private ObjectDao objectDao; + private AutoCloseable closeable; + private MockedStatic mockedConfigUtils; + + @Before + public void setUp() throws Exception { + closeable = MockitoAnnotations.openMocks(this); + mockedConfigUtils = Mockito.mockStatic(ConfigUtils.class); + } + + @After + public void tearDown() throws Exception { + closeable.close(); + mockedConfigUtils.close(); + } + + /** + * TC5: Config disabled (value = 0) -> return empty map + */ + @Test + public void testLoadApplicationTousseSplitByOrgUnit_ConfigDisabled_ReturnEmptyMap() { + when(ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)).thenReturn(0); + + RecyclingApplication app = mock(RecyclingApplication.class); + when(app.getTousseBarcodes()).thenReturn("[barcode1]"); + when(objectDao.getById(eq(RecyclingApplication.class.getSimpleName()), anyLong())).thenReturn(app); + + Map result = recyclingRecordManager.loadApplicationTousseSplitByOrgUnit(1L); + assertTrue(result.isEmpty()); + } + + /** + * TC1: invoicePlanId == null -> return empty map + */ + @Test + public void testLoadApplicationTousseSplitByOrgUnit_NullInvoicePlanId_ReturnEmptyMap() { + when(ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)).thenReturn(1); + Map result = recyclingRecordManager.loadApplicationTousseSplitByOrgUnit(null); + assertTrue(result.isEmpty()); + } + + /** + * TC2: invoicePlanId <= 0 -> return empty map + */ + @Test + public void testLoadApplicationTousseSplitByOrgUnit_InvalidInvoicePlanId_ReturnEmptyMap() { + when(ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)).thenReturn(2); + Map result = recyclingRecordManager.loadApplicationTousseSplitByOrgUnit(-1L); + assertTrue(result.isEmpty()); + } + + /** + * TC3: App not found -> return empty map + */ + @Test + public void testLoadApplicationTousseSplitByOrgUnit_AppNotFound_ReturnEmptyMap() { + when(ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)).thenReturn(1); + when(objectDao.getById(eq(RecyclingApplication.class.getSimpleName()), anyLong())).thenReturn(null); + + Map result = recyclingRecordManager.loadApplicationTousseSplitByOrgUnit(1L); + assertTrue(result.isEmpty()); + } + + /** + * TC4: Blank barcodes -> return empty map + */ + @Test + public void testLoadApplicationTousseSplitByOrgUnit_BlankBarcodes_ReturnEmptyMap() { + when(ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)).thenReturn(1); + RecyclingApplication app = mock(RecyclingApplication.class); + when(app.getTousseBarcodes()).thenReturn(""); + when(objectDao.getById(eq(RecyclingApplication.class.getSimpleName()), anyLong())).thenReturn(app); + + Map result = recyclingRecordManager.loadApplicationTousseSplitByOrgUnit(1L); + assertTrue(result.isEmpty()); + } + + @Test + public void testLoadApplicationTousseSplitByOrgUnit_NullUseRecord_ReturnEmptyMap() throws Exception { + when(ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)).thenReturn(1); + RecyclingApplication app = mock(RecyclingApplication.class); + when(app.getTousseBarcodes()).thenReturn(";1246236;"); + when(objectDao.getById(eq(RecyclingApplication.class.getSimpleName()), anyLong())).thenReturn(app); + when(app.getUseRecord()).thenReturn(null); + + Map result = recyclingRecordManager.loadApplicationTousseSplitByOrgUnit(1L); + assertTrue(result.isEmpty()); + } + + @Test + public void testLoadApplicationTousseSplitByOrgUnit_EmptyApplicationItem_ReturnEmptyMap() throws Exception { + when(ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)).thenReturn(1); + RecyclingApplication app = mock(RecyclingApplication.class); + when(app.getTousseBarcodes()).thenReturn(";1246236;"); + when(app.getUseRecord()).thenReturn(mock(UseRecord.class)); + when(app.getApplicationItems()).thenReturn(new ArrayList<>()); + when(objectDao.getById(eq(RecyclingApplication.class.getSimpleName()), anyLong())).thenReturn(app); + + + Map result = recyclingRecordManager.loadApplicationTousseSplitByOrgUnit(1L); + assertTrue(result.isEmpty()); + } + + @Test + public void testLoadApplicationTousseSplitByOrgUnit_ApplicationItemAllTerminatedOrRecycled_ReturnEmptyMap() throws Exception { + when(ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)).thenReturn(1); + // Mock TousseItem + List items = new ArrayList<>(); + items.add(createTousseItem(200L, 1, 0, true)); + items.add(createTousseItem(300L, 1, 1, false)); + RecyclingApplication app = mock(RecyclingApplication.class); + when(app.getTousseBarcodes()).thenReturn(";1246236;"); + when(app.getUseRecord()).thenReturn(mock(UseRecord.class)); + when(app.getApplicationItems()).thenReturn(items); + when(objectDao.getById(eq(RecyclingApplication.class.getSimpleName()), anyLong())).thenReturn(app); + + + Map result = recyclingRecordManager.loadApplicationTousseSplitByOrgUnit(1L); + assertTrue(result.isEmpty()); + } + + @Test + public void testLoadApplicationTousseSplitByOrgUnit_WaitRecyclingTousseEmpty_ReturnEmptyMap() throws Exception { + when(ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)).thenReturn(1); + // Mock TousseItem + List items = new ArrayList<>(); + items.add(createTousseItem(200L, 1, 0, false)); + items.add(createTousseItem(300L, 2, 1, false)); + RecyclingApplication app = mock(RecyclingApplication.class); + when(app.getTousseBarcodes()).thenReturn(";1246236;"); + when(app.getUseRecord()).thenReturn(mock(UseRecord.class)); + when(app.getApplicationItems()).thenReturn(items); + when(objectDao.getById(RecyclingApplication.class.getSimpleName(), 1L)).thenReturn(app); + doReturn(Collections.emptyList()).when(recyclingRecordManager).loadInvoicePlanUseRecordWaiteRecyclingTousseInstance(app); + + Map result = recyclingRecordManager.loadApplicationTousseSplitByOrgUnit(1L); + assertTrue(result.isEmpty()); + } + + /** + * TC6: Config enabled (value = 1), split by depart coding + */ + @Test + public void testLoadApplicationTousseSplitByOrgUnit_MethodOne_SplitByDepartCoding() throws Exception { + when(ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)).thenReturn(1); + + // Mock app + RecyclingApplication app = mock(RecyclingApplication.class); + UseRecord useRecord = mock(UseRecord.class); + when(useRecord.getDepartCoding()).thenReturn("DEPT_A"); + when(app.getUseRecord()).thenReturn(useRecord); + when(app.getTousseBarcodes()).thenReturn(";barcode1;"); + when(app.getDepartCoding()).thenReturn("DEPT_B"); + + when(objectDao.getById(eq(RecyclingApplication.class.getSimpleName()), anyLong())).thenReturn(app); + + // Mock TousseItem + List items = new ArrayList<>(); + items.add(createTousseItem(100L, 2, 0, false)); + items.add(createTousseItem(200L, 1, 0, true)); + items.add(createTousseItem(300L, 1, 1, false)); + when(app.getApplicationItems()).thenReturn(items); + + // Mock TousseInstance + List tousseInstances = new ArrayList<>(); + // 正常的实例 + TousseInstance ti = createTousseInstance(100L, "Test Tousse", 200L, "barcode1", "idcard1"); + tousseInstances.add(ti); + // 包定义id不在申请项中 + ti = createTousseInstance(101L, "Test Tousse2", 201L, "barcode2", "idcard2"); + tousseInstances.add(ti); + // 已经终止的申请项 + ti = createTousseInstance(300L, "Test Tousse3", 201L, "barcode3", "idcard3"); + tousseInstances.add(ti); + // 发货单id无效 + ti = createTousseInstance(100L, "Test Tousse", 0L, "barcode4", "idcard4"); + tousseInstances.add(ti); + // 发货单id不存在 + ti = createTousseInstance(100L, "Test Tousse", 201L, "barcode5", "idcard5"); + tousseInstances.add(ti); + // 第2个正常的包实例 + ti = createTousseInstance(100L, "Test Tousse", 200L, "barcode6", "idcard6"); + tousseInstances.add(ti); + + when(objectDao.findBySql(anyString(), anyString())).thenReturn(tousseInstances); + + // Mock Invoice + Invoice invoice = mock(Invoice.class); + when(invoice.getDepartCoding()).thenReturn("DEPT_C"); + when(invoice.getDepart()).thenReturn("Department C"); + when(invoice.getId()).thenReturn(200L); + when(objectDao.findByIds(eq(Invoice.class.getSimpleName()), anyList())).thenReturn(Collections.singletonList(invoice)); + + // Mock ResultSet for RecyclingTousseInstance + ResultSet rs = mock(ResultSet.class); + when(rs.next()).thenReturn(false); // No previous split + when(objectDao.executeSql(anyString())).thenReturn(rs); + + Map result = recyclingRecordManager.loadApplicationTousseSplitByOrgUnit(1L); + + assertEquals(1, result.size()); + assertNotNull(result.get("100_DEPT_C")); + assertEquals(2, result.get("100_DEPT_C").getAmount().intValue()); + } + + /** + * TC7: Config enabled (value = 2), split by asset belong code + */ + @Test + public void testLoadApplicationTousseSplitByOrgUnit_MethodTwo_SplitByAssetBelong() throws Exception { + when(ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)).thenReturn(2); + + // Mock app + RecyclingApplication app = mock(RecyclingApplication.class); + UseRecord useRecord = mock(UseRecord.class); + when(useRecord.getDepartCoding()).thenReturn("DEPT_A"); + when(app.getUseRecord()).thenReturn(useRecord); + when(app.getTousseBarcodes()).thenReturn("[barcode1]"); + when(app.getDepartCoding()).thenReturn("DEPT_B"); + + when(objectDao.getById(eq(RecyclingApplication.class.getSimpleName()), anyLong())).thenReturn(app); + + // Mock TousseItem + List items = new ArrayList<>(); + TousseItem item = createTousseItem(100L, 2, 0, false); + items.add(item); + when(app.getApplicationItems()).thenReturn(items); + + // Mock TousseInstance + List tis = createTousseInstance(100L, "Test Tousse", "ASSET_DEPT_X", + "Asset Department X","barcode1", "idcard1", "barcode2", "idcard2","barcode3","idcard3"); + // 跟申请单科室一样 + tis.addAll(createTousseInstance(102L, "Test Tousse2", "DEPT_B", + "Department B","barcode21", "idcard21")); + List tds = tis.stream().map(TousseInstance::getTousseDefinition).collect(Collectors.toList()); + + // 额外添加一些其他的器械包,没有在包定义的查询结果中 + tis.addAll(createTousseInstance(104L, "Test Tousse3", "ASSET_DEPT_Y", + "Asset Department Y","barcode31", "idcard31")); + + when(objectDao.findBySql(anyString(), anyString())).thenReturn(tis); + + // Mock TousseDefinition lookup + when(objectDao.findByIds(eq(TousseDefinition.class.getSimpleName()), anyList())).thenReturn(tds); + + // Mock ResultSet for RecyclingTousseInstance + ResultSet rs = mock(ResultSet.class); + when(rs.next()).thenReturn(false); // No previous split + when(objectDao.executeSql(anyString())).thenReturn(rs); + + Map result = recyclingRecordManager.loadApplicationTousseSplitByOrgUnit(1L); + + assertEquals(1, result.size()); + assertNotNull(result.get("100_ASSET_DEPT_X")); + assertEquals(2, result.get("100_ASSET_DEPT_X").getAmount().intValue()); + } + + private static TousseItem createTousseItem(Long tousseDefinitionId, int amount, int recycledAmount, boolean terminated) { + TousseItem item = mock(TousseItem.class); + when(item.getIsTerminated()).thenReturn(terminated); + when(item.getAmount()).thenReturn(amount); + when(item.getRecyclingAmount()).thenReturn(recycledAmount); + when(item.getTousseDefinitionId()).thenReturn(tousseDefinitionId); + return item; + } + + private List createTousseInstance(Long tdId, String tousseName, String assetsBelongCode, String assetsBelong, String... barcodes) { + TousseDefinition td = mock(TousseDefinition.class); + when(td.getId()).thenReturn(tdId); + when(td.getName()).thenReturn(tousseName); + when(td.getAssetsBelongCode()).thenReturn(assetsBelongCode); + when(td.getAssetsBelong(any())).thenReturn(assetsBelong); + List tis = new ArrayList<>(); + for (int i = 0; i < barcodes.length / 2; i++) { + TousseInstance ti = mock(TousseInstance.class); + when(ti.getTousseDefinition()).thenReturn(td); + when(ti.getBarcode()).thenReturn(barcodes[i * 2]); + when(ti.getIdCardInstanceBarcode()).thenReturn(barcodes[i * 2 + 1]); + tis.add(ti); + } + return tis; + } + + private TousseInstance createTousseInstance(Long tdId, String tousseName, Long lastInvoiceId, String barcode, String idCardBarcode) { + TousseInstance ti = mock(TousseInstance.class); + TousseDefinition td = mock(TousseDefinition.class); + when(td.getId()).thenReturn(tdId); + when(td.getName()).thenReturn(tousseName); + when(ti.getTousseDefinition()).thenReturn(td); + when(ti.getLastInvoiceId()).thenReturn(lastInvoiceId); + when(ti.getBarcode()).thenReturn(barcode); + when(ti.getIdCardInstanceBarcode()).thenReturn(idCardBarcode); + return ti; + } +} Index: ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadTousseInstanceInvoiceMapUnitTest.java =================================================================== diff -u --- ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadTousseInstanceInvoiceMapUnitTest.java (revision 0) +++ ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadTousseInstanceInvoiceMapUnitTest.java (revision 41453) @@ -0,0 +1,141 @@ +package com.forgon.disinfectsystem.recyclingrecord.service; + +import com.forgon.disinfectsystem.entity.basedatamanager.toussedefinition.TousseInstance; +import com.forgon.disinfectsystem.entity.invoicemanager.Invoice; +import com.forgon.tools.hibernate.ObjectDao; +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 java.util.*; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class RecyclingRecordManagerImplLoadTousseInstanceInvoiceMapUnitTest { + + @InjectMocks + @Spy + private RecyclingRecordManagerImpl recyclingRecordManager; + + @Mock + private ObjectDao objectDao; + + private AutoCloseable closeable; + + @Before + public void setUp() { + closeable = MockitoAnnotations.openMocks(this); + } + + @After + public void tearDown() throws Exception { + closeable.close(); + } + + /** + * TC1: Input list is null -> should return empty map + */ + @Test + public void testLoadTousseInstanceInvoiceMap_NullInput_ReturnsEmptyMap() { + Map result = recyclingRecordManager.loadTousseInstanceInvoiceMap(null); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + /** + * TC2: Input list is empty -> should return empty map + */ + @Test + public void testLoadTousseInstanceInvoiceMap_EmptyInput_ReturnsEmptyMap() { + List tiList = Collections.emptyList(); + Map result = recyclingRecordManager.loadTousseInstanceInvoiceMap(tiList); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + /** + * TC3: All TousseInstances have invalid lastInvoiceId (null or <= 0) + * Should skip them and return empty map + */ + @Test + public void testLoadTousseInstanceInvoiceMap_AllInvalidIds_ReturnsEmptyMap() { + List tiList = new ArrayList<>(); + TousseInstance ti1 = mock(TousseInstance.class); + TousseInstance ti2 = mock(TousseInstance.class); + + when(ti1.getLastInvoiceId()).thenReturn(null); // Invalid + when(ti2.getLastInvoiceId()).thenReturn(-1L); // Invalid + + tiList.add(ti1); + tiList.add(ti2); + + Map result = recyclingRecordManager.loadTousseInstanceInvoiceMap(tiList); + assertNotNull(result); + assertTrue(result.isEmpty()); + + verify(ti1).getLastInvoiceId(); + verify(ti2).getLastInvoiceId(); + } + + /** + * TC4: Some valid IDs, but no corresponding invoices found in DB + * Should return empty map + */ + @Test + public void testLoadTousseInstanceInvoiceMap_ValidIdsButNoMatchingInvoices_ReturnsEmptyMap() { + List tiList = new ArrayList<>(); + TousseInstance ti = mock(TousseInstance.class); + when(ti.getLastInvoiceId()).thenReturn(999L); // Valid ID + tiList.add(ti); + + when(objectDao.findByIds(anyString(), anyList())).thenReturn(Collections.emptyList()); + + Map result = recyclingRecordManager.loadTousseInstanceInvoiceMap(tiList); + assertNotNull(result); + assertTrue(result.isEmpty()); + + verify(ti).getLastInvoiceId(); + verify(objectDao).findByIds(Invoice.class.getSimpleName(), Collections.singletonList(999L)); + } + + /** + * TC5: Valid IDs with matching invoices in DB + * Should build correct map + */ + @Test + public void testLoadTousseInstanceInvoiceMap_ValidIdsWithMatchingInvoices_BuildsCorrectMap() { + List tiList = new ArrayList<>(); + TousseInstance ti1 = mock(TousseInstance.class); + TousseInstance ti2 = mock(TousseInstance.class); + + when(ti1.getLastInvoiceId()).thenReturn(100L); + when(ti2.getLastInvoiceId()).thenReturn(200L); + + tiList.add(ti1); + tiList.add(ti2); + + Invoice inv1 = mock(Invoice.class); + Invoice inv2 = mock(Invoice.class); + + when(inv1.getId()).thenReturn(100L); + when(inv2.getId()).thenReturn(200L); + + List invoiceList = Arrays.asList(inv1, inv2); + when(objectDao.findByIds(eq(Invoice.class.getSimpleName()), anyList())).thenReturn(invoiceList); + + Map result = recyclingRecordManager.loadTousseInstanceInvoiceMap(tiList); + assertNotNull(result); + assertEquals(2, result.size()); + assertSame(inv1, result.get(100L)); + assertSame(inv2, result.get(200L)); + + verify(ti1).getLastInvoiceId(); + verify(ti2).getLastInvoiceId(); + verify(objectDao).findByIds(Invoice.class.getSimpleName(), Arrays.asList(100L, 200L)); + } +} Index: ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadRecyclingTousseInstanceIdListTest.java =================================================================== diff -u --- ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadRecyclingTousseInstanceIdListTest.java (revision 0) +++ ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadRecyclingTousseInstanceIdListTest.java (revision 41453) @@ -0,0 +1,134 @@ +package com.forgon.disinfectsystem.recyclingrecord.service; + +import com.forgon.disinfectsystem.entity.recyclingapplication.RecyclingApplication; +import com.forgon.disinfectsystem.entity.recyclingrecord.RecyclingTousseInstance; +import com.forgon.tools.db.DatabaseUtil; +import com.forgon.tools.hibernate.ObjectDao; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.MockitoAnnotations; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests for {@link RecyclingRecordManagerImpl#loadRecyclingTousseInstanceIdList}. + */ +public class RecyclingRecordManagerImplLoadRecyclingTousseInstanceIdListTest { + + @InjectMocks + private RecyclingRecordManagerImpl recyclingRecordManager; + + @Mock + private ObjectDao objectDao; + + @Mock + private ResultSet resultSet; + + private AutoCloseable closeable; + private MockedStatic mockDatabaseUtil; + + @Before + public void setUp() { + closeable = MockitoAnnotations.openMocks(this); + mockDatabaseUtil = mockStatic(DatabaseUtil.class); + } + + @After + public void tearDown() throws Exception { + closeable.close(); + mockDatabaseUtil.close(); + } + + /** + * Test normal flow: SQL returns multiple valid IDs. + */ + @Test + public void testLoadRecyclingTousseInstanceIdList_NormalCase_ReturnsIds() throws SQLException { + // Arrange + RecyclingApplication app = new RecyclingApplication(); + app.setId(100L); + + String expectedSql = "select rt.tousseInstanceId from " + + RecyclingTousseInstance.class.getSimpleName() + + " rt where rt.invoicePlanId = 100"; + + when(objectDao.executeSql(expectedSql)).thenReturn(resultSet); + when(resultSet.next()) + .thenReturn(true).thenReturn(true).thenReturn(false); // Two rows then end + when(resultSet.getLong("tousseInstanceId")) + .thenReturn(1001L).thenReturn(1002L); + + // Act + List result = recyclingRecordManager.loadRecyclingTousseInstanceIdList(app); + + // Assert + assertNotNull(result); + assertEquals(2, result.size()); + assertTrue(result.contains(1001L)); + assertTrue(result.contains(1002L)); + + verify(resultSet, times(2)).getLong("tousseInstanceId"); + verify(resultSet, times(3)).next(); + mockDatabaseUtil.verify(() -> DatabaseUtil.closeResultSetAndStatement(resultSet)); + } + + /** + * Test edge case: No matching records found. + */ + @Test + public void testLoadRecyclingTousseInstanceIdList_EmptyResultSet_ReturnsEmptyList() throws SQLException { + // Arrange + RecyclingApplication app = new RecyclingApplication(); + app.setId(999L); + + String expectedSql = "select rt.tousseInstanceId from " + + RecyclingTousseInstance.class.getSimpleName() + + " rt where rt.invoicePlanId = 999"; + + when(objectDao.executeSql(expectedSql)).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); // No rows + + // Act + List result = recyclingRecordManager.loadRecyclingTousseInstanceIdList(app); + + // Assert + assertNotNull(result); + assertTrue(result.isEmpty()); + + verify(resultSet, never()).getLong(anyString()); + verify(resultSet, times(1)).next(); + mockDatabaseUtil.verify(() -> DatabaseUtil.closeResultSetAndStatement(resultSet)); + } + + /** + * Test error handling: SQLException thrown by executeSql. + */ + @Test + public void testLoadRecyclingTousseInstanceIdList_SqlExceptionOccurs_PrintsStackTraceAndReturnsEmptyList() throws SQLException { + // Arrange + RecyclingApplication app = new RecyclingApplication(); + app.setId(500L); + + String expectedSql = "select rt.tousseInstanceId from " + + RecyclingTousseInstance.class.getSimpleName() + + " rt where rt.invoicePlanId = 500"; + + RuntimeException sqlException = new RuntimeException("Simulated SQL error"); + when(objectDao.executeSql(expectedSql)).thenThrow(sqlException); + + // Act & Assert + List result = recyclingRecordManager.loadRecyclingTousseInstanceIdList(app); + assertNotNull(result); + assertTrue(result.isEmpty()); + // Verify logging would occur (not directly testable here unless captured) + } +}