From e484964b1d78cedabbe182c789a8e4c9b53e29d3 Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Thu, 29 May 2025 05:12:36 +0000
Subject: (김준회) 기술영업 조선 RFQ 파일첨부 및 채팅 기능 구현 / menuConfig
 수정 (벤더 기술영업)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/techsales-rfq/table/create-rfq-dialog.tsx | 244 +++++++++++++++++++++-----
 1 file changed, 200 insertions(+), 44 deletions(-)

(limited to 'lib/techsales-rfq/table/create-rfq-dialog.tsx')

diff --git a/lib/techsales-rfq/table/create-rfq-dialog.tsx b/lib/techsales-rfq/table/create-rfq-dialog.tsx
index cc652b44..5faa3a0b 100644
--- a/lib/techsales-rfq/table/create-rfq-dialog.tsx
+++ b/lib/techsales-rfq/table/create-rfq-dialog.tsx
@@ -197,20 +197,60 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
         throw new Error("로그인이 필요합니다")
       }
 
-      // 자재코드(item_code) 배열을 materialGroupCodes로 전달
-      const result = await createTechSalesRfq({
-        biddingProjectId: data.biddingProjectId,
-        materialGroupCodes: data.materialCodes, // item_code를 자재코드로 사용
-        createdBy: Number(session.user.id),
-        dueDate: data.dueDate,
+      // 선택된 아이템들을 아이템명(itemList)으로 그룹핑
+      const groupedItems = selectedItems.reduce((groups, item) => {
+        const actualItemName = item.itemList // 실제 조선 아이템명
+        if (!actualItemName) {
+          throw new Error(`아이템 "${item.itemCode}"의 아이템명(itemList)이 없습니다.`)
+        }
+        if (!groups[actualItemName]) {
+          groups[actualItemName] = []
+        }
+        groups[actualItemName].push(item)
+        return groups
+      }, {} as Record<string, typeof selectedItems>)
+
+      const rfqGroups = Object.entries(groupedItems).map(([actualItemName, items]) => {
+        const itemCodes = items.map(item => item.itemCode) // 자재그룹코드들
+        const joinedItemCodes = itemCodes.join(',')
+        return {
+          actualItemName,
+          items,
+          itemCodes,
+          joinedItemCodes,
+          codeLength: joinedItemCodes.length,
+          isOverLimit: joinedItemCodes.length > 255
+        }
       })
+
+      // 255자 초과 그룹 확인
+      const overLimitGroups = rfqGroups.filter(group => group.isOverLimit)
+      if (overLimitGroups.length > 0) {
+        const groupNames = overLimitGroups.map(g => `"${g.actualItemName}" (${g.codeLength}자)`).join(', ')
+        throw new Error(`다음 아이템 그룹의 자재코드가 255자를 초과합니다: ${groupNames}`)
+      }
+
+      // 각 그룹별로 RFQ 생성
+      const createPromises = rfqGroups.map(group => 
+        createTechSalesRfq({
+          biddingProjectId: data.biddingProjectId,
+          materialGroupCodes: [group.joinedItemCodes], // 그룹화된 자재코드들
+          createdBy: Number(session.user.id),
+          dueDate: data.dueDate,
+        })
+      )
+
+      const results = await Promise.all(createPromises)
       
-      if (result.error) {
-        throw new Error(result.error)
+      // 오류 확인
+      const errors = results.filter(result => result.error)
+      if (errors.length > 0) {
+        throw new Error(errors.map(e => e.error).join(', '))
       }
       
       // 성공적으로 생성되면 다이얼로그 닫기 및 메시지 표시
-      toast.success(`${result.data?.length || 0}개의 RFQ가 성공적으로 생성되었습니다`)
+      const totalRfqs = results.reduce((sum, result) => sum + (result.data?.length || 0), 0)
+      toast.success(`${rfqGroups.length}개 아이템 그룹으로 총 ${totalRfqs}개의 RFQ가 성공적으로 생성되었습니다`)
       setIsDialogOpen(false)
       form.reset({
         biddingProjectId: undefined,
@@ -423,38 +463,45 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
                               아이템을 불러오는 중...
                             </div>
                           ) : availableItems.length > 0 ? (
-                            availableItems.map((item) => {
-                              const isSelected = selectedItems.some(selected => selected.id === item.id)
-                              return (
-                                <div
-                                  key={item.id}
-                                  className={cn(
-                                    "flex items-center space-x-2 p-2 rounded-md cursor-pointer hover:bg-muted",
-                                    isSelected && "bg-muted"
-                                  )}
-                                  onClick={() => handleItemToggle(item)}
-                                >
-                                  <div className="flex items-center space-x-2 flex-1">
-                                    {isSelected ? (
-                                      <CheckSquare className="h-4 w-4" />
-                                    ) : (
-                                      <Square className="h-4 w-4" />
+                            [...availableItems]
+                              .sort((a, b) => {
+                                // itemList 기준으로 정렬 (없는 경우 itemName 사용, 둘 다 없으면 맨 뒤로)
+                                const aName = a.itemList || a.itemName || 'zzz'
+                                const bName = b.itemList || b.itemName || 'zzz'
+                                return aName.localeCompare(bName, 'ko', { numeric: true })
+                              })
+                              .map((item) => {
+                                const isSelected = selectedItems.some(selected => selected.id === item.id)
+                                return (
+                                  <div
+                                    key={item.id}
+                                    className={cn(
+                                      "flex items-center space-x-2 p-2 rounded-md cursor-pointer hover:bg-muted",
+                                      isSelected && "bg-muted"
                                     )}
-                                    <div className="flex-1">
-                                      <div className="font-medium">
-                                        {item.itemList || item.itemName}
-                                      </div>
-                                      <div className="text-sm text-muted-foreground">
-                                        {item.itemCode} • {item.description || '설명 없음'}
-                                      </div>
-                                      <div className="text-xs text-muted-foreground">
-                                        공종: {item.workType} • 선종: {item.shipTypes}
+                                    onClick={() => handleItemToggle(item)}
+                                  >
+                                    <div className="flex items-center space-x-2 flex-1">
+                                      {isSelected ? (
+                                        <CheckSquare className="h-4 w-4" />
+                                      ) : (
+                                        <Square className="h-4 w-4" />
+                                      )}
+                                      <div className="flex-1">
+                                        <div className="font-medium">
+                                          {item.itemList || item.itemName || '아이템명 없음'}
+                                        </div>
+                                        <div className="text-sm text-muted-foreground">
+                                          {item.itemCode} • {item.description || '설명 없음'}
+                                        </div>
+                                        <div className="text-xs text-muted-foreground">
+                                          공종: {item.workType} • 선종: {item.shipTypes}
+                                        </div>
                                       </div>
                                     </div>
                                   </div>
-                                </div>
-                              )
-                            })
+                                )
+                              })
                           ) : (
                             <div className="text-center py-8 text-muted-foreground">
                               {itemSearchQuery ? "검색 결과가 없습니다" : "아이템이 없습니다"}
@@ -480,7 +527,7 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
                                     variant="secondary"
                                     className="flex items-center gap-1"
                                   >
-                                    {item.itemList || item.itemName} ({item.itemCode})
+                                    {item.itemList || item.itemName || '아이템명 없음'} ({item.itemCode})
                                     <X
                                       className="h-3 w-3 cursor-pointer hover:text-destructive"
                                       onClick={() => handleRemoveItem(item.id)}
@@ -498,19 +545,93 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
                         </FormItem>
                       )}
                     />
+
+                    {/* RFQ 그룹핑 미리보기 */}
+                    {selectedItems.length > 0 && (
+                      <div className="space-y-3">
+                        <FormLabel>생성될 RFQ 그룹 미리보기</FormLabel>
+                        <div className="border rounded-md p-3 bg-background">
+                          {(() => {
+                            // 아이템명(itemList)으로 그룹핑
+                            const groupedItems = selectedItems.reduce((groups, item) => {
+                              const actualItemName = item.itemList // 실제 조선 아이템명
+                              if (!actualItemName) {
+                                return groups // itemList가 없는 경우 제외
+                              }
+                              if (!groups[actualItemName]) {
+                                groups[actualItemName] = []
+                              }
+                              groups[actualItemName].push(item)
+                              return groups
+                            }, {} as Record<string, typeof selectedItems>)
+
+                            const rfqGroups = Object.entries(groupedItems).map(([actualItemName, items]) => {
+                              const itemCodes = items.map(item => item.itemCode) // 자재그룹코드들
+                              const joinedItemCodes = itemCodes.join(',')
+                              return {
+                                actualItemName,
+                                items,
+                                itemCodes,
+                                joinedItemCodes,
+                                codeLength: joinedItemCodes.length,
+                                isOverLimit: joinedItemCodes.length > 255
+                              }
+                            })
+
+                            return (
+                              <div className="space-y-3">
+                                <div className="text-sm text-muted-foreground">
+                                  총 {rfqGroups.length}개의 RFQ가 생성됩니다 (아이템명별로 그룹핑)
+                                </div>
+                                {rfqGroups.map((group, index) => (
+                                  <div 
+                                    key={group.actualItemName}
+                                    className={cn(
+                                      "p-3 border rounded-md space-y-2",
+                                      group.isOverLimit && "border-destructive bg-destructive/5"
+                                    )}
+                                  >
+                                    <div className="flex items-center justify-between">
+                                      <div className="font-medium">
+                                        RFQ #{index + 1}: {group.actualItemName}
+                                      </div>
+                                      <Badge variant={group.isOverLimit ? "destructive" : "secondary"}>
+                                        {group.itemCodes.length}개 자재코드 ({group.codeLength}/255자)
+                                      </Badge>
+                                    </div>
+                                    <div className="text-sm text-muted-foreground">
+                                      자재코드: {group.joinedItemCodes}
+                                    </div>
+                                    {group.isOverLimit && (
+                                      <div className="text-sm text-destructive">
+                                        ⚠️ 자재코드 길이가 255자를 초과합니다. 일부 아이템을 제거해주세요.
+                                      </div>
+                                    )}
+                                    <div className="text-xs text-muted-foreground">
+                                      포함된 아이템: {group.items.map(item => `${item.itemCode}`).join(', ')}
+                                    </div>
+                                  </div>
+                                ))}
+                              </div>
+                            )
+                          })()}
+                        </div>
+                      </div>
+                    )}
                   </div>
                 </div>
               )}
               
               {/* 안내 메시지 */}
-              {selectedProject && (
+              {/* {selectedProject && (
                 <div className="text-sm text-muted-foreground bg-muted p-3 rounded-md">
                   <p>• 공종별 조선 아이템을 선택하세요.</p>
-                  <p>• 선택된 아이템의 자재코드(item_code)별로 개별 RFQ가 생성됩니다.</p>
-                  <p>• 아이템 코드가 자재 그룹 코드로 사용됩니다.</p>
+                  <p>• <strong>같은 아이템명의 다른 자재코드들은 하나의 RFQ로 그룹핑됩니다.</strong></p>
+                  <p>• 그룹핑된 자재코드들은 comma로 구분되어 저장됩니다.</p>
+                  <p>• 자재코드 길이는 최대 255자까지 가능합니다.</p>
                   <p>• 마감일은 벤더가 견적을 제출해야 하는 날짜입니다.</p>
                 </div>
-              )}
+              )} */}
                
               <div className="flex justify-end space-x-2 pt-4">
                 <Button
@@ -523,9 +644,44 @@ export function CreateRfqDialog({ onCreated }: CreateRfqDialogProps) {
                 </Button>
                 <Button 
                   type="submit" 
-                  disabled={isProcessing || !selectedProject || selectedItems.length === 0}
+                  disabled={
+                    isProcessing || 
+                    !selectedProject || 
+                    selectedItems.length === 0 ||
+                    // 255자 초과 그룹이 있는지 확인
+                    (() => {
+                      const groupedItems = selectedItems.reduce((groups, item) => {
+                        const actualItemName = item.itemList // 실제 조선 아이템명
+                        if (!actualItemName) {
+                          return groups // itemList가 없는 경우 제외
+                        }
+                        if (!groups[actualItemName]) {
+                          groups[actualItemName] = []
+                        }
+                        groups[actualItemName].push(item.itemCode)
+                        return groups
+                      }, {} as Record<string, string[]>)
+                      
+                      return Object.values(groupedItems).some(itemCodes => itemCodes.join(',').length > 255)
+                    })()
+                  }
                 >
-                  {isProcessing ? "처리 중..." : `${selectedItems.length}개 자재코드로 생성하기`}
+                  {isProcessing ? "처리 중..." : (() => {
+                    const groupedItems = selectedItems.reduce((groups, item) => {
+                      const actualItemName = item.itemList // 실제 조선 아이템명
+                      if (!actualItemName) {
+                        return groups // itemList가 없는 경우 제외
+                      }
+                      if (!groups[actualItemName]) {
+                        groups[actualItemName] = []
+                      }
+                      groups[actualItemName].push(item.itemCode)
+                      return groups
+                    }, {} as Record<string, string[]>)
+                    
+                    const groupCount = Object.keys(groupedItems).length
+                    return `${groupCount}개 아이템 그룹으로 생성하기`
+                  })()}
                 </Button>
               </div>
             </form>
-- 
cgit v1.2.3

