Index: ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/vo/RecyclingContext.java =================================================================== diff -u -r40462 -r41537 --- ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/vo/RecyclingContext.java (.../RecyclingContext.java) (revision 40462) +++ ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/vo/RecyclingContext.java (.../RecyclingContext.java) (revision 41537) @@ -1,17 +1,19 @@ package com.forgon.disinfectsystem.recyclingrecord.vo; -import java.util.Date; -import java.util.List; - +import com.fasterxml.jackson.core.type.TypeReference; +import com.forgon.Constants; +import com.forgon.disinfectsystem.recyclingapplication.vo.ReturnGoodVo; +import com.forgon.disinfectsystem.recyclingrecord.param.SplitSaveRecyclingTousseParam; +import com.forgon.tools.json.JacksonUtil; +import com.forgon.tools.util.ForgonDateUtils; import net.sf.json.JSONArray; import net.sf.json.JSONObject; - import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.BooleanUtils; -import com.forgon.Constants; -import com.forgon.disinfectsystem.recyclingapplication.vo.ReturnGoodVo; -import com.forgon.tools.util.ForgonDateUtils; +import java.util.Collections; +import java.util.Date; +import java.util.List; /** * 回收的上下文环境。记录参数及 @@ -599,4 +601,25 @@ public void setSaveRecyclingRecord(boolean saveRecyclingRecord) { this.saveRecyclingRecord = saveRecyclingRecord; } + + public List splitTousseJson() { + JSONArray array = jsonParamObject.optJSONArray("splitTousseJson"); + if(array == null){ + return Collections.emptyList(); + } + return JacksonUtil.parseObject(array.toString(), new TypeReference>() {}); + } + + private List tousseJson; + public List tousseJson() { + if(tousseJson != null) { + return tousseJson; + } + JSONArray array = jsonParamObject.optJSONArray("tousseJson"); + if(array == null){ + return Collections.emptyList(); + } + tousseJson = JacksonUtil.parseObject(array.toString(), new TypeReference>() {}); + return tousseJson; + } } Index: ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/CardinalNumLimitValidatorTest.java =================================================================== diff -u --- ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/CardinalNumLimitValidatorTest.java (revision 0) +++ ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/CardinalNumLimitValidatorTest.java (revision 41537) @@ -0,0 +1,235 @@ +package com.forgon.disinfectsystem.recyclingrecord.service; + +import com.forgon.disinfectsystem.common.CssdUtils; +import com.forgon.disinfectsystem.model.DepartmentAware; +import com.forgon.disinfectsystem.recyclingrecord.vo.RecyclingBasketItemVo; +import com.forgon.disinfectsystem.tousse.toussedefinition.service.TousseInstanceManager; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * CardinalNumLimitValidator 单元测试类 + */ +public class CardinalNumLimitValidatorTest { + + private TousseInstanceManager tousseInstanceManager; + private DepartmentAware departmentAware; + private List basketItemVos; + + @Before + public void setUp() { + // 初始化mock对象 + tousseInstanceManager = mock(TousseInstanceManager.class); + departmentAware = mock(DepartmentAware.class); + basketItemVos = new ArrayList<>(); + } + + /** + * 测试用例TC001: 系统禁用基数限制时,应返回空验证器 + * 条件: disableCardinalNumLimit=true + */ + @Test + public void testValidator_WhenCardinalNumLimitDisabled_ShouldReturnEmptyValidator() { + try (MockedStatic mockedCssdUtils = Mockito.mockStatic(CssdUtils.class)) { + // Given: 系统设置禁用基数限制 + mockedCssdUtils.when(() -> CssdUtils.getSystemSetConfigByNameBool("disableCardinalNumLimit", false)) + .thenReturn(true); + + // When: 调用validator方法 + CardinalNumLimitValidator validator = CardinalNumLimitValidator.validator( + tousseInstanceManager, departmentAware, basketItemVos); + + // Then: 应该返回非null的验证器(空实现) + assertNotNull(validator); + assertSame(CardinalNumLimitValidator.EMPTY, validator); + + // 验证没有调用任何mock方法 + verifyNoInteractions(tousseInstanceManager); + verifyNoInteractions(departmentAware); + } + } + + /** + * 测试用例TC002: 科室编码为null时,应返回空验证器 + * 条件: disableCardinalNumLimit=false, departCode=null + */ + @Test + public void testValidator_WhenDepartmentCodeIsNull_ShouldReturnEmptyValidator() { + try (MockedStatic mockedCssdUtils = Mockito.mockStatic(CssdUtils.class)) { + // Given: 系统启用基数限制,科室编码为null + mockedCssdUtils.when(() -> CssdUtils.getSystemSetConfigByNameBool("disableCardinalNumLimit", false)) + .thenReturn(false); + when(departmentAware.getDepartCode()).thenReturn(null); + + // When: 调用validator方法 + CardinalNumLimitValidator validator = CardinalNumLimitValidator.validator( + tousseInstanceManager, departmentAware, basketItemVos); + + // Then: 应该返回空验证器 + assertNotNull(validator); + assertSame(CardinalNumLimitValidator.EMPTY, validator); + + // 验证只调用了获取科室编码的方法 + verify(departmentAware).getDepartCode(); + verifyNoMoreInteractions(departmentAware); + verifyNoInteractions(tousseInstanceManager); + } + } + + /** + * 测试用例TC003: 科室编码为空字符串时,应返回空验证器 + * 条件: disableCardinalNumLimit=false, departCode="" + */ + @Test + public void testValidator_WhenDepartmentCodeIsEmpty_ShouldReturnEmptyValidator() { + try (MockedStatic mockedCssdUtils = Mockito.mockStatic(CssdUtils.class)) { + // Given: 系统启用基数限制,科室编码为空字符串 + mockedCssdUtils.when(() -> CssdUtils.getSystemSetConfigByNameBool("disableCardinalNumLimit", false)) + .thenReturn(false); + when(departmentAware.getDepartCode()).thenReturn(""); + + // When: 调用validator方法 + CardinalNumLimitValidator validator = CardinalNumLimitValidator.validator( + tousseInstanceManager, departmentAware, basketItemVos); + + // Then: 应该返回空验证器 + assertNotNull(validator); + assertSame(CardinalNumLimitValidator.EMPTY, validator); + + // 验证调用了相关方法 + verify(departmentAware).getDepartCode(); + } + } + + /** + * 测试用例TC004: 包列表为空时,应返回空验证器 + * 条件: disableCardinalNumLimit=false, departCode="DEPT001", tousseJson=empty + */ + @Test + public void testValidator_WhenTousseJsonListIsEmpty_ShouldReturnEmptyValidator() { + try (MockedStatic mockedCssdUtils = Mockito.mockStatic(CssdUtils.class)) { + // Given: 系统启用基数限制,科室编码有效,包列表为空 + mockedCssdUtils.when(() -> CssdUtils.getSystemSetConfigByNameBool("disableCardinalNumLimit", false)) + .thenReturn(false); + when(departmentAware.getDepartCode()).thenReturn("DEPT001"); + // mockTousseJsonList 已经是空列表 + + // When: 调用validator方法 + CardinalNumLimitValidator validator = CardinalNumLimitValidator.validator( + tousseInstanceManager, departmentAware, basketItemVos); + + // Then: 应该返回空验证器 + assertNotNull(validator); + assertSame(CardinalNumLimitValidator.EMPTY, validator); + + // 验证调用了相关方法 + verify(departmentAware).getDepartCode(); + } + } + + /** + * 测试用例TC005: 正常情况,应返回实际验证器 + * 条件: disableCardinalNumLimit=false, departCode="DEPT001", tousseJson=not empty + */ + @Test + public void testValidator_NormalCase_ShouldReturnActualValidator() { + try (MockedStatic mockedCssdUtils = Mockito.mockStatic(CssdUtils.class)) { + // Given: 系统启用基数限制,科室编码有效,包列表不为空 + mockedCssdUtils.when(() -> CssdUtils.getSystemSetConfigByNameBool("disableCardinalNumLimit", false)) + .thenReturn(false); + when(departmentAware.getDepartCode()).thenReturn("DEPT001"); + + // 创建模拟的回收篮项目 + RecyclingBasketItemVo mockItem1 = mock(RecyclingBasketItemVo.class); + when(mockItem1.getTousseName()).thenReturn("TOUSSE001"); + RecyclingBasketItemVo mockItem2 = mock(RecyclingBasketItemVo.class); + when(mockItem2.getTousseName()).thenReturn("TOUSSE002"); + + basketItemVos.add(mockItem1); + basketItemVos.add(mockItem2); + + // 模拟tousseInstanceManager的返回值 + Map> mockResultMap = new HashMap<>(); + mockResultMap.put("TOUSSE001", Collections.singletonMap("STATUS1", 10)); + mockResultMap.put("TOUSSE002", Collections.singletonMap("STATUS1", 5)); + + when(tousseInstanceManager.getTousseAmountByGroupStatus( + eq("DEPT001"), + any(Set.class), + eq(false), + eq(true))) + .thenReturn(mockResultMap); + + // When: 调用validator方法 + CardinalNumLimitValidator validator = CardinalNumLimitValidator.validator( + tousseInstanceManager, departmentAware, basketItemVos); + + // Then: 应该返回实际的验证器实现 + assertNotNull(validator); + assertTrue(validator instanceof CardinalNumLimitValidator.CardinalNumLimitValidatorImpl); + + // 验证所有mock方法都被正确调用 + verify(departmentAware, times(2)).getDepartCode(); + verify(tousseInstanceManager).getTousseAmountByGroupStatus( + eq("DEPT001"), + any(Set.class), + eq(false), + eq(true)); + } + } + + /** + * 测试边界情况:包列表包含重复包名 + */ + @Test + public void testValidator_WithDuplicateTousseNames_ShouldHandleCorrectly() { + try (MockedStatic mockedCssdUtils = Mockito.mockStatic(CssdUtils.class)) { + // Given: 系统启用基数限制,科室编码有效,包列表包含重复项 + mockedCssdUtils.when(() -> CssdUtils.getSystemSetConfigByNameBool("disableCardinalNumLimit", false)) + .thenReturn(false); + when(departmentAware.getDepartCode()).thenReturn("DEPT001"); + + // 创建包含重复包名的回收篮项目 + RecyclingBasketItemVo mockItem1 = mock(RecyclingBasketItemVo.class); + when(mockItem1.getTousseName()).thenReturn("TOUSSE001"); + RecyclingBasketItemVo mockItem2 = mock(RecyclingBasketItemVo.class); + when(mockItem2.getTousseName()).thenReturn("TOUSSE001"); // 重复 + + basketItemVos.add(mockItem1); + basketItemVos.add(mockItem2); + + // 模拟tousseInstanceManager的返回值 + Map> mockResultMap = new HashMap<>(); + mockResultMap.put("TOUSSE001", Collections.singletonMap("STATUS1", 10)); + + when(tousseInstanceManager.getTousseAmountByGroupStatus( + eq("DEPT001"), + any(Set.class), + eq(false), + eq(true))) + .thenReturn(mockResultMap); + + // When: 调用validator方法 + CardinalNumLimitValidator validator = CardinalNumLimitValidator.validator( + tousseInstanceManager, departmentAware, basketItemVos); + + // Then: 应该正常处理并返回验证器 + assertNotNull(validator); + assertTrue(validator instanceof CardinalNumLimitValidator.CardinalNumLimitValidatorImpl); + + // 验证传递给manager的set只包含唯一元素 + verify(tousseInstanceManager).getTousseAmountByGroupStatus( + eq("DEPT001"), + argThat(set -> set.size() == 1 && set.contains("TOUSSE001")), + eq(false), + eq(true)); + } + } +} Index: ssts-basedata/src/main/java/com/forgon/disinfectsystem/entity/recyclingrecord/RecyclingItem.java =================================================================== diff -u -r40302 -r41537 --- ssts-basedata/src/main/java/com/forgon/disinfectsystem/entity/recyclingrecord/RecyclingItem.java (.../RecyclingItem.java) (revision 40302) +++ ssts-basedata/src/main/java/com/forgon/disinfectsystem/entity/recyclingrecord/RecyclingItem.java (.../RecyclingItem.java) (revision 41537) @@ -4,6 +4,8 @@ import java.util.List; import javax.persistence.Entity; + +import com.forgon.disinfectsystem.model.ToussePackage; import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; import javax.persistence.Column; @@ -44,7 +46,7 @@ ,@Index(columnList = "tousseDefinitionId,recyclingRecord_id,amount,materialAmount", name = "rci_td_rr_amount_mamount") }) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -public class RecyclingItem { +public class RecyclingItem implements ToussePackage { /**回收项id*/ private Long id; /**器械包名称*/ @@ -124,6 +126,7 @@ this.spelling = spelling; } + @Override public String getTousseName() { return tousseName; } @@ -207,6 +210,7 @@ } return false; } + @Override public Long getTousseDefinitionId() { return tousseDefinitionId; } Index: ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/vo/RecyclingBasketItemVo.java =================================================================== diff -u -r27394 -r41537 --- ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/vo/RecyclingBasketItemVo.java (.../RecyclingBasketItemVo.java) (revision 27394) +++ ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/vo/RecyclingBasketItemVo.java (.../RecyclingBasketItemVo.java) (revision 41537) @@ -34,6 +34,10 @@ */ private String idCardBarcode; /** + * 器械包实例条码 + */ + private String tousseInstanceBarcode; + /** * 篮筐条码 */ private String basketBarcode; @@ -64,6 +68,10 @@ * 包内材料 */ private List materials = new ArrayList<>(); + /** + * 拆分的器械包条码。这个不是前端传入的,是后台自己分的。以英文分号分隔 + */ + private String splitTousseBarcodes; public String getItemType() { return itemType; } @@ -90,6 +98,15 @@ public void setIdCardBarcode(String idCardBarcode) { this.idCardBarcode = idCardBarcode; } + + public String getTousseInstanceBarcode() { + return tousseInstanceBarcode; + } + + public void setTousseInstanceBarcode(String tousseInstanceBarcode) { + this.tousseInstanceBarcode = tousseInstanceBarcode; + } + public String getBasketBarcode() { return basketBarcode; } @@ -144,6 +161,15 @@ public void setLastTousseInstanceId(Long lastTousseInstanceId) { this.lastTousseInstanceId = lastTousseInstanceId; } + + public String getSplitTousseBarcodes() { + return splitTousseBarcodes; + } + + public void setSplitTousseBarcodes(String splitTousseBarcodes) { + this.splitTousseBarcodes = splitTousseBarcodes; + } + /** * 判断类型是否材料 * @return Index: ssts-web/src/main/webapp/disinfectsystem/touchScreen/recycle/recycleForTouchScreen.js =================================================================== diff -u -r41439 -r41537 --- ssts-web/src/main/webapp/disinfectsystem/touchScreen/recycle/recycleForTouchScreen.js (.../recycleForTouchScreen.js) (revision 41439) +++ ssts-web/src/main/webapp/disinfectsystem/touchScreen/recycle/recycleForTouchScreen.js (.../recycleForTouchScreen.js) (revision 41537) @@ -425,14 +425,45 @@ }) } +/** + * 在器械包拆单信息中查找对应科室和器械包名称的记录 + * @param array 就是splitTousseArray2,从后台返回的器械包拆单信息 + * @param depart 科室名称 + * @param tousseName 器械包名称 + * @returns {*|null} 如果找到则返回对应的记录,否则返回null + */ +function searchDepartTousseOfSplitTousse(array, depart, tousseName) { + array = array || []; + for (var i = 0; i < array.length; i++) { + if (array[i].tousseName == tousseName && depart == array[i].depart) { + return array[i]; + } + } + return null; +} + +/** + * 判断拆分项中是否包含指定的器械包条码 + * @param splitItem 器械包拆分项。splitTousseArray2中的一项 + * @param tousseInstanceBarcode 器械包实例的条码 + */ +function splitTousseItemContainsTousseBarcode(splitItem, tousseInstanceBarcode) { + var splitTousseBarcodes = splitItem.splitTousseBarcodes || [] + for(var i = 0; i < splitTousseBarcodes.length; i++) { + if(splitTousseBarcodes[i].tousseBarcode == tousseInstanceBarcode) { + return true; + } + } + return false; +} + // 打开不同申请科室或者不同资产归属的器械包 function openApplicationTousseSplitByOrgUnit(newTousseArray, tousseArray) { var depart = $('#depart').text(); - var table = '
该申请单中以下物品不属于当前回收科室"' + depart + '",请确认需要拆单回收的物品以及所属科室:
'; - var name = (sstsConfig.methodOfSplitRecyclingApplication == 2) ? '资产归属' : '所属科室'; + var table = '
以下物品不属于"' + depart + '"科室,是否返回原科室:
'; table += ''; table += ''; - table += ''; + table += ''; table += ''; table += ''; for (var i = 0; i < newTousseArray.length; i++) { @@ -506,19 +537,22 @@ } for (var j = 0; j < tousseArray.length; j++) { if (tousseArray[j].tousseName == tousseName) { - if (tousseArray[j].amount - amount <= 0) { - newSplitTousseArray[k] = $.extend({}, tousseArray[j], splitTousseArray[k]); - tousseArray.splice(j, 1); - } else { - tousseArray[j].amount = tousseArray[j].amount - amount; - newSplitTousseArray[k] = $.extend({}, tousseArray[j], splitTousseArray[k]); - } + newSplitTousseArray[k] = $.extend({}, tousseArray[j], splitTousseArray[k]); + break; + // if (tousseArray[j].amount - amount <= 0) { + // newSplitTousseArray[k] = $.extend({}, tousseArray[j], splitTousseArray[k]); + // tousseArray.splice(j, 1); + // j--; + // } else { + // tousseArray[j].amount = tousseArray[j].amount - amount; + // newSplitTousseArray[k] = $.extend({}, tousseArray[j], splitTousseArray[k]); + // } } } k++; } }) - submitFormFunction(false, false, false, false, false, true); + submitFormFunction(false, false, false, false, false, true, undefined, false); layer.closeAll(); }) } @@ -5692,6 +5726,7 @@ depart: depart } k++ + break // 这里匹配上之后不应该结束内层循环吗?虽然标识牌能保证不重复,但是后面可能空循环吧 } } else { newTousseArray[k] = { @@ -5700,6 +5735,7 @@ depart: depart } k++ + break // 这里不退出内层循环,那比上面标识牌要严重多了,会导致重复记录(相同包定义的实例在tousseArray中可能有多条,因为条码不同) } } } @@ -5773,7 +5809,7 @@ function checkAutoReturnBorrowing(confirmation, saveAndNew, print, recyclingAmountConfirm) { //除申请还物单、自定义器械申请单、外来器械申请单、历史回收记录等可以直接提交外,其它类型的单回收都需要判断是否有物品要归还,包括手动添加回收申请单、科室申领添加的申请单、使用记录转换的申请单等都需要判断是否需要自动归还 if (!isUndefinedOrNullOrEmpty(params_id)) { - submitFormFunction(confirmation, saveAndNew, false, print, recyclingAmountConfirm); + submitFormFunction(confirmation, saveAndNew, false, print, recyclingAmountConfirm, undefined, undefined, true); return; } var tousseNames = []; @@ -5821,9 +5857,9 @@ table += '
物品名称数量' + name + '是否拆单
物品数量原科室是否返回原科室
'; var html = '
' + table + '
'; dialogConfirm(html, function () { - submitFormFunction(confirmation, saveAndNew, true, print, recyclingAmountConfirm); + submitFormFunction(confirmation, saveAndNew, true, print, recyclingAmountConfirm, undefined, undefined, true); }, function () { - submitFormFunction(confirmation, saveAndNew, false, print, recyclingAmountConfirm); + submitFormFunction(confirmation, saveAndNew, false, print, recyclingAmountConfirm, undefined, undefined, true); }, 800, 500); } else { //ZSYY-335:外来器械发货后可以二次回收 @@ -5839,14 +5875,14 @@ } if (html == '') { - submitFormFunction(confirmation, saveAndNew, false, print, recyclingAmountConfirm, false, '否'); + submitFormFunction(confirmation, saveAndNew, false, print, recyclingAmountConfirm, false, '否', true); } else { dialogConfirm(html + "是否直接进行二次回收?", function () { - submitFormFunction(confirmation, saveAndNew, false, print, recyclingAmountConfirm, false, '否'); + submitFormFunction(confirmation, saveAndNew, false, print, recyclingAmountConfirm, false, '否', true); }); } } else { - submitFormFunction(confirmation, saveAndNew, false, print, recyclingAmountConfirm); + submitFormFunction(confirmation, saveAndNew, false, print, recyclingAmountConfirm, undefined, undefined, true); } } } @@ -5860,7 +5896,7 @@ function checkAutoReturnBorrowingForInventoryConfirm(confirmation, saveAndNew, print, recyclingAmountConfirm) { //除申请还物单、自定义器械申请单、外来器械申请单、历史回收记录等可以直接提交外,其它类型的单回收都需要判断是否有物品要归还,包括手动添加回收申请单、科室申领添加的申请单、使用记录转换的申请单等都需要判断是否需要自动归还 if (!isUndefinedOrNullOrEmpty(params_id)) { - submitFormFunction(confirmation, saveAndNew, false, print, recyclingAmountConfirm); + submitFormFunction(confirmation, saveAndNew, false, print, recyclingAmountConfirm, undefined, undefined, true); return; } var tousseNames = []; @@ -6093,6 +6129,48 @@ } /** + * 回收拆单的检查。之前回收拆单处理是针对使用记录转换的申请单进行的。详情可以查看 {@link http://yun.dingxiangsoft.com:9090/browse/PYQZYY-173 PYQZYY-173}. + * 对于回收申请单,没有做处理,所以需要做调整。详情查看{@link http://yun.dingxiangsoft.com:9090/browse/GDSFYBJY-28 GDSFYBJY-28} + */ +function recyclingSplitCheck(confirmation, saveAndNew, autoReturnTheBorrowingTousse, print, recyclingAmountConfirm, isConfirm, continueRecycleItemsthatLessThanSendAmount) { + var id = document.getElementById('id').value; + var app_id = document.getElementById('recyclingApplicationId').value; + var departCode = document.getElementById('departCode').value; + if(id || app_id) { + // 这就是已经回收了,当前是打开历史回收记录或者是有申请单的,这里不处理。这里只处理添加回收申请单的情况,不包括打开历史回收记录 + return false; + } + if(sstsConfig.methodOfSplitRecyclingApplication === 1 || sstsConfig.methodOfSplitRecyclingApplication === 2) { + // 启用了拆分回收功能,如果是使用记录转换的申请单,则之前已经具备了处理条件,按现有根据申请单处理的逻辑正常处理。 + // 这里需要针对回收申请单,进一步处理。将当前扫描的器械包和回收项传给后台,然后根据这些信息去拆分。如果返回了拆分信息,则提示用户是否拆分 + $.ajax({ + url: WWWROOT + "/disinfectSystem/recycling-splitting.mhtml", + type: "POST", + timeout: 300000, + dataType: 'json', + contentType: 'application/json', + data: JSON.stringify({ + tousseArray: tousseArray, + recyclingItemArray: getRecyclingItemInfo(), + departCode: departCode + }), + success: function (result) { + // const { success, data = [] } = result; + var success = result.success; + var data = result.data || []; + if (success && data.length > 0) { + splitTousseArray2 = data; + } + submitFormFunction(confirmation, saveAndNew, autoReturnTheBorrowingTousse, print, + recyclingAmountConfirm, isConfirm, continueRecycleItemsthatLessThanSendAmount, false); + } + }) + return true; + } + return false; +} + +/** * 提交回收记录. * @param confirmation 确认回收记录操作 * @param saveAndNew 保存并新建回收记录操作 @@ -6102,17 +6180,21 @@ */ //是否进行清点确认 false 为保存,true 为清点确认 var IntheBoxState = false; -function submitFormFunction(confirmation, saveAndNew, autoReturnTheBorrowingTousse, print, recyclingAmountConfirm, isConfirm, continueRecycleItemsthatLessThanSendAmount) { +function submitFormFunction(confirmation, saveAndNew, autoReturnTheBorrowingTousse, print, recyclingAmountConfirm, isConfirm, continueRecycleItemsthatLessThanSendAmount, recycleSplit) { isConfirm = isConfirm || false; var basketSize = getBasketSizeObject('array'); if (typeof basketSize == 'string') { showResult(basketSize); return; } + // 这里只是处理了使用记录转换的申请单,针对回收申请单并不处理 var jsonStr = getJsonParams(confirmation, isConfirm, basketSize); if (jsonStr == '') { return; } + if(recycleSplit && recyclingSplitCheck(confirmation, saveAndNew, autoReturnTheBorrowingTousse, print, recyclingAmountConfirm, isConfirm, continueRecycleItemsthatLessThanSendAmount)) { + // 已经走回收拆单提示的处理逻辑了,这里可以直接返回 + return; + } var jSONe = JSON.parse(jsonStr); - alertDiv("保存中,请稍候......", '', true); if (IntheBoxState == true) { jSONe.tally = true; } else { @@ -6137,6 +6219,7 @@ if (sstsConfig.hasOwnProperty('recyclingUserDefault') && !sstsConfig.recyclingUserDefault) { recyclingUserDefault = false; } + alertDiv("保存中,请稍候......", '', true); var submitUrl = WWWROOT + "/disinfectSystem/recyclingRecordAction!saveRecyclingRecord.do"; $.ajax({ url: submitUrl, @@ -6269,7 +6352,7 @@ msg = result.message.split(',')[0] + ',' + result.message.split(',')[1] + ',' + '可能导致数据出现误差,是否继续操作?'; } dialogConfirm(msg, function () { - submitFormFunction(confirmation, saveAndNew, autoReturnTheBorrowingTousse, print, recyclingAmountConfirm, isConfirm, '是') + submitFormFunction(confirmation, saveAndNew, autoReturnTheBorrowingTousse, print, recyclingAmountConfirm, isConfirm, '是',false) }); return; } Index: ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplSplitByDepartmentOfInvoiceAndUseRecordUnitTest.java =================================================================== diff -u --- ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplSplitByDepartmentOfInvoiceAndUseRecordUnitTest.java (revision 0) +++ ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplSplitByDepartmentOfInvoiceAndUseRecordUnitTest.java (revision 41537) @@ -0,0 +1,342 @@ +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.recyclingrecord.vo.SplitDepartTousseVo; +import com.forgon.disinfectsystem.recyclingrecord.vo.SplitTousseBarcodeVo; +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 RecyclingRecordManagerImplSplitByDepartmentOfInvoiceAndUseRecordUnitTest { + + @InjectMocks + @Spy + private RecyclingRecordManagerImpl recyclingRecordManager; + + @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: waiteRecyclingTousseInstanceList 为空列表 -> splitDepartTousseVoMap 无变化 + */ + @Test + public void testSplitByDepartmentOfInvoiceAndUseRecord_EmptyList_NoChangeInMap() { + List list = Collections.emptyList(); + List tdIds = Arrays.asList(1L, 2L); + Map amountMap = new HashMap<>(); + String departCodingToCompare = "DEPT_A"; + Map resultMap = mock(Map.class); + + recyclingRecordManager.splitByDepartmentOfInvoiceAndUseRecord(list, tdIds, amountMap, departCodingToCompare, resultMap); + + verifyNoInteractions(resultMap); + } + + /** + * TC2: 所有 ti.getTousseDefinition().getId() 不在 tdIds 中 -> splitDepartTousseVoMap 无变化 + */ + @Test + public void testSplitByDepartmentOfInvoiceAndUseRecord_TdIdsNotContainAnyDefinitionId_NoChangeInMap() { + TousseDefinition td = mock(TousseDefinition.class); + when(td.getId()).thenReturn(3L); // 不在 tdIds 中 + + TousseInstance ti = mock(TousseInstance.class); + when(ti.getTousseDefinition()).thenReturn(td); + when(ti.getLastInvoiceId()).thenReturn(100L); + + List list = Collections.singletonList(ti); + List tdIds = Arrays.asList(1L, 2L); + Map amountMap = new HashMap<>(); + amountMap.put(3L, 1); + String departCodingToCompare = "DEPT_A"; + Map resultMap = mock(Map.class); + + recyclingRecordManager.splitByDepartmentOfInvoiceAndUseRecord(list, tdIds, amountMap, departCodingToCompare, resultMap); + + verifyNoInteractions(resultMap); + } + + /** + * TC3: 所有 waiteRecyclingAmountMap 数量为null、0或负数 -> splitDepartTousseVoMap 无变化 + */ + @Test + public void testSplitByDepartmentOfInvoiceAndUseRecord_ZeroOrNegativeAmount_NoChangeInMap() { + TousseDefinition td = mock(TousseDefinition.class); + when(td.getId()).thenReturn(1L); + + TousseInstance ti = mock(TousseInstance.class); + when(ti.getTousseDefinition()).thenReturn(td); + when(ti.getLastInvoiceId()).thenReturn(100L); + + List list = Collections.singletonList(ti); + List tdIds = Collections.singletonList(1L); + String departCodingToCompare = "DEPT_A"; + Map resultMap = mock(Map.class); + Map amountMap = new HashMap<>(); + + recyclingRecordManager.splitByDepartmentOfInvoiceAndUseRecord(list, tdIds, amountMap, departCodingToCompare, resultMap); + verifyNoInteractions(resultMap); + + amountMap.put(1L, 0); // 0 + recyclingRecordManager.splitByDepartmentOfInvoiceAndUseRecord(list, tdIds, amountMap, departCodingToCompare, resultMap); + verifyNoInteractions(resultMap); + + // 再次测试负数情况 + amountMap.put(1L, -1); + recyclingRecordManager.splitByDepartmentOfInvoiceAndUseRecord(list, tdIds, amountMap, departCodingToCompare, resultMap); + verifyNoInteractions(resultMap); + } + + /** + * TC4: 所有 lastInvoiceId 无效 -> splitDepartTousseVoMap 无变化 + */ + @Test + public void testSplitByDepartmentOfInvoiceAndUseRecord_InvalidLastInvoiceId_NoChangeInMap() { + TousseDefinition td = mock(TousseDefinition.class); + when(td.getId()).thenReturn(1L); + + TousseInstance ti = mock(TousseInstance.class); + when(ti.getTousseDefinition()).thenReturn(td); + when(ti.getLastInvoiceId()).thenReturn(-1L); // 无效 ID + + List list = Collections.singletonList(ti); + List tdIds = Collections.singletonList(1L); + Map amountMap = new HashMap<>(); + amountMap.put(1L, 1); + String departCodingToCompare = "DEPT_A"; + Map resultMap = mock(Map.class); + + recyclingRecordManager.splitByDepartmentOfInvoiceAndUseRecord(list, tdIds, amountMap, departCodingToCompare, resultMap); + + verifyNoInteractions(resultMap); + } + + /** + * TC5: 所有 invoiceMap.get(lastInvoiceId) 返回 null -> splitDepartTousseVoMap 无变化 + */ + @Test + public void testSplitByDepartmentOfInvoiceAndUseRecord_InvoiceNotFound_NoChangeInMap() { + TousseDefinition td = mock(TousseDefinition.class); + when(td.getId()).thenReturn(1L); + + TousseInstance ti = mock(TousseInstance.class); + when(ti.getTousseDefinition()).thenReturn(td); + when(ti.getLastInvoiceId()).thenReturn(100L); + + List list = Collections.singletonList(ti); + List tdIds = Collections.singletonList(1L); + Map amountMap = new HashMap<>(); + amountMap.put(1L, 1); + String departCodingToCompare = "DEPT_A"; + Map resultMap = mock(Map.class); + + // 模拟 loadTousseInstanceInvoiceMap 返回空 map + doReturn(new HashMap<>()).when(recyclingRecordManager).loadTousseInstanceInvoiceMap(anyList()); + + recyclingRecordManager.splitByDepartmentOfInvoiceAndUseRecord(list, tdIds, amountMap, departCodingToCompare, resultMap); + + verifyNoInteractions(resultMap); + } + + /** + * TC6: 所有 departCoding == departCodingToCompare -> splitDepartTousseVoMap 无变化 + */ + @Test + public void testSplitByDepartmentOfInvoiceAndUseRecord_DepartCodingEqualsTarget_NoChangeInMap() { + TousseInstance ti = buildTousseInstance(1L, "TOUSSE_NAME", "BARCODE", "IDCARD_BARCODE", 100L); + + Invoice invoice = buildInvoice(100L, "DEPT_A", "DEPARTMENT_A"); + + List list = Collections.singletonList(ti); + List tdIds = Collections.singletonList(1L); + Map amountMap = new HashMap<>(); + amountMap.put(1L, 1); + String departCodingToCompare = "DEPT_A"; // 匹配目标科室编码 + Map resultMap = mock(Map.class); + + Map invoiceMap = new HashMap<>(); + invoiceMap.put(100L, invoice); + doReturn(invoiceMap).when(recyclingRecordManager).loadTousseInstanceInvoiceMap(anyList()); + + recyclingRecordManager.splitByDepartmentOfInvoiceAndUseRecord(list, tdIds, amountMap, departCodingToCompare, resultMap); + + verifyNoInteractions(resultMap); + } + + /** + * TC7: 正常流程,添加一条记录到 splitDepartTousseVoMap + */ + @Test + public void testSplitByDepartmentOfInvoiceAndUseRecord_NormalFlow_AddOneEntryToMap() { + Long tdId = 1L; + TousseInstance ti = buildTousseInstance(tdId, "TOUSSE_NAME", "BARCODE", "IDCARD_BARCODE", 100L); + Invoice invoice = buildInvoice(100L, "DEPT_B", "DEPARTMENT_B"); + + List list = Collections.singletonList(ti); + List tdIds = Collections.singletonList(1L); + Map amountMap = new HashMap<>(); + amountMap.put(tdId, 1); + String departCodingToCompare = "DEPT_A"; // 不匹配的目标科室编码 + Map resultMap = new HashMap<>(); + + Map invoiceMap = new HashMap<>(); + invoiceMap.put(100L, invoice); + doReturn(invoiceMap).when(recyclingRecordManager).loadTousseInstanceInvoiceMap(anyList()); + + recyclingRecordManager.splitByDepartmentOfInvoiceAndUseRecord(list, tdIds, amountMap, departCodingToCompare, resultMap); + + assertEquals(1, resultMap.size()); + String key = "1_DEPT_B"; + assertTrue(resultMap.containsKey(key)); + SplitDepartTousseVo vo = resultMap.get(key); + verifyDepartTousse(vo, 1, "TOUSSE_NAME", "DEPARTMENT_B", "DEPT_B"); + + List barcodes = vo.getSplitTousseBarcodes(); + assertNotNull(barcodes); + assertEquals(1, barcodes.size()); + verifyBarcode(barcodes.get(0), "BARCODE", "IDCARD_BARCODE"); + + // 验证 amountMap 已经减少 + assertEquals((Integer) 0, amountMap.get(1L)); + } + + /** + * 多科室的多个不同发货单的器械包,数量不等,按科室分组,拆分 + */ + @Test + public void testSplitByDepartmentOfInvoiceAndUseRecord_MultiDepartments_MultiInvoices_MultiTousseInstances() { + TousseInstance ti1 = buildTousseInstance(1L, "TOUSSE_NAME_1", "BARCODE_1", "IDCARD_BARCODE_1", 100L); + TousseInstance ti2 = buildTousseInstance(2L, "TOUSSE_NAME_2", "BARCODE_2", "IDCARD_BARCODE_2", 200L); + TousseInstance ti3 = buildTousseInstance(2L, "TOUSSE_NAME_3", "BARCODE_3", "IDCARD_BARCODE_3", 200L); + TousseInstance ti4 = buildTousseInstance(3L, "TOUSSE_NAME_4", "BARCODE_4", "IDCARD_BARCODE_4", 300L); + TousseInstance ti5 = buildTousseInstance(3L, "TOUSSE_NAME_5", "BARCODE_5", "IDCARD_BARCODE_5", 300L); + TousseInstance ti6 = buildTousseInstance(4L, "TOUSSE_NAME_6", "BARCODE_6", "IDCARD_BARCODE_6", 300L); + TousseInstance ti7 = buildTousseInstance(4L, "TOUSSE_NAME_7", "BARCODE_7", "IDCARD_BARCODE_7", 300L); + TousseInstance ti8 = buildTousseInstance(4L, "TOUSSE_NAME_8", "BARCODE_8", "IDCARD_BARCODE_8", 400L); + TousseInstance ti9 = buildTousseInstance(4L, "TOUSSE_NAME_9", "BARCODE_9", "IDCARD_BARCODE_9", 400L); + TousseInstance ti10 = buildTousseInstance(4L, "TOUSSE_NAME_10", "BARCODE_10", "IDCARD_BARCODE_10", 400L); + Invoice invoice1 = buildInvoice(100L, "DEPT_A", "DEPARTMENT_A"); + Invoice invoice2 = buildInvoice(200L, "DEPT_B", "DEPARTMENT_B"); + Invoice invoice3 = buildInvoice(300L, "DEPT_B", "DEPARTMENT_B"); + Invoice invoice4 = buildInvoice(400L, "DEPT_C", "DEPARTMENT_C"); + + List list = Arrays.asList(ti1, ti2, ti3, ti4, ti5, ti6, ti7, ti8, ti9, ti10); + List tdIds = Arrays.asList(1L, 2L, 3L, 4L); + Map amountMap = new HashMap<>(); + amountMap.put(1L, 1); + amountMap.put(2L, 2); + amountMap.put(3L, 3); + amountMap.put(4L, 5); + String departCodingToCompare = "DEPT_A"; + Map resultMap = new HashMap<>(); + Map invoiceMap = new HashMap<>(); + invoiceMap.put(100L, invoice1); + invoiceMap.put(200L, invoice2); + invoiceMap.put(300L, invoice3); + invoiceMap.put(400L, invoice4); + doReturn(invoiceMap).when(recyclingRecordManager).loadTousseInstanceInvoiceMap(anyList()); + recyclingRecordManager.splitByDepartmentOfInvoiceAndUseRecord(list, tdIds, amountMap, departCodingToCompare, resultMap); + assertEquals(4, resultMap.size()); + String key1 = "1_DEPT_A"; + assertFalse(resultMap.containsKey(key1)); + String key2 = "2_DEPT_B"; + assertTrue(resultMap.containsKey(key2)); + SplitDepartTousseVo vo2 = resultMap.get(key2); + verifyDepartTousse(vo2, 2, "TOUSSE_NAME_2", "DEPARTMENT_B", "DEPT_B"); + List barcodes2 = vo2.getSplitTousseBarcodes(); + assertNotNull(barcodes2); + assertEquals(2, barcodes2.size()); + verifyBarcode(barcodes2.get(0), "BARCODE_2", "IDCARD_BARCODE_2"); + verifyBarcode(barcodes2.get(1), "BARCODE_3", "IDCARD_BARCODE_3"); + String key3 = "3_DEPT_B"; + assertTrue(resultMap.containsKey(key3)); + SplitDepartTousseVo vo3 = resultMap.get(key3); + verifyDepartTousse(vo3, 2, "TOUSSE_NAME_4", "DEPARTMENT_B", "DEPT_B"); + List barcodes3 = vo3.getSplitTousseBarcodes(); + assertNotNull(barcodes3); + assertEquals(2, barcodes3.size()); + verifyBarcode(barcodes3.get(0), "BARCODE_4", "IDCARD_BARCODE_4"); + verifyBarcode(barcodes3.get(1), "BARCODE_5", "IDCARD_BARCODE_5"); + String key4 = "4_DEPT_B"; + assertTrue(resultMap.containsKey(key4)); + SplitDepartTousseVo vo4 = resultMap.get(key4); + verifyDepartTousse(vo4, 2, "TOUSSE_NAME_6", "DEPARTMENT_B", "DEPT_B"); + List barcodes4 = vo4.getSplitTousseBarcodes(); + assertNotNull(barcodes4); + assertEquals(2, barcodes4.size()); + verifyBarcode(barcodes4.get(0), "BARCODE_6", "IDCARD_BARCODE_6"); + verifyBarcode(barcodes4.get(1), "BARCODE_7", "IDCARD_BARCODE_7"); + String key5 = "4_DEPT_C"; + assertTrue(resultMap.containsKey(key5)); + SplitDepartTousseVo vo5 = resultMap.get(key5); + verifyDepartTousse(vo5, 3, "TOUSSE_NAME_8", "DEPARTMENT_C", "DEPT_C"); + List barcodes5 = vo5.getSplitTousseBarcodes(); + assertNotNull(barcodes5); + assertEquals(3, barcodes5.size()); + verifyBarcode(barcodes5.get(0), "BARCODE_8", "IDCARD_BARCODE_8"); + verifyBarcode(barcodes5.get(1), "BARCODE_9", "IDCARD_BARCODE_9"); + verifyBarcode(barcodes5.get(2), "BARCODE_10", "IDCARD_BARCODE_10"); + assertEquals((Integer) 0, amountMap.get(4L)); + assertEquals((Integer) 1, amountMap.get(3L)); + assertEquals((Integer) 0, amountMap.get(2L)); + assertEquals((Integer) 1, amountMap.get(1L)); + } + + private static Invoice buildInvoice(Long invoiceId, String departCode, String department) { + Invoice invoice = mock(Invoice.class); + when(invoice.getId()).thenReturn(invoiceId); + when(invoice.getDepartCoding()).thenReturn(departCode); // 不同科室编码 + when(invoice.getDepart()).thenReturn(department); + return invoice; + } + + private static TousseInstance buildTousseInstance(Long tdId, String tousseName, String tousseBarcode, String idCardBarcode, Long invoiceId) { + TousseDefinition td = mock(TousseDefinition.class); + when(td.getId()).thenReturn(tdId); + when(td.getName()).thenReturn(tousseName); + + TousseInstance ti = mock(TousseInstance.class); + when(ti.getTousseDefinition()).thenReturn(td); + when(ti.getLastInvoiceId()).thenReturn(invoiceId); + when(ti.getBarcode()).thenReturn(tousseBarcode); + when(ti.getIdCardInstanceBarcode()).thenReturn(idCardBarcode); + return ti; + } + + private static void verifyDepartTousse(SplitDepartTousseVo vo, Integer amount, String tousseName, String depart, String departCoding) { + assertNotNull(vo); + assertEquals(amount, vo.getAmount()); + assertEquals(tousseName, vo.getTousseName()); + assertEquals(depart, vo.getDepart()); + assertEquals(departCoding, vo.getDepartCoding()); + } + + private static void verifyBarcode(SplitTousseBarcodeVo barcode, String tousseBarcode, String idCardInstanceBarcode) { + assertEquals(tousseBarcode, barcode.getTousseBarcode()); + assertEquals(idCardInstanceBarcode, barcode.getIdCardInstanceBarcode()); + } +} Index: ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/controller/RecyclingSplitController.java =================================================================== diff -u --- ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/controller/RecyclingSplitController.java (revision 0) +++ ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/controller/RecyclingSplitController.java (revision 41537) @@ -0,0 +1,39 @@ +package com.forgon.disinfectsystem.recyclingrecord.controller; + +import com.forgon.disinfectsystem.recyclingrecord.param.RecyclingSplitLoadParam; +import com.forgon.disinfectsystem.recyclingrecord.service.RecyclingRecordManager; +import com.forgon.disinfectsystem.recyclingrecord.vo.SplitDepartTousseVo; +import com.forgon.response.ListDataResponse; +import com.forgon.tools.json.JacksonUtil; +import org.apache.commons.collections.MapUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * 回收拆单业务处理器 + */ +@RestController +@RequestMapping(value = "/disinfectSystem/recycling-splitting", produces = "application/json;charset=UTF-8") +public class RecyclingSplitController { + @Autowired + private RecyclingRecordManager recyclingRecordManager; + @RequestMapping(method = RequestMethod.POST) + public ListDataResponse load(@RequestBody String body) { + RecyclingSplitLoadParam param = JacksonUtil.parseObject(body, RecyclingSplitLoadParam.class); + List list = Collections.emptyList(); + // 回收申请单 + Map map = recyclingRecordManager.loadRecyclingTousseSplitByOrgUnit(param); + if(MapUtils.isNotEmpty(map)){ + list = new ArrayList<>(map.values()); + } + return new ListDataResponse<>(list); + } +} Index: ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/param/RecyclingSplitLoadParam.java =================================================================== diff -u --- ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/param/RecyclingSplitLoadParam.java (revision 0) +++ ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/param/RecyclingSplitLoadParam.java (revision 41537) @@ -0,0 +1,41 @@ +package com.forgon.disinfectsystem.recyclingrecord.param; + +import com.forgon.disinfectsystem.recyclingrecord.vo.RecyclingBasketItemVo; + +import java.util.List; + +public class RecyclingSplitLoadParam { + private List tousseArray; + /** + * 左侧器械包列表 + */ + private List recyclingItemArray; + /** + * 科室编码 + */ + private String departCode; + + public List getTousseArray() { + return tousseArray; + } + + public void setTousseArray(List tousseArray) { + this.tousseArray = tousseArray; + } + + public List getRecyclingItemArray() { + return recyclingItemArray; + } + + public void setRecyclingItemArray(List recyclingItemArray) { + this.recyclingItemArray = recyclingItemArray; + } + + public String getDepartCode() { + return departCode; + } + + public void setDepartCode(String departCode) { + this.departCode = departCode; + } +} Index: ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/param/RecyclingItemParam.java =================================================================== diff -u --- ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/param/RecyclingItemParam.java (revision 0) +++ ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/param/RecyclingItemParam.java (revision 41537) @@ -0,0 +1,138 @@ +package com.forgon.disinfectsystem.recyclingrecord.param; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.forgon.disinfectsystem.model.ToussePackage; + +import java.util.Objects; + +/** + * 回收项参数。回收界面左侧的列表数据。主要有器械包名称、申请数量、回收数量、装载数量等 + */ +public class RecyclingItemParam implements ToussePackage { + private Long tousseDefinitionID; + private String tousseName; + private Integer applicationAmount; + private Integer recycleAmount; + private Integer loadedAmount; + private String tousseRemark; + private String errorRemark; + private String damageRemark; + private Integer urgentLevel; + private String errorDamageQmKey; + private String tousseOrIDCardInstanceBarcodes; + + public Long getTousseDefinitionID() { + return tousseDefinitionID; + } + + public void setTousseDefinitionID(Long tousseDefinitionID) { + this.tousseDefinitionID = tousseDefinitionID; + } + + @JsonIgnore + @Override + public Long getTousseDefinitionId() { + return tousseDefinitionID; + } + + @Override + public String getTousseName() { + return tousseName; + } + + public void setTousseName(String tousseName) { + this.tousseName = tousseName; + } + + public Integer getApplicationAmount() { + return applicationAmount; + } + + public void setApplicationAmount(Integer applicationAmount) { + this.applicationAmount = applicationAmount; + } + + public Integer getRecycleAmount() { + return recycleAmount; + } + + public void setRecycleAmount(Integer recycleAmount) { + this.recycleAmount = recycleAmount; + } + + public Integer getLoadedAmount() { + return loadedAmount; + } + + public void setLoadedAmount(Integer loadedAmount) { + this.loadedAmount = loadedAmount; + } + + public String getTousseRemark() { + return tousseRemark; + } + + public void setTousseRemark(String tousseRemark) { + this.tousseRemark = tousseRemark; + } + + public String getErrorRemark() { + return errorRemark; + } + + public void setErrorRemark(String errorRemark) { + this.errorRemark = errorRemark; + } + + public String getDamageRemark() { + return damageRemark; + } + + public void setDamageRemark(String damageRemark) { + this.damageRemark = damageRemark; + } + + public Integer getUrgentLevel() { + return urgentLevel; + } + + public void setUrgentLevel(Integer urgentLevel) { + this.urgentLevel = urgentLevel; + } + + public String getErrorDamageQmKey() { + return errorDamageQmKey; + } + + public void setErrorDamageQmKey(String errorDamageQmKey) { + this.errorDamageQmKey = errorDamageQmKey; + } + + public String getTousseOrIDCardInstanceBarcodes() { + return tousseOrIDCardInstanceBarcodes; + } + + public void setTousseOrIDCardInstanceBarcodes(String tousseOrIDCardInstanceBarcodes) { + this.tousseOrIDCardInstanceBarcodes = tousseOrIDCardInstanceBarcodes; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (!(object instanceof RecyclingItemParam)) return false; + RecyclingItemParam that = (RecyclingItemParam) object; + return Objects.equals(tousseDefinitionID, that.tousseDefinitionID) && Objects.equals(tousseName, that.tousseName) + && Objects.equals(applicationAmount, that.applicationAmount) + && Objects.equals(recycleAmount, that.recycleAmount) && Objects.equals(loadedAmount, that.loadedAmount) + && Objects.equals(tousseRemark, that.tousseRemark) && Objects.equals(errorRemark, that.errorRemark) + && Objects.equals(damageRemark, that.damageRemark) && Objects.equals(urgentLevel, that.urgentLevel) + && Objects.equals(errorDamageQmKey, that.errorDamageQmKey) + && Objects.equals(tousseOrIDCardInstanceBarcodes, that.tousseOrIDCardInstanceBarcodes); + } + + @Override + public int hashCode() { + return Objects.hash(tousseDefinitionID, tousseName, applicationAmount, recycleAmount, loadedAmount, tousseRemark, + errorRemark, damageRemark, urgentLevel, errorDamageQmKey, tousseOrIDCardInstanceBarcodes); + } +} Index: ssts-basedata/src/main/java/com/forgon/disinfectsystem/model/ToussePackage.java =================================================================== diff -u --- ssts-basedata/src/main/java/com/forgon/disinfectsystem/model/ToussePackage.java (revision 0) +++ ssts-basedata/src/main/java/com/forgon/disinfectsystem/model/ToussePackage.java (revision 41537) @@ -0,0 +1,17 @@ +package com.forgon.disinfectsystem.model; + +/** + * 器械包接口。有些对象都包含器械包的信息,比如包定义id,器械包名称。虽然是不同对象,但是在一些基于器械包基础信息的算法中,可以互相替换 + */ +public interface ToussePackage { + /** + * 获取器械包定义id + * @return 器械包定义id + */ + Long getTousseDefinitionId(); + /** + * 获取器械包名称 + * @return 器械包名称 + */ + String getTousseName(); +} Index: ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadRecyclingTousseSplitByOrgUnitTest.java =================================================================== diff -u --- ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadRecyclingTousseSplitByOrgUnitTest.java (revision 0) +++ ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadRecyclingTousseSplitByOrgUnitTest.java (revision 41537) @@ -0,0 +1,213 @@ +package com.forgon.disinfectsystem.recyclingrecord.service; + +import com.forgon.disinfectsystem.entity.basedatamanager.toussedefinition.TousseInstance; +import com.forgon.disinfectsystem.entity.idcardinstance.IDCardInstance; +import com.forgon.disinfectsystem.idcardinstance.service.IDCardInstanceManager; +import com.forgon.disinfectsystem.recyclingrecord.param.RecyclingItemParam; +import com.forgon.disinfectsystem.recyclingrecord.param.RecyclingSplitLoadParam; +import com.forgon.disinfectsystem.recyclingrecord.vo.RecyclingBasketItemVo; +import com.forgon.disinfectsystem.recyclingrecord.vo.SplitDepartTousseVo; +import com.forgon.disinfectsystem.tousse.toussedefinition.service.TousseInstanceManager; +import com.forgon.tools.util.ConfigUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.*; + +import java.util.*; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; + +public class RecyclingRecordManagerImplLoadRecyclingTousseSplitByOrgUnitTest { + + @InjectMocks + @Spy + private RecyclingRecordManagerImpl recyclingRecordManager; + + @Mock + private TousseInstanceManager tousseInstanceManager; + @Mock + private IDCardInstanceManager idCardInstanceManager; + + private MockedStatic configUtilsMock; + private AutoCloseable autoCloseable; + @Before + public void setUp() { + autoCloseable = MockitoAnnotations.openMocks(this); + configUtilsMock = mockStatic(ConfigUtils.class); + } + + @After + public void tearDown() throws Exception { + autoCloseable.close(); + configUtilsMock.close(); + } + + @Test + public void testLoadRecyclingTousseSplitByOrgUnit_whenMethodIsZero_shouldReturnEmptyMap() { + // Given + RecyclingSplitLoadParam param = new RecyclingSplitLoadParam(); + List tousseArray = new ArrayList<>(); + tousseArray.add(new RecyclingBasketItemVo()); + param.setTousseArray(tousseArray); + + List recyclingItemArray = new ArrayList<>(); + recyclingItemArray.add(new RecyclingItemParam()); + param.setRecyclingItemArray(recyclingItemArray); + + configUtilsMock.when(() -> ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)) + .thenReturn(0); + + // When + Map result = recyclingRecordManager.loadRecyclingTousseSplitByOrgUnit(param); + + // Then + assertTrue(result.isEmpty()); + } + + @Test + public void testLoadRecyclingTousseSplitByOrgUnit_whenTousseArrayIsEmpty_shouldReturnEmptyMap() { + // Given + RecyclingSplitLoadParam param = new RecyclingSplitLoadParam(); + param.setTousseArray(Collections.emptyList()); + param.setRecyclingItemArray(Collections.singletonList(new RecyclingItemParam())); + + configUtilsMock.when(() -> ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)) + .thenReturn(1); + + // When + Map result = recyclingRecordManager.loadRecyclingTousseSplitByOrgUnit(param); + + // Then + assertTrue(result.isEmpty()); + } + + @Test + public void testLoadRecyclingTousseSplitByOrgUnit_whenRecyclingItemArrayIsEmpty_shouldReturnEmptyMap() { + // Given + RecyclingSplitLoadParam param = new RecyclingSplitLoadParam(); + param.setTousseArray(Collections.singletonList(new RecyclingBasketItemVo())); + param.setRecyclingItemArray(Collections.emptyList()); + + configUtilsMock.when(() -> ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)) + .thenReturn(1); + + // When + Map result = recyclingRecordManager.loadRecyclingTousseSplitByOrgUnit(param); + + // Then + assertTrue(result.isEmpty()); + } + + @Test + public void testLoadRecyclingTousseSplitByOrgUnit_whenMethodIsOne_shouldCallSplitByDepartment() { + // Given + RecyclingSplitLoadParam param = createValidParam(); + configUtilsMock.when(() -> ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)) + .thenReturn(1); + + List tousseInstances = createTousseInstances(); + TousseInstance ti = createTousseInstance(23L, "TI010", "IC010"); + when(tousseInstanceManager.getCollection(eq("barcode"),anySet())).thenReturn(tousseInstances); + when(tousseInstanceManager.getCollection(new HashSet<>(Collections.singletonList(23L)))).thenReturn(Collections.singletonList(ti)); + IDCardInstance idCardInstance = createIDCardInstance(11L, "IC010", 23L); + when(idCardInstanceManager.getCollection(eq("barcode"), anySet())).thenReturn(Collections.singletonList(idCardInstance)); + doNothing().when(recyclingRecordManager).splitByDepartmentOfInvoiceAndUseRecord(anyList(), anyList(), anyMap(), anyString(), anyMap()); + + // When + Map result = recyclingRecordManager.loadRecyclingTousseSplitByOrgUnit(param); + + // Then + // 验证调用了tousseInstanceManager.getCollection方法 + verify(tousseInstanceManager).getCollection("barcode", new HashSet<>(Arrays.asList("TI001", "TI002"))); + verify(tousseInstanceManager).getCollection(new HashSet<>(Collections.singletonList(23L))); + verify(idCardInstanceManager).getCollection("barcode", new HashSet<>(Collections.singletonList("IC010"))); + List expect = new ArrayList<>(tousseInstances); + expect.add(ti); + HashMap expectAmountMap = new HashMap<>(); + expectAmountMap.put(1L, 5); + verify(recyclingRecordManager).splitByDepartmentOfInvoiceAndUseRecord(expect,Collections.singletonList(1L), expectAmountMap, param.getDepartCode(), result); + assertNotNull(result); + } + + @Test + public void testLoadRecyclingTousseSplitByOrgUnit_whenMethodIsTwo_shouldCallSplitByAsset() { + // Given + RecyclingSplitLoadParam param = createValidParam(); + configUtilsMock.when(() -> ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)) + .thenReturn(2); + + List tousseInstances = createTousseInstances(); + when(tousseInstanceManager.getCollection(eq("barcode"), anySet())).thenReturn(tousseInstances); + doNothing().when(recyclingRecordManager).splitByAssetOfTousseDefinition(anyList(), anyList(), anyList(), anyString(), anyMap(),anyMap()); + + // When + Map result = recyclingRecordManager.loadRecyclingTousseSplitByOrgUnit(param); + + // Then + // 验证调用了tousseInstanceManager.getCollection方法 + verify(tousseInstanceManager).getCollection("barcode", new HashSet<>(Arrays.asList("TI001", "TI002"))); + Map expectAmountMap = new HashMap<>(); + expectAmountMap.put(1L, 5); + verify(recyclingRecordManager).splitByAssetOfTousseDefinition(param.getRecyclingItemArray(), tousseInstances,Collections.singletonList(1L), param.getDepartCode(), expectAmountMap, result); + assertNotNull(result); + } + + // 创建有效的测试参数 + private RecyclingSplitLoadParam createValidParam() { + RecyclingSplitLoadParam param = new RecyclingSplitLoadParam(); + + // 设置tousseArray + List tousseArray = new ArrayList<>(); + RecyclingBasketItemVo tousseParam = new RecyclingBasketItemVo(); + tousseParam.setTousseInstanceBarcode("TI001"); + tousseArray.add(tousseParam); + tousseParam = new RecyclingBasketItemVo(); + tousseParam.setTousseInstanceBarcode("TI002"); + tousseArray.add(tousseParam); + tousseParam = new RecyclingBasketItemVo(); + tousseParam.setIdCardBarcode("IC010"); + tousseArray.add(tousseParam); + param.setTousseArray(tousseArray); + + // 设置recyclingItemArray + List recyclingItemArray = new ArrayList<>(); + RecyclingItemParam itemParam = new RecyclingItemParam(); + itemParam.setTousseDefinitionID(1L); + itemParam.setRecycleAmount(5); + recyclingItemArray.add(itemParam); + param.setRecyclingItemArray(recyclingItemArray); + + param.setDepartCode("DEPT001"); + + return param; + } + + // 创建TousseInstance测试数据 + private List createTousseInstances() { + List instances = new ArrayList<>(); + TousseInstance instance = createTousseInstance(1L, "TI001", "IC001"); + instances.add(instance); + instance = createTousseInstance(2L, "TI002", "IC002"); + instances.add(instance); + return instances; + } + + private TousseInstance createTousseInstance(Long id, String barcode, String idCardBarcode) { + TousseInstance instance = new TousseInstance(); + instance.setId(id); + instance.setBarcode(barcode); + instance.setIdCardInstanceBarcode(idCardBarcode); + return instance; + } + + private IDCardInstance createIDCardInstance(Long id, String barcode, Long lastTousseInstanceId) { + IDCardInstance instance = new IDCardInstance(); + instance.setId(id); + instance.setBarcode(barcode); + instance.setLastTousseInstanceId(lastTousseInstanceId); + return instance; + } +} Index: ssts-basedata/src/main/java/com/forgon/disinfectsystem/model/DepartmentAware.java =================================================================== diff -u --- ssts-basedata/src/main/java/com/forgon/disinfectsystem/model/DepartmentAware.java (revision 0) +++ ssts-basedata/src/main/java/com/forgon/disinfectsystem/model/DepartmentAware.java (revision 41537) @@ -0,0 +1,18 @@ +package com.forgon.disinfectsystem.model; + +/** + * 拥有科室信息的对象。在一些需要科室名称和科室编码的算法中,可以互相替换 + */ +public interface DepartmentAware { + /** + * 科室名称 + * @return 名称 + */ + String getDepart(); + + /** + * 科室编码 + * @return 编码 + */ + String getDepartCode(); +} Index: ssts-basedata/src/main/java/com/forgon/disinfectsystem/entity/tousseitem/TousseItem.java =================================================================== diff -u -r40422 -r41537 --- ssts-basedata/src/main/java/com/forgon/disinfectsystem/entity/tousseitem/TousseItem.java (.../TousseItem.java) (revision 40422) +++ ssts-basedata/src/main/java/com/forgon/disinfectsystem/entity/tousseitem/TousseItem.java (.../TousseItem.java) (revision 41537) @@ -12,6 +12,7 @@ import javax.persistence.Table; import javax.persistence.Transient; +import com.forgon.disinfectsystem.model.ToussePackage; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.hibernate.annotations.Cache; @@ -53,7 +54,7 @@ ,@Index(columnList = "tousseType,amount,tousseDefinitionId,recyclingApplication_ID", name = "TousseItem_tatr_index") }) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -public class TousseItem implements IDAble{ +public class TousseItem implements IDAble, ToussePackage { public final static String DIPOSABLE_YES = "是"; public final static String DIPOSABLE_NO = "否"; @@ -517,6 +518,7 @@ this.id = id; } + @Override public String getTousseName() { return tousseName; } @@ -736,6 +738,7 @@ } return false; } + @Override public Long getTousseDefinitionId() { return tousseDefinitionId; } Index: ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/param/SplitSaveRecyclingTousseParam.java =================================================================== diff -u --- ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/param/SplitSaveRecyclingTousseParam.java (revision 0) +++ ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/param/SplitSaveRecyclingTousseParam.java (revision 41537) @@ -0,0 +1,88 @@ +package com.forgon.disinfectsystem.recyclingrecord.param; + +import java.util.List; + +/** + * 拆单回收保存时拆分的器械包信息参数。对应前端中splitTousseJson数组中的每项记录 + */ +public class SplitSaveRecyclingTousseParam { + private Long tousseDefinitionID; + private String tousseName; + private Integer amount; + private String depart; + private String departCoding; + private String itemType; + private String tousseNameForMaterial; + /** + * 拆分的器械包条码,包括包实例条码和标识牌条码。这个不是前端传入的,是后台自己分的。 + */ + private List splitTousseBarcodes; + + public Long getTousseDefinitionID() { + return tousseDefinitionID; + } + + public void setTousseDefinitionID(Long tousseDefinitionID) { + this.tousseDefinitionID = tousseDefinitionID; + } + + public String getTousseName() { + return tousseName; + } + + public void setTousseName(String tousseName) { + this.tousseName = tousseName; + } + + public Integer getAmount() { + return amount; + } + + public void setAmount(Integer amount) { + this.amount = amount; + } + + public String getDepart() { + return depart; + } + + public void setDepart(String depart) { + this.depart = depart; + } + + public String getDepartCoding() { + return departCoding; + } + + public void setDepartCoding(String departCoding) { + this.departCoding = departCoding; + } + + public String getItemType() { + return itemType; + } + + public void setItemType(String itemType) { + this.itemType = itemType; + } + + public boolean itemTypeMaterial() { + return "材料".equals(itemType); + } + + public String getTousseNameForMaterial() { + return tousseNameForMaterial; + } + + public void setTousseNameForMaterial(String tousseNameForMaterial) { + this.tousseNameForMaterial = tousseNameForMaterial; + } + + public List getSplitTousseBarcodes() { + return splitTousseBarcodes; + } + + public void setSplitTousseBarcodes(List splitTousseBarcodes) { + this.splitTousseBarcodes = splitTousseBarcodes; + } +} Index: ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/CardinalNumLimitValidatorImplValidateTest.java =================================================================== diff -u --- ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/CardinalNumLimitValidatorImplValidateTest.java (revision 0) +++ ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/CardinalNumLimitValidatorImplValidateTest.java (revision 41537) @@ -0,0 +1,176 @@ +package com.forgon.disinfectsystem.recyclingrecord.service; + +import com.forgon.disinfectsystem.entity.basedatamanager.toussedefinition.TousseInstance; +import com.forgon.disinfectsystem.model.DepartmentAware; +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.springframework.test.util.ReflectionTestUtils; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.when; + +/** + * 基数限制验证器实现类单元测试 + */ +public class CardinalNumLimitValidatorImplValidateTest { + + @InjectMocks + private CardinalNumLimitValidator.CardinalNumLimitValidatorImpl validator; + private final Map> tousseNameToAmountMap = new HashMap<>(); + + @Mock + private DepartmentAware departmentAware; + private AutoCloseable autoCloseable; + @Before + public void setUp() { + autoCloseable = MockitoAnnotations.openMocks(this); + ReflectionTestUtils.setField(validator, "tousseNameToAmountMap", tousseNameToAmountMap); + } + + @After + public void tearDown() throws Exception { + if (autoCloseable != null) { + autoCloseable.close(); + } + } + + /** + * 测试用例:tousseName在映射中不存在 + */ + @Test + public void testValidate_TousseNameNotExists_ShouldPass() { + // Given + String tousseName = "testTousse"; + Integer amount = 10; + + // When & Then + validator.validate(tousseName, amount); + } + + /** + * 测试用例:数量映射为空 + */ + @Test + public void testValidate_AmountMapEmpty_ShouldPass() { + // Given + String tousseName = "testTousse"; + Integer amount = 10; + Map emptyAmountMap = new HashMap<>(); + tousseNameToAmountMap.put(tousseName, emptyAmountMap); + + // When & Then + validator.validate(tousseName, amount); + } + + /** + * 测试用例:基数不存在 + */ + @Test + public void testValidate_CardinalNumNotExists_ShouldPass() { + // Given + String tousseName = "testTousse"; + Integer amount = 10; + Map amountMap = new HashMap<>(); + amountMap.put("任意内容", 3); + tousseNameToAmountMap.put(tousseName, amountMap); + + // When & Then + validator.validate(tousseName, amount); + } + + /** + * 测试用例:基数为0 + */ + @Test + public void testValidate_CardinalNumIsZero_ShouldPass() { + // Given + String tousseName = "testTousse"; + Integer amount = 10; + Map amountMap = new HashMap<>(); + amountMap.put(TousseInstance.STATUS_CARDINALNUM, 0); + tousseNameToAmountMap.put(tousseName, amountMap); + + // When & Then + validator.validate(tousseName, amount); + } + + /** + * 测试用例:已使用数量大于等于基数 + */ + @Test + public void testValidate_UsedAmountExceedsCardinalNum_ShouldThrowException() { + // Given + String tousseName = "testTousse"; + Integer amount = 10; + String department = "TestDept"; + Map amountMap = new HashMap<>(); + amountMap.put(TousseInstance.STATUS_CARDINALNUM, 5); // 基数为5 + amountMap.put(TousseInstance.STATUS_USEDCARDINALNUM, 5); // 已使用5个 + tousseNameToAmountMap.put(tousseName, amountMap); + + when(departmentAware.getDepart()).thenReturn(department); + + // When & Then + RuntimeException exception = assertThrows(RuntimeException.class, + () -> validator.validate(tousseName, amount)); + + assertEquals(department + "的" + tousseName + "的回收数量超过了基数上限,最大可回收数量为0", + exception.getMessage()); + } + + /** + * 测试用例:回收数量超过剩余基数 + */ + @Test + public void testValidate_RecoveryAmountExceedsSurplus_ShouldThrowException() { + // Given + String tousseName = "testTousse"; + Integer amount = 8; // 想要回收8个 + String department = "TestDept"; + Integer cardinalNum = 10; // 基数10个 + Integer usedAmount = 5; // 已使用5个 + int surplusAmount = cardinalNum - usedAmount; // 剩余5个 + + Map amountMap = new HashMap<>(); + amountMap.put(TousseInstance.STATUS_CARDINALNUM, cardinalNum); + amountMap.put(TousseInstance.STATUS_USEDCARDINALNUM, usedAmount); + tousseNameToAmountMap.put(tousseName, amountMap); + + when(departmentAware.getDepart()).thenReturn(department); + + // When & Then + RuntimeException exception = assertThrows(RuntimeException.class, + () -> validator.validate(tousseName, amount)); + + assertEquals(department + "的" + tousseName + "的回收数量超过了基数上限,最大可回收数量为" + surplusAmount, + exception.getMessage()); + } + + /** + * 测试用例:正常情况-回收数量在限制内 + */ + @Test + public void testValidate_NormalCaseWithinLimit_ShouldPass() { + // Given + String tousseName = "testTousse"; + Integer amount = 3; // 想要回收3个 + Integer cardinalNum = 10; // 基数10个 + Integer usedAmount = 5; // 已使用5个 + + Map amountMap = new HashMap<>(); + amountMap.put(TousseInstance.STATUS_CARDINALNUM, cardinalNum); + amountMap.put(TousseInstance.STATUS_USEDCARDINALNUM, usedAmount); + tousseNameToAmountMap.put(tousseName, amountMap); + + // When & Then + validator.validate(tousseName, amount); + } +} Index: ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplSplitByAssetOfTousseDefinitionUnitTest.java =================================================================== diff -u --- ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplSplitByAssetOfTousseDefinitionUnitTest.java (revision 0) +++ ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplSplitByAssetOfTousseDefinitionUnitTest.java (revision 41537) @@ -0,0 +1,252 @@ +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.model.ToussePackage; +import com.forgon.disinfectsystem.recyclingrecord.vo.SplitDepartTousseVo; +import com.forgon.disinfectsystem.recyclingrecord.vo.SplitTousseBarcodeVo; +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.*; + +import java.util.*; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class RecyclingRecordManagerImplSplitByAssetOfTousseDefinitionUnitTest { + + @InjectMocks + @Spy + private RecyclingRecordManagerImpl recyclingRecordManager; + + @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: applicationItems 为 null -> 不执行任何操作 + */ + @Test + public void testSplitByAssetOfTousseDefinition_ApplicationItemsIsNull_NoOperation() { + // 准备参数 + List waiteRecyclingTousseInstanceList = new ArrayList<>(); + List tdIds = new ArrayList<>(); + String departCodingToCompare = "DEPT_A"; + Map waiteRecyclingAmountMap = new HashMap<>(); + Map splitDepartTousseVoMap = new HashMap<>(); + + // 调用方法 + recyclingRecordManager.splitByAssetOfTousseDefinition( + null, + waiteRecyclingTousseInstanceList, + tdIds, + departCodingToCompare, + waiteRecyclingAmountMap, + splitDepartTousseVoMap + ); + + // 断言没有变化 + assertTrue(splitDepartTousseVoMap.isEmpty()); + } + + /** + * TC2: applicationItems 为空列表 -> 不执行任何操作 + */ + @Test + public void testSplitByAssetOfTousseDefinition_ApplicationItemsIsEmpty_NoOperation() { + List waiteRecyclingTousseInstanceList = new ArrayList<>(); + List tdIds = new ArrayList<>(); + String departCodingToCompare = "DEPT_A"; + Map waiteRecyclingAmountMap = new HashMap<>(); + Map splitDepartTousseVoMap = new HashMap<>(); + + recyclingRecordManager.splitByAssetOfTousseDefinition( + Collections.emptyList(), + waiteRecyclingTousseInstanceList, + tdIds, + departCodingToCompare, + waiteRecyclingAmountMap, + splitDepartTousseVoMap + ); + + assertTrue(splitDepartTousseVoMap.isEmpty()); + } + + /** + * TC3: 包定义不存在于数据库查询结果中 -> 忽略该项 + */ + @Test + public void testSplitByAssetOfTousseDefinition_TousseDefinitionNotFound_IgnoreItem() { + // 模拟 applicationItems + ToussePackage item = mock(ToussePackage.class); + when(item.getTousseDefinitionId()).thenReturn(1L); + when(item.getTousseName()).thenReturn("Test Package"); + + List applicationItems = Arrays.asList(item); + + // 模拟数据库未查出对应定义 + when(objectDao.findByIds(anyString(), anyList())).thenReturn(Collections.emptyList()); + + List waiteRecyclingTousseInstanceList = new ArrayList<>(); + List tdIds = Arrays.asList(1L); + String departCodingToCompare = "DEPT_B"; + Map waiteRecyclingAmountMap = new HashMap<>(); + waiteRecyclingAmountMap.put(1L, 5); + Map splitDepartTousseVoMap = new HashMap<>(); + + recyclingRecordManager.splitByAssetOfTousseDefinition( + applicationItems, + waiteRecyclingTousseInstanceList, + tdIds, + departCodingToCompare, + waiteRecyclingAmountMap, + splitDepartTousseVoMap + ); + + assertTrue(splitDepartTousseVoMap.isEmpty()); + } + + /** + * TC4: 资产归属编码相同 -> 不处理 + */ + @Test + public void testSplitByAssetOfTousseDefinition_AssetsBelongSameDepartment_NotProcessed() { + ToussePackage item = mock(ToussePackage.class); + when(item.getTousseDefinitionId()).thenReturn(1L); + when(item.getTousseName()).thenReturn("Test Package"); + + TousseDefinition td = mock(TousseDefinition.class); + when(td.getId()).thenReturn(1L); + when(td.getName()).thenReturn("Test Definition"); + when(td.getAssetsBelongCode()).thenReturn("DEPT_A"); // same as compare code + + when(objectDao.findByIds(anyString(), anyList())).thenReturn(Arrays.asList(td)); + + List waiteRecyclingTousseInstanceList = new ArrayList<>(); + List tdIds = Arrays.asList(1L); + String departCodingToCompare = "DEPT_A"; // same + Map waiteRecyclingAmountMap = new HashMap<>(); + waiteRecyclingAmountMap.put(1L, 5); + Map splitDepartTousseVoMap = new HashMap<>(); + + recyclingRecordManager.splitByAssetOfTousseDefinition( + Arrays.asList(item), + waiteRecyclingTousseInstanceList, + tdIds, + departCodingToCompare, + waiteRecyclingAmountMap, + splitDepartTousseVoMap + ); + + assertTrue(splitDepartTousseVoMap.isEmpty()); + } + + /** + * TC5: 待回收数量 <= 0 -> 不添加到 splitDepartTousseVoMap + */ + @Test + public void testSplitByAssetOfTousseDefinition_WaitingAmountIsZeroOrNegative_NotAdded() { + ToussePackage item = mock(ToussePackage.class); + when(item.getTousseDefinitionId()).thenReturn(1L); + when(item.getTousseName()).thenReturn("Test Package"); + + TousseDefinition td = mock(TousseDefinition.class); + when(td.getId()).thenReturn(1L); + when(td.getName()).thenReturn("Test Definition"); + when(td.getAssetsBelongCode()).thenReturn("DEPT_B"); // different from current dept + + when(objectDao.findByIds(anyString(), anyList())).thenReturn(Arrays.asList(td)); + + List waiteRecyclingTousseInstanceList = new ArrayList<>(); + List tdIds = Arrays.asList(1L); + String departCodingToCompare = "DEPT_A"; + Map waiteRecyclingAmountMap = new HashMap<>(); + waiteRecyclingAmountMap.put(1L, 0); // zero amount + Map splitDepartTousseVoMap = new HashMap<>(); + + recyclingRecordManager.splitByAssetOfTousseDefinition( + Arrays.asList(item), + waiteRecyclingTousseInstanceList, + tdIds, + departCodingToCompare, + waiteRecyclingAmountMap, + splitDepartTousseVoMap + ); + + assertTrue(splitDepartTousseVoMap.isEmpty()); + } + + /** + * TC6: 正常流程:资产归属不同,有待回收数量,成功填充 splitDepartTousseVoMap + */ + @Test + public void testSplitByAssetOfTousseDefinition_NormalCase_SuccessfullyPopulated() { + // 模拟 applicationItems + ToussePackage item = mock(ToussePackage.class); + when(item.getTousseDefinitionId()).thenReturn(1L); + when(item.getTousseName()).thenReturn("Test Package"); + + // 模拟 TousseDefinition + TousseDefinition td = mock(TousseDefinition.class); + when(td.getId()).thenReturn(1L); + when(td.getName()).thenReturn("Test Definition"); + when(td.getAssetsBelongCode()).thenReturn("DEPT_B"); // different from current dept + + when(objectDao.findByIds(anyString(), anyList())).thenReturn(Arrays.asList(td)); + + // 模拟 TousseInstance + TousseInstance ti = mock(TousseInstance.class); + when(ti.getTousseDefinition()).thenReturn(td); + when(ti.getBarcode()).thenReturn("BARCODE_001"); + when(ti.getIdCardInstanceBarcode()).thenReturn("IDCARD_BARCODE_001"); + + List waiteRecyclingTousseInstanceList = Arrays.asList(ti); + List tdIds = Arrays.asList(1L); + String departCodingToCompare = "DEPT_A"; + Map waiteRecyclingAmountMap = new HashMap<>(); + waiteRecyclingAmountMap.put(1L, 2); // two items available for splitting + Map splitDepartTousseVoMap = new HashMap<>(); + + recyclingRecordManager.splitByAssetOfTousseDefinition( + Arrays.asList(item), + waiteRecyclingTousseInstanceList, + tdIds, + departCodingToCompare, + waiteRecyclingAmountMap, + splitDepartTousseVoMap + ); + + assertEquals(1, splitDepartTousseVoMap.size()); + String key = "1_DEPT_B"; + assertTrue(splitDepartTousseVoMap.containsKey(key)); + SplitDepartTousseVo vo = splitDepartTousseVoMap.get(key); + assertNotNull(vo); + assertEquals((Integer) 2, vo.getAmount()); + assertEquals("Test Definition", vo.getTousseName()); + assertEquals("DEPT_B", vo.getDepartCoding()); + + List barcodes = vo.getSplitTousseBarcodes(); + assertNotNull(barcodes); + assertEquals(1, barcodes.size()); + assertEquals("BARCODE_001", barcodes.get(0).getTousseBarcode()); + assertEquals("IDCARD_BARCODE_001", barcodes.get(0).getIdCardInstanceBarcode()); + + // verify that the remaining amount was updated correctly + assertEquals((Integer) 1, waiteRecyclingAmountMap.get(1L)); + } +} Index: ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadApplicationTousseSplitByOrgUnitTest.java =================================================================== diff -u -r41453 -r41537 --- ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadApplicationTousseSplitByOrgUnitTest.java (.../RecyclingRecordManagerImplLoadApplicationTousseSplitByOrgUnitTest.java) (revision 41453) +++ ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplLoadApplicationTousseSplitByOrgUnitTest.java (.../RecyclingRecordManagerImplLoadApplicationTousseSplitByOrgUnitTest.java) (revision 41537) @@ -218,10 +218,7 @@ 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); + Invoice invoice = createInvoice("DEPT_C", "Department C", 200L); when(objectDao.findByIds(eq(Invoice.class.getSimpleName()), anyList())).thenReturn(Collections.singletonList(invoice)); // Mock ResultSet for RecyclingTousseInstance @@ -288,6 +285,90 @@ assertEquals(2, result.get("100_ASSET_DEPT_X").getAmount().intValue()); } + /** + * 复杂的场景:同一个包定义的多个包实例,分别发货到不同的科室,然后在其中一个科室录入使用记录,包含这种器械包的两个科室的包实例, + * 并且还包含录入使用记录的科室自己的另一种包定义的包实例以及共享科室的第三种包定义的包实例。 + * 共有3种包定义,其中一种是来自2个科室,另两种分别来自1个科室 + */ + @Test + public void testLoadApplicationTousseSplitByOrgUnit_ComplexScenario() throws Exception { + // Mock app + RecyclingApplication app = new RecyclingApplication(); + UseRecord useRecord = new UseRecord(); + useRecord.setDepartCoding("DEPT_A"); + app.setUseRecord(useRecord); + app.setTousseBarcodes(";barcode11;barcode12;barcode13;barcode21;barcode22;barcode31;barcode32;barcode33;barcode34"); + app.setDepartCoding("DEPT_A"); + + when(objectDao.getById(eq(RecyclingApplication.class.getSimpleName()), anyLong())).thenReturn(app); + + // Mock TousseItem + List items = new ArrayList<>(); + TousseItem item = createTousseItem(100L, 3, 0, false); + items.add(item); + items.add(createTousseItem(101L, 2, 0, false)); + items.add(createTousseItem(102L, 4, 0, false)); + app.setApplicationItems(items); + + // Mock TousseInstance + List tis = new ArrayList<>(); + TousseInstance ti = createTousseInstance(100L, "Test Tousse", 21L, "barcode11", "idcard11"); + tis.add(ti); + ti = createTousseInstance(100L, "Test Tousse", 21L, "barcode12", "idcard12"); + tis.add(ti); + ti = createTousseInstance(100L, "Test Tousse", 22L, "barcode13", "idcard13"); + tis.add(ti); + ti = createTousseInstance(101L, "Test Tousse2", 31L,"barcode21", "idcard21"); + tis.add(ti); + ti = createTousseInstance(101L, "Test Tousse2", 31L,"barcode22", "idcard22"); + tis.add(ti); + ti = createTousseInstance(102L, "Test Tousse3", 41L,"barcode31", "idcard31"); + tis.add(ti); + ti = createTousseInstance(102L, "Test Tousse3", 41L,"barcode32", "idcard32"); + tis.add(ti); + ti = createTousseInstance(102L, "Test Tousse3", 41L,"barcode33", "idcard33"); + tis.add(ti); + ti = createTousseInstance(102L, "Test Tousse3", 41L,"barcode34", "idcard34"); + tis.add(ti); + + when(objectDao.findBySql(anyString(), anyString())).thenReturn(tis); + + // Mock Invoice + List invoices = new ArrayList<>(); + Invoice invoice = createInvoice("DEPT_A", "Department A", 21L); + invoices.add(invoice); + invoice = createInvoice("DEPT_B", "Department B", 22L); + invoices.add(invoice); + invoice = createInvoice("DEPT_A", "Department A", 31L); + invoices.add(invoice); + invoice = createInvoice("DEPT_B", "Department B", 41L); + invoices.add(invoice); + when(objectDao.findByIds(eq(Invoice.class.getSimpleName()), anyList())).thenReturn(invoices); + + // Mock ResultSet for RecyclingTousseInstance + ResultSet rs = mock(ResultSet.class); + when(rs.next()).thenReturn(false); // No previous split + when(objectDao.executeSql(anyString())).thenReturn(rs); + + when(ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0)).thenReturn(1); + + Map result = recyclingRecordManager.loadApplicationTousseSplitByOrgUnit(1L); + + assertEquals(2, result.size()); + assertNotNull(result.get("100_DEPT_B")); + assertEquals(1, result.get("100_DEPT_B").getAmount().intValue()); + assertNotNull(result.get("102_DEPT_B")); + assertEquals(4, result.get("102_DEPT_B").getAmount().intValue()); + } + + private static Invoice createInvoice(String departCoding, String departName, long invoiceId) { + Invoice invoice = mock(Invoice.class); + when(invoice.getDepartCoding()).thenReturn(departCoding); + when(invoice.getDepart()).thenReturn(departName); + when(invoice.getId()).thenReturn(invoiceId); + return invoice; + } + private static TousseItem createTousseItem(Long tousseDefinitionId, int amount, int recycledAmount, boolean terminated) { TousseItem item = mock(TousseItem.class); when(item.getIsTerminated()).thenReturn(terminated); Index: ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplSaveAndSplitRecyclingRecordTest.java =================================================================== diff -u -r41453 -r41537 --- ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplSaveAndSplitRecyclingRecordTest.java (.../RecyclingRecordManagerImplSaveAndSplitRecyclingRecordTest.java) (revision 41453) +++ ssts-recyclingrecord/src/test/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImplSaveAndSplitRecyclingRecordTest.java (.../RecyclingRecordManagerImplSaveAndSplitRecyclingRecordTest.java) (revision 41537) @@ -4,9 +4,11 @@ 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.idcardinstance.service.IDCardInstanceManager; import com.forgon.disinfectsystem.recyclingapplication.service.InvoicePlanManager; import com.forgon.disinfectsystem.recyclingapplication.service.RecyclingApplicationManager; import com.forgon.disinfectsystem.recyclingrecord.vo.RecyclingContext; +import com.forgon.disinfectsystem.tousse.toussedefinition.service.TousseInstanceManager; import com.forgon.exception.SystemException; import com.forgon.tools.hibernate.ObjectDao; import net.sf.json.JSONArray; @@ -37,6 +39,10 @@ @Mock private InvoicePlanManager invoicePlanManager; + @Mock + private TousseInstanceManager tousseInstanceManager; + @Mock + private IDCardInstanceManager idCardInstanceManager; @Mock private ObjectDao objectDao; @@ -94,14 +100,16 @@ doNothing().when(objectDao).saveOrUpdate(any()); doNothing().when(invoicePlanManager).terminateTousseItemByIds(anyList(), anyString()); doNothing().when(recyclingRecordManager).save(any(RecyclingContext.class)); + doReturn(Collections.emptyList()).when(recyclingRecordManager).loadRecyclingTousseInstances(recyclingContext, invoicePlan); // 执行测试 recyclingRecordManager.saveAndSplitRecyclingRecord(recyclingContext, null); // 验证结果 verify(recyclingApplicationManager).get("1"); verify(invoicePlanManager).terminateTousseItemByIds(Collections.singletonList(1L), "申请单上的物品被拆分"); - verify(recyclingRecordManager, times(2)).save(any(RecyclingContext.class)); + // 全部物品被拆分,原申请单会被终止 只保存被拆分的物品 + verify(recyclingRecordManager).save(any(RecyclingContext.class)); assertEquals(5, item.getAmount().intValue()); // 全部拆分,原申请项已经终止,这种情况下在实现上并没有修改申请数量 } @@ -271,6 +279,45 @@ recyclingRecordManager.saveAndSplitRecyclingRecord(recyclingContext, null); } + /** + * 针对回收申请单中存在拆分物品 + * 验证是否正确获取申请单并执行拆分逻辑 + */ + @Test + public void testSaveAndSplitRecyclingRecord_RecyclingAppType_WithSplitItems() { + // 准备数据 + RecyclingContext recyclingContext = new RecyclingContext(); + recyclingContext.setRecyclingApplicationId(""); + 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()); + + when(recyclingApplicationManager.get(anyString())).thenReturn(null); + + 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(""); + verify(recyclingRecordManager).save(any(RecyclingContext.class)); + } + private static JSONObject createRecyclingItem(int value) { JSONObject recyclingItem = new JSONObject(); recyclingItem.put("tousseDefinitionID", "100"); Index: ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImpl.java =================================================================== diff -u -r41453 -r41537 --- ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImpl.java (.../RecyclingRecordManagerImpl.java) (revision 41453) +++ ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManagerImpl.java (.../RecyclingRecordManagerImpl.java) (revision 41537) @@ -1,42 +1,5 @@ package com.forgon.disinfectsystem.recyclingrecord.service; -import java.math.BigDecimal; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.sql.Types; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.Map.Entry; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import com.forgon.disinfectsystem.entity.adverseeventrecord.AdverseEventWarnOrgUnit; -import com.forgon.exception.CleaningBasketDoesNotMatchException; -import com.forgon.tools.*; -import net.sf.json.JSONArray; -import net.sf.json.JSONException; -import net.sf.json.JSONObject; -import net.sf.json.JsonConfig; -import net.sf.json.processors.JsonValueProcessor; -import net.sf.json.util.CycleDetectionStrategy; -import net.sf.json.util.PropertyFilter; - -import org.apache.commons.collections.MapUtils; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.collections4.Predicate; -import org.apache.commons.collections4.PredicateUtils; -import org.apache.commons.collections4.map.MultiValueMap; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang3.BooleanUtils; -import org.apache.log4j.Logger; -import org.springframework.jdbc.core.BatchPreparedStatementSetter; -import org.springframework.jdbc.core.JdbcTemplate; - import com.forgon.Constants; import com.forgon.databaseadapter.service.DateQueryAdapter; import com.forgon.directory.acegi.tools.AcegiHelper; @@ -60,6 +23,7 @@ import com.forgon.disinfectsystem.common.CssdUtils; import com.forgon.disinfectsystem.datasynchronization.dao.SyncForeignTousseApplicationDao; import com.forgon.disinfectsystem.entity.adverseeventrecord.AdverseEventRecord; +import com.forgon.disinfectsystem.entity.adverseeventrecord.AdverseEventWarnOrgUnit; import com.forgon.disinfectsystem.entity.assestmanagement.DiposableGoodsInstance; import com.forgon.disinfectsystem.entity.basedatamanager.cleanmethod.CleanMethod; import com.forgon.disinfectsystem.entity.basedatamanager.container.Container; @@ -88,11 +52,7 @@ import com.forgon.disinfectsystem.entity.packing.PackingTask; import com.forgon.disinfectsystem.entity.recycledepartmentgroup.DepartmentGroup; import com.forgon.disinfectsystem.entity.recyclingapplication.RecyclingApplication; -import com.forgon.disinfectsystem.entity.recyclingrecord.RecyclingBasketSequence; -import com.forgon.disinfectsystem.entity.recyclingrecord.RecyclingDateDetails; -import com.forgon.disinfectsystem.entity.recyclingrecord.RecyclingItem; -import com.forgon.disinfectsystem.entity.recyclingrecord.RecyclingRecord; -import com.forgon.disinfectsystem.entity.recyclingrecord.RecyclingTousseInstance; +import com.forgon.disinfectsystem.entity.recyclingrecord.*; import com.forgon.disinfectsystem.entity.tousseitem.MaterialModifyRecord; import com.forgon.disinfectsystem.entity.tousseitem.PrepareRecyleScanBarcode; import com.forgon.disinfectsystem.entity.tousseitem.TousseItem; @@ -108,6 +68,8 @@ import com.forgon.disinfectsystem.idcardinstance.util.IDCardInstanceUtils; import com.forgon.disinfectsystem.idpredicate.IDOperators; import com.forgon.disinfectsystem.materialerrordamage.service.MaterialErrorDamageDetailManager; +import com.forgon.disinfectsystem.model.DepartmentAware; +import com.forgon.disinfectsystem.model.ToussePackage; import com.forgon.disinfectsystem.packing.service.ClassifyBasketRecyclingSequenceManager; import com.forgon.disinfectsystem.packing.service.PackingManager; import com.forgon.disinfectsystem.recyclingapplication.service.InvoicePlanManager; @@ -117,26 +79,10 @@ import com.forgon.disinfectsystem.recyclingapplication.updateLog.ApplicationLogManager; import com.forgon.disinfectsystem.recyclingapplication.vo.ApplicationItemVO; import com.forgon.disinfectsystem.recyclingapplication.vo.ReturnGoodVo; -import com.forgon.disinfectsystem.recyclingrecord.vo.ClassifybasketVo; -import com.forgon.disinfectsystem.recyclingrecord.vo.CustomIntoBasket; -import com.forgon.disinfectsystem.recyclingrecord.vo.CustomIntoBasketItem; -import com.forgon.disinfectsystem.recyclingrecord.vo.DepartRecycleTousse; -import com.forgon.disinfectsystem.recyclingrecord.vo.ErrorDamageDetailVo; -import com.forgon.disinfectsystem.recyclingrecord.vo.MaterialErrorDamageDetailVo; -import com.forgon.disinfectsystem.recyclingrecord.vo.RecyclingBasketItemMaterialVo; -import com.forgon.disinfectsystem.recyclingrecord.vo.RecyclingBasketItemVo; -import com.forgon.disinfectsystem.recyclingrecord.vo.RecyclingContext; -import com.forgon.disinfectsystem.recyclingrecord.vo.RecyclingPrintData; -import com.forgon.disinfectsystem.recyclingrecord.vo.RecyclingPrintItem; -import com.forgon.disinfectsystem.recyclingrecord.vo.RecyclingRecordVo; -import com.forgon.disinfectsystem.recyclingrecord.vo.RecyclingSumInfoVo; -import com.forgon.disinfectsystem.recyclingrecord.vo.SimpleTousseItem; -import com.forgon.disinfectsystem.recyclingrecord.vo.SimpleTousseItemImpl; -import com.forgon.disinfectsystem.recyclingrecord.vo.SplitDepartTousseVo; -import com.forgon.disinfectsystem.recyclingrecord.vo.SplitTousseBarcodeVo; -import com.forgon.disinfectsystem.recyclingrecord.vo.TousseDefinitionAble; -import com.forgon.disinfectsystem.recyclingrecord.vo.TousseIntoBasketService; -import com.forgon.disinfectsystem.recyclingrecord.vo.UrgentTousseItem; +import com.forgon.disinfectsystem.recyclingrecord.param.RecyclingItemParam; +import com.forgon.disinfectsystem.recyclingrecord.param.RecyclingSplitLoadParam; +import com.forgon.disinfectsystem.recyclingrecord.param.SplitSaveRecyclingTousseParam; +import com.forgon.disinfectsystem.recyclingrecord.vo.*; import com.forgon.disinfectsystem.tousse.instrumentinstance.service.InstrumentInstanceManager; import com.forgon.disinfectsystem.tousse.materialdefinition.service.MaterialDefinitionManager; import com.forgon.disinfectsystem.tousse.materialinstance.service.MaterialInstanceManager; @@ -151,6 +97,7 @@ import com.forgon.disinfectsystem.vo.TousseItemVo; import com.forgon.disinfectsystem.washTransition.service.IdCardMaterialErrorDamageManager; import com.forgon.disinfectsystem.washanddisinfectmanager.washanddisinfectrecord.service.WashAndDisinfectRecordManager; +import com.forgon.exception.CleaningBasketDoesNotMatchException; import com.forgon.exception.SystemException; import com.forgon.exception.UnfinishedPackingTaskException; import com.forgon.log.enums.ApplicationLogStatusEnum; @@ -161,12 +108,17 @@ import com.forgon.serialnumber.service.SerialNumManager; import com.forgon.system.activity.model.Activity; import com.forgon.system.activity.model.AmountControl; +import com.forgon.tools.BeanUtils; +import com.forgon.tools.MathTools; +import com.forgon.tools.PaginationQuery; +import com.forgon.tools.SpringBeanManger; import com.forgon.tools.date.DateTools; import com.forgon.tools.db.DatabaseUtil; import com.forgon.tools.db.InitDbConnection; import com.forgon.tools.format.ConvertNumber; import com.forgon.tools.hibernate.BasePoManagerImpl; import com.forgon.tools.json.JSONUtil; +import com.forgon.tools.json.JacksonUtil; import com.forgon.tools.json.JsonPropertyFilter; import com.forgon.tools.string.StringTools; import com.forgon.tools.util.ConfigUtils; @@ -175,7 +127,33 @@ import com.forgon.tools.util.SqlUtils; import com.forgon.websocket.NoticeType; import com.forgon.websocket.WebSocketServer; +import net.sf.json.*; +import net.sf.json.processors.JsonValueProcessor; +import net.sf.json.util.CycleDetectionStrategy; +import net.sf.json.util.PropertyFilter; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.PredicateUtils; +import org.apache.commons.collections4.map.MultiValueMap; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.log4j.Logger; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import java.math.BigDecimal; +import java.sql.*; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + /** * @author wangyi * @@ -1066,29 +1044,11 @@ * @param record * @return */ - private List getTousseItemJsonArray(List tousseJson,RecyclingRecord record, List tds){ + private List getTousseItemJsonArray(List tousseJson, DepartmentAware record, List tds){ if(tousseJson == null){ return null; } - //是否禁用基数限制 - boolean disableCardinalNumLimit = CssdUtils.getSystemSetConfigByNameBool("disableCardinalNumLimit", false); - Map> tousseNameToAmountMap = new HashMap>();//<包名,<状态,数量>> - /** - * 基数验证第一第二步 - */ - { - if(!disableCardinalNumLimit && StringUtils.isNotBlank(record.getDepartCode())){//申请科室为空做不了基数验证 - Set tousseNameSet = new HashSet(); - // 基数验证第一步:收集需要验证的包名 - for (int i = 0; i < tousseJson.size(); i++) { - tousseNameSet.add(tousseJson.get(i).getTousseName()); - } - if(CollectionUtils.isNotEmpty(tousseNameSet)){ - //基数验证第二步:统一查询科室库存基数和已用基数 - tousseNameToAmountMap = tousseInstanceManager.getTousseAmountByGroupStatus(record.getDepartCode(), tousseNameSet, false, true); - } - } - } + CardinalNumLimitValidator validator = CardinalNumLimitValidator.validator(tousseInstanceManager, record, tousseJson); Map>> map = new LinkedHashMap>>(); Set miIds = new HashSet();//材料实例的id 用于查询 for (int i = 0; i < tousseJson.size(); i++) { @@ -1107,27 +1067,7 @@ RecyclingBasketItemVo basketItemVo = tousseJson.get(i); String tousseName = basketItemVo.getTousseName(); Integer amount = basketItemVo.getAmount(); - /** - * 基数验证第三步:验证是否有物品超过基数 - */ - { - if(!disableCardinalNumLimit && tousseNameToAmountMap.containsKey(tousseName)){ - Map amountMap = tousseNameToAmountMap.get(tousseName); - if(MapUtils.isNotEmpty(amountMap) && amountMap.get(TousseInstance.STATUS_CARDINALNUM) != null){ - Integer cardinalNum = amountMap.get(TousseInstance.STATUS_CARDINALNUM);//设置的基数 - if(cardinalNum != null && cardinalNum != 0){ - Integer existsAmount = amountMap.get(TousseInstance.STATUS_USEDCARDINALNUM); - if(existsAmount >= cardinalNum){ - throw new RuntimeException(record.getDepart()+ "的" + tousseName + "的回收数量超过了基数上限,最大可回收数量为0"); - } - Integer surplusBaseAmount = cardinalNum - existsAmount; - if(amount > surplusBaseAmount){ - throw new RuntimeException(record.getDepart()+ "的" + tousseName + "的回收数量超过了基数上限,最大可回收数量为" + surplusBaseAmount); - } - } - } - } - } + validator.validate(tousseName, amount); Long tousseDefinitionId = basketItemVo.getTousseDefinitionID(); String idCardBarcode = basketItemVo.getIdCardBarcode(); Long lastTousseInstanceId = basketItemVo.getLastTousseInstanceId(); @@ -1139,18 +1079,10 @@ } //key = 包名称+标识牌条码+二次回收的包实例id String key = tousseDefinitionId + idCardBarcode + lastTousseInstanceId; - Map> basketMap = map.get(key); - if(basketMap == null){ - basketMap = new HashMap>(); - map.put(key, basketMap); - } - //key = 物品所属篮筐或篮筐组条码 - List list = basketMap.get(basketGroupBarcodes); - if(list == null){ - list = new ArrayList(); - basketMap.put(basketGroupBarcodes, list); - } - if(basketItemVo.itemTypeMaterial()){ + Map> basketMap = map.computeIfAbsent(key, k -> new HashMap<>()); + //key = 物品所属篮筐或篮筐组条码 + List list = basketMap.computeIfAbsent(basketGroupBarcodes, k -> new ArrayList<>()); + if(basketItemVo.itemTypeMaterial()){ String materialName = basketItemVo.getTousseName(); Long tousseDefinitionID = basketItemVo.getTousseDefinitionID(); Integer tousseAmount = basketItemVo.getTousseAmountForMaterial(); @@ -2439,6 +2371,18 @@ } return barcodeRedurAmountMap; } + + /** + * 加载包定义同时加载材料,避免 N+1 问题 + * @param tousseDefinitionIds 包定义id + * @return 包定义 + */ + private List loadTousseDefinitionAndFetchMaterials(Collection tousseDefinitionIds) { + return objectDao.findByHql("select distinct po from " + + TousseDefinition.class.getSimpleName() + + " po left join fetch po.materialInstances where " + +SqlUtils.getNonStringFieldInLargeCollectionsPredicate("po.id", tousseDefinitionIds)); + } @Override @Activity(name = AmountControl.RECYCLING) public void save(RecyclingRecord record, RecyclingContext recyclingContext) { @@ -2451,7 +2395,7 @@ RecyclingSumInfoVo recyclingSumInfoVo = setRecyclingSumInfoVoInfo(); loopSetOldRecyclingItemInfo(record, recyclingSumInfoVo); //放入篮筐的物品 - List basketItemJson = JSONUtil.fromJson(params.optJSONArray("tousseJson"), RecyclingBasketItemVo.class) ; + List basketItemJson = recyclingContext.tousseJson(); //校验点击清点确认按钮时,篮筐中的物品 //validateRecyclingBasketItemVo(recyclingContext, basketItemJson); boolean enableSecondRecyclingAfterInvoiceFunction = CssdUtils.getSystemSetConfigByNameBool("enableSecondRecyclingAfterInvoiceFunction", false); @@ -2517,10 +2461,7 @@ Collection tousseDefIds = CollectionUtils.union(tousseDefIdTousseItemVoMap.keySet(), basketItemJson.stream().map(p->p.getTousseDefinitionID()).collect(Collectors.toSet())); //包定义集合 @SuppressWarnings("unchecked") - List tousseDefinitions = objectDao.findByHql("select distinct po from " - + TousseDefinition.class.getSimpleName() - + " po left join fetch po.materialInstances where " - +SqlUtils.getNonStringFieldInLargeCollectionsPredicate("po.id", tousseDefIds)); + List tousseDefinitions = loadTousseDefinitionAndFetchMaterials(tousseDefIds); Set taskGroupsOfUpdateUrgent = null; if(CollectionUtils.isNotEmpty(tdIdsOfupdateUrgent)){ taskGroupsOfUpdateUrgent = new HashSet(); @@ -2550,7 +2491,7 @@ } //验证回收时的包定义 verifyTousseAtRecycling(record, tousseDefIds,recyclingContext); - Map containerMap = new HashMap(); + Map containerMap = new HashMap<>(); @@ -3358,7 +3299,7 @@ } boolean autoReturnTheBorrowingTousse = recyclingContext.getAutoReturnTheBorrowingTousse(); //放入篮筐的物品 - List basketItemJson = JSONUtil.fromJson(params.optJSONArray("tousseJson"), RecyclingBasketItemVo.class) ; + List basketItemJson = recyclingContext.tousseJson(); //校验点击清点确认按钮时,篮筐中的物品 //validateRecyclingBasketItemVo(recyclingContext, basketItemJson); //未入篮筐删掉的物品 @@ -10124,9 +10065,11 @@ if(app == null){ return splitDepartTousseVoMap; } - UseRecord useRecord = app.getUseRecord(); - if(StringUtils.isBlank(app.getTousseBarcodes()) || useRecord == null){ - return splitDepartTousseVoMap; + if(!InvoicePlan.TYPE_RECYCLINGCREATE_APPLICATION.equals(app.getType())){ + UseRecord useRecord = app.getUseRecord(); + if(StringUtils.isBlank(app.getTousseBarcodes()) || useRecord == null){ + return splitDepartTousseVoMap; + } } // 已经回收过的申请单,不需要再显示拆分物品的悬浮窗 @@ -10169,25 +10112,53 @@ // 当配置项值为1时,根据申请单中物品的发货科室与使用记录录入科室进行拆分 if(methodOfSplitRecyclingApplication.intValue() == 1){ - splitByDepartmentOfInvoiceAndUseRecord(waiteRecyclingTousseInstanceList, tdIds, waiteRecyclingAmountMap, app, splitDepartTousseVoMap); + splitByDepartmentOfInvoiceAndUseRecord(waiteRecyclingTousseInstanceList, tdIds, waiteRecyclingAmountMap, app.getDepartCoding(), splitDepartTousseVoMap); } else if (methodOfSplitRecyclingApplication.intValue() == 2){ // 当配置项值为2时,根据申请单中物品的包定义的【资产归属】进行拆分 // 遍历申请单申请单所有物品,并根据【资产归属】进行分组 - splitByAssetOfTousseDefinition(applicationItems, waiteRecyclingTousseInstanceList, tdIds, app, waiteRecyclingAmountMap, splitDepartTousseVoMap); + splitByAssetOfTousseDefinition(applicationItems, waiteRecyclingTousseInstanceList, tdIds, app.getDepartCoding(), waiteRecyclingAmountMap, splitDepartTousseVoMap); } return splitDepartTousseVoMap; } + @Override + public Map loadRecyclingTousseSplitByOrgUnit(RecyclingSplitLoadParam param) { + Map splitDepartTousseVoMap = new HashMap<>(); + int methodOfSplitRecyclingApplication = + ConfigUtils.getSystemSetConfigByNameInt("methodOfSplitRecyclingApplication", 0); + if(methodOfSplitRecyclingApplication == 0){ + return splitDepartTousseVoMap; + } + if(CollectionUtils.isEmpty(param.getTousseArray()) || CollectionUtils.isEmpty(param.getRecyclingItemArray())){ + return splitDepartTousseVoMap; + } + List waiteRecyclingTousseInstanceList = loadTousseInstance(param.getTousseArray()); + Map waiteRecyclingAmountMap = param.getRecyclingItemArray().stream().collect(Collectors.toMap(RecyclingItemParam::getTousseDefinitionID, RecyclingItemParam::getRecycleAmount)); + List tdIds = param.getRecyclingItemArray().stream().map(RecyclingItemParam::getTousseDefinitionID).collect(Collectors.toList()); + // 当配置项值为1时,根据申请单中物品的发货科室与使用记录录入科室进行拆分 + if(methodOfSplitRecyclingApplication == 1){ + splitByDepartmentOfInvoiceAndUseRecord(waiteRecyclingTousseInstanceList, tdIds, waiteRecyclingAmountMap, param.getDepartCode(), splitDepartTousseVoMap); + } else if (methodOfSplitRecyclingApplication == 2){ + // 当配置项值为2时,根据申请单中物品的包定义的【资产归属】进行拆分 + // 遍历申请单申请单所有物品,并根据【资产归属】进行分组 + splitByAssetOfTousseDefinition(param.getRecyclingItemArray(), waiteRecyclingTousseInstanceList, tdIds, param.getDepartCode(), waiteRecyclingAmountMap, splitDepartTousseVoMap); + } + return splitDepartTousseVoMap; + } + /** * 根据申请单中物品的包定义的【资产归属】进行拆分 * @param applicationItems * @param waiteRecyclingTousseInstanceList * @param tdIds - * @param app + * @param departCodingToCompare 比较的科室编译。包定义的资产归属科室编码与此编码不同,则需要拆分 * @param waiteRecyclingAmountMap * @param splitDepartTousseVoMap */ - private void splitByAssetOfTousseDefinition(List applicationItems, List waiteRecyclingTousseInstanceList, List tdIds, RecyclingApplication app, Map waiteRecyclingAmountMap, Map splitDepartTousseVoMap) { + void splitByAssetOfTousseDefinition( + List applicationItems, List waiteRecyclingTousseInstanceList, List tdIds, + String departCodingToCompare, Map waiteRecyclingAmountMap, + Map splitDepartTousseVoMap) { if(CollectionUtils.isNotEmpty(applicationItems)){ Map> tousseInstanceMap = new HashMap<>(); for (TousseInstance tousseInstance : waiteRecyclingTousseInstanceList) { @@ -10204,13 +10175,13 @@ for (TousseDefinition td : tdList) { idToTdMap.put(td.getId(), td); } - for (TousseItem item : applicationItems) { + for (ToussePackage 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())){ + if(StringUtils.isNotBlank(td.getAssetsBelongCode()) && !td.getAssetsBelongCode().equals(departCodingToCompare)){ Integer waiteRecyclingAmount = waiteRecyclingAmountMap.get(td.getId()); if(waiteRecyclingAmount <= 0){ continue; @@ -10243,7 +10214,10 @@ } } - private void splitByDepartmentOfInvoiceAndUseRecord(List waiteRecyclingTousseInstanceList, List tdIds, Map waiteRecyclingAmountMap, RecyclingApplication app, Map splitDepartTousseVoMap) { + void splitByDepartmentOfInvoiceAndUseRecord( + List waiteRecyclingTousseInstanceList, + List tdIds, Map waiteRecyclingAmountMap, String departCodingToCompare, + Map splitDepartTousseVoMap) { //查询器械包实例的发货单map Map invoiceMap = loadTousseInstanceInvoiceMap(waiteRecyclingTousseInstanceList); @@ -10256,7 +10230,7 @@ //部分回收时,返回的可拆分数量需要减去已经回收数量 Integer waiteRecyclingAmount = waiteRecyclingAmountMap.get(td.getId()); - if(waiteRecyclingAmount == null || waiteRecyclingAmount.intValue() <= 0){ + if(waiteRecyclingAmount == null || waiteRecyclingAmount <= 0){ continue; } @@ -10275,7 +10249,7 @@ //申请科室 String depart = invoice.getDepart(); - if(StringUtils.isNotBlank(departCoding) && !app.getDepartCoding().equals(departCoding)){ + if(StringUtils.isNotBlank(departCoding) && !Objects.equals(departCodingToCompare, departCoding)){ String key = td.getId() + "_" + departCoding; SplitDepartTousseVo splitDepartTousseVo = splitDepartTousseVoMap.get(key); if(splitDepartTousseVo == null){ @@ -10318,11 +10292,48 @@ } splitTousseBarcodes.add(vo); } - + /** + * 加载回收的器械包实例。这里需要多考虑一下了。比如回收时扫描器械包条码添加的器械包。
+ * 1、打开申请单扫描器械包条码添加;
+ * 2、针对回收申请单扫描器械包条码添加。
+ * 上面两种情况下条码都可能是器械包实例条码或者是标识牌实例条码 + * @param recyclingContext 回收上下文 + * @param app 申请单。可能为空,比如回收申请单的情况 + * @return 器械包实例列表 + */ + List loadRecyclingTousseInstances(RecyclingContext recyclingContext, RecyclingApplication app) { + List tis = new ArrayList<>(); + if(app != null) { + tis.addAll(loadInvoicePlanUseRecordWaiteRecyclingTousseInstance(app)); + } + List recyclingBasketItems = recyclingContext.tousseJson(); + List tousseInstances = loadTousseInstance(recyclingBasketItems); + tis.addAll(tousseInstances); + return tis; + } + + /** + * 加载器械包。这里是所有装入篮筐的器械包,包括器械包条码和标识牌条码的器械包。但是必须是扫描条码装入篮筐的,否则是没有条码信息的 + * @param recyclingBasketItems 装载篮筐的物品列表 + * @return 器械包实例列表 + */ + private List loadTousseInstance(List recyclingBasketItems) { + List list = new ArrayList<>(); + Set tousseBarcodes = recyclingBasketItems.stream().map(RecyclingBasketItemVo::getTousseInstanceBarcode).filter(StringUtils::isNotBlank).collect(Collectors.toSet()); + Set cardBarcodes = recyclingBasketItems.stream().map(RecyclingBasketItemVo::getIdCardBarcode).filter(StringUtils::isNotBlank).collect(Collectors.toSet()); + List tousseInstances = tousseInstanceManager.getCollection("barcode", tousseBarcodes); + List idCardInstances = idCardInstanceManager.getCollection("barcode", cardBarcodes); + Set tiIds = idCardInstances.stream().map(IDCardInstance::getLastTousseInstanceId).collect(Collectors.toSet()); + list.addAll(tousseInstances); + list.addAll(tousseInstanceManager.getCollection(tiIds)); + return list; + } + + /** * 查询使用记录转换的申请单上,关联的待回收包实例 - * @param app - * @return + * @param app 使用记录转换的申请单 + * @return 器械包实例列表 */ @SuppressWarnings("unchecked") List loadInvoicePlanUseRecordWaiteRecyclingTousseInstance(RecyclingApplication app) { @@ -10441,57 +10452,64 @@ } //参数拆分 splitRecyclingContext(recyclingContext, invoicePlan); - JSONObject jsonParamObject = recyclingContext.getJsonParamObject(); //原单回收的物品 - JSONArray tousseJson = jsonParamObject.optJSONArray("tousseJson"); + List tousseJson = recyclingContext.tousseJson(); //拆分回收的物品 - JSONArray splitJsonArray = jsonParamObject.optJSONArray("splitTousseJson"); - if((splitJsonArray != null && splitJsonArray.size() > 0)){ - if(invoicePlan == null){ - throw new SystemException("申请单不存在!"); + List splitJsonArray = recyclingContext.splitTousseJson(); + if((splitJsonArray != null && !splitJsonArray.isEmpty())){ + // 是否拆分回收申请单。回收申请单在拆分的时候,还没有生成 + boolean splitRecyclingApplicationType = invoicePlan == null; +// if(invoicePlan == null){ +// throw new SystemException("申请单不存在!"); +// } + //拆分回收 根据科室分组 + Map> departToTousseMap = splitTousseByDepart(splitJsonArray); + Map> departBasketItemMap = splitBasketItemByDepart(tousseJson, departToTousseMap); + if(splitRecyclingApplicationType) { + + }else{ + //部分回收时,减少被拆分回收的申请单的申请数量 + List willTerminateItemIds = splitInvoicePlanTousseItemAmount(invoicePlan, splitJsonArray); + if(CollectionUtils.isNotEmpty(willTerminateItemIds)){ + //单上物品都被拆分回收了,需要终止申请单 + String endCause = "申请单上的物品被拆分"; + invoicePlanManager.terminateTousseItemByIds(willTerminateItemIds, endCause); + } } - //部分回收时,减少被拆分回收的申请单的申请数量 - List willTerminateItemIds = splitInvoicePlanTousseItemAmount(invoicePlan, splitJsonArray); - if(CollectionUtils.isNotEmpty(willTerminateItemIds)){ - //单上物品都被拆分回收了,需要终止申请单 - String endCause = "申请单上的物品被拆分"; - invoicePlanManager.terminateTousseItemByIds(willTerminateItemIds, endCause); - } - + //继续回收保留在原来申请单上的物品 - if(tousseJson != null && tousseJson.size() > 0){ + if(tousseJson != null && !tousseJson.isEmpty()){ //不拆分回收 if(recyclingRecord != null){ this.save(recyclingRecord, recyclingContext); }else{ this.save(recyclingContext); } } - - //拆分回收 根据科室分组 - Map departToTousseMap = splitTousseByDepart(splitJsonArray); - for(Entry entry : departToTousseMap.entrySet()){ + + for(Entry> entry : departBasketItemMap.entrySet()){ String key = entry.getKey(); - JSONArray tousseArr = entry.getValue(); + List tousseArr = entry.getValue(); String[] arr = key.split("@_@"); String departCode = arr[0]; String depart = arr[1]; + if(StringUtils.isBlank(departCode)){ + OrgUnit ou = (OrgUnit)objectDao.getByProperty(OrgUnit.class.getSimpleName(), "name", depart); + departCode = ou == null ? "" : ou.getOrgUnitCoding(); + } // 构建回收的jsonParam参数 - String jsonParam = bulidJsonParam(tousseArr, jsonParamObject); + String jsonParam = buildJsonParam(tousseArr, recyclingContext, null); RecyclingContext tempRecyclingContext = new RecyclingContext(); org.springframework.beans.BeanUtils.copyProperties(recyclingContext, tempRecyclingContext); tempRecyclingContext.setId(""); tempRecyclingContext.setRecyclingApplicationId(""); - tempRecyclingContext.setSplitRecyclingApplicationId(invoicePlan.getId()); + if(!splitRecyclingApplicationType){ + tempRecyclingContext.setSplitRecyclingApplicationId(invoicePlan.getId()); + } tempRecyclingContext.setJsonParam(jsonParam); tempRecyclingContext.parseParameters(); tempRecyclingContext.setDepart(depart); - if(StringUtils.isNotBlank(departCode)){ - tempRecyclingContext.setDepartCode(departCode); - }else{ - OrgUnit ou = (OrgUnit)objectDao.getByProperty(OrgUnit.class.getSimpleName(), "name", depart); - tempRecyclingContext.setDepartCode(ou == null ? "" : ou.getOrgUnitCoding()); - } + tempRecyclingContext.setDepartCode(departCode); //不拆分回收 this.save(tempRecyclingContext); } @@ -10506,15 +10524,88 @@ } // throw new RuntimeException("回收测试"); } - + + private Map> splitBasketItemByDepart( + List tousseJson, + Map> departToTousseMap) { + if(CollectionUtils.isEmpty(tousseJson)){ + return Collections.emptyMap(); + } + Map> map = new HashMap<>(); + for(String key : departToTousseMap.keySet()) { + List recyclingTousses = departToTousseMap.get(key); + List departBasketItems = recyclingTousses.stream().flatMap(recyclingTousse -> { + Integer recyclingAmount = recyclingTousse.getAmount(); + Long tdId = recyclingTousse.getTousseDefinitionID(); + List vos = tousseJson.stream().filter(basketItem -> { + Integer amount = basketItem.getAmount(); + if(basketItem.itemTypeMaterial()){ + amount = basketItem.getTousseAmountForMaterial(); + } + if(amount == null || amount <= 0){ + return false; + } + if(!Objects.equals(basketItem.getTousseDefinitionID(), tdId)){ + return false; + } + if(StringUtils.isNotBlank(basketItem.getTousseInstanceBarcode())){ + // 扫描条码入框的,那条码也得对应得上 + if(CollectionUtils.isNotEmpty(recyclingTousse.getSplitTousseBarcodes())){ + return recyclingTousse.getSplitTousseBarcodes().contains(basketItem.getTousseInstanceBarcode()); + } + return false; + } + // 标识牌 + if(StringUtils.isNotBlank(basketItem.getIdCardBarcode())){ + if(CollectionUtils.isNotEmpty(recyclingTousse.getSplitTousseBarcodes())){ + return recyclingTousse.getSplitTousseBarcodes().contains(basketItem.getIdCardBarcode()); + } + return false; + } + // 包定义id一致,没有扫描条码的。可能是按单回收直接入筐的情况 + return true; + }).collect(Collectors.toList()); + List list = new ArrayList<>(); + for(RecyclingBasketItemVo basketItem : vos) { + Integer amount = basketItem.getAmount(); + if(basketItem.itemTypeMaterial()){ + amount = basketItem.getTousseAmountForMaterial(); + } + if(amount <= recyclingAmount){ + // 全部抵扣,从newTousseJson中移除 + tousseJson.remove(basketItem); + recyclingAmount -= amount; + list.add(basketItem); + }else{ + amount -= recyclingAmount; + // 复制一份,修改数量 + RecyclingBasketItemVo vo = JacksonUtil.parseObject(JacksonUtil.trans2JsonStr(basketItem), RecyclingBasketItemVo.class); + if(vo.itemTypeMaterial()){ + vo.setTousseAmountForMaterial(recyclingAmount); + basketItem.setTousseAmountForMaterial(amount); + }else{ + vo.setAmount(recyclingAmount); + basketItem.setAmount(amount); + } + list.add(vo); + break; + } + } + return list.stream(); + }).collect(Collectors.toList()); + map.put(key, departBasketItems); + } + return map; + } + /** * 回收拆分申请单时,添加旧回收单删除物品、删除被拆分的加急物品、删除被拆分的丢失报损物品 * @param recyclingContext */ private void splitRecyclingContext(RecyclingContext recyclingContext, RecyclingApplication invoicePlan) { JSONObject jsonParamObject = recyclingContext.getJsonParamObject(); - JSONArray splitTousseJsonArray = jsonParamObject.optJSONArray("splitTousseJson"); - if(splitTousseJsonArray != null && splitTousseJsonArray.size() > 0){ + List splitTousseJsonArray = recyclingContext.splitTousseJson(); + if(splitTousseJsonArray != null && !splitTousseJsonArray.isEmpty()){ // 添加旧回收单删除物品、删除被拆分的加急物品、删除被拆分的丢失报损物品 JSONArray delToussItems = jsonParamObject.optJSONArray("delToussItems"); JSONArray urgentTousseItems = jsonParamObject.optJSONArray("urgentTousseItems"); @@ -10532,27 +10623,25 @@ } // 只包含器械包的数组(非整包清洗的材料组合成器械包) - JSONArray tempSplitJsonArray = new JSONArray(); + List tempSplitJsonArray = new ArrayList<>(); // 拆分的器械包数量(key=tousseDefinitionID + "_" + tousseName + "_" + depart) - Map splitTdAmoutMap = new HashMap(); - for (Object object : splitTousseJsonArray) { - JSONObject jsonObj = (JSONObject) object; - String itemType = jsonObj.optString("itemType"); - if("材料".equals(itemType)){ - String tousseDefinitionID = jsonObj.optString("tousseDefinitionID"); + Map splitTdAmoutMap = new HashMap<>(); + for (SplitSaveRecyclingTousseParam jsonObj : splitTousseJsonArray) { + if(jsonObj.itemTypeMaterial()){ + Long tousseDefinitionID = jsonObj.getTousseDefinitionID(); // 将非整包清洗放入篮筐的材料组合成器械包 - String tousseName = jsonObj.optString("tousseNameForMaterial"); - String depart = jsonObj.optString("depart"); + String tousseName = jsonObj.getTousseNameForMaterial(); + String depart = jsonObj.getDepart(); // 被拆分的器械包数量(非整包清洗的情况,每个材料都会记被拆分的器械包数量) - Integer splitAmount = jsonObj.optInt("amount", 0); + Integer splitAmount = jsonObj.getAmount(); String key = tousseDefinitionID + "_" + tousseName + "_" + depart; if(splitTdAmoutMap.containsKey(key)) continue; - JSONObject tousseJson = new JSONObject(); - tousseJson.put("tousseDefinitionID", tousseDefinitionID); - tousseJson.put("tousseName", tousseName); - tousseJson.put("depart", depart); - tousseJson.put("amount", splitAmount); + SplitSaveRecyclingTousseParam tousseJson = new SplitSaveRecyclingTousseParam(); + tousseJson.setTousseDefinitionID(tousseDefinitionID); + tousseJson.setTousseName(tousseName); + tousseJson.setDepart(depart); + tousseJson.setAmount(splitAmount); splitTdAmoutMap.put(key, splitAmount); tempSplitJsonArray.add(tousseJson); } else { @@ -10561,26 +10650,25 @@ } // 器械包的拆分总数(key=tousseDefinitionID + "_" + tousseName) - Map tdAmountMap = new HashMap(); + Map tdAmountMap = new HashMap<>(); // 删除被拆分的加急物品、删除被拆分的丢失报损物品 - for (Object object : tempSplitJsonArray) { - JSONObject jsonObj = (JSONObject) object; - String tousseDefinitionID = jsonObj.optString("tousseDefinitionID"); - String tousseName = jsonObj.optString("tousseName"); + for (SplitSaveRecyclingTousseParam jsonObj : tempSplitJsonArray) { + Long tousseDefinitionID = jsonObj.getTousseDefinitionID(); + String tousseName = jsonObj.getTousseName(); // 被拆分的器械包数量(非整包清洗的情况,每个材料都会记被拆分的器械包数量) - Integer splitAmount = jsonObj.optInt("amount", 0); + Integer splitAmount = jsonObj.getAmount(); String key = tousseDefinitionID + "_" + tousseName; if(tdAmountMap.containsKey(key)){ - Integer amount = tdAmountMap.get(key); - tdAmountMap.put(key, splitAmount + amount); + tdAmountMap.compute(key, (k, amount) -> MathTools.add(splitAmount, amount).intValue()); continue; } tdAmountMap.put(key, splitAmount); for (Object object2 : urgentTousseItems) { JSONObject urgentJson = (JSONObject) object2; - if(urgentJson.optString("tousseDefinitionID","").equals(tousseDefinitionID) + long tdUrgent = urgentJson.optLong("tousseDefinitionID",0L); + if(Objects.equals(tdUrgent, tousseDefinitionID) && urgentJson.optString("tousseName","").equals(tousseName)){ removeUrgentTousseItems.add(urgentJson); break; @@ -10589,20 +10677,24 @@ for (Object object3 : errorDamageDetail) { JSONObject errorDamageJson = (JSONObject) object3; - if(errorDamageJson.optString("tousseDefinitionID","").equals(tousseDefinitionID)){ + long tdErr = errorDamageJson.optLong("tousseDefinitionID",0L); + if(Objects.equals(tdErr, tousseDefinitionID)){ removeErrorDamageDetail.add(errorDamageJson); } } } // 拆分物品数量和回收物品数量做对比,拆分数量==回收数量时,回收单需要删除物品 JSONArray recyclingItemArray = jsonParamObject.optJSONArray("recyclingItemArray"); + // 如果是回收申请单第一次保存,那拆分数据==回收数量的就删除。 + // 针对回收申请单第一次保存,后面并没有根据delToussItems的参数做处理 + List listToRemove = new ArrayList<>(); for(Object itemJson : recyclingItemArray){ JSONObject json = (JSONObject) itemJson; String tousseName = json.optString("tousseName"); String tousseDefinitionID = json.optString("tousseDefinitionID"); String key = tousseDefinitionID + "_" + tousseName; - Integer applicationAmount = json.optInt("applicationAmount", 0); + Integer applicationAmount = invoicePlan == null ? json.optInt("recycleAmount"):json.optInt("applicationAmount", 0); Integer amount = tdAmountMap.get(key); amount = amount == null ? MathTools.ZERO_INTEGER : amount; if(applicationAmount > 0 && amount >= applicationAmount){ @@ -10611,8 +10703,14 @@ delJson.put("tousseName", tousseName); delJson.put("amount", 0); delToussItems.add(delJson); + listToRemove.add(json); } } + if(invoicePlan == null){ + // 回收申请单第一次保存 + recyclingItemArray.removeAll(listToRemove); + jsonParamObject.put("recyclingItemArray", recyclingItemArray); + } jsonParamObject.put("splitTousseJson", splitTousseJsonArray); jsonParamObject.put("delToussItems", delToussItems); @@ -10621,9 +10719,9 @@ jsonParamObject.put("urgentTousseItems", urgentTousseItems); jsonParamObject.put("errorDamageDetail", errorDamageDetail); } + //设置回收的器械包条码 + jsonParamObject = setRecyclingTousseInstanceBarcode(recyclingContext, invoicePlan); if(invoicePlan != null){ - //设置回收的器械包条码 - jsonParamObject = setRecyclingTousseInstanceBarcode(jsonParamObject, invoicePlan); recyclingContext.setSplitRecyclingApplicationId(invoicePlan.getId()); } recyclingContext.setJsonParamObject(jsonParamObject); @@ -10632,16 +10730,16 @@ /** * 设置回收的器械包条码 - * @param jsonParamObject - * @param invoicePlan - * @return + * @param recyclingContext 回收上下文 + * @param invoicePlan 被回收的申请单。可能为null + * @return recyclingContext中的jsonParamObject */ - private JSONObject setRecyclingTousseInstanceBarcode(JSONObject jsonParamObject, RecyclingApplication invoicePlan) { - if(invoicePlan == null){ - return jsonParamObject; - } - //待回收的使用记录录入的器械包实例 - List waiteRecyclingTousseInstanceList = loadInvoicePlanUseRecordWaiteRecyclingTousseInstance(invoicePlan); + private JSONObject setRecyclingTousseInstanceBarcode(RecyclingContext recyclingContext, RecyclingApplication invoicePlan) { + JSONObject jsonParamObject = recyclingContext.getJsonParamObject(); +// if(invoicePlan == null){ +// return jsonParamObject; +// } + List waiteRecyclingTousseInstanceList = loadRecyclingTousseInstances(recyclingContext, invoicePlan); if(CollectionUtils.isEmpty(waiteRecyclingTousseInstanceList)){ return jsonParamObject; } @@ -10651,10 +10749,10 @@ } //优先设置拆分回收的条码 - JSONArray splitJsonParamObject = jsonParamObject.optJSONArray("splitTousseJson"); + List splitJsonParamObject = recyclingContext.splitTousseJson(); splitJsonParamObject = setSplitTousseInstanceBarcode(splitJsonParamObject, waiteRecyclingTousseInstanceMap); //设置拆分回收后,剩下的,原单回收的条码 - JSONArray tousseJsonArray = jsonParamObject.optJSONArray("tousseJson"); + List tousseJsonArray = recyclingContext.tousseJson(); tousseJsonArray = setRecyclingTousseInstanceBarcode(tousseJsonArray, waiteRecyclingTousseInstanceMap); jsonParamObject.put("splitTousseJson", splitJsonParamObject); jsonParamObject.put("tousseJson", tousseJsonArray); @@ -10668,8 +10766,9 @@ * @param waiteRecyclingTousseInstanceMap * @return */ - private JSONArray setRecyclingTousseInstanceBarcode(JSONArray tousseJsonArray, Map>> waiteRecyclingTousseInstanceMap) { - if(tousseJsonArray == null || tousseJsonArray.size() == 0){ + private List setRecyclingTousseInstanceBarcode(List tousseJsonArray, + Map>> waiteRecyclingTousseInstanceMap) { + if(tousseJsonArray == null || tousseJsonArray.isEmpty()){ return tousseJsonArray; } if(MapUtils.isEmpty(waiteRecyclingTousseInstanceMap)){ @@ -10683,13 +10782,9 @@ } for (int i=0;i tousseInstanceList = tousseInstanceMap.get(tousseDefinitionID); if(CollectionUtils.isEmpty(tousseInstanceList)){ continue; @@ -10704,7 +10799,7 @@ tousseInstanceList.removeAll(removeTousseInstanceList); tousseInstanceMap.put(tousseDefinitionID, tousseInstanceList); String splitTousseBarcodes = StringTools.join(tousseBarcodeList, ";"); - json.put("splitTousseBarcodes", splitTousseBarcodes); + json.setSplitTousseBarcodes(splitTousseBarcodes); } return tousseJsonArray; @@ -10714,22 +10809,18 @@ * @param splitJsonParamObject * @return */ - private JSONArray setSplitTousseInstanceBarcode(JSONArray splitJsonParamObject, Map>> waiteRecyclingTousseInstanceMap) { - if(splitJsonParamObject == null || splitJsonParamObject.size() == 0){ + private List setSplitTousseInstanceBarcode(List splitJsonParamObject, Map>> waiteRecyclingTousseInstanceMap) { + if(splitJsonParamObject == null || splitJsonParamObject.isEmpty()){ return splitJsonParamObject; } if(MapUtils.isEmpty(waiteRecyclingTousseInstanceMap)){ return splitJsonParamObject; } for (int i=0;i> tousseInstanceMap = waiteRecyclingTousseInstanceMap.get(depart); if(MapUtils.isEmpty(tousseInstanceMap)){ continue; @@ -10743,23 +10834,26 @@ for(int j=0; j>> getWaiteRecyclingTousseInstanceMap(List waiteRecyclingTousseInstanceList, RecyclingApplication invoicePlan) { @@ -10784,7 +10878,7 @@ } } - if(depart == null){ + if(depart == null && invoicePlan != null){ depart = invoicePlan.getDepart(); } @@ -10817,16 +10911,16 @@ if(StringUtils.isNotBlank(td.getAssetsBelongCode())){ depart = orgUnitCodeAndNameMap.get(td.getAssetsBelongCode()); } - if(depart == null){ + if(depart == null && invoicePlan != null){ depart = invoicePlan.getDepart(); } Map> tousseInstanceMap = waiteRecyclingTousseInstanceMap.get(depart); if(tousseInstanceMap == null){ - tousseInstanceMap = new HashMap>(); + tousseInstanceMap = new HashMap<>(); } List tousseInstanceList = tousseInstanceMap.get(td.getId()); if(tousseInstanceList == null){ - tousseInstanceList = new ArrayList(); + tousseInstanceList = new ArrayList<>(); } tousseInstanceList.add(ti); tousseInstanceMap.put(td.getId(), tousseInstanceList); @@ -11042,14 +11136,13 @@ * @param invoicePlan * @param splitJsonArray */ - private List splitInvoicePlanTousseItemAmount(InvoicePlan invoicePlan, JSONArray splitJsonArray) { + private List splitInvoicePlanTousseItemAmount(InvoicePlan invoicePlan, List splitJsonArray) { List willTerminateItems = new ArrayList(); Map splitAmountMap = new HashMap(); - for (Object object : splitJsonArray) { - JSONObject jsonObj = (JSONObject) object; - Long tousseDefinitionID = jsonObj.optLong("tousseDefinitionID"); + for (SplitSaveRecyclingTousseParam jsonObj : splitJsonArray) { + Long tousseDefinitionID = jsonObj.getTousseDefinitionID(); // 被拆分的器械包数量(非整包清洗的情况,每个材料都会记被拆分的器械包数量) - Integer splitAmount = jsonObj.optInt("amount", 0); + Integer splitAmount = jsonObj.getAmount(); splitAmountMap.put(tousseDefinitionID, MathTools.add(splitAmount, splitAmountMap.get(tousseDefinitionID)).intValue()); } @@ -11066,11 +11159,11 @@ } Integer applicationAmount = item.getAmount(); Integer recyclingAmount = item.getRecyclingAmount(); - Integer waiteRecyclingAmount = MathTools.sub(applicationAmount, recyclingAmount).intValue(); - if(splitAmount.intValue() > applicationAmount.intValue()){ + int waiteRecyclingAmount = MathTools.sub(applicationAmount, recyclingAmount).intValue(); + if(splitAmount > applicationAmount){ throw new SystemException("拆分数量不能大于申请数量!"); } - if(splitAmount.intValue() > waiteRecyclingAmount.intValue()){ + if(splitAmount > waiteRecyclingAmount){ throw new SystemException("拆分数量不能大于待回收数量!"); } if(splitAmount.intValue() == applicationAmount.intValue()){ @@ -11086,7 +11179,7 @@ } //修改加急数量,加急数量优先保留在原单;直接减去被拆分到回收申请单的加急数量 Integer urgentAmount = item.getUrgentAmount(); - Integer urgentSubAmount = MathTools.sub(item.getAmount(), urgentAmount).intValue(); + int urgentSubAmount = MathTools.sub(item.getAmount(), urgentAmount).intValue(); if(urgentSubAmount >= 0){ continue; } @@ -11166,22 +11259,17 @@ * @param splitJsonArray * @return */ - private Map splitTousseByDepart(JSONArray splitJsonArray) { - Map departToTousseMap = new HashMap(); - if(splitJsonArray == null || splitJsonArray.size() == 0){ + private Map> splitTousseByDepart(List splitJsonArray) { + Map> departToTousseMap = new HashMap<>(); + if(splitJsonArray == null || splitJsonArray.isEmpty()){ return departToTousseMap; } - for (Object object : splitJsonArray) { - JSONObject json = (JSONObject) object; - String depart = json.optString("depart"); - String departCoding = json.optString("departCoding"); + for (SplitSaveRecyclingTousseParam json : splitJsonArray) { + String depart = json.getDepart(); + String departCoding = json.getDepartCoding(); String key = departCoding + "@_@" + depart; - JSONArray arr = departToTousseMap.get(key); - if(arr == null){ - arr = new JSONArray(); - } - arr.add(json); - departToTousseMap.put(key, arr); + List arr = departToTousseMap.computeIfAbsent(key, k -> new ArrayList<>()); + arr.add(json); } return departToTousseMap; } @@ -11190,13 +11278,15 @@ * 需要注意的是 以后物品新加功能和属性 拆出去容易因为参数未被拆 出现拆出去的物品缺失功能和属性 * 根据拆分物品的JSONArray,构建回收参数 * @param jsonArray 拆分物品 - * @param oldJsonParam + * @param oldRecyclingContext 旧的回收上下文 * @return */ - private String bulidJsonParam(JSONArray jsonArray, JSONObject oldJsonParam) { - if(jsonArray == null || jsonArray.size() == 0){ + private String buildJsonParam(List jsonArray,RecyclingContext oldRecyclingContext, DepartmentAware departmentAware) { + if(jsonArray == null || jsonArray.isEmpty()){ return ""; } + JSONObject oldJsonParam = oldRecyclingContext.getJsonParamObject(); + List oldBasketItems = oldRecyclingContext.tousseJson(); String jsonParam = ""; // jsonParam JSONObject jsonObject = new JSONObject(); @@ -11241,13 +11331,11 @@ oldCameraPhotoInfo = new JSONArray(); } Set tdNameSet = new HashSet(); - for (Object object : jsonArray) { - JSONObject jsonObj = (JSONObject) object; - String tousseDefinitionID = jsonObj.optString("tousseDefinitionID"); - String tousseName = jsonObj.optString("tousseName"); - String tousseNameForMaterial = jsonObj.optString("tousseNameForMaterial"); - String itemType = jsonObj.optString("itemType"); - if("材料".equals(itemType)){ + for (RecyclingBasketItemVo jsonObj : jsonArray) { + Long tousseDefinitionID = jsonObj.getTousseDefinitionID(); + String tousseName = jsonObj.getTousseName(); + String tousseNameForMaterial = jsonObj.getTousseNameForMaterial(); + if(jsonObj.itemTypeMaterial()){ tousseName = tousseNameForMaterial; } @@ -11260,7 +11348,8 @@ for(int i=0;i { + // do nothing + }; + class CardinalNumLimitValidatorImpl implements CardinalNumLimitValidator { + private final DepartmentAware departmentAware; + private final Map> tousseNameToAmountMap; + private CardinalNumLimitValidatorImpl(DepartmentAware departmentAware, Map> tousseNameToAmountMap) { + this.departmentAware = departmentAware; + this.tousseNameToAmountMap = tousseNameToAmountMap; + } + @Override + public void validate(String tousseName, Integer amount) { + if(tousseNameToAmountMap.containsKey(tousseName)){ + Map amountMap = tousseNameToAmountMap.get(tousseName); + if(MapUtils.isNotEmpty(amountMap) && amountMap.get(TousseInstance.STATUS_CARDINALNUM) != null){ + Integer cardinalNum = amountMap.get(TousseInstance.STATUS_CARDINALNUM);//设置的基数 + if(cardinalNum != null && cardinalNum != 0){ + Integer existsAmount = amountMap.get(TousseInstance.STATUS_USEDCARDINALNUM); + if(existsAmount >= cardinalNum){ + throw new RuntimeException(departmentAware.getDepart()+ "的" + tousseName + "的回收数量超过了基数上限,最大可回收数量为0"); + } + Integer surplusBaseAmount = cardinalNum - existsAmount; + if(amount > surplusBaseAmount){ + throw new RuntimeException(departmentAware.getDepart()+ "的" + tousseName + "的回收数量超过了基数上限,最大可回收数量为" + surplusBaseAmount); + } + } + } + } + } + } + + static CardinalNumLimitValidator validator(TousseInstanceManager tousseInstanceManager, DepartmentAware departmentAware, List tousseJson) { + //是否禁用基数限制 + boolean disableCardinalNumLimit = CssdUtils.getSystemSetConfigByNameBool("disableCardinalNumLimit", false); + //<包名,<状态,数量>> + Map> tousseNameToAmountMap = new HashMap>(); + if(!disableCardinalNumLimit && StringUtils.isNotBlank(departmentAware.getDepartCode())){//申请科室为空做不了基数验证 + Set tousseNameSet = new HashSet(); + // 基数验证第一步:收集需要验证的包名 + for (RecyclingBasketItemVo recyclingBasketItemVo : tousseJson) { + tousseNameSet.add(recyclingBasketItemVo.getTousseName()); + } + if(CollectionUtils.isNotEmpty(tousseNameSet)){ + //基数验证第二步:统一查询科室库存基数和已用基数 + tousseNameToAmountMap = tousseInstanceManager.getTousseAmountByGroupStatus(departmentAware.getDepartCode(), tousseNameSet, false, true); + return new CardinalNumLimitValidatorImpl(departmentAware, tousseNameToAmountMap); + } + } + // 未启用或者申请科室为空或者器械包为空,不需要验证 + return EMPTY; + } +} Index: ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManager.java =================================================================== diff -u -r41453 -r41537 --- ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManager.java (.../RecyclingRecordManager.java) (revision 41453) +++ ssts-recyclingrecord/src/main/java/com/forgon/disinfectsystem/recyclingrecord/service/RecyclingRecordManager.java (.../RecyclingRecordManager.java) (revision 41537) @@ -4,6 +4,7 @@ import java.util.Map; import java.util.Set; +import com.forgon.disinfectsystem.recyclingrecord.param.RecyclingSplitLoadParam; import net.sf.json.JSONArray; import net.sf.json.JSONObject; @@ -278,6 +279,13 @@ * @return */ public Map loadApplicationTousseSplitByOrgUnit(Long invoicePlanId); + + /** + * 针对回收申请单的拆单处理 + * @param param 回收申请单回收物品的信息 + * @return 拆单信息 + */ + Map loadRecyclingTousseSplitByOrgUnit(RecyclingSplitLoadParam param); /** * 保存并拆分回收记录(PYQZYY-173 多手术室器械包流转功能改造)