1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
'use server'
import { revalidatePath } from "next/cache";
import db from "@/db/db";
import { rfqsLast, rfqLastTbeSessions, rfqLastDetails } from "@/db/schema";
import { rfqLastVendorResponses } from "@/db/schema/rfqVendor";
import { eq, and, inArray } from "drizzle-orm";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
import { cancelRFQ } from "@/lib/soap/ecc/send/delete-rfq";
/**
* RFQ 삭제 (상태 변경) 서버 액션
* ANFNR이 있는 RFQ만 삭제 가능하며, ECC로 SOAP 취소 요청을 전송한 후
* 성공 시 RFQ 상태를 "RFQ 삭제"로 변경하고 연결된 TBE 세션을 "취소" 상태로 변경
* 또한 연결된 vendor response를 "RFQ 삭제" 상태로 변경하고,
* 업체선정이 진행중인 경우 업체선정 취소 처리
*/
export async function deleteRfq(rfqIds: number[], deleteReason?: string): Promise<{
success: boolean;
message: string;
results?: Array<{ rfqId: number; success: boolean; error?: string }>;
}> {
try {
const session = await getServerSession(authOptions);
if (!session?.user?.id) {
return {
success: false,
message: "인증이 필요합니다."
};
}
const userId = Number(session.user.id);
// 1. RFQ 정보 조회 및 ANFNR 유효성 검증
const rfqs = await db.query.rfqsLast.findMany({
where: inArray(rfqsLast.id, rfqIds),
columns: {
id: true,
rfqCode: true,
ANFNR: true,
status: true,
}
});
// ANFNR이 있는 RFQ만 필터링
const rfqsWithAnfnr = rfqs.filter(rfq => rfq.ANFNR && rfq.ANFNR.trim() !== "");
if (rfqsWithAnfnr.length === 0) {
return {
success: false,
message: "ANFNR이 있는 RFQ가 선택되지 않았습니다."
};
}
// 요청된 RFQ 중 일부가 없거나 ANFNR이 없는 경우 확인
const missingIds = rfqIds.filter(id => !rfqs.find(r => r.id === id));
const rfqsWithoutAnfnr = rfqs.filter(rfq => !rfq.ANFNR || rfq.ANFNR.trim() === "");
if (missingIds.length > 0 || rfqsWithoutAnfnr.length > 0) {
const warnings: string[] = [];
if (missingIds.length > 0) {
warnings.push(`존재하지 않는 RFQ: ${missingIds.join(", ")}`);
}
if (rfqsWithoutAnfnr.length > 0) {
warnings.push(`ANFNR이 없는 RFQ: ${rfqsWithoutAnfnr.map(r => r.rfqCode || r.id).join(", ")}`);
}
}
const results: Array<{ rfqId: number; success: boolean; error?: string }> = [];
// 2. 각 RFQ에 대해 ECC 취소 요청 및 상태 변경 처리
for (const rfq of rfqsWithAnfnr) {
try {
// 2-1. ECC로 SOAP 취소 요청 전송
const cancelResult = await cancelRFQ(rfq.ANFNR!);
if (!cancelResult.success) {
results.push({
rfqId: rfq.id,
success: false,
error: cancelResult.message
});
continue;
}
// 2-2. ECC 요청 성공 시 트랜잭션 내에서 상태 변경
await db.transaction(async (tx) => {
// RFQ 상태를 "RFQ 삭제"로 변경 및 삭제 사유 저장
await tx
.update(rfqsLast)
.set({
status: "RFQ 삭제",
deleteReason: deleteReason || null,
updatedBy: userId,
updatedAt: new Date()
})
.where(eq(rfqsLast.id, rfq.id));
// 연결된 모든 TBE 세션을 "취소" 상태로 변경
// TBE 세션이 없어도 정상 동작 (조건에 맞는 레코드가 없으면 업데이트 없이 종료)
await tx
.update(rfqLastTbeSessions)
.set({
status: "취소",
updatedBy: userId,
updatedAt: new Date()
})
.where(
and(
eq(rfqLastTbeSessions.rfqsLastId, rfq.id),
inArray(rfqLastTbeSessions.status, ["생성중", "준비중", "진행중", "검토중", "보류"])
)
);
// 연결된 모든 vendor response를 "취소" 상태로 변경 (RFQ 삭제 처리)
// 참고: 스키마에 "RFQ 삭제" 상태가 없으므로 "취소" 상태를 사용
await tx
.update(rfqLastVendorResponses)
.set({
status: "취소",
updatedBy: userId,
updatedAt: new Date()
})
.where(
and(
eq(rfqLastVendorResponses.rfqsLastId, rfq.id),
eq(rfqLastVendorResponses.isLatest, true),
inArray(rfqLastVendorResponses.status, ["대기중", "작성중", "제출완료", "수정요청", "최종확정"])
)
);
// 업체선정이 진행중인 경우 취소 처리
// isSelected가 true인 경우 또는 contractStatus가 "일반계약 진행중"인 경우
await tx
.update(rfqLastDetails)
.set({
isSelected: false,
selectionDate: null,
selectionReason: null,
selectedBy: null,
contractStatus: null, // 계약 상태 초기화
updatedBy: userId,
updatedAt: new Date()
})
.where(
and(
eq(rfqLastDetails.rfqsLastId, rfq.id),
eq(rfqLastDetails.isLatest, true)
)
);
});
// 2-3. 캐시 갱신
revalidatePath("/evcp/rfq-last");
revalidatePath(`/evcp/rfq-last/${rfq.id}`);
results.push({
rfqId: rfq.id,
success: true
});
} catch (error) {
console.error(`RFQ 삭제 실패 (ID: ${rfq.id}, ANFNR: ${rfq.ANFNR}):`, error);
results.push({
rfqId: rfq.id,
success: false,
error: error instanceof Error ? error.message : "알 수 없는 오류"
});
}
}
const successCount = results.filter(r => r.success).length;
const failCount = results.length - successCount;
if (failCount === 0) {
return {
success: true,
message: `RFQ 삭제가 완료되었습니다. (${successCount}건)`,
results
};
} else {
return {
success: false,
message: `RFQ 삭제 중 일부 실패했습니다. (성공: ${successCount}건, 실패: ${failCount}건)`,
results
};
}
} catch (error) {
console.error("RFQ 삭제 처리 중 오류:", error);
return {
success: false,
message: error instanceof Error ? error.message : "RFQ 삭제 처리 중 오류가 발생했습니다."
};
}
}
|