Skip to content

Commit 1981bee

Browse files
authored
Merge pull request #218 from ASSU-org/develop
[DEPLOY] v.35 내가 받을 수 있는 제휴 조회
2 parents 4198662 + e704f57 commit 1981bee

File tree

9 files changed

+234
-7
lines changed

9 files changed

+234
-7
lines changed

src/main/java/com/assu/server/domain/partnership/repository/PaperContentRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,7 @@ Optional<PaperContent> findLatestValidByStoreIdNative(
6767
@Param("price") String price, // CriterionType.PRICE.name()
6868
@Param("headcount") String headcount // CriterionType.HEADCOUNT.name()
6969
);
70+
71+
Optional<PaperContent> findTopByPaperIdOrderByIdDesc(Long paperId);
72+
7073
}

src/main/java/com/assu/server/domain/partnership/repository/PaperRepository.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.springframework.data.jpa.repository.Query;
1212
import org.springframework.data.repository.query.Param;
1313

14+
import java.time.LocalDate;
1415
import java.util.List;
1516
import java.util.Optional;
1617

@@ -49,4 +50,16 @@ List<Paper> findAllSuspendedByAdminWithNoPartner(
4950
Optional<Paper> findTopPaperByStoreId(Long storeId);
5051
long countByStore_Id(Long storeId);
5152

53+
@Query("""
54+
SELECT p FROM Paper p
55+
WHERE p.admin.id IN :adminIds
56+
AND p.isActivated = :status
57+
AND p.partnershipPeriodStart <= :today
58+
AND p.partnershipPeriodEnd >= :today
59+
""")
60+
List<Paper> findActivePapersByAdminIds(@Param("adminIds") List<Long> adminIds,
61+
@Param("today") LocalDate today,
62+
@Param("status") ActivationStatus status);
63+
64+
List<Paper> findByStoreIdAndAdminIdAndIsActivated(Long storeId, Long adminId, ActivationStatus isActivated);
5265
}

src/main/java/com/assu/server/domain/user/controller/StudentController.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
import com.assu.server.global.apiPayload.BaseResponse;
2222
import com.assu.server.global.apiPayload.code.status.SuccessStatus;
2323
import org.springframework.web.bind.annotation.*;
24+
25+
import java.util.List;
26+
2427
@RestController
2528
@Tag(name = "유저 관련 api", description = "유저와 관련된 로직을 처리하는 api")
2629
@RequiredArgsConstructor
@@ -73,9 +76,6 @@ public ResponseEntity<BaseResponse<Page<StudentResponseDTO.UsageDetailDTO>>> get
7376
studentService.getUnreviewedUsage(pd.getId(), pageable)));
7477
}
7578

76-
77-
78-
7979
@Operation(
8080
summary = "사용자 stamp 개수 조회 API",
8181
description = "# [v1.0 (2025-09-09)](https://www.notion.so/2691197c19ed805c980dd546adee9301?source=copy_link)\n" +
@@ -90,4 +90,24 @@ public BaseResponse<StudentResponseDTO.CheckStampResponseDTO> getStamp(
9090
) {
9191
return BaseResponse.onSuccess(SuccessStatus._OK, studentService.getStamp(pd.getId()));
9292
}
93+
94+
@Operation(
95+
summary = "사용자의 이용 가능한 제휴 조회 API",
96+
description = "# [v1.0 (2025-10-30)](https://clumsy-seeder-416.notion.site/API-29c1197c19ed8030b1f5e2a744416651?source=copy_link)\n" +
97+
"- all = true면 전체 조회, false면 2개만 조회"
98+
)
99+
@GetMapping("/usable")
100+
public BaseResponse<List<StudentResponseDTO.UsablePartnershipDTO>> getUsablePartnership(
101+
@AuthenticationPrincipal PrincipalDetails pd,
102+
@RequestParam(name = "all", defaultValue = "false") boolean all
103+
) {
104+
return BaseResponse.onSuccess(SuccessStatus._OK, studentService.getUsablePartnership(pd.getId(), all));
105+
}
106+
107+
@PostMapping("/sync/all")
108+
public BaseResponse<String> syncAllStudentsNow() {
109+
studentService.syncUserPapersForAllStudents();
110+
return BaseResponse.onSuccess(SuccessStatus._OK, "전체 학생 user_paper 동기화 완료");
111+
}
112+
93113
}

src/main/java/com/assu/server/domain/user/dto/StudentResponseDTO.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import java.time.LocalDateTime;
55
import java.util.List;
66

7+
import com.assu.server.domain.partnership.entity.enums.CriterionType;
8+
import com.assu.server.domain.partnership.entity.enums.OptionType;
79
import lombok.AllArgsConstructor;
810
import lombok.Builder;
911
import lombok.Getter;
@@ -66,4 +68,20 @@ public static class CheckStampResponseDTO {
6668
private String message;
6769
}
6870

71+
@Getter
72+
@NoArgsConstructor
73+
@AllArgsConstructor
74+
@Builder
75+
public static class UsablePartnershipDTO {
76+
private Long partnershipId;
77+
private String adminName;
78+
private String partnerName;
79+
private CriterionType criterionType;
80+
private OptionType optionType;
81+
private Integer people;
82+
private Long cost;
83+
private String category;
84+
private Long discountRate;
85+
}
86+
6987
}

src/main/java/com/assu/server/domain/user/repository/StudentRepository.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ public interface StudentRepository extends JpaRepository<Student, Long> {
1111

1212
Optional<Student> findStudentById(Long id);
1313

14+
1415
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.assu.server.domain.user.repository;
2+
3+
import com.assu.server.domain.user.entity.UserPaper;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.data.jpa.repository.Query;
6+
import org.springframework.data.repository.query.Param;
7+
8+
import java.time.LocalDate;
9+
import java.util.List;
10+
11+
public interface UserPaperRepository extends JpaRepository<UserPaper, Long> {
12+
13+
@Query("""
14+
SELECT up FROM UserPaper up
15+
JOIN FETCH up.paper p
16+
LEFT JOIN FETCH p.store s
17+
LEFT JOIN FETCH p.admin a
18+
WHERE up.student.id = :studentId
19+
AND p.isActivated = com.assu.server.domain.common.enums.ActivationStatus.ACTIVE
20+
AND p.partnershipPeriodStart <= :today
21+
AND p.partnershipPeriodEnd >= :today
22+
ORDER BY p.id DESC
23+
""")
24+
List<UserPaper> findActivePartnershipsByStudentId(@Param("studentId") Long studentId,
25+
@Param("today") LocalDate today);
26+
27+
boolean existsByStudentIdAndPaperId(Long studentId, Long paperId);
28+
29+
}

src/main/java/com/assu/server/domain/user/service/StudentService.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55

66
import com.assu.server.domain.user.dto.StudentResponseDTO;
77

8+
import java.util.List;
9+
810
public interface StudentService {
911
StudentResponseDTO.myPartnership getMyPartnership(Long studentId, int year, int month);
1012
StudentResponseDTO.CheckStampResponseDTO getStamp(Long memberId);//조회
11-
1213
Page<StudentResponseDTO.UsageDetailDTO> getUnreviewedUsage(Long memberId, Pageable pageable);
14+
List<StudentResponseDTO.UsablePartnershipDTO> getUsablePartnership(Long memberId, Boolean all);
15+
void syncUserPapersForAllStudents();
1316
}

src/main/java/com/assu/server/domain/user/service/StudentServiceImpl.java

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
11
package com.assu.server.domain.user.service;
22

3+
import java.time.LocalDate;
34
import java.time.LocalDateTime;
45
import java.time.format.DateTimeFormatter;
56
import java.util.List;
67

8+
import com.assu.server.domain.admin.entity.Admin;
9+
import com.assu.server.domain.admin.repository.AdminRepository;
10+
import com.assu.server.domain.common.enums.ActivationStatus;
11+
import com.assu.server.domain.partner.entity.Partner;
12+
import com.assu.server.domain.partnership.entity.Goods;
13+
import com.assu.server.domain.partnership.entity.Paper;
714
import com.assu.server.domain.partnership.entity.PaperContent;
15+
import com.assu.server.domain.partnership.entity.enums.OptionType;
16+
import com.assu.server.domain.partnership.repository.GoodsRepository;
817
import com.assu.server.domain.partnership.repository.PaperContentRepository;
18+
import com.assu.server.domain.partnership.repository.PaperRepository;
919
import com.assu.server.domain.store.entity.Store;
1020
import com.assu.server.domain.user.converter.StudentConverter;
1121
import com.assu.server.domain.user.dto.StudentResponseDTO;
1222
import com.assu.server.domain.user.entity.PartnershipUsage;
1323
import com.assu.server.domain.user.entity.Student;
24+
import com.assu.server.domain.user.entity.UserPaper;
1425
import com.assu.server.domain.user.repository.PartnershipUsageRepository;
1526
import com.assu.server.domain.user.repository.StudentRepository;
27+
import com.assu.server.domain.user.repository.UserPaperRepository;
1628
import com.assu.server.global.apiPayload.code.status.ErrorStatus;
1729
import com.assu.server.global.exception.DatabaseException;
1830
import jakarta.transaction.Transactional;
@@ -27,6 +39,13 @@
2739
@RequiredArgsConstructor
2840
public class StudentServiceImpl implements StudentService {
2941
private final StudentRepository studentRepository;
42+
private final UserPaperRepository userPaperRepository;
43+
private final PaperContentRepository paperContentRepository;
44+
private final PartnershipUsageRepository partnershipUsageRepository;
45+
private final GoodsRepository goodsRepository;
46+
private final AdminRepository adminRepository;
47+
private final PaperRepository paperRepository;
48+
3049
@Override
3150
@Transactional
3251
public StudentResponseDTO.CheckStampResponseDTO getStamp(Long memberId) {
@@ -36,9 +55,6 @@ public StudentResponseDTO.CheckStampResponseDTO getStamp(Long memberId) {
3655
return StudentConverter.checkStampResponseDTO(student, "스탬프 조회 성공");
3756
}
3857

39-
private final PaperContentRepository paperContentRepository;
40-
private final PartnershipUsageRepository partnershipUsageRepository;
41-
4258
@Override
4359
@Transactional
4460
public StudentResponseDTO.myPartnership getMyPartnership(Long studentId, int year, int month) {
@@ -112,4 +128,105 @@ public Page<StudentResponseDTO.UsageDetailDTO> getUnreviewedUsage(Long memberId,
112128
});
113129
}
114130

131+
@Override
132+
public List<StudentResponseDTO.UsablePartnershipDTO> getUsablePartnership(Long memberId, Boolean all) {
133+
LocalDate today = LocalDate.now();
134+
135+
List<UserPaper> userPapers = userPaperRepository.findActivePartnershipsByStudentId(memberId, today);
136+
137+
List<StudentResponseDTO.UsablePartnershipDTO> result = userPapers.stream().map(up -> {
138+
Paper paper = up.getPaper();
139+
PaperContent content = up.getPaperContent();
140+
Store store = paper.getStore();
141+
142+
String adminName = (paper.getAdmin() != null) ? paper.getAdmin().getName() : null;
143+
String partnerName = (store != null) ? store.getName() : null;
144+
145+
// 카테고리 결정 로직 그대로
146+
String finalCategory = null;
147+
if (content != null) {
148+
if (content.getCategory() != null) {
149+
finalCategory = content.getCategory();
150+
} else if (content.getOptionType() == OptionType.SERVICE) {
151+
List<Goods> goods = goodsRepository.findByContentId(content.getId());
152+
if (!goods.isEmpty()) {
153+
finalCategory = goods.get(0).getBelonging();
154+
}
155+
}
156+
}
157+
158+
return StudentResponseDTO.UsablePartnershipDTO.builder()
159+
.partnershipId(paper.getId())
160+
.adminName(adminName)
161+
.partnerName(partnerName)
162+
.criterionType(content != null ? content.getCriterionType() : null)
163+
.optionType(content != null ? content.getOptionType() : null)
164+
.people(content != null ? content.getPeople() : null)
165+
.cost(content != null ? content.getCost() : null)
166+
.category(finalCategory)
167+
.discountRate(content != null ? content.getDiscount() : null)
168+
.build();
169+
}).toList();
170+
171+
return Boolean.FALSE.equals(all) ? result.stream().limit(2).toList() : result;
172+
}
173+
174+
@Transactional
175+
public void syncUserPapersForStudent(Long studentId) {
176+
Student student = studentRepository.findById(studentId)
177+
.orElseThrow(() -> new DatabaseException(ErrorStatus.NO_SUCH_STUDENT));
178+
179+
// 1. 학생 기준으로 admin 찾기
180+
List<Admin> admins = adminRepository.findMatchingAdmins(
181+
student.getUniversity(),
182+
student.getDepartment(),
183+
student.getMajor()
184+
);
185+
186+
if (admins.isEmpty()) {
187+
return;
188+
}
189+
190+
List<Long> adminIds = admins.stream().map(Admin::getId).toList();
191+
LocalDate today = LocalDate.now();
192+
193+
// 2. admin들이 만든 오늘 유효한 paper 조회
194+
List<Paper> papers = paperRepository.findActivePapersByAdminIds(
195+
adminIds,
196+
today,
197+
ActivationStatus.ACTIVE
198+
);
199+
200+
// 3. user_paper에 없으면 넣기
201+
for (Paper paper : papers) {
202+
boolean exists = userPaperRepository.existsByStudentIdAndPaperId(studentId, paper.getId());
203+
if (exists) continue;
204+
205+
PaperContent latestContent = paperContentRepository
206+
.findTopByPaperIdOrderByIdDesc(paper.getId())
207+
.orElse(null);
208+
209+
UserPaper up = UserPaper.builder()
210+
.paper(paper)
211+
.paperContent(latestContent)
212+
.student(student)
213+
.build();
214+
215+
userPaperRepository.save(up);
216+
}
217+
}
218+
219+
/**
220+
* 전체 학생에 대해 일괄로 user_paper 채워 넣는 메서드
221+
* (스케줄러에서 이거만 호출하면 됨)
222+
*/
223+
@Transactional
224+
@Override
225+
public void syncUserPapersForAllStudents() {
226+
List<Student> students = studentRepository.findAll();
227+
for (Student s : students) {
228+
syncUserPapersForStudent(s.getId());
229+
}
230+
}
115231
}
232+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.assu.server.domain.user.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.scheduling.annotation.Scheduled;
5+
import org.springframework.stereotype.Component;
6+
7+
@Component
8+
@RequiredArgsConstructor
9+
public class UserPaperScheduler {
10+
11+
private final StudentServiceImpl studentService; // 또는 StudentService
12+
13+
/**
14+
* 매일 새벽 3시에 전체 학생의 user_paper를 동기화
15+
* cron 형식: 초 분 시 일 월 요일
16+
* "0 0 3 * * *" → 매일 03:00:00
17+
*/
18+
@Scheduled(cron = "0 0 3 * * *", zone = "Asia/Seoul")
19+
public void syncAllStudentsDaily() {
20+
studentService.syncUserPapersForAllStudents();
21+
}
22+
23+
}

0 commit comments

Comments
 (0)