@@ -622,5 +622,191 @@ export const CommissionService = {
622622 } else {
623623 return `${ diffMinutes } 분 전` ;
624624 }
625- }
625+ } ,
626+
627+ // 캐릭터 데이터
628+ CHARACTER_DATA : [
629+ {
630+ image : "https://example.com/character1.png" ,
631+ quote : {
632+ title : "커미션계의 VIP" ,
633+ description : "\"커미션계의 큰 손 등장!\" 덕분에 작가님들의 창작 활동이 풍요로워졌어요."
634+ } ,
635+ condition : "월 사용 포인트 15만포인트 이상"
636+ } ,
637+ {
638+ image : "https://example.com/character2.png" ,
639+ quote : {
640+ title : "작가 덕후 신청자" ,
641+ description : "\"이 작가님만큼은 믿고 맡긴다!\" 단골의 미덕을 지닌 당신, 작가님도 감동했을 거예요."
642+ } ,
643+ condition : "같은 작가에게 3회 이상 신청"
644+ } ,
645+ {
646+ image : "https://example.com/character3.png" ,
647+ quote : {
648+ title : "호기심 대장 신청자" ,
649+ description : "호기심이 가득해서, 언제나 새로운 작가를 탐색해요."
650+ } ,
651+ condition : "서로 다른 작가 5명 이상에게 커미션을 신청"
652+ } ,
653+ {
654+ image : "https://example.com/character4.png" ,
655+ quote : {
656+ title : "숨겨진 보석 발굴가" ,
657+ description : "\"빛나는 원석을 내가 발견했다!\" 성장하는 작가님들의 첫걸음을 함께한 당신, 멋져요."
658+ } ,
659+ condition : "팔로워 수가 0명인 작가에게 신청 2회 이상"
660+ } ,
661+ {
662+ image : "https://example.com/character5.png" ,
663+ quote : {
664+ title : "빠른 피드백러" ,
665+ description : "\"작가님, 이번 커미션 최고였어요!\" 정성 가득한 피드백으로 건강한 커미션 문화를 만들어가요."
666+ } ,
667+ condition : "커미션 완료 후 후기 작성률 100% 달성"
668+ }
669+ ] ,
670+
671+ /**
672+ * 커미션 리포트 조회
673+ */
674+ async getReport ( userId ) {
675+ // 현재 날짜 기준으로 이전 달 계산
676+ const now = new Date ( ) ;
677+ const currentMonth = now . getMonth ( ) + 1 ; // getMonth()는 0부터 시작
678+ const currentYear = now . getFullYear ( ) ;
679+
680+ // 이전 달 계산 (1월이면 작년 12월)
681+ const reportYear = currentMonth === 1 ? currentYear - 1 : currentYear ;
682+ const reportMonth = currentMonth === 1 ? 12 : currentMonth - 1 ;
683+
684+ // 사용자 닉네임 조회
685+ const userNickname = await CommissionRepository . findUserNicknameById ( userId ) ;
686+
687+ // 해당 월 승인받은 리퀘스트들 조회
688+ const requests = await CommissionRepository . findApprovedRequestsByUserAndMonth (
689+ userId ,
690+ reportYear ,
691+ reportMonth
692+ ) ;
693+
694+ // 통계 계산
695+ const statistics = this . calculateReportStatistics ( requests ) ;
696+
697+ // 랜덤 캐릭터 선택
698+ const randomCharacter = this . CHARACTER_DATA [ Math . floor ( Math . random ( ) * this . CHARACTER_DATA . length ) ] ;
699+
700+ return {
701+ reportInfo : {
702+ userNickname : userNickname ,
703+ month : reportMonth
704+ } ,
705+ characterImage : randomCharacter . image ,
706+ quote : randomCharacter . quote ,
707+ condition : randomCharacter . condition ,
708+ statistics : statistics
709+ } ;
710+ } ,
711+
712+ /**
713+ * 리포트 통계 계산
714+ */
715+ calculateReportStatistics ( requests ) {
716+ if ( requests . length === 0 ) {
717+ // 데이터가 없어도 랜덤 캐릭터는 나오게
718+ return {
719+ mainCategory : { name : "없음" , count : 0 } ,
720+ favoriteArtist : { id : null , nickname : "없음" , profileImage : null } ,
721+ pointsUsed : 0 ,
722+ reviewRate : 0.0
723+ } ;
724+ }
725+
726+ // 카테고리별 집계 (횟수 → 포인트 순)
727+ const categoryStats = this . aggregateByCategory ( requests ) ;
728+ const mainCategory = categoryStats [ 0 ] ? {
729+ name : categoryStats [ 0 ] . name ,
730+ count : categoryStats [ 0 ] . count
731+ } : { name : "없음" , count : 0 } ;
732+
733+ // 작가별 집계 (횟수 → 포인트 순)
734+ const artistStats = this . aggregateByArtist ( requests ) ;
735+ const favoriteArtist = artistStats [ 0 ] ? {
736+ id : artistStats [ 0 ] . id ,
737+ nickname : artistStats [ 0 ] . nickname ,
738+ profileImage : artistStats [ 0 ] . profileImage
739+ } : {
740+ id : null ,
741+ nickname : "없음" ,
742+ profileImage : null
743+ } ;
744+
745+ // 총 사용 포인트
746+ const pointsUsed = requests . reduce ( ( sum , req ) => sum + req . totalPrice , 0 ) ;
747+
748+ // 리뷰 작성률 (COMPLETED 중에서)
749+ const completedRequests = requests . filter ( req => req . status === 'COMPLETED' ) ;
750+ const reviewedRequests = completedRequests . filter ( req => req . reviews . length > 0 ) ;
751+ const reviewRate = completedRequests . length > 0
752+ ? Math . round ( ( reviewedRequests . length / completedRequests . length ) * 1000 ) / 10 // 소수점 1자리
753+ : 0.0 ;
754+
755+ return {
756+ mainCategory,
757+ favoriteArtist,
758+ pointsUsed,
759+ reviewRate
760+ } ;
761+ } ,
762+
763+ /**
764+ * 카테고리별 집계
765+ */
766+ aggregateByCategory ( requests ) {
767+ const categoryMap = new Map ( ) ;
768+
769+ requests . forEach ( req => {
770+ const categoryName = req . commission . category . name ;
771+ const existing = categoryMap . get ( categoryName ) || { name : categoryName , count : 0 , points : 0 } ;
772+ existing . count += 1 ;
773+ existing . points += req . totalPrice ;
774+ categoryMap . set ( categoryName , existing ) ;
775+ } ) ;
776+
777+ // 1순위: 횟수, 2순위: 포인트로 정렬
778+ return Array . from ( categoryMap . values ( ) )
779+ . sort ( ( a , b ) => {
780+ if ( a . count !== b . count ) return b . count - a . count ; // 횟수 많은 순
781+ return b . points - a . points ; // 포인트 많은 순
782+ } ) ;
783+ } ,
784+
785+ /**
786+ * 작가별 집계
787+ */
788+ aggregateByArtist ( requests ) {
789+ const artistMap = new Map ( ) ;
790+
791+ requests . forEach ( req => {
792+ const artistId = req . commission . artist . id ;
793+ const existing = artistMap . get ( artistId ) || {
794+ id : artistId ,
795+ nickname : req . commission . artist . nickname ,
796+ profileImage : req . commission . artist . profileImage ,
797+ count : 0 ,
798+ points : 0
799+ } ;
800+ existing . count += 1 ;
801+ existing . points += req . totalPrice ;
802+ artistMap . set ( artistId , existing ) ;
803+ } ) ;
804+
805+ // 1순위: 횟수, 2순위: 포인트로 정렬
806+ return Array . from ( artistMap . values ( ) )
807+ . sort ( ( a , b ) => {
808+ if ( a . count !== b . count ) return b . count - a . count ; // 횟수 많은 순
809+ return b . points - a . points ; // 포인트 많은 순
810+ } ) ;
811+ }
626812} ;
0 commit comments