Files
wace_rps/backend-node/src/routes/salesPurchaseRequestRoutes.ts
T
hjjeong 75f4ca8127 영업관리 구매요청 2메뉴 액션 완성 + SmartSelect 키보드 네비
- sales_request_part DDL 추출(운영 11133)→RPS(11134) 마이그레이션
- 백엔드 6 엔드포인트: 프로젝트 자동채움/M-BOM 품목/저장/품의서생성/SSO
  · 품의서 결재상신 Amaranth SSO (target_type=PROPOSAL, formId=1163)
- 프론트 다이얼로그 2개 (구매요청서작성 / 품의서생성 확인)
  · 프로젝트 선택→주문유형·제품구분·국내외·고객사·유무상 자동 채움
  · 행추가 시 M-BOM 품번 셀렉트→품명/공급업체/단가 자동 셋팅
- 공용 SmartSelect: ↑↓·Enter·Esc·Home·End·PageUp·Down 키보드 네비
- 그리드 delivery_request_date . → - 형식 정규화

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:01:26 +09:00

142 lines
5.6 KiB
TypeScript

// ============================================================
// 영업관리 > 구매요청서관리 / 품의서관리 라우트
// app.ts: app.use("/api/sales", salesPurchaseRequestRoutes)
// GET /api/sales/purchase-request — 구매요청서 그리드
// GET /api/sales/purchase-request/mbom-parts — 프로젝트별 M-BOM 품목 목록 (다이얼로그용)
// GET /api/sales/purchase-request/:objid — 단건 + 라인
// GET /api/sales/purchase-request/:objid/proposal-targets — 품의서 대상 품목
// POST /api/sales/purchase-request — 저장(UPSERT master + 라인 재생성)
// POST /api/sales/purchase-request/:objid/proposal — 품의서 생성
// GET /api/sales/purchase-proposal — 영업>품의서 그리드
// POST /api/sales/purchase-proposal/:objid/approval — Amaranth SSO 결재상신
// ============================================================
import { Router, Response } from "express";
import { authenticateToken } from "../middleware/authMiddleware";
import { AuthenticatedRequest } from "../types/auth";
import * as svc from "../services/salesPurchaseRequestService";
import { logger } from "../utils/logger";
import { AppError } from "../middleware/errorHandler";
const router = Router();
router.use(authenticateToken);
function parseFilter(q: Record<string, any>): svc.SalesPurchaseRequestFilter {
const f: svc.SalesPurchaseRequestFilter = { ...q };
if (q.page) f.page = Number(q.page);
if (q.page_size) f.page_size = Number(q.page_size);
return f;
}
async function run(
fn: (f: svc.SalesPurchaseRequestFilter) => Promise<any>,
req: AuthenticatedRequest,
res: Response,
name: string,
) {
try {
const data = await fn(parseFilter(req.query as Record<string, any>));
return res.json({ success: true, data });
} catch (e: any) {
logger.error(`${name} 실패`, { error: e.message });
return res.status(500).json({ success: false, message: e.message });
}
}
function handleError(res: Response, e: any, label: string) {
if (e instanceof AppError) {
return res.status(e.statusCode).json({ success: false, message: e.message });
}
logger.error(`${label} 실패`, { error: e?.message });
return res.status(500).json({ success: false, message: e?.message ?? `${label} 실패` });
}
router.get("/purchase-request", (req, res) => run(svc.listPurchaseRequestReg, req as AuthenticatedRequest, res, "구매요청서관리"));
router.get("/purchase-proposal", (req, res) => run(svc.listPurchaseRegProposal, req as AuthenticatedRequest, res, "영업>품의서관리"));
// 프로젝트 자동채움 정보 (주문유형/제품구분/국내외/고객사/유무상 + mbom_header_objid)
router.get("/purchase-request/project-info/:projectObjid", async (req, res) => {
try {
const info = await svc.getProjectAutoFillInfo(req.params.projectObjid);
return res.json({ success: true, data: info });
} catch (e: any) {
return handleError(res, e, "프로젝트 자동채움 정보");
}
});
// 프로젝트별 M-BOM 품목 (행추가 시 품번 셀렉트 옵션)
router.get("/purchase-request/mbom-parts", async (req, res) => {
try {
const projectObjid = String(req.query.project_objid ?? "");
if (!projectObjid) return res.json({ success: true, data: [] });
const rows = await svc.listMbomPartsForProject(projectObjid);
return res.json({ success: true, data: rows });
} catch (e: any) {
return handleError(res, e, "M-BOM 품목 조회");
}
});
// 단건 + 라인
router.get("/purchase-request/:objid", async (req, res) => {
try {
const detail = await svc.getPurchaseRequestDetail(req.params.objid);
return res.json({ success: true, data: detail });
} catch (e: any) {
return handleError(res, e, "구매요청서 상세");
}
});
// 품의서 대상 품목
router.get("/purchase-request/:objid/proposal-targets", async (req, res) => {
try {
const data = await svc.getProposalTargetParts(req.params.objid);
return res.json({ success: true, data });
} catch (e: any) {
return handleError(res, e, "품의서 대상 품목");
}
});
// 저장 (신규/수정 UPSERT)
router.post("/purchase-request", async (req, res) => {
const ar = req as AuthenticatedRequest;
try {
const userId = ar.user?.userId;
if (!userId) return res.status(401).json({ success: false, message: "인증 필요" });
const out = await svc.savePurchaseRequest(userId, req.body);
return res.json({ success: true, data: out });
} catch (e: any) {
return handleError(res, e, "구매요청서 저장");
}
});
// 품의서 생성
router.post("/purchase-request/:objid/proposal", async (req, res) => {
const ar = req as AuthenticatedRequest;
try {
const userId = ar.user?.userId;
if (!userId) return res.status(401).json({ success: false, message: "인증 필요" });
const out = await svc.createProposalFromPurchaseReg(userId, req.params.objid);
return res.json({ success: true, data: out });
} catch (e: any) {
return handleError(res, e, "품의서 생성");
}
});
// 결재상신 (Amaranth SSO)
router.post("/purchase-proposal/:objid/approval", async (req, res) => {
const ar = req as AuthenticatedRequest;
try {
const userId = ar.user?.userId;
if (!userId) return res.status(401).json({ success: false, message: "인증 필요" });
const out = await svc.startProposalApproval(userId, req.params.objid, {
approvalTitle: req.body?.approvalTitle,
subjectStr: req.body?.subjectStr,
});
return res.json({ success: true, data: out });
} catch (e: any) {
return handleError(res, e, "품의서 결재상신");
}
});
export default router;