경로: /COMPANY_16/quality/semi-product-inspection
반제품 입고에 대한 양품/불량 판정. 단일 테이블(pms_quality_semi_product_inspection)에 양품 마스터(DATA_TYPE='GOOD')와 불량 행('DEFECT')을 동일 INSPECTION_GROUP_ID 로 묶어 저장합니다.
diff --git a/docs/manual/screenshots/_capture.mjs b/docs/manual/screenshots/_capture.mjs
new file mode 100644
index 00000000..7e81a5e3
--- /dev/null
+++ b/docs/manual/screenshots/_capture.mjs
@@ -0,0 +1,152 @@
+/**
+ * RPS PLM 시스템 메뉴얼용 화면 캡처 스크립트.
+ *
+ * 사용:
+ * node docs/manual/screenshots/_capture.mjs
+ *
+ * 전제: rps-front (localhost:9781) + rps_backend (localhost:8090) 실행 중.
+ * Playwright 는 /Users/chpark/invyone/frontend/node_modules/playwright 를 재사용.
+ */
+import { chromium } from "/Users/chpark/invyone/frontend/node_modules/playwright/index.mjs";
+import { writeFileSync } from "fs";
+import path from "path";
+
+const FRONT = "http://localhost:9781";
+const OUT = "/Users/chpark/vexplor_rps/docs/manual/screenshots";
+const SHOTS = [];
+
+const wait = (ms) => new Promise((r) => setTimeout(r, ms));
+
+async function shot(page, name, description) {
+ const file = path.join(OUT, `${name}.png`);
+ await page.screenshot({ path: file, fullPage: false });
+ SHOTS.push({ name, description, file });
+ console.log(` 📸 ${name} — ${description}`);
+}
+
+(async () => {
+ const browser = await chromium.launch({ headless: true });
+ const ctx = await browser.newContext({ viewport: { width: 1600, height: 900 }, locale: "ko-KR" });
+ const page = await ctx.newPage();
+
+ try {
+ // 1. 로그인 화면
+ console.log("→ 로그인 화면");
+ await page.goto(`${FRONT}/login`, { waitUntil: "domcontentloaded", timeout: 60000 });
+ await page.waitForLoadState("networkidle", { timeout: 30000 });
+ await wait(800);
+ await shot(page, "01_login", "로그인 화면 (사용자 ID / 비밀번호 입력)");
+
+ // 2. 로그인 진행
+ console.log("→ 로그인");
+ await page.getByPlaceholder("사용자 ID").fill("wace");
+ await page.getByPlaceholder("비밀번호").fill("qlalfqjsgh11");
+ await Promise.all([
+ page.waitForURL((u) => !u.toString().includes("/login"), { timeout: 180000 }),
+ page.getByRole("button", { name: "로그인" }).click(),
+ ]);
+ await page.waitForLoadState("networkidle", { timeout: 120000 }).catch(() => {});
+ await wait(6000);
+ await shot(page, "02_main", "로그인 직후 메인 화면 — 좌측 모듈 메뉴 + 견적관리 자동 진입");
+
+ // 3. 견적관리 (이미 로그인 후 첫 화면이 견적관리)
+ console.log("→ 견적관리 그리드");
+ await page.goto(`${FRONT}/main`, { waitUntil: "networkidle", timeout: 60000 });
+ await wait(6000);
+ // 영업관리 → 견적관리 클릭
+ try {
+ await page.getByText("영업관리", { exact: true }).first().click({ timeout: 5000 });
+ await wait(500);
+ await page.getByText("견적관리", { exact: true }).first().click({ timeout: 5000 });
+ await wait(3000);
+ } catch { /* already there */ }
+ await shot(page, "03_estimate_list", "견적관리 — 12컬럼 그리드 + 검색 필터 + 결재상신 버튼");
+
+ // 4. 견적 행 선택 → 결재상신 버튼 활성화 상태 캡처
+ console.log("→ 견적 행 선택");
+ try {
+ const firstRow = page.locator("table tbody tr").first();
+ if (await firstRow.isVisible({ timeout: 5000 })) {
+ await firstRow.click({ timeout: 3000 });
+ await wait(800);
+ await shot(page, "04_estimate_selected", "견적 행 선택 시 상단 액션 버튼 활성화 (결재상신 강조)");
+ }
+ } catch (e) { console.warn(" ⚠ skip 행선택:", e.message); }
+
+ // 5. 외부 커넥션 관리 (REST API 탭)
+ console.log("→ 외부 커넥션 관리");
+ await page.goto(`${FRONT}/admin/automaticMng/exconList`, { waitUntil: "networkidle", timeout: 60000 });
+ await wait(6000);
+ await shot(page, "05_excon_db_tab", "외부 커넥션 관리 — 데이터베이스 연결 탭 (기본)");
+
+ // REST API 탭 클릭
+ try {
+ await page.getByText("REST API 연결", { exact: true }).click({ timeout: 5000 });
+ await wait(3000);
+ await shot(page, "06_excon_rest_tab", "외부 커넥션 관리 — REST API 연결 탭 (Amaranth 7종)");
+ } catch (e) { console.warn(" ⚠ skip REST 탭:", e.message); }
+
+ // 6. + 새 연결 버튼 클릭 → 등록 모달
+ console.log("→ REST API 등록 모달");
+ try {
+ const newBtn = page.getByRole("button", { name: /새 연결|새 REST|새로 추가|\+ 새/ }).first();
+ if (await newBtn.isVisible({ timeout: 5000 })) {
+ await newBtn.click({ timeout: 3000 });
+ await wait(2500);
+ await shot(page, "07_excon_rest_create", "REST API 신규 등록 모달 — 연결명/URL/인증 정보 입력");
+ // ESC 닫기
+ await page.keyboard.press("Escape");
+ await wait(500);
+ }
+ } catch (e) { console.warn(" ⚠ skip 등록모달:", e.message); }
+
+ // 7. 화면 관리 (Screen Designer 진입점)
+ console.log("→ 화면 관리");
+ await page.goto(`${FRONT}/admin/screenMng/screenMngList`, { waitUntil: "networkidle", timeout: 60000 });
+ await wait(6000);
+ await shot(page, "08_screen_mng_list", "화면 관리 — 등록된 화면 목록 (그룹 트리 + 카드)");
+
+ // 8. 화면 한개 클릭 → 설정 패널
+ try {
+ const firstCard = page.locator(".cursor-pointer, [role=button]").filter({ hasText: /화면|sales|order|관리/ }).first();
+ if (await firstCard.isVisible({ timeout: 4000 })) {
+ await firstCard.click({ timeout: 3000 });
+ await wait(2500);
+ await shot(page, "09_screen_detail", "화면 상세 — 메인 테이블 + 버튼 + 데이터 흐름 + 미리보기");
+ }
+ } catch (e) { console.warn(" ⚠ skip 화면상세:", e.message); }
+
+ // 9. 대시보드
+ console.log("→ 대시보드");
+ await page.goto(`${FRONT}/dashboard`, { waitUntil: "domcontentloaded", timeout: 30000 });
+ await wait(3000);
+ await shot(page, "10_dashboard", "사용자 대시보드 — 매출/수주 위젯 + 모니터링");
+
+ // 10. 품질관리 - 반제품검사 (데이터 보임)
+ console.log("→ 반제품검사");
+ await page.goto(`${FRONT}/COMPANY_16/quality/semi-product-inspection`, { waitUntil: "domcontentloaded", timeout: 30000 });
+ await wait(3500);
+ await shot(page, "11_quality_semi", "품질관리 — 반제품검사 (waceplm 운영 데이터 82행)");
+
+ // 11. 공정검사
+ console.log("→ 공정검사");
+ await page.goto(`${FRONT}/COMPANY_16/quality/process-inspection`, { waitUntil: "domcontentloaded", timeout: 30000 });
+ await wait(3500);
+ await shot(page, "12_quality_process", "품질관리 — 공정검사 (3행, 검사자 자동 산정)");
+
+ console.log("\n✅ 캡처 완료");
+ console.log(`총 ${SHOTS.length} 장`);
+ SHOTS.forEach((s) => console.log(` - ${s.name}.png : ${s.description}`));
+
+ // 인덱스 JSON 출력
+ writeFileSync(
+ path.join(OUT, "_index.json"),
+ JSON.stringify(SHOTS.map((s) => ({ name: s.name, description: s.description })), null, 2),
+ );
+ } catch (err) {
+ console.error("❌ 캡처 실패:", err.message);
+ process.exit(1);
+ } finally {
+ await browser.close();
+ }
+})();
diff --git a/docs/manual/screenshots/_capture_retry.mjs b/docs/manual/screenshots/_capture_retry.mjs
new file mode 100644
index 00000000..c55b43df
--- /dev/null
+++ b/docs/manual/screenshots/_capture_retry.mjs
@@ -0,0 +1,58 @@
+/**
+ * 부족한 캡처 재시도 — 02_main (로딩만 캡처됨), 06_excon_rest_tab (탭 클릭 실패).
+ */
+import { chromium } from "/Users/chpark/invyone/frontend/node_modules/playwright/index.mjs";
+import path from "path";
+
+const FRONT = "http://localhost:9781";
+const OUT = "/Users/chpark/vexplor_rps/docs/manual/screenshots";
+const wait = (ms) => new Promise((r) => setTimeout(r, ms));
+
+(async () => {
+ const browser = await chromium.launch({ headless: true });
+ const ctx = await browser.newContext({ viewport: { width: 1600, height: 900 }, locale: "ko-KR" });
+ const page = await ctx.newPage();
+
+ try {
+ // 로그인
+ await page.goto(`${FRONT}/login`, { waitUntil: "domcontentloaded", timeout: 60000 });
+ await page.waitForLoadState("networkidle", { timeout: 30000 }).catch(() => {});
+ await wait(2000);
+ await page.getByPlaceholder("사용자 ID").fill("wace");
+ await page.getByPlaceholder("비밀번호").fill("qlalfqjsgh11");
+ await page.getByRole("button", { name: "로그인" }).click({ timeout: 120000 });
+ await page.waitForURL((u) => !u.toString().includes("/login"), { timeout: 180000 });
+ await page.waitForLoadState("networkidle", { timeout: 120000 }).catch(() => {});
+ await wait(15000); // 메인 진입 후 견적관리 페이지까지 완전 렌더 대기
+ console.log("→ 메인 화면 재캡처");
+ await page.screenshot({ path: path.join(OUT, "02_main.png"), fullPage: false });
+
+ // REST API 탭 재시도
+ console.log("→ REST API 탭");
+ await page.goto(`${FRONT}/admin/automaticMng/exconList`, { waitUntil: "networkidle", timeout: 60000 });
+ await wait(6000);
+ // 다양한 셀렉터 시도
+ try {
+ await page.locator('[role="tab"]').filter({ hasText: "REST API" }).click({ timeout: 5000 });
+ } catch {
+ await page.locator('button:has-text("REST API 연결")').click({ timeout: 5000 });
+ }
+ await wait(4000);
+ await page.screenshot({ path: path.join(OUT, "06_excon_rest_tab.png"), fullPage: false });
+ console.log(" ✓ 06_excon_rest_tab.png");
+
+ // REST API 신규 등록 모달
+ console.log("→ + 새 연결 버튼");
+ try {
+ await page.getByRole("button", { name: /^\+/ }).first().click({ timeout: 4000 });
+ await wait(3000);
+ await page.screenshot({ path: path.join(OUT, "07_excon_rest_create.png"), fullPage: false });
+ console.log(" ✓ 07_excon_rest_create.png");
+ } catch (e) { console.warn(" ⚠ 모달 클릭 실패:", e.message); }
+
+ } catch (e) {
+ console.error("FAIL:", e.message);
+ } finally {
+ await browser.close();
+ }
+})();
diff --git a/docs/manual/screenshots/_index.json b/docs/manual/screenshots/_index.json
new file mode 100644
index 00000000..3bb20eb2
--- /dev/null
+++ b/docs/manual/screenshots/_index.json
@@ -0,0 +1,42 @@
+[
+ {
+ "name": "01_login",
+ "description": "로그인 화면 (사용자 ID / 비밀번호 입력)"
+ },
+ {
+ "name": "02_main",
+ "description": "로그인 직후 메인 화면 — 좌측 모듈 메뉴 + 견적관리 자동 진입"
+ },
+ {
+ "name": "03_estimate_list",
+ "description": "견적관리 — 12컬럼 그리드 + 검색 필터 + 결재상신 버튼"
+ },
+ {
+ "name": "04_estimate_selected",
+ "description": "견적 행 선택 시 상단 액션 버튼 활성화 (결재상신 강조)"
+ },
+ {
+ "name": "05_excon_db_tab",
+ "description": "외부 커넥션 관리 — 데이터베이스 연결 탭 (기본)"
+ },
+ {
+ "name": "08_screen_mng_list",
+ "description": "화면 관리 — 등록된 화면 목록 (그룹 트리 + 카드)"
+ },
+ {
+ "name": "09_screen_detail",
+ "description": "화면 상세 — 메인 테이블 + 버튼 + 데이터 흐름 + 미리보기"
+ },
+ {
+ "name": "10_dashboard",
+ "description": "사용자 대시보드 — 매출/수주 위젯 + 모니터링"
+ },
+ {
+ "name": "11_quality_semi",
+ "description": "품질관리 — 반제품검사 (waceplm 운영 데이터 82행)"
+ },
+ {
+ "name": "12_quality_process",
+ "description": "품질관리 — 공정검사 (3행, 검사자 자동 산정)"
+ }
+]
\ No newline at end of file