구매관리 발주서 폼 저장/삭제 + general 양식 다이얼로그

- 백엔드: POST /api/purchase/order-form/save (마스터 55 + 파트 40 컬럼 UPSERT + 삭제파트 cascade, 트랜잭션, wace mergePurchaseOrderMaster/PartInfo 1:1)
- 백엔드: DELETE /api/purchase/order-form/:objid (마스터+파트 cascade)
- 프론트 lib/api: initOrderForm/getOrderForm/saveOrderForm/deleteOrderForm
- 프론트 컴포넌트: PurchaseOrderGeneralFormDialog — wace purchaseOrderFormPopup_general.jsp 1:1 (좌 5필드/우 담당자 + 회사정보 2줄/그리드 10컬럼/총공급가액/보안문구)
- /purchase/proposal "발주서생성" 버튼 활성화 → 품의서 자동 채움 다이얼로그
- /purchase/order 행 클릭/체크 → 수정/삭제 액션 + 다이얼로그
- Radix UI 접근성: DialogTitle/Description sr-only 처리

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hjjeong
2026-05-19 13:06:18 +09:00
parent 6a0705c5b7
commit e48bd83667
7 changed files with 1088 additions and 4 deletions
@@ -73,6 +73,40 @@ export async function getPurchaseOrderForm(req: AuthenticatedRequest, res: Respo
}
}
/**
* POST /api/purchase/order-form/save
* 마스터 + 파트 UPSERT + 삭제 처리. 트랜잭션.
*/
export async function savePurchaseOrderForm(req: AuthenticatedRequest, res: Response) {
try {
const payload = req.body as formSvc.SaveOrderFormPayload;
if (!payload || !payload.master) {
return res.status(400).json({ success: false, message: "master 가 필요해요" });
}
const writer = String(req.user?.userId ?? "");
const result = await formSvc.savePurchaseOrderForm(payload, writer);
return res.json({ success: true, data: result });
} catch (e: any) {
logger.error("발주서 폼 저장 실패", { error: e.message });
return res.status(500).json({ success: false, message: e.message });
}
}
/**
* DELETE /api/purchase/order-form/:objid
*/
export async function deletePurchaseOrderForm(req: AuthenticatedRequest, res: Response) {
try {
const objid = String(req.params.objid ?? "").trim();
if (!objid) return res.status(400).json({ success: false, message: "objid required" });
await formSvc.deletePurchaseOrderForm(objid);
return res.json({ success: true });
} catch (e: any) {
logger.error("발주서 폼 삭제 실패", { error: e.message });
return res.status(500).json({ success: false, message: e.message });
}
}
export async function getSuppliers(_req: AuthenticatedRequest, res: Response) {
try {
const data = await svc.listSupplierOptions();
+4 -2
View File
@@ -21,8 +21,10 @@ router.get("/project-status", ctrl.getProjectStatus); // 프로젝트별
router.get("/order-list", ctrl.getPurchaseOrderList); // 발주서관리 (wace purchaseOrderMasterList_new 1:1)
// 발주서 폼 (general 양식, wace purchaseOrderFormPopup_general.do 1:1)
router.get("/order-form/init", ctrl.getPurchaseOrderFormInit); // 품의서에서 자동 채움
router.get("/order-form/:objid", ctrl.getPurchaseOrderForm); // 수정/조회
router.get ("/order-form/init", ctrl.getPurchaseOrderFormInit); // 품의서에서 자동 채움
router.post ("/order-form/save", ctrl.savePurchaseOrderForm); // 마스터+파트 UPSERT
router.get ("/order-form/:objid", ctrl.getPurchaseOrderForm); // 수정/조회
router.delete("/order-form/:objid", ctrl.deletePurchaseOrderForm); // 삭제 cascade
// 공통 옵션
router.get("/options/suppliers", ctrl.getSuppliers);
@@ -21,12 +21,24 @@
import { getPool } from "../database/db";
import { logger } from "../utils/logger";
import { createObjId } from "../utils/objidUtil";
export interface OrderFormInitResult {
master: Record<string, any>;
parts: Record<string, any>[];
}
export interface SaveOrderFormPayload {
master: Record<string, any>;
parts: Record<string, any>[];
deletedPartObjids?: string[];
}
export interface SaveOrderFormResult {
objid: string;
purchase_order_no: string;
}
/**
* GET /api/purchase/order-form/init?proposal_objid=...
*
@@ -238,3 +250,336 @@ function toNum(v: any): number {
function formatKorDate(d: Date): string {
return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, "0")}${String(d.getDate()).padStart(2, "0")}`;
}
/** "1,234,567.50" / 1234567.5 / null → '1234567.5' (numeric 컬럼은 모두 varchar 저장) */
function strNum(v: any): string {
if (v == null) return "";
const s = String(v).replace(/,/g, "").trim();
return s;
}
/** pick from various-cased keys (UPPER/lower/snake). 클라이언트가 어느 케이스로 보내든 수용. */
function pick(o: Record<string, any>, ...keys: string[]): any {
for (const k of keys) {
if (o == null) continue;
if (k in o && o[k] !== undefined && o[k] !== null) return o[k];
const lk = k.toLowerCase();
if (lk in o && o[lk] !== undefined && o[lk] !== null) return o[lk];
const uk = k.toUpperCase();
if (uk in o && o[uk] !== undefined && o[uk] !== null) return o[uk];
}
return "";
}
/**
* POST /api/purchase/order-form/save
*
* wace `PurchaseOrderService.savePurchaseOrder_new` (1472-1817) 의 단일-마스터 분기 1:1.
* 1) 마스터 UPSERT (mergePurchaseOrderMaster — INSERT ... ON CONFLICT(OBJID) DO UPDATE)
* 2) 파트 UPSERT (mergePurchaseOrderPartInfo — INSERT ... ON CONFLICT(OBJID) DO UPDATE)
* 3) deletedPartObjids 일괄 DELETE
* 트랜잭션. 동시발주(MULTI_*)는 RPS 미사용이라 제외.
*
* payload.master.objid 가 비어 있으면 신규 채번(createObjId).
* payload.parts[i].objid 가 비어 있으면 신규 채번.
*/
export async function savePurchaseOrderForm(
payload: SaveOrderFormPayload,
writerId: string,
): Promise<SaveOrderFormResult> {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
const m = payload.master ?? {};
let masterObjid = String(pick(m, "objid") || "").trim();
const isNew = !masterObjid;
if (isNew) masterObjid = createObjId();
// 마스터 UPSERT — wace mergePurchaseOrderMaster (purchaseOrder.xml 530-714) 1:1
// PURCHASE_ORDER_NO: 신규일 때만 채번 서브쿼리, 수정은 기존 값 유지.
const writer = writerId || String(pick(m, "writer") || "");
const params: any[] = [
/* 1 */ masterObjid,
/* 2 */ String(pick(m, "po_client_id") || ""),
/* 3 */ String(pick(m, "category_cd") || ""),
/* 4 */ String(pick(m, "product_group") || ""),
/* 5 */ String(pick(m, "product") || ""),
/* 6 */ String(pick(m, "product_code") || ""),
/* 7 */ String(pick(m, "my_company_objid") || ""),
/* 8 */ String(pick(m, "partner_objid") || ""),
/* 9 */ String(pick(m, "delivery_date") || ""),
/* 10 */ String(pick(m, "delivery_place") || ""),
/* 11 */ String(pick(m, "effective_date") || ""),
/* 12 */ String(pick(m, "payment_terms") || ""),
/* 13 */ String(pick(m, "remark") || ""),
/* 14 */ String(pick(m, "request_content") || ""),
/* 15 */ writer,
/* 16 */ String(pick(m, "status") || "create"),
/* 17 */ String(pick(m, "sales_request_objid") || ""),
/* 18 */ String(pick(m, "sales_mng_user_id") || ""),
/* 19 */ String(pick(m, "sales_mng_user_id2") || ""),
/* 20 */ String(pick(m, "form_type") || "general"),
/* 21 */ String(pick(m, "title") || ""),
/* 22 */ String(pick(m, "purchase_date") || ""),
/* 23 */ String(pick(m, "contract_mgmt_objid") || ""),
/* 24 */ String(pick(m, "type") || ""),
/* 25 */ String(pick(m, "inspect_method") || ""),
/* 26 */ String(pick(m, "total_price_txt") || ""),
/* 27 */ String(pick(m, "total_price_txt_all") || ""),
/* 28 */ String(pick(m, "vat_method") || ""),
/* 29 */ strNum(pick(m, "total_supply_unit_price")),
/* 30 */ strNum(pick(m, "total_supply_price")),
/* 31 */ strNum(pick(m, "total_real_supply_price")),
/* 32 */ strNum(pick(m, "discount_price")),
/* 33 */ strNum(pick(m, "total_price")),
/* 34 */ strNum(pick(m, "total_price_all")),
/* 35 */ strNum(pick(m, "nego_rate")),
/* 36 */ String(pick(m, "supply_bus_no") || ""),
/* 37 */ String(pick(m, "supply_user_name") || ""),
/* 38 */ String(pick(m, "supply_user_hp") || ""),
/* 39 */ String(pick(m, "supply_user_tel") || ""),
/* 40 */ String(pick(m, "supply_user_fax") || ""),
/* 41 */ String(pick(m, "supply_user_email") || ""),
/* 42 */ String(pick(m, "supply_addr") || ""),
/* 43 */ String(pick(m, "unit_code") || ""),
/* 44 */ String(pick(m, "bom_report_objid") || ""),
/* 45 */ String(pick(m, "order_type_cd") || ""),
/* 46 */ String(pick(m, "multi_yn") || "N"),
/* 47 */ String(pick(m, "multi_master_yn") || "N"),
/* 48 */ String(pick(m, "multi_master_objid") || ""),
/* 49 */ String(pick(m, "delivery_plan_date") || ""),
/* 50 */ String(pick(m, "delivery_plan_qty") || ""),
/* 51 */ String(pick(m, "purchase_order_no_org") || ""),
/* 52 */ String(pick(m, "shipment") || ""),
/* 53 */ String(pick(m, "packing") || ""),
/* 54 */ String(pick(m, "validity") || ""),
/* 55 */ String(pick(m, "attn_to") || ""),
];
const upsertMasterSql = `
INSERT INTO PURCHASE_ORDER_MASTER (
OBJID, PO_CLIENT_ID, PURCHASE_ORDER_NO, CATEGORY_CD, PRODUCT_GROUP, PRODUCT, PRODUCT_CODE,
MY_COMPANY_OBJID, PARTNER_OBJID, DELIVERY_DATE, DELIVERY_PLACE, EFFECTIVE_DATE, PAYMENT_TERMS,
REMARK, REQUEST_CONTENT, WRITER, REGDATE, STATUS, SALES_REQUEST_OBJID, SALES_MNG_USER_ID,
SALES_MNG_USER_ID2, FORM_TYPE, TITLE, PURCHASE_DATE, CONTRACT_MGMT_OBJID, TYPE, INSPECT_METHOD,
TOTAL_PRICE_TXT, TOTAL_PRICE_TXT_ALL, VAT_METHOD, TOTAL_SUPPLY_UNIT_PRICE, TOTAL_SUPPLY_PRICE,
TOTAL_REAL_SUPPLY_PRICE, DISCOUNT_PRICE, TOTAL_PRICE, TOTAL_PRICE_ALL, NEGO_RATE,
SUPPLY_BUS_NO, SUPPLY_USER_NAME, SUPPLY_USER_HP, SUPPLY_USER_TEL, SUPPLY_USER_FAX,
SUPPLY_USER_EMAIL, SUPPLY_ADDR, UNIT_CODE, BOM_REPORT_OBJID, ORDER_TYPE_CD, MULTI_YN,
MULTI_MASTER_YN, MULTI_MASTER_OBJID, DELIVERY_PLAN_DATE, DELIVERY_PLAN_QTY,
PURCHASE_ORDER_NO_ORG, SHIPMENT, PACKING, VALIDITY, ATTN_TO
) VALUES (
$1, $2,
(SELECT 'RPS'||TO_CHAR(NOW(),'YY')||'-'||TO_CHAR(NOW(),'MMDD')||'-'||
LPAD((COALESCE(MAX(CASE WHEN PURCHASE_ORDER_NO LIKE 'RPS'||TO_CHAR(NOW(),'YY-MMDD')||'-%'
THEN SPLIT_PART(PURCHASE_ORDER_NO,'-',3) ELSE '0' END)::INTEGER,0)+1)::TEXT, 2, '0')
FROM PURCHASE_ORDER_MASTER),
$3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15,
NOW(), $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
) ON CONFLICT (OBJID) DO UPDATE SET
PO_CLIENT_ID = EXCLUDED.PO_CLIENT_ID,
CATEGORY_CD = EXCLUDED.CATEGORY_CD,
PRODUCT_GROUP = EXCLUDED.PRODUCT_GROUP,
PRODUCT = EXCLUDED.PRODUCT,
PRODUCT_CODE = EXCLUDED.PRODUCT_CODE,
MY_COMPANY_OBJID = EXCLUDED.MY_COMPANY_OBJID,
PARTNER_OBJID = EXCLUDED.PARTNER_OBJID,
DELIVERY_DATE = EXCLUDED.DELIVERY_DATE,
DELIVERY_PLACE = EXCLUDED.DELIVERY_PLACE,
EFFECTIVE_DATE = EXCLUDED.EFFECTIVE_DATE,
PAYMENT_TERMS = EXCLUDED.PAYMENT_TERMS,
REMARK = EXCLUDED.REMARK,
REQUEST_CONTENT = EXCLUDED.REQUEST_CONTENT,
WRITER = EXCLUDED.WRITER,
STATUS = EXCLUDED.STATUS,
SALES_MNG_USER_ID = EXCLUDED.SALES_MNG_USER_ID,
SALES_MNG_USER_ID2 = EXCLUDED.SALES_MNG_USER_ID2,
FORM_TYPE = CASE WHEN EXCLUDED.FORM_TYPE = '' THEN PURCHASE_ORDER_MASTER.FORM_TYPE ELSE EXCLUDED.FORM_TYPE END,
TITLE = EXCLUDED.TITLE,
PURCHASE_DATE = EXCLUDED.PURCHASE_DATE,
CONTRACT_MGMT_OBJID = EXCLUDED.CONTRACT_MGMT_OBJID,
TYPE = EXCLUDED.TYPE,
INSPECT_METHOD = EXCLUDED.INSPECT_METHOD,
TOTAL_PRICE_TXT = EXCLUDED.TOTAL_PRICE_TXT,
TOTAL_PRICE_TXT_ALL = EXCLUDED.TOTAL_PRICE_TXT_ALL,
VAT_METHOD = EXCLUDED.VAT_METHOD,
TOTAL_SUPPLY_UNIT_PRICE = EXCLUDED.TOTAL_SUPPLY_UNIT_PRICE,
TOTAL_SUPPLY_PRICE = EXCLUDED.TOTAL_SUPPLY_PRICE,
TOTAL_REAL_SUPPLY_PRICE = EXCLUDED.TOTAL_REAL_SUPPLY_PRICE,
DISCOUNT_PRICE = EXCLUDED.DISCOUNT_PRICE,
TOTAL_PRICE = EXCLUDED.TOTAL_PRICE,
TOTAL_PRICE_ALL = EXCLUDED.TOTAL_PRICE_ALL,
NEGO_RATE = EXCLUDED.NEGO_RATE,
SUPPLY_BUS_NO = EXCLUDED.SUPPLY_BUS_NO,
SUPPLY_USER_NAME = EXCLUDED.SUPPLY_USER_NAME,
SUPPLY_USER_HP = EXCLUDED.SUPPLY_USER_HP,
SUPPLY_USER_TEL = EXCLUDED.SUPPLY_USER_TEL,
SUPPLY_USER_FAX = EXCLUDED.SUPPLY_USER_FAX,
SUPPLY_USER_EMAIL = EXCLUDED.SUPPLY_USER_EMAIL,
SUPPLY_ADDR = EXCLUDED.SUPPLY_ADDR,
UNIT_CODE = EXCLUDED.UNIT_CODE,
BOM_REPORT_OBJID = EXCLUDED.BOM_REPORT_OBJID,
ORDER_TYPE_CD = EXCLUDED.ORDER_TYPE_CD,
DELIVERY_PLAN_DATE = EXCLUDED.DELIVERY_PLAN_DATE,
DELIVERY_PLAN_QTY = EXCLUDED.DELIVERY_PLAN_QTY,
MULTI_YN = EXCLUDED.MULTI_YN,
MULTI_MASTER_YN = EXCLUDED.MULTI_MASTER_YN,
PURCHASE_ORDER_NO_ORG = EXCLUDED.PURCHASE_ORDER_NO_ORG,
SHIPMENT = EXCLUDED.SHIPMENT,
PACKING = EXCLUDED.PACKING,
VALIDITY = EXCLUDED.VALIDITY,
ATTN_TO = EXCLUDED.ATTN_TO
RETURNING OBJID, PURCHASE_ORDER_NO`;
const upRes = await client.query(upsertMasterSql, params);
const savedNo: string = upRes.rows[0]?.purchase_order_no ?? "";
// 파트 UPSERT — wace mergePurchaseOrderPartInfo (1205-1325) 1:1
const parts = payload.parts ?? [];
for (const raw of parts) {
const p: Record<string, any> = raw ?? {};
let popObjid = String(pick(p, "objid") || "").trim();
if (!popObjid) popObjid = createObjId();
const popParams: any[] = [
/* 1 */ popObjid,
/* 2 */ masterObjid,
/* 3 */ String(pick(p, "part_objid") || ""), // bigint 캐스트 in SQL
/* 4 */ strNum(pick(p, "bom_qty")),
/* 5 */ strNum(pick(p, "qty", "order_qty")),
/* 6 */ strNum(pick(p, "order_qty")),
/* 7 */ strNum(pick(p, "partner_price")),
/* 8 */ String(pick(p, "remark") || ""),
/* 9 */ writer,
/* 10 */ String(pick(p, "status") || "create"),
/* 11 */ String(pick(p, "part_name") || ""),
/* 12 */ String(pick(p, "part_no") || ""),
/* 13 */ String(pick(p, "do_no") || ""),
/* 14 */ String(pick(p, "thickness") || ""),
/* 15 */ String(pick(p, "width") || ""),
/* 16 */ String(pick(p, "height") || ""),
/* 17 */ String(pick(p, "out_diameter") || ""),
/* 18 */ String(pick(p, "length") || ""),
/* 19 */ String(pick(p, "in_diameter") || ""),
/* 20 */ String(pick(p, "inven_total_qty") || ""),
/* 21 */ String(pick(p, "ld_part_objid") || ""),
/* 22 */ String(pick(p, "spec") || ""),
/* 23 */ String(pick(p, "maker") || ""),
/* 24 */ String(pick(p, "unit") || ""),
/* 25 */ strNum(pick(p, "supply_unit_price")),
/* 26 */ strNum(pick(p, "price1")),
/* 27 */ strNum(pick(p, "price2")),
/* 28 */ strNum(pick(p, "price3")),
/* 29 */ strNum(pick(p, "price4")),
/* 30 */ strNum(pick(p, "supply_unit_vat_price")),
/* 31 */ strNum(pick(p, "supply_unit_vat_sum_price")),
/* 32 */ strNum(pick(p, "total_order_qty")),
/* 33 */ strNum(pick(p, "stock_qty")),
/* 34 */ strNum(pick(p, "real_order_qty")),
/* 35 */ strNum(pick(p, "real_supply_price")),
/* 36 */ String(pick(p, "part_delivery_place") || ""),
/* 37 */ String(pick(p, "product_name") || ""),
/* 38 */ String(pick(p, "work_order_no") || ""),
/* 39 */ String(pick(p, "delivery_request_date") || ""),
/* 40 */ String(pick(p, "currency") || ""),
];
// part_objid 가 빈 문자열이면 NULL 로 — bigint 컬럼은 빈 문자열 INSERT 실패.
const partObjidParam = popParams[2] === "" ? null : popParams[2];
popParams[2] = partObjidParam;
await client.query(
`INSERT INTO PURCHASE_ORDER_PART (
OBJID, PURCHASE_ORDER_MASTER_OBJID, PART_OBJID, BOM_QTY, QTY, ORDER_QTY,
PARTNER_PRICE, REMARK, WRITER, REGDATE, STATUS,
PART_NAME, PART_NO, DO_NO, THICKNESS, WIDTH, HEIGHT, OUT_DIAMETER, LENGTH,
IN_DIAMETER, INVEN_TOTAL_QTY, LD_PART_OBJID, SPEC, MAKER, UNIT,
SUPPLY_UNIT_PRICE, PRICE1, PRICE2, PRICE3, PRICE4,
SUPPLY_UNIT_VAT_PRICE, SUPPLY_UNIT_VAT_SUM_PRICE, TOTAL_ORDER_QTY,
STOCK_QTY, REAL_ORDER_QTY, REAL_SUPPLY_PRICE,
PART_DELIVERY_PLACE, PRODUCT_NAME, WORK_ORDER_NO, DELIVERY_REQUEST_DATE, CURRENCY
) VALUES (
$1, $2, NULLIF($3::text,'')::bigint, $4, $5,
COALESCE(NULLIF($6,'')::numeric::integer, 0),
$7, $8, $9, NOW(), $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
) ON CONFLICT (OBJID) DO UPDATE SET
ORDER_QTY = COALESCE(NULLIF($6,'')::numeric::integer, 0),
PARTNER_PRICE = EXCLUDED.PARTNER_PRICE,
REMARK = EXCLUDED.REMARK,
STATUS = EXCLUDED.STATUS,
MODIFIER = EXCLUDED.WRITER,
UPDATE_DATE = NOW(),
LD_PART_OBJID = EXCLUDED.LD_PART_OBJID,
SPEC = EXCLUDED.SPEC,
MAKER = EXCLUDED.MAKER,
UNIT = EXCLUDED.UNIT,
SUPPLY_UNIT_PRICE = EXCLUDED.SUPPLY_UNIT_PRICE,
PRICE1 = EXCLUDED.PRICE1,
PRICE2 = EXCLUDED.PRICE2,
PRICE3 = EXCLUDED.PRICE3,
PRICE4 = EXCLUDED.PRICE4,
SUPPLY_UNIT_VAT_PRICE = EXCLUDED.SUPPLY_UNIT_VAT_PRICE,
SUPPLY_UNIT_VAT_SUM_PRICE = EXCLUDED.SUPPLY_UNIT_VAT_SUM_PRICE,
TOTAL_ORDER_QTY = EXCLUDED.TOTAL_ORDER_QTY,
STOCK_QTY = EXCLUDED.STOCK_QTY,
REAL_ORDER_QTY = EXCLUDED.REAL_ORDER_QTY,
REAL_SUPPLY_PRICE = EXCLUDED.REAL_SUPPLY_PRICE,
PART_DELIVERY_PLACE = EXCLUDED.PART_DELIVERY_PLACE,
PRODUCT_NAME = EXCLUDED.PRODUCT_NAME,
WORK_ORDER_NO = EXCLUDED.WORK_ORDER_NO,
DELIVERY_REQUEST_DATE = EXCLUDED.DELIVERY_REQUEST_DATE,
CURRENCY = EXCLUDED.CURRENCY`,
popParams,
);
}
// 삭제된 파트 정리 — 클라이언트가 보낸 deletedPartObjids 일괄 DELETE
const deletedIds = (payload.deletedPartObjids ?? []).filter((s) => !!s);
if (deletedIds.length > 0) {
await client.query(
`DELETE FROM PURCHASE_ORDER_PART
WHERE PURCHASE_ORDER_MASTER_OBJID = $1
AND OBJID = ANY($2::text[])`,
[masterObjid, deletedIds],
);
}
await client.query("COMMIT");
return { objid: masterObjid, purchase_order_no: savedNo };
} catch (e: any) {
await client.query("ROLLBACK");
logger.error("savePurchaseOrderForm 실패", { error: e.message, stack: e.stack });
throw e;
} finally {
client.release();
}
}
/**
* DELETE /api/purchase/order-form/:objid
*
* 마스터 1건 + 파트 cascade 삭제. wace `deletePurchaseOrderMaster` 의 단일판.
*/
export async function deletePurchaseOrderForm(objid: string): Promise<void> {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
await client.query(`DELETE FROM PURCHASE_ORDER_PART WHERE PURCHASE_ORDER_MASTER_OBJID = $1`, [objid]);
await client.query(`DELETE FROM PURCHASE_ORDER_MASTER WHERE OBJID = $1`, [objid]);
await client.query("COMMIT");
} catch (e: any) {
await client.query("ROLLBACK");
logger.error("deletePurchaseOrderForm 실패", { error: e.message });
throw e;
} finally {
client.release();
}
}