diff --git a/A00_Common/group_workflow.ts b/A00_Common/group_workflow.ts index 5217705d5..9bdda4a68 100644 --- a/A00_Common/group_workflow.ts +++ b/A00_Common/group_workflow.ts @@ -37,11 +37,11 @@ const FLOWS: readonly GroupFlow[] = [ "DE_Step_StabGround", "DE_Step_StabSection", "DE_Step_Stab3D", - "DE_Step_StabReport", "DE_Step_StabRock", "DE_Step_StabArea", + "DE_Step_StabReport", ], - icons: ["๐Ÿชจ", "๐Ÿ“", "๐ŸงŠ", "๐Ÿ“„", "โ›ฐ๏ธ", "๐Ÿ—บ๏ธ"], + icons: ["๐Ÿชจ", "๐Ÿ“", "๐ŸงŠ", "โ›ฐ๏ธ", "๐Ÿ—บ๏ธ", "๐Ÿ“„"], }, ]; diff --git a/B01_Dashboard/B01_Dashboard_Summary.py b/B01_Dashboard/B01_Dashboard_Summary.py index eeec1b3a3..d9f7f04cd 100644 --- a/B01_Dashboard/B01_Dashboard_Summary.py +++ b/B01_Dashboard/B01_Dashboard_Summary.py @@ -26,7 +26,7 @@ THUMB_POINTS = 100 STAGE_NAMES: dict[str, tuple[str, ...]] = { "ROAD": ("ํŒŒ์ผ์ž…๋ ฅ", "์ „์ฒ˜๋ฆฌ", "๋…ธ์„ ์„ค๊ณ„", "ํšก๋‹จ์„ค๊ณ„", "์ƒ์„ธ์„ค๊ณ„", "์ˆ˜๋Ÿ‰์‚ฐ์ถœ", "์›๊ฐ€๊ณ„์‚ฐ"), "PLAN": ("๊ธฐ์ค€ ์ง€์ •", "๋…ธ์„  ๋ถ„์„", "ํ™•์ • ยท ๋‚ด๋ณด๋‚ด๊ธฐ"), - "STABILITY": ("์ง€๋ฐ˜ ์กฐ๊ฑด", "2D ํ•ด์„", "3D ํ•ด์„", "๋ณด๊ณ ์„œ", "์•”๋ฐ˜ ํ•ด์„", "๊ด‘์—ญ ์œ„ํ—˜"), + "STABILITY": ("์ง€๋ฐ˜ ์กฐ๊ฑด", "2D ํ•ด์„", "3D ํ•ด์„", "์•”๋ฐ˜ ํ•ด์„", "๊ด‘์—ญ ์œ„ํ—˜", "๋ณด๊ณ ์„œ"), } diff --git a/B01_Dashboard/B01_Dashboard_UI_Projects.ts b/B01_Dashboard/B01_Dashboard_UI_Projects.ts index 313aeac3f..ba7ae0660 100644 --- a/B01_Dashboard/B01_Dashboard_UI_Projects.ts +++ b/B01_Dashboard/B01_Dashboard_UI_Projects.ts @@ -66,7 +66,7 @@ export function projectTable(projects: ProjectItem[], currentUser: DashboardUser ); } -/** ๋‹จ๊ณ„ ๋ง‰๋Œ€ โ€” ์ข…๋ฅ˜๋ณ„ ๋‹จ๊ณ„(ROAD = B03~B09 ยท PLAN = D02~D04 ยท STABILITY = E02~E05) ยท ๋ˆ„๋ฆ„ = ๊ทธ ๋‹จ๊ณ„ ํ™”๋ฉด */ +/** ๋‹จ๊ณ„ ๋ง‰๋Œ€ โ€” ์ข…๋ฅ˜๋ณ„ ๋‹จ๊ณ„(ROAD = B03~B09 ยท PLAN = D02~D04 ยท STABILITY = E02 ยท E03 ยท E04 ยท E06 ยท E07 ยท E05) ยท ๋ˆ„๋ฆ„ = ๊ทธ ๋‹จ๊ณ„ ํ™”๋ฉด */ export function workflow(project: ProjectItem): HTMLElement { const group = groupFlowOf(project.project_kind); const routes = group?.routes ?? WORKFLOW_STEP_ROUTES; diff --git a/E05_StabReport/E05_StabReport_Build.py b/E05_StabReport/E05_StabReport_Build.py index bf0407150..acb6df786 100644 --- a/E05_StabReport/E05_StabReport_Build.py +++ b/E05_StabReport/E05_StabReport_Build.py @@ -194,7 +194,7 @@ def gather(project_root: Path) -> dict[str, Any]: project_root = Path(project_root) r2 = _result2d(project_root) if not r2 or not r2.get("stations"): - raise MissingInput("2D ํ•ด์„ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค โ€” E03 ์—์„œ [ํ•ด์„] ๋’ค ๋งŒ๋“œ์„ธ์š”.") + raise MissingInput("2D ํ•ด์„ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค โ€” E02 ์—์„œ [ํ•ด์„ ์‹œ์ž‘] ๋’ค ๋งŒ๋“œ์„ธ์š”.") root = source.source_dir(project_root) db = source.db_snapshot(root) ground = _ground(project_root) diff --git a/common_util/common_util_workflow_state.py b/common_util/common_util_workflow_state.py index b6bb4f882..aaf1657e1 100644 --- a/common_util/common_util_workflow_state.py +++ b/common_util/common_util_workflow_state.py @@ -20,11 +20,37 @@ STAGE_KEYS = [ ] -# ์ข…๋ฅ˜๋ณ„ ๋‹จ๊ณ„(์š”๊ตฌ 345) โ€” ROAD = B03~B09 ยท PLAN = D02~D04 ยท STABILITY = E02~E06. +# ์ข…๋ฅ˜๋ณ„ ๋‹จ๊ณ„(์š”๊ตฌ 345 ยท 390) โ€” ROAD = B03~B09 ยท PLAN = D02~D04 ยท +# STABILITY = E02 ยท E03 ยท E04 ยท E06 ยท E07 ยท E05(๋ณด๊ณ ์„œ ๋งจ ๋’ค ยท ์˜› ํ–‰ = 022 SQL ๋กœ ๋‹ค์‹œ ๊น–). STAGE_KEYS_BY_KIND = { "ROAD": STAGE_KEYS, "PLAN": ["PLAN_BASE", "PLAN_ANALYSIS", "PLAN_CONFIRM"], - "STABILITY": ["STAB_GROUND", "STAB_SECTION", "STAB_3D", "STAB_REPORT", "STAB_ROCK"], + "STABILITY": [ + "STAB_GROUND", + "STAB_SECTION", + "STAB_3D", + "STAB_ROCK", + "STAB_AREA", + "STAB_REPORT", + ], +} + +# E ์ฒด์ธ ๋‹จ๊ณ„ ์—ด์‡ (`E02_StabGround_Chain.STEPS` + ์ง€๋ฐ˜) โ†’ ๋‹จ๊ณ„ ํ–‰ ์—ด์‡  +STAB_STEP_STAGE_KEYS = { + "ground": "STAB_GROUND", + "2d": "STAB_SECTION", + "3d": "STAB_3D", + "rock": "STAB_ROCK", + "area": "STAB_AREA", + "report": "STAB_REPORT", +} +# ์ฒด์ธ ๋‹จ๊ณ„ ์ƒํƒœ โ†’ ๋‹จ๊ณ„ ํ–‰ ์ƒํƒœ(๊ฑด๋„ˆ๋œ€ = ์ €์žฅ๋ณธ์ด ์ž…๋ ฅ๊ณผ ๊ฐ™์Œ ยท ๋Š์Œ = ์˜› ๊ฒฐ๊ณผ ๋‚ก์•˜์„ ์ˆ˜ ์žˆ์Œ) +STAB_STEP_STATES = { + "run": "IN_PROGRESS", + "done": "COMPLETE", + "skip": "COMPLETE", + "error": "FAILED", + "cancel": "STALE", } @@ -208,6 +234,66 @@ async def mark_stage( ) +async def mark_stab_step( + project_id: str, + step: str, + state: str, + message: str | None = None, + cursor: aiomysql.DictCursor | None = None, +) -> None: + """E ์•ˆ์ •์„ฑํ‰๊ฐ€ ๋‹จ๊ณ„ ์ƒํƒœ ํ•œ ๊ณณ(์š”๊ตฌ 390 ยท PLAN 565-1) โ€” ๋Œ€์‹œ๋ณด๋“œ ๋ง‰๋Œ€ ์ƒ‰. + + `step` = `STAB_STEP_STAGE_KEYS` ์—ด์‡  ยท `state` = `STAB_STEP_STATES` ์—ด์‡ (์ฒด์ธ ๋‚ฑ๋ง ๊ทธ๋Œ€๋กœ) ยท + `message` = ์‹คํŒจ ๊นŒ๋‹ญ(ํ–‰ ์นธ ๊ธธ์ด๋กœ ์ž๋ฆ„). ์ปค์„œ ์—†์œผ๋ฉด ์ œ ํŠธ๋žœ์žญ์…˜ ยท DB ์—†์œผ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ํ•จ. + ๋’ค ๋‹จ๊ณ„๋ฅผ STALE ๋กœ ๋Œ๋ฆฌ์ง€ ์•Š์Œ(์ฒด์ธ์ด ์ฐจ๋ก€๋กœ ๋‹ค ๋Ž). + """ + stage_key = STAB_STEP_STAGE_KEYS[step] + row_state = STAB_STEP_STATES[state] + if cursor is None: + from config import config_db + + if config_db.db_pool is None: + return + async with config_db.get_db_pool().acquire() as connection: + await connection.begin() + try: + async with connection.cursor() as own: + await mark_stab_step(project_id, step, state, message, own) + await connection.commit() + except Exception: + await connection.rollback() + raise + return + now = datetime.utcnow() + trimmed = (message or "").strip()[:_STAGE_MESSAGE_MAX_LENGTH] or None + await cursor.execute( + """ + UPDATE project_workflow_stages + SET state = %s, + progress_percent = CASE WHEN %s = 'COMPLETE' THEN 100 + WHEN %s = 'IN_PROGRESS' THEN 0 ELSE progress_percent END, + message = CASE WHEN message LIKE 'DEV_UNLOCK:%%' THEN message ELSE %s END, + started_at = CASE WHEN %s = 'IN_PROGRESS' THEN %s ELSE started_at END, + completed_at = CASE WHEN %s = 'COMPLETE' THEN %s + WHEN %s = 'IN_PROGRESS' THEN NULL ELSE completed_at END + WHERE project_id = %s AND stage_key = %s + """, + ( + row_state, + row_state, + row_state, + trimmed if row_state == "FAILED" else None, + row_state, + now, + row_state, + now, + row_state, + project_id, + stage_key, + ), + ) + + async def update_stage_progress( cursor: aiomysql.DictCursor, project_id: str, stage_no: int, progress: int ) -> None: diff --git a/config/config_frontend.ts b/config/config_frontend.ts index e256ad5e0..c65487277 100644 --- a/config/config_frontend.ts +++ b/config/config_frontend.ts @@ -141,14 +141,14 @@ export const PLAN_STEP_ROUTES = [ ROUTES.D04_PLAN_CONFIRM, ] as const; -/** E ์•ˆ์ •์„ฑํ‰๊ฐ€ ๋‹จ๊ณ„ ์ฃผ์†Œ(E02 โ†’ E06 ยท ๊ด‘์—ญ E07 ยท ์ƒ์„ฑ E01 ์€ ๋‹จ๊ณ„ ๋ฐ–) */ +/** E ์•ˆ์ •์„ฑํ‰๊ฐ€ ๋‹จ๊ณ„ ์ฃผ์†Œ(E02 โ†’ E04 ยท ์•”๋ฐ˜ E06 ยท ๊ด‘์—ญ E07 ยท ๋ณด๊ณ ์„œ E05 ๋งจ ๋’ค ยท ์ƒ์„ฑ E01 ์€ ๋‹จ๊ณ„ ๋ฐ–) */ export const STAB_STEP_ROUTES = [ ROUTES.E02_STAB_GROUND, ROUTES.E03_STAB_SECTION, ROUTES.E04_STAB_3D, - ROUTES.E05_STAB_REPORT, ROUTES.E06_STAB_ROCK, ROUTES.E07_STAB_AREA, + ROUTES.E05_STAB_REPORT, ] as const; export type RouteKey = keyof typeof ROUTES; diff --git a/db_management/022_stab_stages_six.sql b/db_management/022_stab_stages_six.sql new file mode 100644 index 000000000..f0a50ee73 --- /dev/null +++ b/db_management/022_stab_stages_six.sql @@ -0,0 +1,31 @@ +-- 022_stab_stages_six.sql +-- ์•ˆ์ •์„ฑํ‰๊ฐ€ ๋‹จ๊ณ„ ํ–‰ ์—ฌ์„ฏ ยท ๋ณด๊ณ ์„œ ๋งจ ๋’ค (์š”๊ตฌ์„œ 390 3์žฅ 5 ยท PLAN 565-1) +-- ์ฐจ๋ก€ = STAB_GROUND 0 ยท STAB_SECTION 1 ยท STAB_3D 2 ยท STAB_ROCK 3 ยท STAB_AREA 4 ยท STAB_REPORT 5 +-- ์˜› ํ–‰(๋‹ค์„ฏ ยท REPORT 3 ยท ROCK 4)์€ ์—ด์‡ ๋กœ ๋ฒˆํ˜ธ๋งŒ ๋ฐ”๊ฟˆ(์ƒํƒœ ๊ทธ๋Œ€๋กœ) ยท ์—†๋Š” ๊ด‘์—ญ ํ–‰ = NOT_STARTED ยท ๋‹ค์‹œ ๋Œ๋ ค๋„ ๊ฐ™์Œ + +USE aislo_db; + +-- 1) ์˜› ๋ฒˆํ˜ธ ๋น„์ผœ ๋‘ (uq_project_stage ๊ฒน์นจ ํ”ผํ•จ) +UPDATE project_workflow_stages w +JOIN projects p ON p.id = w.project_id +SET w.stage_no = w.stage_no + 100 +WHERE p.project_kind = 'STABILITY' AND w.stage_no < 100; + +-- 2) ์—ด์‡ ๋กœ ์ƒˆ ๋ฒˆํ˜ธ +UPDATE project_workflow_stages w +JOIN projects p ON p.id = w.project_id +SET w.stage_no = CASE w.stage_key + WHEN 'STAB_GROUND' THEN 0 + WHEN 'STAB_SECTION' THEN 1 + WHEN 'STAB_3D' THEN 2 + WHEN 'STAB_ROCK' THEN 3 + WHEN 'STAB_AREA' THEN 4 + WHEN 'STAB_REPORT' THEN 5 +END +WHERE p.project_kind = 'STABILITY' AND w.stage_no >= 100; + +-- 3) ์—†๋Š” ๊ด‘์—ญ ํ–‰ +INSERT IGNORE INTO project_workflow_stages (project_id, stage_no, stage_key, state, progress_percent) +SELECT p.id, 4, 'STAB_AREA', 'NOT_STARTED', 0 +FROM projects p +WHERE p.project_kind = 'STABILITY'; diff --git a/resources/tester/helper_280_2_step_nav.cjs b/resources/tester/helper_280_2_step_nav.cjs index 01d9efb6d..799750e73 100644 --- a/resources/tester/helper_280_2_step_nav.cjs +++ b/resources/tester/helper_280_2_step_nav.cjs @@ -119,7 +119,15 @@ const STUBS = { ROUTES, API_BASE_URL: "", PLAN_STEP_ROUTES: ["d02-plan-base", "d03-plan-analysis", "d04-plan-confirm"], - STAB_STEP_ROUTES: ["e02-stab-ground", "e03-stab-section", "e04-stab-3d", "e05-stab-report"], + // 565-1 โ€” ๋ณด๊ณ ์„œ(E05) ๋งจ ๋’ค + STAB_STEP_ROUTES: [ + "e02-stab-ground", + "e03-stab-section", + "e04-stab-3d", + "e06-stab-rock", + "e07-stab-area", + "e05-stab-report", + ], }, "./ui_template_locale": { t: (key) => key }, "./ui_template_modal": { openModal: () => undefined }, @@ -202,6 +210,7 @@ const ALL = [ "d04-plan-confirm", "e01-stab-register", "e02-stab-ground", + "e07-stab-area", "e05-stab-report", ]; const rows = {}; diff --git a/resources/tester/test_280_2_step_nav.py b/resources/tester/test_280_2_step_nav.py index 20a92d14c..2e8ae7087 100644 --- a/resources/tester/test_280_2_step_nav.py +++ b/resources/tester/test_280_2_step_nav.py @@ -43,6 +43,14 @@ def test_529_1_D_E_๋ฌถ์Œ_์•ˆ์—์„œ๋งŒ_์•ž๋’ค(results): assert rows["d04-plan-confirm"]["plain"]["disabled"][:3] == [False, False, True] assert rows["e02-stab-ground"]["plain"]["disabled"][:3] == [False, True, False] assert rows["e05-stab-report"]["plain"]["disabled"][:3] == [False, False, True] + # 565-1 โ€” ๊ด‘์—ญ(E07) ๋’ค์— ๋ณด๊ณ ์„œ ยท ์ง„์งœ ์ฐจ๋ก€๋„ E05 ๊ฐ€ ๋ + assert rows["e07-stab-area"]["plain"]["disabled"][:3] == [False, False, False] + config = (ROOT / "config" / "config_frontend.ts").read_text(encoding="utf-8") + stab = config.split("export const STAB_STEP_ROUTES = [")[1].split("]")[0] + assert [s.strip() for s in stab.split(",") if s.strip()][-2:] == [ + "ROUTES.E07_STAB_AREA", + "ROUTES.E05_STAB_REPORT", + ] @pytest.mark.parametrize("shape", ["helpRow", "plain"]) diff --git a/resources/tester/test_528_project_groups.py b/resources/tester/test_528_project_groups.py index 9f76ff31f..907fe4cce 100644 --- a/resources/tester/test_528_project_groups.py +++ b/resources/tester/test_528_project_groups.py @@ -19,7 +19,12 @@ from A07_Register.A07_Register_Schema import RegisterRequest from common_util import common_util_auth as auth from common_util import common_util_auth_repository as auth_repo from common_util.common_util_project_kind import KIND_STAGE_DIRS, init_kind_storage -from common_util.common_util_workflow_state import initialize_project_stages +from common_util.common_util_workflow_state import ( + STAB_STEP_STAGE_KEYS, + STAGE_KEYS_BY_KIND, + initialize_project_stages, + mark_stab_step, +) from config.config_system import PROJECT_STORAGE_STAGE_DIRS from D01_PlanRegister import D01_PlanRegister_Router as d01 from E01_StabRegister import E01_StabRegister_Router as e01 @@ -261,13 +266,38 @@ def test_register_ignores_sent_company(): assert "company_name" not in data -@pytest.mark.parametrize("kind,count", [("ROAD", 7), ("PLAN", 3), ("STABILITY", 5)]) +@pytest.mark.parametrize("kind,count", [("ROAD", 7), ("PLAN", 3), ("STABILITY", 6)]) def test_stage_seed_by_kind(kind, count): log = [] asyncio.run(initialize_project_stages(FakeCursor({}, log), "p1", kind)) assert len(log) == count if kind == "PLAN": assert [a[2] for _, a in log] == ["PLAN_BASE", "PLAN_ANALYSIS", "PLAN_CONFIRM"] + if kind == "STABILITY": # 565-1 โ€” ๋ณด๊ณ ์„œ ๋งจ ๋’ค + assert [a[2] for _, a in log][3:] == ["STAB_ROCK", "STAB_AREA", "STAB_REPORT"] + + +def test_565_1_stab_step_marks_by_key(): + """์ฒด์ธ ๋‚ฑ๋ง โ†’ ํ–‰ ์ƒํƒœ ยท ์—ด์‡ ๋กœ ์ฐพ์Œ ยท ์‹คํŒจ ๊นŒ๋‹ญ๋งŒ ๋‚จ๊น€ ยท ๋ชจ๋ฅด๋Š” ๋‚ฑ๋ง ๊ฑฐ์ ˆ.""" + log = [] + for step, state, message in ( + ("3d", "run", None), + ("3d", "skip", None), + ("area", "error", "x" * 300), + ("report", "cancel", None), + ): + asyncio.run(mark_stab_step("p1", step, state, message, FakeCursor({}, log))) + rows = [(args[0], args[3], args[-1]) for _, args in log] + assert rows == [ + ("IN_PROGRESS", None, "STAB_3D"), + ("COMPLETE", None, "STAB_3D"), + ("FAILED", "x" * 200, "STAB_AREA"), + ("STALE", None, "STAB_REPORT"), + ] + assert all("stage_key = %s" in sql for sql, _ in log) + assert set(STAB_STEP_STAGE_KEYS.values()) == set(STAGE_KEYS_BY_KIND["STABILITY"]) + with pytest.raises(KeyError): + asyncio.run(mark_stab_step("p1", "2d", "weird", None, FakeCursor({}, log))) def test_storage_dirs_allowed(): diff --git a/ui_template/ui_template_locale_e05.ts b/ui_template/ui_template_locale_e05.ts index 1e1b979b0..27dc95934 100644 --- a/ui_template/ui_template_locale_e05.ts +++ b/ui_template/ui_template_locale_e05.ts @@ -10,8 +10,8 @@ import type { LocaleEntry } from "./ui_template_locale_common"; export const ui_locales_e05 = { /* --- E05 ๋ณด๊ณ ์„œ --- */ E05_Note: [ - "ํ‘œ์ง€ ยท ๊ฐœ์š”๋Š” ์‚ฌ์šฉ์ž ๋ชซ โ€” [๋ณด๊ณ ์„œ ๊ฐฑ์‹ ] ๋•Œ ๊ทธ๋Œ€๋กœ ๋‘ก๋‹ˆ๋‹ค. ๋ชฉ์ฐจ ยท ์ž…๋ ฅ์กฐ๊ฑด ยท ์ธก์ ํ‘œ ยท ๋Œ€ํ‘œ๋‹จ๋ฉด ยท 3D ยท ๊ด‘์—ญ ยท ๋ณด๊ฐ• ยท ๋ถ€๋ก์€ ํ•ด์„ ๊ฒฐ๊ณผ๋กœ ๋‹ค์‹œ ์”๋‹ˆ๋‹ค.", - "Cover and summary are yours โ€” kept on [Refresh report]. Contents, conditions, stations, sections, 3D, area, reinforcement and appendix sheets are rewritten from results.", + "ํ‘œ์ง€ ยท ๊ฐœ์š”๋Š” ์‚ฌ์šฉ์ž ๋ชซ โ€” [๋ณด๊ณ ์„œ ๊ฐฑ์‹ ] ๋•Œ ๊ทธ๋Œ€๋กœ ๋‘ก๋‹ˆ๋‹ค. ๋ชฉ์ฐจ ยท ์ž…๋ ฅ์กฐ๊ฑด ยท ์ธก์ ํ‘œ ยท ๋Œ€ํ‘œ๋‹จ๋ฉด ยท 3D ยท ๊ด‘์—ญ ยท ๋ณด๊ฐ• ยท ์•”๋ฐ˜ ยท ํ™•๋ฅ  ยท ๋ถ€๋ก์€ ํ•ด์„ ๊ฒฐ๊ณผ๋กœ ๋‹ค์‹œ ์”๋‹ˆ๋‹ค.", + "Cover and summary are yours โ€” kept on [Refresh report]. Contents, conditions, stations, sections, 3D, area, reinforcement, rock, probability and appendix sheets are rewritten from results.", ], E05_Btn_Refresh: ["๋ณด๊ณ ์„œ ๊ฐฑ์‹ ", "Refresh report"], E05_Btn_Xlsx: ["์—‘์…€", "Excel"],