diff options
| author | joonhoekim <26rote@gmail.com> | 2025-09-05 11:59:38 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-09-05 11:59:38 +0000 |
| commit | e832a508e1b3c531fb3e1b9761e18e1b55e3d76a (patch) | |
| tree | b18c85a4f448f6a45984444f0f68c915dcc5ea22 /lib/vendors | |
| parent | 50adedf48ee4674ebe00f1ee72d93485183cdc51 (diff) | |
(김준회) RFQ 인터페이스 처리, 변수명 오타 수정(VendorPossibleMaterials), 협력업체 관리페이지 구매요구사항 반영
Diffstat (limited to 'lib/vendors')
| -rw-r--r-- | lib/vendors/repository.ts | 33 | ||||
| -rw-r--r-- | lib/vendors/service.ts | 91 | ||||
| -rw-r--r-- | lib/vendors/table/vendors-table-columns.tsx | 195 | ||||
| -rw-r--r-- | lib/vendors/validations.ts | 5 |
4 files changed, 260 insertions, 64 deletions
diff --git a/lib/vendors/repository.ts b/lib/vendors/repository.ts index c5362ccf..d2be43ca 100644 --- a/lib/vendors/repository.ts +++ b/lib/vendors/repository.ts @@ -2,7 +2,7 @@ import { and, eq, inArray, count, gt, AnyColumn, SQLWrapper, SQL} from "drizzle-orm"; import { PgTransaction } from "drizzle-orm/pg-core"; -import { VendorContact, vendorContacts, vendorItemsView, vendorMaterialsView, vendorPossibleItems, vendors, vendorsWithTypesView, type Vendor } from "@/db/schema/vendors"; +import { VendorContact, vendorContacts, vendorItemsView, vendorMaterialsView, vendorPossibleItems, vendorPossibleMaterials, vendors, vendorsWithTypesView, vendorsWithTypesAndMaterialsView, type Vendor } from "@/db/schema/vendors"; import db from '@/db/db'; import { items } from "@/db/schema/items"; import { rfqs,rfqItems, rfqEvaluations, vendorResponses } from "@/db/schema/rfq"; @@ -70,6 +70,33 @@ export async function countVendorsWithTypes( const res = await tx.select({ count: count() }).from(vendorsWithTypesView).where(where); return res[0]?.count ?? 0; } + +/** + * 새로운 확장 뷰를 사용하는 조회 함수 + */ +export async function selectVendorsWithTypesAndMaterials( + tx: PgTransaction<any, any, any>, + { where, orderBy, offset, limit }: SelectVendorsOptions +) { + return tx + .select() + .from(vendorsWithTypesAndMaterialsView) + .where(where ?? undefined) + .orderBy(...(orderBy ?? [])) + .offset(offset ?? 0) + .limit(limit ?? 20); +} + +/** + * 새로운 확장 뷰에 대한 COUNT 함수 + */ +export async function countVendorsWithTypesAndMaterials( + tx: PgTransaction<any, any, any>, + where?: any + ) { + const res = await tx.select({ count: count() }).from(vendorsWithTypesAndMaterialsView).where(where); + return res[0]?.count ?? 0; + } /** @@ -280,9 +307,9 @@ export async function insertVendorMaterial( ) { // returning() 사용 시 배열로 돌아오므로 [0]만 리턴 return tx - .insert(vendorPossibleMateirals) + .insert(vendorPossibleMaterials) .values(data) - .returning({ id: vendorPossibleMateirals.id, createdAt: vendorPossibleMateirals.createdAt }); + .returning({ id: vendorPossibleMaterials.id, createdAt: vendorPossibleMaterials.createdAt }); } export async function selectRfqHistory( diff --git a/lib/vendors/service.ts b/lib/vendors/service.ts index 5d790a6e..0c8254f2 100644 --- a/lib/vendors/service.ts +++ b/lib/vendors/service.ts @@ -2,7 +2,7 @@ import { revalidatePath, revalidateTag, unstable_noStore } from "next/cache"; import db from "@/db/db"; -import { vendorAttachments, VendorContact, vendorContacts, vendorDetailView, vendorItemsView, vendorMaterialsView, vendorPossibleItems, vendorPossibleMateirals, vendors, vendorsWithTypesView, vendorTypes, type Vendor } from "@/db/schema"; +import { vendorAttachments, VendorContact, vendorContacts, vendorDetailView, vendorItemsView, vendorMaterialsView, vendorPossibleItems, vendorPossibleMaterials, vendors, vendorsWithTypesView, vendorsWithTypesAndMaterialsView, vendorTypes, type Vendor } from "@/db/schema"; import logger from '@/lib/logger'; import * as z from "zod" import crypto from 'crypto'; @@ -17,8 +17,6 @@ import { unstable_cache } from "@/lib/unstable-cache"; import { getErrorMessage } from "@/lib/handle-error"; import { - selectVendors, - countVendors, insertVendor, updateVendor, updateVendors, groupByStatus, @@ -32,8 +30,8 @@ import { insertVendorItem, countRfqHistory, selectRfqHistory, - selectVendorsWithTypes, - countVendorsWithTypes, + selectVendorsWithTypesAndMaterials, + countVendorsWithTypesAndMaterials, countVendorMaterials, selectVendorMaterials, insertVendorMaterial, @@ -41,7 +39,6 @@ import { } from "./repository"; import type { - CreateVendorSchema, UpdateVendorSchema, GetVendorsSchema, GetVendorContactsSchema, @@ -52,7 +49,7 @@ import type { GetVendorMaterialsSchema, } from "./validations"; -import { asc, desc, ilike, inArray, and, or, gte, lte, eq, isNull, count, sql } from "drizzle-orm"; +import { asc, desc, ilike, inArray, and, or, eq, isNull, sql } from "drizzle-orm"; import { rfqItems, rfqs, vendorRfqView } from "@/db/schema/rfq"; import { sendEmail } from "../mail/sendEmail"; import { PgTransaction } from "drizzle-orm/pg-core"; @@ -60,12 +57,11 @@ import { items, materials } from "@/db/schema/items"; import { mfaTokens, roles, userRoles, users } from "@/db/schema/users"; import { getServerSession } from "next-auth"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; -import { contracts, contractsDetailView, projects, vendorPQSubmissions, vendorsLogs } from "@/db/schema"; -import { deleteFile, saveFile, saveBuffer } from "../file-stroage"; +import { contractsDetailView, projects, vendorPQSubmissions, vendorsLogs } from "@/db/schema"; +import { deleteFile, saveFile } from "../file-stroage"; import { basicContractTemplates } from "@/db/schema/basicContractDocumnet"; import { basicContract } from "@/db/schema/basicContractDocumnet"; import { headers } from 'next/headers'; -import { Router } from "lucide-react"; /* ----------------------------------------------------- 1) 조회 관련 ----------------------------------------------------- */ @@ -81,9 +77,9 @@ export async function getVendors(input: GetVendorsSchema) { try { const offset = (input.page - 1) * input.perPage; - // 1) 고급 필터 - vendors 대신 vendorsWithTypesView 사용 + // 1) 고급 필터 - 새로운 확장 뷰 사용 const advancedWhere = filterColumns({ - table: vendorsWithTypesView, + table: vendorsWithTypesAndMaterialsView, filters: input.filters, joinOperator: input.joinOperator, }); @@ -93,28 +89,32 @@ export async function getVendors(input: GetVendorsSchema) { if (input.search) { const s = `%${input.search}%`; globalWhere = or( - ilike(vendorsWithTypesView.vendorName, s), - ilike(vendorsWithTypesView.vendorCode, s), - ilike(vendorsWithTypesView.email, s), - ilike(vendorsWithTypesView.status, s), + ilike(vendorsWithTypesAndMaterialsView.vendorName, s), + ilike(vendorsWithTypesAndMaterialsView.vendorCode, s), + ilike(vendorsWithTypesAndMaterialsView.email, s), + ilike(vendorsWithTypesAndMaterialsView.status, s), // 추가: 업체 유형 검색 - ilike(vendorsWithTypesView.vendorTypeName, s) + ilike(vendorsWithTypesAndMaterialsView.vendorTypeName, s), + // 대표품목 검색 추가 + ilike(vendorsWithTypesAndMaterialsView.primaryMaterial1, s), + ilike(vendorsWithTypesAndMaterialsView.primaryMaterial2, s), + ilike(vendorsWithTypesAndMaterialsView.primaryMaterial3, s) ); } // 최종 where 결합 const finalWhere = and(advancedWhere, globalWhere); - // 간단 검색 (advancedTable=false) 시 예시 - const simpleWhere = and( - input.vendorName - ? ilike(vendorsWithTypesView.vendorName, `%${input.vendorName}%`) - : undefined, - input.status ? ilike(vendorsWithTypesView.status, input.status) : undefined, - input.country - ? ilike(vendorsWithTypesView.country, `%${input.country}%`) - : undefined - ); + // 간단 검색 (advancedTable=false) 시 예시 - 현재 미사용 + // const simpleWhere = and( + // input.vendorName + // ? ilike(vendorsWithTypesAndMaterialsView.vendorName, `%${input.vendorName}%`) + // : undefined, + // input.status ? ilike(vendorsWithTypesAndMaterialsView.status, input.status) : undefined, + // input.country + // ? ilike(vendorsWithTypesAndMaterialsView.country, `%${input.country}%`) + // : undefined + // ); // 실제 사용될 where const where = finalWhere; @@ -123,14 +123,14 @@ export async function getVendors(input: GetVendorsSchema) { const orderBy = input.sort.length > 0 ? input.sort.map((item) => - item.desc ? desc(vendorsWithTypesView[item.id]) : asc(vendorsWithTypesView[item.id]) + item.desc ? desc(vendorsWithTypesAndMaterialsView[item.id]) : asc(vendorsWithTypesAndMaterialsView[item.id]) ) - : [asc(vendorsWithTypesView.createdAt)]; + : [asc(vendorsWithTypesAndMaterialsView.createdAt)]; // 트랜잭션 내에서 데이터 조회 const { data, total } = await db.transaction(async (tx) => { - // 1) vendor 목록 조회 (view 사용) - const vendorsData = await selectVendorsWithTypes(tx, { + // 1) vendor 목록 조회 (새로운 확장 뷰 사용) + const vendorsData = await selectVendorsWithTypesAndMaterials(tx, { where, orderBy, offset, @@ -158,7 +158,7 @@ export async function getVendors(input: GetVendorsSchema) { ); // 3) 전체 개수 - const total = await countVendorsWithTypes(tx, where); + const total = await countVendorsWithTypesAndMaterials(tx, where); return { data: vendorsWithAttachments, total }; }); @@ -452,6 +452,7 @@ export async function modifyVendor( creditRating: input.creditRating, cashFlowRating: input.cashFlowRating, status: input.status, + isAssociationMember: input.isAssociationMember, }); // 3. 상태가 변경되었다면 로그 기록 @@ -1035,12 +1036,12 @@ export async function getMaterialsForVendor(vendorId: number) { }) .from(materials) .leftJoin( - vendorPossibleMateirals, - eq(materials.itemCode, vendorPossibleMateirals.itemCode) + vendorPossibleMaterials, + eq(materials.itemCode, vendorPossibleMaterials.itemCode) ) // vendorPossibleItems.vendorId가 이 vendorId인 행이 없는(즉 아직 등록되지 않은) 아이템만 .where( - isNull(vendorPossibleMateirals.id) // 또는 isNull(vendorPossibleItems.itemCode) + isNull(vendorPossibleMaterials.id) // 또는 isNull(vendorPossibleItems.itemCode) ) .orderBy(asc(materials.itemName)) @@ -1109,11 +1110,11 @@ export async function deleteVendorMaterial( }) await db - .delete(vendorPossibleMateirals) + .delete(vendorPossibleMaterials) .where( and( - eq(vendorPossibleMateirals.itemCode, validatedData.itemCode), - eq(vendorPossibleMateirals.vendorId, validatedData.vendorId) + eq(vendorPossibleMaterials.itemCode, validatedData.itemCode), + eq(vendorPossibleMaterials.vendorId, validatedData.vendorId) ) ) @@ -1148,16 +1149,16 @@ export async function updateVendorMaterial( await db.transaction(async (tx) => { // 기존 아이템 삭제 await tx - .delete(vendorPossibleMateirals) + .delete(vendorPossibleMaterials) .where( and( - eq(vendorPossibleMateirals.itemCode, validatedData.oldItemCode), - eq(vendorPossibleMateirals.vendorId, validatedData.vendorId) + eq(vendorPossibleMaterials.itemCode, validatedData.oldItemCode), + eq(vendorPossibleMaterials.vendorId, validatedData.vendorId) ) ) // 새 아이템 추가 - await tx.insert(vendorPossibleMateirals).values({ + await tx.insert(vendorPossibleMaterials).values({ vendorId: validatedData.vendorId, itemCode: validatedData.newItemCode, }) @@ -1186,11 +1187,11 @@ export async function removeVendorMaterials(input: { const validatedData = removeVendormaterialsSchema.parse(input) await db - .delete(vendorPossibleMateirals) + .delete(vendorPossibleMaterials) .where( and( - inArray(vendorPossibleMateirals.itemCode, validatedData.itemCodes), - eq(vendorPossibleMateirals.vendorId, validatedData.vendorId) + inArray(vendorPossibleMaterials.itemCode, validatedData.itemCodes), + eq(vendorPossibleMaterials.vendorId, validatedData.vendorId) ) ) diff --git a/lib/vendors/table/vendors-table-columns.tsx b/lib/vendors/table/vendors-table-columns.tsx index 0a5f066f..f2de179c 100644 --- a/lib/vendors/table/vendors-table-columns.tsx +++ b/lib/vendors/table/vendors-table-columns.tsx @@ -24,10 +24,17 @@ import { DropdownMenuSubTrigger, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header" import { useRouter } from "next/navigation" -import { VendorWithType, vendors, VendorWithAttachments } from "@/db/schema/vendors" +import { VendorWithTypeAndMaterials, vendors, VendorWithAttachments } from "@/db/schema/vendors" import { modifyVendor } from "../service" import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" import { vendorColumnsConfig } from "@/config/vendorColumnsConfig" @@ -49,22 +56,33 @@ type StatusDisplayMap = { type NextRouter = ReturnType<typeof useRouter>; interface GetColumnsProps { - setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<VendorWithType> | null>>; + setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<VendorWithTypeAndMaterials> | null>>; router: NextRouter; userId: number; } +// 권한 체크 헬퍼 함수들 (향후 실제 권한 시스템과 연동) +const checkEditAssociationPermission = (userId: number): boolean => { + // TODO: 실제 권한 체크 로직 구현 + // 예: 특정 역할(ADMIN, VENDOR_MANAGER 등)을 가진 사용자만 수정 가능 + // const userRoles = await getUserRoles(userId); + // return userRoles.includes('VENDOR_MANAGER') || userRoles.includes('ADMIN'); + + // 개발 중에는 모든 사용자가 수정 가능 + return true; +}; + /** * tanstack table 컬럼 정의 (중첩 헤더 버전) */ -export function getColumns({ setRowAction, router, userId }: GetColumnsProps): ColumnDef<VendorWithType>[] { +export function getColumns({ setRowAction, router, userId }: GetColumnsProps): ColumnDef<VendorWithTypeAndMaterials>[] { // ---------------------------------------------------------------- // 1) select 컬럼 (체크박스) // ---------------------------------------------------------------- - const selectColumn: ColumnDef<VendorWithType> = { + const selectColumn: ColumnDef<VendorWithTypeAndMaterials> = { id: "select", header: ({ table }) => ( <Checkbox @@ -85,7 +103,9 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C className="translate-y-0.5" /> ), - size: 40, + size: 50, + minSize: 50, + maxSize: 50, enableSorting: false, enableHiding: false, } @@ -93,7 +113,7 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C // ---------------------------------------------------------------- // 2) actions 컬럼 (Dropdown 메뉴) // ---------------------------------------------------------------- - const actionsColumn: ColumnDef<VendorWithType> = { + const actionsColumn: ColumnDef<VendorWithTypeAndMaterials> = { id: "actions", enableHiding: false, cell: function Cell({ row }) { @@ -157,11 +177,11 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C toast.promise( modifyVendor({ id: String(row.original.id), - status: value as VendorWithType["status"], + status: value as any, userId, vendorName: row.original.vendorName, // Required field from UpdateVendorSchema comment: `Status changed to ${value}` - }), + } as any), { loading: "Updating...", success: "Label updated", @@ -190,14 +210,16 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C </DropdownMenu> ) }, - size: 40, + size: 60, + minSize: 60, + maxSize: 60, } // ---------------------------------------------------------------- // 3) 일반 컬럼들을 "그룹"별로 묶어 중첩 columns 생성 // ---------------------------------------------------------------- // 3-1) groupMap: { [groupName]: ColumnDef<VendorWithType>[] } - const groupMap: Record<string, ColumnDef<VendorWithType>[]> = {} + const groupMap: Record<string, ColumnDef<VendorWithTypeAndMaterials>[]> = {} vendorColumnsConfig.forEach((cfg) => { // 만약 group가 없으면 "_noGroup" 처리 @@ -208,7 +230,7 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C } // child column 정의 - const childCol: ColumnDef<VendorWithType> = { + const childCol: ColumnDef<VendorWithTypeAndMaterials> = { accessorKey: cfg.id, enableResizing: true, header: ({ column }) => ( @@ -219,6 +241,9 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C group: cfg.group, type: cfg.type, }, + size: cfg.width || 150, + minSize: cfg.minWidth || 100, + maxSize: cfg.maxWidth, cell: ({ row, cell }) => { // Status 컬럼 렌더링 개선 - 아이콘과 더 선명한 배경색 사용 if (cfg.id === "status") { @@ -383,10 +408,148 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C return formatDate(dateVal, "KR") } + // 업체대표품목 컬럼들 처리 + if (cfg.id === "primaryMaterial1" || cfg.id === "primaryMaterial2" || cfg.id === "primaryMaterial3") { + const materialVal = cell.getValue() as string | null; + if (!materialVal) return <span className="text-gray-400">-</span>; + + return ( + <div className="text-sm font-medium whitespace-pre-line max-w-[200px]"> + {materialVal} + </div> + ); + } + + // 성조회가입여부 처리 - 권한에 따라 드롭다운 또는 읽기전용 배지 + if (cfg.id === "isAssociationMember") { + const [isUpdating, setIsUpdating] = React.useState(false); + const memberVal = row.original.isAssociationMember as string | null; + + const getDisplayText = (value: string | null) => { + switch (value) { + case "Y": return "가입"; + case "N": return "미가입"; + case "E": return "해당없음"; + default: return "-"; + } + }; + + const getBadgeStyle = (value: string | null) => { + switch (value) { + case "Y": + return "bg-green-100 text-green-800 border-green-300"; + case "N": + return "bg-red-100 text-red-800 border-red-300"; + case "E": + return "bg-gray-100 text-gray-800 border-gray-300"; + default: + return "bg-gray-100 text-gray-800 border-gray-300"; + } + }; + + // 권한 체크: 성조회가입여부 수정 권한이 있는지 확인 + const hasEditPermission = checkEditAssociationPermission(userId); + + const handleValueChange = async (newValue: string) => { + // "NONE" 값을 null로 변환 + const actualValue = newValue === "NONE" ? null : newValue; + + setIsUpdating(true); + try { + await modifyVendor({ + id: String(row.original.id), + isAssociationMember: actualValue, + userId, + vendorName: row.original.vendorName, + comment: `성조회가입여부 변경: ${getDisplayText(memberVal)} → ${getDisplayText(actualValue)}` + } as any); + + toast.success("성조회가입여부가 업데이트되었습니다."); + } catch (error) { + toast.error("업데이트에 실패했습니다: " + getErrorMessage(error)); + } finally { + setIsUpdating(false); + } + }; + + // 권한이 있는 경우 드롭다운 표시 + if (hasEditPermission) { + return ( + <Select + value={memberVal || "NONE"} + onValueChange={handleValueChange} + disabled={isUpdating} + > + <SelectTrigger className="w-[120px] h-8"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + <SelectItem value="NONE">-</SelectItem> + <SelectItem value="Y">가입</SelectItem> + <SelectItem value="N">미가입</SelectItem> + <SelectItem value="E">해당없음</SelectItem> + </SelectContent> + </Select> + ); + } + + // 권한이 없는 경우 읽기전용 배지 표시 + return ( + <Badge variant="outline" className={getBadgeStyle(memberVal)}> + {getDisplayText(memberVal)} + </Badge> + ); + } + + // 최근 발주 실적 컬럼들 처리 + if (cfg.id === "recentPoNumber") { + const poNumber = cell.getValue() as string | null; + if (!poNumber) return <span className="text-gray-400">-</span>; + + return ( + <div className="text-sm font-medium max-w-[150px] truncate" title={poNumber}> + {poNumber} + </div> + ); + } + + if (cfg.id === "recentPoOrderBy") { + const orderBy = cell.getValue() as string | null; + if (!orderBy) return <span className="text-gray-400">-</span>; + + return ( + <div className="text-sm font-medium whitespace-pre-line max-w-[150px]"> + {orderBy} + </div> + ); + } + + if (cfg.id === "recentPoDate") { + const poDate = cell.getValue() as Date | null; + if (!poDate) return <span className="text-gray-400">-</span>; + + return ( + <div className="text-sm"> + {formatDate(poDate, "KR")} + </div> + ); + } + + // TODO 컬럼들 (UI만) - 모두 "-" 표시 + if (cfg.id === "regularEvaluationGrade" || cfg.id === "faContract" || + cfg.id === "avlRegistration" || cfg.id === "regularVendorRegistration" || + cfg.id === "recentDeliveryNumber" || cfg.id === "recentDeliveryBy") { + return <span className="text-gray-400">-</span>; + } + + // 날짜 컬럼들 (TODO) + if (cfg.id === "recentDeliveryDate") { + return <span className="text-gray-400">-</span>; + } + // code etc... return row.getValue(cfg.id) ?? "" }, - minSize: 150 } groupMap[groupName].push(childCol) @@ -395,7 +558,7 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C // ---------------------------------------------------------------- // 3-2) groupMap에서 실제 상위 컬럼(그룹)을 만들기 // ---------------------------------------------------------------- - const nestedColumns: ColumnDef<VendorWithType>[] = [] + const nestedColumns: ColumnDef<VendorWithTypeAndMaterials>[] = [] // 순서를 고정하고 싶다면 group 순서를 미리 정의하거나 sort해야 함 // 여기서는 그냥 Object.entries 순서 @@ -414,7 +577,7 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C }) // attachments 컬럼 타입 문제 해결을 위한 타입 단언 - const attachmentsColumn: ColumnDef<VendorWithType> = { + const attachmentsColumn: ColumnDef<VendorWithTypeAndMaterials> = { id: "attachments", header: ({ column }) => ( <DataTableColumnHeaderSimple column={column} title="" /> @@ -441,7 +604,9 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C }, enableSorting: false, enableHiding: false, - minSize: 45, + size: 50, + minSize: 50, + maxSize: 50, }; diff --git a/lib/vendors/validations.ts b/lib/vendors/validations.ts index 2538e1c8..237dc846 100644 --- a/lib/vendors/validations.ts +++ b/lib/vendors/validations.ts @@ -121,7 +121,7 @@ export type CreditAgencyType = z.infer<typeof creditAgencyEnum>; export const updateVendorSchema = z.object({ vendorName: z.string().min(1, "업체명은 필수 입력사항입니다"), vendorCode: z.string().optional(), - address: z.string().min(1, "주소는 필수 입력사항입니다."), + address: z.string().optional(), // 부분 업데이트를 위해 optional로 변경 addressDetail: z.string().optional(), postalCode: z.string().optional(), country: z.string().optional(), @@ -130,6 +130,7 @@ export const updateVendorSchema = z.object({ website: z.string().url("유효한 URL을 입력해주세요").optional(), status: z.enum(vendors.status.enumValues).optional(), vendorTypeId: z.number().optional(), + isAssociationMember: z.string().optional(), // 성조회가입여부 추가 // Optional fields for buyer information buyerName: z.string().optional(), @@ -144,6 +145,8 @@ export const updateVendorSchema = z.object({ // evaluationScore: z.string().optional(), }); +export type UpdateVendorSchema = z.infer<typeof updateVendorSchema>; + const contactSchema = z.object({ contactName: z |
