diff --git a/docs/sql/2026-07-30-school-product-config.sql b/docs/sql/2026-07-30-school-product-config.sql new file mode 100644 index 0000000..c0e741e --- /dev/null +++ b/docs/sql/2026-07-30-school-product-config.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS school_product_config ( + school_id VARCHAR(64) NOT NULL PRIMARY KEY, + organization_mode VARCHAR(32) NOT NULL DEFAULT 'ADMIN_CLASS_ENABLED', + student_roster_owner VARCHAR(32) NOT NULL DEFAULT 'SCHOOL_ADMIN', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) COMMENT='school product capability configuration'; + +INSERT INTO school_product_config (school_id, organization_mode, student_roster_owner) +SELECT school_id, 'ADMIN_CLASS_ENABLED', 'SCHOOL_ADMIN' FROM school +ON DUPLICATE KEY UPDATE school_id = VALUES(school_id); + +ALTER TABLE teaching_class_student MODIFY admin_class_id VARCHAR(64) NULL; diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/SchoolProductConfig.java b/src/main/java/com/sztzjy/linkCommerce/entity/SchoolProductConfig.java new file mode 100644 index 0000000..1efca7f --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/SchoolProductConfig.java @@ -0,0 +1,22 @@ +package com.sztzjy.linkCommerce.entity; + +import java.util.Date; + +public class SchoolProductConfig { + private String schoolId; + private String organizationMode; + private String studentRosterOwner; + private Date createTime; + private Date updateTime; + + public String getSchoolId() { return schoolId; } + public void setSchoolId(String schoolId) { this.schoolId = schoolId == null ? null : schoolId.trim(); } + public String getOrganizationMode() { return organizationMode; } + public void setOrganizationMode(String organizationMode) { this.organizationMode = organizationMode == null ? null : organizationMode.trim(); } + public String getStudentRosterOwner() { return studentRosterOwner; } + public void setStudentRosterOwner(String studentRosterOwner) { this.studentRosterOwner = studentRosterOwner == null ? null : studentRosterOwner.trim(); } + public Date getCreateTime() { return createTime; } + public void setCreateTime(Date createTime) { this.createTime = createTime; } + public Date getUpdateTime() { return updateTime; } + public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } +} diff --git a/src/main/java/com/sztzjy/linkCommerce/mapper/SchoolProductConfigMapper.java b/src/main/java/com/sztzjy/linkCommerce/mapper/SchoolProductConfigMapper.java new file mode 100644 index 0000000..4f1ac5a --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/mapper/SchoolProductConfigMapper.java @@ -0,0 +1,13 @@ +package com.sztzjy.linkCommerce.mapper; + +import com.sztzjy.linkCommerce.entity.SchoolProductConfig; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SchoolProductConfigMapper { + SchoolProductConfig selectByPrimaryKey(String schoolId); + + int insert(SchoolProductConfig record); + + int updateByPrimaryKey(SchoolProductConfig record); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/SchoolProductConfigService.java b/src/main/java/com/sztzjy/linkCommerce/service/SchoolProductConfigService.java new file mode 100644 index 0000000..90f4fc9 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/SchoolProductConfigService.java @@ -0,0 +1,13 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.entity.SchoolProductConfig; + +public interface SchoolProductConfigService { + SchoolProductConfig getRequiredConfig(String schoolId); + + boolean requiresAdminClass(String schoolId); + + boolean isTeacherRosterManaged(String schoolId); + + void requireAdminClassEnabled(String schoolId); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/SchoolProductConfigServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/SchoolProductConfigServiceImpl.java new file mode 100644 index 0000000..5cbe4c1 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/SchoolProductConfigServiceImpl.java @@ -0,0 +1,50 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.config.exception.handler.ServiceException; +import com.sztzjy.linkCommerce.entity.SchoolProductConfig; +import com.sztzjy.linkCommerce.mapper.SchoolProductConfigMapper; +import com.sztzjy.linkCommerce.service.SchoolProductConfigService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +@Service +public class SchoolProductConfigServiceImpl implements SchoolProductConfigService { + public static final String ADMIN_CLASS_ENABLED = "ADMIN_CLASS_ENABLED"; + public static final String TEACHING_CLASS_ONLY = "TEACHING_CLASS_ONLY"; + public static final String SCHOOL_ADMIN = "SCHOOL_ADMIN"; + public static final String TEACHER = "TEACHER"; + + @Autowired + public SchoolProductConfigMapper schoolProductConfigMapper; + + @Override + public SchoolProductConfig getRequiredConfig(String schoolId) { + if (StringUtils.isBlank(schoolId)) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "学校组织配置不存在"); + } + SchoolProductConfig config = schoolProductConfigMapper.selectByPrimaryKey(schoolId); + if (config == null) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "学校组织配置不存在"); + } + return config; + } + + @Override + public boolean requiresAdminClass(String schoolId) { + return ADMIN_CLASS_ENABLED.equals(getRequiredConfig(schoolId).getOrganizationMode()); + } + + @Override + public boolean isTeacherRosterManaged(String schoolId) { + return TEACHER.equals(getRequiredConfig(schoolId).getStudentRosterOwner()); + } + + @Override + public void requireAdminClassEnabled(String schoolId) { + if (!requiresAdminClass(schoolId)) { + throw new ServiceException(HttpStatus.BAD_REQUEST, "当前学校未启用行政班"); + } + } +} diff --git a/src/main/resources/mappers/SchoolProductConfigMapper.xml b/src/main/resources/mappers/SchoolProductConfigMapper.xml new file mode 100644 index 0000000..bf91bf8 --- /dev/null +++ b/src/main/resources/mappers/SchoolProductConfigMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + school_id, organization_mode, student_roster_owner, create_time, update_time + + + insert into school_product_config (school_id, organization_mode, student_roster_owner, create_time, update_time) + values (#{schoolId,jdbcType=VARCHAR}, #{organizationMode,jdbcType=VARCHAR}, #{studentRosterOwner,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}) + + + update school_product_config set organization_mode = #{organizationMode,jdbcType=VARCHAR}, student_roster_owner = #{studentRosterOwner,jdbcType=VARCHAR}, update_time = #{updateTime,jdbcType=TIMESTAMP} where school_id = #{schoolId,jdbcType=VARCHAR} + + diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/SchoolProductConfigServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/SchoolProductConfigServiceImplTest.java new file mode 100644 index 0000000..99697c4 --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/SchoolProductConfigServiceImplTest.java @@ -0,0 +1,63 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.config.exception.handler.ServiceException; +import com.sztzjy.linkCommerce.entity.SchoolProductConfig; +import com.sztzjy.linkCommerce.mapper.SchoolProductConfigMapper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class SchoolProductConfigServiceImplTest { + + @Test + void teachingClassOnlySchoolDoesNotRequireAdministrativeClass() { + SchoolProductConfigServiceImpl service = new SchoolProductConfigServiceImpl(); + service.schoolProductConfigMapper = mock(SchoolProductConfigMapper.class); + when(service.schoolProductConfigMapper.selectByPrimaryKey("school-zj")) + .thenReturn(config("school-zj", "TEACHING_CLASS_ONLY", "TEACHER")); + + assertFalse(service.requiresAdminClass("school-zj")); + assertTrue(service.isTeacherRosterManaged("school-zj")); + } + + @Test + void standardSchoolRequiresAdministrativeClass() { + SchoolProductConfigServiceImpl service = new SchoolProductConfigServiceImpl(); + service.schoolProductConfigMapper = mock(SchoolProductConfigMapper.class); + when(service.schoolProductConfigMapper.selectByPrimaryKey("school-standard")) + .thenReturn(config("school-standard", "ADMIN_CLASS_ENABLED", "SCHOOL_ADMIN")); + + assertTrue(service.requiresAdminClass("school-standard")); + assertFalse(service.isTeacherRosterManaged("school-standard")); + } + + @Test + void missingConfigurationIsRejected() { + SchoolProductConfigServiceImpl service = new SchoolProductConfigServiceImpl(); + service.schoolProductConfigMapper = mock(SchoolProductConfigMapper.class); + + assertThrows(ServiceException.class, () -> service.getRequiredConfig("missing-school")); + } + + @Test + void administrativeClassGuardRejectsTeachingClassOnlySchool() { + SchoolProductConfigServiceImpl service = new SchoolProductConfigServiceImpl(); + service.schoolProductConfigMapper = mock(SchoolProductConfigMapper.class); + when(service.schoolProductConfigMapper.selectByPrimaryKey("school-zj")) + .thenReturn(config("school-zj", "TEACHING_CLASS_ONLY", "TEACHER")); + + assertThrows(ServiceException.class, () -> service.requireAdminClassEnabled("school-zj")); + } + + private SchoolProductConfig config(String schoolId, String organizationMode, String rosterOwner) { + SchoolProductConfig config = new SchoolProductConfig(); + config.setSchoolId(schoolId); + config.setOrganizationMode(organizationMode); + config.setStudentRosterOwner(rosterOwner); + return config; + } +}