9 changed files with 732 additions and 0 deletions
@ -0,0 +1,63 @@ |
|||||
|
package com.mydreamjob.onboarding; |
||||
|
|
||||
|
import jakarta.persistence.Column; |
||||
|
import jakarta.persistence.Entity; |
||||
|
import jakarta.persistence.EntityListeners; |
||||
|
import jakarta.persistence.GeneratedValue; |
||||
|
import jakarta.persistence.GenerationType; |
||||
|
import jakarta.persistence.Id; |
||||
|
import jakarta.persistence.Table; |
||||
|
import jakarta.persistence.UniqueConstraint; |
||||
|
import jakarta.persistence.Version; |
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Builder; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import org.springframework.data.annotation.CreatedDate; |
||||
|
import org.springframework.data.annotation.LastModifiedDate; |
||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener; |
||||
|
|
||||
|
import java.time.LocalDateTime; |
||||
|
|
||||
|
@Data |
||||
|
@Builder |
||||
|
@NoArgsConstructor |
||||
|
@AllArgsConstructor |
||||
|
@Entity |
||||
|
@Table( |
||||
|
name = "company_knowledge", |
||||
|
uniqueConstraints = @UniqueConstraint( |
||||
|
name = "uk_company_knowledge_company", |
||||
|
columnNames = "company_id")) |
||||
|
@EntityListeners(AuditingEntityListener.class) |
||||
|
public class CompanyKnowledge { |
||||
|
|
||||
|
@Id |
||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||
|
private Long id; |
||||
|
|
||||
|
@Column(name = "company_id", nullable = false) |
||||
|
private Long companyId; |
||||
|
|
||||
|
@Column(length = 2000) |
||||
|
private String culture; |
||||
|
|
||||
|
@Column(name = "work_policies", length = 4000) |
||||
|
private String workPolicies; |
||||
|
|
||||
|
@Column(name = "onboarding_guide", length = 4000) |
||||
|
private String onboardingGuide; |
||||
|
|
||||
|
@Version |
||||
|
@Column(nullable = false) |
||||
|
@Builder.Default |
||||
|
private Long version = 0L; |
||||
|
|
||||
|
@CreatedDate |
||||
|
@Column(name = "created_at", nullable = false, updatable = false) |
||||
|
private LocalDateTime createdAt; |
||||
|
|
||||
|
@LastModifiedDate |
||||
|
@Column(name = "updated_at", nullable = false) |
||||
|
private LocalDateTime updatedAt; |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
package com.mydreamjob.onboarding; |
||||
|
|
||||
|
import org.springframework.data.jpa.repository.JpaRepository; |
||||
|
import org.springframework.stereotype.Repository; |
||||
|
|
||||
|
import java.util.Optional; |
||||
|
|
||||
|
@Repository |
||||
|
public interface CompanyKnowledgeRepository extends JpaRepository<CompanyKnowledge, Long> { |
||||
|
|
||||
|
Optional<CompanyKnowledge> findByCompanyId(Long companyId); |
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
package com.mydreamjob.onboarding; |
||||
|
|
||||
|
import jakarta.persistence.Column; |
||||
|
import jakarta.persistence.Entity; |
||||
|
import jakarta.persistence.EntityListeners; |
||||
|
import jakarta.persistence.GeneratedValue; |
||||
|
import jakarta.persistence.GenerationType; |
||||
|
import jakarta.persistence.Id; |
||||
|
import jakarta.persistence.Table; |
||||
|
import jakarta.persistence.UniqueConstraint; |
||||
|
import jakarta.persistence.Version; |
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Builder; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import org.springframework.data.annotation.CreatedDate; |
||||
|
import org.springframework.data.annotation.LastModifiedDate; |
||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener; |
||||
|
|
||||
|
import java.time.LocalDateTime; |
||||
|
|
||||
|
@Data |
||||
|
@Builder |
||||
|
@NoArgsConstructor |
||||
|
@AllArgsConstructor |
||||
|
@Entity |
||||
|
@Table( |
||||
|
name = "onboarding_checklist_items", |
||||
|
uniqueConstraints = { |
||||
|
@UniqueConstraint( |
||||
|
name = "uk_onboarding_checklist_scope_item", |
||||
|
columnNames = {"scope_type", "scope_id", "item_key"}), |
||||
|
@UniqueConstraint( |
||||
|
name = "uk_onboarding_checklist_company_idempotency", |
||||
|
columnNames = {"company_id", "idempotency_key"}) |
||||
|
}) |
||||
|
@EntityListeners(AuditingEntityListener.class) |
||||
|
public class OnboardingChecklistItem { |
||||
|
|
||||
|
@Id |
||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||
|
private Long id; |
||||
|
|
||||
|
@Column(name = "scope_type", nullable = false, length = 20) |
||||
|
private String scopeType; |
||||
|
|
||||
|
@Column(name = "scope_id", nullable = false) |
||||
|
private Long scopeId; |
||||
|
|
||||
|
@Column(name = "company_id") |
||||
|
private Long companyId; |
||||
|
|
||||
|
@Column(name = "item_key", nullable = false, length = 80) |
||||
|
private String itemKey; |
||||
|
|
||||
|
@Column(nullable = false, length = 120) |
||||
|
private String title; |
||||
|
|
||||
|
@Column(length = 1000) |
||||
|
private String description; |
||||
|
|
||||
|
@Column(nullable = false, length = 30) |
||||
|
private String category; |
||||
|
|
||||
|
@Column(name = "sort_order", nullable = false) |
||||
|
@Builder.Default |
||||
|
private Integer sortOrder = 1000; |
||||
|
|
||||
|
@Column(nullable = false) |
||||
|
@Builder.Default |
||||
|
private Boolean required = false; |
||||
|
|
||||
|
@Column(nullable = false) |
||||
|
@Builder.Default |
||||
|
private Boolean active = true; |
||||
|
|
||||
|
@Column(name = "idempotency_key", length = 64) |
||||
|
private String idempotencyKey; |
||||
|
|
||||
|
@Column(name = "request_hash", columnDefinition = "CHAR(64)") |
||||
|
private String requestHash; |
||||
|
|
||||
|
@Version |
||||
|
@Column(nullable = false) |
||||
|
@Builder.Default |
||||
|
private Long version = 0L; |
||||
|
|
||||
|
@CreatedDate |
||||
|
@Column(name = "created_at", nullable = false, updatable = false) |
||||
|
private LocalDateTime createdAt; |
||||
|
|
||||
|
@LastModifiedDate |
||||
|
@Column(name = "updated_at", nullable = false) |
||||
|
private LocalDateTime updatedAt; |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
package com.mydreamjob.onboarding; |
||||
|
|
||||
|
import org.springframework.data.jpa.repository.JpaRepository; |
||||
|
import org.springframework.data.jpa.repository.Query; |
||||
|
import org.springframework.data.repository.query.Param; |
||||
|
import org.springframework.stereotype.Repository; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Repository |
||||
|
public interface OnboardingChecklistItemRepository extends JpaRepository<OnboardingChecklistItem, Long> { |
||||
|
|
||||
|
@Query(""" |
||||
|
SELECT item |
||||
|
FROM OnboardingChecklistItem item |
||||
|
WHERE item.active = true |
||||
|
AND ( |
||||
|
item.scopeType = 'STANDARD' |
||||
|
OR (item.scopeType = 'COMPANY' |
||||
|
AND item.scopeId = :companyId |
||||
|
AND item.companyId = :companyId) |
||||
|
) |
||||
|
ORDER BY item.sortOrder ASC, item.id ASC |
||||
|
""") |
||||
|
List<OnboardingChecklistItem> findEffectiveChecklistItems(@Param("companyId") Long companyId); |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
package com.mydreamjob.onboarding; |
||||
|
|
||||
|
import jakarta.persistence.Column; |
||||
|
import jakarta.persistence.Entity; |
||||
|
import jakarta.persistence.EntityListeners; |
||||
|
import jakarta.persistence.GeneratedValue; |
||||
|
import jakarta.persistence.GenerationType; |
||||
|
import jakarta.persistence.Id; |
||||
|
import jakarta.persistence.Table; |
||||
|
import jakarta.persistence.UniqueConstraint; |
||||
|
import jakarta.persistence.Version; |
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Builder; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import org.springframework.data.annotation.CreatedDate; |
||||
|
import org.springframework.data.annotation.LastModifiedDate; |
||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener; |
||||
|
|
||||
|
import java.time.LocalDateTime; |
||||
|
|
||||
|
@Data |
||||
|
@Builder |
||||
|
@NoArgsConstructor |
||||
|
@AllArgsConstructor |
||||
|
@Entity |
||||
|
@Table( |
||||
|
name = "student_onboarding_progress", |
||||
|
uniqueConstraints = @UniqueConstraint( |
||||
|
name = "uk_student_onboarding_progress_student_item", |
||||
|
columnNames = {"student_user_id", "item_id"})) |
||||
|
@EntityListeners(AuditingEntityListener.class) |
||||
|
public class StudentOnboardingProgress { |
||||
|
|
||||
|
@Id |
||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||
|
private Long id; |
||||
|
|
||||
|
@Column(name = "student_user_id", nullable = false) |
||||
|
private Long studentUserId; |
||||
|
|
||||
|
@Column(name = "item_id", nullable = false) |
||||
|
private Long itemId; |
||||
|
|
||||
|
@Column(nullable = false) |
||||
|
@Builder.Default |
||||
|
private Boolean completed = false; |
||||
|
|
||||
|
@Column(name = "completed_at") |
||||
|
private LocalDateTime completedAt; |
||||
|
|
||||
|
@Version |
||||
|
@Column(nullable = false) |
||||
|
@Builder.Default |
||||
|
private Long version = 0L; |
||||
|
|
||||
|
@CreatedDate |
||||
|
@Column(name = "created_at", nullable = false, updatable = false) |
||||
|
private LocalDateTime createdAt; |
||||
|
|
||||
|
@LastModifiedDate |
||||
|
@Column(name = "updated_at", nullable = false) |
||||
|
private LocalDateTime updatedAt; |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
package com.mydreamjob.onboarding; |
||||
|
|
||||
|
import org.springframework.data.jpa.repository.JpaRepository; |
||||
|
import org.springframework.stereotype.Repository; |
||||
|
|
||||
|
import java.util.Collection; |
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
|
||||
|
@Repository |
||||
|
public interface StudentOnboardingProgressRepository |
||||
|
extends JpaRepository<StudentOnboardingProgress, Long> { |
||||
|
|
||||
|
List<StudentOnboardingProgress> findByStudentUserIdAndItemIdIn( |
||||
|
Long studentUserId, Collection<Long> itemIds); |
||||
|
|
||||
|
Optional<StudentOnboardingProgress> findByStudentUserIdAndItemId( |
||||
|
Long studentUserId, Long itemId); |
||||
|
} |
||||
@ -0,0 +1,399 @@ |
|||||
|
package com.mydreamjob.onboarding; |
||||
|
|
||||
|
import com.mydreamjob.auth.User; |
||||
|
import com.mydreamjob.auth.UserRepository; |
||||
|
import com.mydreamjob.company.Company; |
||||
|
import com.mydreamjob.company.CompanyRepository; |
||||
|
import com.mydreamjob.job.Job; |
||||
|
import com.mydreamjob.job.JobRepository; |
||||
|
import com.mydreamjob.review.JobReview; |
||||
|
import com.mydreamjob.review.JobReviewDimensionScore; |
||||
|
import com.mydreamjob.review.JobReviewDimensionScoreRepository; |
||||
|
import com.mydreamjob.review.JobReviewRepository; |
||||
|
import org.hibernate.boot.model.relational.Namespace; |
||||
|
import org.hibernate.boot.model.relational.Sequence; |
||||
|
import org.hibernate.mapping.Table; |
||||
|
import org.junit.jupiter.api.BeforeEach; |
||||
|
import org.hibernate.tool.schema.spi.SchemaFilter; |
||||
|
import org.hibernate.tool.schema.spi.SchemaFilterProvider; |
||||
|
import org.junit.jupiter.api.Test; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.boot.test.context.SpringBootTest; |
||||
|
import org.springframework.boot.test.mock.mockito.MockBean; |
||||
|
import org.springframework.data.redis.core.RedisTemplate; |
||||
|
import org.springframework.jdbc.core.JdbcTemplate; |
||||
|
import org.springframework.test.context.ActiveProfiles; |
||||
|
import org.springframework.test.context.TestPropertySource; |
||||
|
import org.springframework.transaction.annotation.Transactional; |
||||
|
|
||||
|
import java.time.LocalDateTime; |
||||
|
import java.util.List; |
||||
|
import java.util.Locale; |
||||
|
import java.util.Optional; |
||||
|
import java.util.Set; |
||||
|
import java.util.concurrent.atomic.AtomicLong; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
|
||||
|
@SpringBootTest |
||||
|
@TestPropertySource(properties = { |
||||
|
"spring.datasource.url=jdbc:h2:mem:integrationdb_onboarding_repository;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE", |
||||
|
"spring.datasource.driver-class-name=org.h2.Driver", |
||||
|
"spring.datasource.username=sa", |
||||
|
"spring.datasource.password=", |
||||
|
"spring.jpa.hibernate.ddl-auto=validate", |
||||
|
"spring.jpa.properties.hibernate.hbm2ddl.schema_filter_provider=com.mydreamjob.onboarding.OnboardingRepositoryIntegrationTest$OnboardingSchemaFilterProvider" |
||||
|
}) |
||||
|
@ActiveProfiles("integration-test") |
||||
|
@Transactional |
||||
|
class OnboardingRepositoryIntegrationTest { |
||||
|
|
||||
|
private static final AtomicLong PHONE_SEQ = new AtomicLong(900); |
||||
|
|
||||
|
@Autowired private OnboardingChecklistItemRepository checklistItemRepository; |
||||
|
@Autowired private StudentOnboardingProgressRepository progressRepository; |
||||
|
@Autowired private CompanyKnowledgeRepository knowledgeRepository; |
||||
|
@Autowired private JobReviewRepository reviewRepository; |
||||
|
@Autowired private JobReviewDimensionScoreRepository dimensionScoreRepository; |
||||
|
@Autowired private UserRepository userRepository; |
||||
|
@Autowired private CompanyRepository companyRepository; |
||||
|
@Autowired private JobRepository jobRepository; |
||||
|
@Autowired private JdbcTemplate jdbcTemplate; |
||||
|
|
||||
|
@MockBean private RedisTemplate<String, Object> redisTemplate; |
||||
|
|
||||
|
@BeforeEach |
||||
|
void clearData() { |
||||
|
progressRepository.deleteAll(); |
||||
|
knowledgeRepository.deleteAll(); |
||||
|
dimensionScoreRepository.deleteAll(); |
||||
|
reviewRepository.deleteAll(); |
||||
|
jobRepository.deleteAll(); |
||||
|
checklistItemRepository.deleteAll(); |
||||
|
companyRepository.deleteAll(); |
||||
|
userRepository.deleteAll(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void effectiveChecklistReturnsActiveStandardAndCompanyItemsInStableOrder() { |
||||
|
Long companyAId = createCompany("清单企业A"); |
||||
|
Long companyBId = createCompany("清单企业B"); |
||||
|
|
||||
|
OnboardingChecklistItem standardActive = saveChecklistItem( |
||||
|
"STANDARD", 0L, null, "standard-visible", "通用可见项", 100, true, null, null); |
||||
|
saveChecklistItem("STANDARD", 0L, null, "standard-inactive", "通用停用项", 1, false, null, null); |
||||
|
OnboardingChecklistItem companyFirst = saveChecklistItem( |
||||
|
"COMPANY", companyAId, companyAId, "company-a-first", "企业A首项", 50, true, |
||||
|
"company-a-first-key", "a".repeat(64)); |
||||
|
OnboardingChecklistItem companySecond = saveChecklistItem( |
||||
|
"COMPANY", companyAId, companyAId, "company-a-second", "企业A次项", 50, true, |
||||
|
"company-a-second-key", "b".repeat(64)); |
||||
|
saveChecklistItem("COMPANY", companyAId, companyAId, "company-a-inactive", "企业A停用项", |
||||
|
40, false, null, null); |
||||
|
saveChecklistItem("COMPANY", companyBId, companyBId, "company-b-visible", "企业B可见项", |
||||
|
10, true, "company-b-key", "c".repeat(64)); |
||||
|
|
||||
|
List<OnboardingChecklistItem> result = checklistItemRepository.findEffectiveChecklistItems(companyAId); |
||||
|
|
||||
|
assertThat(result) |
||||
|
.extracting(OnboardingChecklistItem::getId) |
||||
|
.containsExactly(companyFirst.getId(), companySecond.getId(), standardActive.getId()); |
||||
|
assertThat(result) |
||||
|
.extracting(OnboardingChecklistItem::getItemKey) |
||||
|
.containsExactly("company-a-first", "company-a-second", "standard-visible"); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void requestHashIsMappedAsChar64() { |
||||
|
Long companyId = createCompany("哈希企业"); |
||||
|
String hash = "0123456789abcdef".repeat(4); |
||||
|
OnboardingChecklistItem saved = saveChecklistItem( |
||||
|
"COMPANY", companyId, companyId, "company-hash", "哈希映射项", 200, true, |
||||
|
"company-hash-key", hash); |
||||
|
checklistItemRepository.flush(); |
||||
|
|
||||
|
assertThat(checklistItemRepository.findById(saved.getId())) |
||||
|
.get() |
||||
|
.extracting(OnboardingChecklistItem::getRequestHash) |
||||
|
.isEqualTo(hash); |
||||
|
|
||||
|
String typeName = jdbcTemplate.queryForObject( |
||||
|
"SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS " + |
||||
|
"WHERE UPPER(TABLE_NAME) = 'ONBOARDING_CHECKLIST_ITEMS' " + |
||||
|
"AND UPPER(COLUMN_NAME) = 'REQUEST_HASH'", |
||||
|
String.class); |
||||
|
Integer columnSize = jdbcTemplate.queryForObject( |
||||
|
"SELECT CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS " + |
||||
|
"WHERE UPPER(TABLE_NAME) = 'ONBOARDING_CHECKLIST_ITEMS' " + |
||||
|
"AND UPPER(COLUMN_NAME) = 'REQUEST_HASH'", |
||||
|
Integer.class); |
||||
|
|
||||
|
assertThat(typeName).isNotNull().startsWith("CHAR").doesNotContain("VARCHAR"); |
||||
|
assertThat(columnSize).isEqualTo(64); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void progressBatchAndUniqueLookupsAreScopedToStudentAndItem() { |
||||
|
Long studentOne = createUser(User.Role.STUDENT, "进度学生A"); |
||||
|
Long studentTwo = createUser(User.Role.STUDENT, "进度学生B"); |
||||
|
OnboardingChecklistItem firstItem = saveChecklistItem( |
||||
|
"STANDARD", 0L, null, "progress-item-1", "进度项1", 10, true, null, null); |
||||
|
OnboardingChecklistItem secondItem = saveChecklistItem( |
||||
|
"STANDARD", 0L, null, "progress-item-2", "进度项2", 20, true, null, null); |
||||
|
OnboardingChecklistItem thirdItem = saveChecklistItem( |
||||
|
"STANDARD", 0L, null, "progress-item-3", "进度项3", 30, true, null, null); |
||||
|
|
||||
|
LocalDateTime completedAt = LocalDateTime.of(2026, 9, 16, 8, 0); |
||||
|
progressRepository.save(StudentOnboardingProgress.builder() |
||||
|
.studentUserId(studentOne).itemId(firstItem.getId()).completed(true) |
||||
|
.completedAt(completedAt).build()); |
||||
|
progressRepository.save(StudentOnboardingProgress.builder() |
||||
|
.studentUserId(studentOne).itemId(secondItem.getId()).completed(false) |
||||
|
.completedAt(null).build()); |
||||
|
progressRepository.save(StudentOnboardingProgress.builder() |
||||
|
.studentUserId(studentTwo).itemId(firstItem.getId()).completed(true) |
||||
|
.completedAt(LocalDateTime.of(2026, 9, 16, 9, 0)).build()); |
||||
|
progressRepository.flush(); |
||||
|
|
||||
|
List<StudentOnboardingProgress> batch = progressRepository.findByStudentUserIdAndItemIdIn( |
||||
|
studentOne, List.of(firstItem.getId(), secondItem.getId(), thirdItem.getId())); |
||||
|
assertThat(batch) |
||||
|
.extracting(StudentOnboardingProgress::getItemId) |
||||
|
.containsExactlyInAnyOrder(firstItem.getId(), secondItem.getId()); |
||||
|
assertThat(batch) |
||||
|
.filteredOn(progress -> progress.getItemId().equals(firstItem.getId())) |
||||
|
.singleElement() |
||||
|
.extracting(StudentOnboardingProgress::getCompletedAt) |
||||
|
.isEqualTo(completedAt); |
||||
|
|
||||
|
Optional<StudentOnboardingProgress> unique = progressRepository.findByStudentUserIdAndItemId( |
||||
|
studentOne, firstItem.getId()); |
||||
|
assertThat(unique).isPresent(); |
||||
|
assertThat(unique.orElseThrow().getCompleted()).isTrue(); |
||||
|
assertThat(progressRepository.findByStudentUserIdAndItemId(studentOne, thirdItem.getId())).isEmpty(); |
||||
|
assertThat(progressRepository.findByStudentUserIdAndItemId(studentTwo, secondItem.getId())).isEmpty(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void companyKnowledgeLookupIsScopedToCompany() { |
||||
|
Long companyId = createCompany("知识企业"); |
||||
|
Long otherCompanyId = createCompany("其他知识企业"); |
||||
|
knowledgeRepository.save(CompanyKnowledge.builder() |
||||
|
.companyId(companyId) |
||||
|
.culture("重视透明沟通") |
||||
|
.workPolicies("弹性工作制") |
||||
|
.onboardingGuide("前台报到") |
||||
|
.build()); |
||||
|
knowledgeRepository.flush(); |
||||
|
|
||||
|
Optional<CompanyKnowledge> found = knowledgeRepository.findByCompanyId(companyId); |
||||
|
assertThat(found).isPresent(); |
||||
|
assertThat(found.orElseThrow().getCulture()).isEqualTo("重视透明沟通"); |
||||
|
assertThat(found.orElseThrow().getWorkPolicies()).isEqualTo("弹性工作制"); |
||||
|
assertThat(found.orElseThrow().getOnboardingGuide()).isEqualTo("前台报到"); |
||||
|
assertThat(knowledgeRepository.findByCompanyId(otherCompanyId)).isEmpty(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void companyReviewAggregatesIncludeOnlyPublishedApprovedReviews() { |
||||
|
Long companyId = createCompany("评价企业A"); |
||||
|
Long otherCompanyId = createCompany("评价企业B"); |
||||
|
Long studentOne = createUser(User.Role.STUDENT, "评价学生A"); |
||||
|
Long studentTwo = createUser(User.Role.STUDENT, "评价学生B"); |
||||
|
Long studentThree = createUser(User.Role.STUDENT, "评价学生C"); |
||||
|
Long hiddenStudent = createUser(User.Role.STUDENT, "评价学生D"); |
||||
|
Long otherStudent = createUser(User.Role.STUDENT, "评价学生E"); |
||||
|
|
||||
|
Long firstJobId = createJob(companyId, "评价岗位A1"); |
||||
|
Long secondJobId = createJob(companyId, "评价岗位A2"); |
||||
|
Long otherJobId = createJob(otherCompanyId, "评价岗位B"); |
||||
|
|
||||
|
JobReview five = saveReview(firstJobId, studentOne, 5, "[\"成长空间\",\"团队氛围\"]", |
||||
|
"PUBLISHED", JobReview.MODERATION_APPROVED); |
||||
|
JobReview four = saveReview(secondJobId, studentTwo, 4, "[\"成长空间\"]", |
||||
|
"PUBLISHED", JobReview.MODERATION_AUTO_APPROVED); |
||||
|
JobReview three = saveReview(firstJobId, studentThree, 3, "[\"团队氛围\"]", |
||||
|
"PUBLISHED", JobReview.MODERATION_APPROVED); |
||||
|
JobReview hidden = saveReview(firstJobId, hiddenStudent, 1, "[\"薪资透明\"]", |
||||
|
"PUBLISHED", JobReview.MODERATION_PENDING); |
||||
|
saveReview(otherJobId, otherStudent, 2, "[\"其他企业标签\"]", |
||||
|
"PUBLISHED", JobReview.MODERATION_APPROVED); |
||||
|
|
||||
|
dimensionScoreRepository.saveAll(List.of( |
||||
|
dimensionScore(five.getId(), "TEAM_ATMOSPHERE", 5), |
||||
|
dimensionScore(five.getId(), "GROWTH", 5), |
||||
|
dimensionScore(four.getId(), "TEAM_ATMOSPHERE", 4), |
||||
|
dimensionScore(four.getId(), "GROWTH", 4), |
||||
|
dimensionScore(three.getId(), "TEAM_ATMOSPHERE", 3), |
||||
|
dimensionScore(hidden.getId(), "TEAM_ATMOSPHERE", 1) |
||||
|
)); |
||||
|
dimensionScoreRepository.flush(); |
||||
|
|
||||
|
List<Object[]> summary = reviewRepository.countAndAveragePublishedRatingByCompanyId(companyId); |
||||
|
assertThat(summary).hasSize(1); |
||||
|
assertThat(((Number) summary.get(0)[0]).longValue()).isEqualTo(3); |
||||
|
assertThat(((Number) summary.get(0)[1]).doubleValue()).isEqualTo(4.0); |
||||
|
|
||||
|
List<Object[]> distribution = reviewRepository.findPublishedRatingDistributionByCompanyId(companyId); |
||||
|
assertThat(distribution).hasSize(3); |
||||
|
assertThat(((Number) distribution.get(0)[0]).intValue()).isEqualTo(3); |
||||
|
assertThat(((Number) distribution.get(0)[1]).longValue()).isEqualTo(1); |
||||
|
assertThat(((Number) distribution.get(1)[0]).intValue()).isEqualTo(4); |
||||
|
assertThat(((Number) distribution.get(1)[1]).longValue()).isEqualTo(1); |
||||
|
assertThat(((Number) distribution.get(2)[0]).intValue()).isEqualTo(5); |
||||
|
assertThat(((Number) distribution.get(2)[1]).longValue()).isEqualTo(1); |
||||
|
|
||||
|
List<String> tags = reviewRepository.findPublishedTagsByCompanyId(companyId); |
||||
|
assertThat(tags).hasSize(3); |
||||
|
assertThat(tags).allSatisfy(tag -> |
||||
|
assertThat(tag).doesNotContain("薪资透明", "其他企业标签")); |
||||
|
assertThat(tags).anySatisfy(tag -> |
||||
|
assertThat(tag).contains("成长空间").contains("团队氛围")); |
||||
|
assertThat(tags).anySatisfy(tag -> |
||||
|
assertThat(tag).contains("成长空间").doesNotContain("团队氛围")); |
||||
|
assertThat(tags).anySatisfy(tag -> |
||||
|
assertThat(tag).contains("团队氛围").doesNotContain("成长空间")); |
||||
|
|
||||
|
List<Object[]> dimensions = dimensionScoreRepository.aggregatePublishedDimensionsByCompanyId(companyId); |
||||
|
assertThat(dimensions).hasSize(2); |
||||
|
assertThat(dimensions.get(0)[0]).isEqualTo("GROWTH"); |
||||
|
assertThat(((Number) dimensions.get(0)[1]).doubleValue()).isEqualTo(4.5); |
||||
|
assertThat(((Number) dimensions.get(0)[2]).longValue()).isEqualTo(2); |
||||
|
assertThat(dimensions.get(1)[0]).isEqualTo("TEAM_ATMOSPHERE"); |
||||
|
assertThat(((Number) dimensions.get(1)[1]).doubleValue()).isEqualTo(4.0); |
||||
|
assertThat(((Number) dimensions.get(1)[2]).longValue()).isEqualTo(3); |
||||
|
} |
||||
|
|
||||
|
private Long createCompany(String name) { |
||||
|
Long ownerId = createUser(User.Role.COMPANY, name + "负责人"); |
||||
|
return companyRepository.save(Company.builder() |
||||
|
.ownerUserId(ownerId) |
||||
|
.name(name) |
||||
|
.verified(true) |
||||
|
.build()).getId(); |
||||
|
} |
||||
|
|
||||
|
private Long createUser(User.Role role, String nickname) { |
||||
|
User user = userRepository.save(User.builder() |
||||
|
.phone("175" + String.format("%08d", PHONE_SEQ.incrementAndGet())) |
||||
|
.nickname(nickname) |
||||
|
.role(role) |
||||
|
.roles(Set.of(role)) |
||||
|
.active(true) |
||||
|
.verified(true) |
||||
|
.build()); |
||||
|
return user.getId(); |
||||
|
} |
||||
|
|
||||
|
private Long createJob(Long companyId, String title) { |
||||
|
return jobRepository.save(Job.builder() |
||||
|
.companyId(companyId) |
||||
|
.title(title) |
||||
|
.description("岗位描述") |
||||
|
.city("上海") |
||||
|
.employmentType("FULL_TIME") |
||||
|
.status("PUBLISHED") |
||||
|
.build()).getId(); |
||||
|
} |
||||
|
|
||||
|
private OnboardingChecklistItem saveChecklistItem( |
||||
|
String scopeType, |
||||
|
Long scopeId, |
||||
|
Long companyId, |
||||
|
String itemKey, |
||||
|
String title, |
||||
|
int sortOrder, |
||||
|
boolean active, |
||||
|
String idempotencyKey, |
||||
|
String requestHash) { |
||||
|
return checklistItemRepository.save(OnboardingChecklistItem.builder() |
||||
|
.scopeType(scopeType) |
||||
|
.scopeId(scopeId) |
||||
|
.companyId(companyId) |
||||
|
.itemKey(itemKey) |
||||
|
.title(title) |
||||
|
.description(title + "说明") |
||||
|
.category("OTHER") |
||||
|
.sortOrder(sortOrder) |
||||
|
.required(false) |
||||
|
.active(active) |
||||
|
.idempotencyKey(idempotencyKey) |
||||
|
.requestHash(requestHash) |
||||
|
.build()); |
||||
|
} |
||||
|
|
||||
|
private JobReview saveReview( |
||||
|
Long jobId, |
||||
|
Long studentUserId, |
||||
|
int rating, |
||||
|
String tags, |
||||
|
String status, |
||||
|
String moderationStatus) { |
||||
|
return reviewRepository.save(JobReview.builder() |
||||
|
.jobId(jobId) |
||||
|
.studentUserId(studentUserId) |
||||
|
.rating(rating) |
||||
|
.content("评价内容") |
||||
|
.tags(tags) |
||||
|
.status(status) |
||||
|
.moderationStatus(moderationStatus) |
||||
|
.build()); |
||||
|
} |
||||
|
|
||||
|
private JobReviewDimensionScore dimensionScore(Long reviewId, String dimensionCode, int score) { |
||||
|
return JobReviewDimensionScore.builder() |
||||
|
.reviewId(reviewId) |
||||
|
.dimensionCode(dimensionCode) |
||||
|
.score(score) |
||||
|
.build(); |
||||
|
} |
||||
|
|
||||
|
public static class OnboardingSchemaFilterProvider implements SchemaFilterProvider { |
||||
|
|
||||
|
private static final Set<String> VALIDATED_TABLES = Set.of( |
||||
|
"onboarding_checklist_items", |
||||
|
"student_onboarding_progress", |
||||
|
"company_knowledge" |
||||
|
); |
||||
|
|
||||
|
@Override |
||||
|
public SchemaFilter getCreateFilter() { |
||||
|
return SchemaFilter.ALL; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public SchemaFilter getDropFilter() { |
||||
|
return SchemaFilter.ALL; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public SchemaFilter getTruncatorFilter() { |
||||
|
return SchemaFilter.ALL; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public SchemaFilter getMigrateFilter() { |
||||
|
return SchemaFilter.ALL; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public SchemaFilter getValidateFilter() { |
||||
|
return new SchemaFilter() { |
||||
|
@Override |
||||
|
public boolean includeNamespace(Namespace namespace) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean includeTable(Table table) { |
||||
|
return VALIDATED_TABLES.contains(table.getName().toLowerCase(Locale.ROOT)); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean includeSequence(Sequence sequence) { |
||||
|
return false; |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue