diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/common/model/BaseEntity.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/common/model/BaseEntity.java index 74546fe1..b4caf129 100644 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/common/model/BaseEntity.java +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/common/model/BaseEntity.java @@ -16,7 +16,7 @@ public abstract class BaseEntity { private Long id; @Override - public final boolean equals(Object o) { + public boolean equals(Object o) { if (this == o) { return true; } diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/common/util/OrdinalFormatter.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/common/util/OrdinalFormatter.java new file mode 100644 index 00000000..bc27882a --- /dev/null +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/common/util/OrdinalFormatter.java @@ -0,0 +1,120 @@ +package org.nexters.jaknaesocore.common.util; + +import static java.util.Map.entry; + +import java.util.Map; + +public class OrdinalFormatter { + + private static final Map ORDINALS = + Map.ofEntries( + entry(1, "첫번째"), + entry(2, "두번째"), + entry(3, "세번째"), + entry(4, "네번째"), + entry(5, "다섯번째"), + entry(6, "여섯번째"), + entry(7, "일곱번째"), + entry(8, "여덟번째"), + entry(9, "아홉번째"), + entry(10, "열번째"), + entry(11, "열한번째"), + entry(12, "열두번째"), + entry(13, "열세번째"), + entry(14, "열네번째"), + entry(15, "열다섯번째"), + entry(16, "열여섯번째"), + entry(17, "열일곱번째"), + entry(18, "열여덟번째"), + entry(19, "열아홉번째"), + entry(20, "스무번째"), + entry(21, "스물한번째"), + entry(22, "스물두번째"), + entry(23, "스물세번째"), + entry(24, "스물네번째"), + entry(25, "스물다섯번째"), + entry(26, "스물여섯번째"), + entry(27, "스물일곱번째"), + entry(28, "스물여덟번째"), + entry(29, "스물아홉번째"), + entry(30, "서른번째"), + entry(31, "서른한번째"), + entry(32, "서른두번째"), + entry(33, "서른세번째"), + entry(34, "서른네번째"), + entry(35, "서른다섯번째"), + entry(36, "서른여섯번째"), + entry(37, "서른일곱번째"), + entry(38, "서른여덟번째"), + entry(39, "서른아홉번째"), + entry(40, "마흔번째"), + entry(41, "마흔한번째"), + entry(42, "마흔두번째"), + entry(43, "마흔세번째"), + entry(44, "마흔네번째"), + entry(45, "마흔다섯번째"), + entry(46, "마흔여섯번째"), + entry(47, "마흔일곱번째"), + entry(48, "마흔여덟번째"), + entry(49, "마흔아홉번째"), + entry(50, "쉰번째"), + entry(51, "쉰한번째"), + entry(52, "쉰두번째"), + entry(53, "쉰세번째"), + entry(54, "쉰네번째"), + entry(55, "쉰다섯번째"), + entry(56, "쉰여섯번째"), + entry(57, "쉰일곱번째"), + entry(58, "쉰여덟번째"), + entry(59, "쉰아홉번째"), + entry(60, "예순번째"), + entry(61, "예순한번째"), + entry(62, "예순두번째"), + entry(63, "예순세번째"), + entry(64, "예순네번째"), + entry(65, "예순다섯번째"), + entry(66, "예순여섯번째"), + entry(67, "예순일곱번째"), + entry(68, "예순여덟번째"), + entry(69, "예순아홉번째"), + entry(70, "일흔번째"), + entry(71, "일흔한번째"), + entry(72, "일흔두번째"), + entry(73, "일흔세번째"), + entry(74, "일흔네번째"), + entry(75, "일흔다섯번째"), + entry(76, "일흔여섯번째"), + entry(77, "일흔일곱번째"), + entry(78, "일흔여덟번째"), + entry(79, "일흔아홉번째"), + entry(80, "여든번째"), + entry(81, "여든한번째"), + entry(82, "여든두번째"), + entry(83, "여든세번째"), + entry(84, "여든네번째"), + entry(85, "여든다섯번째"), + entry(86, "여든여섯번째"), + entry(87, "여든일곱번째"), + entry(88, "여든여덟번째"), + entry(89, "여든아홉번째"), + entry(90, "아흔번째"), + entry(91, "아흔한번째"), + entry(92, "아흔두번째"), + entry(93, "아흔세번째"), + entry(94, "아흔네번째"), + entry(95, "아흔다섯번째"), + entry(96, "아흔여섯번째"), + entry(97, "아흔일곱번째"), + entry(98, "아흔여덟번째"), + entry(99, "아흔아홉번째"), + entry(100, "백번째")); + + private static String FORMAT = "%s %s"; + private static String DEFAULT_ORDINAL = "첫번째"; + private static String CHARACTER_WORD = "캐릭터"; + + public static String getCharacterNo(final Integer ordinalNumber) { + return String.format( + FORMAT, ORDINALS.getOrDefault(ordinalNumber, DEFAULT_ORDINAL), CHARACTER_WORD); + } +} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterRecord.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterRecord.java index f683b0aa..df7e015b 100644 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterRecord.java +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterRecord.java @@ -5,21 +5,27 @@ import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToOne; +import jakarta.persistence.OneToMany; import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.nexters.jaknaesocore.common.model.BaseTimeEntity; +import org.nexters.jaknaesocore.common.util.OrdinalFormatter; import org.nexters.jaknaesocore.domain.member.model.Member; +import org.nexters.jaknaesocore.domain.survey.model.Keyword; import org.nexters.jaknaesocore.domain.survey.model.SurveyBundle; +import org.nexters.jaknaesocore.domain.survey.model.SurveySubmission; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity public class CharacterRecord extends BaseTimeEntity { + private int ordinalNumber; private String characterNo; @ManyToOne @@ -38,26 +44,100 @@ public class CharacterRecord extends BaseTimeEntity { @JoinColumn(name = "bundle_id") private SurveyBundle surveyBundle; - @OneToOne(mappedBy = "characterRecord", cascade = CascadeType.ALL, orphanRemoval = true) - private CharacterValueReport characterValueReport; + @OneToMany(mappedBy = "characterRecord", cascade = CascadeType.PERSIST, orphanRemoval = true) + private List valueReports = new ArrayList<>(); + + private CharacterRecord( + final int ordinalNumber, + final String characterNo, + final Member member, + final SurveyBundle surveyBundle, + final LocalDate startDate) { + this.ordinalNumber = ordinalNumber; + this.characterNo = characterNo; + this.member = member; + this.surveyBundle = surveyBundle; + this.startDate = startDate; + } @Builder private CharacterRecord( + final int ordinalNumber, final String characterNo, final ValueCharacter valueCharacter, final LocalDate startDate, final LocalDate endDate, final Member member, - final SurveyBundle surveyBundle) { + final SurveyBundle surveyBundle, + final List valueReports) { + this.ordinalNumber = ordinalNumber; this.characterNo = characterNo; this.valueCharacter = valueCharacter; this.startDate = startDate; this.endDate = endDate; this.member = member; this.surveyBundle = surveyBundle; + + if (valueReports != null) { + this.valueReports = valueReports; + this.valueReports.forEach(it -> it.updateCharacterRecord(this)); + } + } + + public static CharacterRecord of( + final CharacterRecord previousRecord, + final Member member, + final SurveyBundle bundle, + final LocalDate startDate) { + final int ordinalNumber = previousRecord.getOrdinalNumber() + 1; + return new CharacterRecord( + ordinalNumber, OrdinalFormatter.getCharacterNo(ordinalNumber), member, bundle, startDate); + } + + public static CharacterRecord ofFirst( + final Member member, + final SurveyBundle surveyBundle, + final ValueCharacter valueCharacter, + final List valueReports, + List submissions) { + final int first = 1; + return new CharacterRecord( + first, + OrdinalFormatter.getCharacterNo(first), + valueCharacter, + submissions.getFirst().getSubmittedAt().toLocalDate(), + submissions.getLast().getSubmittedAt().toLocalDate(), + member, + surveyBundle, + valueReports); + } + + public void updateRecord( + final ValueCharacter valueCharacter, + final List valueReports, + final List submissions) { + this.valueCharacter = valueCharacter; + this.startDate = submissions.getFirst().getSubmittedAt().toLocalDate(); + this.endDate = submissions.getLast().getSubmittedAt().toLocalDate(); + if (valueReports != null) { + this.valueReports = valueReports; + this.valueReports.forEach(it -> it.updateCharacterRecord(this)); + } + } + + public Keyword getKeyword() { + return valueCharacter.getKeyword(); + } + + public String getName() { + return valueCharacter.getName(); + } + + public String getDescription() { + return valueCharacter.getDescription(); } - public void updateCharacterValueReport(final CharacterValueReport characterValueReport) { - this.characterValueReport = characterValueReport; + public List getTraitsByType(final CharacterTraitType characterTraitType) { + return valueCharacter.getTraitsByType(characterTraitType); } } diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterTrait.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterTrait.java new file mode 100644 index 00000000..0cc75392 --- /dev/null +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterTrait.java @@ -0,0 +1,39 @@ +package org.nexters.jaknaesocore.domain.character.model; + +import static jakarta.persistence.FetchType.LAZY; + +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.nexters.jaknaesocore.common.model.BaseTimeEntity; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CharacterTrait extends BaseTimeEntity { + + @ManyToOne(fetch = LAZY) + @JoinColumn(name = "value_character_id") + private ValueCharacter valueCharacter; + + @Getter private String description; + + @Enumerated(EnumType.STRING) + @Getter + private CharacterTraitType type; + + @Builder + private CharacterTrait( + final ValueCharacter valueCharacter, + final String description, + final CharacterTraitType type) { + this.valueCharacter = valueCharacter; + this.description = description; + this.type = type; + } +} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterTraitType.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterTraitType.java new file mode 100644 index 00000000..5e95caa5 --- /dev/null +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterTraitType.java @@ -0,0 +1,7 @@ +package org.nexters.jaknaesocore.domain.character.model; + +public enum CharacterTraitType { + MAIN_TRAIT, + STRENGTH, + WEAKNESS, +} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterTraits.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterTraits.java new file mode 100644 index 00000000..26bfb126 --- /dev/null +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterTraits.java @@ -0,0 +1,37 @@ +package org.nexters.jaknaesocore.domain.character.model; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLRestriction; + +@Embeddable +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CharacterTraits { + + @OneToMany(mappedBy = "valueCharacter", cascade = CascadeType.PERSIST) + @SQLRestriction("deleted_at IS NULL") + private List values = new ArrayList<>(); + + @Builder + private CharacterTraits(final List values) { + this.values = values; + } + + public static CharacterTraits of(final List values) { + return new CharacterTraits(values); + } + + public static CharacterTraits empty() { + return new CharacterTraits(); + } + + public List getByType(final CharacterTraitType type) { + return values.stream().filter(t -> t.getType().equals(type)).toList(); + } +} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterValueReport.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterValueReport.java deleted file mode 100644 index 66883602..00000000 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/CharacterValueReport.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.nexters.jaknaesocore.domain.character.model; - -import jakarta.persistence.CollectionTable; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToOne; -import java.util.List; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.nexters.jaknaesocore.common.model.BaseTimeEntity; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Entity -public class CharacterValueReport extends BaseTimeEntity { - - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "character_record_id") - private CharacterRecord characterRecord; - - // TODO: 추후 대체 - // @OneToMany(mappedBy = "characterValueReport", cascade = CascadeType.ALL, orphanRemoval = true) - @ElementCollection - @CollectionTable( - name = "value_reports", - joinColumns = @JoinColumn(name = "character_value_report_id")) - private List valueReports; - - public CharacterValueReport( - final CharacterRecord characterRecord, final List valueReports) { - this.characterRecord = characterRecord; - this.valueReports = valueReports; - characterRecord.updateCharacterValueReport(this); - } -} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ScoreEvaluator.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ScoreEvaluator.java new file mode 100644 index 00000000..63d75212 --- /dev/null +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ScoreEvaluator.java @@ -0,0 +1,67 @@ +package org.nexters.jaknaesocore.domain.character.model; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import lombok.Builder; +import org.nexters.jaknaesocore.common.model.ScaledBigDecimal; +import org.nexters.jaknaesocore.domain.survey.model.Keyword; +import org.nexters.jaknaesocore.domain.survey.model.KeywordMetrics; +import org.nexters.jaknaesocore.domain.survey.model.KeywordScore; +import org.nexters.jaknaesocore.domain.survey.model.SurveySubmission; + +public class ScoreEvaluator { + + private final List scores; + private final List submissions; + + @Builder + private ScoreEvaluator( + final List scores, final List submissions) { + this.scores = scores; + this.submissions = submissions; + } + + public static ScoreEvaluator of( + final List scores, final List submissions) { + return new ScoreEvaluator(scores, submissions); + } + + public List generateValueReports() { + final List valueReports = provideValueReport(); + return valueReports; + } + + private List provideValueReport() { + final Map metricsMap = calculateKeywordMetrics(scores); + final Map weightMap = calculateKeywordWeights(metricsMap); + + return ValueReports.report(weightMap, metricsMap, submissions); + } + + private Map calculateKeywordMetrics(final List scores) { + return scores.stream() + .collect(Collectors.groupingBy(KeywordScore::getKeyword, Collectors.toList())) + .entrySet() + .stream() + .collect( + Collectors.toMap(Map.Entry::getKey, entry -> KeywordMetrics.create(entry.getValue()))); + } + + private Map calculateKeywordWeights( + final Map metricsMap) { + int keywordCnt = metricsMap.size(); + ScaledBigDecimal sumPerKeyword = + ScaledBigDecimal.of(BigDecimal.valueOf(100)).divide(BigDecimal.valueOf(keywordCnt)); + + Map weightMap = new HashMap<>(); + metricsMap.forEach( + (k, v) -> { + var sum = v.getPositive().subtract(v.getNegative()); + weightMap.put(k, sumPerKeyword.divide(sum).getValue()); + }); + return weightMap; + } +} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueCharacter.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueCharacter.java index 86262065..999681e8 100644 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueCharacter.java +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueCharacter.java @@ -1,9 +1,12 @@ package org.nexters.jaknaesocore.domain.character.model; +import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import java.util.List; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.nexters.jaknaesocore.common.model.BaseEntity; @@ -21,9 +24,16 @@ public class ValueCharacter extends BaseEntity { @Enumerated(EnumType.STRING) private Keyword keyword; + @Embedded private CharacterTraits characterTraits = CharacterTraits.empty(); + + @Builder public ValueCharacter(final String name, final String description, final Keyword keyword) { this.name = name; this.description = description; this.keyword = keyword; } + + public List getTraitsByType(final CharacterTraitType type) { + return characterTraits.getByType(type); + } } diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueCharacters.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueCharacters.java new file mode 100644 index 00000000..ad6f7c7b --- /dev/null +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueCharacters.java @@ -0,0 +1,32 @@ +package org.nexters.jaknaesocore.domain.character.model; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.nexters.jaknaesocore.domain.survey.model.Keyword; + +public class ValueCharacters { + + private final Map values; + + private ValueCharacters(final Map values) { + this.values = values; + } + + public static ValueCharacters of(final List values) { + return new ValueCharacters( + values.stream() + .collect( + Collectors.toMap(ValueCharacter::getKeyword, valueCharacter -> valueCharacter))); + } + + public ValueCharacter findTopValueCharacter(final List valueReports) { + final ValueReport topReport = + valueReports.stream() + .max(Comparator.comparing(ValueReport::getPercentage)) + .orElseThrow(() -> new IllegalStateException("ValueReport가 비어 있습니다.")); + + return values.get(topReport.getKeyword()); + } +} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueReport.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueReport.java index d02fd4ef..79e48001 100644 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueReport.java +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueReport.java @@ -1,27 +1,34 @@ package org.nexters.jaknaesocore.domain.character.model; -import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import java.math.BigDecimal; +import java.util.Objects; import lombok.AccessLevel; -import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; +import org.nexters.jaknaesocore.common.model.BaseTimeEntity; import org.nexters.jaknaesocore.common.model.ScaledBigDecimal; import org.nexters.jaknaesocore.domain.survey.model.Keyword; -@EqualsAndHashCode @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Embeddable -public class ValueReport { +@Entity +public class ValueReport extends BaseTimeEntity { @Enumerated(EnumType.STRING) private Keyword keyword; private BigDecimal percentage; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "character_record_id") + private CharacterRecord characterRecord; + private ValueReport(final Keyword keyword, final BigDecimal percentage) { this.keyword = keyword; this.percentage = percentage; @@ -31,6 +38,27 @@ public static ValueReport of(final Keyword keyword, final ScaledBigDecimal perce return new ValueReport(keyword, percentage.getValue()); } + public void updateCharacterRecord(final CharacterRecord characterRecord) { + this.characterRecord = characterRecord; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ValueReport that = (ValueReport) o; + return keyword == that.keyword && Objects.equals(percentage, that.percentage); + } + + @Override + public int hashCode() { + return Objects.hash(keyword, percentage); + } + @Override public String toString() { return "ValueReport{" + "keyword=" + keyword + ",percentage=" + percentage + "}"; diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueReports.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueReports.java index 6401ec25..5d0ed24a 100644 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueReports.java +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/model/ValueReports.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import lombok.Getter; import org.nexters.jaknaesocore.common.model.ScaledBigDecimal; import org.nexters.jaknaesocore.domain.survey.model.Keyword; import org.nexters.jaknaesocore.domain.survey.model.KeywordMetrics; @@ -13,29 +12,20 @@ import org.nexters.jaknaesocore.domain.survey.model.KeywordScoreNormalizer; import org.nexters.jaknaesocore.domain.survey.model.SurveySubmission; -@Getter public class ValueReports { private static final ScaledBigDecimal PERCENTAGE100 = ScaledBigDecimal.of(BigDecimal.valueOf(100)); - private List reports; - - private ValueReports(final List reports) { - this.reports = reports; - } - - public static ValueReports of( + public static List report( final Map weights, final Map metrics, final List submissions) { Map percentage = getKeywordPercentage(weights, metrics, submissions); - List reports = - percentage.entrySet().stream() - .map(it -> ValueReport.of(it.getKey(), ScaledBigDecimal.of(it.getValue()))) - .collect(Collectors.toList()); - return new ValueReports(reports); + return percentage.entrySet().stream() + .map(it -> ValueReport.of(it.getKey(), ScaledBigDecimal.of(it.getValue()))) + .collect(Collectors.toList()); } private static Map getKeywordPercentage( diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/repository/CharacterRecordRepository.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/repository/CharacterRecordRepository.java index bcc54a9d..55a69f24 100644 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/repository/CharacterRecordRepository.java +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/repository/CharacterRecordRepository.java @@ -9,9 +9,14 @@ public interface CharacterRecordRepository extends JpaRepository { @Query( - "SELECT c FROM CharacterRecord c JOIN FETCH c.member cm " - + "WHERE cm.id = :memberId AND c.deletedAt IS NULL ORDER BY c.endDate DESC LIMIT 1") - Optional findTopByMemberIdAndDeletedAtIsNullWithMember(final Long memberId); + "SELECT c FROM CharacterRecord c " + + "JOIN FETCH c.member cm " + + "LEFT JOIN c.valueCharacter vc " + + "WHERE cm.id = :memberId " + + "AND vc.id IS NOT NULL " + + "AND c.deletedAt IS NULL " + + "ORDER BY c.endDate DESC LIMIT 1") + Optional findLatestCompletedCharacter(final Long memberId); @Query( "SELECT c FROM CharacterRecord c JOIN FETCH c.member cm " @@ -27,8 +32,14 @@ List findByMemberIdAndDeletedAtIsNullWithMemberAndSurveyBundle( final Long memberId); @Query( - "SELECT c FROM CharacterRecord c JOIN FETCH c.member cm " - + "WHERE c.id = :id AND cm.id = :memberId AND c.deletedAt IS NULL") - Optional existsByIdAndMemberIdAndDeletedAtIsNullWithMember( - final Long id, final Long memberId); + "SELECT c FROM CharacterRecord c JOIN FETCH c.valueReports cr " + + "WHERE c.id = :id AND c.deletedAt IS NULL") + Optional findByIdAndDeletedAtIsNullWithValueReports(final Long id); + + @Query( + "SELECT c FROM CharacterRecord c " + + "WHERE c.member.id = :memberId " + + "AND c.surveyBundle.id = :bundleId " + + "AND c.deletedAt IS NULL") + Optional findByMemberIdAndBundleId(final Long memberId, final Long bundleId); } diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/repository/CharacterValueReportRepository.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/repository/CharacterValueReportRepository.java deleted file mode 100644 index d977055c..00000000 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/repository/CharacterValueReportRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.nexters.jaknaesocore.domain.character.repository; - -import java.util.Optional; -import org.nexters.jaknaesocore.domain.character.model.CharacterValueReport; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -public interface CharacterValueReportRepository extends JpaRepository { - - @Query( - "SELECT c FROM CharacterValueReport c " - + "JOIN FETCH c.characterRecord cc JOIN FETCH c.valueReports " - + "WHERE cc.id = :characterId AND c.deletedAt IS NULL") - Optional findByCharacterIdAndDeletedAtIsNullWithCharacterAndValueReports( - final Long characterId); -} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/repository/ValueReportRepository.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/repository/ValueReportRepository.java new file mode 100644 index 00000000..c168cd7a --- /dev/null +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/repository/ValueReportRepository.java @@ -0,0 +1,6 @@ +package org.nexters.jaknaesocore.domain.character.repository; + +import org.nexters.jaknaesocore.domain.character.model.ValueReport; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ValueReportRepository extends JpaRepository {} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/CharacterService.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/CharacterService.java index 5e93e1c0..105840c4 100644 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/CharacterService.java +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/CharacterService.java @@ -1,62 +1,55 @@ package org.nexters.jaknaesocore.domain.character.service; +import java.time.LocalDate; import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.nexters.jaknaesocore.common.support.error.CustomException; -import org.nexters.jaknaesocore.domain.character.model.CharacterValueReport; +import org.nexters.jaknaesocore.domain.character.model.CharacterRecord; +import org.nexters.jaknaesocore.domain.character.model.ScoreEvaluator; +import org.nexters.jaknaesocore.domain.character.model.ValueCharacter; +import org.nexters.jaknaesocore.domain.character.model.ValueCharacters; +import org.nexters.jaknaesocore.domain.character.model.ValueReport; import org.nexters.jaknaesocore.domain.character.repository.CharacterRecordRepository; -import org.nexters.jaknaesocore.domain.character.repository.CharacterValueReportRepository; +import org.nexters.jaknaesocore.domain.character.repository.ValueCharacterRepository; import org.nexters.jaknaesocore.domain.character.service.dto.CharacterCommand; import org.nexters.jaknaesocore.domain.character.service.dto.CharacterResponse; import org.nexters.jaknaesocore.domain.character.service.dto.CharacterValueReportCommand; import org.nexters.jaknaesocore.domain.character.service.dto.CharacterValueReportResponse; import org.nexters.jaknaesocore.domain.character.service.dto.CharactersResponse; -import org.nexters.jaknaesocore.domain.character.service.dto.CharactersResponse.SimpleCharacterResponse; +import org.nexters.jaknaesocore.domain.character.service.dto.SimpleCharacterResponse; +import org.nexters.jaknaesocore.domain.member.model.Member; import org.nexters.jaknaesocore.domain.member.repository.MemberRepository; +import org.nexters.jaknaesocore.domain.survey.model.KeywordScore; +import org.nexters.jaknaesocore.domain.survey.model.SurveyBundle; +import org.nexters.jaknaesocore.domain.survey.model.SurveySubmission; +import org.nexters.jaknaesocore.domain.survey.repository.SurveySubmissionRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@Slf4j @RequiredArgsConstructor @Service public class CharacterService { private final MemberRepository memberRepository; private final CharacterRecordRepository characterRecordRepository; - private final CharacterValueReportRepository characterValueReportRepository; + private final ValueCharacterRepository valueCharacterRepository; + private final SurveySubmissionRepository surveySubmissionRepository; @Transactional(readOnly = true) public CharacterResponse getCharacter(final CharacterCommand command) { return characterRecordRepository .findByIdAndMemberIdAndDeletedAtIsNullWithMember(command.memberId(), command.characterId()) - .map( - it -> - CharacterResponse.builder() - .characterId(it.getId()) - .characterNo(it.getCharacterNo()) - .valueCharacter(it.getValueCharacter()) - .name(it.getValueCharacter().getName()) - .description(it.getValueCharacter().getDescription()) - .startDate(it.getStartDate()) - .endDate(it.getEndDate()) - .build()) + .map(CharacterResponse::of) .orElseThrow(() -> CustomException.CHARACTER_NOT_FOUND); } @Transactional(readOnly = true) public CharacterResponse getCurrentCharacter(final Long memberId) { return characterRecordRepository - .findTopByMemberIdAndDeletedAtIsNullWithMember(memberId) - .map( - it -> - CharacterResponse.builder() - .characterId(it.getId()) - .characterNo(it.getCharacterNo()) - .valueCharacter(it.getValueCharacter()) - .name(it.getValueCharacter().getName()) - .description(it.getValueCharacter().getDescription()) - .startDate(it.getStartDate()) - .endDate(it.getEndDate()) - .build()) + .findLatestCompletedCharacter(memberId) + .map(CharacterResponse::of) .orElseThrow(() -> CustomException.CHARACTER_NOT_FOUND); } @@ -64,32 +57,59 @@ public CharacterResponse getCurrentCharacter(final Long memberId) { public CharactersResponse getCharacters(final Long memberId) { memberRepository.findMember(memberId); - final List characters = - characterRecordRepository - .findByMemberIdAndDeletedAtIsNullWithMemberAndSurveyBundle(memberId) - .stream() - .map( - it -> - SimpleCharacterResponse.builder() - .characterNo(it.getCharacterNo()) - .characterId(it.getId()) - .bundleId(it.getSurveyBundle().getId()) - .build()) - .toList(); - return new CharactersResponse(characters); + final List records = + characterRecordRepository.findByMemberIdAndDeletedAtIsNullWithMemberAndSurveyBundle( + memberId); + return new CharactersResponse(SimpleCharacterResponse.listOf(records)); } @Transactional(readOnly = true) public CharacterValueReportResponse getCharacterReport( final CharacterValueReportCommand command) { - characterRecordRepository - .findByIdAndMemberIdAndDeletedAtIsNullWithMember(command.characterId(), command.memberId()) - .orElseThrow(() -> CustomException.CHARACTER_NOT_FOUND); + final CharacterRecord characterRecord = + characterRecordRepository + .findByIdAndDeletedAtIsNullWithValueReports(command.characterId()) + .orElseThrow(() -> CustomException.CHARACTER_NOT_FOUND); + return CharacterValueReportResponse.of(characterRecord.getValueReports()); + } + + @Transactional + public void createFirstCharacter( + Member member, + SurveyBundle bundle, + List scores, + List submissions) { + final List valueReports = + ScoreEvaluator.of(scores, submissions).generateValueReports(); + final ValueCharacter valueCharacter = + ValueCharacters.of(valueCharacterRepository.findAll()).findTopValueCharacter(valueReports); + characterRecordRepository.save( + CharacterRecord.ofFirst(member, bundle, valueCharacter, valueReports, submissions)); + } - final CharacterValueReport report = - characterValueReportRepository - .findByCharacterIdAndDeletedAtIsNullWithCharacterAndValueReports(command.characterId()) - .orElseThrow(() -> CustomException.CHARACTER_VALUE_REPORT_NOT_FOUND); - return CharacterValueReportResponse.of(report.getValueReports()); + @Transactional + public void createCharacter(Member member, SurveyBundle bundle, LocalDate startDate) { + final CharacterRecord previousRecord = + characterRecordRepository + .findLatestCompletedCharacter(member.getId()) + .orElseThrow(() -> CustomException.CHARACTER_NOT_FOUND); + characterRecordRepository.save(CharacterRecord.of(previousRecord, member, bundle, startDate)); + } + + @Transactional + public void updateCharacter( + Member member, + SurveyBundle bundle, + List scores, + List submissions) { + final List valueReports = + ScoreEvaluator.of(scores, submissions).generateValueReports(); + final ValueCharacter valueCharacter = + ValueCharacters.of(valueCharacterRepository.findAll()).findTopValueCharacter(valueReports); + final CharacterRecord characterRecord = + characterRecordRepository + .findByMemberIdAndBundleId(member.getId(), bundle.getId()) + .orElseThrow(() -> CustomException.CHARACTER_NOT_FOUND); + characterRecord.updateRecord(valueCharacter, valueReports, submissions); } } diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/dto/CharacterResponse.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/dto/CharacterResponse.java index 1e2dcfbb..50d7476d 100644 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/dto/CharacterResponse.java +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/dto/CharacterResponse.java @@ -1,34 +1,51 @@ package org.nexters.jaknaesocore.domain.character.service.dto; -import java.time.LocalDate; +import java.util.List; import lombok.Builder; -import org.nexters.jaknaesocore.domain.character.model.ValueCharacter; +import org.nexters.jaknaesocore.domain.character.model.CharacterRecord; +import org.nexters.jaknaesocore.domain.character.model.CharacterTrait; +import org.nexters.jaknaesocore.domain.character.model.CharacterTraitType; +import org.nexters.jaknaesocore.domain.survey.model.Keyword; +@Builder public record CharacterResponse( Long characterId, String characterNo, - String characterType, + Keyword characterType, String name, String description, + List mainTraits, + List strengths, + List weaknesses, String startDate, String endDate) { @Builder - public static CharacterResponse of( - final Long characterId, - final String characterNo, - final ValueCharacter valueCharacter, - final String name, - final String description, - final LocalDate startDate, - final LocalDate endDate) { + public static CharacterResponse of(final CharacterRecord characterRecord) { return new CharacterResponse( - characterId, - characterNo, - valueCharacter.getKeyword().name(), - name, - description, - startDate.toString(), - endDate.toString()); + characterRecord.getId(), + characterRecord.getCharacterNo(), + characterRecord.getKeyword(), + characterRecord.getName(), + characterRecord.getDescription(), + characterRecord.getTraitsByType(CharacterTraitType.MAIN_TRAIT).stream() + .map(CharacterTraitResponse::of) + .toList(), + characterRecord.getTraitsByType(CharacterTraitType.STRENGTH).stream() + .map(CharacterTraitResponse::of) + .toList(), + characterRecord.getTraitsByType(CharacterTraitType.WEAKNESS).stream() + .map(CharacterTraitResponse::of) + .toList(), + characterRecord.getStartDate().toString(), + characterRecord.getEndDate().toString()); + } + + @Builder + public record CharacterTraitResponse(String description) { + + public static CharacterTraitResponse of(final CharacterTrait characterTrait) { + return new CharacterTraitResponse(characterTrait.getDescription()); + } } } diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/dto/CharactersResponse.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/dto/CharactersResponse.java index c0842c2a..ce7389ab 100644 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/dto/CharactersResponse.java +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/dto/CharactersResponse.java @@ -1,10 +1,5 @@ package org.nexters.jaknaesocore.domain.character.service.dto; import java.util.List; -import lombok.Builder; -public record CharactersResponse(List characters) { - - @Builder - public record SimpleCharacterResponse(String characterNo, Long characterId, Long bundleId) {} -} +public record CharactersResponse(List characters) {} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/dto/SimpleCharacterResponse.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/dto/SimpleCharacterResponse.java new file mode 100644 index 00000000..fb452876 --- /dev/null +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/character/service/dto/SimpleCharacterResponse.java @@ -0,0 +1,24 @@ +package org.nexters.jaknaesocore.domain.character.service.dto; + +import java.util.List; +import lombok.Builder; +import org.nexters.jaknaesocore.domain.character.model.CharacterRecord; + +@Builder +public record SimpleCharacterResponse( + int ordinalNumber, String characterNo, Long characterId, Long bundleId, Boolean isCompleted) { + + public static List listOf(final List records) { + return records.stream().map(SimpleCharacterResponse::of).toList(); + } + + public static SimpleCharacterResponse of(final CharacterRecord record) { + return SimpleCharacterResponse.builder() + .ordinalNumber(record.getOrdinalNumber()) + .characterNo(record.getCharacterNo()) + .characterId(record.getId()) + .bundleId(record.getSurveyBundle().getId()) + .isCompleted(true) + .build(); + } +} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/model/KeywordMetricsMap.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/model/KeywordMetricsMap.java deleted file mode 100644 index 1589305f..00000000 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/model/KeywordMetricsMap.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.nexters.jaknaesocore.domain.survey.model; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class KeywordMetricsMap { - - public static Map generate(final List scores) { - return scores.stream() - .collect(Collectors.groupingBy(KeywordScore::getKeyword, Collectors.toList())) - .entrySet() - .stream() - .collect( - Collectors.toMap(Map.Entry::getKey, entry -> KeywordMetrics.create(entry.getValue()))); - } -} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/model/KeywordScores.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/model/KeywordScores.java new file mode 100644 index 00000000..89c28ada --- /dev/null +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/model/KeywordScores.java @@ -0,0 +1,19 @@ +package org.nexters.jaknaesocore.domain.survey.model; + +import java.util.List; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class KeywordScores { + + @Getter private final List values; + + public static KeywordScores of(List values) { + return new KeywordScores( + values.stream() + .flatMap(survey -> survey.getOptions().stream()) + .flatMap(option -> option.getScores().stream()) + .toList()); + } +} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/model/KeywordWeightMap.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/model/KeywordWeightMap.java deleted file mode 100644 index 60899a35..00000000 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/model/KeywordWeightMap.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.nexters.jaknaesocore.domain.survey.model; - -import java.math.BigDecimal; -import java.util.HashMap; -import java.util.Map; -import org.nexters.jaknaesocore.common.model.ScaledBigDecimal; - -public class KeywordWeightMap { - - public static Map generate(final Map metricsMap) { - Map weightMap = new HashMap<>(); - - int keywordCnt = metricsMap.size(); - ScaledBigDecimal sumPerKeyword = - ScaledBigDecimal.of(BigDecimal.valueOf(100)).divide(BigDecimal.valueOf(keywordCnt)); - - metricsMap.forEach( - (k, v) -> { - var sum = v.getPositive().subtract(v.getNegative()); - weightMap.put(k, sumPerKeyword.divide(sum).getValue()); - }); - return weightMap; - } -} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/model/SurveySubmissions.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/model/SurveySubmissions.java index ccf076a7..ec506ca0 100644 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/model/SurveySubmissions.java +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/model/SurveySubmissions.java @@ -9,10 +9,18 @@ public class SurveySubmissions { private final List submissions; + public static SurveySubmissions of(List submissions) { + return new SurveySubmissions(submissions); + } + public List getSubmittedSurvey(final Long memberId) { return submissions.stream() .filter(submission -> submission.getMember().getId().equals(memberId)) .map(SurveySubmission::getSurvey) .collect(Collectors.toList()); } + + public boolean isFirstSubmitted() { + return submissions.size() == 1; + } } diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/repository/OnboardingSurveyRepository.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/repository/OnboardingSurveyRepository.java index 49df6d10..4db5325f 100644 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/repository/OnboardingSurveyRepository.java +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/repository/OnboardingSurveyRepository.java @@ -1,6 +1,10 @@ package org.nexters.jaknaesocore.domain.survey.repository; +import java.util.Optional; import org.nexters.jaknaesocore.domain.survey.model.OnboardingSurvey; import org.springframework.data.jpa.repository.JpaRepository; -public interface OnboardingSurveyRepository extends JpaRepository {} +public interface OnboardingSurveyRepository extends JpaRepository { + + Optional findTopBy(); +} diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/repository/SurveyRepository.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/repository/SurveyRepository.java index a0bad312..13ba159b 100644 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/repository/SurveyRepository.java +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/repository/SurveyRepository.java @@ -1,6 +1,7 @@ package org.nexters.jaknaesocore.domain.survey.repository; import java.util.List; +import java.util.Optional; import org.nexters.jaknaesocore.domain.survey.model.Survey; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -9,4 +10,7 @@ public interface SurveyRepository extends JpaRepository { @Query("SELECT s FROM Survey s JOIN FETCH s.options WHERE s.id IN :surveyIds") List findAllByIdWithOptions(List surveyIds); + + @Query("SELECT s FROM Survey s JOIN FETCH s.surveyBundle WHERE s.id = :id") + Optional findByIdWithSurveyBundle(final Long id); } diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/repository/SurveySubmissionRepository.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/repository/SurveySubmissionRepository.java index 9daa6077..bc03c971 100644 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/repository/SurveySubmissionRepository.java +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/repository/SurveySubmissionRepository.java @@ -22,12 +22,14 @@ List findByMember_IdAndSurvey_SurveyBundle_Id( "SELECT s FROM SurveySubmission s " + "JOIN FETCH s.member sm JOIN FETCH s.survey ss JOIN FETCH ss.surveyBundle sb " + "WHERE sm.id = :memberId AND sb.id = :bundleId AND s.deletedAt IS NULL") - List findWithSurveyByMemberIdAndBundleIdAndDeletedAtIsNull( + List findByMemberIdAndBundleIdAndDeletedAtIsNullWithSurveyAndSurveyBundle( final Long memberId, final Long bundleId); @Query( "SELECT s FROM SurveySubmission s " - + "JOIN FETCH s.member sm JOIN FETCH s.survey ss JOIN FETCH ss.surveyBundle sb " + + "JOIN FETCH s.member sm JOIN FETCH s.survey ss " + + "JOIN FETCH ss.surveyBundle sb JOIN FETCH ss.options " + "WHERE sm.id = :memberId AND s.deletedAt IS NULL") - List findWithSurveyBundlesByMemberIdAndDeletedAtIsNull(final Long memberId); + List findByMemberIdAndDeletedAtIsNullWithSurveyAndSurveyBundleAndSurveyOption( + final Long memberId); } diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/service/SurveyService.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/service/SurveyService.java index 0e05609b..3b0f6093 100644 --- a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/service/SurveyService.java +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/service/SurveyService.java @@ -2,16 +2,35 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.*; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.nexters.jaknaesocore.common.model.BaseEntity; import org.nexters.jaknaesocore.common.support.error.CustomException; +import org.nexters.jaknaesocore.domain.character.service.CharacterService; import org.nexters.jaknaesocore.domain.member.model.Member; import org.nexters.jaknaesocore.domain.member.repository.MemberRepository; -import org.nexters.jaknaesocore.domain.survey.dto.*; -import org.nexters.jaknaesocore.domain.survey.model.*; +import org.nexters.jaknaesocore.domain.survey.dto.OnboardingSubmissionResult; +import org.nexters.jaknaesocore.domain.survey.dto.OnboardingSubmissionsCommand; +import org.nexters.jaknaesocore.domain.survey.dto.OnboardingSurveyResponse; +import org.nexters.jaknaesocore.domain.survey.dto.SurveyHistoryDetailResponse; +import org.nexters.jaknaesocore.domain.survey.dto.SurveyHistoryResponse; +import org.nexters.jaknaesocore.domain.survey.dto.SurveyResponse; +import org.nexters.jaknaesocore.domain.survey.dto.SurveySubmissionCommand; +import org.nexters.jaknaesocore.domain.survey.dto.SurveySubmissionHistoryCommand; +import org.nexters.jaknaesocore.domain.survey.dto.SurveySubmissionHistoryResponse; +import org.nexters.jaknaesocore.domain.survey.model.KeywordScores; +import org.nexters.jaknaesocore.domain.survey.model.OnboardingSurvey; +import org.nexters.jaknaesocore.domain.survey.model.Survey; +import org.nexters.jaknaesocore.domain.survey.model.SurveyBundle; +import org.nexters.jaknaesocore.domain.survey.model.SurveyOption; +import org.nexters.jaknaesocore.domain.survey.model.SurveyRecord; +import org.nexters.jaknaesocore.domain.survey.model.SurveySubmission; +import org.nexters.jaknaesocore.domain.survey.model.SurveySubmissions; import org.nexters.jaknaesocore.domain.survey.repository.OnboardingSurveyRepository; import org.nexters.jaknaesocore.domain.survey.repository.SurveyBundleRepository; import org.nexters.jaknaesocore.domain.survey.repository.SurveyRepository; @@ -28,6 +47,7 @@ public class SurveyService { private final SurveySubmissionRepository surveySubmissionRepository; private final SurveyRepository surveyRepository; private final OnboardingSurveyRepository onboardingSurveyRepository; + private final CharacterService characterService; @Transactional(readOnly = true) public SurveyResponse getNextSurvey(final Long bundleId, final Long memberId) { @@ -123,7 +143,7 @@ private SurveyHistoryResponse getNextBundleHistory(List submis public void submitSurvey(SurveySubmissionCommand command, LocalDateTime submittedAt) { Survey survey = surveyRepository - .findById(command.surveyId()) + .findByIdWithSurveyBundle(command.surveyId()) .orElseThrow(() -> CustomException.SURVEY_NOT_FOUND); SurveyOption surveyOption = survey.getOptionById(command.optionId()); Member member = @@ -135,6 +155,26 @@ public void submitSurvey(SurveySubmissionCommand command, LocalDateTime submitte SurveySubmission.create(member, survey, surveyOption, command.comment(), submittedAt); surveySubmissionRepository.save(surveySubmission); + + final SurveyBundle bundle = survey.getSurveyBundle(); + final List submissions = + surveySubmissionRepository + .findByMemberIdAndBundleIdAndDeletedAtIsNullWithSurveyAndSurveyBundle( + member.getId(), bundle.getId()); + if (SurveySubmissions.of(submissions).isFirstSubmitted()) { + characterService.createCharacter(member, bundle, submittedAt.toLocalDate()); + } + if (bundle.isAllSubmitted(submissions)) { + completeCharacter(member, bundle, submissions); + } + } + + private void completeCharacter( + final Member member, final SurveyBundle bundle, final List submissions) { + final List surveys = submissions.stream().map(SurveySubmission::getSurvey).toList(); + final KeywordScores scores = KeywordScores.of(surveys); + + characterService.updateCharacter(member, bundle, scores.getValues(), submissions); } @Transactional(readOnly = true) @@ -170,10 +210,18 @@ public void submitOnboardingSurvey( createSurveyToSelectedOption(command.submissions(), surveyMap); List submissions = - createSubmissionsBy(submittedAt, surveyToSelectedOption, member); - - surveySubmissionRepository.saveAll(submissions); + surveySubmissionRepository.saveAll( + createSubmissionsBy(submittedAt, surveyToSelectedOption, member)); member.completeOnboarding(submittedAt); + final SurveyBundle onboardingBundle = + onboardingSurveyRepository + .findTopBy() + .orElseThrow(() -> CustomException.SURVEY_NOT_FOUND) + .getSurveyBundle(); + final KeywordScores scores = + KeywordScores.of(submissions.stream().map(SurveySubmission::getSurvey).toList()); + characterService.createFirstCharacter( + member, onboardingBundle, scores.getValues(), submissions); } private Member getMember(Long memberId) { diff --git a/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/service/event/CreateCharacterEvent.java b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/service/event/CreateCharacterEvent.java new file mode 100644 index 00000000..b08cfd88 --- /dev/null +++ b/jaknaeso-core/src/main/java/org/nexters/jaknaesocore/domain/survey/service/event/CreateCharacterEvent.java @@ -0,0 +1,13 @@ +package org.nexters.jaknaesocore.domain.survey.service.event; + +import java.util.List; +import org.nexters.jaknaesocore.domain.member.model.Member; +import org.nexters.jaknaesocore.domain.survey.model.KeywordScore; +import org.nexters.jaknaesocore.domain.survey.model.SurveyBundle; +import org.nexters.jaknaesocore.domain.survey.model.SurveySubmission; + +public record CreateCharacterEvent( + Member member, + SurveyBundle bundle, + List scores, + List submissions) {} diff --git a/jaknaeso-core/src/main/resources/sql/037/001_alter_table_member_add_name_col_add_email_col.sql b/jaknaeso-core/src/main/resources/sql/037/001_alter_table_member_add_name_col_add_email_col.sql index 64f2a78c..272f5966 100644 --- a/jaknaeso-core/src/main/resources/sql/037/001_alter_table_member_add_name_col_add_email_col.sql +++ b/jaknaeso-core/src/main/resources/sql/037/001_alter_table_member_add_name_col_add_email_col.sql @@ -1,3 +1,3 @@ alter table member - add name varchar(60) + add name varchar(60), add email varchar(255); \ No newline at end of file diff --git a/jaknaeso-core/src/main/resources/sql/057/002_create_table_character_record.sql b/jaknaeso-core/src/main/resources/sql/057/002_create_table_character_record.sql index bb6c2099..47cba915 100644 --- a/jaknaeso-core/src/main/resources/sql/057/002_create_table_character_record.sql +++ b/jaknaeso-core/src/main/resources/sql/057/002_create_table_character_record.sql @@ -6,7 +6,8 @@ create table if not exists character_record description varchar(255), start_date date, end_date date, - bundle_id bigint, + bundle_id bigint not null, + member_id bigint not null, created_at timestamp(6), updated_at timestamp(6), deleted_at timestamp(6) diff --git a/jaknaeso-core/src/main/resources/sql/057/003_create_table_character_value_report.sql b/jaknaeso-core/src/main/resources/sql/057/003_create_table_character_value_report.sql index 7dc3849a..e23b4095 100644 --- a/jaknaeso-core/src/main/resources/sql/057/003_create_table_character_value_report.sql +++ b/jaknaeso-core/src/main/resources/sql/057/003_create_table_character_value_report.sql @@ -1,8 +1,8 @@ create table if not exists character_value_report ( - id bigint auto_increment primary key, - character_id bigint, - created_at timestamp(6), - updated_at timestamp(6), - deleted_at timestamp(6) + id bigint auto_increment primary key, + character_record_id bigint, + created_at timestamp(6), + updated_at timestamp(6), + deleted_at timestamp(6) ); \ No newline at end of file diff --git a/jaknaeso-core/src/main/resources/sql/093/002_alter_table_character_record_drop_type_col_drop_description_col.sql b/jaknaeso-core/src/main/resources/sql/093/002_alter_table_character_record_drop_type_col_drop_description_col.sql new file mode 100644 index 00000000..4bf324fb --- /dev/null +++ b/jaknaeso-core/src/main/resources/sql/093/002_alter_table_character_record_drop_type_col_drop_description_col.sql @@ -0,0 +1,3 @@ +alter table character_record + drop column type, + drop column description; \ No newline at end of file diff --git a/jaknaeso-core/src/main/resources/sql/093/003_alter_table_character_record_add_value_character_id_col.sql b/jaknaeso-core/src/main/resources/sql/093/003_alter_table_character_record_add_value_character_id_col.sql new file mode 100644 index 00000000..ec4d030a --- /dev/null +++ b/jaknaeso-core/src/main/resources/sql/093/003_alter_table_character_record_add_value_character_id_col.sql @@ -0,0 +1,2 @@ +alter table character_record + add value_character_id bigint; \ No newline at end of file diff --git a/jaknaeso-core/src/main/resources/sql/095/001_drop_table_value_reports.sql b/jaknaeso-core/src/main/resources/sql/095/001_drop_table_value_reports.sql new file mode 100644 index 00000000..1bd13152 --- /dev/null +++ b/jaknaeso-core/src/main/resources/sql/095/001_drop_table_value_reports.sql @@ -0,0 +1 @@ +drop table if exists value_reports; \ No newline at end of file diff --git a/jaknaeso-core/src/main/resources/sql/095/002_drop_table_character_value_report.sql b/jaknaeso-core/src/main/resources/sql/095/002_drop_table_character_value_report.sql new file mode 100644 index 00000000..064872a3 --- /dev/null +++ b/jaknaeso-core/src/main/resources/sql/095/002_drop_table_character_value_report.sql @@ -0,0 +1 @@ +drop table character_value_report; \ No newline at end of file diff --git a/jaknaeso-core/src/main/resources/sql/095/003_create_table_value_report.sql b/jaknaeso-core/src/main/resources/sql/095/003_create_table_value_report.sql new file mode 100644 index 00000000..afcb4881 --- /dev/null +++ b/jaknaeso-core/src/main/resources/sql/095/003_create_table_value_report.sql @@ -0,0 +1,10 @@ +create table if not exists value_report +( + id bigint auto_increment primary key, + keyword varchar(30), + percentage decimal(5,2), + character_record_id bigint not null, + created_at timestamp(6), + updated_at timestamp(6), + deleted_at timestamp(6) +); \ No newline at end of file diff --git a/jaknaeso-core/src/main/resources/sql/095/004_create_table_character_trait.sql b/jaknaeso-core/src/main/resources/sql/095/004_create_table_character_trait.sql new file mode 100644 index 00000000..2440e396 --- /dev/null +++ b/jaknaeso-core/src/main/resources/sql/095/004_create_table_character_trait.sql @@ -0,0 +1,10 @@ +create table if not exists character_trait +( + id bigint auto_increment primary key, + value_character_id bigint unsigned not null, + type varchar(20) not null, + description varchar(255) not null, + created_at timestamp(6), + updated_at timestamp(6), + deleted_at timestamp(6) +); \ No newline at end of file diff --git a/jaknaeso-core/src/main/resources/sql/095/005_alter_table_character_record.sql b/jaknaeso-core/src/main/resources/sql/095/005_alter_table_character_record.sql new file mode 100644 index 00000000..993bba0d --- /dev/null +++ b/jaknaeso-core/src/main/resources/sql/095/005_alter_table_character_record.sql @@ -0,0 +1,2 @@ +alter table character_record + add ordinal_number int null after id; diff --git a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/model/CharacterTraitsTest.java b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/model/CharacterTraitsTest.java new file mode 100644 index 00000000..b55d452e --- /dev/null +++ b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/model/CharacterTraitsTest.java @@ -0,0 +1,101 @@ +package org.nexters.jaknaesocore.domain.character.model; + +import static org.assertj.core.api.BDDAssertions.then; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class CharacterTraitsTest { + + @Nested + @DisplayName("getByType 메소드는") + class getByType { + + @Nested + @DisplayName("MAIN_TRAIT을 전달받으면, ") + class whenMainTrait { + + @Test + @DisplayName("주요 특성 목록만 반환한다.") + void shouldOnlyReturnMainTraits() { + final CharacterTraits sut = + createCharacterTraits( + CharacterTraitType.MAIN_TRAIT, + CharacterTraitType.STRENGTH, + CharacterTraitType.WEAKNESS); + + then(sut.getByType(CharacterTraitType.MAIN_TRAIT)) + .hasSize(1) + .extracting("type") + .containsExactly(CharacterTraitType.MAIN_TRAIT); + } + } + + @Nested + @DisplayName("MAIN_TRAIT을 전달 받았는데 목록이 비어있다면, ") + class whenMainTraitButEmpty { + + @Test + @DisplayName("빈 배열을 반환한다.") + void shouldReturnEmptyList() { + final CharacterTraits sut = + createCharacterTraits(CharacterTraitType.STRENGTH, CharacterTraitType.WEAKNESS); + + then(sut.getByType(CharacterTraitType.MAIN_TRAIT)).hasSize(0); + } + } + + @Nested + @DisplayName("STRENGTH을 전달받으면, ") + class whenStrength { + + @Test + @DisplayName("주요 특성 목록만 반환한다.") + void shouldOnlyReturnStrengthTraits() { + final CharacterTraits sut = + createCharacterTraits( + CharacterTraitType.MAIN_TRAIT, + CharacterTraitType.STRENGTH, + CharacterTraitType.WEAKNESS); + + then(sut.getByType(CharacterTraitType.STRENGTH)) + .hasSize(1) + .extracting("type") + .containsExactly(CharacterTraitType.STRENGTH); + } + } + + @Nested + @DisplayName("WEAKNESS을 전달받으면, ") + class whenWeakness { + + @Test + @DisplayName("주요 특성 목록만 반환한다.") + void shouldOnlyReturnWeaknessTraits() { + final CharacterTraits sut = + createCharacterTraits( + CharacterTraitType.MAIN_TRAIT, + CharacterTraitType.STRENGTH, + CharacterTraitType.WEAKNESS); + + then(sut.getByType(CharacterTraitType.WEAKNESS)) + .hasSize(1) + .extracting("type") + .containsExactly(CharacterTraitType.WEAKNESS); + } + } + } + + private CharacterTrait createCharacterTrait(final CharacterTraitType type) { + return CharacterTrait.builder().type(type).build(); + } + + private CharacterTraits createCharacterTraits(final CharacterTraitType... type) { + return CharacterTraits.builder() + .values(Stream.of(type).map(this::createCharacterTrait).toList()) + .build(); + } +} diff --git a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/model/ValueReportsTest.java b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/model/ValueReportsTest.java index 2a6c7b1a..f9f70465 100644 --- a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/model/ValueReportsTest.java +++ b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/model/ValueReportsTest.java @@ -8,21 +8,53 @@ import static org.nexters.jaknaesocore.domain.survey.model.Keyword.SUCCESS; import java.math.BigDecimal; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.nexters.jaknaesocore.common.model.ScaledBigDecimal; +import org.nexters.jaknaesocore.common.support.IntegrationTest; +import org.nexters.jaknaesocore.domain.character.repository.CharacterRecordRepository; +import org.nexters.jaknaesocore.domain.character.repository.ValueReportRepository; import org.nexters.jaknaesocore.domain.survey.model.BalanceSurvey; import org.nexters.jaknaesocore.domain.survey.model.Keyword; import org.nexters.jaknaesocore.domain.survey.model.KeywordMetrics; -import org.nexters.jaknaesocore.domain.survey.model.KeywordMetricsMap; import org.nexters.jaknaesocore.domain.survey.model.KeywordScore; -import org.nexters.jaknaesocore.domain.survey.model.KeywordWeightMap; import org.nexters.jaknaesocore.domain.survey.model.SurveyBundle; import org.nexters.jaknaesocore.domain.survey.model.SurveyOption; import org.nexters.jaknaesocore.domain.survey.model.SurveySubmission; +import org.springframework.beans.factory.annotation.Autowired; -class ValueReportsTest { +class ValueReportsTest extends IntegrationTest { + + @Autowired private CharacterRecordRepository characterRecordRepository; + @Autowired private ValueReportRepository valueReportRepository; + + private Map generateMetricsMap(List scores) { + return scores.stream() + .collect(Collectors.groupingBy(KeywordScore::getKeyword, Collectors.toList())) + .entrySet() + .stream() + .collect( + Collectors.toMap(Map.Entry::getKey, entry -> KeywordMetrics.create(entry.getValue()))); + } + + private Map generateWeightsMap( + final Map metricsMap) { + Map weightMap = new HashMap<>(); + + int keywordCnt = metricsMap.size(); + ScaledBigDecimal sumPerKeyword = + ScaledBigDecimal.of(BigDecimal.valueOf(100)).divide(BigDecimal.valueOf(keywordCnt)); + + metricsMap.forEach( + (k, v) -> { + var sum = v.getPositive().subtract(v.getNegative()); + weightMap.put(k, sumPerKeyword.divide(sum).getValue()); + }); + return weightMap; + } @Test void 키워드_가중치와_설문_응답_목록으로_가치관_리포트를_반환한다() { @@ -33,8 +65,8 @@ class ValueReportsTest { KeywordScore.builder().keyword(SUCCESS).score(BigDecimal.valueOf(1)).build(), KeywordScore.builder().keyword(BENEVOLENCE).score(BigDecimal.valueOf(1)).build()); - final Map metricsMap = KeywordMetricsMap.generate(scores); - final Map weightMap = KeywordWeightMap.generate(metricsMap); + final Map metricsMap = generateMetricsMap(scores); + final Map weightMap = generateWeightsMap(metricsMap); final SurveyBundle bundle = new SurveyBundle(); final BalanceSurvey survey1 = @@ -95,9 +127,7 @@ class ValueReportsTest { SurveySubmission.builder().survey(survey4).selectedOption(option4).build(), SurveySubmission.builder().survey(survey5).selectedOption(option5).build()); - final ValueReports reports = ValueReports.of(weightMap, metricsMap, submissions); - - final List actual = reports.getReports(); + final List actual = ValueReports.report(weightMap, metricsMap, submissions); assertAll( () -> diff --git a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/repository/CharacterRecordRepositoryTest.java b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/repository/CharacterRecordRepositoryTest.java index cd134028..4376040b 100644 --- a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/repository/CharacterRecordRepositoryTest.java +++ b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/repository/CharacterRecordRepositoryTest.java @@ -2,21 +2,25 @@ import static org.assertj.core.api.BDDAssertions.then; import static org.junit.jupiter.api.Assertions.assertAll; +import static org.nexters.jaknaesocore.domain.survey.model.Keyword.SELF_DIRECTION; +import static org.nexters.jaknaesocore.domain.survey.model.Keyword.SUCCESS; +import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.nexters.jaknaesocore.common.model.ScaledBigDecimal; import org.nexters.jaknaesocore.common.support.IntegrationTest; import org.nexters.jaknaesocore.domain.character.model.CharacterRecord; import org.nexters.jaknaesocore.domain.character.model.ValueCharacter; +import org.nexters.jaknaesocore.domain.character.model.ValueReport; import org.nexters.jaknaesocore.domain.member.model.Member; import org.nexters.jaknaesocore.domain.member.repository.MemberRepository; -import org.nexters.jaknaesocore.domain.survey.model.Keyword; import org.nexters.jaknaesocore.domain.survey.model.SurveyBundle; import org.nexters.jaknaesocore.domain.survey.repository.SurveyBundleRepository; -import org.nexters.jaknaesocore.domain.survey.repository.SurveyOptionRepository; -import org.nexters.jaknaesocore.domain.survey.repository.SurveyRepository; -import org.nexters.jaknaesocore.domain.survey.repository.SurveySubmissionRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; @@ -27,50 +31,91 @@ class CharacterRecordRepositoryTest extends IntegrationTest { @Autowired private CharacterRecordRepository sut; + @Autowired private ValueReportRepository valueReportRepository; @Autowired private ValueCharacterRepository valueCharacterRepository; - @Autowired private CharacterValueReportRepository characterValueReportRepository; @Autowired private MemberRepository memberRepository; @Autowired private SurveyBundleRepository surveyBundleRepository; - @Autowired private SurveyRepository surveyRepository; - @Autowired private SurveyOptionRepository surveyOptionRepository; - @Autowired private SurveySubmissionRepository surveySubmissionRepository; - @Test - void 회원의_현재_캐릭터를_조회한다() { - final Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); - final SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); - final ValueCharacter valueCharacter1 = - valueCharacterRepository.save( - new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", Keyword.SUCCESS)); - final ValueCharacter valueCharacter2 = - valueCharacterRepository.save( - new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", Keyword.SUCCESS)); - final CharacterRecord characterRecord1 = - CharacterRecord.builder() - .characterNo("첫번째") - .valueCharacter(valueCharacter1) - .startDate(LocalDate.now().minusDays(31)) - .endDate(LocalDate.now().minusDays(16)) - .member(member) - .surveyBundle(bundle) - .build(); - final CharacterRecord characterRecord2 = - CharacterRecord.builder() - .characterNo("두번째") - .valueCharacter(valueCharacter2) - .startDate(LocalDate.now().minusDays(15)) - .endDate(LocalDate.now()) - .member(member) - .surveyBundle(bundle) - .build(); - sut.saveAll(List.of(characterRecord1, characterRecord2)); + @AfterEach + void tearDown() { + valueReportRepository.deleteAllInBatch(); + sut.deleteAllInBatch(); + valueCharacterRepository.deleteAllInBatch(); + surveyBundleRepository.deleteAllInBatch(); + memberRepository.deleteAllInBatch(); + } - final CharacterRecord actual = - sut.findTopByMemberIdAndDeletedAtIsNullWithMember(member.getId()).get(); + @Nested + @DisplayName("findLatestCompletedCharacter 메소드는 ") + class findLatestCompletedCharacter { - then(actual) - .extracting("characterNo", "valueCharacter") - .containsExactly("두번째", valueCharacter2); + @Nested + @DisplayName("모든 데이터에 ValueCharacter가 지정되어 있으면, ") + class whenValueCharacterIsNotNull { + + @Test + void 가장_최신의_캐릭터를_조회한다() { + final Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); + final SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); + final ValueCharacter valueCharacter = + valueCharacterRepository.save(ValueCharacter.builder().build()); + + sut.saveAll( + List.of( + CharacterRecord.builder() + .characterNo("첫번째") + .endDate(LocalDate.now().minusDays(15)) + .member(member) + .surveyBundle(bundle) + .valueCharacter(valueCharacter) + .build(), + CharacterRecord.builder() + .characterNo("두번째") + .endDate(LocalDate.now()) + .member(member) + .surveyBundle(bundle) + .valueCharacter(valueCharacter) + .build())); + + final CharacterRecord actual = sut.findLatestCompletedCharacter(member.getId()).get(); + + then(actual.getCharacterNo()).isEqualTo("두번째"); + } + } + + @Nested + @DisplayName("지정된 ValueCharacter가 없는 데이터가 있는 경우, ") + class whenValueCharacterIsNull { + + @Test + void ValueCharacter가_있는_데이터만_고려하여_가장_최신의_캐릭터를_조회한다() { + final Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); + final SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); + final ValueCharacter valueCharacter = + valueCharacterRepository.save(ValueCharacter.builder().build()); + + sut.saveAll( + List.of( + CharacterRecord.builder() + .characterNo("첫번째") + .endDate(LocalDate.now().minusDays(15)) + .member(member) + .surveyBundle(bundle) + .valueCharacter(valueCharacter) + .build(), + CharacterRecord.builder() + .characterNo("두번째") + .endDate(LocalDate.now()) + .member(member) + .surveyBundle(bundle) + .valueCharacter(null) + .build())); + + final CharacterRecord actual = sut.findLatestCompletedCharacter(member.getId()).get(); + + then(actual.getCharacterNo()).isEqualTo("첫번째"); + } + } } @Test @@ -78,15 +123,12 @@ class CharacterRecordRepositoryTest extends IntegrationTest { final Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); final SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); final ValueCharacter valueCharacter = - valueCharacterRepository.save( - new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", Keyword.SUCCESS)); + valueCharacterRepository.save(new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", SUCCESS)); final CharacterRecord characterRecord = sut.save( CharacterRecord.builder() .characterNo("첫번째") .valueCharacter(valueCharacter) - .startDate(LocalDate.now().minusDays(31)) - .endDate(LocalDate.now().minusDays(16)) .member(member) .surveyBundle(bundle) .build()); @@ -103,18 +145,8 @@ class CharacterRecordRepositoryTest extends IntegrationTest { void 회원의_캐릭터를_설문_번들과_함께_조회한다() { final Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); final SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); - final ValueCharacter valueCharacter = - valueCharacterRepository.save( - new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", Keyword.SUCCESS)); - sut.save( - CharacterRecord.builder() - .characterNo("첫번째") - .valueCharacter(valueCharacter) - .startDate(LocalDate.now().minusDays(15)) - .endDate(LocalDate.now()) - .member(member) - .surveyBundle(bundle) - .build()); + + sut.save(CharacterRecord.builder().member(member).surveyBundle(bundle).build()); final List actual = sut.findByMemberIdAndDeletedAtIsNullWithMemberAndSurveyBundle(member.getId()); @@ -123,4 +155,31 @@ class CharacterRecordRepositoryTest extends IntegrationTest { () -> then(actual).hasSize(1), () -> then(actual.get(0).getSurveyBundle()).isEqualTo(bundle)); } + + @Transactional + @Test + void 회원의_캐릭터_분석_결과를_조회한다() { + final Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); + final SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); + final ValueReport valueReport = + valueReportRepository.save( + ValueReport.of(SELF_DIRECTION, ScaledBigDecimal.of(BigDecimal.valueOf(100)))); + + final CharacterRecord characterRecord = + sut.save( + CharacterRecord.builder() + .characterNo("첫번째") + .valueReports(List.of(valueReport)) + .member(member) + .surveyBundle(bundle) + .build()); + + final CharacterRecord actual = + sut.findByIdAndDeletedAtIsNullWithValueReports(characterRecord.getId()).get(); + + then(actual) + .extracting("valueReports") + .usingRecursiveComparison() + .isEqualTo(List.of(valueReport)); + } } diff --git a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/repository/CharacterValueReportRepositoryTest.java b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/repository/CharacterValueReportRepositoryTest.java deleted file mode 100644 index 5c7a7413..00000000 --- a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/repository/CharacterValueReportRepositoryTest.java +++ /dev/null @@ -1,168 +0,0 @@ -package org.nexters.jaknaesocore.domain.character.repository; - -import static org.assertj.core.api.BDDAssertions.then; -import static org.nexters.jaknaesocore.domain.survey.model.Keyword.BENEVOLENCE; -import static org.nexters.jaknaesocore.domain.survey.model.Keyword.SELF_DIRECTION; -import static org.nexters.jaknaesocore.domain.survey.model.Keyword.STABILITY; -import static org.nexters.jaknaesocore.domain.survey.model.Keyword.SUCCESS; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.nexters.jaknaesocore.common.support.IntegrationTest; -import org.nexters.jaknaesocore.domain.character.model.CharacterRecord; -import org.nexters.jaknaesocore.domain.character.model.CharacterValueReport; -import org.nexters.jaknaesocore.domain.character.model.ValueCharacter; -import org.nexters.jaknaesocore.domain.character.model.ValueReports; -import org.nexters.jaknaesocore.domain.member.model.Member; -import org.nexters.jaknaesocore.domain.member.repository.MemberRepository; -import org.nexters.jaknaesocore.domain.survey.model.BalanceSurvey; -import org.nexters.jaknaesocore.domain.survey.model.Keyword; -import org.nexters.jaknaesocore.domain.survey.model.KeywordMetrics; -import org.nexters.jaknaesocore.domain.survey.model.KeywordMetricsMap; -import org.nexters.jaknaesocore.domain.survey.model.KeywordScore; -import org.nexters.jaknaesocore.domain.survey.model.KeywordWeightMap; -import org.nexters.jaknaesocore.domain.survey.model.SurveyBundle; -import org.nexters.jaknaesocore.domain.survey.model.SurveyOption; -import org.nexters.jaknaesocore.domain.survey.model.SurveySubmission; -import org.nexters.jaknaesocore.domain.survey.repository.SurveyBundleRepository; -import org.nexters.jaknaesocore.domain.survey.repository.SurveyOptionRepository; -import org.nexters.jaknaesocore.domain.survey.repository.SurveyRepository; -import org.nexters.jaknaesocore.domain.survey.repository.SurveySubmissionRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Transactional; - -class CharacterValueReportRepositoryTest extends IntegrationTest { - - @Autowired private CharacterValueReportRepository sut; - - @Autowired private ValueCharacterRepository valueCharacterRepository; - @Autowired private CharacterRecordRepository characterRecordRepository; - @Autowired private MemberRepository memberRepository; - @Autowired private SurveyBundleRepository surveyBundleRepository; - @Autowired private SurveyRepository surveyRepository; - @Autowired private SurveyOptionRepository surveyOptionRepository; - @Autowired private SurveySubmissionRepository surveySubmissionRepository; - - @AfterEach - void tearDown() { - sut.deleteAllInBatch(); - characterRecordRepository.deleteAllInBatch(); - valueCharacterRepository.deleteAllInBatch(); - surveySubmissionRepository.deleteAllInBatch(); - surveyOptionRepository.deleteAllInBatch(); - surveyRepository.deleteAllInBatch(); - surveyBundleRepository.deleteAllInBatch(); - memberRepository.deleteAllInBatch(); - } - - @Transactional - @Test - void 회원의_캐릭터_분석_결과를_조회한다() { - final Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); - final SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); - final BalanceSurvey survey1 = - new BalanceSurvey( - "꿈에 그리던 드림 기업에 입사했다. 연봉도 좋지만, 무엇보다 회사의 근무 방식이 나와 잘 맞는 것 같다. 우리 회사의 근무 방식은...", bundle); - final SurveyOption option1 = - SurveyOption.builder() - .survey(survey1) - .content("자율 출퇴근제로 원하는 시간에 근무하며 창의적인 성과 내기") - .scores( - List.of( - KeywordScore.builder().keyword(SELF_DIRECTION).score(BigDecimal.ONE).build())) - .build(); - - final BalanceSurvey survey2 = new BalanceSurvey("독립에 대한 고민이 깊어지는 요즘... 드디어 결정을 내렸다.", bundle); - final SurveyOption option2 = - SurveyOption.builder() - .survey(survey2) - .content("내 취향대로 꾸민 집에서 자유롭게 생활하기") - .scores( - List.of( - KeywordScore.builder().keyword(SELF_DIRECTION).score(BigDecimal.ONE).build())) - .build(); - - final BalanceSurvey survey3 = - new BalanceSurvey("바쁜 일상에 지쳐버린 나. 여가 시간을 더 의미 있게 보내고 싶어졌다.", bundle); - final SurveyOption option3 = - SurveyOption.builder() - .survey(survey3) - .content("매년 새로운 취미에 도전하며 색다른 즐거움 찾기") - .scores( - List.of(KeywordScore.builder().keyword(STABILITY).score(BigDecimal.ONE).build())) - .build(); - - final BalanceSurvey survey4 = new BalanceSurvey("회사에서 새로운 평가 시스템을 도입한다. 당신의 선택은?", bundle); - final SurveyOption option4 = - SurveyOption.builder() - .survey(survey4) - .content("업무 성과에 따라 차등 보너스를 지급한다") - .scores(List.of(KeywordScore.builder().keyword(SUCCESS).score(BigDecimal.ONE).build())) - .build(); - - final BalanceSurvey survey5 = - new BalanceSurvey("연애를 시작한지도 어연 3개월, 그 사람과 나의 연애는 꽤 잘 맞는다. 우리의 관계는...", bundle); - final SurveyOption option5 = - SurveyOption.builder() - .survey(survey5) - .content("서로의 일상 속에서 따뜻하게 지지하는 관계") - .scores( - List.of(KeywordScore.builder().keyword(BENEVOLENCE).score(BigDecimal.ONE).build())) - .build(); - - final List submissions = - List.of( - SurveySubmission.builder().survey(survey1).selectedOption(option1).build(), - SurveySubmission.builder().survey(survey2).selectedOption(option2).build(), - SurveySubmission.builder().survey(survey3).selectedOption(option3).build(), - SurveySubmission.builder().survey(survey4).selectedOption(option4).build(), - SurveySubmission.builder().survey(survey5).selectedOption(option5).build()); - - final Map weights = new HashMap<>(); - weights.put(SELF_DIRECTION, BigDecimal.valueOf(5)); - weights.put(STABILITY, BigDecimal.valueOf(25)); - weights.put(SUCCESS, BigDecimal.valueOf(25)); - weights.put(BENEVOLENCE, BigDecimal.valueOf(5)); - - final List scores = - List.of( - KeywordScore.builder().keyword(SELF_DIRECTION).score(BigDecimal.valueOf(1)).build(), - KeywordScore.builder().keyword(STABILITY).score(BigDecimal.valueOf(1)).build(), - KeywordScore.builder().keyword(SUCCESS).score(BigDecimal.valueOf(1)).build(), - KeywordScore.builder().keyword(BENEVOLENCE).score(BigDecimal.valueOf(1)).build()); - - final Map metricsMap = KeywordMetricsMap.generate(scores); - final Map weightMap = KeywordWeightMap.generate(metricsMap); - - final ValueCharacter valueCharacter = - valueCharacterRepository.save( - new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", Keyword.SUCCESS)); - final CharacterRecord characterRecord = - characterRecordRepository.save( - CharacterRecord.builder() - .characterNo("첫번째") - .valueCharacter(valueCharacter) - .startDate(LocalDate.now().minusDays(15)) - .endDate(LocalDate.now()) - .member(member) - .surveyBundle(bundle) - .build()); - final CharacterValueReport report = - new CharacterValueReport( - characterRecord, ValueReports.of(weightMap, metricsMap, submissions).getReports()); - - final CharacterValueReport actual = - sut.findByCharacterIdAndDeletedAtIsNullWithCharacterAndValueReports(characterRecord.getId()) - .get(); - - then(actual) - .extracting("valueReports") - .usingRecursiveComparison() - .isEqualTo(ValueReports.of(weightMap, metricsMap, submissions).getReports()); - } -} diff --git a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/service/CharacterServiceTest.java b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/service/CharacterServiceTest.java index d38a718a..1b97173f 100644 --- a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/service/CharacterServiceTest.java +++ b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/character/service/CharacterServiceTest.java @@ -3,25 +3,28 @@ import static org.assertj.core.api.BDDAssertions.then; import static org.assertj.core.api.BDDAssertions.thenThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; +import static org.nexters.jaknaesocore.domain.survey.model.Keyword.BENEVOLENCE; import static org.nexters.jaknaesocore.domain.survey.model.Keyword.SELF_DIRECTION; +import static org.nexters.jaknaesocore.domain.survey.model.Keyword.STABILITY; +import static org.nexters.jaknaesocore.domain.survey.model.Keyword.SUCCESS; import java.math.BigDecimal; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; -import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.nexters.jaknaesocore.common.model.ScaledBigDecimal; import org.nexters.jaknaesocore.common.support.IntegrationTest; import org.nexters.jaknaesocore.common.support.error.CustomException; import org.nexters.jaknaesocore.domain.character.model.CharacterRecord; -import org.nexters.jaknaesocore.domain.character.model.CharacterValueReport; import org.nexters.jaknaesocore.domain.character.model.ValueCharacter; -import org.nexters.jaknaesocore.domain.character.model.ValueReports; +import org.nexters.jaknaesocore.domain.character.model.ValueReport; import org.nexters.jaknaesocore.domain.character.repository.CharacterRecordRepository; -import org.nexters.jaknaesocore.domain.character.repository.CharacterValueReportRepository; import org.nexters.jaknaesocore.domain.character.repository.ValueCharacterRepository; +import org.nexters.jaknaesocore.domain.character.repository.ValueReportRepository; import org.nexters.jaknaesocore.domain.character.service.dto.CharacterCommand; import org.nexters.jaknaesocore.domain.character.service.dto.CharacterResponse; import org.nexters.jaknaesocore.domain.character.service.dto.CharacterValueReportCommand; @@ -30,12 +33,8 @@ import org.nexters.jaknaesocore.domain.member.model.Member; import org.nexters.jaknaesocore.domain.member.repository.MemberRepository; import org.nexters.jaknaesocore.domain.survey.model.BalanceSurvey; -import org.nexters.jaknaesocore.domain.survey.model.Keyword; -import org.nexters.jaknaesocore.domain.survey.model.KeywordMetrics; -import org.nexters.jaknaesocore.domain.survey.model.KeywordMetricsMap; import org.nexters.jaknaesocore.domain.survey.model.KeywordScore; -import org.nexters.jaknaesocore.domain.survey.model.KeywordWeightMap; -import org.nexters.jaknaesocore.domain.survey.model.Survey; +import org.nexters.jaknaesocore.domain.survey.model.KeywordScores; import org.nexters.jaknaesocore.domain.survey.model.SurveyBundle; import org.nexters.jaknaesocore.domain.survey.model.SurveyOption; import org.nexters.jaknaesocore.domain.survey.model.SurveySubmission; @@ -46,6 +45,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.transaction.annotation.Transactional; @DirtiesContext(classMode = ClassMode.BEFORE_CLASS) class CharacterServiceTest extends IntegrationTest { @@ -53,9 +53,9 @@ class CharacterServiceTest extends IntegrationTest { @Autowired private CharacterService sut; @Autowired private MemberRepository memberRepository; + @Autowired private ValueReportRepository valueReportRepository; @Autowired private ValueCharacterRepository valueCharacterRepository; @Autowired private CharacterRecordRepository characterRecordRepository; - @Autowired private CharacterValueReportRepository characterValueReportRepository; @Autowired private SurveyBundleRepository surveyBundleRepository; @Autowired private SurveyRepository surveyRepository; @Autowired private SurveyOptionRepository surveyOptionRepository; @@ -63,7 +63,7 @@ class CharacterServiceTest extends IntegrationTest { @AfterEach void tearDown() { - characterValueReportRepository.deleteAllInBatch(); + valueReportRepository.deleteAllInBatch(); characterRecordRepository.deleteAllInBatch(); valueCharacterRepository.deleteAllInBatch(); surveySubmissionRepository.deleteAllInBatch(); @@ -108,8 +108,7 @@ void shouldReturnCharacter() { final Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); final SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); final ValueCharacter valueCharacter = - valueCharacterRepository.save( - new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", Keyword.SUCCESS)); + valueCharacterRepository.save(new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", SUCCESS)); final CharacterRecord characterRecord = characterRecordRepository.save( CharacterRecord.builder() @@ -126,7 +125,7 @@ void shouldReturnCharacter() { then(actual) .extracting("characterNo", "characterType") - .containsExactly("첫번째", valueCharacter.getKeyword().name()); + .containsExactly("첫번째", valueCharacter.getKeyword()); } } } @@ -157,8 +156,7 @@ void shouldReturnCharacter() { final Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); final SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); final ValueCharacter valueCharacter = - valueCharacterRepository.save( - new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", Keyword.SUCCESS)); + valueCharacterRepository.save(new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", SUCCESS)); characterRecordRepository.save( CharacterRecord.builder() .characterNo("첫번째") @@ -173,7 +171,7 @@ void shouldReturnCharacter() { then(actual) .extracting("characterNo", "characterType") - .containsExactly("첫번째", valueCharacter.getKeyword().name()); + .containsExactly("첫번째", valueCharacter.getKeyword()); } } } @@ -202,10 +200,9 @@ class whenMemberFound { @DisplayName("회원의 캐릭터 리스트를 반환한다.") void shouldReturnCharacters() { final Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); - final SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); + final SurveyBundle completeBundle = surveyBundleRepository.save(new SurveyBundle()); final ValueCharacter valueCharacter = - valueCharacterRepository.save( - new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", Keyword.SUCCESS)); + valueCharacterRepository.save(new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", SUCCESS)); characterRecordRepository.save( CharacterRecord.builder() .characterNo("첫번째") @@ -213,17 +210,56 @@ void shouldReturnCharacters() { .startDate(LocalDate.now().minusDays(15)) .endDate(LocalDate.now()) .member(member) - .surveyBundle(bundle) + .surveyBundle(completeBundle) + .build()); + + final SurveyBundle incompleteBundle = surveyBundleRepository.save(new SurveyBundle()); + characterRecordRepository.save( + CharacterRecord.builder() + .characterNo("두번째") + .valueCharacter(null) + .startDate(LocalDate.now().minusDays(15)) + .endDate(LocalDate.now()) + .member(member) + .surveyBundle(incompleteBundle) + .build()); + + final BalanceSurvey survey = + surveyRepository.save( + new BalanceSurvey( + "꿈에 그리던 드림 기업에 입사했다. 연봉도 좋지만, 무엇보다 회사의 근무 방식이 나와 잘 맞는 것 같다. 우리 회사의 근무 방식은...", + incompleteBundle)); + final SurveyOption option = + surveyOptionRepository.save( + SurveyOption.builder() + .survey(survey) + .content("자율 출퇴근제로 원하는 시간에 근무하며 창의적인 성과 내기") + .scores( + List.of( + KeywordScore.builder() + .keyword(SELF_DIRECTION) + .score(BigDecimal.ONE) + .build())) + .build()); + surveySubmissionRepository.save( + SurveySubmission.builder() + .survey(survey) + .member(member) + .selectedOption(option) .build()); final CharactersResponse actual = sut.getCharacters(member.getId()); assertAll( - () -> then(actual.characters()).hasSize(1), + () -> then(actual.characters()).hasSize(2), () -> then(actual.characters().get(0)) .extracting("characterNo", "bundleId") - .containsExactly("첫번째", bundle.getId())); + .containsExactly("첫번째", completeBundle.getId()), + () -> + then(actual.characters().get(1)) + .extracting("characterNo", "bundleId") + .containsExactly("두번째", incompleteBundle.getId())); // 추후 같이 수정 } } } @@ -248,62 +284,154 @@ void shouldThrowException() { @DisplayName("캐릭터 기록을 찾으면") class whenCharacterRecordFound { + @Transactional @Test @DisplayName("회원의 캐릭터 가치관 분석 정보를 반환한다.") void shouldReturnReport() { final Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); final SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); - final Survey survey = - surveyRepository.save( - new BalanceSurvey( - "꿈에 그리던 드림 기업에 입사했다. 연봉도 좋지만, 무엇보다 회사의 근무 방식이 나와 잘 맞는 것 같다. 우리 회사의 근무 방식은...", - bundle)); - final List scores = - List.of(KeywordScore.builder().keyword(SELF_DIRECTION).score(BigDecimal.ONE).build()); - final SurveyOption option = - surveyOptionRepository.save( - SurveyOption.builder() - .survey(survey) - .content("자율 출퇴근제로 원하는 시간에 근무하며 창의적인 성과 내기") - .scores(scores) - .build()); - final SurveySubmission submission = - surveySubmissionRepository.save( - SurveySubmission.builder() - .member(member) - .survey(survey) - .selectedOption(option) - .build()); - - final Map metricsMap = KeywordMetricsMap.generate(scores); - final Map weightMap = KeywordWeightMap.generate(metricsMap); - - final ValueCharacter valueCharacter = - valueCharacterRepository.save( - new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", Keyword.SUCCESS)); + final ValueReport valueReport = + valueReportRepository.save( + ValueReport.of(SELF_DIRECTION, ScaledBigDecimal.of(BigDecimal.valueOf(100)))); final CharacterRecord characterRecord = characterRecordRepository.save( CharacterRecord.builder() .characterNo("첫번째") - .valueCharacter(valueCharacter) + .valueReports(List.of(valueReport)) .startDate(LocalDate.now().minusDays(15)) .endDate(LocalDate.now()) .member(member) .surveyBundle(bundle) .build()); - characterValueReportRepository.save( - new CharacterValueReport( - characterRecord, - ValueReports.of(weightMap, metricsMap, List.of(submission)).getReports())); final CharacterValueReportResponse actual = sut.getCharacterReport( createCharacterReportCommand(member.getId(), characterRecord.getId())); - then(actual.valueReports()) - .usingRecursiveComparison() - .isEqualTo(ValueReports.of(weightMap, metricsMap, List.of(submission)).getReports()); + then(actual.valueReports()).usingRecursiveComparison().isEqualTo(List.of(valueReport)); } } } + + @Nested + @DisplayName("createFirstCharacter 메소드는") + class createFirstCharacter { + + @Transactional + @Test + @DisplayName("온보딩 후 회원의 첫번째 캐릭터를 생성한다.") + void shouldCreateCharacter() { + final Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); + final SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); + + final BalanceSurvey survey1 = + surveyRepository.save( + new BalanceSurvey( + "꿈에 그리던 드림 기업에 입사했다. 연봉도 좋지만, 무엇보다 회사의 근무 방식이 나와 잘 맞는 것 같다. 우리 회사의 근무 방식은...", + bundle)); + final BalanceSurvey survey2 = + surveyRepository.save(new BalanceSurvey("독립에 대한 고민이 깊어지는 요즘... 드디어 결정을 내렸다.", bundle)); + final BalanceSurvey survey3 = + surveyRepository.save( + new BalanceSurvey("바쁜 일상에 지쳐버린 나. 여가 시간을 더 의미 있게 보내고 싶어졌다.", bundle)); + final BalanceSurvey survey4 = + surveyRepository.save(new BalanceSurvey("회사에서 새로운 평가 시스템을 도입한다. 당신의 선택은?", bundle)); + final BalanceSurvey survey5 = + surveyRepository.save( + new BalanceSurvey("연애를 시작한지도 어연 3개월, 그 사람과 나의 연애는 꽤 잘 맞는다. 우리의 관계는...", bundle)); + + final SurveyOption option1 = + surveyOptionRepository.save( + SurveyOption.builder() + .survey(survey1) + .content("자율 출퇴근제로 원하는 시간에 근무하며 창의적인 성과 내기") + .scores( + List.of( + KeywordScore.builder() + .keyword(SELF_DIRECTION) + .score(BigDecimal.ONE) + .build())) + .build()); + final SurveyOption option2 = + surveyOptionRepository.save( + SurveyOption.builder() + .survey(survey2) + .content("내 취향대로 꾸민 집에서 자유롭게 생활하기") + .scores( + List.of( + KeywordScore.builder() + .keyword(SELF_DIRECTION) + .score(BigDecimal.ONE) + .build())) + .build()); + final SurveyOption option3 = + surveyOptionRepository.save( + SurveyOption.builder() + .survey(survey3) + .content("매년 새로운 취미에 도전하며 색다른 즐거움 찾기") + .scores( + List.of( + KeywordScore.builder().keyword(STABILITY).score(BigDecimal.ONE).build())) + .build()); + final SurveyOption option4 = + surveyOptionRepository.save( + SurveyOption.builder() + .survey(survey4) + .content("업무 성과에 따라 차등 보너스를 지급한다") + .scores( + List.of( + KeywordScore.builder().keyword(SUCCESS).score(BigDecimal.ONE).build())) + .build()); + final SurveyOption option5 = + surveyOptionRepository.save( + SurveyOption.builder() + .survey(survey5) + .content("서로의 일상 속에서 따뜻하게 지지하는 관계") + .scores( + List.of( + KeywordScore.builder() + .keyword(BENEVOLENCE) + .score(BigDecimal.ONE) + .build())) + .build()); + + final List submissions = + surveySubmissionRepository.saveAll( + List.of( + SurveySubmission.builder() + .survey(survey1) + .selectedOption(option1) + .submittedAt(LocalDateTime.of(2025, 2, 11, 0, 0)) + .build(), + SurveySubmission.builder() + .survey(survey2) + .selectedOption(option2) + .submittedAt(LocalDateTime.of(2025, 2, 12, 0, 0)) + .build(), + SurveySubmission.builder() + .survey(survey3) + .selectedOption(option3) + .submittedAt(LocalDateTime.of(2025, 2, 13, 0, 0)) + .build(), + SurveySubmission.builder() + .survey(survey4) + .selectedOption(option4) + .submittedAt(LocalDateTime.of(2025, 2, 14, 0, 0)) + .build(), + SurveySubmission.builder() + .survey(survey5) + .selectedOption(option5) + .submittedAt(LocalDateTime.of(2025, 2, 15, 0, 0)) + .build())); + + final KeywordScores scores = + KeywordScores.of(List.of(survey1, survey2, survey3, survey4, survey5)); + + sut.createFirstCharacter(member, bundle, scores.getValues(), submissions); + + assertAll( + () -> then(characterRecordRepository.findAll()).hasSize(1), + () -> then(valueReportRepository.findAll()).hasSize(4)); + } + } } diff --git a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/survey/model/KeywordMetricsMapTest.java b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/survey/model/KeywordMetricsMapTest.java deleted file mode 100644 index 684156ac..00000000 --- a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/survey/model/KeywordMetricsMapTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.nexters.jaknaesocore.domain.survey.model; - -import static org.assertj.core.api.BDDAssertions.then; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.nexters.jaknaesocore.domain.survey.model.Keyword.ADVENTURE; -import static org.nexters.jaknaesocore.domain.survey.model.Keyword.SECURITY; - -import java.math.BigDecimal; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.Test; - -class KeywordMetricsMapTest { - - @Test - void 키워드_점수_리스트를_바탕으로_키워드_지표_맵을_생성한다() { - final List scores = - List.of( - KeywordScore.builder().keyword(ADVENTURE).score(BigDecimal.valueOf(1)).build(), - KeywordScore.builder().keyword(ADVENTURE).score(BigDecimal.valueOf(-1)).build(), - KeywordScore.builder().keyword(SECURITY).score(BigDecimal.valueOf(1)).build()); - - final Map actual = KeywordMetricsMap.generate(scores); - - assertAll( - () -> - then(actual.get(ADVENTURE)) - .extracting("cnt", "positive", "negative") - .containsExactly(2, BigDecimal.valueOf(1), BigDecimal.valueOf(-1)), - () -> - then(actual.get(SECURITY)) - .extracting("cnt", "positive", "negative") - .containsExactly(1, BigDecimal.valueOf(1), BigDecimal.valueOf(0))); - } -} diff --git a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/survey/model/KeywordWeightMapTest.java b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/survey/model/KeywordWeightMapTest.java deleted file mode 100644 index ea508223..00000000 --- a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/survey/model/KeywordWeightMapTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.nexters.jaknaesocore.domain.survey.model; - -import static org.assertj.core.api.BDDAssertions.then; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.nexters.jaknaesocore.domain.survey.model.Keyword.ADVENTURE; -import static org.nexters.jaknaesocore.domain.survey.model.Keyword.SECURITY; -import static org.nexters.jaknaesocore.domain.survey.model.Keyword.SUCCESS; -import static org.nexters.jaknaesocore.domain.survey.model.Keyword.UNIVERSALISM; - -import java.math.BigDecimal; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.Test; - -class KeywordWeightMapTest { - - @Test - void 키워드_지표를_바탕으로_키워드_응답별_가중치를_계산한다() { - final List scores = - List.of( - KeywordScore.builder().keyword(ADVENTURE).score(BigDecimal.valueOf(1)).build(), - KeywordScore.builder().keyword(ADVENTURE).score(BigDecimal.valueOf(-1)).build(), - KeywordScore.builder().keyword(SECURITY).score(BigDecimal.valueOf(1)).build(), - KeywordScore.builder().keyword(SUCCESS).score(BigDecimal.valueOf(1)).build(), - KeywordScore.builder().keyword(UNIVERSALISM).score(BigDecimal.valueOf(-1)).build()); - final Map metricsMap = KeywordMetricsMap.generate(scores); - - final Map actual = KeywordWeightMap.generate(metricsMap); - - assertAll( - () -> then(actual).hasSize(4), - () -> then(actual.get(ADVENTURE)).isEqualByComparingTo(BigDecimal.valueOf(12.50)), - () -> then(actual.get(SECURITY)).isEqualByComparingTo(BigDecimal.valueOf(25.00)), - () -> then(actual.get(SUCCESS)).isEqualByComparingTo(BigDecimal.valueOf(25.00)), - () -> then(actual.get(UNIVERSALISM)).isEqualByComparingTo(BigDecimal.valueOf(25.00))); - } -} diff --git a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/survey/repository/SurveySubmissionRepositoryTest.java b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/survey/repository/SurveySubmissionRepositoryTest.java index 20245916..2359d2b5 100644 --- a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/survey/repository/SurveySubmissionRepositoryTest.java +++ b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/survey/repository/SurveySubmissionRepositoryTest.java @@ -195,8 +195,9 @@ void findByMember_IdAndDeletedAtIsNull() { SurveySubmission.builder().member(member).survey(survey).selectedOption(option).build()); List actual = - surveySubmissionRepository.findWithSurveyByMemberIdAndBundleIdAndDeletedAtIsNull( - member.getId(), bundle.getId()); + surveySubmissionRepository + .findByMemberIdAndBundleIdAndDeletedAtIsNullWithSurveyAndSurveyBundle( + member.getId(), bundle.getId()); assertAll( () -> then(actual).hasSize(1), () -> then(actual.get(0).getSurvey()).isEqualTo(survey)); @@ -204,7 +205,7 @@ void findByMember_IdAndDeletedAtIsNull() { @Transactional @Test - void 회원이_참여한_설문_응답_리스트를_설문_번들과_함께_조회한다() { + void 회원이_참여한_설문_응답_리스트를_번들과_설문_옵션과_함께_조회한다() { Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); BalanceSurvey survey = @@ -228,8 +229,9 @@ void findByMember_IdAndDeletedAtIsNull() { SurveySubmission.builder().member(member).survey(survey).selectedOption(option).build()); List actual = - surveySubmissionRepository.findWithSurveyBundlesByMemberIdAndDeletedAtIsNull( - member.getId()); + surveySubmissionRepository + .findByMemberIdAndDeletedAtIsNullWithSurveyAndSurveyBundleAndSurveyOption( + member.getId()); assertAll( () -> then(actual).hasSize(1), diff --git a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/survey/service/SurveyServiceTest.java b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/survey/service/SurveyServiceTest.java index 2540accb..b4eaab10 100644 --- a/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/survey/service/SurveyServiceTest.java +++ b/jaknaeso-core/src/test/java/org/nexters/jaknaesocore/domain/survey/service/SurveyServiceTest.java @@ -1,27 +1,50 @@ package org.nexters.jaknaesocore.domain.survey.service; -import static org.assertj.core.api.BDDAssertions.*; +import static org.assertj.core.api.BDDAssertions.then; +import static org.assertj.core.api.BDDAssertions.thenThrownBy; +import static org.assertj.core.api.BDDAssertions.tuple; +import static org.nexters.jaknaesocore.domain.survey.model.Keyword.SELF_DIRECTION; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import org.nexters.jaknaesocore.common.support.IntegrationTest; import org.nexters.jaknaesocore.common.support.error.CustomException; +import org.nexters.jaknaesocore.domain.character.model.CharacterRecord; +import org.nexters.jaknaesocore.domain.character.model.ValueCharacter; +import org.nexters.jaknaesocore.domain.character.repository.CharacterRecordRepository; +import org.nexters.jaknaesocore.domain.character.repository.ValueCharacterRepository; +import org.nexters.jaknaesocore.domain.character.repository.ValueReportRepository; +import org.nexters.jaknaesocore.domain.character.service.CharacterService; import org.nexters.jaknaesocore.domain.member.model.Member; import org.nexters.jaknaesocore.domain.member.repository.MemberRepository; -import org.nexters.jaknaesocore.domain.survey.dto.*; -import org.nexters.jaknaesocore.domain.survey.model.*; +import org.nexters.jaknaesocore.domain.survey.dto.OnboardingSubmissionResult; +import org.nexters.jaknaesocore.domain.survey.dto.OnboardingSubmissionsCommand; +import org.nexters.jaknaesocore.domain.survey.dto.OnboardingSurveyResponse; +import org.nexters.jaknaesocore.domain.survey.dto.SurveyHistoryResponse; +import org.nexters.jaknaesocore.domain.survey.dto.SurveyResponse; +import org.nexters.jaknaesocore.domain.survey.dto.SurveySubmissionCommand; +import org.nexters.jaknaesocore.domain.survey.dto.SurveySubmissionHistoryCommand; +import org.nexters.jaknaesocore.domain.survey.dto.SurveySubmissionHistoryResponse; import org.nexters.jaknaesocore.domain.survey.model.BalanceSurvey; import org.nexters.jaknaesocore.domain.survey.model.Keyword; import org.nexters.jaknaesocore.domain.survey.model.KeywordScore; +import org.nexters.jaknaesocore.domain.survey.model.MultipleChoiceSurvey; +import org.nexters.jaknaesocore.domain.survey.model.OnboardingSurvey; import org.nexters.jaknaesocore.domain.survey.model.SurveyBundle; import org.nexters.jaknaesocore.domain.survey.model.SurveyOption; +import org.nexters.jaknaesocore.domain.survey.model.SurveySubmission; import org.nexters.jaknaesocore.domain.survey.repository.SurveyBundleRepository; import org.nexters.jaknaesocore.domain.survey.repository.SurveyOptionRepository; import org.nexters.jaknaesocore.domain.survey.repository.SurveyRepository; import org.nexters.jaknaesocore.domain.survey.repository.SurveySubmissionRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; import org.springframework.transaction.annotation.Transactional; class SurveyServiceTest extends IntegrationTest { @@ -33,14 +56,22 @@ class SurveyServiceTest extends IntegrationTest { @Autowired private SurveyBundleRepository surveyBundleRepository; @Autowired private SurveyRepository surveyRepository; @Autowired private SurveyOptionRepository surveyOptionRepository; + @Autowired private CharacterRecordRepository characterRecordRepository; + @Autowired private ValueReportRepository valueReportRepository; + @Autowired private ValueCharacterRepository valueCharacterRepository; + + @MockitoSpyBean private CharacterService characterService; @AfterEach void tearDown() { + valueReportRepository.deleteAllInBatch(); + characterRecordRepository.deleteAllInBatch(); surveySubmissionRepository.deleteAllInBatch(); surveyOptionRepository.deleteAllInBatch(); surveyRepository.deleteAllInBatch(); surveyBundleRepository.deleteAllInBatch(); memberRepository.deleteAllInBatch(); + valueCharacterRepository.deleteAllInBatch(); } @DisplayName("설문을 조회한다.") @@ -77,109 +108,6 @@ void getNextSurvey() { .containsExactly(tuple(option.getId(), "질문 옵션 내용")); } - @DisplayName("submitSurvey 메서드는") - @Nested - class submitSurvey { - - @DisplayName("회원이 존재하지 않으면") - @Nested - class whenMemberNotFound { - @Test - @DisplayName("예외를 발생시킨다") - void throwMemberNotFoundException() { - // given - Member member = Member.create("나민혁", "test@test.com"); - memberRepository.save(member); - SurveyBundle surveyBundle = new SurveyBundle(); - surveyBundleRepository.save(surveyBundle); - BalanceSurvey balanceSurvey = new BalanceSurvey("질문내용", surveyBundle); - surveyRepository.save(balanceSurvey); - List scores = - List.of( - KeywordScore.builder().keyword(Keyword.ADVENTURE).score(BigDecimal.ONE).build(), - KeywordScore.builder().keyword(Keyword.BENEVOLENCE).score(BigDecimal.TWO).build()); - SurveyOption option = - SurveyOption.builder().survey(balanceSurvey).scores(scores).content("질문 옵션 내용").build(); - surveyOptionRepository.save(option); - - SurveySubmissionCommand command = - new SurveySubmissionCommand(option.getId(), balanceSurvey.getId(), 0L, "나는 행복한게 좋으니까"); - - // when - // then - thenThrownBy(() -> surveyService.submitSurvey(command, LocalDateTime.now())) - .isEqualTo(CustomException.MEMBER_NOT_FOUND); - } - } - - @DisplayName("설문을 저장한다") - @Nested - class shouldSubmitted { - @Test - void 설문에_응답을_제출한다() { - // given - Member member = Member.create("나민혁", "test@test.com"); - memberRepository.save(member); - SurveyBundle surveyBundle = new SurveyBundle(); - surveyBundleRepository.save(surveyBundle); - BalanceSurvey balanceSurvey = new BalanceSurvey("질문내용", surveyBundle); - surveyRepository.save(balanceSurvey); - List scores = - List.of( - KeywordScore.builder().keyword(Keyword.ADVENTURE).score(BigDecimal.ONE).build(), - KeywordScore.builder().keyword(Keyword.BENEVOLENCE).score(BigDecimal.TWO).build()); - SurveyOption option = - SurveyOption.builder().survey(balanceSurvey).scores(scores).content("질문 옵션 내용").build(); - surveyOptionRepository.save(option); - - SurveySubmissionCommand command = - new SurveySubmissionCommand( - option.getId(), balanceSurvey.getId(), member.getId(), "나는 행복한게 좋으니까"); - - // when - LocalDateTime submittedAt = LocalDateTime.of(2025, 2, 13, 18, 0, 0); - surveyService.submitSurvey(command, submittedAt); - // then - List submissions = surveySubmissionRepository.findAll(); - then(submissions).hasSize(1); - then(submissions.getFirst()) - .extracting("member.id", "survey.id", "selectedOption.id", "retrospective") - .containsExactly(member.getId(), balanceSurvey.getId(), option.getId(), "나는 행복한게 좋으니까"); - } - } - - @DisplayName("설문이 존재하지 않으면") - @Nested - class whenSurveyNotFound { - @Test - @DisplayName("예외를 발생시킨다") - void throwSurveyNotFoundException() { - // given - Member member = Member.create("나민혁", "test@test.com"); - memberRepository.save(member); - SurveyBundle surveyBundle = new SurveyBundle(); - surveyBundleRepository.save(surveyBundle); - BalanceSurvey balanceSurvey = new BalanceSurvey("질문내용", surveyBundle); - surveyRepository.save(balanceSurvey); - List scores = - List.of( - KeywordScore.builder().keyword(Keyword.ADVENTURE).score(BigDecimal.ONE).build(), - KeywordScore.builder().keyword(Keyword.BENEVOLENCE).score(BigDecimal.TWO).build()); - SurveyOption option = - SurveyOption.builder().survey(balanceSurvey).scores(scores).content("질문 옵션 내용").build(); - surveyOptionRepository.save(option); - - SurveySubmissionCommand command = - new SurveySubmissionCommand(0L, member.getId(), option.getId(), "나는 행복한게 좋으니까"); - - // when - // then - thenThrownBy(() -> surveyService.submitSurvey(command, LocalDateTime.now())) - .isEqualTo(CustomException.SURVEY_NOT_FOUND); - } - } - } - @Disabled("클라이언트의 온보딩 작업이 완료되면 테스트를 진행합니다.") @Test void 설문_기록이_없으면_접근_할_수_없다() { @@ -643,6 +571,14 @@ void throwSurveyNotFoundException() { memberRepository.save(member); SurveyBundle surveyBundle = new SurveyBundle(); + final ValueCharacter valueCharacter = + valueCharacterRepository.save( + ValueCharacter.builder() + .name("아낌없이 주는 보금자리 유형") + .description("보금자리 유형 설명") + .keyword(Keyword.BENEVOLENCE) + .build()); + surveyBundleRepository.save(surveyBundle); OnboardingSurvey survey1 = @@ -660,19 +596,25 @@ void throwSurveyNotFoundException() { surveyRepository.saveAll(List.of(survey1, survey2, survey3, survey4)); - List scores = + List scores1 = List.of( KeywordScore.builder().keyword(Keyword.ADVENTURE).score(BigDecimal.ONE).build(), KeywordScore.builder().keyword(Keyword.BENEVOLENCE).score(BigDecimal.TWO).build()); + List scores2 = + List.of( + KeywordScore.builder() + .keyword(Keyword.ADVENTURE) + .score(BigDecimal.valueOf(-1)) + .build()); SurveyOption option1 = - SurveyOption.builder().survey(survey1).scores(scores).content("전혀 나와 같지않다.").build(); + SurveyOption.builder().survey(survey1).scores(scores1).content("전혀 나와 같지않다.").build(); SurveyOption option2 = - SurveyOption.builder().survey(survey2).scores(scores).content("나와 같지 않다.").build(); + SurveyOption.builder().survey(survey2).scores(scores1).content("나와 같지 않다.").build(); SurveyOption option3 = - SurveyOption.builder().survey(survey3).scores(scores).content("나와 조금 같다.").build(); + SurveyOption.builder().survey(survey3).scores(scores2).content("나와 조금 같다.").build(); SurveyOption option4 = - SurveyOption.builder().survey(survey4).scores(scores).content("나와 같다.").build(); + SurveyOption.builder().survey(survey4).scores(scores2).content("나와 같다.").build(); surveyOptionRepository.saveAll(List.of(option1, option2, option3, option4)); LocalDateTime submittedAt = LocalDateTime.of(2025, 2, 13, 18, 25, 0); @@ -699,6 +641,330 @@ void throwSurveyNotFoundException() { tuple(member.getId(), survey3.getId(), option3.getId(), submittedAt), tuple(member.getId(), survey4.getId(), option4.getId(), submittedAt)); then(member).extracting("completedOnboardingAt").isEqualTo(submittedAt); + then(characterRecordRepository.findAll()) + .hasSize(1) + .extracting( + "ordinalNumber", + "characterNo", + "member.id", + "surveyBundle.id", + "valueCharacter.id", + "startDate", + "endDate") + .containsExactly( + tuple( + 1, + "첫번째 캐릭터", + member.getId(), + surveyBundle.getId(), + valueCharacter.getId(), + submittedAt.toLocalDate(), + submittedAt.toLocalDate())); + } + + @DisplayName("submitSurvey 메서드는") + @Nested + class submitSurvey { + + @DisplayName("회원이 존재하지 않으면") + @Nested + class whenMemberNotFound { + + @Test + @DisplayName("예외를 발생시킨다") + void throwMemberNotFoundException() { + // given + Member member = Member.create("나민혁", "test@test.com"); + memberRepository.save(member); + SurveyBundle surveyBundle = new SurveyBundle(); + surveyBundleRepository.save(surveyBundle); + BalanceSurvey balanceSurvey = new BalanceSurvey("질문내용", surveyBundle); + surveyRepository.save(balanceSurvey); + List scores = + List.of( + KeywordScore.builder().keyword(Keyword.ADVENTURE).score(BigDecimal.ONE).build(), + KeywordScore.builder().keyword(Keyword.BENEVOLENCE).score(BigDecimal.TWO).build()); + SurveyOption option = + SurveyOption.builder().survey(balanceSurvey).scores(scores).content("질문 옵션 내용").build(); + surveyOptionRepository.save(option); + + SurveySubmissionCommand command = + new SurveySubmissionCommand(option.getId(), balanceSurvey.getId(), 0L, "나는 행복한게 좋으니까"); + + // when + // then + thenThrownBy(() -> surveyService.submitSurvey(command, LocalDateTime.now())) + .isEqualTo(CustomException.MEMBER_NOT_FOUND); + } + } + + @DisplayName("첫 설문을 시도하면, ") + @Nested + class whenFirstSubmitAndNotCompleted { + + @Test + void 설문에_응답을_제출하고_빈_캐릭터를_생성한다() { + // given + Member member = Member.create("나민혁", "test@test.com"); + memberRepository.save(member); + + final ValueCharacter valueCharacter = + valueCharacterRepository.save( + ValueCharacter.builder() + .name("아낌없이 주는 보금자리 유형") + .description("보금자리 유형 설명") + .keyword(Keyword.BENEVOLENCE) + .build()); + + final SurveyBundle onboardingBundle = surveyBundleRepository.save(new SurveyBundle()); + OnboardingSurvey onboardingSurvey = + surveyRepository.save(new OnboardingSurvey("온보딩질문내용1", onboardingBundle)); + characterRecordRepository.save( + CharacterRecord.builder() + .valueCharacter(valueCharacter) + .ordinalNumber(1) + .characterNo("첫번째 캐릭터") + .member(member) + .surveyBundle(onboardingBundle) + .build()); + + final SurveyBundle surveyBundle = new SurveyBundle(); + surveyBundleRepository.save(surveyBundle); + BalanceSurvey balanceSurvey1 = + surveyRepository.save(new BalanceSurvey("질문내용1", surveyBundle)); + BalanceSurvey balanceSurvey2 = + surveyRepository.save(new BalanceSurvey("질문내용2", surveyBundle)); + List scores = + List.of( + KeywordScore.builder().keyword(Keyword.ADVENTURE).score(BigDecimal.ONE).build(), + KeywordScore.builder().keyword(Keyword.BENEVOLENCE).score(BigDecimal.TWO).build()); + SurveyOption option1 = + surveyOptionRepository.save( + SurveyOption.builder() + .survey(balanceSurvey1) + .scores(scores) + .content("질문 옵션 내용1") + .build()); + SurveyOption option2 = + surveyOptionRepository.save( + SurveyOption.builder() + .survey(balanceSurvey2) + .scores(scores) + .content("질문 옵션 내용2") + .build()); + + SurveySubmissionCommand command = + new SurveySubmissionCommand( + option1.getId(), balanceSurvey1.getId(), member.getId(), "나는 행복한게 좋으니까"); + + // when + LocalDateTime submittedAt = LocalDateTime.of(2025, 2, 13, 18, 0, 0); + surveyService.submitSurvey(command, submittedAt); + // then + List submissions = surveySubmissionRepository.findAll(); + then(submissions).hasSize(1); + then(submissions.getFirst()) + .extracting("member.id", "survey.id", "selectedOption.id", "retrospective") + .containsExactly( + member.getId(), balanceSurvey1.getId(), option1.getId(), "나는 행복한게 좋으니까"); + then(characterRecordRepository.findAll()) + .hasSize(2) + .extracting( + "ordinalNumber", "characterNo", "member.id", "surveyBundle.id", "valueCharacter") + .containsExactly( + tuple(1, "첫번째 캐릭터", member.getId(), onboardingBundle.getId(), valueCharacter), + tuple(2, "두번째 캐릭터", member.getId(), surveyBundle.getId(), null)); + } + } + + @DisplayName("설문이 존재하지 않으면") + @Nested + class whenSurveyNotFound { + + @Test + @DisplayName("예외를 발생시킨다") + void throwSurveyNotFoundException() { + // given + Member member = Member.create("나민혁", "test@test.com"); + memberRepository.save(member); + SurveyBundle surveyBundle = new SurveyBundle(); + surveyBundleRepository.save(surveyBundle); + BalanceSurvey balanceSurvey = new BalanceSurvey("질문내용", surveyBundle); + surveyRepository.save(balanceSurvey); + List scores = + List.of( + KeywordScore.builder().keyword(Keyword.ADVENTURE).score(BigDecimal.ONE).build(), + KeywordScore.builder().keyword(Keyword.BENEVOLENCE).score(BigDecimal.TWO).build()); + SurveyOption option = + SurveyOption.builder().survey(balanceSurvey).scores(scores).content("질문 옵션 내용").build(); + surveyOptionRepository.save(option); + + SurveySubmissionCommand command = + new SurveySubmissionCommand(0L, member.getId(), option.getId(), "나는 행복한게 좋으니까"); + + // when + // then + thenThrownBy(() -> surveyService.submitSurvey(command, LocalDateTime.now())) + .isEqualTo(CustomException.SURVEY_NOT_FOUND); + } + } + + @DisplayName("회원과 설문을 찾았고") + @Nested + class whenMemberAndSurveyFound { + + @DisplayName("설문 번들의 모든 설문 응답을 제출하였다면") + @Nested + class whenSurveyBundleIsCompleted { + + @DisplayName("캐릭터를 생성한다.") + @Test + void shouldCreateCharacter() { + final Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); + final SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); + + final BalanceSurvey survey = + surveyRepository.save( + new BalanceSurvey( + "꿈에 그리던 드림 기업에 입사했다. 연봉도 좋지만, 무엇보다 회사의 근무 방식이 나와 잘 맞는 것 같다. 우리 회사의 근무 방식은...", + bundle)); + final SurveyOption option = + surveyOptionRepository.save( + SurveyOption.builder() + .survey(survey) + .content("자율 출퇴근제로 원하는 시간에 근무하며 창의적인 성과 내기") + .scores( + List.of( + KeywordScore.builder() + .keyword(SELF_DIRECTION) + .score(BigDecimal.ONE) + .build())) + .build()); + + // 가치관 캐릭터 + final ValueCharacter valueCharacter = + valueCharacterRepository.save( + ValueCharacter.builder() + .name("마이웨이 유형") + .description("마이웨이 유형 설명") + .keyword(SELF_DIRECTION) + .build()); + // 온보딩 캐릭터 + final SurveyBundle onboardingBundle = surveyBundleRepository.save(new SurveyBundle()); + characterRecordRepository.save( + CharacterRecord.builder() + .ordinalNumber(1) + .characterNo("첫번째 캐릭터") + .valueCharacter(valueCharacter) + .member(member) + .surveyBundle(onboardingBundle) + .build()); + + final SurveySubmissionCommand command = + new SurveySubmissionCommand( + option.getId(), + survey.getId(), + member.getId(), + "자유롭게 일하면 집중이 잘되는 시간에 일할 수 있기 때문에 일의 능률이 오를 것 같다."); + final LocalDateTime submittedAt = LocalDateTime.of(2025, 2, 17, 0, 0, 0); + + surveyService.submitSurvey(command, submittedAt); + then(characterRecordRepository.findAll()) + .hasSize(2) + .extracting( + "ordinalNumber", + "characterNo", + "member.id", + "surveyBundle.id", + "valueCharacter.id") + .containsExactly( + tuple( + 1, + "첫번째 캐릭터", + member.getId(), + onboardingBundle.getId(), + valueCharacter.getId()), + tuple(2, "두번째 캐릭터", member.getId(), bundle.getId(), valueCharacter.getId())); + } + } + + @DisplayName("설문 번들의 설문 응답이 남아있다면") + @Nested + class whenSurveyBundleIsInProgress { + + @DisplayName("캐릭터를 생성하지 않는다.") + @Test + void shouldNotCreateCharacter() { + final Member member = memberRepository.save(Member.create("홍길동", "test@example.com")); + final SurveyBundle bundle = surveyBundleRepository.save(new SurveyBundle()); + + final BalanceSurvey survey1 = + surveyRepository.save( + new BalanceSurvey( + "꿈에 그리던 드림 기업에 입사했다. 연봉도 좋지만, 무엇보다 회사의 근무 방식이 나와 잘 맞는 것 같다. 우리 회사의 근무 방식은...", + bundle)); + final BalanceSurvey survey2 = + surveyRepository.save( + new BalanceSurvey("독립에 대한 고민이 깊어지는 요즘... 드디어 결정을 내렸다.", bundle)); + final BalanceSurvey survey3 = surveyRepository.save(new BalanceSurvey("세번째 질문", bundle)); + final SurveyOption option1 = + surveyOptionRepository.save( + SurveyOption.builder() + .survey(survey1) + .content("자율 출퇴근제로 원하는 시간에 근무하며 창의적인 성과 내기") + .scores( + List.of( + KeywordScore.builder() + .keyword(SELF_DIRECTION) + .score(BigDecimal.ONE) + .build())) + .build()); + final SurveyOption option2 = + surveyOptionRepository.save( + SurveyOption.builder() + .survey(survey2) + .content("내 취향대로 꾸민 집에서 자유롭게 생활하기") + .scores( + List.of( + KeywordScore.builder() + .keyword(SELF_DIRECTION) + .score(BigDecimal.ONE) + .build())) + .build()); + final SurveyOption option3 = + surveyOptionRepository.save( + SurveyOption.builder() + .survey(survey3) + .content("세번째 답변") + .scores( + List.of( + KeywordScore.builder() + .keyword(SELF_DIRECTION) + .score(BigDecimal.ONE) + .build())) + .build()); + + surveySubmissionRepository.save( + SurveySubmission.builder() + .survey(survey1) + .member(member) + .selectedOption(option1) + .submittedAt(LocalDateTime.of(2025, 2, 17, 0, 0, 0)) + .build()); + + final SurveySubmissionCommand command = + new SurveySubmissionCommand( + option2.getId(), + survey2.getId(), + member.getId(), + "자유롭게 일하면 집중이 잘되는 시간에 일할 수 있기 때문에 일의 능률이 오를 것 같다."); + final LocalDateTime submittedAt = LocalDateTime.of(2025, 2, 17, 0, 0, 0); + + surveyService.submitSurvey(command, submittedAt); + then(characterRecordRepository.findAll()).hasSize(0); + } + } + } } @Test diff --git a/jaknaeso-server/src/test/java/org/nexters/jaknaesoserver/domain/character/controller/CharacterControllerTest.java b/jaknaeso-server/src/test/java/org/nexters/jaknaesoserver/domain/character/controller/CharacterControllerTest.java index 51aae460..d3f12c64 100644 --- a/jaknaeso-server/src/test/java/org/nexters/jaknaesoserver/domain/character/controller/CharacterControllerTest.java +++ b/jaknaeso-server/src/test/java/org/nexters/jaknaesoserver/domain/character/controller/CharacterControllerTest.java @@ -23,10 +23,11 @@ import org.nexters.jaknaesocore.domain.character.model.ValueReport; import org.nexters.jaknaesocore.domain.character.service.dto.CharacterCommand; import org.nexters.jaknaesocore.domain.character.service.dto.CharacterResponse; +import org.nexters.jaknaesocore.domain.character.service.dto.CharacterResponse.CharacterTraitResponse; import org.nexters.jaknaesocore.domain.character.service.dto.CharacterValueReportCommand; import org.nexters.jaknaesocore.domain.character.service.dto.CharacterValueReportResponse; import org.nexters.jaknaesocore.domain.character.service.dto.CharactersResponse; -import org.nexters.jaknaesocore.domain.character.service.dto.CharactersResponse.SimpleCharacterResponse; +import org.nexters.jaknaesocore.domain.character.service.dto.SimpleCharacterResponse; import org.nexters.jaknaesocore.domain.survey.model.Keyword; import org.nexters.jaknaesoserver.common.support.ControllerTest; import org.nexters.jaknaesoserver.common.support.WithMockCustomUser; @@ -70,15 +71,21 @@ class CharacterControllerTest extends ControllerTest { fieldWithPath("result") .type(SimpleType.STRING) .description("API 요청 결과 (성공/실패)"), + fieldWithPath("data.characters[].ordinalNumber") + .type(SimpleType.NUMBER) + .description("캐릭터 순서 번호"), fieldWithPath("data.characters[].characterNo") .type(SimpleType.STRING) .description("캐릭터 회차"), fieldWithPath("data.characters[].characterId") .type(SimpleType.NUMBER) - .description("캐릭터 아이디"), + .description("캐릭터 아이디 (캐릭터가 완성되지 않은 경우 null)"), fieldWithPath("data.characters[].bundleId") .type(SimpleType.NUMBER) .description("설문 번들 아이디"), + fieldWithPath("data.characters[].isCompleted") + .type(SimpleType.BOOLEAN) + .description("설문 번들 완료 여부"), fieldWithPath("error").description("에러").optional()) .responseSchema(schema("CharactersResponse")) .build()))); @@ -87,19 +94,8 @@ class CharacterControllerTest extends ControllerTest { @WithMockCustomUser @Test void 특정_캐릭터_정보를_조회한다() throws Exception { - - ValueCharacter valueCharacter = new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", Keyword.SUCCESS); given(characterService.getCharacter(new CharacterCommand(1L, 1L))) - .willReturn( - CharacterResponse.builder() - .characterId(1L) - .characterNo("첫번째") - .valueCharacter(valueCharacter) - .name(valueCharacter.getName()) - .description(valueCharacter.getDescription()) - .startDate(LocalDate.now().minusDays(15)) - .endDate(LocalDate.now()) - .build()); + .willReturn(createCharacterResponse()); mockMvc .perform( @@ -141,6 +137,15 @@ class CharacterControllerTest extends ControllerTest { fieldWithPath("data.description") .type(SimpleType.STRING) .description("캐릭터 설명"), + fieldWithPath("data.mainTraits[].description") + .type(SimpleType.STRING) + .description("캐릭터 주요 특성"), + fieldWithPath("data.strengths[].description") + .type(SimpleType.STRING) + .description("캐릭터 강점"), + fieldWithPath("data.weaknesses[].description") + .type(SimpleType.STRING) + .description("캐릭터 약점"), fieldWithPath("data.startDate") .type(SimpleType.STRING) .description("시작 일자"), @@ -157,17 +162,7 @@ class CharacterControllerTest extends ControllerTest { void 현재_캐릭터_정보를_조회한다() throws Exception { ValueCharacter valueCharacter = new ValueCharacter("성취를 쫓는 노력가", "성공 캐릭터 설명", Keyword.SUCCESS); - given(characterService.getCurrentCharacter(1L)) - .willReturn( - CharacterResponse.builder() - .characterId(1L) - .characterNo("첫번째") - .valueCharacter(valueCharacter) - .name(valueCharacter.getName()) - .description(valueCharacter.getDescription()) - .startDate(LocalDate.now().minusDays(15)) - .endDate(LocalDate.now()) - .build()); + given(characterService.getCurrentCharacter(1L)).willReturn(createCharacterResponse()); mockMvc .perform( @@ -205,6 +200,15 @@ class CharacterControllerTest extends ControllerTest { fieldWithPath("data.description") .type(SimpleType.STRING) .description("캐릭터 설명"), + fieldWithPath("data.mainTraits[].description") + .type(SimpleType.STRING) + .description("캐릭터 주요 특성"), + fieldWithPath("data.strengths[].description") + .type(SimpleType.STRING) + .description("캐릭터 강점"), + fieldWithPath("data.weaknesses[].description") + .type(SimpleType.STRING) + .description("캐릭터 약점"), fieldWithPath("data.startDate") .type(SimpleType.STRING) .description("시작 일자"), @@ -260,4 +264,32 @@ class CharacterControllerTest extends ControllerTest { .responseSchema(schema("CharacterValueReportResponse")) .build()))); } + + private CharacterResponse createCharacterResponse() { + return CharacterResponse.builder() + .characterId(1L) + .characterNo("첫번째") + .characterType(Keyword.SUCCESS) + .name("성취를 쫓는 노력가") + .description( + "목표를 이루고 영향력을 가지는 것을 중요하게 여기는\n노력가 유형은 끊임없는 노력과 성과를 내는 것을 중요시 여겨요.\n사회에서 의미 있는 위치를 차지하고 싶어하는 야망가!") + .mainTraits( + List.of( + CharacterTraitResponse.builder() + .description("목표를 이루기 위해 끊임없이 노력하며, 성장하는 과정에서 보람을 느껴요.") + .build())) + .strengths( + List.of( + CharacterTraitResponse.builder() + .description("강한 추진력과 목표 지향적인 태도로 원하는 결과를 만들어내요.") + .build())) + .weaknesses( + List.of( + CharacterTraitResponse.builder() + .description("성과 중심적인 태도가 타인과의 관계에 영향을 줄 수 있어요.") + .build())) + .startDate(LocalDate.now().minusDays(15).toString()) + .endDate(LocalDate.now().toString()) + .build(); + } }