75f4ca8127
- 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>
142 lines
5.6 KiB
TypeScript
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;
|