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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dist
# misc
.vscode
.cursor
.omx
.DS_Store
.env.local
.env.development.local
Expand Down
2 changes: 2 additions & 0 deletions src/config/container.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ const lecturesService = new LecturesService(
enrollmentsRepo,
lectureEnrollmentsRepo,
instructorRepo,
studentRepo,
parentChildLinkRepo,
permissionService,
prisma,
);
Expand Down
8 changes: 6 additions & 2 deletions src/repos/enrollments.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,16 +374,20 @@ export class EnrollmentsRepository {
});
}

/** ํ•™์ƒ ์ „ํ™”๋ฒˆํ˜ธ ๊ธฐ์ค€ AppParentLinkId ์—…๋ฐ์ดํŠธ (์ž๋…€ ๋“ฑ๋ก ์‹œ ์—ฐ๋™) */
/** ํ•™์ƒ/ํ•™๋ถ€๋ชจ ํ”„๋กœํ•„ ๊ธฐ์ค€ AppParentLinkId ์—…๋ฐ์ดํŠธ (์ž๋…€ ๋“ฑ๋ก ์‹œ ์—ฐ๋™) */
async updateAppParentLinkIdByStudentPhone(
studentPhone: string,
studentName: string,
parentPhoneNumber: string,
appParentLinkId: string,
tx?: Prisma.TransactionClient,
) {
const client = tx ?? this.prisma;
return await client.enrollment.updateMany({
where: {
studentPhone: studentPhone,
studentPhone,
studentName,
parentPhone: parentPhoneNumber,
appParentLinkId: null, // ์•„์ง ์—ฐ๋™๋˜์ง€ ์•Š์€ ๊ฑด๋“ค์ด๋งŒ
},
data: {
Expand Down
155 changes: 155 additions & 0 deletions src/services/enrollments.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,161 @@ describe('EnrollmentsService - @unit #critical', () => {
);
});

it('๊ธฐ์กด Enrollment๋ฅผ ์žฌ์‚ฌ์šฉํ•  ๋•Œ appStudentId๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฉด ์ž๋™ ์—ฐ๊ฒฐ์„ ๋ณด์ •ํ•œ๋‹ค', async () => {
const existingEnrollment = {
...mockEnrollments.active,
appStudentId: null,
studentPhone: createEnrollmentRequests.basic.studentPhone,
studentName: createEnrollmentRequests.basic.studentName,
parentPhone: createEnrollmentRequests.basic.parentPhone,
};

mockLecturesRepo.findById.mockResolvedValue(mockLectures.basic);
mockPermissionService.validateInstructorAccess.mockResolvedValue();
mockEnrollmentsRepo.findManyByInstructorAndPhones.mockResolvedValue([
existingEnrollment,
]);
mockStudentRepo.findByPhoneNumberAndProfile.mockResolvedValue(
mockStudents.basic,
);
mockEnrollmentsRepo.update.mockResolvedValue({
...existingEnrollment,
appStudentId: mockStudents.basic.id,
});
mockLectureEnrollmentsRepo.findByLectureIdAndEnrollmentId.mockResolvedValueOnce(
null,
);
mockLectureEnrollmentsRepo.create.mockResolvedValue({
id: 'le-1',
memo: null,
lectureId,
enrollmentId: existingEnrollment.id,
registeredAt: new Date(),
});
mockLectureEnrollmentsRepo.findByLectureIdAndEnrollmentId.mockResolvedValueOnce(
{
id: 'le-1',
memo: null,
lectureId,
enrollmentId: existingEnrollment.id,
registeredAt: new Date(),
enrollment: {
...existingEnrollment,
appStudentId: mockStudents.basic.id,
},
},
);

await enrollmentsService.createEnrollment(
lectureId,
{
...createEnrollmentRequests.basic,
instructorId,
status: EnrollmentStatus.ACTIVE,
},
UserType.INSTRUCTOR,
instructorId,
);

expect(
mockStudentRepo.findByPhoneNumberAndProfile,
).toHaveBeenCalledWith(
createEnrollmentRequests.basic.studentPhone,
createEnrollmentRequests.basic.studentName,
createEnrollmentRequests.basic.parentPhone,
mockPrisma,
);
expect(mockEnrollmentsRepo.update).toHaveBeenCalledWith(
existingEnrollment.id,
{
appStudent: {
connect: {
id: mockStudents.basic.id,
},
},
},
mockPrisma,
);
expect(mockEnrollmentsRepo.create).not.toHaveBeenCalled();
});

it('๊ธฐ์กด Enrollment๋ฅผ ์žฌ์‚ฌ์šฉํ•  ๋•Œ appParentLinkId๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฉด ์ž๋™ ์—ฐ๊ฒฐ์„ ๋ณด์ •ํ•œ๋‹ค', async () => {
const existingEnrollment = {
...mockEnrollments.active,
appParentLinkId: null,
studentPhone: createEnrollmentRequests.basic.studentPhone,
studentName: createEnrollmentRequests.basic.studentName,
parentPhone: createEnrollmentRequests.basic.parentPhone,
};

mockLecturesRepo.findById.mockResolvedValue(mockLectures.basic);
mockPermissionService.validateInstructorAccess.mockResolvedValue();
mockEnrollmentsRepo.findManyByInstructorAndPhones.mockResolvedValue([
existingEnrollment,
]);
mockParentsService.findLinkByPhoneNumberAndProfile.mockResolvedValue(
mockParentLinks.active,
);
mockEnrollmentsRepo.update.mockResolvedValue({
...existingEnrollment,
appParentLinkId: mockParentLinks.active.id,
});
mockLectureEnrollmentsRepo.findByLectureIdAndEnrollmentId.mockResolvedValueOnce(
null,
);
mockLectureEnrollmentsRepo.create.mockResolvedValue({
id: 'le-1',
memo: null,
lectureId,
enrollmentId: existingEnrollment.id,
registeredAt: new Date(),
});
mockLectureEnrollmentsRepo.findByLectureIdAndEnrollmentId.mockResolvedValueOnce(
{
id: 'le-1',
memo: null,
lectureId,
enrollmentId: existingEnrollment.id,
registeredAt: new Date(),
enrollment: {
...existingEnrollment,
appParentLinkId: mockParentLinks.active.id,
},
},
);

await enrollmentsService.createEnrollment(
lectureId,
{
...createEnrollmentRequests.basic,
instructorId,
status: EnrollmentStatus.ACTIVE,
},
UserType.INSTRUCTOR,
instructorId,
);

expect(
mockParentsService.findLinkByPhoneNumberAndProfile,
).toHaveBeenCalledWith(
createEnrollmentRequests.basic.studentPhone,
createEnrollmentRequests.basic.studentName,
createEnrollmentRequests.basic.parentPhone,
);
expect(mockEnrollmentsRepo.update).toHaveBeenCalledWith(
existingEnrollment.id,
{
appParentLink: {
connect: {
id: mockParentLinks.active.id,
},
},
},
mockPrisma,
);
expect(mockEnrollmentsRepo.create).not.toHaveBeenCalled();
});

it('์ˆ˜๊ฐ•์ƒ ๋“ฑ๋ก ์‹œ ํ•™์ƒ ์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ํ•™๋ถ€๋ชจ-์ž๋…€ ๋งํฌ์™€ ์ผ์น˜ํ•  ๋•Œ, ParentLink๊ฐ€ ์ž๋™์œผ๋กœ ์—ฐ๊ฒฐ๋œ๋‹ค', async () => {
mockLecturesRepo.findById.mockResolvedValue(mockLectures.basic);
mockPermissionService.validateInstructorAccess.mockResolvedValue();
Expand Down
101 changes: 70 additions & 31 deletions src/services/enrollments.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,29 +84,38 @@ export class EnrollmentsService {
);

return await this.prisma.$transaction(async (tx) => {
// 3. ๊ธฐ์กด Enrollment ํ™•์ธ (๊ฐ™์€ ๊ฐ•์‚ฌ, ๊ฐ™์€ ํ•™์ƒ ๋ฒˆํ˜ธ)
let enrollmentId: string | null = null;
const resolveStudentId = async () => {
let studentId = data.appStudentId;

if (data.studentPhone) {
const existingEnrollments =
await this.enrollmentsRepository.findManyByInstructorAndPhones(
lecture.instructorId,
[data.studentPhone],
tx,
);
if (!studentId && data.studentPhone) {
const studentPhone = data.studentPhone as string;
const studentName = data.studentName as string | undefined;
const parentPhone = data.parentPhone as string | undefined;

if (existingEnrollments.length > 0) {
enrollmentId = existingEnrollments[0].id;
if (studentName && parentPhone) {
const student =
await this.studentRepository.findByPhoneNumberAndProfile(
studentPhone,
studentName,
parentPhone,
tx,
);
if (student) {
studentId = student.id;
}
}
}
}

// 4. ์—†์œผ๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑ
if (!enrollmentId) {
return studentId;
};

const resolveParentLinkId = async () => {
let parentLinkId = data.appParentLinkId;

if (!parentLinkId && data.studentPhone) {
const studentPhone = data.studentPhone as string;
const studentName = data.studentName as string | undefined;
const parentPhone = data.parentPhone as string | undefined;
const studentName = data.studentName as string;
const parentPhone = data.parentPhone as string;

if (studentName && parentPhone) {
const link =
Expand All @@ -121,25 +130,55 @@ export class EnrollmentsService {
}
}

let studentId = data.appStudentId;
if (!studentId && data.studentPhone) {
const studentPhone = data.studentPhone as string;
const studentName = data.studentName as string | undefined;
const parentPhone = data.parentPhone as string | undefined;
return parentLinkId;
};

if (studentName && parentPhone) {
const student =
await this.studentRepository.findByPhoneNumberAndProfile(
studentPhone,
studentName,
parentPhone,
tx,
);
if (student) {
studentId = student.id;
// 3. ๊ธฐ์กด Enrollment ํ™•์ธ (๊ฐ™์€ ๊ฐ•์‚ฌ, ๊ฐ™์€ ํ•™์ƒ ๋ฒˆํ˜ธ)
let enrollmentId: string | null = null;

if (data.studentPhone) {
const existingEnrollments =
await this.enrollmentsRepository.findManyByInstructorAndPhones(
lecture.instructorId,
[data.studentPhone],
tx,
);

if (existingEnrollments.length > 0) {
const existingEnrollment = existingEnrollments[0];
enrollmentId = existingEnrollment.id;
const connectionData: Prisma.EnrollmentUpdateInput = {};

if (!existingEnrollment.appStudentId) {
const studentId = await resolveStudentId();
if (studentId) {
connectionData.appStudent = { connect: { id: studentId } };
}
}

if (!existingEnrollment.appParentLinkId) {
const parentLinkId = await resolveParentLinkId();
if (parentLinkId) {
connectionData.appParentLink = {
connect: { id: parentLinkId },
};
}
}

if (Object.keys(connectionData).length > 0) {
await this.enrollmentsRepository.update(
existingEnrollment.id,
connectionData,
tx,
);
}
}
}

// 4. ์—†์œผ๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑ
if (!enrollmentId) {
const parentLinkId = await resolveParentLinkId();
const studentId = await resolveStudentId();

const newEnrollment = await this.enrollmentsRepository.create(
{
Expand Down
Loading