@@ -504,12 +504,14 @@ public class ActivityService implements IActivityService {
. collect ( Collectors . toList ( ) ) ;
Map < Integer , SchClassDto > classes = schoolClasses ( schId ) ;
List < Integer > targetIds = targetClassIds ( activity , classes ) ;
Set < Integer > targetSet = new LinkedHashSet < > ( targetIds ) ;
// 已读统计: fa_activity_read 为班级粒度,投影到学生数并与接收班级求交(与详情 readnumcount 同口径);
// readClassId s 供名单行按" 该生所在班 是否已读" 填充 read(与 SignupDetail 的 findRead 判定一致)
// 已读统计(学生粒度):readCount = 接收班级内的已读记录条数(即实际打开过的家长数),
// readRate = readCount/should 取整;readKey s 供名单行按「 该生本人 是否已读」 填充 read(与 SignupDetail 判定一致)
List < ActivityRead > reads = activityReadRepository . findByActivityId ( serid ) ;
Set < String > readClassIds = reads . stream ( )
. map ( ActivityRead : : getClassId )
Set < String > readKeys = reads . stream ( )
. map ( r - > readKey ( r . getClassId ( ) , r . getStudentId ( ) ) )
. collect ( Collectors . toSet ( ) ) ;
Map < String , List < ActivitySignup > > byClass = filtered . stream ( )
@@ -518,17 +520,17 @@ public class ActivityService implements IActivityService {
List < SignupGroup > groups = byClass . entrySet ( ) . stream ( )
. map ( e - > new SignupGroup ( e . getKey ( ) , e . getValue ( ) . stream ( )
. map ( r - > toBrief ( r , readClassIds ) ) . collect ( Collectors . toList ( ) ) ) )
. map ( r - > toBrief ( r , readKeys ) ) . collect ( Collectors . toList ( ) ) ) )
. collect ( Collectors . toList ( ) ) ;
Map < Integer , List < SchStudentDto > > byClassStudents = studentsByClass ( schId , targetClassIds ( activity , classes ) ) ;
Map < Integer , List < SchStudentDto > > byClassStudents = studentsByClass ( schId , targetIds ) ;
int should = byClassStudents . values ( ) . stream ( ) . mapToInt ( List : : size ) . sum ( ) ;
if ( should = = 0 & & ! records . isEmpty ( ) ) {
should = records . size ( ) ; // 班级数据不可达时降级为回执表实有人数
}
int readCount = reads . stream ( )
. mapToInt ( r - > byClassStudents . getOrDefault ( parseSafe ( r . getClassId ( ) ) , Collections . emptyList ( ) ) . size ( ) )
. sum ( ) ;
int readCount = ( int ) reads . stream ( )
. filter ( r - > targetSet . contains ( parseSafe ( r . getClassId ( ) ) ) )
. count ( ) ;
SignupsResultVo vo = new SignupsResultVo ( ) ;
vo . setActivityId ( activity . getId ( ) ) ;
@@ -556,7 +558,11 @@ public class ActivityService implements IActivityService {
ActivitySignup record = activitySignupRepository . findById ( signupId )
. orElseThrow ( ( ) - > new BizException ( ErrorCode . PARAM_ERROR , " 回执记录不存在 " ) ) ;
Activity activity = requireActivity ( record . getActivityId ( ) ) ;
ActivityRead read = findRead ( record . getActivityId ( ) , record . getClassId ( ) ) ;
// 已读按学生粒度:该生本人是否打开过(历史班级粒度数据不误标同班其他学生)
ActivityRead read = record . getStudentId ( ) = = null ? null
: activityReadRepository
. findByActivityIdAndClassIdAndStudentId ( record . getActivityId ( ) , record . getClassId ( ) , record . getStudentId ( ) )
. orElse ( null ) ;
SignupDetailVo vo = new SignupDetailVo ( ) ;
vo . setSignupId ( record . getId ( ) ) ;
@@ -908,9 +914,10 @@ public class ActivityService implements IActivityService {
List < Integer > targetIds = targetClassIds ( a , classes ) ;
int allowCount = targetIds . stream ( ) . mapToInt ( t - > classSize . getOrDefault ( t , 0 ) ) . sum ( ) ;
Set < Integer > targetSet = new LinkedHashSet < > ( targetIds ) ;
int readCount = readMap . getOrDefault ( a . getId ( ) , Collections . emptyList ( ) ) . stream ( )
. mapToInt ( r - > targetSet . contains ( parseSafe ( r . getClassId ( ) ) )
? classSize . getOrDefault ( parseSafe ( r . getClassId ( ) ) , 0 ) : 0 ) . sum ( ) ;
// 已读人数 = 接收班级内的已读记录条数(学生粒度),与列表/详情同口径
int readCount = ( int ) readMap . getOrDefault ( a . getId ( ) , Collections . emptyList ( ) ) . stream ( )
. filter ( r - > targetSet . contains ( parseSafe ( r . getClassId ( ) ) ) )
. count ( ) ;
fillProgressItem ( item , computeProgress ( a , records , allowCount ) , readCount , allowCount ) ;
myItems . add ( item ) ;
}
@@ -1165,19 +1172,24 @@ public class ActivityService implements IActivityService {
if ( pc . myClassId = = null | | candidates . isEmpty ( ) ) {
return Collections . emptySet ( ) ;
}
Integer myStudentId = studentIdOf ( pc ) ;
List < Integer > ids = candidates . stream ( ) . map ( Activity : : getId ) . collect ( Collectors . toList ( ) ) ;
// 已读是班级粒度(fa_activity_read 唯一键为 activity+class, studentId 只是首读学生),
// 不能按学生过滤,否则同班其他学生永远「未读」
// 已读按学生粒度:本人(学生身份)打开过才算已读;studentId 缺失的存量数据退化为班级粒度
return activityReadRepository
. findByActivityIdIn ( ids ) . stream ( )
. filter ( r - > pc . myClassId . equals ( r . getClassId ( ) ) )
. filter ( r - > myStudentId = = null | | myStudentId . equals ( r . getStudentId ( ) ) )
. map ( ActivityRead : : getActivityId )
. collect ( Collectors . toSet ( ) ) ;
}
/** 标读(班级 粒度,幂等) */
/** 标读(学生 粒度,幂等:同一家长重复打开只记一条;学生档案不可得时退化为班级粒度 ) */
private void markRead ( Integer activityId , String classId , Integer studentId ) {
if ( activityReadRepository . findByActivityIdAndClassId ( activityId , classId ) . isPresent ( ) ) {
if ( studentId = = null ) {
if ( activityReadRepository . findByActivityIdAndClassId ( activityId , classId ) . isPresent ( ) ) {
return ;
}
} else if ( activityReadRepository . existsByActivityIdAndClassIdAndStudentId ( activityId , classId , studentId ) ) {
return ;
}
ActivityRead read = new ActivityRead ( ) ;
@@ -1189,13 +1201,6 @@ public class ActivityService implements IActivityService {
activityReadRepository . save ( read ) ;
}
private ActivityRead findRead ( Integer activityId , String classId ) {
if ( classId = = null ) {
return null ;
}
return activityReadRepository . findByActivityIdAndClassId ( activityId , classId ) . orElse ( null ) ;
}
/** confirm 模式:家长打开活动即自动确认(未回执 → 已确认) */
private void autoConfirmIfNeeded ( Activity activity , Integer studentId ) {
if ( ! ReceiptModeEnum . CONFIRM . getCode ( ) . equalsIgnoreCase ( activity . getReceiptMode ( ) ) ) {
@@ -1481,10 +1486,10 @@ public class ActivityService implements IActivityService {
List < Integer > targetIds = targetClassIds ( a , classes ) ;
Set < Integer > targetSet = new LinkedHashSet < > ( targetIds ) ;
int allowCount = targetIds . stream ( ) . mapToInt ( t - > classSize . getOrDefault ( t , 0 ) ) . sum ( ) ;
// 已读只按目标班级累计( 收窄接收班级后的残留已读行 不计入)
int readCount = readMap . getOrDefault ( a . getId ( ) , Collections . emptyList ( ) ) . stream ( )
. mapToInt ( r - > targetSet . contains ( parseSafe ( r . getClassId ( ) ) )
? classSize . getOrDefault ( parseSafe ( r . getClassId ( ) ) , 0 ) : 0 ) . sum ( ) ;
// 已读人数 = 接收班级内的已读记录条数(学生粒度,一人一条; 收窄接收班级后的出界记录 不计入)
int readCount = ( int ) readMap . getOrDefault ( a . getId ( ) , Collections . emptyList ( ) ) . stream ( )
. filter ( r - > targetSet . contains ( parseSafe ( r . getClassId ( ) ) ) )
. count ( ) ;
vo . setAllowCount ( allowCount ) ;
vo . setReadCount ( readCount ) ;
// 管理端列表不填已读标志:fa_activity_read 是班级粒度且 studentId 只记首读学生,
@@ -1593,9 +1598,10 @@ public class ActivityService implements IActivityService {
List < Integer > targetIds = targetClassIds ( a , classes ) ;
Map < Integer , List < SchStudentDto > > byClass = studentsByClass ( schId , targetIds ) ;
int allowCount = byClass . values ( ) . stream ( ) . mapToInt ( List : : size ) . sum ( ) ;
int readCount = activityReadRepository . findByActivityId ( a . getId ( ) ) . stream ( )
. mapToInt ( r - > byClass . getOrDefault ( parseSafe ( r . getClassId ( ) ) , Collections . emptyList ( ) ) . size ( ) )
. sum ( ) ;
// 已读人数 = 接收班级内的已读记录条数(学生粒度),与列表 readCount 同口径
int readCount = ( int ) activityReadRepository . findByActivityId ( a . getId ( ) ) . stream ( )
. filter ( r - > byClass . containsKey ( parseSafe ( r . getClassId ( ) ) ) )
. count ( ) ;
vo . setNumcount ( allowCount ) ;
vo . setReadnumcount ( readCount ) ;
if ( a . isPublished ( ) & & ! ReceiptModeEnum . NONE . getCode ( ) . equalsIgnoreCase ( a . getReceiptMode ( ) ) ) {
@@ -1606,7 +1612,7 @@ public class ActivityService implements IActivityService {
return vo ;
}
private SignupBrief toBrief ( ActivitySignup r , Set < String > readClassIds ) {
private SignupBrief toBrief ( ActivitySignup r , Set < String > readKeys ) {
SignupBrief brief = new SignupBrief ( ) ;
brief . setSignupId ( r . getId ( ) ) ;
brief . setStudentId ( r . getStudentId ( ) ) ;
@@ -1620,11 +1626,16 @@ public class ActivityService implements IActivityService {
brief . setSignedAt ( fmt ( r . getSignedAt ( ) ) ) ;
boolean receipt = ! SignupStatusEnum . NONE . getCode ( ) . equalsIgnoreCase ( r . getStatus ( ) ) ;
brief . setHasReceipt ( receipt ) ;
brief . setRead ( readClassIds . contains ( r . getClassId ( ) ) ) ;
brief . setRead ( readKeys . contains ( readKey ( r . getClassId ( ) , r . getStudentId ( ) ) ) ) ;
brief . setRemark ( r . getRemark ( ) ) ;
return brief ;
}
/** 学生粒度已读键:classId:studentId( studentId 为 null 的存量数据按 classId:null 匹配,互不误标) */
private String readKey ( String classId , Integer studentId ) {
return classId + " : " + studentId ;
}
/** 是否占用名额(已报名/已确认) */
private boolean isOccupying ( ActivitySignup r ) {
return SignupStatusEnum . SIGNED . getCode ( ) . equalsIgnoreCase ( r . getStatus ( ) )