Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,7 @@ Optional<PaperContent> findLatestValidByStoreIdNative(
@Param("price") String price, // CriterionType.PRICE.name()
@Param("headcount") String headcount // CriterionType.HEADCOUNT.name()
);

Optional<PaperContent> findTopByPaperIdOrderByIdDesc(Long paperId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

Expand Down Expand Up @@ -49,4 +50,16 @@ List<Paper> findAllSuspendedByAdminWithNoPartner(
Optional<Paper> findTopPaperByStoreId(Long storeId);
long countByStore_Id(Long storeId);

@Query("""
SELECT p FROM Paper p
WHERE p.admin.id IN :adminIds
AND p.isActivated = :status
AND p.partnershipPeriodStart <= :today
AND p.partnershipPeriodEnd >= :today
""")
List<Paper> findActivePapersByAdminIds(@Param("adminIds") List<Long> adminIds,
@Param("today") LocalDate today,
@Param("status") ActivationStatus status);

List<Paper> findByStoreIdAndAdminIdAndIsActivated(Long storeId, Long adminId, ActivationStatus isActivated);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
import com.assu.server.global.apiPayload.BaseResponse;
import com.assu.server.global.apiPayload.code.status.SuccessStatus;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@Tag(name = "유저 관련 api", description = "유저와 관련된 로직을 처리하는 api")
@RequiredArgsConstructor
Expand Down Expand Up @@ -73,9 +76,6 @@ public ResponseEntity<BaseResponse<Page<StudentResponseDTO.UsageDetailDTO>>> get
studentService.getUnreviewedUsage(pd.getId(), pageable)));
}




@Operation(
summary = "사용자 stamp 개수 조회 API",
description = "# [v1.0 (2025-09-09)](https://www.notion.so/2691197c19ed805c980dd546adee9301?source=copy_link)\n" +
Expand All @@ -90,4 +90,24 @@ public BaseResponse<StudentResponseDTO.CheckStampResponseDTO> getStamp(
) {
return BaseResponse.onSuccess(SuccessStatus._OK, studentService.getStamp(pd.getId()));
}

@Operation(
summary = "사용자의 이용 가능한 제휴 조회 API",
description = "# [v1.0 (2025-10-30)](https://clumsy-seeder-416.notion.site/API-29c1197c19ed8030b1f5e2a744416651?source=copy_link)\n" +
"- all = true면 전체 조회, false면 2개만 조회"
)
@GetMapping("/usable")
public BaseResponse<List<StudentResponseDTO.UsablePartnershipDTO>> getUsablePartnership(
@AuthenticationPrincipal PrincipalDetails pd,
@RequestParam(name = "all", defaultValue = "false") boolean all
) {
return BaseResponse.onSuccess(SuccessStatus._OK, studentService.getUsablePartnership(pd.getId(), all));
}

@PostMapping("/sync/all")
public BaseResponse<String> syncAllStudentsNow() {
studentService.syncUserPapersForAllStudents();
return BaseResponse.onSuccess(SuccessStatus._OK, "전체 학생 user_paper 동기화 완료");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.time.LocalDateTime;
import java.util.List;

import com.assu.server.domain.partnership.entity.enums.CriterionType;
import com.assu.server.domain.partnership.entity.enums.OptionType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand Down Expand Up @@ -66,4 +68,20 @@ public static class CheckStampResponseDTO {
private String message;
}

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class UsablePartnershipDTO {
private Long partnershipId;
private String adminName;
private String partnerName;
private CriterionType criterionType;
private OptionType optionType;
private Integer people;
private Long cost;
private String category;
private Long discountRate;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ public interface StudentRepository extends JpaRepository<Student, Long> {

Optional<Student> findStudentById(Long id);


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.assu.server.domain.user.repository;

import com.assu.server.domain.user.entity.UserPaper;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.time.LocalDate;
import java.util.List;

public interface UserPaperRepository extends JpaRepository<UserPaper, Long> {

@Query("""
SELECT up FROM UserPaper up
JOIN FETCH up.paper p
LEFT JOIN FETCH p.store s
LEFT JOIN FETCH p.admin a
WHERE up.student.id = :studentId
AND p.isActivated = com.assu.server.domain.common.enums.ActivationStatus.ACTIVE
AND p.partnershipPeriodStart <= :today
AND p.partnershipPeriodEnd >= :today
ORDER BY p.id DESC
""")
List<UserPaper> findActivePartnershipsByStudentId(@Param("studentId") Long studentId,
@Param("today") LocalDate today);

boolean existsByStudentIdAndPaperId(Long studentId, Long paperId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@

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

import java.util.List;

public interface StudentService {
StudentResponseDTO.myPartnership getMyPartnership(Long studentId, int year, int month);
StudentResponseDTO.CheckStampResponseDTO getStamp(Long memberId);//조회

Page<StudentResponseDTO.UsageDetailDTO> getUnreviewedUsage(Long memberId, Pageable pageable);
List<StudentResponseDTO.UsablePartnershipDTO> getUsablePartnership(Long memberId, Boolean all);
void syncUserPapersForAllStudents();
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
package com.assu.server.domain.user.service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

import com.assu.server.domain.admin.entity.Admin;
import com.assu.server.domain.admin.repository.AdminRepository;
import com.assu.server.domain.common.enums.ActivationStatus;
import com.assu.server.domain.partner.entity.Partner;
import com.assu.server.domain.partnership.entity.Goods;
import com.assu.server.domain.partnership.entity.Paper;
import com.assu.server.domain.partnership.entity.PaperContent;
import com.assu.server.domain.partnership.entity.enums.OptionType;
import com.assu.server.domain.partnership.repository.GoodsRepository;
import com.assu.server.domain.partnership.repository.PaperContentRepository;
import com.assu.server.domain.partnership.repository.PaperRepository;
import com.assu.server.domain.store.entity.Store;
import com.assu.server.domain.user.converter.StudentConverter;
import com.assu.server.domain.user.dto.StudentResponseDTO;
import com.assu.server.domain.user.entity.PartnershipUsage;
import com.assu.server.domain.user.entity.Student;
import com.assu.server.domain.user.entity.UserPaper;
import com.assu.server.domain.user.repository.PartnershipUsageRepository;
import com.assu.server.domain.user.repository.StudentRepository;
import com.assu.server.domain.user.repository.UserPaperRepository;
import com.assu.server.global.apiPayload.code.status.ErrorStatus;
import com.assu.server.global.exception.DatabaseException;
import jakarta.transaction.Transactional;
Expand All @@ -27,6 +39,13 @@
@RequiredArgsConstructor
public class StudentServiceImpl implements StudentService {
private final StudentRepository studentRepository;
private final UserPaperRepository userPaperRepository;
private final PaperContentRepository paperContentRepository;
private final PartnershipUsageRepository partnershipUsageRepository;
private final GoodsRepository goodsRepository;
private final AdminRepository adminRepository;
private final PaperRepository paperRepository;

@Override
@Transactional
public StudentResponseDTO.CheckStampResponseDTO getStamp(Long memberId) {
Expand All @@ -36,9 +55,6 @@ public StudentResponseDTO.CheckStampResponseDTO getStamp(Long memberId) {
return StudentConverter.checkStampResponseDTO(student, "스탬프 조회 성공");
}

private final PaperContentRepository paperContentRepository;
private final PartnershipUsageRepository partnershipUsageRepository;

@Override
@Transactional
public StudentResponseDTO.myPartnership getMyPartnership(Long studentId, int year, int month) {
Expand Down Expand Up @@ -112,4 +128,105 @@ public Page<StudentResponseDTO.UsageDetailDTO> getUnreviewedUsage(Long memberId,
});
}

@Override
public List<StudentResponseDTO.UsablePartnershipDTO> getUsablePartnership(Long memberId, Boolean all) {
LocalDate today = LocalDate.now();

List<UserPaper> userPapers = userPaperRepository.findActivePartnershipsByStudentId(memberId, today);

List<StudentResponseDTO.UsablePartnershipDTO> result = userPapers.stream().map(up -> {
Paper paper = up.getPaper();
PaperContent content = up.getPaperContent();
Store store = paper.getStore();

String adminName = (paper.getAdmin() != null) ? paper.getAdmin().getName() : null;
String partnerName = (store != null) ? store.getName() : null;

// 카테고리 결정 로직 그대로
String finalCategory = null;
if (content != null) {
if (content.getCategory() != null) {
finalCategory = content.getCategory();
} else if (content.getOptionType() == OptionType.SERVICE) {
List<Goods> goods = goodsRepository.findByContentId(content.getId());
if (!goods.isEmpty()) {
finalCategory = goods.get(0).getBelonging();
}
}
}

return StudentResponseDTO.UsablePartnershipDTO.builder()
.partnershipId(paper.getId())
.adminName(adminName)
.partnerName(partnerName)
.criterionType(content != null ? content.getCriterionType() : null)
.optionType(content != null ? content.getOptionType() : null)
.people(content != null ? content.getPeople() : null)
.cost(content != null ? content.getCost() : null)
.category(finalCategory)
.discountRate(content != null ? content.getDiscount() : null)
.build();
}).toList();

return Boolean.FALSE.equals(all) ? result.stream().limit(2).toList() : result;
}

@Transactional
public void syncUserPapersForStudent(Long studentId) {
Student student = studentRepository.findById(studentId)
.orElseThrow(() -> new DatabaseException(ErrorStatus.NO_SUCH_STUDENT));

// 1. 학생 기준으로 admin 찾기
List<Admin> admins = adminRepository.findMatchingAdmins(
student.getUniversity(),
student.getDepartment(),
student.getMajor()
);

if (admins.isEmpty()) {
return;
}

List<Long> adminIds = admins.stream().map(Admin::getId).toList();
LocalDate today = LocalDate.now();

// 2. admin들이 만든 오늘 유효한 paper 조회
List<Paper> papers = paperRepository.findActivePapersByAdminIds(
adminIds,
today,
ActivationStatus.ACTIVE
);

// 3. user_paper에 없으면 넣기
for (Paper paper : papers) {
boolean exists = userPaperRepository.existsByStudentIdAndPaperId(studentId, paper.getId());
if (exists) continue;

PaperContent latestContent = paperContentRepository
.findTopByPaperIdOrderByIdDesc(paper.getId())
.orElse(null);

UserPaper up = UserPaper.builder()
.paper(paper)
.paperContent(latestContent)
.student(student)
.build();

userPaperRepository.save(up);
}
}

/**
* 전체 학생에 대해 일괄로 user_paper 채워 넣는 메서드
* (스케줄러에서 이거만 호출하면 됨)
*/
@Transactional
@Override
public void syncUserPapersForAllStudents() {
List<Student> students = studentRepository.findAll();
for (Student s : students) {
syncUserPapersForStudent(s.getId());
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.assu.server.domain.user.service;

import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class UserPaperScheduler {

private final StudentServiceImpl studentService; // 또는 StudentService

/**
* 매일 새벽 3시에 전체 학생의 user_paper를 동기화
* cron 형식: 초 분 시 일 월 요일
* "0 0 3 * * *" → 매일 03:00:00
*/
@Scheduled(cron = "0 0 3 * * *", zone = "Asia/Seoul")
public void syncAllStudentsDaily() {
studentService.syncUserPapersForAllStudents();
}

}