feat(M02): 가져올 곳 탭 readableSources · 회사 맡음 manages — 복사 생성 · 복사해 가져오기 탭 목록(39-6 E5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013rJwJMVL4EE31y77TB11AP
This commit is contained in:
@@ -93,6 +93,78 @@ export function accessFor(
|
||||
return { canWrite: layer === "personal" || companyAdmin, needCompany: false };
|
||||
}
|
||||
|
||||
/** 회사 C 를 맡음 — 시스템 관리자 · 또는 C 의 회사 관리자(서버 `M02_Layer_Access.manages` 짝 · 39-6) */
|
||||
export function manages(company: string | null, user: SessionUser | null): boolean {
|
||||
if (!company || !user) return false;
|
||||
if (user.role === "SYSTEM_ADMIN") return true;
|
||||
const companyAdmin = user.role === "ADMIN" || !!user.is_master;
|
||||
return companyAdmin && company === ownOwner("company", user);
|
||||
}
|
||||
|
||||
/** 가져올 곳 탭 하나 — `people` = 다른 사람(주인은 부르는 쪽이 `fetchOwners` 로 고름) */
|
||||
export interface Source {
|
||||
id: "master" | "company" | "personal" | "people" | "practice";
|
||||
layer: ManageLayer;
|
||||
/** 층 주인 — 자기 층 · 마스터 · 실무문서 · 다른 사람 = null */
|
||||
owner: string | null;
|
||||
label: string;
|
||||
}
|
||||
|
||||
/** 복사 생성 · 복사해 가져오기의 탭(39-6 · 답 2) — 읽을 수 있는 곳 + 기본 탭.
|
||||
* 기본 = 시스템 관리자 마스터 · 그 밖 내 개인(회사 없으면 마스터) · `practice` = 일위대가 고를 때만 */
|
||||
export function readableSources(
|
||||
user: SessionUser | null,
|
||||
company: string | null,
|
||||
opt: { practice?: boolean } = {},
|
||||
): { tabs: Source[]; first: Source["id"] } {
|
||||
const isSystem = user?.role === "SYSTEM_ADMIN";
|
||||
const own = ownOwner("personal", user);
|
||||
const tabs: Source[] = [
|
||||
{ id: "master", layer: "system", owner: null, label: L("M02_PickerSystem") },
|
||||
...(company
|
||||
? [
|
||||
{
|
||||
id: "company" as const,
|
||||
layer: "company" as const,
|
||||
owner: company === ownOwner("company", user) ? null : company,
|
||||
label: L("M02_PickerCompany"),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(own
|
||||
? [
|
||||
{
|
||||
id: "personal" as const,
|
||||
layer: "personal" as const,
|
||||
owner: null,
|
||||
label: L("M02_PickerPersonal"),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(manages(company, user)
|
||||
? [
|
||||
{
|
||||
id: "people" as const,
|
||||
layer: "personal" as const,
|
||||
owner: null,
|
||||
label: L("M02_SourcePeople"),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(opt.practice && isSystem
|
||||
? [
|
||||
{
|
||||
id: "practice" as const,
|
||||
layer: "system" as const,
|
||||
owner: null,
|
||||
label: L("M02_SourcePractice"),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
return { tabs, first: isSystem || !own ? "master" : "personal" };
|
||||
}
|
||||
|
||||
/** 회사 없음 — 개인 · 회사 층은 회사 폴더 아래라 회사부터 */
|
||||
export function buildNeedCompany(): HTMLElement {
|
||||
const go = createButton({ label: L("M02_NeedCompanyGo"), variant: "filled" });
|
||||
|
||||
@@ -87,6 +87,55 @@ def test_주소_인자를_라우트에서_뗌():
|
||||
assert 'open: "구조물조합", root: "personal"' in picker and "layer=personal" not in picker
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
||||
def test_가져올_곳_탭():
|
||||
"""39-6 · 답 2 — 탭 = 마스터 · 볼 회사 · 내 개인 · 다른 사람(맡은 회사) · 실무문서(시스템 관리자 ·
|
||||
일위대가) · 기본 = 시스템 관리자 마스터 · 그 밖 내 개인(회사 없으면 마스터)."""
|
||||
text = _text(LAYER)
|
||||
parts = [
|
||||
re.search(r"^export const ownOwner[\s\S]*?;\n", text, re.M),
|
||||
re.search(r"^export function manages[\s\S]*?^}", text, re.M),
|
||||
re.search(r"^export interface Source[\s\S]*?^}", text, re.M),
|
||||
re.search(r"^export function readableSources[\s\S]*?^}", text, re.M),
|
||||
]
|
||||
assert all(parts), "ownOwner · manages · Source · readableSources 를 못 찾음"
|
||||
script = (
|
||||
'type ManageLayer = "system" | "company" | "personal";\n'
|
||||
"interface SessionUser { id: string; role?: string; company_id?: number | null;"
|
||||
" is_master?: boolean }\n"
|
||||
"const L = (key: string): string => key;\n"
|
||||
+ "\n".join(p.group(0).replace("export ", "") for p in parts if p)
|
||||
+ "\nconst cases = JSON.parse(process.argv[1]);"
|
||||
+ "\nconsole.log(JSON.stringify(cases.map(([u, c, practice]) => {"
|
||||
+ "\n const got = readableSources(u, c, { practice });"
|
||||
+ "\n return [got.first, got.tabs.map((t) => t.id + (t.owner ? ':' + t.owner : ''))];"
|
||||
+ "\n})));"
|
||||
)
|
||||
sys1 = {"id": "1", "role": "SYSTEM_ADMIN", "company_id": 1}
|
||||
admin = {"id": "5", "role": "ADMIN", "company_id": 3}
|
||||
master = {"id": "6", "role": "USER", "company_id": 3, "is_master": True}
|
||||
user = {"id": "7", "role": "USER", "company_id": 3}
|
||||
orphan = {"id": "8", "role": "USER", "company_id": None}
|
||||
cases = [
|
||||
[sys1, "3", True], [sys1, "1", False], [admin, "3", True], [master, "3", False],
|
||||
[user, "3", True], [orphan, None, False],
|
||||
] # fmt: skip
|
||||
result = subprocess.run(
|
||||
["node", "--experimental-strip-types", "--no-warnings",
|
||||
"--input-type=module-typescript", "-e", script, json.dumps(cases)],
|
||||
capture_output=True, text=True, encoding="utf-8", timeout=60,
|
||||
) # fmt: skip
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == [
|
||||
["master", ["master", "company:3", "personal", "people", "practice"]], # 고객 회사 봄
|
||||
["master", ["master", "company", "personal", "people"]], # 자기 회사 · 일위대가 아님
|
||||
["personal", ["master", "company", "personal", "people"]], # 회사 관리자 · 실무문서 없음
|
||||
["personal", ["master", "company", "personal", "people"]], # is_master = 회사 관리자
|
||||
["personal", ["master", "company", "personal"]], # 일반 = 다른 사람 없음
|
||||
["master", ["master"]], # 회사 없음
|
||||
] # fmt: skip
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
||||
def test_볼_회사는_시스템_관리자만_고름():
|
||||
"""29-2 — 시스템 관리자 = 해시 company · sessionStorage aislo.adminCompany · 자기 회사 차례 ·
|
||||
|
||||
@@ -103,6 +103,8 @@ export const ui_locales_m2 = {
|
||||
M02_RootShared: ["회사 공용", "Company shared"],
|
||||
M02_RootReadOnly: ["읽기", "read-only"],
|
||||
M02_RootNone: ["볼 수 있는 층이 없음", "Nothing to show"],
|
||||
M02_SourcePeople: ["다른 사람", "Others"],
|
||||
M02_SourcePractice: ["실무문서", "Practice documents"],
|
||||
M02_TreeSearch: [
|
||||
"찾기 — 이름 · 키 · 폴더 · 단위 (낱말 여럿 = 모두 담은 것)",
|
||||
"Find — name, key, folder, unit (all words)",
|
||||
|
||||
Reference in New Issue
Block a user