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 src/modules/estimate/estimate.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ export const receivedEstimateRepository = {
select: {
id: true,
customerId: true,
moveDate: true,
status: true,
confirmedEstimateId: true,
},
Expand Down
35 changes: 33 additions & 2 deletions src/modules/estimate/estimate.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { AppError } from "../../lib/app-error";
import { buildPagination } from "../../utils/pagination.util";
import { runTransaction } from "../../utils/transaction";
import { notificationService } from "../notification/notification.service";
import { moverEstimateRequestRepository, receivedEstimateRepository } from "./estimate.repository";
import type {
ConfirmReceivedEstimateParams,
Expand All @@ -24,17 +25,35 @@ import type {
-DB 결과 API 응답 형태로 가공
*/

/*
/*
2026.07.23 add 김성현
- 받은 견적 목록 비즈니스 로직
- 받은 견적 상세 비즈니스 로직
- 받은 견적 확정 비즈니스 로직
*/

const KST_OFFSET_MS = 9 * 60 * 60 * 1000;

// =============================================================================
// 기사: 고객의 견적 요청 목록 조회
// =============================================================================

function getKstEndOfDay(date: Date): Date {
const kstDate = new Date(date.getTime() + KST_OFFSET_MS);

return new Date(
Date.UTC(
kstDate.getUTCFullYear(),
kstDate.getUTCMonth(),
kstDate.getUTCDate(),
14,
59,
59,
999,
),
);
}

function getCursorId(cursor: string | undefined) {
if (!cursor) {
return undefined;
Expand Down Expand Up @@ -601,7 +620,7 @@ export const receivedEstimateService = {
estimateId,
customerId,
}: ConfirmReceivedEstimateParams) {
return runTransaction(async (tx) => {
const result = await runTransaction(async (tx) => {
//확정 대상 견적 조회
const estimate = await receivedEstimateRepository.findReceivedEstimateForConfirm(
estimateRequestId,
Expand Down Expand Up @@ -702,6 +721,7 @@ export const receivedEstimateService = {
//확정 응답 형태 가공
return {
estimateRequest: confirmedEstimateRequest,
moveDate: estimate.estimateRequest.moveDate,
estimate: {
id: confirmedEstimate.id,
price: confirmedEstimate.price,
Expand All @@ -717,5 +737,16 @@ export const receivedEstimateService = {
expiredEstimateCount: expiredEstimates.count,
};
});

await notificationService.createNotification({
userId: result.estimate.mover.id,
type: "ESTIMATE_CONFIRMED",
title: "견적 확정",
content: "고객님이 회원님의 견적을 확정했습니다.",
linkUrl: "/estimate/received-requests",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

알림 문구가 "고객님이 견적을 확정"으로 끝나 문장이 다소 어색한것 같아서 수정이 필요해 보입니다!

예: "고객님이 회원님의 견적을 확정했습니다."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수신자가 기사인거는 확인했습니다.
다만 linkUrl 이 /estimate/received-requests 로 되어ㅏ있는데 기사가 확정된 견적을 보는 페이지가 맞나요?

맞다면 그냥 넘기셔도됩니다. :)

expiresAt: getKstEndOfDay(result.moveDate),
});
Comment on lines +741 to +748

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

트랜잭션 후 알림 실패를 도메인 작업 실패로 전파하지 않도록 통합 처리해 주세요.

두 서비스 모두 도메인 트랜잭션이 커밋된 뒤 알림 생성 오류를 그대로 전파하므로, 성공한 견적 확정/리뷰 작성이 API 실패로 보이고 재시도 시 충돌이 발생합니다.

  • src/modules/estimate/estimate.service.ts#L722-L729: 견적 확정 후 알림을 outbox 또는 재시도 큐로 전달하고 알림 실패가 확정 결과를 덮어쓰지 않게 처리해 주세요.
  • src/modules/review/review.service.ts#L264-L271: 리뷰/통계 커밋 후 동일한 알림 전달 및 실패 처리 정책을 적용해 주세요.
📍 Affects 2 files
  • src/modules/estimate/estimate.service.ts#L722-L729 (this comment)
  • src/modules/review/review.service.ts#L264-L271
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/estimate/estimate.service.ts` around lines 722 - 729, 트랜잭션 커밋 이후
실행되는 estimate.service.ts의 견적 확정 알림과 review.service.ts의 리뷰 작성 알림을 outbox 또는 재시도
큐로 전달하도록 변경하고, 알림 생성 실패가 도메인 작업의 성공 결과나 API 응답을 덮어쓰지 않게 처리하세요. 두 위치 모두 동일한 비동기
알림 전달·실패 처리 정책을 적용하며, 견적 확정 및 리뷰/통계 커밋 로직은 그대로 성공하도록 유지하세요.

Source: Path instructions


return result;
},
};
10 changes: 10 additions & 0 deletions src/modules/review/review.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { EstimateRequestStatus, EstimateStatus, Prisma } from "@prisma/client";
import { AppError } from "../../lib/app-error";
import { buildPagination } from "../../utils/pagination.util";
import { runTransaction } from "../../utils/transaction";
import { notificationService } from "../notification/notification.service";
import { reviewRepository } from "./review.repository";

type GetMyReviewListParams = {
Expand Down Expand Up @@ -260,6 +261,15 @@ export const reviewService = {
},
);

await notificationService.createNotification({
userId: estimate.moverId,
type: "REVIEW_RECEIVED",
title: "리뷰 도착",
content: "고객님이 회원님에게 리뷰를 작성했습니다.",
linkUrl: null,
expiresAt: null,
});

return {
review,
};
Expand Down