diff options
Diffstat (limited to 'lib/bidding/actions.ts')
| -rw-r--r-- | lib/bidding/actions.ts | 226 |
1 files changed, 226 insertions, 0 deletions
diff --git a/lib/bidding/actions.ts b/lib/bidding/actions.ts new file mode 100644 index 00000000..9aabd469 --- /dev/null +++ b/lib/bidding/actions.ts @@ -0,0 +1,226 @@ +"use server"
+
+import db from "@/db/db"
+import { eq, and } from "drizzle-orm"
+import {
+ biddings,
+ biddingCompanies,
+ prItemsForBidding,
+ vendors,
+ generalContracts,
+ generalContractItems
+} from "@/db/schema"
+import { createPurchaseOrder } from "@/lib/soap/ecc/send/create-po"
+import { getCurrentSAPDate } from "@/lib/soap/utils"
+
+// TO Contract 서버 액션
+export async function transmitToContract(biddingId: number, userId: number) {
+ console.log('=== transmitToContract STARTED ===')
+ console.log('biddingId:', biddingId, 'userId:', userId)
+
+ try {
+ // 1. 입찰 정보 조회 (단순 쿼리)
+ console.log('Querying bidding...')
+ const bidding = await db.select()
+ .from(biddings)
+ .where(eq(biddings.id, biddingId))
+ .limit(1)
+
+ if (!bidding || bidding.length === 0) {
+ throw new Error("입찰 정보를 찾을 수 없습니다.")
+ }
+
+ const biddingData = bidding[0]
+ console.log('biddingData', biddingData)
+
+ // 2. 낙찰된 업체들 조회 (별도 쿼리)
+ console.log('Querying bidding companies...')
+ let winnerCompaniesData = []
+ try {
+ // 2.1 biddingCompanies만 먼저 조회 (join 제거)
+ console.log('Step 1: Querying biddingCompanies only...')
+ const biddingCompaniesRaw = await db.select()
+ .from(biddingCompanies)
+ .where(
+ and(
+ eq(biddingCompanies.biddingId, biddingId),
+ eq(biddingCompanies.isWinner, true)
+ )
+ )
+
+ console.log('biddingCompaniesRaw:', biddingCompaniesRaw)
+
+ // 2.2 각 company에 대한 vendor 정보 개별 조회
+ for (const bc of biddingCompaniesRaw) {
+ console.log('Processing companyId:', bc.companyId)
+
+ try {
+ const vendorData = await db.select()
+ .from(vendors)
+ .where(eq(vendors.id, bc.companyId))
+ .limit(1)
+
+ const vendor = vendorData.length > 0 ? vendorData[0] : null
+ console.log('Vendor data for', bc.companyId, ':', vendor)
+
+ winnerCompaniesData.push({
+ companyId: bc.companyId,
+ finalQuoteAmount: bc.finalQuoteAmount,
+ vendorCode: vendor?.vendorCode || null,
+ vendorName: vendor?.vendorName || null,
+ })
+ } catch (vendorError) {
+ console.error('Vendor query error for', bc.companyId, ':', vendorError)
+ // vendor 정보 없이도 진행
+ winnerCompaniesData.push({
+ companyId: bc.companyId,
+ finalQuoteAmount: bc.finalQuoteAmount,
+ vendorCode: null,
+ vendorName: null,
+ })
+ }
+ }
+
+ console.log('winnerCompaniesData type:', typeof winnerCompaniesData)
+ console.log('winnerCompaniesData length:', winnerCompaniesData?.length)
+ console.log('winnerCompaniesData:', winnerCompaniesData)
+ } catch (queryError) {
+ console.error('Query error:', queryError)
+ throw new Error(`biddingCompanies 쿼리 실패: ${queryError}`)
+ }
+
+ // 상태 검증
+ console.log('biddingData.status', biddingData.status)
+ if (biddingData.status !== 'vendor_selected') {
+ throw new Error("업체 선정이 완료되지 않은 입찰입니다.")
+ }
+
+ // 낙찰된 업체 검증
+ if (winnerCompaniesData.length === 0) {
+ throw new Error("낙찰된 업체가 없습니다.")
+ }
+
+ console.log('Processing', winnerCompaniesData.length, 'winner companies')
+ for (const winnerCompany of winnerCompaniesData) {
+ // 계약 번호 자동 생성 (현재 시간 기반)
+ const contractNumber = `CONTRACT-BID-${Date.now()}-${winnerCompany.companyId}`
+ console.log('contractNumber', contractNumber)
+ // general-contract 생성
+ const contractResult = await db.insert(generalContracts).values({
+ contractNumber,
+ revision: 0,
+ contractSourceType: 'bid', // 입찰에서 생성됨
+ status: 'Draft',
+ category: biddingData.contractType as any, // 단가계약, 일반계약, 매각계약
+ name: biddingData.title,
+ selectionMethod: '입찰',
+ vendorId: winnerCompany.companyId,
+ linkedBidNumber: biddingData.biddingNumber,
+ contractAmount: winnerCompany.finalQuoteAmount || undefined,
+ currency: biddingData.currency || 'KRW',
+ registeredById: userId, // TODO: 현재 사용자 ID로 변경 필요
+ lastUpdatedById: userId, // TODO: 현재 사용자 ID로 변경 필요
+ }).returning({ id: generalContracts.id })
+ console.log('contractResult', contractResult)
+ const contractId = contractResult[0].id
+
+ // 3. PR 아이템들로 general-contract-items 생성 (일단 생략)
+ console.log('Skipping PR items creation for now')
+ }
+
+ return { success: true, message: `${winnerCompaniesData.length}개의 계약서가 생성되었습니다.` }
+
+ } catch (error) {
+ console.error('TO Contract 실패:', error)
+ throw new Error(error instanceof Error ? error.message : '계약서 생성에 실패했습니다.')
+ }
+}
+
+// TO PO 서버 액션
+export async function transmitToPO(biddingId: number) {
+ try {
+ // 1. 입찰 정보 및 낙찰 업체 조회
+ const bidding = await db.query.biddings.findFirst({
+ where: eq(biddings.id, biddingId),
+ with: {
+ biddingCompanies: {
+ where: eq(biddingCompanies.isWinner, true), // 낙찰된 업체만
+ with: {
+ vendor: true
+ }
+ },
+ prItemsForBidding: true
+ }
+ })
+
+ if (!bidding) {
+ throw new Error("입찰 정보를 찾을 수 없습니다.")
+ }
+
+ if (bidding.status !== 'vendor_selected') {
+ throw new Error("업체 선정이 완료되지 않은 입찰입니다.")
+ }
+
+ const winnerCompanies = bidding.biddingCompanies.filter(bc => bc.isWinner)
+
+ if (winnerCompanies.length === 0) {
+ throw new Error("낙찰된 업체가 없습니다.")
+ }
+
+ // 2. PO 데이터 구성
+ const poData = {
+ T_Bidding_HEADER: winnerCompanies.map((company, index) => ({
+ ANFNR: bidding.biddingNumber,
+ LIFNR: company.vendor?.vendorCode || `VENDOR${company.companyId}`,
+ ZPROC_IND: 'A', // 구매 처리 상태
+ ANGNR: bidding.biddingNumber,
+ WAERS: bidding.currency || 'KRW',
+ ZTERM: '0001', // 기본 지급조건
+ INCO1: 'FOB',
+ INCO2: 'Seoul, Korea',
+ MWSKZ: 'V0', // 세금 코드
+ LANDS: 'KR',
+ ZRCV_DT: getCurrentSAPDate(),
+ ZATTEN_IND: 'Y',
+ IHRAN: getCurrentSAPDate(),
+ TEXT: `PO from Bidding: ${bidding.title}`,
+ })),
+ T_Bidding_ITEM: bidding.prItemsForBidding?.map((item, index) => ({
+ ANFNR: bidding.biddingNumber,
+ ANFPS: (index + 1).toString().padStart(5, '0'),
+ LIFNR: winnerCompanies[0]?.vendor?.vendorCode || `VENDOR${winnerCompanies[0]?.companyId}`,
+ NETPR: item.annualUnitPrice?.toString() || '0',
+ PEINH: '1',
+ BPRME: item.quantityUnit || 'EA',
+ NETWR: item.annualUnitPrice && item.quantity
+ ? (item.annualUnitPrice * item.quantity).toString()
+ : '0',
+ BRTWR: item.annualUnitPrice && item.quantity
+ ? ((item.annualUnitPrice * item.quantity) * 1.1).toString() // 10% 부가세 가정
+ : '0',
+ LFDAT: item.requestedDeliveryDate?.toISOString().split('T')[0] || getCurrentSAPDate(),
+ })) || [],
+ T_PR_RETURN: [{
+ ANFNR: bidding.biddingNumber,
+ ANFPS: '00001',
+ EBELN: `PR${bidding.biddingNumber}`,
+ EBELP: '00001',
+ MSGTY: 'S',
+ MSGTXT: 'Success'
+ }]
+ }
+
+ // 3. SAP으로 PO 전송
+ const result = await createPurchaseOrder(poData)
+
+ if (!result.success) {
+ throw new Error(result.message)
+ }
+
+ return { success: true, message: result.message }
+
+ } catch (error) {
+ console.error('TO PO 실패:', error)
+ throw new Error(error instanceof Error ? error.message : 'PO 전송에 실패했습니다.')
+ }
+}
|
