Skip to content

Commit 1cc58b4

Browse files
authored
Merge pull request #70 from UMC-AlbaLog/fix/ALBA_69
fix:반복설정 관련 논리적 모순 수정
2 parents 44d6214 + fd6ee8b commit 1cc58b4

3 files changed

Lines changed: 116 additions & 24 deletions

File tree

src/DTO/user_alba_schedule_dto.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export interface CreateManualScheduleBody {
77
work_time?: string;
88
day_of_week?: user_alba_schedule_day_of_week; // 실제 enum 타입으로 바꿔도 됨
99
repeat_type?: user_alba_schedule_repeat_type; // 실제 enum 타입으로 바꿔도 됨
10-
repeat_days?: string;
10+
repeat_days?: string | null;
1111
hourly_wage?: number;
1212
memo?: string;
1313
}
@@ -18,7 +18,7 @@ export interface UpdateManualScheduleBody {
1818
work_time?: string;
1919
day_of_week?: user_alba_schedule_day_of_week; // 실제 enum 타입으로 바꿔도 됨
2020
repeat_type?: user_alba_schedule_repeat_type; // 실제 enum 타입으로 바꿔도 됨
21-
repeat_days?: string;
21+
repeat_days?: string | null;
2222
hourly_wage?: number;
2323
memo?: string;
2424
}

src/repository/user_alba_schedule_repository.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ export class UserAlbaScheduleRepository {
5151
public async createWithWorkLog(
5252
userId: string,
5353
input: CreateUserAlbaScheduleRepoInput,
54-
workLogData: { workDate: Date; startTime: Date | null; endTime: Date | null; workMinutes: number | null },
54+
workLogData: {
55+
workDate: Date;
56+
startTime: Date | null;
57+
endTime: Date | null;
58+
workMinutes: number | null;
59+
},
5560
): Promise<Uint8Array> {
5661
const userIdBin = uuidToBin(userId);
5762

@@ -91,6 +96,23 @@ export class UserAlbaScheduleRepository {
9196
return schedule.user_alba_schedule_id;
9297
}
9398

99+
public async findByIdAndUserId(userId: string, scheduleId: string) {
100+
const userIdBin = uuidToBin(userId);
101+
const scheduleIdBin = uuidToBin(scheduleId);
102+
103+
return prisma.user_alba_schedule.findFirst({
104+
where: {
105+
user_id: userIdBin,
106+
user_alba_schedule_id: scheduleIdBin,
107+
},
108+
select: {
109+
repeat_type: true,
110+
repeat_days: true,
111+
// merge 검증에 필요하면 더 추가 가능
112+
},
113+
});
114+
}
115+
94116
public async updateByIdAndUserId(
95117
userId: string,
96118
scheduleId: string,

src/service/user_alba_schedule_service.ts

Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import {
33
CreateManualScheduleBody,
44
CreateFromAlbaBody,
5-
UpdateManualScheduleBody,} from '../DTO/user_alba_schedule_dto';
5+
UpdateManualScheduleBody,
6+
} from '../DTO/user_alba_schedule_dto';
67
import { UserAlbaScheduleRepository } from '../repository/user_alba_schedule_repository';
78
import { UserWorkLogRepository } from '../repository/user_work_log_repository';
89
import { binToUuid } from '../util/uuid';
@@ -11,6 +12,8 @@ import { CustomError } from '../DTO/error_dto';
1112
const scheduleRepo = new UserAlbaScheduleRepository();
1213
const workLogRepo = new UserWorkLogRepository();
1314

15+
type RepeatBody = Pick<CreateManualScheduleBody, 'repeat_type' | 'repeat_days'>;
16+
1417
function 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 = /^[01]{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

Comments
 (0)