@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.
- package/dist-client/pages/master/applied-when-dialog.d.ts +45 -0
- package/dist-client/pages/master/applied-when-dialog.js +139 -0
- package/dist-client/pages/master/applied-when-dialog.js.map +1 -0
- package/dist-client/pages/master/assignment-fields.d.ts +28 -0
- package/dist-client/pages/master/assignment-fields.js +55 -0
- package/dist-client/pages/master/assignment-fields.js.map +1 -0
- package/dist-client/pages/master/equipment.js +1 -0
- package/dist-client/pages/master/equipment.js.map +1 -1
- package/dist-client/pages/master/master-page-base.d.ts +14 -0
- package/dist-client/pages/master/master-page-base.js +40 -4
- package/dist-client/pages/master/master-page-base.js.map +1 -1
- package/dist-client/pages/master/operational-location.js +1 -0
- package/dist-client/pages/master/operational-location.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/assignment/applied-when.d.ts +24 -0
- package/dist-server/service/assignment/applied-when.js +16 -0
- package/dist-server/service/assignment/applied-when.js.map +1 -0
- package/dist-server/service/assignment/assignment-claim.d.ts +111 -0
- package/dist-server/service/assignment/assignment-claim.js +160 -0
- package/dist-server/service/assignment/assignment-claim.js.map +1 -0
- package/dist-server/service/assignment/assignment-history.d.ts +87 -0
- package/dist-server/service/assignment/assignment-history.js +250 -0
- package/dist-server/service/assignment/assignment-history.js.map +1 -0
- package/dist-server/service/assignment/assignment-write.d.ts +25 -0
- package/dist-server/service/assignment/assignment-write.js +69 -0
- package/dist-server/service/assignment/assignment-write.js.map +1 -0
- package/dist-server/service/equipment/equipment-master-resolver.d.ts +9 -1
- package/dist-server/service/equipment/equipment-master-resolver.js +6 -3
- package/dist-server/service/equipment/equipment-master-resolver.js.map +1 -1
- package/dist-server/service/index.d.ts +3 -0
- package/dist-server/service/index.js +3 -0
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/location/location-master-resolver.d.ts +4 -1
- package/dist-server/service/location/location-master-resolver.js +6 -3
- package/dist-server/service/location/location-master-resolver.js.map +1 -1
- package/dist-server/service/master-crud.d.ts +11 -0
- package/dist-server/service/master-crud.js +12 -1
- package/dist-server/service/master-crud.js.map +1 -1
- package/dist-server/service/material/material-revision-service.js +13 -0
- package/dist-server/service/material/material-revision-service.js.map +1 -1
- package/dist-server/service/recipe/recipe-revision-service.js +13 -0
- package/dist-server/service/recipe/recipe-revision-service.js.map +1 -1
- package/dist-server/service/test-spec/test-spec-revision-service.js +14 -0
- package/dist-server/service/test-spec/test-spec-revision-service.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import { ASSIGNMENT_FIELDS } from './assignment-fields.js';
|
|
3
|
+
/**
|
|
4
|
+
* **언제부터 그러한가를 묻는 자리.**
|
|
5
|
+
*
|
|
6
|
+
* ── 왜 날짜 칸 하나가 아닌가 ───────────────────────────────────────────────
|
|
7
|
+
* 그리드에 날짜 열을 하나 더하면 사람은 그것을 **또 하나의 값**으로 읽는다. 이 셋(자리 배정 · 근무조
|
|
8
|
+
* 선언 · 부모 관계)은 값이 아니라 **언제부터 그렇게 볼 것인가**를 함께 정하는 일이고, 과거를 적으면
|
|
9
|
+
* **지연 기록인지 정정인지**까지 갈라야 한다. 그래서 저장 직전에 한 번 묻는다.
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* 바뀌기 전 → 바뀐 뒤
|
|
13
|
+
* 적용 시작 기본값 「지금부터」
|
|
14
|
+
* 과거 시각이면 지연 기록인가 이미 적은 것의 정정인가
|
|
15
|
+
* 미래 시각이면 지금 값은 그대로이고 변경이 예약된다고 알린다
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* ⚠ **기록 시각은 안 묻는다.** 사람이 넣으면 「언제 알았나」를 사람이 정하게 된다 — 서버가 붙인다.
|
|
19
|
+
*/
|
|
20
|
+
export interface AppliedAnswer {
|
|
21
|
+
at?: string;
|
|
22
|
+
kind?: 'change' | 'correction';
|
|
23
|
+
until?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface AssignmentChange {
|
|
26
|
+
rowName: string;
|
|
27
|
+
field: string;
|
|
28
|
+
before: string;
|
|
29
|
+
after: string;
|
|
30
|
+
}
|
|
31
|
+
export declare class AppliedWhenDialog extends LitElement {
|
|
32
|
+
static styles: import("lit").CSSResult;
|
|
33
|
+
changes: AssignmentChange[];
|
|
34
|
+
onAnswer?: (answer: AppliedAnswer) => void;
|
|
35
|
+
onCancel?: () => void;
|
|
36
|
+
private at;
|
|
37
|
+
private kind;
|
|
38
|
+
private until;
|
|
39
|
+
/** 안 적으면 지금부터다 — 그 기본값이 곧 「지금부터」라는 주장이다. */
|
|
40
|
+
private get when();
|
|
41
|
+
private get past();
|
|
42
|
+
private get future();
|
|
43
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
44
|
+
}
|
|
45
|
+
export { ASSIGNMENT_FIELDS };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import { LitElement, css, html } from 'lit';
|
|
3
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
4
|
+
import { i18next } from '@operato/i18n';
|
|
5
|
+
import { ASSIGNMENT_FIELDS } from './assignment-fields.js';
|
|
6
|
+
let AppliedWhenDialog = class AppliedWhenDialog extends LitElement {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
this.changes = [];
|
|
10
|
+
this.at = '';
|
|
11
|
+
this.kind = 'change';
|
|
12
|
+
this.until = '';
|
|
13
|
+
}
|
|
14
|
+
static { this.styles = css `
|
|
15
|
+
:host {
|
|
16
|
+
display: block;
|
|
17
|
+
padding: var(--spacing-medium, 12px);
|
|
18
|
+
}
|
|
19
|
+
.what {
|
|
20
|
+
display: grid;
|
|
21
|
+
grid-template-columns: auto 1fr;
|
|
22
|
+
gap: var(--spacing-tiny, 4px) var(--spacing-small, 8px);
|
|
23
|
+
margin-bottom: var(--spacing-medium, 12px);
|
|
24
|
+
}
|
|
25
|
+
.what span {
|
|
26
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
27
|
+
}
|
|
28
|
+
.note {
|
|
29
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
30
|
+
margin-top: var(--spacing-small, 8px);
|
|
31
|
+
}
|
|
32
|
+
.buttons {
|
|
33
|
+
display: flex;
|
|
34
|
+
justify-content: flex-end;
|
|
35
|
+
gap: var(--spacing-small, 8px);
|
|
36
|
+
margin-top: var(--spacing-medium, 12px);
|
|
37
|
+
}
|
|
38
|
+
`; }
|
|
39
|
+
/** 안 적으면 지금부터다 — 그 기본값이 곧 「지금부터」라는 주장이다. */
|
|
40
|
+
get when() {
|
|
41
|
+
return this.at ? new Date(this.at).getTime() : Date.now();
|
|
42
|
+
}
|
|
43
|
+
get past() {
|
|
44
|
+
return this.when < Date.now() - 60000;
|
|
45
|
+
}
|
|
46
|
+
get future() {
|
|
47
|
+
return this.when > Date.now() + 60000;
|
|
48
|
+
}
|
|
49
|
+
render() {
|
|
50
|
+
return html `
|
|
51
|
+
<div class="what">
|
|
52
|
+
${this.changes.map(one => html `
|
|
53
|
+
<span>${one.rowName} · ${labelOf(one.field)}</span>
|
|
54
|
+
<div>${one.before || i18next.t('text.none')} → <b>${one.after || i18next.t('text.none')}</b></div>
|
|
55
|
+
`)}
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<label>
|
|
59
|
+
${i18next.t('label.applied-from')}
|
|
60
|
+
<input
|
|
61
|
+
type="datetime-local"
|
|
62
|
+
.value=${this.at}
|
|
63
|
+
@change=${(e) => (this.at = e.target.value)}
|
|
64
|
+
placeholder=${i18next.t('text.from-now')}
|
|
65
|
+
/>
|
|
66
|
+
</label>
|
|
67
|
+
|
|
68
|
+
${this.past
|
|
69
|
+
? html `
|
|
70
|
+
<div>
|
|
71
|
+
<label>
|
|
72
|
+
<input type="radio" name="kind" ?checked=${this.kind === 'change'} @change=${() => (this.kind = 'change')} />
|
|
73
|
+
${i18next.t('label.late-record')}
|
|
74
|
+
</label>
|
|
75
|
+
<label>
|
|
76
|
+
<input type="radio" name="kind" ?checked=${this.kind === 'correction'} @change=${() => (this.kind = 'correction')} />
|
|
77
|
+
${i18next.t('label.correct-what-was-recorded')}
|
|
78
|
+
</label>
|
|
79
|
+
${this.kind === 'correction'
|
|
80
|
+
? html `
|
|
81
|
+
<label>
|
|
82
|
+
${i18next.t('label.applied-until')}
|
|
83
|
+
<input type="datetime-local" .value=${this.until} @change=${(e) => (this.until = e.target.value)} />
|
|
84
|
+
</label>
|
|
85
|
+
`
|
|
86
|
+
: ''}
|
|
87
|
+
</div>
|
|
88
|
+
`
|
|
89
|
+
: ''}
|
|
90
|
+
${this.future ? html `<div class="note">${i18next.t('text.change-is-scheduled')}</div>` : ''}
|
|
91
|
+
|
|
92
|
+
<div class="buttons">
|
|
93
|
+
<button @click=${() => this.onCancel?.()}>${i18next.t('button.cancel')}</button>
|
|
94
|
+
<button
|
|
95
|
+
@click=${() => this.onAnswer?.({
|
|
96
|
+
...(this.at ? { at: new Date(this.at).toISOString() } : {}),
|
|
97
|
+
...(this.past ? { kind: this.kind } : {}),
|
|
98
|
+
...(this.past && this.kind === 'correction' && this.until ? { until: new Date(this.until).toISOString() } : {})
|
|
99
|
+
})}
|
|
100
|
+
>
|
|
101
|
+
${i18next.t('button.save')}
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
__decorate([
|
|
108
|
+
property({ type: Array }),
|
|
109
|
+
__metadata("design:type", Array)
|
|
110
|
+
], AppliedWhenDialog.prototype, "changes", void 0);
|
|
111
|
+
__decorate([
|
|
112
|
+
property({ type: Object }),
|
|
113
|
+
__metadata("design:type", Function)
|
|
114
|
+
], AppliedWhenDialog.prototype, "onAnswer", void 0);
|
|
115
|
+
__decorate([
|
|
116
|
+
property({ type: Object }),
|
|
117
|
+
__metadata("design:type", Function)
|
|
118
|
+
], AppliedWhenDialog.prototype, "onCancel", void 0);
|
|
119
|
+
__decorate([
|
|
120
|
+
state(),
|
|
121
|
+
__metadata("design:type", Object)
|
|
122
|
+
], AppliedWhenDialog.prototype, "at", void 0);
|
|
123
|
+
__decorate([
|
|
124
|
+
state(),
|
|
125
|
+
__metadata("design:type", String)
|
|
126
|
+
], AppliedWhenDialog.prototype, "kind", void 0);
|
|
127
|
+
__decorate([
|
|
128
|
+
state(),
|
|
129
|
+
__metadata("design:type", Object)
|
|
130
|
+
], AppliedWhenDialog.prototype, "until", void 0);
|
|
131
|
+
AppliedWhenDialog = __decorate([
|
|
132
|
+
customElement('applied-when-dialog')
|
|
133
|
+
], AppliedWhenDialog);
|
|
134
|
+
export { AppliedWhenDialog };
|
|
135
|
+
function labelOf(field) {
|
|
136
|
+
return i18next.t(`field.${field}`, { defaultValue: field });
|
|
137
|
+
}
|
|
138
|
+
export { ASSIGNMENT_FIELDS };
|
|
139
|
+
//# sourceMappingURL=applied-when-dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"applied-when-dialog.js","sourceRoot":"","sources":["../../../client/pages/master/applied-when-dialog.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { i18next } from '@operato/i18n'\n\nimport { ASSIGNMENT_FIELDS } from './assignment-fields.js'\n\n/**\n * **언제부터 그러한가를 묻는 자리.**\n *\n * ── 왜 날짜 칸 하나가 아닌가 ───────────────────────────────────────────────\n * 그리드에 날짜 열을 하나 더하면 사람은 그것을 **또 하나의 값**으로 읽는다. 이 셋(자리 배정 · 근무조\n * 선언 · 부모 관계)은 값이 아니라 **언제부터 그렇게 볼 것인가**를 함께 정하는 일이고, 과거를 적으면\n * **지연 기록인지 정정인지**까지 갈라야 한다. 그래서 저장 직전에 한 번 묻는다.\n *\n * ```\n * 바뀌기 전 → 바뀐 뒤\n * 적용 시작 기본값 「지금부터」\n * 과거 시각이면 지연 기록인가 이미 적은 것의 정정인가\n * 미래 시각이면 지금 값은 그대로이고 변경이 예약된다고 알린다\n * ```\n *\n * ⚠ **기록 시각은 안 묻는다.** 사람이 넣으면 「언제 알았나」를 사람이 정하게 된다 — 서버가 붙인다.\n */\nexport interface AppliedAnswer {\n at?: string\n kind?: 'change' | 'correction'\n until?: string\n}\n\nexport interface AssignmentChange {\n rowName: string\n field: string\n before: string\n after: string\n}\n\n@customElement('applied-when-dialog')\nexport class AppliedWhenDialog extends LitElement {\n static styles = css`\n :host {\n display: block;\n padding: var(--spacing-medium, 12px);\n }\n .what {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: var(--spacing-tiny, 4px) var(--spacing-small, 8px);\n margin-bottom: var(--spacing-medium, 12px);\n }\n .what span {\n color: var(--md-sys-color-on-surface-variant);\n }\n .note {\n color: var(--md-sys-color-on-surface-variant);\n margin-top: var(--spacing-small, 8px);\n }\n .buttons {\n display: flex;\n justify-content: flex-end;\n gap: var(--spacing-small, 8px);\n margin-top: var(--spacing-medium, 12px);\n }\n `\n\n @property({ type: Array }) changes: AssignmentChange[] = []\n @property({ type: Object }) onAnswer?: (answer: AppliedAnswer) => void\n @property({ type: Object }) onCancel?: () => void\n\n @state() private at = ''\n @state() private kind: 'change' | 'correction' = 'change'\n @state() private until = ''\n\n /** 안 적으면 지금부터다 — 그 기본값이 곧 「지금부터」라는 주장이다. */\n private get when(): number {\n return this.at ? new Date(this.at).getTime() : Date.now()\n }\n\n private get past(): boolean {\n return this.when < Date.now() - 60000\n }\n\n private get future(): boolean {\n return this.when > Date.now() + 60000\n }\n\n render() {\n return html`\n <div class=\"what\">\n ${this.changes.map(\n one => html`\n <span>${one.rowName} · ${labelOf(one.field)}</span>\n <div>${one.before || i18next.t('text.none')} → <b>${one.after || i18next.t('text.none')}</b></div>\n `\n )}\n </div>\n\n <label>\n ${i18next.t('label.applied-from')}\n <input\n type=\"datetime-local\"\n .value=${this.at}\n @change=${(e: Event) => (this.at = (e.target as HTMLInputElement).value)}\n placeholder=${i18next.t('text.from-now')}\n />\n </label>\n\n ${this.past\n ? html`\n <div>\n <label>\n <input type=\"radio\" name=\"kind\" ?checked=${this.kind === 'change'} @change=${() => (this.kind = 'change')} />\n ${i18next.t('label.late-record')}\n </label>\n <label>\n <input type=\"radio\" name=\"kind\" ?checked=${this.kind === 'correction'} @change=${() => (this.kind = 'correction')} />\n ${i18next.t('label.correct-what-was-recorded')}\n </label>\n ${this.kind === 'correction'\n ? html`\n <label>\n ${i18next.t('label.applied-until')}\n <input type=\"datetime-local\" .value=${this.until} @change=${(e: Event) => (this.until = (e.target as HTMLInputElement).value)} />\n </label>\n `\n : ''}\n </div>\n `\n : ''}\n ${this.future ? html`<div class=\"note\">${i18next.t('text.change-is-scheduled')}</div>` : ''}\n\n <div class=\"buttons\">\n <button @click=${() => this.onCancel?.()}>${i18next.t('button.cancel')}</button>\n <button\n @click=${() =>\n this.onAnswer?.({\n ...(this.at ? { at: new Date(this.at).toISOString() } : {}),\n ...(this.past ? { kind: this.kind } : {}),\n ...(this.past && this.kind === 'correction' && this.until ? { until: new Date(this.until).toISOString() } : {})\n })}\n >\n ${i18next.t('button.save')}\n </button>\n </div>\n `\n }\n}\n\nfunction labelOf(field: string): string {\n return i18next.t(`field.${field}`, { defaultValue: field })\n}\n\nexport { ASSIGNMENT_FIELDS }\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **이력을 받는 칸 셋** — 화면이 저장 직전에 「언제부터」를 묻는 대상.
|
|
3
|
+
*
|
|
4
|
+
* 서버의 `ASSIGNMENT_FIELDS` 와 같은 목록이다. 화면이 서버를 import 하지 않으므로(그러면 빌드가
|
|
5
|
+
* 옮겨 간다) 여기 한 벌을 둔다 — **갈라지면 안 묻고 보내는 칸이 생긴다.**
|
|
6
|
+
*/
|
|
7
|
+
export declare const ASSIGNMENT_FIELDS: {
|
|
8
|
+
axis: string;
|
|
9
|
+
field: string;
|
|
10
|
+
}[];
|
|
11
|
+
/** 이 축에서 이력을 받는 칸들. */
|
|
12
|
+
export declare function assignmentFieldsOf(axis: string): string[];
|
|
13
|
+
export interface AssignmentChange {
|
|
14
|
+
rowName: string;
|
|
15
|
+
field: string;
|
|
16
|
+
before: string;
|
|
17
|
+
after: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 보내려는 변경 중 **이력을 받는 칸을 건드린 것**만 고른다.
|
|
21
|
+
*
|
|
22
|
+
* ⚠ **새로 만드는 행은 안 묻는다.** 그 행은 그 순간 생긴 것이고 「언제부터」는 그 순간이다.
|
|
23
|
+
* 물으면 존재하기 전의 자리를 적을 수 있게 된다.
|
|
24
|
+
*
|
|
25
|
+
* ⚠ **같은 값을 다시 보낸 것도 안 묻는다.** 그리드는 한 칸만 고쳐도 행 전체를 보낸다 — 안 바뀐
|
|
26
|
+
* 칸까지 물으면 사람이 매번 날짜를 보게 되고, 그러면 아무도 안 읽는다.
|
|
27
|
+
*/
|
|
28
|
+
export declare function assignmentChangesIn(axis: string, patches: readonly Record<string, unknown>[], before: (id: string) => Record<string, unknown> | undefined): AssignmentChange[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **이력을 받는 칸 셋** — 화면이 저장 직전에 「언제부터」를 묻는 대상.
|
|
3
|
+
*
|
|
4
|
+
* 서버의 `ASSIGNMENT_FIELDS` 와 같은 목록이다. 화면이 서버를 import 하지 않으므로(그러면 빌드가
|
|
5
|
+
* 옮겨 간다) 여기 한 벌을 둔다 — **갈라지면 안 묻고 보내는 칸이 생긴다.**
|
|
6
|
+
*/
|
|
7
|
+
export const ASSIGNMENT_FIELDS = [
|
|
8
|
+
{ axis: 'equipment', field: 'homeLocation' },
|
|
9
|
+
{ axis: 'operationalLocation', field: 'workShiftNames' },
|
|
10
|
+
{ axis: 'operationalLocation', field: 'parentId' }
|
|
11
|
+
];
|
|
12
|
+
/** 이 축에서 이력을 받는 칸들. */
|
|
13
|
+
export function assignmentFieldsOf(axis) {
|
|
14
|
+
return ASSIGNMENT_FIELDS.filter(one => one.axis === axis).map(one => one.field);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 보내려는 변경 중 **이력을 받는 칸을 건드린 것**만 고른다.
|
|
18
|
+
*
|
|
19
|
+
* ⚠ **새로 만드는 행은 안 묻는다.** 그 행은 그 순간 생긴 것이고 「언제부터」는 그 순간이다.
|
|
20
|
+
* 물으면 존재하기 전의 자리를 적을 수 있게 된다.
|
|
21
|
+
*
|
|
22
|
+
* ⚠ **같은 값을 다시 보낸 것도 안 묻는다.** 그리드는 한 칸만 고쳐도 행 전체를 보낸다 — 안 바뀐
|
|
23
|
+
* 칸까지 물으면 사람이 매번 날짜를 보게 되고, 그러면 아무도 안 읽는다.
|
|
24
|
+
*/
|
|
25
|
+
export function assignmentChangesIn(axis, patches, before) {
|
|
26
|
+
const fields = assignmentFieldsOf(axis);
|
|
27
|
+
if (!fields.length) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const changes = [];
|
|
31
|
+
for (const patch of patches) {
|
|
32
|
+
if (String(patch.cuFlag ?? '').toUpperCase() !== 'M') {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const was = before(String(patch.id)) ?? {};
|
|
36
|
+
for (const field of fields) {
|
|
37
|
+
if (!(field in patch)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const after = show(patch[field]);
|
|
41
|
+
const had = show(was[field]);
|
|
42
|
+
if (after !== had) {
|
|
43
|
+
changes.push({ rowName: String(patch.name ?? was.name ?? patch.id), field, before: had, after });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return changes;
|
|
48
|
+
}
|
|
49
|
+
function show(value) {
|
|
50
|
+
if (value === null || value === undefined || value === '') {
|
|
51
|
+
return '';
|
|
52
|
+
}
|
|
53
|
+
return Array.isArray(value) ? value.map(String).join(', ') : String(value);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=assignment-fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assignment-fields.js","sourceRoot":"","sources":["../../../client/pages/master/assignment-fields.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAsC;IAClE,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,EAAE;IAC5C,EAAE,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,gBAAgB,EAAE;IACxD,EAAE,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,UAAU,EAAE;CACnD,CAAA;AAED,uBAAuB;AACvB,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,iBAAiB,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;AACjF,CAAC;AASD;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,OAA2C,EAC3C,MAA2D;IAE3D,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAA;IAEvC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,OAAO,GAAuB,EAAE,CAAA;IAEtC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,GAAG,EAAE,CAAC;YACrD,SAAQ;QACV,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QAE1C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,EAAE,CAAC;gBACtB,SAAQ;YACV,CAAC;YAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAA;YAChC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAA;YAE5B,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;gBAClB,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAA;YAClG,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAS,IAAI,CAAC,KAAc;IAC1B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QAC1D,OAAO,EAAE,CAAA;IACX,CAAC;IAED,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC5E,CAAC","sourcesContent":["/**\n * **이력을 받는 칸 셋** — 화면이 저장 직전에 「언제부터」를 묻는 대상.\n *\n * 서버의 `ASSIGNMENT_FIELDS` 와 같은 목록이다. 화면이 서버를 import 하지 않으므로(그러면 빌드가\n * 옮겨 간다) 여기 한 벌을 둔다 — **갈라지면 안 묻고 보내는 칸이 생긴다.**\n */\nexport const ASSIGNMENT_FIELDS: { axis: string; field: string }[] = [\n { axis: 'equipment', field: 'homeLocation' },\n { axis: 'operationalLocation', field: 'workShiftNames' },\n { axis: 'operationalLocation', field: 'parentId' }\n]\n\n/** 이 축에서 이력을 받는 칸들. */\nexport function assignmentFieldsOf(axis: string): string[] {\n return ASSIGNMENT_FIELDS.filter(one => one.axis === axis).map(one => one.field)\n}\n\nexport interface AssignmentChange {\n rowName: string\n field: string\n before: string\n after: string\n}\n\n/**\n * 보내려는 변경 중 **이력을 받는 칸을 건드린 것**만 고른다.\n *\n * ⚠ **새로 만드는 행은 안 묻는다.** 그 행은 그 순간 생긴 것이고 「언제부터」는 그 순간이다.\n * 물으면 존재하기 전의 자리를 적을 수 있게 된다.\n *\n * ⚠ **같은 값을 다시 보낸 것도 안 묻는다.** 그리드는 한 칸만 고쳐도 행 전체를 보낸다 — 안 바뀐\n * 칸까지 물으면 사람이 매번 날짜를 보게 되고, 그러면 아무도 안 읽는다.\n */\nexport function assignmentChangesIn(\n axis: string,\n patches: readonly Record<string, unknown>[],\n before: (id: string) => Record<string, unknown> | undefined\n): AssignmentChange[] {\n const fields = assignmentFieldsOf(axis)\n\n if (!fields.length) {\n return []\n }\n\n const changes: AssignmentChange[] = []\n\n for (const patch of patches) {\n if (String(patch.cuFlag ?? '').toUpperCase() !== 'M') {\n continue\n }\n\n const was = before(String(patch.id)) ?? {}\n\n for (const field of fields) {\n if (!(field in patch)) {\n continue\n }\n\n const after = show(patch[field])\n const had = show(was[field])\n\n if (after !== had) {\n changes.push({ rowName: String(patch.name ?? was.name ?? patch.id), field, before: had, after })\n }\n }\n }\n\n return changes\n}\n\nfunction show(value: unknown): string {\n if (value === null || value === undefined || value === '') {\n return ''\n }\n\n return Array.isArray(value) ? value.map(String).join(', ') : String(value)\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"equipment.js","sourceRoot":"","sources":["../../../client/pages/master/equipment.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAc,MAAM,uBAAuB,CAAA;AAEvF;;;;;;;GAOG;AAEI,IAAM,kBAAkB,GAAxB,MAAM,kBAAmB,SAAQ,cAAc;IACpD,IAAc,IAAI;QAChB,OAAO;YACL,GAAG,EAAE,WAAW;YAChB,KAAK,EAAE,WAAW;YAClB,IAAI,EAAE,sBAAsB;YAC5B,KAAK,EAAE,eAAe;YACtB,QAAQ,EAAE,yBAAyB;YACnC,OAAO,EAAE;gBACP,GAAG,QAAQ;gBACX,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;gBACtE,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;gBACnE;oBACE,IAAI,EAAE,cAAc;oBACpB,KAAK,EAAE,eAAe;oBACtB,IAAI,EAAE,WAAW;oBACjB,SAAS,EAAE,EAAE,SAAS,EAAE,sBAAsB,EAAE;oBAChD,KAAK,EAAE,GAAG;iBACX;gBACD;;;;;;;;;;;;;;;mBAeG;gBACH,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC1F,GAAG,SAAS;aACb;SACF,CAAA;IACH,CAAC;CACF,CAAA;
|
|
1
|
+
{"version":3,"file":"equipment.js","sourceRoot":"","sources":["../../../client/pages/master/equipment.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAc,MAAM,uBAAuB,CAAA;AAEvF;;;;;;;GAOG;AAEI,IAAM,kBAAkB,GAAxB,MAAM,kBAAmB,SAAQ,cAAc;IACpD,IAAc,IAAI;QAChB,OAAO;YACL,GAAG,EAAE,WAAW;YAChB,IAAI,EAAE,WAAW;YACjB,KAAK,EAAE,WAAW;YAClB,IAAI,EAAE,sBAAsB;YAC5B,KAAK,EAAE,eAAe;YACtB,QAAQ,EAAE,yBAAyB;YACnC,OAAO,EAAE;gBACP,GAAG,QAAQ;gBACX,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;gBACtE,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;gBACnE;oBACE,IAAI,EAAE,cAAc;oBACpB,KAAK,EAAE,eAAe;oBACtB,IAAI,EAAE,WAAW;oBACjB,SAAS,EAAE,EAAE,SAAS,EAAE,sBAAsB,EAAE;oBAChD,KAAK,EAAE,GAAG;iBACX;gBACD;;;;;;;;;;;;;;;mBAeG;gBACH,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC1F,GAAG,SAAS;aACb;SACF,CAAA;IACH,CAAC;CACF,CAAA;AAzCY,kBAAkB;IAD9B,aAAa,CAAC,sBAAsB,CAAC;GACzB,kBAAkB,CAyC9B","sourcesContent":["import { customElement } from 'lit/decorators.js'\n\nimport { EFFECTIVE, IDENTITY, MasterPageBase, MasterSpec } from './master-page-base.js'\n\n/**\n * 실제로 돌아가는 설비 하나하나. 실적과 정지 기록이 이것에 붙는다.\n *\n * 근무 캘린더를 정해야 계획 조업 시간이 생기고, 그것이 없으면 가동률이 「모름」이 된다.\n *\n * 화면 골격과 저장은 MasterPageBase 가 갖는다. 이 파일은 **이 화면이 무엇을 보이나**만 말한다 —\n * 자기 인터페이스가 필요해지면 여기서 render 를 덮어쓴다.\n */\n@customElement('ops-master-equipment')\nexport class OpsMasterEquipment extends MasterPageBase {\n protected get spec(): MasterSpec {\n return {\n key: 'equipment',\n axis: 'equipment',\n title: 'equipment',\n help: 'ops-master/equipment',\n query: 'equipmentList',\n mutation: 'updateMultipleEquipment',\n columns: [\n ...IDENTITY,\n { name: 'kind', label: 'equipment-class', type: 'string', width: 200 },\n { name: 'identity', label: 'identity', type: 'string', width: 200 },\n {\n name: 'homeLocation',\n label: 'home-location',\n type: 'reference',\n reference: { queryName: 'operationalLocations' },\n width: 140\n },\n /*\n * ── 손으로 칠 수 있는 값이 아니다 ─────────────────────────────────\n *\n * `type: 'json'` 은 글자를 그대로 받아 `JSON.parse` 하고, 실패하면 **오류 없이 `null`**\n * 로 둔다(`data-grid-body.js`). 그래서 이 칸을 고치려면 사람이 이런 것을 손으로 쳐야\n * 했고, 오타 하나에 그 설비의 계획 가동 시간이 조용히 사라졌다.\n *\n * [{\"id\":\"A조\",\"fromDayOffset\":0,\"fromTime\":\"06:00\",\"toDayOffset\":0,\"toTime\":\"14:00\"}]\n *\n * 그리고 **정상은 이 칸이 비는 것**이다. 근무조는 자리가 선언하고 설비가 물려받는다\n * (`OperationalLocation.workShiftNames`). 이 칸은 「이 설비만 따로 이렇게 가동한다」는\n * 예외를 적는 자리다 — 예외가 없는 설비에 값이 있으면 화면이 거짓말을 한다.\n *\n * 읽기 전용으로 둔다. 값이 있으면 보이고, 고치는 길은 예외를 제대로 세울 때 만든다.\n * 지금 상태로 열어 두는 것은 고칠 수 있는 척하면서 자료를 잃는 것이다.\n */\n { name: 'workCalendar', label: 'work-calendar', type: 'json', width: 200, readonly: true },\n ...EFFECTIVE\n ]\n }\n }\n}\n"]}
|
|
@@ -3,6 +3,9 @@ import '@operato/data-grist/ox-grist.js';
|
|
|
3
3
|
import { DataGrist, FetchOption } from '@operato/data-grist';
|
|
4
4
|
import type { ReferenceOptions } from './grist-reference.js';
|
|
5
5
|
import { PageView } from '@operato/shell';
|
|
6
|
+
import { AssignmentChange } from './assignment-fields.js';
|
|
7
|
+
import { AppliedAnswer } from './applied-when-dialog.js';
|
|
8
|
+
import './applied-when-dialog.js';
|
|
6
9
|
/**
|
|
7
10
|
* 마스터 화면이 함께 쓰는 것 — **코드 재사용은 여기서, 화면은 각자.**
|
|
8
11
|
*
|
|
@@ -94,6 +97,13 @@ export interface MasterColumn {
|
|
|
94
97
|
export interface MasterSpec {
|
|
95
98
|
/** 컬럼 설정을 사람마다 따로 저장하는 키. 화면마다 달라야 한다. */
|
|
96
99
|
key: string;
|
|
100
|
+
/**
|
|
101
|
+
* 이 화면이 다루는 마스터 축.
|
|
102
|
+
*
|
|
103
|
+
* **배정·선언 칸이 있는 축만 「언제부터」를 묻는다**(§`assignment-fields`). 안 적으면 안 묻는다 —
|
|
104
|
+
* 그 축에는 이력을 받는 칸이 없다는 뜻이다.
|
|
105
|
+
*/
|
|
106
|
+
axis?: string;
|
|
97
107
|
/** 제목 문구 키. `title.<이것>`. */
|
|
98
108
|
title: string;
|
|
99
109
|
/**
|
|
@@ -352,6 +362,10 @@ export declare abstract class MasterPageBase extends MasterPageBase_base {
|
|
|
352
362
|
private headerIndex;
|
|
353
363
|
/** 이 이름들이 이미 있나 — 있으면 그 `id`. 없는 이름은 지도에 없다. */
|
|
354
364
|
private idsByName;
|
|
365
|
+
/** 마지막으로 읽은 줄들. 「바뀌기 전 → 바뀐 뒤」를 보여 주는 데 쓴다. */
|
|
366
|
+
protected lastRead?: Map<string, any>;
|
|
367
|
+
/** 「언제부터」를 묻는다. 사람이 그만두면 `undefined` 이고, 그때는 **아무것도 안 보낸다.** */
|
|
368
|
+
protected askAppliedWhen(changes: AssignmentChange[]): Promise<AppliedAnswer | undefined>;
|
|
355
369
|
protected save(): Promise<void>;
|
|
356
370
|
}
|
|
357
371
|
export {};
|
|
@@ -7,12 +7,14 @@ import { property, query, state } from 'lit/decorators.js';
|
|
|
7
7
|
import { DataGrist, getEditor } from '@operato/data-grist';
|
|
8
8
|
import { client } from '@operato/graphql';
|
|
9
9
|
import { i18next, localize } from '@operato/i18n';
|
|
10
|
-
import { notify } from '@operato/layout';
|
|
10
|
+
import { notify, openPopup } from '@operato/layout';
|
|
11
11
|
import { p13n } from '@operato/p13n';
|
|
12
12
|
import { PageView } from '@operato/shell';
|
|
13
13
|
import { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles';
|
|
14
14
|
import { isMobileDevice } from '@operato/utils';
|
|
15
15
|
import { patchesFor, readImportRows } from './import-rows.js';
|
|
16
|
+
import { assignmentChangesIn } from './assignment-fields.js';
|
|
17
|
+
import './applied-when-dialog.js';
|
|
16
18
|
/** 열두 축이 공통으로 갖는 유효 기간 — ISA-95. */
|
|
17
19
|
export const EFFECTIVE = [
|
|
18
20
|
{ name: 'effectiveStart', label: 'effective-start', type: 'date', width: 150 },
|
|
@@ -380,6 +382,8 @@ export class MasterPageBase extends p13n(localize(i18next)(PageView)) {
|
|
|
380
382
|
});
|
|
381
383
|
const payload = response.data?.[this.spec.query];
|
|
382
384
|
const records = (payload?.items ?? []).map((item) => this.spreadJson(item));
|
|
385
|
+
/* 바뀌기 전 값을 보여 주려면 읽은 것을 들고 있어야 한다 — 그리드는 바뀐 것만 돌려준다. */
|
|
386
|
+
this.lastRead = new Map(records.map((one) => [String(one.id), one]));
|
|
383
387
|
return { total: payload?.total ?? 0, records };
|
|
384
388
|
}
|
|
385
389
|
/**
|
|
@@ -554,6 +558,22 @@ export class MasterPageBase extends p13n(localize(i18next)(PageView)) {
|
|
|
554
558
|
});
|
|
555
559
|
return new Map((response.data?.[this.spec.query]?.items ?? []).map((item) => [item.name, item.id]));
|
|
556
560
|
}
|
|
561
|
+
/** 「언제부터」를 묻는다. 사람이 그만두면 `undefined` 이고, 그때는 **아무것도 안 보낸다.** */
|
|
562
|
+
askAppliedWhen(changes) {
|
|
563
|
+
return new Promise(resolve => {
|
|
564
|
+
const popup = openPopup(html `<applied-when-dialog
|
|
565
|
+
.changes=${changes}
|
|
566
|
+
.onAnswer=${(answer) => {
|
|
567
|
+
popup?.close?.();
|
|
568
|
+
resolve(answer);
|
|
569
|
+
}}
|
|
570
|
+
.onCancel=${() => {
|
|
571
|
+
popup?.close?.();
|
|
572
|
+
resolve(undefined);
|
|
573
|
+
}}
|
|
574
|
+
></applied-when-dialog>`, { backdrop: true, size: 'medium', title: i18next.t('title.when-does-this-apply') });
|
|
575
|
+
});
|
|
576
|
+
}
|
|
557
577
|
async save() {
|
|
558
578
|
const patches = this.grist.dirtyRecords;
|
|
559
579
|
if (!patches?.length) {
|
|
@@ -566,6 +586,22 @@ export class MasterPageBase extends p13n(localize(i18next)(PageView)) {
|
|
|
566
586
|
notify({ message: packed.problems.join('\n'), level: 'error' });
|
|
567
587
|
return;
|
|
568
588
|
}
|
|
589
|
+
/*
|
|
590
|
+
* ── 배정과 선언은 「언제부터」를 묻고 보낸다 ─────────────────────────────
|
|
591
|
+
*
|
|
592
|
+
* 자리 배정 · 근무조 선언 · 부모 관계는 가동률 분모를 정한다. 그래서 값만 바꾸는 것이 아니라
|
|
593
|
+
* **언제부터 그렇게 볼 것인가**를 함께 정해야 하고, 과거를 적으면 지연 기록인지 정정인지까지
|
|
594
|
+
* 갈라야 한다(§`applied-when-dialog`).
|
|
595
|
+
*
|
|
596
|
+
* 안 물어도 저장은 된다 — 그때는 「지금부터」라는 주장이 된다. 다만 **묻지 않으면 사람이 그것을
|
|
597
|
+
* 주장한 줄 모른다.**
|
|
598
|
+
*/
|
|
599
|
+
const assignments = assignmentChangesIn(this.spec.axis ?? '', packed.patches, id => this.lastRead?.get(id));
|
|
600
|
+
const applied = assignments.length ? await this.askAppliedWhen(assignments) : undefined;
|
|
601
|
+
/* 그만두었으면 아무것도 안 보낸다 — 절반만 저장되면 화면과 저장된 것이 달라진다. */
|
|
602
|
+
if (assignments.length && !applied) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
569
605
|
const response = await client.mutate({
|
|
570
606
|
/*
|
|
571
607
|
* **타입 이름은 `Object` 다.**
|
|
@@ -576,14 +612,14 @@ export class MasterPageBase extends p13n(localize(i18next)(PageView)) {
|
|
|
576
612
|
* 않았다 — 이 앱의 클라이언트는 서버가 거절해도 던지지 않는다.
|
|
577
613
|
*/
|
|
578
614
|
mutation: gql `
|
|
579
|
-
mutation ($patches: [Object!]
|
|
580
|
-
${this.spec.mutation}(patches: $patches) {
|
|
615
|
+
mutation ($patches: [Object!]!, $applied: Object) {
|
|
616
|
+
${this.spec.mutation}(patches: $patches, applied: $applied) {
|
|
581
617
|
id
|
|
582
618
|
name
|
|
583
619
|
}
|
|
584
620
|
}
|
|
585
621
|
`,
|
|
586
|
-
variables: { patches: packed.patches }
|
|
622
|
+
variables: { patches: packed.patches, applied }
|
|
587
623
|
});
|
|
588
624
|
/*
|
|
589
625
|
* 이 클라이언트는 서버가 거절해도 던지지 않는다 — 오류는 `response.errors` 에 담겨 온다.
|