Merge remote-tracking branch 'origin/dev' into sub_laptop_5
This commit is contained in:
@@ -30,7 +30,6 @@ from B09_Estimation import B09_Estimation_Bill_Library as library
|
||||
from B09_Estimation import B09_Estimation_Sangeun as sg
|
||||
from B09_Estimation.B09_Estimation_Calc_Labor import COPY, COVER_KEYS, with_copies
|
||||
from B09_Estimation.B09_Estimation_Labor_Source import AREA_FILE, daega_inputs
|
||||
from M02_MasterTemplete import M02_Hopyo_Build as hopyo_build
|
||||
from M02_MasterTemplete import M02_Item_Prices as item_prices
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
@@ -261,11 +260,11 @@ def _subs(root: Path, lines: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
||||
def _carry(lines: list[Any], subs: dict[str, dict[str, Any]], done: dict[str, Any]) -> list[Any]:
|
||||
"""불러온 하위 참조 → 지금 층 새 키(하위 먼저 · 같은 것 한 번 · 앞서 옮긴 것 = 그 키)."""
|
||||
here = refs.here()
|
||||
have = {
|
||||
refs.ref_id(got["복사원본"]): key
|
||||
for key, got, _raw in hopyo_build._origins(store.root())
|
||||
if isinstance(got.get("복사원본"), dict)
|
||||
}
|
||||
have: dict[str, str] = {} # 733-2 — 실무자료뿐 아니라 모든 사본 원본(`copied_from`)
|
||||
for path in sorted((store.root() / KIND).glob(f"{refs.PREFIX[KIND]}-*.json")):
|
||||
got = refs.copied_from(read_json(path))
|
||||
if got is not None:
|
||||
have.setdefault(refs.ref_id(got), path.stem)
|
||||
out = []
|
||||
for one in lines:
|
||||
ref = one.get("참조") if isinstance(one, dict) else None
|
||||
@@ -284,8 +283,32 @@ def _carry(lines: list[Any], subs: dict[str, dict[str, Any]], done: dict[str, An
|
||||
return out
|
||||
|
||||
|
||||
def write(doc: dict[str, Any], overwrite: str | None = None) -> dict[str, Any]:
|
||||
"""지금 층(`store.at` 안)에 씀 — 같은 이름이면 409 · `overwrite` = 같은 이름 그 키를 덮음."""
|
||||
def _ask(kind: str, doc: dict[str, Any], subs: dict, new: bool, use: str | None) -> str | None:
|
||||
"""같은 층 같은 내용(733-2 · `M02_Library_Ask`) — 하위 호표(층 밖 참조) 지문은 굳힌 문서로."""
|
||||
from M02_MasterTemplete import M02_Library_Ask as ask
|
||||
from M02_MasterTemplete.M02_Library_Same import Same
|
||||
|
||||
s = Same()
|
||||
|
||||
def fill(rid: str) -> None:
|
||||
if rid in s.memo or rid not in subs:
|
||||
return
|
||||
sub = {k: v for k, v in subs[rid].items() if k not in ("키", "알약")}
|
||||
for ref in _own(sub.get("담은로직")):
|
||||
fill(refs.ref_id(ref))
|
||||
s.memo[rid] = s.finger(KIND, sub) or f"없음:{rid}"
|
||||
|
||||
for rid in subs:
|
||||
fill(rid)
|
||||
return ask.ask(kind, doc, new, use, s)
|
||||
|
||||
|
||||
def write(
|
||||
doc: dict[str, Any], overwrite: str | None = None, new: bool = False, use: str | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""지금 층(`store.at` 안)에 씀 — 같은 이름이면 409 · `overwrite` = 같은 이름 그 키를 덮음 ·
|
||||
같은 내용(733-2) = 409 `{같은것, 까닭}`(같은 이름도 있으면 `같은이름 · 이름` 함께) →
|
||||
`new` 그래도 새로 · `use` 그 키(새로 안 만듦 · 답 `씀`)."""
|
||||
subs = doc.get(SUBS) or {}
|
||||
doc = {k: v for k, v in doc.items() if k != SUBS}
|
||||
kind = SK_KIND if doc.get("종류") == refs.TITLE[SK_KIND] else KIND
|
||||
@@ -296,8 +319,16 @@ def write(doc: dict[str, Any], overwrite: str | None = None) -> dict[str, Any]:
|
||||
]
|
||||
if overwrite and overwrite not in same:
|
||||
raise store.StoreError(409, f"덮어쓸 「{doc['이름']}」 이 없음 — 다시 누름")
|
||||
if same and not overwrite:
|
||||
raise store.StoreError(409, {"같은이름": same[0], "이름": doc["이름"]})
|
||||
if not overwrite:
|
||||
named = {"같은이름": same[0], "이름": doc["이름"]} if same else {}
|
||||
try:
|
||||
got = _ask(kind, doc, subs, new, use)
|
||||
except store.StoreError as e:
|
||||
raise store.StoreError(e.status, {**e.detail, **named}) from e
|
||||
if got:
|
||||
return {"이름": got, "씀": True}
|
||||
if same:
|
||||
raise store.StoreError(409, named)
|
||||
if subs:
|
||||
doc = {**doc, "담은로직": _carry(doc["담은로직"], subs, {})}
|
||||
if overwrite:
|
||||
|
||||
@@ -511,6 +511,8 @@ class PersonalBody(BaseModel):
|
||||
name: str | None = Field(None, max_length=200)
|
||||
overwrite: str | None = Field(None, max_length=20)
|
||||
user: dict[str, Any] | None = None
|
||||
새로: bool = False # 같은 내용 409 뒤 — 그래도 새로(733-2)
|
||||
씀: str | None = Field(None, max_length=20) # 같은 내용 409 뒤 — 그 키 씀
|
||||
|
||||
|
||||
@router.post("/{project_id}/estimation/daega/personal")
|
||||
@@ -529,7 +531,8 @@ async def save_personal(
|
||||
if not made["문서"]["이름"]:
|
||||
raise HTTPException(status_code=400, detail="이름이 빔")
|
||||
folder = await asyncio.to_thread(_root, session, "personal", None, True)
|
||||
got = await asyncio.to_thread(_call, folder, personal.write, made["문서"], body.overwrite)
|
||||
args = (made["문서"], body.overwrite, body.새로, body.씀)
|
||||
got = await asyncio.to_thread(_call, folder, personal.write, *args)
|
||||
return {
|
||||
"키": got["이름"],
|
||||
"이름": made["문서"]["이름"],
|
||||
|
||||
@@ -18,6 +18,7 @@ from M02_MasterTemplete import M02_Hopyo_Build as hb
|
||||
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
|
||||
from M02_MasterTemplete import M02_Item_Personal as personal
|
||||
from M02_MasterTemplete import M02_Labor_Spec as spec
|
||||
from M02_MasterTemplete import M02_Library_Ask as ask
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
|
||||
PATH = ["실무자료"]
|
||||
@@ -96,8 +97,12 @@ def make_item(
|
||||
bundle: list[int] | None = None,
|
||||
name: str | None = None,
|
||||
who: Any = None,
|
||||
new: bool = False,
|
||||
use: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""[구조물 조합으로] → `{key, 열, 이름, 줄수, 열수, 빠진줄}` — `bundle` = 규격 열로 묶을 호표 번(연 호표 빼고)."""
|
||||
"""[구조물 조합으로] → `{key, 열, 이름, 줄수, 열수, 빠진줄}` —
|
||||
`bundle` = 규격 열로 묶을 호표 번(연 호표 빼고) ·
|
||||
이 층 같은 내용 항목 = 409(733-2) → `new` 그래도 새로 · `use` 그 항목(`씀` · 새로 안 만듦)."""
|
||||
main = _doc(folder, source, key, no, lines, whole, who)
|
||||
doc, hopyo = main["문서"], main["호표"]
|
||||
title = (name or "").strip() or doc["이름"]
|
||||
@@ -130,6 +135,14 @@ def make_item(
|
||||
"기본": found[int(no)]["열"],
|
||||
"수량": qty,
|
||||
}
|
||||
count = {
|
||||
"줄수": len(doc["담은로직"]) + len(doc["자재"]),
|
||||
"열수": len(cols_doc["열"]) if cols_doc else 1,
|
||||
}
|
||||
want = {"담은로직": doc["담은로직"], "자재": doc["자재"], "단위": doc.get("단위")}
|
||||
reuse = ask.ask_item(folder, {**want, "규격": cols_doc}, new, use)
|
||||
if reuse:
|
||||
return {**reuse, **count, "빠진줄": main["빠진줄"]}
|
||||
with store.at(folder):
|
||||
made = personal.create_item(folder, PATH, title, doc.get("단위"))
|
||||
have = store.read("labor", made["열"])
|
||||
@@ -138,8 +151,4 @@ def make_item(
|
||||
if cols_doc:
|
||||
body["규격"] = cols_doc
|
||||
store.write("labor", made["열"], have["판"], body)
|
||||
count = {
|
||||
"줄수": len(doc["담은로직"]) + len(doc["자재"]),
|
||||
"열수": len(cols_doc["열"]) if cols_doc else 1,
|
||||
}
|
||||
return {**made, **count, "빠진줄": main["빠진줄"]}
|
||||
|
||||
@@ -383,9 +383,30 @@ def origin_mark(doc: dict[str, Any], ref: Ref) -> dict[str, Any]:
|
||||
return {**doc, "본뜬": ref}
|
||||
|
||||
|
||||
def copied_from(doc: Any) -> Ref | None:
|
||||
"""복사 문서의 원본 참조(`origin_mark` 거꾸로) — 실무자료 본뜬 = `복사원본` · 그 밖 = `본뜬`."""
|
||||
own = doc.get("본뜬") if isinstance(doc, dict) else None
|
||||
if isinstance(own, dict) and own.get("층") == "실무자료":
|
||||
own = own.get("복사원본")
|
||||
return own if isinstance(own, dict) and own.get("키") else None
|
||||
|
||||
|
||||
def _reused(kind: str, doc: dict[str, Any], ref: Ref) -> str | None:
|
||||
"""지금 층에 같은 원본(`ref`) 본뜬 같은 내용 사본이 있으면 그 키(733-2 · 다시 복사 = 재사용)."""
|
||||
from M02_MasterTemplete.M02_Library_Same import Same
|
||||
|
||||
rid = ref_id(ref)
|
||||
for key in Same().find_same(store.root(), kind, doc):
|
||||
got = copied_from(read_ref({"층": here()[0], "주인": here()[1], "키": key}))
|
||||
if got is not None and ref_id(got) == rid:
|
||||
return key
|
||||
return None
|
||||
|
||||
|
||||
def carry(doc: dict[str, Any], source: Here) -> dict[str, Any]:
|
||||
"""복사(원본 층 `source` → 지금 층) — 지금 층이 못 읽는 참조는 그 문서를 지금 층에 새 키로
|
||||
같이 복사(`본뜬` = 원래 참조) · 참조를 새 키로 바꿈. 원본 층 읽기 권한은 부르는 쪽 몫."""
|
||||
같이 복사(`본뜬` = 원래 참조) · 참조를 새 키로 바꿈 · 같은 원본 같은 내용 사본이 이미 있으면
|
||||
그 키(733-2). 원본 층 읽기 권한은 부르는 쪽 몫."""
|
||||
target, done = here(), {}
|
||||
|
||||
def move(ref: Ref, depth: int) -> Ref:
|
||||
@@ -398,9 +419,10 @@ def carry(doc: dict[str, Any], source: Here) -> dict[str, Any]:
|
||||
got = read_ref(ref)
|
||||
if got is None:
|
||||
raise _bad(f"없는 참조 「{ref['키']}」")
|
||||
body = _move_doc(got, move, depth + 1)
|
||||
made = create(kind_of(ref["키"]), origin_mark(_spec_moved(body, done), ref))
|
||||
done[ref_id(ref)] = {"층": target[0], "주인": target[1], "키": made["이름"]}
|
||||
body = origin_mark(_spec_moved(_move_doc(got, move, depth + 1), done), ref)
|
||||
kind = kind_of(ref["키"])
|
||||
key = _reused(kind, body, ref) or create(kind, body)["이름"]
|
||||
done[ref_id(ref)] = {"층": target[0], "주인": target[1], "키": key}
|
||||
return done[ref_id(ref)]
|
||||
|
||||
return _spec_moved(_move_doc(doc, move, 1), done)
|
||||
|
||||
@@ -38,6 +38,7 @@ from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_Layer_Access as access
|
||||
from M02_MasterTemplete import M02_Library_Ask as library_ask
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_Material_Own as own
|
||||
from M02_MasterTemplete import M02_Precombo_Folders as pre_folders
|
||||
@@ -100,10 +101,13 @@ def labor_pills(
|
||||
|
||||
|
||||
class LibraryBody(BaseModel):
|
||||
"""새로 = `문서` · 복사 = `본뜬` {층, 주인, 키}(원본 읽기 권한 · 못 읽는 참조는 같이 복사)."""
|
||||
"""새로 = `문서` · 복사 = `본뜬` {층, 주인, 키}(원본 읽기 권한 · 못 읽는 참조는 같이 복사) ·
|
||||
새로 같은 내용 409 → `새로` = 그래도 새로 · `씀` = 그 키(733-2 · `M02_Library_Ask`)."""
|
||||
|
||||
문서: dict[str, Any] | None = None
|
||||
본뜬: dict[str, Any] | None = None
|
||||
새로: bool = False
|
||||
씀: str | None = None
|
||||
|
||||
|
||||
def _copy(kind: str, doc: dict[str, Any], source: refs.Here, ref: dict) -> dict[str, Any]:
|
||||
@@ -121,7 +125,7 @@ def new_library(
|
||||
"""서고 새로 · 복사 — 키는 서버가 그 층 다음 번호(SP · SC) · 답 `이름` = 새 키."""
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
if body.본뜬 is None:
|
||||
return _call(folder, refs.create, kind, body.문서 or {})
|
||||
return _call(folder, library_ask.create, kind, body.문서 or {}, body.새로, body.씀)
|
||||
src = body.본뜬
|
||||
if src.get("층") not in ("system", "company", "personal"):
|
||||
raise HTTPException(status_code=400, detail="본뜬 층이 틀림")
|
||||
@@ -623,6 +627,8 @@ class ItemBody(BuildBody):
|
||||
|
||||
묶음: list[int] = Field(default_factory=list, max_length=20)
|
||||
이름: str | None = Field(None, max_length=120)
|
||||
새로: bool = False # 같은 내용 항목 409 뒤 — 그래도 새로(733-2)
|
||||
씀: str | None = Field(None, max_length=80) # 같은 내용 409 뒤 — 그 항목 씀
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/hopyo-item")
|
||||
@@ -639,4 +645,5 @@ def hopyo_to_item(
|
||||
lines = [one.model_dump(exclude_none=True) for one in body.줄]
|
||||
whole = body.통째.model_dump(exclude_none=True) if body.통째 else None
|
||||
args = (folder, source, body.키, body.번, lines, whole, body.묶음, body.이름, _who(session))
|
||||
args += (body.새로, body.씀)
|
||||
return _call(folder, hopyo_item.make_item, *args)
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
"""사람 길 만들 때 물음(PLAN 733-2 · 요구서_572 3장 3) — 같은 층 같은 내용 문서가 있으면 409.
|
||||
|
||||
409 detail = `{같은것: [{키, 이름, 층, 주인}], 까닭}` → 다시 보냄 몸 `새로: true` = 그래도 새로 ·
|
||||
`씀: <키>` = 그것을 씀(새로 안 만듦 · 그 키를 돌려줌 · 같은 내용이 아니면 다시 409).
|
||||
지문 = `M02_Library_Same`(일위대가 SC · 단가산출 SK) · 구조물 조합(labor) = 같은 꼴 + 줄 수량 넣음.
|
||||
빈 문서(지문 없음) = 안 물음 · 같은 층 [복사] · 붙여넣기 = 안 물음(복사가 뜻).
|
||||
부르는 쪽 = 지금 층(`store.at` 안).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from common_util.common_util_derived_store import derived_key
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_Library_Same as same
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Tree as tree
|
||||
|
||||
WHY = "같은 층에 같은 내용 문서가 있음 — 그것을 씀 · 그래도 새로"
|
||||
|
||||
|
||||
def _cleaned(kind: str, doc: dict[str, Any]) -> dict[str, Any]:
|
||||
"""저장될 꼴로 견줌(서버 정리) — 틀린 문서는 그대로(만들 때 까닭이 남)."""
|
||||
try:
|
||||
return refs.clean_doc(kind, {**doc, "키": refs.next_key(kind)})
|
||||
except store.StoreError:
|
||||
return doc
|
||||
|
||||
|
||||
def _refuse(rows: list[dict[str, Any]], why: str = WHY) -> store.StoreError:
|
||||
return store.StoreError(409, {"같은것": rows, "까닭": why})
|
||||
|
||||
|
||||
def _pick(found: list[str], rows: list[dict[str, Any]], new: bool, use: str | None) -> str | None:
|
||||
if use:
|
||||
if use in found:
|
||||
return use
|
||||
raise _refuse(rows, f"고른 「{use}」 가 같은 내용이 아님 — 다시 고름")
|
||||
if found and not new:
|
||||
raise _refuse(rows)
|
||||
return None
|
||||
|
||||
|
||||
def ask(
|
||||
kind: str,
|
||||
doc: dict[str, Any],
|
||||
new: bool = False,
|
||||
use: str | None = None,
|
||||
s: same.Same | None = None,
|
||||
) -> str | None:
|
||||
"""서고 문서(SC · SK) — 씀 키 | None(새로 만듦) · 같은 것 있고 물음 없으면 409 ·
|
||||
`s` = 미리 채운 지문 한 벌(층 밖 참조 지문 등)."""
|
||||
folder = store.root()
|
||||
found = (s or same.Same()).find_same(folder, kind, _cleaned(kind, doc))
|
||||
at = refs.where(folder)
|
||||
rows = [
|
||||
{
|
||||
"키": key,
|
||||
"이름": (refs.read_ref({"층": at[0], "주인": at[1], "키": key}) or {}).get("이름"),
|
||||
}
|
||||
for key in found
|
||||
]
|
||||
return _pick(found, [{**r, "층": at[0], "주인": at[1]} for r in rows], new, use)
|
||||
|
||||
|
||||
def labor_finger(doc: Any) -> str | None:
|
||||
"""구조물 조합 일위대가 지문 — 담은로직 · 자재 · 단위 · 규격(줄 수량 넣음 · 차례 무시)."""
|
||||
if not isinstance(doc, dict):
|
||||
return None
|
||||
s = same.Same()
|
||||
mats = [same.norm(m) for m in doc.get("자재") or []]
|
||||
body = {
|
||||
"단위": same.unit(doc.get("단위")),
|
||||
"담은로직": same._sorted([s._line(x, (), True) for x in doc.get("담은로직") or []]),
|
||||
"자재": same._sorted(mats),
|
||||
"규격": same.norm(doc.get("규격")),
|
||||
}
|
||||
body = {k: v for k, v in body.items() if v not in same.EMPTY}
|
||||
if not body.get("담은로직") and not body.get("자재"):
|
||||
return None
|
||||
return f"SU:{derived_key(body)}"
|
||||
|
||||
|
||||
def ask_item(
|
||||
folder: Path, doc: dict[str, Any], new: bool = False, use: str | None = None
|
||||
) -> dict[str, Any] | None:
|
||||
"""구조물 조합 항목(실무 호표 [구조물 조합으로]) — 씀 = `{key, 열, 이름}` | None(새로) · 409."""
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
|
||||
try:
|
||||
doc = {**labor.clean(doc), "규격": doc.get("규격")} # 저장될 꼴로 견줌
|
||||
except store.StoreError:
|
||||
pass
|
||||
want = labor_finger(doc)
|
||||
if not want:
|
||||
return None
|
||||
at = refs.where(folder)
|
||||
with store.at(folder):
|
||||
table = store.read("table", store.STRUCTURE_TABLE)["문서"]
|
||||
cols = {c["id"]: c for c in table.get("열") or [] if tree.is_item(c)}
|
||||
found, rows = [], []
|
||||
for key, col in cols.items():
|
||||
try:
|
||||
have = json.loads((Path(folder) / "labor" / f"{key}.json").read_bytes())
|
||||
except (OSError, ValueError):
|
||||
continue
|
||||
if labor_finger(have) == want:
|
||||
name = " / ".join(tree.path_of(col))
|
||||
found.append(key)
|
||||
rows.append({"키": key, "이름": name, "층": at[0], "주인": at[1]})
|
||||
got = _pick(found, rows, new, use)
|
||||
if got is None:
|
||||
return None
|
||||
return {"key": got, "열": got, "이름": rows[found.index(got)]["이름"], "씀": True}
|
||||
|
||||
|
||||
def create(kind: str, doc: dict[str, Any], new: bool = False, use: str | None = None) -> dict:
|
||||
"""[새로 만들기](733-2) — 같은 것 물음 뒤 `refs.create` · 씀 = `{이름: 그 키, 씀: true}`."""
|
||||
got = ask(kind, doc, new, use)
|
||||
return {"이름": got, "씀": True} if got else refs.create(kind, doc)
|
||||
@@ -481,19 +481,23 @@ def calc_doc(doc: Any, given: dict | None = None) -> dict[str, Any]:
|
||||
return labor._json({**per_unit(got, clean), "머리": head_of(clean)})
|
||||
|
||||
|
||||
def from_line(line: Any, name: str, unit: str) -> dict[str, Any]:
|
||||
def from_line(
|
||||
line: Any, name: str, unit: str, new: bool = False, use: str | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""[단가산출로 저장](715-3) — 일위대가 줄(바탕 · 입력값 · 고름 · 줄 덮개) → 지금 층 새 SK ·
|
||||
답 = `{이름: 새 키, 줄: 그 SK 참조 줄}`(일위대가 문서는 화면 [저장] 때 바뀜)."""
|
||||
답 = `{이름: 새 키, 줄: 그 SK 참조 줄}`(일위대가 문서는 화면 [저장] 때 바뀜) ·
|
||||
같은 층 같은 내용 SK = 409(733-2) → `new` 그래도 새로 · `use` 그 SK(새로 안 만듦)."""
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Labor_Patch as patch
|
||||
from M02_MasterTemplete import M02_Library_Ask as ask
|
||||
|
||||
if not isinstance(line, dict):
|
||||
raise store.StoreError(400, "줄이 묶음 아님")
|
||||
doc = patch.to_sangeun(line, name, unit, m01.loaded()[1])
|
||||
made = refs.create(sd.KIND, doc)
|
||||
key = ask.ask(sd.KIND, doc, new, use) or refs.create(sd.KIND, doc)["이름"]
|
||||
at = refs.here()
|
||||
keep = {k: line[k] for k in ("별칭", "메모", "수량") if k in line}
|
||||
return {
|
||||
"이름": made["이름"],
|
||||
"줄": {"참조": {"층": at[0], "주인": at[1], "키": made["이름"]}, **keep},
|
||||
"이름": key,
|
||||
"줄": {"참조": {"층": at[0], "주인": at[1], "키": key}, **keep},
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ class FromLineBody(BaseModel):
|
||||
줄: dict[str, Any]
|
||||
이름: str
|
||||
단위: str
|
||||
새로: bool = False # 같은 내용 409 뒤 — 그래도 새로(733-2)
|
||||
씀: str | None = None # 같은 내용 409 뒤 — 그 SK 씀
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/sangeun/from-line")
|
||||
@@ -53,7 +55,7 @@ def sangeun_from_line(
|
||||
) -> dict:
|
||||
"""[단가산출로 저장](715-3) — 일위대가 줄 → 그 층 새 단가산출 · 답 = 새 키 + 바꿀 참조 줄."""
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
return _call(folder, calc.from_line, body.줄, body.이름, body.단위)
|
||||
return _call(folder, calc.from_line, body.줄, body.이름, body.단위, body.새로, body.씀)
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/sangeun/calc")
|
||||
|
||||
@@ -29,6 +29,7 @@ from common_util.common_util_json import atomic_write_json
|
||||
from M02_MasterTemplete import M02_Item_Paste as paste_store
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_Layer_Access as access
|
||||
from M02_MasterTemplete import M02_Library_Ask as library_ask
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Items as items_store
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_Precombo_Folders as pre_folders
|
||||
@@ -54,6 +55,8 @@ class AskBody(BaseModel):
|
||||
|
||||
class ApproveBody(BaseModel):
|
||||
경로: list[str] = []
|
||||
새로: bool = False # 회사 층 같은 내용 409 뒤 — 그래도 새로(733-2 · 일위대가)
|
||||
씀: str | None = None # 같은 내용 409 뒤 — 그 회사 문서를 사본으로(새로 안 만듦)
|
||||
|
||||
|
||||
class RejectBody(BaseModel):
|
||||
@@ -171,9 +174,9 @@ def _settle(session: Session, company: str | None, rid: str, done: dict[str, Any
|
||||
return row
|
||||
|
||||
|
||||
def _copy(session: Session, dest: list[str]):
|
||||
def _copy(session: Session, dest: list[str], new: bool = False, use: str | None = None):
|
||||
"""승인 = 「회사로 복사…」와 같은 길 — 구조물 항목 = 붙여넣기 ·
|
||||
사전조합 = 서고 복사(+ 폴더 자리)."""
|
||||
사전조합 = 서고 복사(+ 폴더 자리) · 회사 층 같은 내용 = 409 물음(733-2 · `M02_Library_Ask`)."""
|
||||
|
||||
def run(row: dict[str, Any], company: str) -> str:
|
||||
target = _root(session, "company", company, write=True)
|
||||
@@ -194,11 +197,14 @@ def _copy(session: Session, dest: list[str]):
|
||||
def make() -> dict[
|
||||
str, Any
|
||||
]: # 못 읽는 참조 같이 복사 = 받는 층 안에서(`new_library` 와 같음)
|
||||
got = library_ask.ask("precombo", doc, new, use) # 참조 지문 = 내용 → 옮기기 전 견줌
|
||||
if got:
|
||||
return {"이름": got, "씀": True}
|
||||
return refs.create("precombo", refs.carry(refs.origin_mark(doc, ref), here))
|
||||
|
||||
here = ("personal", row["주인"])
|
||||
made = _call(target, make)
|
||||
if dest:
|
||||
if dest and not made.get("씀"):
|
||||
view = _call(target, pre_folders.view)
|
||||
move = {"op": "move", "키": made["이름"], "새경로": dest, "판": view["판"]}
|
||||
_call(target, pre_folders.edit, move)
|
||||
@@ -215,7 +221,8 @@ def approve(
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict[str, Any]:
|
||||
"""승인 — 회사 층 `경로` 폴더에 사본(새 키 · 같은 이름 「(1)」) · 원본 개인 것은 그대로."""
|
||||
return _settle(session, company, rid, {"상태": "승인", "run": _copy(session, body.경로)})
|
||||
run = _copy(session, body.경로, body.새로, body.씀)
|
||||
return _settle(session, company, rid, {"상태": "승인", "run": run})
|
||||
|
||||
|
||||
@router.post("/share-requests/{rid}/reject")
|
||||
|
||||
@@ -174,7 +174,14 @@ def test_자료집_같은_이름_물음(world):
|
||||
changed["담은로직"][1]["수량"] = {"손": 9}
|
||||
again = personal.write(changed, first["이름"]) # 덮어쓰기
|
||||
assert again["이름"] == first["이름"]
|
||||
other = personal.write({**doc, "이름": "내 돌붙임 2"}) # 새 이름
|
||||
with pytest.raises(m02.StoreError) as e: # 733-2 — 새 이름이라도 같은 내용 = 409 물음
|
||||
personal.write({**doc, "이름": "내 돌붙임 2"})
|
||||
assert (
|
||||
e.value.detail["같은것"][0]["키"] == first["이름"] and "같은이름" not in e.value.detail
|
||||
)
|
||||
other = personal.write({**doc, "이름": "내 돌붙임 2"}, new=True) # 그래도 새로
|
||||
used = personal.write({**doc, "이름": "내 돌붙임 3"}, use=first["이름"]) # 그것을 씀
|
||||
assert used == {"이름": first["이름"], "씀": True}
|
||||
assert other["이름"] != first["이름"]
|
||||
saved = json.loads(
|
||||
(world["folder"] / "precombo" / f"{first['이름']}.json").read_text(encoding="utf-8")
|
||||
|
||||
@@ -359,6 +359,22 @@ def test_구조물_조합으로_남의_층_403(client):
|
||||
assert client.post("/api/m02/layers/company/hopyo-item", json=body).status_code == 403
|
||||
|
||||
|
||||
def test_구조물_조합으로_같은_내용_409_씀_새로(client):
|
||||
"""733-2 — 이 층 같은 내용 항목 = 409 `{같은것}` → `씀` = 새 항목 0 · `새로` = 새 항목 1."""
|
||||
key = _upload(client, "대흥")
|
||||
url, body = "/api/m02/layers/personal/hopyo-item", {"층": "personal", "키": key, "번": 17}
|
||||
first = client.post(url, json=body).json()
|
||||
count = len(list((layers_dir() / "labor").glob("*.json")))
|
||||
res = client.post(url, json=body)
|
||||
assert res.status_code == 409 and res.json()["detail"]["같은것"][0]["키"] == first["열"]
|
||||
used = client.post(url, json={**body, "씀": first["열"]}).json()
|
||||
assert used["열"] == first["열"] and used["씀"] is True
|
||||
assert len(list((layers_dir() / "labor").glob("*.json"))) == count
|
||||
made = client.post(url, json={**body, "새로": True}).json()
|
||||
assert made["열"] != first["열"]
|
||||
assert len(list((layers_dir() / "labor").glob("*.json"))) == count + 1
|
||||
|
||||
|
||||
def test_규격_열_이름():
|
||||
from M02_MasterTemplete import M02_Hopyo_Item as item
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
"""사람 길 만들 때 물음(PLAN 733-2 · `M02_Library_Ask`) — 임시 storage.
|
||||
|
||||
같은 층 같은 내용 = 409 `{같은것: [{키, 이름, 층, 주인}], 까닭}` →
|
||||
`씀` = 새 문서 0 · `새로` = 새 문서 1.
|
||||
길 = 새로 만들기 · [단가산출로 저장] from-line · 공유 요청 승인 · 복사 딸림 재사용(carry) ·
|
||||
빈 문서 · 같은 층 복사 = 안 물음. B09 내 자료집 = `test_703_b09_daega_copy` · 구조물 조합으로 =
|
||||
`test_705_2_hopyo_paste`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
from M02_MasterTemplete import M02_Template_Layers as layers
|
||||
from resources.tester import test_706_1_share_request as share_t
|
||||
from resources.tester.test_706_1_share_request import URL, _as, _client
|
||||
|
||||
world = share_t.world # 같은 임시 storage 판
|
||||
|
||||
TEN = "GC000421"
|
||||
STONE = "GF000219"
|
||||
LIB = "/api/m02/layers/{}/library/{}"
|
||||
|
||||
|
||||
def _count(folder, kind="precombo") -> int:
|
||||
return len(list((folder / kind).glob("S?-*.json")))
|
||||
|
||||
|
||||
def _doc(name: str, code: str = TEN) -> dict:
|
||||
return {"문서": {"이름": name, "단위": "㎡", "담은로직": [{"로직": code}]}}
|
||||
|
||||
|
||||
def test_새로_만들기_같은_내용_409_씀_새로(world):
|
||||
client = _client(world)
|
||||
_as(world)
|
||||
mine = layers.personal_dir(7, 42)
|
||||
url = LIB.format("personal", "precombo")
|
||||
first = client.post(url, json=_doc("가")).json()["이름"]
|
||||
res = client.post(url, json=_doc("이름 다름")) # 이름이 달라도 같은 내용
|
||||
assert res.status_code == 409, res.text
|
||||
detail = res.json()["detail"]
|
||||
assert detail["같은것"] == [{"키": first, "이름": "가", "층": "personal", "주인": "7/42"}]
|
||||
assert detail["까닭"]
|
||||
used = client.post(url, json={**_doc("이름 다름"), "씀": first}).json()
|
||||
assert used == {"이름": first, "씀": True} and _count(mine) == 1 # 그것을 씀 = 새 0
|
||||
made = client.post(url, json={**_doc("이름 다름"), "새로": True}).json()["이름"]
|
||||
assert made != first and _count(mine) == 2 # 그래도 새로 = 새 1
|
||||
bad = client.post(url, json={**_doc("다른 것", STONE), "씀": first})
|
||||
assert bad.status_code == 409 and _count(mine) == 2 # 같은 내용 아닌 키 = 다시 물음
|
||||
assert client.post(url, json=_doc("다른 것", STONE)).status_code == 200 # 다른 내용 = 그대로
|
||||
for _ in range(2): # 빈 새 문서 = 지문 없음 · 안 물음
|
||||
empty = client.post(url, json={"문서": {"이름": "빈", "담은로직": []}})
|
||||
assert empty.status_code == 200, empty.text
|
||||
copy = {"본뜬": {"층": "personal", "주인": "7/42", "키": first}}
|
||||
assert client.post(url, json=copy).status_code == 200 # 같은 층 [복사] = 안 물음
|
||||
|
||||
|
||||
def test_단가산출로_저장_같은_내용_409_씀_새로(world):
|
||||
mine = layers.personal_dir(7, 42)
|
||||
line = {"로직": TEN, "수량": {"손": 2}, "별칭": "내 줄"}
|
||||
with store.at(mine):
|
||||
first = calc.from_line(line, "줄로 만든", "㎡")
|
||||
with pytest.raises(store.StoreError) as e:
|
||||
calc.from_line(line, "또 만든", "㎡")
|
||||
assert e.value.status == 409 and e.value.detail["같은것"][0]["키"] == first["이름"]
|
||||
used = calc.from_line(line, "또 만든", "㎡", use=first["이름"])
|
||||
assert used == first and _count(mine, "sangeun") == 1 # 줄 = 그 SK 참조
|
||||
made = calc.from_line(line, "또 만든", "㎡", new=True)
|
||||
assert made["이름"] != first["이름"] and _count(mine, "sangeun") == 2
|
||||
|
||||
|
||||
def test_공유_승인_회사_같은_내용_409_씀_새로(world):
|
||||
client = _client(world)
|
||||
_as(world)
|
||||
pre = client.post(LIB.format("personal", "precombo"), json=_doc("내 것")).json()["이름"]
|
||||
rid = client.post(URL, json={"종류": "precombo", "키": pre}).json()["id"]
|
||||
_as(world, "ADMIN", user=50)
|
||||
company = layers.company_dir(7)
|
||||
have = client.post(LIB.format("company", "precombo"), json=_doc("회사 것")).json()["이름"]
|
||||
res = client.post(f"{URL}/{rid}/approve", json={"경로": []})
|
||||
assert res.status_code == 409 and res.json()["detail"]["같은것"][0]["키"] == have
|
||||
assert client.get(URL).json()["줄"][0]["상태"] == "요청" # 물음 뒤 그대로
|
||||
ok = client.post(f"{URL}/{rid}/approve", json={"경로": [], "씀": have}).json()
|
||||
assert ok["상태"] == "승인" and ok["사본"] == have and _count(company) == 1
|
||||
_as(world)
|
||||
rid = client.post(URL, json={"종류": "precombo", "키": pre}).json()["id"]
|
||||
_as(world, "ADMIN", user=50)
|
||||
ok = client.post(f"{URL}/{rid}/approve", json={"경로": [], "새로": True}).json()
|
||||
assert ok["사본"] != have and _count(company) == 2
|
||||
|
||||
|
||||
def test_복사_딸림_같은_원본_재사용(world):
|
||||
client = _client(world)
|
||||
_as(world)
|
||||
mine = layers.personal_dir(7, 42)
|
||||
url = LIB.format("personal", "precombo")
|
||||
inner = client.post(url, json=_doc("안")).json()["이름"]
|
||||
ref = {"층": "personal", "주인": "7/42", "키": inner}
|
||||
outer = {"문서": {"이름": "밖", "단위": "㎡", "담은로직": [{"참조": ref}]}}
|
||||
top = client.post(url, json=outer).json()["이름"]
|
||||
with store.at(mine):
|
||||
doc = store.read("precombo", top)["문서"]
|
||||
src = {"층": "personal", "주인": "7/42", "키": top}
|
||||
company = layers.company_dir(7)
|
||||
with store.at(company):
|
||||
one = refs.create("precombo", refs.carry(refs.origin_mark(doc, src), ("personal", "7/42")))
|
||||
assert _count(company) == 2 # 밖 + 딸린 안
|
||||
two = refs.create("precombo", refs.carry(refs.origin_mark(doc, src), ("personal", "7/42")))
|
||||
assert _count(company) == 3 # 다시 복사 = 밖만 새로 · 딸린 안 = 앞 사본 재사용
|
||||
with store.at(company):
|
||||
got = [store.read("precombo", m["이름"])["문서"]["담은로직"][0]["참조"] for m in (one, two)]
|
||||
assert got[0] == got[1] and got[0]["층"] == "company"
|
||||
Reference in New Issue
Block a user