22import {
33 CreateManualScheduleBody ,
44 CreateFromAlbaBody ,
5- UpdateManualScheduleBody , } from '../DTO/user_alba_schedule_dto' ;
5+ UpdateManualScheduleBody ,
6+ } from '../DTO/user_alba_schedule_dto' ;
67import { UserAlbaScheduleRepository } from '../repository/user_alba_schedule_repository' ;
78import { UserWorkLogRepository } from '../repository/user_work_log_repository' ;
89import { binToUuid } from '../util/uuid' ;
@@ -11,6 +12,8 @@ import { CustomError } from '../DTO/error_dto';
1112const scheduleRepo = new UserAlbaScheduleRepository ( ) ;
1213const workLogRepo = new UserWorkLogRepository ( ) ;
1314
15+ type RepeatBody = Pick < CreateManualScheduleBody , 'repeat_type' | 'repeat_days' > ;
16+
1417function toDateString ( d : Date ) : string {
1518 // DB Date -> "YYYY-MM-DD"
1619 const y = d . getFullYear ( ) ;
@@ -29,17 +32,61 @@ function toTimeRangeString(start?: Date | null, end?: Date | null): string | und
2932 return `-${ fmt ( end ! ) } ` ;
3033}
3134
32- function validateManual ( body : CreateManualScheduleBody ) {
33- // repeat 모순 검증
34- if ( body . repeat_type && ! body . repeat_days ) {
35- throw new CustomError ( '400' , 400 , 'repeat_type이 있으면 repeat_days가 필요합니다.' , null ) ;
35+ const MASK7 = / ^ [ 0 1 ] { 7 } $ / ;
36+
37+ function validateManual ( body : RepeatBody ) {
38+ const rt = body . repeat_type ;
39+ const rd = body . repeat_days ;
40+
41+ // 반복 설정 자체가 없으면 단발 일정
42+ if ( ! rt ) {
43+ if ( rd ) {
44+ throw new CustomError ( '400' , 400 , 'repeat_type 없이 repeat_days를 보낼 수 없습니다.' , null ) ;
45+ }
46+ return ;
47+ }
48+
49+ // none/daily는 repeat_days 금지
50+ if ( rt === 'none' || rt === 'daily' ) {
51+ if ( rd ) {
52+ throw new CustomError ( '400' , 400 , `${ rt } 이면 repeat_days는 필요하지 않습니다.` , null ) ;
53+ }
54+ return ;
55+ }
56+
57+ // weekly/biweekly는 repeat_days 필수
58+ if ( ! rd ) {
59+ throw new CustomError ( '400' , 400 , `${ rt } 이면 repeat_days가 필요합니다.` , null ) ;
60+ }
61+
62+ // DB 저장 안정성 + 규격 강제 (VARCHAR(10) 안에서 안전)
63+ if ( rd . length > 10 ) {
64+ throw new CustomError (
65+ '400' ,
66+ 400 ,
67+ 'repeat_days가 너무 깁니다. 7자리 비트마스크만 허용합니다.' ,
68+ null ,
69+ ) ;
70+ }
71+ if ( ! MASK7 . test ( rd ) ) {
72+ throw new CustomError (
73+ '400' ,
74+ 400 ,
75+ 'repeat_days는 7자리 비트마스크(예: 1010100)여야 합니다.' ,
76+ null ,
77+ ) ;
3678 }
37- if ( ! body . repeat_type && body . repeat_days ) {
38- throw new CustomError ( '400' , 400 , 'repeat_days가 있으면 repeat_type이 필요합니다.' , null ) ;
79+ if ( ! rd . includes ( '1' ) ) {
80+ throw new CustomError (
81+ '400' ,
82+ 400 ,
83+ 'repeat_days에는 최소 1개 이상의 요일이 포함되어야 합니다.' ,
84+ null ,
85+ ) ;
3986 }
4087}
4188
42- function validateFromAlba ( body : CreateFromAlbaBody ) {
89+ function validateFromAlba ( body : CreateFromAlbaBody ) {
4390 if ( ! body . user_work_log_id ) {
4491 throw new CustomError ( '400' , 400 , 'user_work_log_id가 필요합니다.' , null ) ;
4592 }
@@ -63,11 +110,15 @@ function parseScheduleToWorkLogData(workDate?: string, workTime?: string) {
63110 const [ startStr , endStr ] = workTime . split ( '-' ) ;
64111 if ( startStr ) {
65112 const [ sh , sm ] = startStr . split ( ':' ) . map ( Number ) ;
66- startTime = new Date ( workDate + `T${ String ( sh ) . padStart ( 2 , '0' ) } :${ String ( sm ) . padStart ( 2 , '0' ) } :00+09:00` ) ;
113+ startTime = new Date (
114+ workDate + `T${ String ( sh ) . padStart ( 2 , '0' ) } :${ String ( sm ) . padStart ( 2 , '0' ) } :00+09:00` ,
115+ ) ;
67116 }
68117 if ( endStr ) {
69118 const [ eh , em ] = endStr . split ( ':' ) . map ( Number ) ;
70- endTime = new Date ( workDate + `T${ String ( eh ) . padStart ( 2 , '0' ) } :${ String ( em ) . padStart ( 2 , '0' ) } :00+09:00` ) ;
119+ endTime = new Date (
120+ workDate + `T${ String ( eh ) . padStart ( 2 , '0' ) } :${ String ( em ) . padStart ( 2 , '0' ) } :00+09:00` ,
121+ ) ;
71122 // 야간 근무 (종료 시간이 시작 시간보다 이전)
72123 if ( endTime <= startTime ! ) {
73124 endTime . setDate ( endTime . getDate ( ) + 1 ) ;
@@ -113,8 +164,7 @@ export async function createManual(
113164}
114165
115166// 유저 알바 정보 기반 스케줄 생성
116- export async function createFromAlba (
117- userId : string , body : CreateFromAlbaBody ) : Promise < string > {
167+ export async function createFromAlba ( userId : string , body : CreateFromAlbaBody ) : Promise < string > {
118168 validateFromAlba ( body ) ;
119169
120170 const wl = await workLogRepo . findByIdAndUser ( userId , body . user_work_log_id ) ;
@@ -129,7 +179,7 @@ export async function createManual(
129179 const hourlyWage = wl . alba_posting ?. hourly_rate ?? undefined ;
130180 const workplace = wl . alba_posting ?. store ?. store_name ?? undefined ;
131181
132- const idBin = await scheduleRepo . create ( userId , {
182+ const idBin = await scheduleRepo . create ( userId , {
133183 hourly_wage : hourlyWage ,
134184 work_date : workDateStr ,
135185 work_time : workTimeStr ,
@@ -144,21 +194,41 @@ export async function update(
144194 scheduleId : string ,
145195 body : UpdateManualScheduleBody ,
146196) : Promise < void > {
147- // PATCH인데 아무것도 안오면 400 처리
148197 const hasAnyField = Object . values ( body ) . some ( ( v ) => v !== undefined ) ;
149198 if ( ! hasAnyField ) {
150199 throw new CustomError ( 'EC400' , 400 , '수정할 값이 없습니다.' , null ) ;
151200 }
152201
202+ // 반복 관련 필드가 들어오면 기존 값과 합쳐서 검증
153203 if ( body . repeat_type !== undefined || body . repeat_days !== undefined ) {
154- validateManual ( body ) ;
155- }
204+ const existing = await scheduleRepo . findByIdAndUserId ( userId , scheduleId ) ;
205+ if ( ! existing ) {
206+ throw new CustomError ( 'EC404' , 404 , '스케줄을 찾을 수 없습니다.' , null ) ;
207+ }
208+
209+ const nextRepeatType = body . repeat_type !== undefined ? body . repeat_type : existing . repeat_type ;
210+
211+ const nextRepeatDays = body . repeat_days !== undefined ? body . repeat_days : existing . repeat_days ;
212+
213+ // daily/none으로 바꾸면 repeat_days는 최종적으로 null로 간주
214+ const normalizedRepeatDays =
215+ nextRepeatType === 'daily' || nextRepeatType === 'none' ? null : nextRepeatDays ;
216+
217+ // 최종값 계산
218+ const merged : RepeatBody = {
219+ repeat_type : nextRepeatType ,
220+ repeat_days : normalizedRepeatDays ,
221+ } ;
222+
223+ validateManual ( merged ) ;
224+ }
225+
226+ const updateData : any = { ...body } ;
227+ if ( body . repeat_type === 'daily' || body . repeat_type === 'none' ) {
228+ updateData . repeat_days = null ; // 선택
229+ }
156230
157- const updatedCount = await scheduleRepo . updateByIdAndUserId (
158- userId ,
159- scheduleId ,
160- body ,
161- ) ;
231+ const updatedCount = await scheduleRepo . updateByIdAndUserId ( userId , scheduleId , updateData ) ;
162232
163233 if ( updatedCount === 0 ) {
164234 throw new CustomError ( 'EC404' , 404 , '스케줄을 찾을 수 없습니다.' , null ) ;
0 commit comments