- SvcFormVo 增 refItems(ItemBrief: id/itemCode/title/siteCode/status),referenced 同源派生;仅 list 填充,versions/get 不带 - ServiceItemRepository.findBySchIdAndFormCodeIsNotNull 一次查全量按 formCode 内存分组,替代逐表单 count 的 N+1 - ServiceFormServiceTest 5 用例(未绑定/单绑/跨站点/多版本聚合/分组不串) - 前端配套(svc-hall-front 另推):绑定明细弹窗+直达事项编辑、发布未绑定引导、schema 预览、事项编辑页草稿表单提示
This commit is contained in:
@@ -355,6 +355,8 @@ public final class SvcModels {
|
|||||||
private String schemaJson;
|
private String schemaJson;
|
||||||
private LocalDateTime publishTime;
|
private LocalDateTime publishTime;
|
||||||
private Boolean referenced;
|
private Boolean referenced;
|
||||||
|
/** 绑定本表单的事项明细(仅 list 填充;表单页反查使用位置) */
|
||||||
|
private List<ItemBrief> refItems;
|
||||||
|
|
||||||
public static SvcFormVo of(com.yunhai.svchall.entity.ServiceForm form, Boolean referenced) {
|
public static SvcFormVo of(com.yunhai.svchall.entity.ServiceForm form, Boolean referenced) {
|
||||||
SvcFormVo vo = new SvcFormVo();
|
SvcFormVo vo = new SvcFormVo();
|
||||||
@@ -368,6 +370,26 @@ public final class SvcModels {
|
|||||||
vo.setReferenced(referenced);
|
vo.setReferenced(referenced);
|
||||||
return vo;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 办件 =====
|
// ===== 办件 =====
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ public interface ServiceItemRepository extends JpaRepository<ServiceItem, Intege
|
|||||||
/** 表单被事项绑定计数(表单删除守护,按 form_code 判) */
|
/** 表单被事项绑定计数(表单删除守护,按 form_code 判) */
|
||||||
long countBySchIdAndFormCode(Long schId, String formCode);
|
long countBySchIdAndFormCode(Long schId, String formCode);
|
||||||
|
|
||||||
|
/** 已绑表单的全部事项(表单列表 refItems 明细,一次查出按 formCode 内存分组) */
|
||||||
|
List<ServiceItem> findBySchIdAndFormCodeIsNotNull(Long schId);
|
||||||
|
|
||||||
// ===== 门户筛选项计数(已发布口径,group by 一次往返/维度,承袭 CMS 口径)=====
|
// ===== 门户筛选项计数(已发布口径,group by 一次往返/维度,承袭 CMS 口径)=====
|
||||||
|
|
||||||
@Query("select i.objectKey, count(i) from ServiceItem i"
|
@Query("select i.objectKey, count(i) from ServiceItem i"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.yunhai.platform.apistd.BizException;
|
|||||||
import com.yunhai.platform.utils.exception.ErrorCode;
|
import com.yunhai.platform.utils.exception.ErrorCode;
|
||||||
import com.yunhai.platform.utils.model.BoaUser;
|
import com.yunhai.platform.utils.model.BoaUser;
|
||||||
import com.yunhai.svchall.entity.ServiceForm;
|
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.DeleteRequest;
|
||||||
import com.yunhai.svchall.model.svc.SvcModels.SvcFormSaveRequest;
|
import com.yunhai.svchall.model.svc.SvcModels.SvcFormSaveRequest;
|
||||||
import com.yunhai.svchall.model.svc.SvcModels.SvcFormVo;
|
import com.yunhai.svchall.model.svc.SvcModels.SvcFormVo;
|
||||||
@@ -18,7 +19,10 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,10 +37,14 @@ public class ServiceFormService {
|
|||||||
private final ServiceItemRepository itemRepository;
|
private final ServiceItemRepository itemRepository;
|
||||||
private final SiteGuard siteGuard;
|
private final SiteGuard siteGuard;
|
||||||
|
|
||||||
/** 表单列表(sch 级;同 code 多版本聚合成一行最新版,referenced=是否被事项绑定) */
|
/** 表单列表(sch 级;同 code 多版本聚合成一行最新版,referenced=是否被事项绑定,refItems=绑定事项明细) */
|
||||||
public List<SvcFormVo> list(BoaUser user, String siteCode) {
|
public List<SvcFormVo> list(BoaUser user, String siteCode) {
|
||||||
SiteInfo site = siteGuard.requireSiteManager(user, siteCode);
|
SiteInfo site = siteGuard.requireSiteManager(user, siteCode);
|
||||||
List<ServiceForm> forms = formRepository.findBySchIdOrderByCodeAscVersionDescIdAsc(site.getSchId());
|
List<ServiceForm> forms = formRepository.findBySchIdOrderByCodeAscVersionDescIdAsc(site.getSchId());
|
||||||
|
Map<String, List<SvcFormVo.ItemBrief>> refs = new HashMap<>();
|
||||||
|
for (ServiceItem item : itemRepository.findBySchIdAndFormCodeIsNotNull(site.getSchId())) {
|
||||||
|
refs.computeIfAbsent(item.getFormCode(), k -> new ArrayList<>()).add(SvcFormVo.ItemBrief.of(item));
|
||||||
|
}
|
||||||
List<SvcFormVo> result = new ArrayList<>();
|
List<SvcFormVo> result = new ArrayList<>();
|
||||||
String lastCode = null;
|
String lastCode = null;
|
||||||
for (ServiceForm form : forms) {
|
for (ServiceForm form : forms) {
|
||||||
@@ -44,7 +52,10 @@ public class ServiceFormService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
lastCode = form.getCode();
|
lastCode = form.getCode();
|
||||||
result.add(SvcFormVo.of(form, itemRepository.countBySchIdAndFormCode(site.getSchId(), form.getCode()) > 0));
|
List<SvcFormVo.ItemBrief> briefs = refs.getOrDefault(form.getCode(), Collections.emptyList());
|
||||||
|
SvcFormVo vo = SvcFormVo.of(form, !briefs.isEmpty());
|
||||||
|
vo.setRefItems(briefs);
|
||||||
|
result.add(vo);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<SvcFormVo> 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<SvcFormVo> list = service.list(null, "S1");
|
||||||
|
assertEquals(0, list.get(1).getRefItems().size(), "seal 表单不应带上 leave 的事项明细");
|
||||||
|
assertEquals("seal", list.get(1).getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user