diff --git a/src/main/java/com/yunhai/svchall/model/svc/SvcModels.java b/src/main/java/com/yunhai/svchall/model/svc/SvcModels.java index f1d07b1..6636181 100644 --- a/src/main/java/com/yunhai/svchall/model/svc/SvcModels.java +++ b/src/main/java/com/yunhai/svchall/model/svc/SvcModels.java @@ -355,6 +355,8 @@ public final class SvcModels { private String schemaJson; private LocalDateTime publishTime; private Boolean referenced; + /** 绑定本表单的事项明细(仅 list 填充;表单页反查使用位置) */ + private List refItems; public static SvcFormVo of(com.yunhai.svchall.entity.ServiceForm form, Boolean referenced) { SvcFormVo vo = new SvcFormVo(); @@ -368,6 +370,26 @@ public final class SvcModels { vo.setReferenced(referenced); return vo; } + + /** 表单绑定事项的摘要行(表单 sch 级共享,跨站点绑定如实展示站点) */ + @Data + public static class ItemBrief { + private Integer id; + private String itemCode; + private String title; + private String siteCode; + private Integer status; + + public static ItemBrief of(com.yunhai.svchall.entity.ServiceItem item) { + ItemBrief brief = new ItemBrief(); + brief.setId(item.getId()); + brief.setItemCode(item.getItemCode()); + brief.setTitle(item.getTitle()); + brief.setSiteCode(item.getSiteCode()); + brief.setStatus(item.getStatus()); + return brief; + } + } } // ===== 办件 ===== diff --git a/src/main/java/com/yunhai/svchall/repository/ServiceItemRepository.java b/src/main/java/com/yunhai/svchall/repository/ServiceItemRepository.java index 2ff26e9..86075c8 100644 --- a/src/main/java/com/yunhai/svchall/repository/ServiceItemRepository.java +++ b/src/main/java/com/yunhai/svchall/repository/ServiceItemRepository.java @@ -40,6 +40,9 @@ public interface ServiceItemRepository extends JpaRepository findBySchIdAndFormCodeIsNotNull(Long schId); + // ===== 门户筛选项计数(已发布口径,group by 一次往返/维度,承袭 CMS 口径)===== @Query("select i.objectKey, count(i) from ServiceItem i" diff --git a/src/main/java/com/yunhai/svchall/service/impl/ServiceFormService.java b/src/main/java/com/yunhai/svchall/service/impl/ServiceFormService.java index 9d94007..0cfeb85 100644 --- a/src/main/java/com/yunhai/svchall/service/impl/ServiceFormService.java +++ b/src/main/java/com/yunhai/svchall/service/impl/ServiceFormService.java @@ -4,6 +4,7 @@ import com.yunhai.platform.apistd.BizException; import com.yunhai.platform.utils.exception.ErrorCode; import com.yunhai.platform.utils.model.BoaUser; import com.yunhai.svchall.entity.ServiceForm; +import com.yunhai.svchall.entity.ServiceItem; import com.yunhai.svchall.model.svc.SvcModels.DeleteRequest; import com.yunhai.svchall.model.svc.SvcModels.SvcFormSaveRequest; import com.yunhai.svchall.model.svc.SvcModels.SvcFormVo; @@ -18,7 +19,10 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; /** @@ -33,10 +37,14 @@ public class ServiceFormService { private final ServiceItemRepository itemRepository; private final SiteGuard siteGuard; - /** 表单列表(sch 级;同 code 多版本聚合成一行最新版,referenced=是否被事项绑定) */ + /** 表单列表(sch 级;同 code 多版本聚合成一行最新版,referenced=是否被事项绑定,refItems=绑定事项明细) */ public List list(BoaUser user, String siteCode) { SiteInfo site = siteGuard.requireSiteManager(user, siteCode); List forms = formRepository.findBySchIdOrderByCodeAscVersionDescIdAsc(site.getSchId()); + Map> refs = new HashMap<>(); + for (ServiceItem item : itemRepository.findBySchIdAndFormCodeIsNotNull(site.getSchId())) { + refs.computeIfAbsent(item.getFormCode(), k -> new ArrayList<>()).add(SvcFormVo.ItemBrief.of(item)); + } List result = new ArrayList<>(); String lastCode = null; for (ServiceForm form : forms) { @@ -44,7 +52,10 @@ public class ServiceFormService { continue; } lastCode = form.getCode(); - result.add(SvcFormVo.of(form, itemRepository.countBySchIdAndFormCode(site.getSchId(), form.getCode()) > 0)); + List briefs = refs.getOrDefault(form.getCode(), Collections.emptyList()); + SvcFormVo vo = SvcFormVo.of(form, !briefs.isEmpty()); + vo.setRefItems(briefs); + result.add(vo); } return result; } diff --git a/src/test/java/com/yunhai/svchall/service/impl/ServiceFormServiceTest.java b/src/test/java/com/yunhai/svchall/service/impl/ServiceFormServiceTest.java new file mode 100644 index 0000000..9de6480 --- /dev/null +++ b/src/test/java/com/yunhai/svchall/service/impl/ServiceFormServiceTest.java @@ -0,0 +1,133 @@ +package com.yunhai.svchall.service.impl; + +import com.yunhai.svchall.entity.ServiceForm; +import com.yunhai.svchall.entity.ServiceItem; +import com.yunhai.svchall.model.svc.SvcModels.SvcFormVo; +import com.yunhai.svchall.repository.ServiceFormRepository; +import com.yunhai.svchall.repository.ServiceItemRepository; +import com.yunhai.svchall.service.SiteDirectory; +import com.yunhai.svchall.service.SiteGuard; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * 表单列表使用场景可见性单测:list() 必须带回"被哪些事项绑定"的明细(refItems), + * 管理员才能从表单页反查使用位置(一期可见闭环);表单 sch 级共享,跨站点绑定如实展示。 + */ +class ServiceFormServiceTest { + + private ServiceFormRepository formRepository; + private ServiceItemRepository itemRepository; + private ServiceFormService service; + private SiteDirectory.SiteInfo site = new SiteDirectory.SiteInfo("S1", "站点一", 1L); + + @BeforeEach + void setUp() { + formRepository = mock(ServiceFormRepository.class); + itemRepository = mock(ServiceItemRepository.class); + SiteGuard siteGuard = mock(SiteGuard.class); + when(siteGuard.requireSiteManager(null, "S1")).thenReturn(site); + service = new ServiceFormService(formRepository, itemRepository, siteGuard); + } + + private ServiceForm form(String code, int version, int status) { + ServiceForm form = new ServiceForm(); + form.setSchId(1L); + form.setCode(code); + form.setName("表单-" + code); + form.setVersion(version); + form.setStatus(status); + form.setSchemaJson("{\"groups\":[]}"); + return form; + } + + private ServiceItem item(Integer id, String siteCode, String itemCode, String title, String formCode) { + ServiceItem item = new ServiceItem(); + item.setSchId(1L); + item.setSiteCode(siteCode); + item.setItemCode(itemCode); + item.setTitle(title); + item.setFormCode(formCode); + item.setStatus(1); + item.setId(id); + return item; + } + + @Test + void unboundFormHasEmptyRefItemsAndNotReferenced() { + when(formRepository.findBySchIdOrderByCodeAscVersionDescIdAsc(1L)) + .thenReturn(Arrays.asList(form("leave", 1, 1))); + when(itemRepository.findBySchIdAndFormCodeIsNotNull(1L)).thenReturn(Arrays.asList()); + + SvcFormVo vo = service.list(null, "S1").get(0); + assertFalse(vo.getReferenced()); + assertTrue(vo.getRefItems().isEmpty(), "未绑定表单的 refItems 应为空列表而非 null"); + } + + @Test + void boundFormCarriesRefItemBrief() { + when(formRepository.findBySchIdOrderByCodeAscVersionDescIdAsc(1L)) + .thenReturn(Arrays.asList(form("leave", 1, 1))); + when(itemRepository.findBySchIdAndFormCodeIsNotNull(1L)) + .thenReturn(Arrays.asList(item(11, "S1", "IT-001", "请假申请", "leave"))); + + SvcFormVo vo = service.list(null, "S1").get(0); + assertTrue(vo.getReferenced()); + assertEquals(1, vo.getRefItems().size()); + SvcFormVo.ItemBrief brief = vo.getRefItems().get(0); + assertEquals(Integer.valueOf(11), brief.getId()); + assertEquals("IT-001", brief.getItemCode()); + assertEquals("请假申请", brief.getTitle()); + assertEquals("S1", brief.getSiteCode()); + assertEquals(Integer.valueOf(1), brief.getStatus()); + } + + @Test + void crossSiteBindingsAreAllListed() { + // 表单 sch 级共享:同 sch 下另一站点的事项绑了同一张表单,也要在明细里可见 + when(formRepository.findBySchIdOrderByCodeAscVersionDescIdAsc(1L)) + .thenReturn(Arrays.asList(form("leave", 1, 1))); + when(itemRepository.findBySchIdAndFormCodeIsNotNull(1L)) + .thenReturn(Arrays.asList( + item(11, "S1", "IT-001", "请假申请", "leave"), + item(12, "S2", "IT-002", "教职工请假", "leave"))); + + SvcFormVo vo = service.list(null, "S1").get(0); + assertEquals(2, vo.getRefItems().size()); + } + + @Test + void versionAggregationKeepsRefItemsOnLatestRow() { + // 同 code 多版本聚合出最新版一行;绑定关系挂在 code 上,明细归到该行 + when(formRepository.findBySchIdOrderByCodeAscVersionDescIdAsc(1L)) + .thenReturn(Arrays.asList(form("leave", 2, 0), form("leave", 1, 1))); + when(itemRepository.findBySchIdAndFormCodeIsNotNull(1L)) + .thenReturn(Arrays.asList(item(11, "S1", "IT-001", "请假申请", "leave"))); + + List list = service.list(null, "S1"); + assertEquals(1, list.size()); + assertEquals(Integer.valueOf(2), list.get(0).getVersion()); + assertEquals(1, list.get(0).getRefItems().size()); + } + + @Test + void refItemsAreGroupedToTheirOwnForm() { + when(formRepository.findBySchIdOrderByCodeAscVersionDescIdAsc(1L)) + .thenReturn(Arrays.asList(form("leave", 1, 1), form("seal", 1, 1))); + when(itemRepository.findBySchIdAndFormCodeIsNotNull(1L)) + .thenReturn(Arrays.asList(item(11, "S1", "IT-001", "请假申请", "leave"))); + + List list = service.list(null, "S1"); + assertEquals(0, list.get(1).getRefItems().size(), "seal 表单不应带上 leave 的事项明细"); + assertEquals("seal", list.get(1).getCode()); + } +}