From fd542b5ad4bf94b82d872f87b96aa2e7514ffbc3 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Fri, 19 Sep 2025 09:40:38 +0000 Subject: (최겸) 구매 일반계약 수정, 견적 입찰 계약 세금코드 select 적용 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/bidding/bidding-conditions-edit.tsx | 35 ++++++-- lib/bidding/list/create-bidding-dialog.tsx | 21 +++-- lib/bidding/vendor/partners-bidding-pre-quote.tsx | 27 ++++-- .../detail/general-contract-basic-info.tsx | 29 ++++--- lib/general-contracts/service.ts | 99 +++++++++++++++++++--- .../editor/commercial-terms-form.tsx | 53 ++++++++++-- .../vendor/batch-update-conditions-dialog.tsx | 59 ++++++++++++- lib/tax-conditions/types.ts | 56 ++++++++++++ 8 files changed, 329 insertions(+), 50 deletions(-) create mode 100644 lib/tax-conditions/types.ts diff --git a/components/bidding/bidding-conditions-edit.tsx b/components/bidding/bidding-conditions-edit.tsx index 51b1a688..1017597b 100644 --- a/components/bidding/bidding-conditions-edit.tsx +++ b/components/bidding/bidding-conditions-edit.tsx @@ -19,6 +19,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Pencil, Save, X } from "lucide-react" import { getBiddingConditions, updateBiddingConditions } from "@/lib/bidding/service" import { getIncotermsForSelection, getPaymentTermsForSelection, getPlaceOfShippingForSelection, getPlaceOfDestinationForSelection } from "@/lib/procurement-select/service" +import { TAX_CONDITIONS, getTaxConditionName } from "@/lib/tax-conditions/types" import { useToast } from "@/hooks/use-toast" interface BiddingConditionsEditProps { @@ -207,7 +208,12 @@ export function BiddingConditionsEdit({ biddingId, initialConditions }: BiddingC
-

{conditions.taxConditions || "미설정"}

+

+ {conditions.taxConditions + ? getTaxConditionName(conditions.taxConditions) + : "미설정" + } +

@@ -308,15 +314,30 @@ export function BiddingConditionsEdit({ biddingId, initialConditions }: BiddingC
- setConditions(prev => ({ + onValueChange={(value) => setConditions(prev => ({ ...prev, - taxConditions: e.target.value + taxConditions: value }))} - /> + > + + + + + {TAX_CONDITIONS.length > 0 ? ( + TAX_CONDITIONS.map((condition) => ( + + {condition.name} + + )) + ) : ( + + 데이터를 불러오는 중... + + )} + +
diff --git a/lib/bidding/list/create-bidding-dialog.tsx b/lib/bidding/list/create-bidding-dialog.tsx index a25dd363..cb91a984 100644 --- a/lib/bidding/list/create-bidding-dialog.tsx +++ b/lib/bidding/list/create-bidding-dialog.tsx @@ -69,6 +69,7 @@ import { Checkbox } from "@/components/ui/checkbox" import { createBidding, type CreateBiddingInput } from "@/lib/bidding/service" import { getIncotermsForSelection, getPaymentTermsForSelection, getPlaceOfShippingForSelection, getPlaceOfDestinationForSelection } from "@/lib/procurement-select/service" +import { TAX_CONDITIONS } from "@/lib/tax-conditions/types" import { createBiddingSchema, type CreateBiddingSchema @@ -1475,14 +1476,24 @@ export function CreateBiddingDialog() { - setBiddingConditions(prev => ({ + onValueChange={(value) => setBiddingConditions(prev => ({ ...prev, - taxConditions: e.target.value + taxConditions: value }))} - /> + > + + + + + {TAX_CONDITIONS.map((condition) => ( + + {condition.name} + + ))} + +
diff --git a/lib/bidding/vendor/partners-bidding-pre-quote.tsx b/lib/bidding/vendor/partners-bidding-pre-quote.tsx index 6b9f956b..8a157c5f 100644 --- a/lib/bidding/vendor/partners-bidding-pre-quote.tsx +++ b/lib/bidding/vendor/partners-bidding-pre-quote.tsx @@ -42,6 +42,7 @@ import { import { getBiddingConditions } from '../service' import { getPriceAdjustmentFormByBiddingCompanyId } from '../detail/service' import { getIncotermsForSelection, getPaymentTermsForSelection, getPlaceOfShippingForSelection, getPlaceOfDestinationForSelection } from '@/lib/procurement-select/service' +import { TAX_CONDITIONS, getTaxConditionName } from '@/lib/tax-conditions/types' import { PrItemsPricingTable } from './components/pr-items-pricing-table' import { SimpleFileUpload } from './components/simple-file-upload' import { @@ -823,7 +824,12 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin
-

{biddingConditions.taxConditions || "미설정"}

+

+ {biddingConditions.taxConditions + ? getTaxConditionName(biddingConditions.taxConditions) + : "미설정" + } +

@@ -1068,12 +1074,21 @@ export function PartnersBiddingPreQuote({ biddingId, companyId }: PartnersBiddin
- setResponseData({...responseData, taxConditionsResponse: e.target.value})} - placeholder={biddingConditions?.taxConditions ? `참고: ${biddingConditions.taxConditions}` : "세금조건에 대한 의견을 입력하세요"} - /> + onValueChange={(value) => setResponseData({...responseData, taxConditionsResponse: value})} + > + + + + + {TAX_CONDITIONS.map((condition) => ( + + {condition.name} + + ))} + +
diff --git a/lib/general-contracts/detail/general-contract-basic-info.tsx b/lib/general-contracts/detail/general-contract-basic-info.tsx index 882ed8b2..d891fe63 100644 --- a/lib/general-contracts/detail/general-contract-basic-info.tsx +++ b/lib/general-contracts/detail/general-contract-basic-info.tsx @@ -15,6 +15,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { GeneralContract } from '@/db/schema' import { ContractDocuments } from './general-contract-documents' import { getPaymentTermsForSelection, getIncotermsForSelection, getPlaceOfShippingForSelection, getPlaceOfDestinationForSelection } from '@/lib/procurement-select/service' +import { TAX_CONDITIONS, getTaxConditionName } from '@/lib/tax-conditions/types' interface ContractBasicInfoProps { contractId: number @@ -46,7 +47,7 @@ export function ContractBasicInfo({ contractId }: ContractBasicInfoProps) { 작업후: { enabled: false, period: 0, maxPeriod: 0 }, 기타: { enabled: false, period: 0, maxPeriod: 0 }, }, - contractAmount: null, + contractAmount: null as number | null, currency: 'KRW', linkedPoNumber: '', linkedBidNumber: '', @@ -57,7 +58,7 @@ export function ContractBasicInfo({ contractId }: ContractBasicInfoProps) { paymentAfterDelivery: {} as any, paymentTerm: '', taxType: '', - liquidatedDamages: false, + liquidatedDamages: false as boolean, liquidatedDamagesPercent: '', deliveryType: '', deliveryTerm: '', @@ -130,7 +131,7 @@ export function ContractBasicInfo({ contractId }: ContractBasicInfoProps) { 작업후: { enabled: false, period: 0, maxPeriod: 0 }, 기타: { enabled: false, period: 0, maxPeriod: 0 }, }, - contractAmount: contractData?.contractAmount || null as number | null, + contractAmount: contractData?.contractAmount || null, currency: contractData?.currency || 'KRW', linkedPoNumber: contractData?.linkedPoNumber || '', linkedBidNumber: contractData?.linkedBidNumber || '', @@ -141,7 +142,7 @@ export function ContractBasicInfo({ contractId }: ContractBasicInfoProps) { paymentAfterDelivery: paymentAfterDelivery || {} as any, paymentTerm: contractData?.paymentTerm || '', taxType: contractData?.taxType || '', - liquidatedDamages: contractData?.liquidatedDamages || false, + liquidatedDamages: Boolean(contractData?.liquidatedDamages), liquidatedDamagesPercent: contractData?.liquidatedDamagesPercent || '', deliveryType: contractData?.deliveryType || '', deliveryTerm: contractData?.deliveryTerm || '', @@ -838,13 +839,21 @@ export function ContractBasicInfo({ contractId }: ContractBasicInfoProps) {
- setFormData(prev => ({ ...prev, taxType: e.target.value }))} - placeholder="세금조건을 입력하세요" - className={errors.taxType ? 'border-red-500' : ''} - /> + onValueChange={(value) => setFormData(prev => ({ ...prev, taxType: value }))} + > + + + + + {TAX_CONDITIONS.map((condition) => ( + + {condition.name} + + ))} + + {errors.taxType && (

세금조건은 필수값입니다.

)} diff --git a/lib/general-contracts/service.ts b/lib/general-contracts/service.ts index 52301dae..2422706a 100644 --- a/lib/general-contracts/service.ts +++ b/lib/general-contracts/service.ts @@ -11,6 +11,7 @@ import { basicContract, basicContractTemplates } from '@/db/schema/basicContract import { vendors } from '@/db/schema/vendors' import { users } from '@/db/schema/users' import { projects } from '@/db/schema/projects' +import { items } from '@/db/schema/items' import { filterColumns } from '@/lib/filter-columns' import { saveDRMFile } from '@/lib/file-stroage' import { decryptWithServerAction } from '@/components/drm/drmUtils' @@ -1312,13 +1313,73 @@ export async function sendContractApprovalRequest( const contractId = newContract.id + // const items: { + // id: number; + // createdAt: Date; + // updatedAt: Date; + // contractId: number; + // itemCode: string | null; + // quantity: string | null; + // contractAmount: string | null; + // contractCurrency: string | null; + // contractDeliveryDate: string | null; + // specification: string | null; + // itemInfo: string | null; + // quantityUnit: string | null; + // totalWeight: string | null; + // weightUnit: string | null; + // contractUnitPrice: string | null; + // }[] + // contractItems 테이블에 품목 정보 저장 (general-contract-items가 있을 때만) if (contractSummary.items && contractSummary.items.length > 0) { - // 새 품목 추가 + const projectNo = contractSummary.basicInfo?.projectCode || contractSummary.basicInfo?.projectId?.toString() || 'NULL' + for (const item of contractSummary.items) { + let itemId: number + + // 1. items 테이블에서 itemCode로 기존 아이템 검색 + if (item.itemCode) { + // const existingItem = await db + // .select({ id: items.id }) + // .from(items) + // .where(and( + // eq(items.itemCode, item.itemCode), + // eq(items.ProjectNo, projectNo) + // )) + // .limit(1) + const existingItem = await db + .select({ id: items.id }) + .from(items) + .where( + eq(items.itemCode, item.itemCode) + ) + .limit(1) + + if (existingItem.length > 0) { + // 기존 아이템이 있으면 해당 ID 사용 + itemId = existingItem[0].id + } else { + // 기존 아이템이 없으면 새로 생성 + const newItem = await db.insert(items).values({ + ProjectNo: projectNo, + itemCode: item.itemCode, + itemName: item.itemInfo || item.description || item.itemCode, + packageCode: item.itemCode, + description: item.specification || item.description || '', + unitOfMeasure: item.quantityUnit || 'EA', + createdAt: new Date(), + updatedAt: new Date(), + }).returning({ id: items.id }) + + itemId = newItem[0].id + } + + + // 2. contractItems에 저장 await db.insert(contractItems).values({ contractId, - itemId: item.itemId || 2602, // 기본값 설정 + itemId: itemId, description: item.itemInfo || item.description || '', quantity: Math.floor(Number(item.quantity) || 1), // 정수로 변환 unitPrice: item.contractUnitPrice || item.unitPrice || 0, @@ -1327,6 +1388,10 @@ export async function sendContractApprovalRequest( totalLineAmount: item.contractAmount || item.totalLineAmount || 0, remark: item.remark || '', }) + }else{ + //아이템코드가 없으니 pass + continue + } } } @@ -1409,21 +1474,29 @@ export async function sendContractApprovalRequest( language: "ko", }, }) + // 계약 상태 업데이트 + await db.update(generalContracts) + .set({ + status: 'Contract Accept Request', + lastUpdatedAt: new Date() + }) + .where(eq(generalContracts.id, generalContractId)) + } catch (error) { console.error('계약승인요청 전송 오류:', error) - + } - //계약상태변경 - revalidatePath('/evcp/general-contracts') - revalidatePath('/evcp/general-contracts/detail') - revalidatePath('/evcp/general-contracts/detail/contract-approval-request-dialog') - revalidatePath('/evcp/general-contracts/detail/contract-approval-request-dialog') - return { - success: true, - message: '계약승인요청이 성공적으로 전송되었습니다.', - pdfPath: saveResult.publicPath - } + + revalidatePath('/evcp/general-contracts') + revalidatePath('/evcp/general-contracts/detail') + revalidatePath('/evcp/general-contracts/detail/contract-approval-request-dialog') + + return { + success: true, + message: '계약승인요청이 성공적으로 전송되었습니다.', + pdfPath: saveResult.publicPath + } } catch (error: any) { console.error('계약승인요청 전송 오류:', error) diff --git a/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx b/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx index 143d08f3..f0c69d8b 100644 --- a/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx +++ b/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx @@ -31,6 +31,7 @@ import { getPlaceOfShippingForSelection, getPlaceOfDestinationForSelection } from "@/lib/procurement-select/service" +import { TAX_CONDITIONS } from "@/lib/tax-conditions/types" import { toast } from "sonner" interface CommercialTermsFormProps { @@ -427,11 +428,53 @@ export default function CommercialTermsForm({ rfqDetail, rfq }: CommercialTermsF
- + + + + + + + + + 검색 결과가 없습니다. + + {TAX_CONDITIONS.map((condition) => ( + setValue("vendorTaxCode", condition.code)} + > +
+ {condition.code} + - + {condition.name} + +
+
+ ))} +
+
+
+
+
diff --git a/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx b/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx index 7de8cfa4..af38ff45 100644 --- a/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx +++ b/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx @@ -46,6 +46,7 @@ import { cn } from "@/lib/utils"; import { toast } from "sonner"; import { updateVendorConditionsBatch } from "../service"; import { Badge } from "@/components/ui/badge"; +import { TAX_CONDITIONS } from "@/lib/tax-conditions/types"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Alert, AlertDescription } from "@/components/ui/alert"; @@ -770,10 +771,60 @@ export function BatchUpdateConditionsDialog({
- + + + + + + + + { + e.stopPropagation(); + const target = e.currentTarget; + target.scrollTop += e.deltaY; + }} + > + 검색 결과가 없습니다. + + {TAX_CONDITIONS.map((condition) => ( + field.onChange(condition.code)} + > +
+ {condition.code} + - + {condition.name} + +
+
+ ))} +
+
+
+
+
diff --git a/lib/tax-conditions/types.ts b/lib/tax-conditions/types.ts new file mode 100644 index 00000000..f77eba2d --- /dev/null +++ b/lib/tax-conditions/types.ts @@ -0,0 +1,56 @@ +// 세금조건 타입 정의 +export interface TaxCondition { + code: string + name: string +} + +// 세금조건 상수 +export const TAX_CONDITIONS: TaxCondition[] = [ + { code: 'V1', name: '[V1] 매입 세금계산서/일반' }, + { code: 'V2', name: '[V2] 매입 세금계산서/고정자산' }, + { code: 'V4', name: '[V4] 매입 세금계산서/영세율' }, + { code: 'V5', name: '[V5] 매입 세금계산서/불공제/교제비' }, + { code: 'V6', name: '[V6] 매입 세금계산서/불공제/업무무관' }, + { code: 'V7', name: '[V7] 매입 세금계산서/불공제/8인승이하 차량' }, + { code: 'V8', name: '[V8] 매입 세금계산서/불공제/면세사업' }, + { code: 'V9', name: '[V9] 매입 세금계산서/불공제/토지관련' }, + { code: 'VB', name: '[VB] 매입 계산서' }, + { code: 'VC', name: '[VC] 매입 매입자발행세금계산서' }, + { code: 'VV', name: '[VV] 매입 Non Taxable' }, + { code: 'X1', name: '[X1] 매입 신용카드/국내/공제' }, + { code: 'X2', name: '[X2] 매입 신용카드/해외사용분/불공제' }, + { code: 'X3', name: '[X3] 매입 신용카드/국내/불공제' }, + { code: 'X4', name: '[X4] 매입 현금영수증/공제' }, + { code: 'X5', name: '[X5] 매입 현금영수증/불공제/간이과특자' }, + { code: 'X7', name: '[X7] 매입 신용카드/해외사용분/환급용' }, + { code: 'Y1', name: '[Y1] 매입 대리납부' }, + { code: 'Y2', name: '[Y2] 매입 의제매입세액 6/106' }, + { code: 'Y3', name: '[Y3] 매입 계산서/의제매입대상' }, + { code: 'Y4', name: '[Y4] 매입 신용카드/면세/의제매입대상' }, + { code: 'Y5', name: '[Y5] 매입 현금영수증/면세/의제매입대상' }, + { code: 'YA', name: '[YA] 매입 세금계산서/일반/공통매입 안분대상' }, + { code: 'YB', name: '[YB] 매입 세금계산서/고정자산/공통매입 안분대상' }, + { code: 'YC', name: '[YC] 매입 신용카드/공제/공통매입 안분대상' }, + { code: 'YD', name: '[YD] 매입 현금영수증/공제/공통매입 안분대상' }, + { code: 'YE', name: '[YE] 매입 불공제/공통매입세' }, + { code: 'YF', name: '[YF] 매입 세금계산서/프로젝트/공통매입 안분대상' }, + { code: 'YG', name: '[YG] 매입 세금계산서/광고대행/메모' }, + { code: 'YH', name: '[YH] 매입 세금계산서/영세율/광고대행/메모' }, + { code: 'YI', name: '[YI] 매입 계산서/광고대행/메모' }, + { code: 'YJ', name: '[YJ] 매입 Invoice/영세율/광고대행/메모' }, + { code: 'Z1', name: '[Z1] 매입 금전등록기 및 간이영수증(3만원 이하)' }, + { code: 'Z2', name: '[Z2] 매입 금전등록기 및 간이영수증(3만원 초과/읍면이외)' }, + { code: 'Z3', name: '[Z3] 매입 금전등록기 및 간이영수증(3만원 초과/읍면지역)' }, +] + +// 세금조건 코드로 이름 찾기 +export const getTaxConditionName = (code: string): string => { + const condition = TAX_CONDITIONS.find(item => item.code === code) + return condition ? condition.name : code +} + +// 세금조건 이름으로 코드 찾기 +export const getTaxConditionCode = (name: string): string => { + const condition = TAX_CONDITIONS.find(item => item.name === name) + return condition ? condition.code : name +} -- cgit v1.2.3