@things-factory/ops-master 10.1.35 → 10.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist-client/pages/master/applied-when-dialog.d.ts +45 -0
  2. package/dist-client/pages/master/applied-when-dialog.js +139 -0
  3. package/dist-client/pages/master/applied-when-dialog.js.map +1 -0
  4. package/dist-client/pages/master/assignment-fields.d.ts +28 -0
  5. package/dist-client/pages/master/assignment-fields.js +55 -0
  6. package/dist-client/pages/master/assignment-fields.js.map +1 -0
  7. package/dist-client/pages/master/equipment.js +1 -0
  8. package/dist-client/pages/master/equipment.js.map +1 -1
  9. package/dist-client/pages/master/master-page-base.d.ts +14 -0
  10. package/dist-client/pages/master/master-page-base.js +40 -4
  11. package/dist-client/pages/master/master-page-base.js.map +1 -1
  12. package/dist-client/pages/master/operational-location.js +1 -0
  13. package/dist-client/pages/master/operational-location.js.map +1 -1
  14. package/dist-client/tsconfig.tsbuildinfo +1 -1
  15. package/dist-server/service/assignment/applied-when.d.ts +24 -0
  16. package/dist-server/service/assignment/applied-when.js +16 -0
  17. package/dist-server/service/assignment/applied-when.js.map +1 -0
  18. package/dist-server/service/assignment/assignment-claim.d.ts +111 -0
  19. package/dist-server/service/assignment/assignment-claim.js +160 -0
  20. package/dist-server/service/assignment/assignment-claim.js.map +1 -0
  21. package/dist-server/service/assignment/assignment-history.d.ts +87 -0
  22. package/dist-server/service/assignment/assignment-history.js +250 -0
  23. package/dist-server/service/assignment/assignment-history.js.map +1 -0
  24. package/dist-server/service/assignment/assignment-write.d.ts +25 -0
  25. package/dist-server/service/assignment/assignment-write.js +69 -0
  26. package/dist-server/service/assignment/assignment-write.js.map +1 -0
  27. package/dist-server/service/equipment/equipment-master-resolver.d.ts +9 -1
  28. package/dist-server/service/equipment/equipment-master-resolver.js +6 -3
  29. package/dist-server/service/equipment/equipment-master-resolver.js.map +1 -1
  30. package/dist-server/service/index.d.ts +3 -0
  31. package/dist-server/service/index.js +3 -0
  32. package/dist-server/service/index.js.map +1 -1
  33. package/dist-server/service/location/location-master-resolver.d.ts +4 -1
  34. package/dist-server/service/location/location-master-resolver.js +6 -3
  35. package/dist-server/service/location/location-master-resolver.js.map +1 -1
  36. package/dist-server/service/master-crud.d.ts +11 -0
  37. package/dist-server/service/master-crud.js +12 -1
  38. package/dist-server/service/master-crud.js.map +1 -1
  39. package/dist-server/service/material/material-revision-service.js +13 -0
  40. package/dist-server/service/material/material-revision-service.js.map +1 -1
  41. package/dist-server/service/recipe/recipe-revision-service.js +13 -0
  42. package/dist-server/service/recipe/recipe-revision-service.js.map +1 -1
  43. package/dist-server/service/test-spec/test-spec-revision-service.js +14 -0
  44. package/dist-server/service/test-spec/test-spec-revision-service.js.map +1 -1
  45. package/dist-server/tsconfig.tsbuildinfo +1 -1
  46. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"master-page-base.js","sourceRoot":"","sources":["../../../client/pages/master/master-page-base.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport '@operato/data-grist/ox-grist.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { property, query, state } from 'lit/decorators.js'\n\nimport { DataGrist, FetchOption, getEditor } from '@operato/data-grist'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { p13n } from '@operato/p13n'\n\nimport type { ReferenceOptions } from './grist-reference.js'\nimport { PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\n\nimport { ImportProblem, patchesFor, readImportRows } from './import-rows.js'\n\n/**\n * 마스터 화면이 함께 쓰는 것 — **코드 재사용은 여기서, 화면은 각자.**\n *\n * ── 왜 화면을 갈랐나 (2026-09-01) ──────────────────────────────────────────\n * 전에는 화면 하나가 `?axis=` 로 열두 축을 다 맡았다. 근거는 「열두 축이 하는 일이 같다」였는데, 그\n * 말은 **지금 자리표시자라서 같아 보이는 것**이었다. 선언을 읽으면 이미 갈라져 있다.\n *\n * 정지 사유 · 위치 계층(parentName · parentId) — 표가 아니라 나무여야 한다\n * 등급 넷 baseIds 다중 상속 — 쉼표 문자열로 편집 중이다\n * 인원 · 설비 · 자산 근무 캘린더 · 검사 결과를 JSON 원문으로 보인다\n * 자재 GTIN · 단위 · 보관처의 칸이 없어 properties JSON 이다\n * 공정 duration 이 ISO 기간, bizStep 이 EPCIS 어휘다\n * 검사 기준 컬럼이 0개다 — 항목 · 상하한 · 단위 · 방법이 들어와야 한다\n *\n * 그 자리표시자를 걷으면 열둘이 서로 다른 화면이 된다. 공용 화면에 「이 축만」 분기를 열둘 넣는 것은\n * 화면 열둘보다 나쁘다.\n *\n * ── UX 일관성과 일괄 개발 편의는 다르다 ────────────────────────────────────\n * 일관성은 **쓰는 사람이 겪는 것**이다. 같은 뜻이 같은 자리에, 같은 용어로, 같은 조작으로. 그것은\n * 어휘와 배치와 부품을 공유해서 얻는다.\n *\n * 일괄 개발의 편의는 **만드는 쪽이 겪는 것**이고, 기반 클래스로 얻으면 된다. 그것을 얻으려고 서로 다른\n * 대상을 한 화면에 묶으면 편의는 만드는 쪽이 가져가고 값은 쓰는 쪽이 치른다.\n *\n * ── MDI 에서도 갈려야 한다 ─────────────────────────────────────────────────\n * MDI 탭은 페이지 **요소**로 나뉜다(`page-mdibar.ts`). 한 라우트면 인원과 설비를 나란히 열 수 없는데,\n * 마스터 작업의 대부분이 두 항목을 비교하며 옮겨 적는 일이다. 품목을 보면서 품목 등급을 채운다.\n *\n * ── 이 클래스가 갖는 것과 갖지 않는 것 ─────────────────────────────────────\n * 갖는 것: 화면 골격 · 컬럼 설정 · 가져오기 · 저장 · 컬럼 하나를 그리는 방식.\n * 갖지 않는 것: **무엇을 보이나.** 그것은 각 화면이 `spec` 으로 말한다.\n */\n\nexport type MasterColumnType =\n | 'string'\n | 'number'\n | 'boolean'\n | 'date'\n | 'json'\n | 'list'\n /** 정해진 값 중에서 고른다. `options` 를 함께 준다. */\n | 'select'\n /**\n * ISO 8601 기간 — `PT30S`.\n *\n * 이름이 `duration` 이 아니다. 그 이름은 초 숫자용으로 이미 임자가 있다(`worklist`). 등록소를\n * 앱 전체가 나눠 쓰므로, 이름이 겹치면 나중에 부팅한 쪽이 오류보고없이 이긴다.\n */\n | 'iso-duration'\n /** 다른 표의 이름 하나. `reference` 를 함께 준다. */\n | 'reference'\n | 'reference-list'\n\nexport interface MasterColumn {\n name: string\n /** 화면 문구 키. `field.<이것>`. */\n label: string\n type: MasterColumnType\n /**\n * `select` 가 고를 값들.\n *\n * **여기에 손으로 적지 않는다.** `master-options.ts` 가 계약에서 가져오거나, 계약이 타입으로만\n * 주는 것은 컴파일러가 계약에 묶은 거울에서 가져온다. 화면에 값을 베껴 적으면 커널이 값을 늘렸을\n * 때 화면만 옛 목록을 보인다.\n */\n options?: readonly string[]\n /**\n * `reference` 가 어느 표에서 고르나.\n *\n * 저장되는 것은 **이름**이지 id 가 아니다 — 커널이 이름으로 주소를 잡는다.\n */\n reference?: ReferenceOptions\n width?: number\n /** 고칠 수 없는 칸. 자동으로 붙는 값이 여기 온다. */\n readonly?: boolean\n /**\n * 가로로 스크롤해도 화면에 남는 컬럼.\n *\n * 컬럼이 열 개를 넘고 폭 합이 1000px 을 넘는 축이 있다. 오른쪽 끝을 고치려고 스크롤하면 이름이 화면\n * 밖으로 나가서 **어느 행을 고치는지 알 수 없다.**\n */\n fixed?: boolean\n /**\n * 표에 그리지 않는다 — **검색에는 남는다.**\n *\n * 같은 사실을 두 번 보이지 않으려고 감추는 자리가 있다. 로케이션의 상위는 트리의 들여쓰기가 말하므로\n * 컬럼으로 또 보일 필요가 없지만, 「LINE-1 아래 있는 것」을 찾는 일은 있다.\n *\n * `searchText` 는 `filter: 'search'` 를 선언한 컬럼마다 필터를 만들고 hidden 을 보지 않는다\n * (`data-grist.ts` 의 `set searchText`). 그래서 감춘 컬럼도 검색에는 참여한다.\n *\n * 다만 컬럼 설정 목록에서는 **빠진다** — personalizer 가 페이지 설정의 hidden 을 걸러낸다\n * (`ox-grist-personalizer.ts:51`). 사용자가 켜야 할 컬럼에는 쓰지 않는다.\n */\n hidden?: boolean\n}\n\n/** 각 마스터 화면이 자기에 대해 말하는 것. */\nexport interface MasterSpec {\n /** 컬럼 설정을 사람마다 따로 저장하는 키. 화면마다 달라야 한다. */\n key: string\n /** 제목 문구 키. `title.<이것>`. */\n title: string\n /**\n * 도움말 문서 키 — `helps/<이것>.md`.\n *\n * `ox-page-title-bar` 가 이 값이 있으면 제목 옆에 도움말 아이콘을 그린다. 상세한 설명은 그 문서가\n * 갖는다 — 화면에 형식적인 소개 문장을 붙이는 것은 자리만 차지하고 도움이 안 된다.\n */\n help: string\n /** GraphQL 조회 이름 — 서버가 낸 그대로. */\n query: string\n /** GraphQL 저장 이름. */\n mutation: string\n columns: MasterColumn[]\n}\n\n/** 열두 축이 공통으로 갖는 유효 기간 — ISA-95. */\nexport const EFFECTIVE: MasterColumn[] = [\n { name: 'effectiveStart', label: 'effective-start', type: 'date', width: 150 },\n { name: 'effectiveEnd', label: 'effective-end', type: 'date', width: 150 }\n]\n\n/** 이름과 설명은 열두 축이 모두 갖는다. 이름은 스크롤해도 남는다. */\nexport const IDENTITY: MasterColumn[] = [\n { name: 'name', label: 'name', type: 'string', width: 160, fixed: true },\n { name: 'description', label: 'description', type: 'string', width: 200 }\n]\n\nexport abstract class MasterPageBase extends p13n(localize(i18next)(PageView)) {\n static styles = [\n /* The work surface frame for a list screen (ADR-0060 decision 8). */\n WorkSurfacePageStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n\n ox-grist {\n overflow-y: auto;\n flex: 1;\n }\n\n /*\n * 헤드룸에는 「어디를 보고 있나」만 남는다. 검색은 타이틀 줄로 올라갔다(configOf 의 search).\n *\n * 범위가 없는 화면은 이 칸이 비고, 아래 규칙이 그 줄을 아예 감춘다. 빈 띠를 남기지 않는다.\n */\n .header {\n grid-template-areas: 'scope';\n grid-template-columns: 1fr;\n }\n\n .header:empty {\n display: none;\n }\n `\n ]\n\n /** 이 화면이 자기에 대해 말하는 것. 화면마다 자기 것을 준다. */\n protected abstract get spec(): MasterSpec\n\n @property({ type: Boolean }) active: boolean = false\n @property({ type: Object }) gristConfig: any\n @property({ type: String }) mode: 'CARD' | 'LIST' | 'GRID' = isMobileDevice() ? 'CARD' : 'GRID'\n\n @state() protected ready = false\n\n @query('ox-grist') protected grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t(`title.${this.spec.title}`),\n /*\n * **파일로 한 번에 올린다.**\n *\n * 새 공장을 받으면 기준정보를 통째로 넣어야 한다. 자리 · 인원 · 설비가 각각 수백에서 수천\n * 줄이고, 화면에서 한 줄씩 넣으면 인원 1000명에 다섯 시간을 타이핑만 한다. 그런데 고객은\n * 그 자료를 이미 엑셀로 갖고 있다.\n *\n * 틀은 플랫폼이 갖고 있다(`@things-factory/import-ui`). 도구막대에 버튼을 달고 파일을\n * 읽어 줄의 배열로 준다. 우리는 그 줄을 이 화면의 열에 맞추고 저장하기만 한다.\n *\n * **이 자리에 두면 마스터 화면 전부가 한꺼번에 된다.** 화면마다 따로 만들면 열넷이 조금씩\n * 다르게 동작한다.\n */\n importable: { handler: this.importHandler.bind(this) },\n /* 제목 옆의 도움말 아이콘 — 상세한 설명은 그 문서가 갖는다. */\n help: this.spec.help,\n /*\n * 검색은 **페이지 타이틀 줄의 오른쪽 끝**에 둔다.\n *\n * 타이틀은 페이지 안의 제목 줄에 선다(ADR-0060 결정 9, 모든 앱이 같다). 그래서 그\n * 줄은 상단 앱 바가 아니라 페이지의 첫 줄이고, 거기 검색을 두면 거르는 곳과 걸러진 결과가 붙어\n * 있다 — 전에 상단 바를 마다한 이유가 여기서는 생기지 않는다.\n *\n * `ox-page-title-bar` 가 이 값을 받아 타이틀 오른쪽에 그린다. 그래서 헤드룸에는 검색을 두지\n * 않는다 — 두 벌이 되고, 두 벌은 어긋난다.\n */\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n actions: [\n {\n title: i18next.t('button.save'),\n action: this.save.bind(this),\n icon: 'save',\n /* 이 화면의 주 동작 — 채운 버튼으로 그려진다. 한 화면에 하나만 둔다. */\n emphasis: { raised: true }\n }\n ],\n exportable: {\n name: i18next.t(`title.${this.spec.title}`),\n data: () => this.grist.exportRecords()\n }\n }\n }\n\n /**\n * 지금 무엇을 보고 있는가 — 표 바로 위.\n *\n * 계층을 드릴다운으로 걷는 화면은 「어디를 보고 있나」를 말해야 한다. 대부분의 화면은 표 하나가 전부라\n * 범위가 없다 — 그런 화면은 아무것도 그리지 않고, 그 줄이 접힌다.\n */\n protected renderScope() {\n return html``\n }\n\n /**\n * 화면 골격 — 표 하나와 컬럼 설정.\n *\n * 자기 인터페이스가 필요한 화면은 이것을 덮어쓴다. 위치와 정지 사유는 계층이라 표가 아니라 나무여야\n * 하고, 그때 이 render 를 쓰지 않는다.\n */\n render() {\n return html`\n <ox-grist\n .mode=${this.mode}\n .config=${this.gristConfig}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider(`ox-grist-${this.spec.key}`)!}\n >\n <!--\n 자리를 뜻으로 구분한다.\n\n 어디를 보고 있나 빵가루 헤드룸 — 표 바로 위\n 좁히는 것 검색 타이틀 줄 오른쪽 끝\n 저지르는 것 저장 · 삭제 화면 바닥 — 셸이 모든 화면에 같은 자리로 둔다\n 고른 것에 하는 것 선택 후 동작 고른 것 옆\n\n 바닥인 이유는 목록을 위에서 아래로 훑고 줄을 고르고 저지르기 때문이다. 손과 눈이 아래에서\n 끝난다. 전에 동작 바를 헤드룸에도 두었다가 「저장」이 위아래에 하나씩 생겼다(실측: top 150 과\n top 720).\n -->\n <div slot=\"headroom\" class=\"header\">${this.renderScope()}</div>\n\n <!-- 컬럼 설정 버튼. provider 만 넘겨도 버튼은 안 생긴다. 이 요소를 화면이 놓아야 한다. -->\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n `\n }\n\n /**\n * 주소 끝의 이름 조각을 **목록 좁히기**에 쓰나.\n *\n * 다른 화면의 참조 칸에서 걸어 들어오면 주소가 `.../ops-master-material-definition/M0161-0003` 이\n * 된다. 그 조각을 아무도 안 읽으면 106줄짜리 목록 첫 장이 뜨고, 사람은 방금 누른 이름을 다시\n * 찾아야 한다 — 걸어온 보람이 없다.\n *\n * 자리 화면과 제조법 화면은 그 조각을 **파고들기**에 이미 쓰고 있다(그 이름 아래를 보여 준다).\n * 그 둘은 여기서 빠진다 — 두 뜻이 한 조각에 겹치면 어느 쪽도 제대로 안 된다.\n */\n protected get pathSegmentNarrowsList(): boolean {\n return true\n }\n\n async pageInitialized() {\n this.gristConfig = this.configOf()\n this.ready = true\n }\n\n /**\n * 셸은 `(changes, after, before)` 를 넘긴다 — 인수 없는 모양으로 좁히면 주소를 읽어야 하는 화면이\n * 이것을 덮어쓸 수 없다(`after.resourceId` 와 `before.resourceId` 를 비교하는 자리다).\n */\n async pageUpdated(_changes?: any, _after?: any, _before?: any) {\n if (this.active) {\n this.grist?.fetch()\n }\n }\n\n /*\n * 반환 타입을 밝힌다. 적지 않으면 이 자리의 리터럴이 그대로 타입이 되고, 컬럼 구성을 바꾸는 화면이\n * 이것을 재정의할 수 없다(로케이션이 실제로 막혔다).\n */\n protected configOf(): any {\n return {\n /*\n * 빈 목록이 하는 말 — **축마다 다르다.**\n *\n * 「아직 아무것도 없습니다」 하나로는 이 화면들이 할 말을 못 한다. 기준정보는 처음 오는\n * 사람이 가장 먼저 만나는 자리이고, 비어 있는 사람이 묻는 것은 「이게 뭔데 채워야 하나」다.\n * 그 답은 축마다 다르다 — 인원이 없으면 작업 스테이션에서 사람을 고를 수 없고, 근무 캘린더가\n * 없으면 가동률이 「모름」이 된다.\n *\n * 항목 파일을 고치지 않는다. spec.key 로 용어를 끌어오므로 용어만 더하면 새 축도 따라온다.\n */\n emptyNote: {\n title: i18next.t(`text.master-empty-${this.spec.key}`),\n description: i18next.t(`text.master-empty-${this.spec.key}-hint`, { defaultValue: '' }) || undefined\n },\n list: { fields: ['name', 'description'], details: [] },\n columns: [\n /* 이름을 고정하면 그 왼쪽의 거터도 함께 남아야 한다 — 아니면 체크박스가 이름 아래로 밀린다. */\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n { type: 'gutter', gutterName: 'row-selector', multiple: true, fixed: true },\n ...this.spec.columns.map(column => this.columnOf(column))\n ],\n /* 새 행을 만들 수 있다 — 마스터를 채우는 것이 이 화면의 일이다. */\n rows: { appendable: true, selectable: { multiple: true } },\n sorters: [{ name: 'name', desc: false }]\n }\n }\n\n protected columnOf(column: MasterColumn) {\n const header = i18next.t(`field.${column.label}`)\n const editable = !column.readonly\n const fixed = column.fixed\n const hidden = column.hidden\n\n switch (column.type) {\n case 'number':\n return { type: 'float', name: column.name, header, record: { editable }, sortable: true, width: column.width, fixed, hidden }\n case 'boolean':\n return { type: 'boolean', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n case 'date':\n return { type: 'datetime', name: column.name, header, record: { editable }, sortable: true, width: column.width, fixed, hidden }\n /*\n * 목록과 JSON 은 **글자로 고친다.** 전용 편집기를 만들면 축마다 다른 편집기가 생기고, 그 편집기가\n * 모르는 칸이 들어오면 값이 오류 보고 없이 사라진다. 지금은 있는 그대로 보이고 그대로 고친다.\n *\n * 이것은 **자리표시자다.** 화면이 갈라진 지금은 그 축의 화면이 자기 편집기를 가질 수 있다.\n */\n case 'select':\n /*\n * **빈 줄이 없으면 되돌릴 수 없다.** 한 번 고르면 「아직 안 정했다」로 못 돌아간다. 비워 둘\n * 수 없는 칸이면 화면 쪽에서 `options` 에 빈 값을 안 넣으면 된다.\n */\n return {\n type: 'select',\n name: column.name,\n header,\n record: { editable, options: column.options ?? [] },\n filter: 'select',\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n case 'reference':\n /*\n * 다른 표에서 골라 온다. 자유 텍스트로 두면 `GUNSAN` 을 `GUNSAM` 이라고 쳐도 저장되고,\n * 그 뒤로 그 참조는 어디에서도 안 걸린다 — 화면에는 값이 있는데.\n *\n * 고르기는 팝업이 하고, 검색은 그 팝업이 갖는다. 그래서 열 자체에는 검색 필터를 안 붙인다.\n */\n return {\n type: 'reference',\n name: column.name,\n header,\n /*\n * **제목을 여기서 채운다.** 팝업 머리에 글자가 없으면 회색 띠에 검색창만 뜨고, 무엇을\n * 고르는 중인지 화면이 말하지 않는다. 편집기 자리에서는 못 채운다 — 그리드가 머리말을\n * `{ renderer }` 객체로 바꿔서 넘기기 때문에 거기서는 글자를 볼 수 없다.\n *\n * 열의 머리말을 그대로 쓴다. 「주 근무 로케이션」을 고르는 팝업의 제목이 「주 근무\n * 로케이션」이면 둘이 어긋날 수가 없다.\n */\n record: { editable, reference: { title: header, ...column.reference } },\n filter: 'search',\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n case 'iso-duration':\n /*\n * ISO 8601 기간(`PT30S`)이다. 글자로 고치면 `30s` 나 `PT30` 처럼 커널이 못 읽는 것이\n * 오류 보고 없이 저장된다. 전용 편집기가 초로 받아 표기를 만든다.\n *\n * 이 편집기는 기본 등록에 없다 — `bootstrap.ts` 가 등록한다.\n */\n return { type: 'iso-duration', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n /*\n * JSON 은 **구조를 아는 편집기로 고친다.** 글자로 고치면 괄호 하나가 빠져도 저장 순간까지\n * 모르고, 저장되면 그 칸을 읽는 쪽이 통째로 못 읽는다. `json5` 는 팝업에서 문법을 보아 준다.\n */\n case 'json':\n return { type: 'json5', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n /*\n * 목록은 아직 **글자로 고친다.** 이 칸들은 대개 다른 표의 이름을 담는데(등급 · 공정 · 상위\n * 등급), 그 표에서 골라 오는 편집기가 아직 없다. 자리표시자다.\n */\n /*\n * ── 여럿을 고르는 참조 ──────────────────────────────────────────────────\n *\n * 상위 등급 · 요구 검사 · 공정의 차례처럼 **관계를 여럿 다는 칸**이다. 그리는 것은 목록과\n * 같고(ops-list), 고치는 것은 참조 팝업이 여럿 고르기로 연다.\n *\n * 전에는 이런 칸이 전부 `list` 였다 — 즉 자유 텍스트였다. 그래서 GUNSAN 을 GUNSAM 이라고\n * 쳐도 저장되고, 그 뒤로 그 참조는 어디에서도 안 걸린다. 열 곳이 그 상태였다\n * (2026-09-05 · 사용자 지시로 전부 바꿈).\n */\n case 'reference-list':\n return {\n type: 'ops-list',\n name: column.name,\n header,\n record: {\n editable,\n /*\n * 편집기는 참조 팝업이 맡는다 — 그리기만 목록 렌더러를 쓴다.\n *\n * 열 빌더는 `column.type` 으로 편집기를 고르므로(column-builder), 여기서 함수를\n * 직접 준다. 문자열을 주면 무시되고 글자 편집기가 붙는다.\n */\n editor: getEditor('reference'),\n /*\n * 팝업으로 연다고 밝힌다. 표식(edit_square)의 판단이 열 타입을 보는데, 이 열은\n * 그리기를 목록 렌더러로 하려고 타입이 `ops-list` 다 — 그대로 두면 칸 안에서\n * 고치는 것으로 표시된다.\n */\n opensPopup: true,\n reference: { title: header, multiple: true, ...column.reference }\n },\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n case 'list':\n /*\n * 그리기만 우리 것을 쓰고 고치기는 글자 그대로다(`ops-list` 는 렌더러만 등록한다).\n * 기본 글자 렌더러는 배열을 구분 없이 이어 붙인다.\n */\n return { type: 'ops-list', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n default:\n return {\n type: 'string',\n name: column.name,\n header,\n record: { editable },\n filter: 'search',\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n }\n }\n\n async fetchHandler({ page = 1, limit = 50, sortings = [], filters = [] }: FetchOption) {\n const fields = this.spec.columns.map(column => column.name).join('\\n')\n\n /*\n * ── 주소 끝의 이름 조각으로 좁힌다 ──────────────────────────────────────\n *\n * 다른 화면의 참조 칸에서 걸어 들어오면 주소가 `.../ops-master-material-definition/M0161-0003`\n * 이 된다. 그 조각을 아무도 안 읽으면 106줄짜리 목록 첫 장이 뜨고, 사람은 방금 누른 이름을 다시\n * 찾아야 한다 — 걸어온 보람이 없다.\n *\n * **그리드의 검색어를 세우는 방식은 안 된다.** 그렇게 해 봤더니 값은 실제로 세워지는데(기억에\n * 남았다) 그 뒤 그리드가 설정을 복원하면서 지웠다. 훅 자리를 앞뒤로 옮겨도 이기지 못하는\n * 경합이다. 질의를 만드는 이 자리에서 조건을 얹으면 경합이 없다 — 언제 불려도 같은 답이다.\n * [확인함] 2026-09-04\n *\n * 자리 화면과 제조법 화면은 그 조각을 **파고들기**에 이미 쓰므로 여기서 빠진다.\n */\n const segment = this.pathSegmentNarrowsList ? (this as any).lifecycle?.resourceId : undefined\n\n if (segment) {\n filters = [...filters, { name: 'name', operator: 'eq', value: decodeURIComponent(segment) }]\n }\n\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n ${this.spec.query}(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n ${fields}\n }\n total\n }\n }\n `,\n variables: { filters, pagination: { page, limit }, sortings },\n fetchPolicy: 'no-cache'\n })\n\n const payload = response.data?.[this.spec.query]\n const records = (payload?.items ?? []).map((item: any) => this.spreadJson(item))\n\n return { total: payload?.total ?? 0, records }\n }\n\n /**\n * JSON 칸을 **사람이 고칠 수 있는 글자로 편다.**\n *\n * 서버는 구조를 구조로 낸다(`ScalarObject` 로 선언하지만 **스키마에 나가는 이름은 `Object`** 다) — 그것이\n * 사실이고, 곧 붙을 전용 편집기가 원하는 모양이다.\n * 그런데 지금 이 그리드의 JSON 칸은 글자 편집기라 객체를 그대로 주면 `[object Object]` 가 된다.\n * 그래서 **화면에 들어올 때만** 편다. 스키마를 화면 사정에 맞추지 않고, 화면이 자기 몫을 한다.\n *\n * 이 옮김은 `packJson` 과 짝이다. 한쪽만 고치면 저장이 오류보고없이 망가진다.\n */\n protected spreadJson(item: any): any {\n const jsonColumns = this.spec.columns.filter(column => column.type === 'json')\n\n if (!jsonColumns.length) {\n return item\n }\n\n const spread = { ...item }\n\n for (const column of jsonColumns) {\n const value = spread[column.name]\n\n /* 비어 있는 것은 비어 있는 채로 둔다 — `null` 을 `\"null\"` 로 만들면 지운 것이 값이 된다. */\n if (value === undefined || value === null || typeof value === 'string') {\n continue\n }\n\n spread[column.name] = JSON.stringify(value, null, 0)\n }\n\n return spread\n }\n\n /**\n * 저장하기 전에 JSON 칸의 글자를 **구조로 되읽는다.**\n *\n * 읽지 못하면 **보내지 않는다.** 그냥 보내면 글자가 통째로 값이 되어, 사람이 적은 구조가 사라진 채로\n * 저장이 성공했다고 나온다. 어느 행 어느 칸인지 말해 주어야 고칠 수 있다.\n */\n protected packJson(patches: any[]): { patches: any[]; problems: string[] } {\n const jsonColumns = this.spec.columns.filter(column => column.type === 'json')\n\n if (!jsonColumns.length) {\n return { patches, problems: [] }\n }\n\n const problems: string[] = []\n\n const packed = patches.map((patch, index) => {\n const one = { ...patch }\n\n for (const column of jsonColumns) {\n const value = one[column.name]\n\n if (typeof value !== 'string') {\n /* 손대지 않은 칸은 이미 구조다. 비운 칸은 비운 채로 간다. */\n continue\n }\n\n if (value.trim() === '') {\n /* 비운 것은 「없앤다」는 뜻이다. 빈 글자로 보내면 칸의 뜻이 흐려진다. */\n one[column.name] = null\n continue\n }\n\n try {\n one[column.name] = JSON.parse(value)\n } catch (error) {\n problems.push(\n i18next.t('text.json-column-unreadable', {\n row: index + 1,\n column: i18next.t(`field.${column.label}`),\n detail: (error as Error).message\n })\n )\n }\n }\n\n return one\n })\n\n return { patches: packed, problems }\n }\n\n /**\n * 파일로 올린 줄들을 이 화면의 마스터로 넣는다.\n *\n * ── 오류보고없이 버리지 않는다 ─────────────────────────────────────────────────\n * 모르는 열 이름, 이름이 없는 줄, 중복된 이름을 **알린다.** 오류보고없이 빼면 사람은 다 올라간 줄 알고,\n * 빠진 것은 나중에 「그 설비가 왜 없지」로 나타난다.\n *\n * ── 하나라도 틀리면 아무것도 안 보낸다 ───────────────────────────────────\n * 절반만 들어가면 무엇이 들어갔는지 사람이 모른다. 파일을 고쳐 다시 올리는 편이 낫다.\n *\n * ── 새로 만들 것과 고칠 것을 이름으로 구분한다 ─────────────────────────────\n * 서버는 `+`(새로)와 `M`(고침, id 필요)을 나눠 받는다. 그러니 **올리기 전에 그 이름들이 이미\n * 있는지 물어야 한다.** 안 물으면 같은 이름이 두 벌 생긴다.\n */\n protected async importHandler(records: any[]) {\n if (!records?.length) {\n notify({ message: i18next.t('text.nothing-to-import'), level: 'info' })\n return\n }\n\n const byHeader = this.headerIndex()\n const reading = readImportRows(records, header => byHeader.get(header))\n\n if (reading.problems.length) {\n /* 열 줄까지만 보인다 — 파일이 통째로 어긋나면 문제도 줄 수만큼 나온다. */\n notify({ message: reading.problems.slice(0, 10).map(problem => this.sayProblem(problem)).join('\\n'), level: 'error' })\n return\n }\n\n const existing = await this.idsByName(reading.rows.map(row => String(row.name)))\n const patches = patchesFor(reading.rows, name => existing.get(name))\n\n const packed = this.packJson(patches)\n\n if (packed.problems.length) {\n notify({ message: packed.problems.slice(0, 10).join('\\n'), level: 'error' })\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($patches: [Object!]!) {\n ${this.spec.mutation}(patches: $patches) {\n id\n }\n }\n `,\n variables: { patches: packed.patches }\n })\n\n /*\n * 이 앱의 클라이언트는 서버가 거절해도 던지지 않고 `errors` 에 담아 준다. 여기서 보지 않으면\n * 아래의 「올렸다」가 실패한 뒤에도 그대로 뜬다. 사유는 앱의 공통 오류 처리가 이미 띄우므로\n * 여기서 다시 띄우지 않는다 — 같은 말이 두 번 뜬다.\n */\n if (response?.errors?.length) {\n return\n }\n\n const made = patches.filter(patch => patch.cuFlag === '+').length\n\n notify({\n message: i18next.t('text.import-done', { made, changed: patches.length - made }),\n level: 'info'\n })\n\n this.grist.fetch()\n }\n\n /**\n * 파일의 머리글을 이 화면의 열 이름으로 바꾼다.\n *\n * **두 가지를 다 받는다** — 열 이름 그대로(`homeLocation`)와 화면에 보이는 말(`홈 자리`).\n * 사람이 이 화면에서 내려받아 고쳐 올리면 머리글이 화면의 말이고, 다른 시스템에서 뽑아 오면\n * 열 이름이다. 한쪽만 받으면 다른 쪽이 통째로 「모르는 열」이 된다.\n */\n /** 무엇이 잘못됐는지를 사람에게 보일 문장으로 바꾼다. 줄 번호는 엑셀에서 보는 그 번호다. */\n private sayProblem(problem: ImportProblem): string {\n switch (problem.kind) {\n case 'unknown-column':\n return i18next.t('error.import-unknown-column', { line: problem.line, column: problem.column })\n case 'no-name':\n return i18next.t('error.import-no-name', { line: problem.line })\n case 'duplicate-name':\n return i18next.t('error.import-duplicate-name', { line: problem.line, name: problem.name })\n }\n }\n\n private headerIndex(): Map<string, string> {\n const found = new Map<string, string>()\n\n for (const column of this.spec.columns) {\n found.set(column.name, column.name)\n found.set(i18next.t(`field.${column.name}`), column.name)\n }\n\n return found\n }\n\n /** 이 이름들이 이미 있나 — 있으면 그 `id`. 없는 이름은 지도에 없다. */\n private async idsByName(names: string[]): Promise<Map<string, string>> {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination) {\n ${this.spec.query}(filters: $filters, pagination: $pagination) {\n items {\n id\n name\n }\n }\n }\n `,\n variables: {\n filters: [{ name: 'name', operator: 'in', value: names }],\n /* 올린 줄 수만큼만 돌아오면 된다. 기본 50으로 두면 51번째부터 새로 만들어 두 벌이 된다. */\n pagination: { page: 1, limit: names.length }\n },\n fetchPolicy: 'no-cache'\n })\n\n return new Map(\n (response.data?.[this.spec.query]?.items ?? []).map((item: any) => [item.name, item.id])\n )\n }\n\n protected async save() {\n const patches = this.grist.dirtyRecords\n\n if (!patches?.length) {\n notify({ message: i18next.t('text.nothing-changed'), level: 'info' })\n return\n }\n\n const packed = this.packJson(patches)\n\n if (packed.problems.length) {\n /* 하나도 보내지 않는다. 절반만 저장되면 화면과 저장된 것이 달라진다. */\n notify({ message: packed.problems.join('\\n'), level: 'error' })\n return\n }\n\n const response = await client.mutate({\n /*\n * **타입 이름은 `Object` 다.**\n *\n * 서버가 `ScalarObject` 라는 이름으로 선언하지만 스키마에 나가는 이름은 `Object` 이고,\n * `ScalarObject` 로 적으면 「Unknown type」으로 거절된다. 2026-09-01부터 이 자리가 그렇게\n * 적혀 있어 **모든 기준정보 화면의 저장이 안 됐다.** 화면은 오류를 그대로 보여 주지도\n * 않았다 — 이 앱의 클라이언트는 서버가 거절해도 던지지 않는다.\n */\n mutation: gql`\n mutation ($patches: [Object!]!) {\n ${this.spec.mutation}(patches: $patches) {\n id\n name\n }\n }\n `,\n variables: { patches: packed.patches }\n })\n\n /*\n * 이 클라이언트는 서버가 거절해도 던지지 않는다 — 오류는 `response.errors` 에 담겨 온다.\n * 보지 않으면 저장에 실패해도 화면이 목록을 다시 읽어 「저장된 것처럼」 보인다.\n * 사유 자체는 앱의 공통 오류 처리가 띄운다.\n */\n if (response.errors?.length) {\n return\n }\n\n /* 지운 행은 사라지지 않고 유효 기간의 끝이 채워진다. 말해 주지 않으면 저장이 안 된 것으로 읽는다. */\n if (packed.patches.some((patch: any) => String(patch.cuFlag).toUpperCase() === '-')) {\n notify({ message: i18next.t('text.retired-not-deleted'), level: 'info' })\n }\n\n this.grist.fetch()\n }\n}\n"]}
1
+ {"version":3,"file":"master-page-base.js","sourceRoot":"","sources":["../../../client/pages/master/master-page-base.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport '@operato/data-grist/ox-grist.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { property, query, state } from 'lit/decorators.js'\n\nimport { DataGrist, FetchOption, getEditor } from '@operato/data-grist'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify, openPopup } from '@operato/layout'\nimport { p13n } from '@operato/p13n'\n\nimport type { ReferenceOptions } from './grist-reference.js'\nimport { PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\n\nimport { ImportProblem, patchesFor, readImportRows } from './import-rows.js'\nimport { AssignmentChange, assignmentChangesIn } from './assignment-fields.js'\nimport { AppliedAnswer } from './applied-when-dialog.js'\nimport './applied-when-dialog.js'\n\n/**\n * 마스터 화면이 함께 쓰는 것 — **코드 재사용은 여기서, 화면은 각자.**\n *\n * ── 왜 화면을 갈랐나 (2026-09-01) ──────────────────────────────────────────\n * 전에는 화면 하나가 `?axis=` 로 열두 축을 다 맡았다. 근거는 「열두 축이 하는 일이 같다」였는데, 그\n * 말은 **지금 자리표시자라서 같아 보이는 것**이었다. 선언을 읽으면 이미 갈라져 있다.\n *\n * 정지 사유 · 위치 계층(parentName · parentId) — 표가 아니라 나무여야 한다\n * 등급 넷 baseIds 다중 상속 — 쉼표 문자열로 편집 중이다\n * 인원 · 설비 · 자산 근무 캘린더 · 검사 결과를 JSON 원문으로 보인다\n * 자재 GTIN · 단위 · 보관처의 칸이 없어 properties JSON 이다\n * 공정 duration 이 ISO 기간, bizStep 이 EPCIS 어휘다\n * 검사 기준 컬럼이 0개다 — 항목 · 상하한 · 단위 · 방법이 들어와야 한다\n *\n * 그 자리표시자를 걷으면 열둘이 서로 다른 화면이 된다. 공용 화면에 「이 축만」 분기를 열둘 넣는 것은\n * 화면 열둘보다 나쁘다.\n *\n * ── UX 일관성과 일괄 개발 편의는 다르다 ────────────────────────────────────\n * 일관성은 **쓰는 사람이 겪는 것**이다. 같은 뜻이 같은 자리에, 같은 용어로, 같은 조작으로. 그것은\n * 어휘와 배치와 부품을 공유해서 얻는다.\n *\n * 일괄 개발의 편의는 **만드는 쪽이 겪는 것**이고, 기반 클래스로 얻으면 된다. 그것을 얻으려고 서로 다른\n * 대상을 한 화면에 묶으면 편의는 만드는 쪽이 가져가고 값은 쓰는 쪽이 치른다.\n *\n * ── MDI 에서도 갈려야 한다 ─────────────────────────────────────────────────\n * MDI 탭은 페이지 **요소**로 나뉜다(`page-mdibar.ts`). 한 라우트면 인원과 설비를 나란히 열 수 없는데,\n * 마스터 작업의 대부분이 두 항목을 비교하며 옮겨 적는 일이다. 품목을 보면서 품목 등급을 채운다.\n *\n * ── 이 클래스가 갖는 것과 갖지 않는 것 ─────────────────────────────────────\n * 갖는 것: 화면 골격 · 컬럼 설정 · 가져오기 · 저장 · 컬럼 하나를 그리는 방식.\n * 갖지 않는 것: **무엇을 보이나.** 그것은 각 화면이 `spec` 으로 말한다.\n */\n\nexport type MasterColumnType =\n | 'string'\n | 'number'\n | 'boolean'\n | 'date'\n | 'json'\n | 'list'\n /** 정해진 값 중에서 고른다. `options` 를 함께 준다. */\n | 'select'\n /**\n * ISO 8601 기간 — `PT30S`.\n *\n * 이름이 `duration` 이 아니다. 그 이름은 초 숫자용으로 이미 임자가 있다(`worklist`). 등록소를\n * 앱 전체가 나눠 쓰므로, 이름이 겹치면 나중에 부팅한 쪽이 오류보고없이 이긴다.\n */\n | 'iso-duration'\n /** 다른 표의 이름 하나. `reference` 를 함께 준다. */\n | 'reference'\n | 'reference-list'\n\nexport interface MasterColumn {\n name: string\n /** 화면 문구 키. `field.<이것>`. */\n label: string\n type: MasterColumnType\n /**\n * `select` 가 고를 값들.\n *\n * **여기에 손으로 적지 않는다.** `master-options.ts` 가 계약에서 가져오거나, 계약이 타입으로만\n * 주는 것은 컴파일러가 계약에 묶은 거울에서 가져온다. 화면에 값을 베껴 적으면 커널이 값을 늘렸을\n * 때 화면만 옛 목록을 보인다.\n */\n options?: readonly string[]\n /**\n * `reference` 가 어느 표에서 고르나.\n *\n * 저장되는 것은 **이름**이지 id 가 아니다 — 커널이 이름으로 주소를 잡는다.\n */\n reference?: ReferenceOptions\n width?: number\n /** 고칠 수 없는 칸. 자동으로 붙는 값이 여기 온다. */\n readonly?: boolean\n /**\n * 가로로 스크롤해도 화면에 남는 컬럼.\n *\n * 컬럼이 열 개를 넘고 폭 합이 1000px 을 넘는 축이 있다. 오른쪽 끝을 고치려고 스크롤하면 이름이 화면\n * 밖으로 나가서 **어느 행을 고치는지 알 수 없다.**\n */\n fixed?: boolean\n /**\n * 표에 그리지 않는다 — **검색에는 남는다.**\n *\n * 같은 사실을 두 번 보이지 않으려고 감추는 자리가 있다. 로케이션의 상위는 트리의 들여쓰기가 말하므로\n * 컬럼으로 또 보일 필요가 없지만, 「LINE-1 아래 있는 것」을 찾는 일은 있다.\n *\n * `searchText` 는 `filter: 'search'` 를 선언한 컬럼마다 필터를 만들고 hidden 을 보지 않는다\n * (`data-grist.ts` 의 `set searchText`). 그래서 감춘 컬럼도 검색에는 참여한다.\n *\n * 다만 컬럼 설정 목록에서는 **빠진다** — personalizer 가 페이지 설정의 hidden 을 걸러낸다\n * (`ox-grist-personalizer.ts:51`). 사용자가 켜야 할 컬럼에는 쓰지 않는다.\n */\n hidden?: boolean\n}\n\n/** 각 마스터 화면이 자기에 대해 말하는 것. */\nexport interface MasterSpec {\n /** 컬럼 설정을 사람마다 따로 저장하는 키. 화면마다 달라야 한다. */\n key: string\n /**\n * 이 화면이 다루는 마스터 축.\n *\n * **배정·선언 칸이 있는 축만 「언제부터」를 묻는다**(§`assignment-fields`). 안 적으면 안 묻는다 —\n * 그 축에는 이력을 받는 칸이 없다는 뜻이다.\n */\n axis?: string\n /** 제목 문구 키. `title.<이것>`. */\n title: string\n /**\n * 도움말 문서 키 — `helps/<이것>.md`.\n *\n * `ox-page-title-bar` 가 이 값이 있으면 제목 옆에 도움말 아이콘을 그린다. 상세한 설명은 그 문서가\n * 갖는다 — 화면에 형식적인 소개 문장을 붙이는 것은 자리만 차지하고 도움이 안 된다.\n */\n help: string\n /** GraphQL 조회 이름 — 서버가 낸 그대로. */\n query: string\n /** GraphQL 저장 이름. */\n mutation: string\n columns: MasterColumn[]\n}\n\n/** 열두 축이 공통으로 갖는 유효 기간 — ISA-95. */\nexport const EFFECTIVE: MasterColumn[] = [\n { name: 'effectiveStart', label: 'effective-start', type: 'date', width: 150 },\n { name: 'effectiveEnd', label: 'effective-end', type: 'date', width: 150 }\n]\n\n/** 이름과 설명은 열두 축이 모두 갖는다. 이름은 스크롤해도 남는다. */\nexport const IDENTITY: MasterColumn[] = [\n { name: 'name', label: 'name', type: 'string', width: 160, fixed: true },\n { name: 'description', label: 'description', type: 'string', width: 200 }\n]\n\nexport abstract class MasterPageBase extends p13n(localize(i18next)(PageView)) {\n static styles = [\n /* The work surface frame for a list screen (ADR-0060 decision 8). */\n WorkSurfacePageStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n ScrollbarStyles,\n css`\n\n ox-grist {\n overflow-y: auto;\n flex: 1;\n }\n\n /*\n * 헤드룸에는 「어디를 보고 있나」만 남는다. 검색은 타이틀 줄로 올라갔다(configOf 의 search).\n *\n * 범위가 없는 화면은 이 칸이 비고, 아래 규칙이 그 줄을 아예 감춘다. 빈 띠를 남기지 않는다.\n */\n .header {\n grid-template-areas: 'scope';\n grid-template-columns: 1fr;\n }\n\n .header:empty {\n display: none;\n }\n `\n ]\n\n /** 이 화면이 자기에 대해 말하는 것. 화면마다 자기 것을 준다. */\n protected abstract get spec(): MasterSpec\n\n @property({ type: Boolean }) active: boolean = false\n @property({ type: Object }) gristConfig: any\n @property({ type: String }) mode: 'CARD' | 'LIST' | 'GRID' = isMobileDevice() ? 'CARD' : 'GRID'\n\n @state() protected ready = false\n\n @query('ox-grist') protected grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t(`title.${this.spec.title}`),\n /*\n * **파일로 한 번에 올린다.**\n *\n * 새 공장을 받으면 기준정보를 통째로 넣어야 한다. 자리 · 인원 · 설비가 각각 수백에서 수천\n * 줄이고, 화면에서 한 줄씩 넣으면 인원 1000명에 다섯 시간을 타이핑만 한다. 그런데 고객은\n * 그 자료를 이미 엑셀로 갖고 있다.\n *\n * 틀은 플랫폼이 갖고 있다(`@things-factory/import-ui`). 도구막대에 버튼을 달고 파일을\n * 읽어 줄의 배열로 준다. 우리는 그 줄을 이 화면의 열에 맞추고 저장하기만 한다.\n *\n * **이 자리에 두면 마스터 화면 전부가 한꺼번에 된다.** 화면마다 따로 만들면 열넷이 조금씩\n * 다르게 동작한다.\n */\n importable: { handler: this.importHandler.bind(this) },\n /* 제목 옆의 도움말 아이콘 — 상세한 설명은 그 문서가 갖는다. */\n help: this.spec.help,\n /*\n * 검색은 **페이지 타이틀 줄의 오른쪽 끝**에 둔다.\n *\n * 타이틀은 페이지 안의 제목 줄에 선다(ADR-0060 결정 9, 모든 앱이 같다). 그래서 그\n * 줄은 상단 앱 바가 아니라 페이지의 첫 줄이고, 거기 검색을 두면 거르는 곳과 걸러진 결과가 붙어\n * 있다 — 전에 상단 바를 마다한 이유가 여기서는 생기지 않는다.\n *\n * `ox-page-title-bar` 가 이 값을 받아 타이틀 오른쪽에 그린다. 그래서 헤드룸에는 검색을 두지\n * 않는다 — 두 벌이 되고, 두 벌은 어긋난다.\n */\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n actions: [\n {\n title: i18next.t('button.save'),\n action: this.save.bind(this),\n icon: 'save',\n /* 이 화면의 주 동작 — 채운 버튼으로 그려진다. 한 화면에 하나만 둔다. */\n emphasis: { raised: true }\n }\n ],\n exportable: {\n name: i18next.t(`title.${this.spec.title}`),\n data: () => this.grist.exportRecords()\n }\n }\n }\n\n /**\n * 지금 무엇을 보고 있는가 — 표 바로 위.\n *\n * 계층을 드릴다운으로 걷는 화면은 「어디를 보고 있나」를 말해야 한다. 대부분의 화면은 표 하나가 전부라\n * 범위가 없다 — 그런 화면은 아무것도 그리지 않고, 그 줄이 접힌다.\n */\n protected renderScope() {\n return html``\n }\n\n /**\n * 화면 골격 — 표 하나와 컬럼 설정.\n *\n * 자기 인터페이스가 필요한 화면은 이것을 덮어쓴다. 위치와 정지 사유는 계층이라 표가 아니라 나무여야\n * 하고, 그때 이 render 를 쓰지 않는다.\n */\n render() {\n return html`\n <ox-grist\n .mode=${this.mode}\n .config=${this.gristConfig}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider(`ox-grist-${this.spec.key}`)!}\n >\n <!--\n 자리를 뜻으로 구분한다.\n\n 어디를 보고 있나 빵가루 헤드룸 — 표 바로 위\n 좁히는 것 검색 타이틀 줄 오른쪽 끝\n 저지르는 것 저장 · 삭제 화면 바닥 — 셸이 모든 화면에 같은 자리로 둔다\n 고른 것에 하는 것 선택 후 동작 고른 것 옆\n\n 바닥인 이유는 목록을 위에서 아래로 훑고 줄을 고르고 저지르기 때문이다. 손과 눈이 아래에서\n 끝난다. 전에 동작 바를 헤드룸에도 두었다가 「저장」이 위아래에 하나씩 생겼다(실측: top 150 과\n top 720).\n -->\n <div slot=\"headroom\" class=\"header\">${this.renderScope()}</div>\n\n <!-- 컬럼 설정 버튼. provider 만 넘겨도 버튼은 안 생긴다. 이 요소를 화면이 놓아야 한다. -->\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n `\n }\n\n /**\n * 주소 끝의 이름 조각을 **목록 좁히기**에 쓰나.\n *\n * 다른 화면의 참조 칸에서 걸어 들어오면 주소가 `.../ops-master-material-definition/M0161-0003` 이\n * 된다. 그 조각을 아무도 안 읽으면 106줄짜리 목록 첫 장이 뜨고, 사람은 방금 누른 이름을 다시\n * 찾아야 한다 — 걸어온 보람이 없다.\n *\n * 자리 화면과 제조법 화면은 그 조각을 **파고들기**에 이미 쓰고 있다(그 이름 아래를 보여 준다).\n * 그 둘은 여기서 빠진다 — 두 뜻이 한 조각에 겹치면 어느 쪽도 제대로 안 된다.\n */\n protected get pathSegmentNarrowsList(): boolean {\n return true\n }\n\n async pageInitialized() {\n this.gristConfig = this.configOf()\n this.ready = true\n }\n\n /**\n * 셸은 `(changes, after, before)` 를 넘긴다 — 인수 없는 모양으로 좁히면 주소를 읽어야 하는 화면이\n * 이것을 덮어쓸 수 없다(`after.resourceId` 와 `before.resourceId` 를 비교하는 자리다).\n */\n async pageUpdated(_changes?: any, _after?: any, _before?: any) {\n if (this.active) {\n this.grist?.fetch()\n }\n }\n\n /*\n * 반환 타입을 밝힌다. 적지 않으면 이 자리의 리터럴이 그대로 타입이 되고, 컬럼 구성을 바꾸는 화면이\n * 이것을 재정의할 수 없다(로케이션이 실제로 막혔다).\n */\n protected configOf(): any {\n return {\n /*\n * 빈 목록이 하는 말 — **축마다 다르다.**\n *\n * 「아직 아무것도 없습니다」 하나로는 이 화면들이 할 말을 못 한다. 기준정보는 처음 오는\n * 사람이 가장 먼저 만나는 자리이고, 비어 있는 사람이 묻는 것은 「이게 뭔데 채워야 하나」다.\n * 그 답은 축마다 다르다 — 인원이 없으면 작업 스테이션에서 사람을 고를 수 없고, 근무 캘린더가\n * 없으면 가동률이 「모름」이 된다.\n *\n * 항목 파일을 고치지 않는다. spec.key 로 용어를 끌어오므로 용어만 더하면 새 축도 따라온다.\n */\n emptyNote: {\n title: i18next.t(`text.master-empty-${this.spec.key}`),\n description: i18next.t(`text.master-empty-${this.spec.key}-hint`, { defaultValue: '' }) || undefined\n },\n list: { fields: ['name', 'description'], details: [] },\n columns: [\n /* 이름을 고정하면 그 왼쪽의 거터도 함께 남아야 한다 — 아니면 체크박스가 이름 아래로 밀린다. */\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n { type: 'gutter', gutterName: 'row-selector', multiple: true, fixed: true },\n ...this.spec.columns.map(column => this.columnOf(column))\n ],\n /* 새 행을 만들 수 있다 — 마스터를 채우는 것이 이 화면의 일이다. */\n rows: { appendable: true, selectable: { multiple: true } },\n sorters: [{ name: 'name', desc: false }]\n }\n }\n\n protected columnOf(column: MasterColumn) {\n const header = i18next.t(`field.${column.label}`)\n const editable = !column.readonly\n const fixed = column.fixed\n const hidden = column.hidden\n\n switch (column.type) {\n case 'number':\n return { type: 'float', name: column.name, header, record: { editable }, sortable: true, width: column.width, fixed, hidden }\n case 'boolean':\n return { type: 'boolean', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n case 'date':\n return { type: 'datetime', name: column.name, header, record: { editable }, sortable: true, width: column.width, fixed, hidden }\n /*\n * 목록과 JSON 은 **글자로 고친다.** 전용 편집기를 만들면 축마다 다른 편집기가 생기고, 그 편집기가\n * 모르는 칸이 들어오면 값이 오류 보고 없이 사라진다. 지금은 있는 그대로 보이고 그대로 고친다.\n *\n * 이것은 **자리표시자다.** 화면이 갈라진 지금은 그 축의 화면이 자기 편집기를 가질 수 있다.\n */\n case 'select':\n /*\n * **빈 줄이 없으면 되돌릴 수 없다.** 한 번 고르면 「아직 안 정했다」로 못 돌아간다. 비워 둘\n * 수 없는 칸이면 화면 쪽에서 `options` 에 빈 값을 안 넣으면 된다.\n */\n return {\n type: 'select',\n name: column.name,\n header,\n record: { editable, options: column.options ?? [] },\n filter: 'select',\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n case 'reference':\n /*\n * 다른 표에서 골라 온다. 자유 텍스트로 두면 `GUNSAN` 을 `GUNSAM` 이라고 쳐도 저장되고,\n * 그 뒤로 그 참조는 어디에서도 안 걸린다 — 화면에는 값이 있는데.\n *\n * 고르기는 팝업이 하고, 검색은 그 팝업이 갖는다. 그래서 열 자체에는 검색 필터를 안 붙인다.\n */\n return {\n type: 'reference',\n name: column.name,\n header,\n /*\n * **제목을 여기서 채운다.** 팝업 머리에 글자가 없으면 회색 띠에 검색창만 뜨고, 무엇을\n * 고르는 중인지 화면이 말하지 않는다. 편집기 자리에서는 못 채운다 — 그리드가 머리말을\n * `{ renderer }` 객체로 바꿔서 넘기기 때문에 거기서는 글자를 볼 수 없다.\n *\n * 열의 머리말을 그대로 쓴다. 「주 근무 로케이션」을 고르는 팝업의 제목이 「주 근무\n * 로케이션」이면 둘이 어긋날 수가 없다.\n */\n record: { editable, reference: { title: header, ...column.reference } },\n filter: 'search',\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n case 'iso-duration':\n /*\n * ISO 8601 기간(`PT30S`)이다. 글자로 고치면 `30s` 나 `PT30` 처럼 커널이 못 읽는 것이\n * 오류 보고 없이 저장된다. 전용 편집기가 초로 받아 표기를 만든다.\n *\n * 이 편집기는 기본 등록에 없다 — `bootstrap.ts` 가 등록한다.\n */\n return { type: 'iso-duration', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n /*\n * JSON 은 **구조를 아는 편집기로 고친다.** 글자로 고치면 괄호 하나가 빠져도 저장 순간까지\n * 모르고, 저장되면 그 칸을 읽는 쪽이 통째로 못 읽는다. `json5` 는 팝업에서 문법을 보아 준다.\n */\n case 'json':\n return { type: 'json5', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n /*\n * 목록은 아직 **글자로 고친다.** 이 칸들은 대개 다른 표의 이름을 담는데(등급 · 공정 · 상위\n * 등급), 그 표에서 골라 오는 편집기가 아직 없다. 자리표시자다.\n */\n /*\n * ── 여럿을 고르는 참조 ──────────────────────────────────────────────────\n *\n * 상위 등급 · 요구 검사 · 공정의 차례처럼 **관계를 여럿 다는 칸**이다. 그리는 것은 목록과\n * 같고(ops-list), 고치는 것은 참조 팝업이 여럿 고르기로 연다.\n *\n * 전에는 이런 칸이 전부 `list` 였다 — 즉 자유 텍스트였다. 그래서 GUNSAN 을 GUNSAM 이라고\n * 쳐도 저장되고, 그 뒤로 그 참조는 어디에서도 안 걸린다. 열 곳이 그 상태였다\n * (2026-09-05 · 사용자 지시로 전부 바꿈).\n */\n case 'reference-list':\n return {\n type: 'ops-list',\n name: column.name,\n header,\n record: {\n editable,\n /*\n * 편집기는 참조 팝업이 맡는다 — 그리기만 목록 렌더러를 쓴다.\n *\n * 열 빌더는 `column.type` 으로 편집기를 고르므로(column-builder), 여기서 함수를\n * 직접 준다. 문자열을 주면 무시되고 글자 편집기가 붙는다.\n */\n editor: getEditor('reference'),\n /*\n * 팝업으로 연다고 밝힌다. 표식(edit_square)의 판단이 열 타입을 보는데, 이 열은\n * 그리기를 목록 렌더러로 하려고 타입이 `ops-list` 다 — 그대로 두면 칸 안에서\n * 고치는 것으로 표시된다.\n */\n opensPopup: true,\n reference: { title: header, multiple: true, ...column.reference }\n },\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n case 'list':\n /*\n * 그리기만 우리 것을 쓰고 고치기는 글자 그대로다(`ops-list` 는 렌더러만 등록한다).\n * 기본 글자 렌더러는 배열을 구분 없이 이어 붙인다.\n */\n return { type: 'ops-list', name: column.name, header, record: { editable }, width: column.width, fixed, hidden }\n default:\n return {\n type: 'string',\n name: column.name,\n header,\n record: { editable },\n filter: 'search',\n sortable: true,\n width: column.width,\n fixed,\n hidden\n }\n }\n }\n\n async fetchHandler({ page = 1, limit = 50, sortings = [], filters = [] }: FetchOption) {\n const fields = this.spec.columns.map(column => column.name).join('\\n')\n\n /*\n * ── 주소 끝의 이름 조각으로 좁힌다 ──────────────────────────────────────\n *\n * 다른 화면의 참조 칸에서 걸어 들어오면 주소가 `.../ops-master-material-definition/M0161-0003`\n * 이 된다. 그 조각을 아무도 안 읽으면 106줄짜리 목록 첫 장이 뜨고, 사람은 방금 누른 이름을 다시\n * 찾아야 한다 — 걸어온 보람이 없다.\n *\n * **그리드의 검색어를 세우는 방식은 안 된다.** 그렇게 해 봤더니 값은 실제로 세워지는데(기억에\n * 남았다) 그 뒤 그리드가 설정을 복원하면서 지웠다. 훅 자리를 앞뒤로 옮겨도 이기지 못하는\n * 경합이다. 질의를 만드는 이 자리에서 조건을 얹으면 경합이 없다 — 언제 불려도 같은 답이다.\n * [확인함] 2026-09-04\n *\n * 자리 화면과 제조법 화면은 그 조각을 **파고들기**에 이미 쓰므로 여기서 빠진다.\n */\n const segment = this.pathSegmentNarrowsList ? (this as any).lifecycle?.resourceId : undefined\n\n if (segment) {\n filters = [...filters, { name: 'name', operator: 'eq', value: decodeURIComponent(segment) }]\n }\n\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n ${this.spec.query}(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n ${fields}\n }\n total\n }\n }\n `,\n variables: { filters, pagination: { page, limit }, sortings },\n fetchPolicy: 'no-cache'\n })\n\n const payload = response.data?.[this.spec.query]\n const records = (payload?.items ?? []).map((item: any) => this.spreadJson(item))\n\n /* 바뀌기 전 값을 보여 주려면 읽은 것을 들고 있어야 한다 — 그리드는 바뀐 것만 돌려준다. */\n this.lastRead = new Map(records.map((one: any) => [String(one.id), one]))\n\n return { total: payload?.total ?? 0, records }\n }\n\n /**\n * JSON 칸을 **사람이 고칠 수 있는 글자로 편다.**\n *\n * 서버는 구조를 구조로 낸다(`ScalarObject` 로 선언하지만 **스키마에 나가는 이름은 `Object`** 다) — 그것이\n * 사실이고, 곧 붙을 전용 편집기가 원하는 모양이다.\n * 그런데 지금 이 그리드의 JSON 칸은 글자 편집기라 객체를 그대로 주면 `[object Object]` 가 된다.\n * 그래서 **화면에 들어올 때만** 편다. 스키마를 화면 사정에 맞추지 않고, 화면이 자기 몫을 한다.\n *\n * 이 옮김은 `packJson` 과 짝이다. 한쪽만 고치면 저장이 오류보고없이 망가진다.\n */\n protected spreadJson(item: any): any {\n const jsonColumns = this.spec.columns.filter(column => column.type === 'json')\n\n if (!jsonColumns.length) {\n return item\n }\n\n const spread = { ...item }\n\n for (const column of jsonColumns) {\n const value = spread[column.name]\n\n /* 비어 있는 것은 비어 있는 채로 둔다 — `null` 을 `\"null\"` 로 만들면 지운 것이 값이 된다. */\n if (value === undefined || value === null || typeof value === 'string') {\n continue\n }\n\n spread[column.name] = JSON.stringify(value, null, 0)\n }\n\n return spread\n }\n\n /**\n * 저장하기 전에 JSON 칸의 글자를 **구조로 되읽는다.**\n *\n * 읽지 못하면 **보내지 않는다.** 그냥 보내면 글자가 통째로 값이 되어, 사람이 적은 구조가 사라진 채로\n * 저장이 성공했다고 나온다. 어느 행 어느 칸인지 말해 주어야 고칠 수 있다.\n */\n protected packJson(patches: any[]): { patches: any[]; problems: string[] } {\n const jsonColumns = this.spec.columns.filter(column => column.type === 'json')\n\n if (!jsonColumns.length) {\n return { patches, problems: [] }\n }\n\n const problems: string[] = []\n\n const packed = patches.map((patch, index) => {\n const one = { ...patch }\n\n for (const column of jsonColumns) {\n const value = one[column.name]\n\n if (typeof value !== 'string') {\n /* 손대지 않은 칸은 이미 구조다. 비운 칸은 비운 채로 간다. */\n continue\n }\n\n if (value.trim() === '') {\n /* 비운 것은 「없앤다」는 뜻이다. 빈 글자로 보내면 칸의 뜻이 흐려진다. */\n one[column.name] = null\n continue\n }\n\n try {\n one[column.name] = JSON.parse(value)\n } catch (error) {\n problems.push(\n i18next.t('text.json-column-unreadable', {\n row: index + 1,\n column: i18next.t(`field.${column.label}`),\n detail: (error as Error).message\n })\n )\n }\n }\n\n return one\n })\n\n return { patches: packed, problems }\n }\n\n /**\n * 파일로 올린 줄들을 이 화면의 마스터로 넣는다.\n *\n * ── 오류보고없이 버리지 않는다 ─────────────────────────────────────────────────\n * 모르는 열 이름, 이름이 없는 줄, 중복된 이름을 **알린다.** 오류보고없이 빼면 사람은 다 올라간 줄 알고,\n * 빠진 것은 나중에 「그 설비가 왜 없지」로 나타난다.\n *\n * ── 하나라도 틀리면 아무것도 안 보낸다 ───────────────────────────────────\n * 절반만 들어가면 무엇이 들어갔는지 사람이 모른다. 파일을 고쳐 다시 올리는 편이 낫다.\n *\n * ── 새로 만들 것과 고칠 것을 이름으로 구분한다 ─────────────────────────────\n * 서버는 `+`(새로)와 `M`(고침, id 필요)을 나눠 받는다. 그러니 **올리기 전에 그 이름들이 이미\n * 있는지 물어야 한다.** 안 물으면 같은 이름이 두 벌 생긴다.\n */\n protected async importHandler(records: any[]) {\n if (!records?.length) {\n notify({ message: i18next.t('text.nothing-to-import'), level: 'info' })\n return\n }\n\n const byHeader = this.headerIndex()\n const reading = readImportRows(records, header => byHeader.get(header))\n\n if (reading.problems.length) {\n /* 열 줄까지만 보인다 — 파일이 통째로 어긋나면 문제도 줄 수만큼 나온다. */\n notify({ message: reading.problems.slice(0, 10).map(problem => this.sayProblem(problem)).join('\\n'), level: 'error' })\n return\n }\n\n const existing = await this.idsByName(reading.rows.map(row => String(row.name)))\n const patches = patchesFor(reading.rows, name => existing.get(name))\n\n const packed = this.packJson(patches)\n\n if (packed.problems.length) {\n notify({ message: packed.problems.slice(0, 10).join('\\n'), level: 'error' })\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($patches: [Object!]!) {\n ${this.spec.mutation}(patches: $patches) {\n id\n }\n }\n `,\n variables: { patches: packed.patches }\n })\n\n /*\n * 이 앱의 클라이언트는 서버가 거절해도 던지지 않고 `errors` 에 담아 준다. 여기서 보지 않으면\n * 아래의 「올렸다」가 실패한 뒤에도 그대로 뜬다. 사유는 앱의 공통 오류 처리가 이미 띄우므로\n * 여기서 다시 띄우지 않는다 — 같은 말이 두 번 뜬다.\n */\n if (response?.errors?.length) {\n return\n }\n\n const made = patches.filter(patch => patch.cuFlag === '+').length\n\n notify({\n message: i18next.t('text.import-done', { made, changed: patches.length - made }),\n level: 'info'\n })\n\n this.grist.fetch()\n }\n\n /**\n * 파일의 머리글을 이 화면의 열 이름으로 바꾼다.\n *\n * **두 가지를 다 받는다** — 열 이름 그대로(`homeLocation`)와 화면에 보이는 말(`홈 자리`).\n * 사람이 이 화면에서 내려받아 고쳐 올리면 머리글이 화면의 말이고, 다른 시스템에서 뽑아 오면\n * 열 이름이다. 한쪽만 받으면 다른 쪽이 통째로 「모르는 열」이 된다.\n */\n /** 무엇이 잘못됐는지를 사람에게 보일 문장으로 바꾼다. 줄 번호는 엑셀에서 보는 그 번호다. */\n private sayProblem(problem: ImportProblem): string {\n switch (problem.kind) {\n case 'unknown-column':\n return i18next.t('error.import-unknown-column', { line: problem.line, column: problem.column })\n case 'no-name':\n return i18next.t('error.import-no-name', { line: problem.line })\n case 'duplicate-name':\n return i18next.t('error.import-duplicate-name', { line: problem.line, name: problem.name })\n }\n }\n\n private headerIndex(): Map<string, string> {\n const found = new Map<string, string>()\n\n for (const column of this.spec.columns) {\n found.set(column.name, column.name)\n found.set(i18next.t(`field.${column.name}`), column.name)\n }\n\n return found\n }\n\n /** 이 이름들이 이미 있나 — 있으면 그 `id`. 없는 이름은 지도에 없다. */\n private async idsByName(names: string[]): Promise<Map<string, string>> {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination) {\n ${this.spec.query}(filters: $filters, pagination: $pagination) {\n items {\n id\n name\n }\n }\n }\n `,\n variables: {\n filters: [{ name: 'name', operator: 'in', value: names }],\n /* 올린 줄 수만큼만 돌아오면 된다. 기본 50으로 두면 51번째부터 새로 만들어 두 벌이 된다. */\n pagination: { page: 1, limit: names.length }\n },\n fetchPolicy: 'no-cache'\n })\n\n return new Map(\n (response.data?.[this.spec.query]?.items ?? []).map((item: any) => [item.name, item.id])\n )\n }\n\n /** 마지막으로 읽은 줄들. 「바뀌기 전 → 바뀐 뒤」를 보여 주는 데 쓴다. */\n protected lastRead?: Map<string, any>\n\n /** 「언제부터」를 묻는다. 사람이 그만두면 `undefined` 이고, 그때는 **아무것도 안 보낸다.** */\n protected askAppliedWhen(changes: AssignmentChange[]): Promise<AppliedAnswer | undefined> {\n return new Promise(resolve => {\n const popup: any = openPopup(\n html`<applied-when-dialog\n .changes=${changes}\n .onAnswer=${(answer: AppliedAnswer) => {\n popup?.close?.()\n resolve(answer)\n }}\n .onCancel=${() => {\n popup?.close?.()\n resolve(undefined)\n }}\n ></applied-when-dialog>`,\n { backdrop: true, size: 'medium', title: i18next.t('title.when-does-this-apply') }\n )\n })\n }\n\n protected async save() {\n const patches = this.grist.dirtyRecords\n\n if (!patches?.length) {\n notify({ message: i18next.t('text.nothing-changed'), level: 'info' })\n return\n }\n\n const packed = this.packJson(patches)\n\n if (packed.problems.length) {\n /* 하나도 보내지 않는다. 절반만 저장되면 화면과 저장된 것이 달라진다. */\n notify({ message: packed.problems.join('\\n'), level: 'error' })\n return\n }\n\n /*\n * ── 배정과 선언은 「언제부터」를 묻고 보낸다 ─────────────────────────────\n *\n * 자리 배정 · 근무조 선언 · 부모 관계는 가동률 분모를 정한다. 그래서 값만 바꾸는 것이 아니라\n * **언제부터 그렇게 볼 것인가**를 함께 정해야 하고, 과거를 적으면 지연 기록인지 정정인지까지\n * 갈라야 한다(§`applied-when-dialog`).\n *\n * 안 물어도 저장은 된다 — 그때는 「지금부터」라는 주장이 된다. 다만 **묻지 않으면 사람이 그것을\n * 주장한 줄 모른다.**\n */\n const assignments = assignmentChangesIn(this.spec.axis ?? '', packed.patches, id => this.lastRead?.get(id))\n const applied = assignments.length ? await this.askAppliedWhen(assignments) : undefined\n\n /* 그만두었으면 아무것도 안 보낸다 — 절반만 저장되면 화면과 저장된 것이 달라진다. */\n if (assignments.length && !applied) {\n return\n }\n\n const response = await client.mutate({\n /*\n * **타입 이름은 `Object` 다.**\n *\n * 서버가 `ScalarObject` 라는 이름으로 선언하지만 스키마에 나가는 이름은 `Object` 이고,\n * `ScalarObject` 로 적으면 「Unknown type」으로 거절된다. 2026-09-01부터 이 자리가 그렇게\n * 적혀 있어 **모든 기준정보 화면의 저장이 안 됐다.** 화면은 오류를 그대로 보여 주지도\n * 않았다 — 이 앱의 클라이언트는 서버가 거절해도 던지지 않는다.\n */\n mutation: gql`\n mutation ($patches: [Object!]!, $applied: Object) {\n ${this.spec.mutation}(patches: $patches, applied: $applied) {\n id\n name\n }\n }\n `,\n variables: { patches: packed.patches, applied }\n })\n\n /*\n * 이 클라이언트는 서버가 거절해도 던지지 않는다 — 오류는 `response.errors` 에 담겨 온다.\n * 보지 않으면 저장에 실패해도 화면이 목록을 다시 읽어 「저장된 것처럼」 보인다.\n * 사유 자체는 앱의 공통 오류 처리가 띄운다.\n */\n if (response.errors?.length) {\n return\n }\n\n /* 지운 행은 사라지지 않고 유효 기간의 끝이 채워진다. 말해 주지 않으면 저장이 안 된 것으로 읽는다. */\n if (packed.patches.some((patch: any) => String(patch.cuFlag).toUpperCase() === '-')) {\n notify({ message: i18next.t('text.retired-not-deleted'), level: 'info' })\n }\n\n this.grist.fetch()\n }\n}\n"]}
@@ -109,6 +109,7 @@ let OpsMasterOperationalLocation = class OpsMasterOperationalLocation extends Ma
109
109
  get spec() {
110
110
  return {
111
111
  key: 'operationalLocation',
112
+ axis: 'operationalLocation',
112
113
  title: 'location',
113
114
  help: 'ops-master/operational-location',
114
115
  query: 'operationalLocations',
@@ -1 +1 @@
1
- {"version":3,"file":"operational-location.js","sourceRoot":"","sources":["../../../client/pages/master/operational-location.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport { FetchOption } from '@operato/data-grist'\nimport { i18next } from '@operato/i18n'\nimport { navigate } from '@operato/shell'\n\nimport { EFFECTIVE, IDENTITY, MasterPageBase, MasterSpec } from './master-page-base.js'\nimport { INTENT_OPTIONS, LEVEL_OPTIONS, SYSTEM_OPTIONS, withBlank } from './master-options.js'\n\n/** 서버가 주는 로케이션 한 줄 — 상위를 `parentId` 로 가리킨다. */\ntype LocationRecord = Record<string, any> & { id: string; name: string; parentId?: string | null }\n\n/**\n * 로케이션 — 공장 · 라인 · 설비 앞 · 창고 칸.\n *\n * ── 계층을 한 단씩 걷는다 ──────────────────────────────────────────────────\n * 로케이션은 계층이다. 공장 안에 라인이 있고 라인 안에 설비 자리가 있다. 평면 표로 늘어놓으면 그 구조가\n * 사라진다 — 이름 순으로 섞여서 공장이 다섯째 줄에, 그 아래 라인이 넷째 줄에 온다.\n *\n * **그리고 이것이 틀리면 그 위의 집계가 전부 틀린다.** 실적이 「어디서」를 이 값으로 가리키므로, 라인\n * 단위 생산량과 공장 단위 가동률이 다 이 구조 위에서 계산된다.\n *\n * 그래서 이 화면은 지금 자리의 **자식만** 보인다. 어디에 있는지는 빵가루가 말하고, 형제가 많으면\n * 페이지네이션이 나눈다.\n *\n * ── 왜 트리 표가 아닌가 ────────────────────────────────────────────────────\n * `ox-grist` 에 트리 표가 있고 처음에 그것으로 만들었다. 두 가지가 걸렸다.\n *\n * **하나. 트리는 서브트리 전부를 메모리로 요구한다.** 지연 로딩이 없다 — 펼침은 플래그만 바꾼다.\n *\n * data-manipulator.ts:231 expandNode(record) { record.__expanded__ = true; this.refresh() }\n *\n * 페이지네이션으로 막을 수도 없다. 그 페이지네이션은 **뿌리를 센다**(`data-grist.ts:1095` 의 `idx` 는\n * 받은 배열 = 뿌리의 첨자). 로케이션의 뿌리는 사이트라서 공장 열 곳을 운영해도 열이고, 그 밑에 이십만이\n * 달려 있어도 페이지 수는 1이다. 창고는 랙 칸마다 한 줄이라 수만이 된다.\n *\n * 지연 로딩을 붙이는 길은 있다 — `expand-node` 가 `composed: true` 로 밖까지 나온다. 그러나 그것도\n * **깊이만** 막는다. 통로 하나에 랙 칸이 이천이면 그 자리를 여는 순간 이천이 온다. 너비까지 막으려면\n * 가지마다 「더 보기」 줄이 필요하고, 그 줄은 데이터 줄처럼 생겨서 `dirtyRecords` · `exportRecords` ·\n * `checkDirties` · 순번 · 저장마다 예외가 된다. 한 곳을 빠뜨리면 「1,900개 더」가 로케이션으로 저장된다.\n *\n * **둘. 개수에 따라 모습을 바꾸면 그 화면은 배울 수 없다.** 한동안 「적으면 트리, 많으면 드릴다운」으로\n * 두었다. 원칙처럼 들렸지만 실제로는 제품이 둘이 되는 것이었다 — 고객마다 다른 화면을 쓰고, 공장이\n * 커지면 쓰던 사람 밑에서 모습이 바뀐다. 링크도 트리에서는 뜻이 없고, 빵가루 · 순번 · 페이지네이션이\n * 있다 없다 한다. 그래서 하나로 정했다.\n *\n * ── 자리의 주인은 URL 이다 ─────────────────────────────────────────────────\n * 지금 어디를 보고 있나는 주소가 갖는다(`pageUpdated`). 그래야 자리를 붙여 보낼 수 있고, 뒤로 가기가\n * 한 단 올라가고, 새로 고쳐도 그 자리에 남는다.\n */\n@customElement('ops-master-operational-location')\nexport class OpsMasterOperationalLocation extends MasterPageBase {\n static styles = [\n ...MasterPageBase.styles,\n css`\n .scope {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 2px;\n min-height: 26px;\n }\n\n .scope button {\n display: flex;\n align-items: center;\n gap: 2px;\n border: none;\n background: none;\n padding: 2px 6px;\n border-radius: 4px;\n font: inherit;\n font-size: 12.5px;\n color: var(--ui-muted, #57676f);\n cursor: pointer;\n }\n\n .scope button:hover {\n background-color: var(--ui-hover, rgba(0, 0, 0, 0.05));\n color: var(--ui-ink, #17272f);\n }\n\n /* 지금 있는 자리. 선택할 곳이 아니므로 버튼으로 그리지 않는다. */\n .scope .here {\n padding: 2px 6px;\n font-size: 12.5px;\n font-weight: 600;\n color: var(--ui-ink, #17272f);\n }\n\n .scope md-icon {\n --md-icon-size: 16px;\n font-size: 16px;\n width: 16px;\n height: 16px;\n }\n\n .scope .sep {\n color: var(--ui-line, #c9d4d9);\n font-size: 12px;\n }\n `\n ]\n\n protected get spec(): MasterSpec {\n return {\n key: 'operationalLocation',\n title: 'location',\n help: 'ops-master/operational-location',\n query: 'operationalLocations',\n mutation: 'updateMultipleOperationalLocation',\n columns: [\n ...IDENTITY,\n { name: 'level', label: 'equipment-level', type: 'select', options: withBlank(LEVEL_OPTIONS), width: 140 },\n { name: 'type', label: 'location-type', type: 'string', width: 140 },\n /*\n * 누구의 자리인가 — 한 현장에 제품이 둘 서면 재고가 이 값으로 갈린다(ADR-0081). 타입 옆에 둔다:\n * 둘 다 「이 자리가 무엇인가」이고, 타입을 적는 사람이 주인도 안다.\n */\n { name: 'system', label: 'owning-system', type: 'select', options: withBlank(SYSTEM_OPTIONS), width: 110 },\n { name: 'capacity', label: 'capacity', type: 'number', width: 90 },\n /*\n * ── 나를 누가 쓰나 ────────────────────────────────────────────────\n *\n * 값의 주인이 이 표가 아니라 **저쪽 표**다. 그래서 여기서 못 고친다 — 바꾸려면 그쪽에서\n * 이 이름을 가리키게 하거나 떼면 된다.\n *\n * 넘치면 **몇이 안 보이는지 함께 낸다.** 오류보고없이 자르면 「셋이 있다」와 「서른이 있는데\n * 셋만 보인다」가 같아 보이고, 그 칸을 보고 사람이 판단을 내린다.\n */\n /*\n * ── 여기서 도는 근무조 ────────────────────────────────────────────\n *\n * **비어 있는 것이 정상이다.** 채우는 것은 위와 다를 때뿐이고, 아래 단은 물려받는다. 공장에\n * 한 줄 적으면 그 아래 라인·셀·설비가 전부 그것으로 돈다.\n *\n * 전에는 이 사실이 설비마다 사본으로 있었다 — 한 공장 22대가 같은 값을 22벌 들었다. 3교대를\n * 2교대로 바꾸려면 22줄을 고쳐야 했고, 하나를 빠뜨리면 그 설비만 옛 근무로 계산됐다.\n */\n {\n name: 'workShiftNames',\n label: 'work-shifts-here',\n type: 'reference-list',\n reference: { queryName: 'workShifts' },\n width: 200\n },\n { name: 'staff', label: 'staff-here', type: 'list', width: 180, readonly: true },\n { name: 'equipmentHere', label: 'equipment-here', type: 'list', width: 200, readonly: true },\n { name: 'assetsHere', label: 'assets-here', type: 'list', width: 200, readonly: true },\n { name: 'occupantOverflow', label: 'not-shown', type: 'number', width: 80, readonly: true },\n /*\n * 상위 로케이션은 컬럼으로 두지 않는다 — 빵가루가 그것을 말한다. 같은 사실을 두 군데 보이면 둘이\n * 어긋날 때 어느 쪽이 맞는지 알 수 없다.\n *\n * 다만 검색에는 남긴다(hidden). 「LINE-1 아래 있는 것」을 찾는 일이 있다.\n */\n {\n /* 이름이 `parentId` 인데 담기는 것은 상위 자리의 **이름**이다(`GUNSAN`). 커널이 이름으로 주소를 잡는다. */\n name: 'parentId',\n label: 'parent-location',\n type: 'reference',\n reference: { queryName: 'operationalLocations' },\n width: 150,\n hidden: true\n },\n /*\n * 하위 개수 — 표에는 「하위」 칸이 따로 그리므로 여기서는 감춘다. 이 줄이 있어야 질의가 그 값을\n * 가져온다(`master-page-base` 의 fetchHandler 가 spec 의 컬럼 이름으로 필드를 짓는다).\n */\n { name: 'childCount', label: 'sub-locations', type: 'number', width: 0, readonly: true, hidden: true },\n ...EFFECTIVE\n ]\n }\n }\n\n /**\n * 빵가루 — 최상위부터 지금 자리까지. 빈 배열이면 최상위를 보고 있다.\n *\n * **이 값의 주인은 URL 이다.** 여기 직접 넣지 않는다 — `enter` 는 주소만 바꾸고, 바뀐 주소를\n * `pageUpdated` 가 받아 이 값을 다시 세운다. 상태를 여기 두고 주소를 따로 갱신하면 둘이 어긋나는 날이\n * 온다.\n */\n @state() private path: LocationRecord[] = []\n\n /* 이 화면은 주소 조각을 **파고들기**에 쓴다 — 목록 좁히기로 겹쳐 쓰지 않는다. */\n protected get pathSegmentNarrowsList(): boolean {\n return false\n }\n\n async pageInitialized() {\n /*\n * 주소에 자리가 담겨 있으면 여기서도 세운다.\n *\n * `pageUpdated` 만 믿으면 **주소로 바로 들어온 경우가 빠진다** — 그 호출이 이 함수보다 먼저\n * 지나간다. 실제로 `/LINE-1` 로 들어갔을 때 최상위가 나왔다. [확인함] 2026-09-01\n */\n await this.syncPath(this.lifecycle?.resourceId)\n\n await super.pageInitialized()\n }\n\n /**\n * 주소가 바뀌면 빵가루를 다시 세운다.\n *\n * 주소에는 **지금 자리 하나만** 담는다(`.../ops-master-operational-location/AISLE-01`). 조상은\n * `parentId` 를 타고 올라가 알아낸다 — 경로 전체를 주소에 늘어놓으면 이름을 고칠 때마다 주소가 깨지고,\n * 사람이 읽을 수 없게 길어진다.\n */\n async pageUpdated(changes: any, after: any, before: any) {\n if (after?.resourceId !== before?.resourceId) {\n await this.syncPath(after?.resourceId)\n }\n\n await super.pageUpdated()\n }\n\n /** 주소가 가리키는 자리로 빵가루를 맞춘다. 자리가 없으면 최상위다. */\n private async syncPath(key?: string) {\n this.path = key ? await this.pathTo(key) : []\n }\n\n /** 이 자리부터 최상위까지 거슬러 올라가며 빵가루를 만든다. */\n private async pathTo(key: string): Promise<LocationRecord[]> {\n const chain: LocationRecord[] = []\n /* 자기 자신이나 서로를 상위로 참조하는 데이터가 있으면 여기서 무한히 반복된다. 밟은 자리를 기억해 끊는다. */\n const walked = new Set<string>()\n\n let cursor: string | null | undefined = key\n\n while (cursor && !walked.has(cursor)) {\n walked.add(cursor)\n\n const node = await this.lookup(cursor)\n if (!node) break\n\n chain.unshift(node)\n cursor = node.parentId\n }\n\n return chain\n }\n\n /** 한 자리를 찾는다. 아이디로 찾고, 없으면 이름으로 찾는다 — 주소에는 사람이 읽는 이름이 온다. */\n private async lookup(key: string): Promise<LocationRecord | undefined> {\n for (const field of ['id', 'name']) {\n const { records } = await super.fetchHandler({\n page: 1,\n limit: 1,\n filters: [{ name: field, operator: 'eq', value: key }]\n } as FetchOption)\n\n if (records.length) return records[0] as LocationRecord\n }\n\n return undefined\n }\n\n protected renderScope() {\n return html`\n <div class=\"scope\">\n <button @click=${() => this.enter(null)}>\n <md-icon>home</md-icon>\n ${i18next.t('label.all-locations')}\n </button>\n ${this.path.map((node, index) =>\n index === this.path.length - 1\n ? html`<span class=\"sep\">/</span><span class=\"here\">${node.name}</span>`\n : html`<span class=\"sep\">/</span><button @click=${() => this.enter(node)}>${node.name}</button>`\n )}\n </div>\n `\n }\n\n /**\n * 그 자리로 들어간다. `null` 이면 최상위로 돌아간다.\n *\n * **주소만 바꾼다.** 빵가루도 목록도 여기서 손대지 않는다 — 셸이 주소를 물고 `pageUpdated` 를 부르고,\n * 거기서 하나로 다시 만들어진다. 여기서 함께 고치면 뒤로 가기로 들어왔을 때와 다른 길이 생긴다.\n */\n private enter(node: LocationRecord | null) {\n /*\n * 아직 저장하지 않은 줄로는 들어갈 수 없다. 표 끝의 빈 줄에도 이 버추가 붙기 때문이다.\n *\n * [확인함] 이 가드가 없을 때 화면을 새로 열면 주소가 `.../operational-location/undefined` 가 됐다.\n * 무엇이 그 클릭을 냈는지는 **아직 못 찾았다** — 화면을 새로 열 때만 나고, 앱 안에서 옮겨 갈 때는\n * 나지 않는다. 원인을 모른 채 「고쳤다」고 하지 않는다. 다만 저장 안 된 줄로 들어가지 못하는 것은\n * 원인과 무관하게 맞는 규칙이다.\n */\n if (node && !node.id) {\n return\n }\n\n navigate(node ? `${this.lifecycle?.page}/${node.name || node.id}` : `${this.lifecycle?.page}`)\n }\n\n protected configOf() {\n const config = super.configOf()\n\n const enterColumn =\n /*\n * 「하위」 칸 — 개수와 들어가기를 함께 담는다.\n *\n * ── 개수가 뜻을 나른다 ─────────────────────────────────────────────\n * 처음에는 아이콘만 두었다. 그러니 그 버튼이 아무것도 알려 주지 않았다. 선택해 보기 전에는 안에\n * 무엇이 있는지, 있기는 한지 알 수 없다. 그리고 **왼쪽 끝의 화살은 트리의 펼침 버튼으로\n * 읽힌다.** 실제로는 다른 자리로 옮겨 가는데.\n *\n * 아이콘을 갈아 봤지만(굽은 화살은 표 안에서 지저분했다) 아이콘이 풀 문제가 아니었다. 칸에\n * 머리를 달고 개수를 넣으면 그림 없이도 읽힌다. 펼침 버튼에는 머리가 없다.\n *\n * 하위\n * 4 › 안에 넷이 있다. 누르면 그리로 간다\n * ― 안이 비었다. 누를 일이 없다\n *\n * 개수는 서버가 낸다(`childCount` 필드 리졸버).\n *\n * ── 왜 거터의 기본 버튼을 쓰지 않나 ─────────────────────────────────\n * 그 거터는 아이콘만 그리고, 누름은 표의 셀 클릭 경로를 탄다. 그 경로에는 이동이 견딜 수 없는\n * 관문이 둘 있다. [확인함] 2026-09-01\n *\n * data-grid-body-click-handler.ts:18 if (this.editTarget) return\n * data-grid-body-click-handler.ts:23 (e.target).closest('ox-grid-field')\n *\n * 앞은 **다른 칸이 편집 중이면 첫 누름을 가져간다.** 값을 고치다가 옮겨 가려면 두 번 선택해야\n * 한다. 뒤는 셀 경계를 스치면 칸을 못 찾는다(아이콘 한가운데를 선택해도 안 먹혔다).\n *\n * 그래서 자기 버튼을 그린다. 진짜 `button` 이라 키보드로도 닿고 초점 표시가 붙는다. 모양은\n * `data-grid-field` 의 `button.icon` 이 갖는다 — 렌더러의 결과는 셀의 그림자 안에 그려지므로\n * 이 화면의 스타일이 닿지 않는다.\n *\n * `gutterName` 은 남긴다 — `type: 'gutter'` 는 등록된 이름이 있어야 컴파일된다. 없이 두면\n * 컬럼 컴파일이 터져 **표에 컬럼이 하나도 안 남는다**.\n *\n * main.js Cannot destructure property 'name' of 'compiled' as it is undefined\n */\n /*\n * ── 거터가 아니라 보통 칸이다 ─────────────────────────────────────────\n * 처음에 `type: 'gutter'` 로 두었다. 렌더러를 덮기 쉬워서였는데, 거터는 여백 규칙이 달라\n * (`--grid-gutter-padding` 2px 대 보통 칸 8px 10px) 경계선도 없다. 그래서 왼쪽 셋이 한 덩어리로\n * 붙어 보였다. [확인함] 2026-09-02\n *\n * # w=20 padding 2px 0 구분선 없음\n * 선택 w=30 padding 2px 0 구분선 없음\n * 하위 w=62 padding 2px 0 구분선 없음 ← 여기\n * 이름 w=160 padding 8px 0 8px 10px 구분선 1px\n *\n * 이 칸은 **개수를 담는 데이터 칸**이다. 거터는 표의 장치(순번 · 선택 · 더러움 표식)를 위한\n * 자리다. 보통 칸으로 두면 여백과 경계선을 다른 칸과 같이 받는다.\n */\n {\n type: 'string',\n name: 'childCount',\n width: 62,\n /* 폭을 끌어 바꿀 것이 없고, 개수로 정렬하려면 서버가 그 축을 내야 한다. */\n resizable: false,\n sortable: false,\n header: i18next.t('field.sub-locations'),\n record: {\n align: 'center',\n renderer: (_value: any, _column: any, record: LocationRecord) => {\n /* 표 끝의 빈 줄에는 그리지 않는다 — 갈 곳이 없다. */\n if (!record?.id) return html``\n\n const count = record.childCount ?? 0\n\n /*\n * 비었으면 **아무것도 그리지 않는다.**\n *\n * 선택해도 빈 화면이 나오는 버튼은 없는 것이 낫다. 줄표를 둘까 했지만 그것은 「아직 안 세어\n * 봤다」와 구별하려는 표시인데, 개수는 줄과 함께 오므로 그런 상태가 없다. 그리고 계층의\n * 아래쪽은 대부분 잎이라 줄표가 화면을 덮는다.\n *\n * 비우면 **들어갈 것이 있는 줄만** 눈에 띈다 — 그게 이 칸의 목적이다.\n */\n if (!count) return html``\n\n return html`<button\n class=\"icon wide\"\n title=${i18next.t('button.enter-location')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.enter(record)\n }}\n >\n <span>${count}</span>\n <md-icon>chevron_right</md-icon>\n </button>`\n }\n },\n fixed: true\n }\n\n /*\n * 표준 거터 뒤, 이름 앞에 끼운다.\n *\n * `#`(순번)과 체크박스는 **모든 마스터 화면에 같은 자리로 있는** 거터다. 그 왼쪽에 이 화면만의 칸을\n * 두었더니 체크박스가 이 화면에서만 오른쪽으로 밀렸다 — 열두 화면을 오가며 고르는 작업에서 손이\n * 매번 다른 자리를 찾는다.\n *\n * 이름 바로 앞인 이유: 「하위」는 그 줄이 무엇인지(이름)에 딸린 사실이라 이름 옆에 있어야 읽힌다.\n */\n const columns = [...config.columns]\n const firstNonGutter = columns.findIndex((column: any) => column?.type !== 'gutter')\n\n columns.splice(firstNonGutter < 0 ? columns.length : firstNonGutter, 0, enterColumn)\n\n return {\n ...config,\n columns,\n emptyNote: {\n icon: 'segment',\n title: i18next.t('text.no-location-here'),\n description: i18next.t('text.no-location-here-description')\n }\n }\n }\n\n /**\n * 지금 자리의 자식만 받는다. 페이지네이션이 그대로 동작한다 — 세는 것이 형제이므로.\n */\n async fetchHandler(options: FetchOption) {\n const filters = options.filters || []\n\n /* 검색이나 필터가 걸려 있으면 범위를 푼다 — 이름을 아는 사람은 계층을 걷지 않는다. */\n if (filters.length > 0 || this.grist?.searchText) {\n return super.fetchHandler(options)\n }\n\n const here = this.path[this.path.length - 1]\n\n return super.fetchHandler({\n ...options,\n filters: [\n ...filters,\n here\n ? /*\n * ── `parentId` 는 아이디를 담기도 하고 이름을 담기도 한다 ──────────\n * 이 칸은 외래 키가 아니라 문자열이라 쓰는 쪽이 정하고, 지금 데이터는 **이름**을 담고 있다.\n * [확인함] 2026-09-01\n *\n * sqlite3 db.sqlite \"select name, parent_id from ops_operational_location\"\n * LINE-1|PLANT-1 ← 아이디가 아니라 이름\n * CUT-01|LINE-1\n *\n * 그래서 아이디만으로 걸면 **아무것도 나오지 않는다**(실제로 그렇게 나왔다). 둘을 다 받는다 —\n * 한쪽만 받으면 규약이 바뀌는 날 화면이 오류보고없이 빈다.\n *\n * 이 두 규약이 섞여 있는 것 자체는 이 화면이 풀 문제가 아니다. 커널과 초기 데이터 쪽 결정이 필요하다.\n */\n { name: 'parentId', operator: 'in', value: [here.id, here.name].filter(Boolean) }\n : /* 최상위 — 상위가 없는 줄들. */ { name: 'parentId', operator: 'is_null', value: '' }\n ]\n } as FetchOption)\n }\n}\n"]}
1
+ {"version":3,"file":"operational-location.js","sourceRoot":"","sources":["../../../client/pages/master/operational-location.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport { FetchOption } from '@operato/data-grist'\nimport { i18next } from '@operato/i18n'\nimport { navigate } from '@operato/shell'\n\nimport { EFFECTIVE, IDENTITY, MasterPageBase, MasterSpec } from './master-page-base.js'\nimport { INTENT_OPTIONS, LEVEL_OPTIONS, SYSTEM_OPTIONS, withBlank } from './master-options.js'\n\n/** 서버가 주는 로케이션 한 줄 — 상위를 `parentId` 로 가리킨다. */\ntype LocationRecord = Record<string, any> & { id: string; name: string; parentId?: string | null }\n\n/**\n * 로케이션 — 공장 · 라인 · 설비 앞 · 창고 칸.\n *\n * ── 계층을 한 단씩 걷는다 ──────────────────────────────────────────────────\n * 로케이션은 계층이다. 공장 안에 라인이 있고 라인 안에 설비 자리가 있다. 평면 표로 늘어놓으면 그 구조가\n * 사라진다 — 이름 순으로 섞여서 공장이 다섯째 줄에, 그 아래 라인이 넷째 줄에 온다.\n *\n * **그리고 이것이 틀리면 그 위의 집계가 전부 틀린다.** 실적이 「어디서」를 이 값으로 가리키므로, 라인\n * 단위 생산량과 공장 단위 가동률이 다 이 구조 위에서 계산된다.\n *\n * 그래서 이 화면은 지금 자리의 **자식만** 보인다. 어디에 있는지는 빵가루가 말하고, 형제가 많으면\n * 페이지네이션이 나눈다.\n *\n * ── 왜 트리 표가 아닌가 ────────────────────────────────────────────────────\n * `ox-grist` 에 트리 표가 있고 처음에 그것으로 만들었다. 두 가지가 걸렸다.\n *\n * **하나. 트리는 서브트리 전부를 메모리로 요구한다.** 지연 로딩이 없다 — 펼침은 플래그만 바꾼다.\n *\n * data-manipulator.ts:231 expandNode(record) { record.__expanded__ = true; this.refresh() }\n *\n * 페이지네이션으로 막을 수도 없다. 그 페이지네이션은 **뿌리를 센다**(`data-grist.ts:1095` 의 `idx` 는\n * 받은 배열 = 뿌리의 첨자). 로케이션의 뿌리는 사이트라서 공장 열 곳을 운영해도 열이고, 그 밑에 이십만이\n * 달려 있어도 페이지 수는 1이다. 창고는 랙 칸마다 한 줄이라 수만이 된다.\n *\n * 지연 로딩을 붙이는 길은 있다 — `expand-node` 가 `composed: true` 로 밖까지 나온다. 그러나 그것도\n * **깊이만** 막는다. 통로 하나에 랙 칸이 이천이면 그 자리를 여는 순간 이천이 온다. 너비까지 막으려면\n * 가지마다 「더 보기」 줄이 필요하고, 그 줄은 데이터 줄처럼 생겨서 `dirtyRecords` · `exportRecords` ·\n * `checkDirties` · 순번 · 저장마다 예외가 된다. 한 곳을 빠뜨리면 「1,900개 더」가 로케이션으로 저장된다.\n *\n * **둘. 개수에 따라 모습을 바꾸면 그 화면은 배울 수 없다.** 한동안 「적으면 트리, 많으면 드릴다운」으로\n * 두었다. 원칙처럼 들렸지만 실제로는 제품이 둘이 되는 것이었다 — 고객마다 다른 화면을 쓰고, 공장이\n * 커지면 쓰던 사람 밑에서 모습이 바뀐다. 링크도 트리에서는 뜻이 없고, 빵가루 · 순번 · 페이지네이션이\n * 있다 없다 한다. 그래서 하나로 정했다.\n *\n * ── 자리의 주인은 URL 이다 ─────────────────────────────────────────────────\n * 지금 어디를 보고 있나는 주소가 갖는다(`pageUpdated`). 그래야 자리를 붙여 보낼 수 있고, 뒤로 가기가\n * 한 단 올라가고, 새로 고쳐도 그 자리에 남는다.\n */\n@customElement('ops-master-operational-location')\nexport class OpsMasterOperationalLocation extends MasterPageBase {\n static styles = [\n ...MasterPageBase.styles,\n css`\n .scope {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 2px;\n min-height: 26px;\n }\n\n .scope button {\n display: flex;\n align-items: center;\n gap: 2px;\n border: none;\n background: none;\n padding: 2px 6px;\n border-radius: 4px;\n font: inherit;\n font-size: 12.5px;\n color: var(--ui-muted, #57676f);\n cursor: pointer;\n }\n\n .scope button:hover {\n background-color: var(--ui-hover, rgba(0, 0, 0, 0.05));\n color: var(--ui-ink, #17272f);\n }\n\n /* 지금 있는 자리. 선택할 곳이 아니므로 버튼으로 그리지 않는다. */\n .scope .here {\n padding: 2px 6px;\n font-size: 12.5px;\n font-weight: 600;\n color: var(--ui-ink, #17272f);\n }\n\n .scope md-icon {\n --md-icon-size: 16px;\n font-size: 16px;\n width: 16px;\n height: 16px;\n }\n\n .scope .sep {\n color: var(--ui-line, #c9d4d9);\n font-size: 12px;\n }\n `\n ]\n\n protected get spec(): MasterSpec {\n return {\n key: 'operationalLocation',\n axis: 'operationalLocation',\n title: 'location',\n help: 'ops-master/operational-location',\n query: 'operationalLocations',\n mutation: 'updateMultipleOperationalLocation',\n columns: [\n ...IDENTITY,\n { name: 'level', label: 'equipment-level', type: 'select', options: withBlank(LEVEL_OPTIONS), width: 140 },\n { name: 'type', label: 'location-type', type: 'string', width: 140 },\n /*\n * 누구의 자리인가 — 한 현장에 제품이 둘 서면 재고가 이 값으로 갈린다(ADR-0081). 타입 옆에 둔다:\n * 둘 다 「이 자리가 무엇인가」이고, 타입을 적는 사람이 주인도 안다.\n */\n { name: 'system', label: 'owning-system', type: 'select', options: withBlank(SYSTEM_OPTIONS), width: 110 },\n { name: 'capacity', label: 'capacity', type: 'number', width: 90 },\n /*\n * ── 나를 누가 쓰나 ────────────────────────────────────────────────\n *\n * 값의 주인이 이 표가 아니라 **저쪽 표**다. 그래서 여기서 못 고친다 — 바꾸려면 그쪽에서\n * 이 이름을 가리키게 하거나 떼면 된다.\n *\n * 넘치면 **몇이 안 보이는지 함께 낸다.** 오류보고없이 자르면 「셋이 있다」와 「서른이 있는데\n * 셋만 보인다」가 같아 보이고, 그 칸을 보고 사람이 판단을 내린다.\n */\n /*\n * ── 여기서 도는 근무조 ────────────────────────────────────────────\n *\n * **비어 있는 것이 정상이다.** 채우는 것은 위와 다를 때뿐이고, 아래 단은 물려받는다. 공장에\n * 한 줄 적으면 그 아래 라인·셀·설비가 전부 그것으로 돈다.\n *\n * 전에는 이 사실이 설비마다 사본으로 있었다 — 한 공장 22대가 같은 값을 22벌 들었다. 3교대를\n * 2교대로 바꾸려면 22줄을 고쳐야 했고, 하나를 빠뜨리면 그 설비만 옛 근무로 계산됐다.\n */\n {\n name: 'workShiftNames',\n label: 'work-shifts-here',\n type: 'reference-list',\n reference: { queryName: 'workShifts' },\n width: 200\n },\n { name: 'staff', label: 'staff-here', type: 'list', width: 180, readonly: true },\n { name: 'equipmentHere', label: 'equipment-here', type: 'list', width: 200, readonly: true },\n { name: 'assetsHere', label: 'assets-here', type: 'list', width: 200, readonly: true },\n { name: 'occupantOverflow', label: 'not-shown', type: 'number', width: 80, readonly: true },\n /*\n * 상위 로케이션은 컬럼으로 두지 않는다 — 빵가루가 그것을 말한다. 같은 사실을 두 군데 보이면 둘이\n * 어긋날 때 어느 쪽이 맞는지 알 수 없다.\n *\n * 다만 검색에는 남긴다(hidden). 「LINE-1 아래 있는 것」을 찾는 일이 있다.\n */\n {\n /* 이름이 `parentId` 인데 담기는 것은 상위 자리의 **이름**이다(`GUNSAN`). 커널이 이름으로 주소를 잡는다. */\n name: 'parentId',\n label: 'parent-location',\n type: 'reference',\n reference: { queryName: 'operationalLocations' },\n width: 150,\n hidden: true\n },\n /*\n * 하위 개수 — 표에는 「하위」 칸이 따로 그리므로 여기서는 감춘다. 이 줄이 있어야 질의가 그 값을\n * 가져온다(`master-page-base` 의 fetchHandler 가 spec 의 컬럼 이름으로 필드를 짓는다).\n */\n { name: 'childCount', label: 'sub-locations', type: 'number', width: 0, readonly: true, hidden: true },\n ...EFFECTIVE\n ]\n }\n }\n\n /**\n * 빵가루 — 최상위부터 지금 자리까지. 빈 배열이면 최상위를 보고 있다.\n *\n * **이 값의 주인은 URL 이다.** 여기 직접 넣지 않는다 — `enter` 는 주소만 바꾸고, 바뀐 주소를\n * `pageUpdated` 가 받아 이 값을 다시 세운다. 상태를 여기 두고 주소를 따로 갱신하면 둘이 어긋나는 날이\n * 온다.\n */\n @state() private path: LocationRecord[] = []\n\n /* 이 화면은 주소 조각을 **파고들기**에 쓴다 — 목록 좁히기로 겹쳐 쓰지 않는다. */\n protected get pathSegmentNarrowsList(): boolean {\n return false\n }\n\n async pageInitialized() {\n /*\n * 주소에 자리가 담겨 있으면 여기서도 세운다.\n *\n * `pageUpdated` 만 믿으면 **주소로 바로 들어온 경우가 빠진다** — 그 호출이 이 함수보다 먼저\n * 지나간다. 실제로 `/LINE-1` 로 들어갔을 때 최상위가 나왔다. [확인함] 2026-09-01\n */\n await this.syncPath(this.lifecycle?.resourceId)\n\n await super.pageInitialized()\n }\n\n /**\n * 주소가 바뀌면 빵가루를 다시 세운다.\n *\n * 주소에는 **지금 자리 하나만** 담는다(`.../ops-master-operational-location/AISLE-01`). 조상은\n * `parentId` 를 타고 올라가 알아낸다 — 경로 전체를 주소에 늘어놓으면 이름을 고칠 때마다 주소가 깨지고,\n * 사람이 읽을 수 없게 길어진다.\n */\n async pageUpdated(changes: any, after: any, before: any) {\n if (after?.resourceId !== before?.resourceId) {\n await this.syncPath(after?.resourceId)\n }\n\n await super.pageUpdated()\n }\n\n /** 주소가 가리키는 자리로 빵가루를 맞춘다. 자리가 없으면 최상위다. */\n private async syncPath(key?: string) {\n this.path = key ? await this.pathTo(key) : []\n }\n\n /** 이 자리부터 최상위까지 거슬러 올라가며 빵가루를 만든다. */\n private async pathTo(key: string): Promise<LocationRecord[]> {\n const chain: LocationRecord[] = []\n /* 자기 자신이나 서로를 상위로 참조하는 데이터가 있으면 여기서 무한히 반복된다. 밟은 자리를 기억해 끊는다. */\n const walked = new Set<string>()\n\n let cursor: string | null | undefined = key\n\n while (cursor && !walked.has(cursor)) {\n walked.add(cursor)\n\n const node = await this.lookup(cursor)\n if (!node) break\n\n chain.unshift(node)\n cursor = node.parentId\n }\n\n return chain\n }\n\n /** 한 자리를 찾는다. 아이디로 찾고, 없으면 이름으로 찾는다 — 주소에는 사람이 읽는 이름이 온다. */\n private async lookup(key: string): Promise<LocationRecord | undefined> {\n for (const field of ['id', 'name']) {\n const { records } = await super.fetchHandler({\n page: 1,\n limit: 1,\n filters: [{ name: field, operator: 'eq', value: key }]\n } as FetchOption)\n\n if (records.length) return records[0] as LocationRecord\n }\n\n return undefined\n }\n\n protected renderScope() {\n return html`\n <div class=\"scope\">\n <button @click=${() => this.enter(null)}>\n <md-icon>home</md-icon>\n ${i18next.t('label.all-locations')}\n </button>\n ${this.path.map((node, index) =>\n index === this.path.length - 1\n ? html`<span class=\"sep\">/</span><span class=\"here\">${node.name}</span>`\n : html`<span class=\"sep\">/</span><button @click=${() => this.enter(node)}>${node.name}</button>`\n )}\n </div>\n `\n }\n\n /**\n * 그 자리로 들어간다. `null` 이면 최상위로 돌아간다.\n *\n * **주소만 바꾼다.** 빵가루도 목록도 여기서 손대지 않는다 — 셸이 주소를 물고 `pageUpdated` 를 부르고,\n * 거기서 하나로 다시 만들어진다. 여기서 함께 고치면 뒤로 가기로 들어왔을 때와 다른 길이 생긴다.\n */\n private enter(node: LocationRecord | null) {\n /*\n * 아직 저장하지 않은 줄로는 들어갈 수 없다. 표 끝의 빈 줄에도 이 버추가 붙기 때문이다.\n *\n * [확인함] 이 가드가 없을 때 화면을 새로 열면 주소가 `.../operational-location/undefined` 가 됐다.\n * 무엇이 그 클릭을 냈는지는 **아직 못 찾았다** — 화면을 새로 열 때만 나고, 앱 안에서 옮겨 갈 때는\n * 나지 않는다. 원인을 모른 채 「고쳤다」고 하지 않는다. 다만 저장 안 된 줄로 들어가지 못하는 것은\n * 원인과 무관하게 맞는 규칙이다.\n */\n if (node && !node.id) {\n return\n }\n\n navigate(node ? `${this.lifecycle?.page}/${node.name || node.id}` : `${this.lifecycle?.page}`)\n }\n\n protected configOf() {\n const config = super.configOf()\n\n const enterColumn =\n /*\n * 「하위」 칸 — 개수와 들어가기를 함께 담는다.\n *\n * ── 개수가 뜻을 나른다 ─────────────────────────────────────────────\n * 처음에는 아이콘만 두었다. 그러니 그 버튼이 아무것도 알려 주지 않았다. 선택해 보기 전에는 안에\n * 무엇이 있는지, 있기는 한지 알 수 없다. 그리고 **왼쪽 끝의 화살은 트리의 펼침 버튼으로\n * 읽힌다.** 실제로는 다른 자리로 옮겨 가는데.\n *\n * 아이콘을 갈아 봤지만(굽은 화살은 표 안에서 지저분했다) 아이콘이 풀 문제가 아니었다. 칸에\n * 머리를 달고 개수를 넣으면 그림 없이도 읽힌다. 펼침 버튼에는 머리가 없다.\n *\n * 하위\n * 4 › 안에 넷이 있다. 누르면 그리로 간다\n * ― 안이 비었다. 누를 일이 없다\n *\n * 개수는 서버가 낸다(`childCount` 필드 리졸버).\n *\n * ── 왜 거터의 기본 버튼을 쓰지 않나 ─────────────────────────────────\n * 그 거터는 아이콘만 그리고, 누름은 표의 셀 클릭 경로를 탄다. 그 경로에는 이동이 견딜 수 없는\n * 관문이 둘 있다. [확인함] 2026-09-01\n *\n * data-grid-body-click-handler.ts:18 if (this.editTarget) return\n * data-grid-body-click-handler.ts:23 (e.target).closest('ox-grid-field')\n *\n * 앞은 **다른 칸이 편집 중이면 첫 누름을 가져간다.** 값을 고치다가 옮겨 가려면 두 번 선택해야\n * 한다. 뒤는 셀 경계를 스치면 칸을 못 찾는다(아이콘 한가운데를 선택해도 안 먹혔다).\n *\n * 그래서 자기 버튼을 그린다. 진짜 `button` 이라 키보드로도 닿고 초점 표시가 붙는다. 모양은\n * `data-grid-field` 의 `button.icon` 이 갖는다 — 렌더러의 결과는 셀의 그림자 안에 그려지므로\n * 이 화면의 스타일이 닿지 않는다.\n *\n * `gutterName` 은 남긴다 — `type: 'gutter'` 는 등록된 이름이 있어야 컴파일된다. 없이 두면\n * 컬럼 컴파일이 터져 **표에 컬럼이 하나도 안 남는다**.\n *\n * main.js Cannot destructure property 'name' of 'compiled' as it is undefined\n */\n /*\n * ── 거터가 아니라 보통 칸이다 ─────────────────────────────────────────\n * 처음에 `type: 'gutter'` 로 두었다. 렌더러를 덮기 쉬워서였는데, 거터는 여백 규칙이 달라\n * (`--grid-gutter-padding` 2px 대 보통 칸 8px 10px) 경계선도 없다. 그래서 왼쪽 셋이 한 덩어리로\n * 붙어 보였다. [확인함] 2026-09-02\n *\n * # w=20 padding 2px 0 구분선 없음\n * 선택 w=30 padding 2px 0 구분선 없음\n * 하위 w=62 padding 2px 0 구분선 없음 ← 여기\n * 이름 w=160 padding 8px 0 8px 10px 구분선 1px\n *\n * 이 칸은 **개수를 담는 데이터 칸**이다. 거터는 표의 장치(순번 · 선택 · 더러움 표식)를 위한\n * 자리다. 보통 칸으로 두면 여백과 경계선을 다른 칸과 같이 받는다.\n */\n {\n type: 'string',\n name: 'childCount',\n width: 62,\n /* 폭을 끌어 바꿀 것이 없고, 개수로 정렬하려면 서버가 그 축을 내야 한다. */\n resizable: false,\n sortable: false,\n header: i18next.t('field.sub-locations'),\n record: {\n align: 'center',\n renderer: (_value: any, _column: any, record: LocationRecord) => {\n /* 표 끝의 빈 줄에는 그리지 않는다 — 갈 곳이 없다. */\n if (!record?.id) return html``\n\n const count = record.childCount ?? 0\n\n /*\n * 비었으면 **아무것도 그리지 않는다.**\n *\n * 선택해도 빈 화면이 나오는 버튼은 없는 것이 낫다. 줄표를 둘까 했지만 그것은 「아직 안 세어\n * 봤다」와 구별하려는 표시인데, 개수는 줄과 함께 오므로 그런 상태가 없다. 그리고 계층의\n * 아래쪽은 대부분 잎이라 줄표가 화면을 덮는다.\n *\n * 비우면 **들어갈 것이 있는 줄만** 눈에 띈다 — 그게 이 칸의 목적이다.\n */\n if (!count) return html``\n\n return html`<button\n class=\"icon wide\"\n title=${i18next.t('button.enter-location')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.enter(record)\n }}\n >\n <span>${count}</span>\n <md-icon>chevron_right</md-icon>\n </button>`\n }\n },\n fixed: true\n }\n\n /*\n * 표준 거터 뒤, 이름 앞에 끼운다.\n *\n * `#`(순번)과 체크박스는 **모든 마스터 화면에 같은 자리로 있는** 거터다. 그 왼쪽에 이 화면만의 칸을\n * 두었더니 체크박스가 이 화면에서만 오른쪽으로 밀렸다 — 열두 화면을 오가며 고르는 작업에서 손이\n * 매번 다른 자리를 찾는다.\n *\n * 이름 바로 앞인 이유: 「하위」는 그 줄이 무엇인지(이름)에 딸린 사실이라 이름 옆에 있어야 읽힌다.\n */\n const columns = [...config.columns]\n const firstNonGutter = columns.findIndex((column: any) => column?.type !== 'gutter')\n\n columns.splice(firstNonGutter < 0 ? columns.length : firstNonGutter, 0, enterColumn)\n\n return {\n ...config,\n columns,\n emptyNote: {\n icon: 'segment',\n title: i18next.t('text.no-location-here'),\n description: i18next.t('text.no-location-here-description')\n }\n }\n }\n\n /**\n * 지금 자리의 자식만 받는다. 페이지네이션이 그대로 동작한다 — 세는 것이 형제이므로.\n */\n async fetchHandler(options: FetchOption) {\n const filters = options.filters || []\n\n /* 검색이나 필터가 걸려 있으면 범위를 푼다 — 이름을 아는 사람은 계층을 걷지 않는다. */\n if (filters.length > 0 || this.grist?.searchText) {\n return super.fetchHandler(options)\n }\n\n const here = this.path[this.path.length - 1]\n\n return super.fetchHandler({\n ...options,\n filters: [\n ...filters,\n here\n ? /*\n * ── `parentId` 는 아이디를 담기도 하고 이름을 담기도 한다 ──────────\n * 이 칸은 외래 키가 아니라 문자열이라 쓰는 쪽이 정하고, 지금 데이터는 **이름**을 담고 있다.\n * [확인함] 2026-09-01\n *\n * sqlite3 db.sqlite \"select name, parent_id from ops_operational_location\"\n * LINE-1|PLANT-1 ← 아이디가 아니라 이름\n * CUT-01|LINE-1\n *\n * 그래서 아이디만으로 걸면 **아무것도 나오지 않는다**(실제로 그렇게 나왔다). 둘을 다 받는다 —\n * 한쪽만 받으면 규약이 바뀌는 날 화면이 오류보고없이 빈다.\n *\n * 이 두 규약이 섞여 있는 것 자체는 이 화면이 풀 문제가 아니다. 커널과 초기 데이터 쪽 결정이 필요하다.\n */\n { name: 'parentId', operator: 'in', value: [here.id, here.name].filter(Boolean) }\n : /* 최상위 — 상위가 없는 줄들. */ { name: 'parentId', operator: 'is_null', value: '' }\n ]\n } as FetchOption)\n }\n}\n"]}