@things-factory/organization 10.1.87 → 10.1.104
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-server/ai-entry/entry-target-shape.d.ts +82 -0
- package/dist-server/ai-entry/entry-target-shape.js +3 -0
- package/dist-server/ai-entry/entry-target-shape.js.map +1 -0
- package/dist-server/ai-entry/organization-entry-rules.d.ts +56 -0
- package/dist-server/ai-entry/organization-entry-rules.js +130 -0
- package/dist-server/ai-entry/organization-entry-rules.js.map +1 -0
- package/dist-server/ai-entry/organization-entry-targets.d.ts +12 -0
- package/dist-server/ai-entry/organization-entry-targets.js +229 -0
- package/dist-server/ai-entry/organization-entry-targets.js.map +1 -0
- package/dist-server/index.d.ts +2 -0
- package/dist-server/index.js +1 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/tests/organization-entry-targets.test.ts +236 -0
- package/translations/en.json +14 -0
- package/translations/ja.json +14 -0
- package/translations/ko.json +14 -0
- package/translations/ms.json +14 -0
- package/translations/zh.json +14 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export type EntryFieldType = 'string' | 'text' | 'number' | 'integer' | 'date' | 'datetime' | 'boolean' | 'enum' | 'reference';
|
|
2
|
+
export interface EntryReference {
|
|
3
|
+
query: string;
|
|
4
|
+
variables(text: string): Record<string, unknown>;
|
|
5
|
+
recordsOf(data: any, text: string): {
|
|
6
|
+
id: string;
|
|
7
|
+
label?: string;
|
|
8
|
+
}[];
|
|
9
|
+
}
|
|
10
|
+
export interface EntryField {
|
|
11
|
+
name: string;
|
|
12
|
+
type: EntryFieldType;
|
|
13
|
+
required?: boolean;
|
|
14
|
+
values?: string[];
|
|
15
|
+
description?: string;
|
|
16
|
+
labelKey?: string;
|
|
17
|
+
total?: boolean;
|
|
18
|
+
valueKeyPrefix?: string;
|
|
19
|
+
reference?: EntryReference;
|
|
20
|
+
}
|
|
21
|
+
export interface EntryIssue {
|
|
22
|
+
field?: string;
|
|
23
|
+
code: string;
|
|
24
|
+
messageKey?: string;
|
|
25
|
+
params?: Record<string, unknown>;
|
|
26
|
+
blocking: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface EntryRow {
|
|
29
|
+
index: number;
|
|
30
|
+
values: Record<string, unknown>;
|
|
31
|
+
sources: unknown[];
|
|
32
|
+
issues: EntryIssue[];
|
|
33
|
+
}
|
|
34
|
+
export type EntryRecord = {
|
|
35
|
+
id: string;
|
|
36
|
+
} & Record<string, unknown>;
|
|
37
|
+
export interface EntryUpdate {
|
|
38
|
+
read: {
|
|
39
|
+
query: string;
|
|
40
|
+
variables(ids: string[]): Record<string, unknown>;
|
|
41
|
+
recordsOf(data: any, ids: string[]): EntryRecord[];
|
|
42
|
+
};
|
|
43
|
+
find?: {
|
|
44
|
+
query: string;
|
|
45
|
+
variables(criteria: Record<string, unknown>): Record<string, unknown>;
|
|
46
|
+
recordsOf(data: any, criteria: Record<string, unknown>): EntryRecord[];
|
|
47
|
+
criteria: EntryField[];
|
|
48
|
+
};
|
|
49
|
+
mutation: string;
|
|
50
|
+
variables(change: {
|
|
51
|
+
id: string;
|
|
52
|
+
record: EntryRecord;
|
|
53
|
+
values: Record<string, unknown>;
|
|
54
|
+
}): Record<string, unknown>;
|
|
55
|
+
labelOf?(record: EntryRecord): string;
|
|
56
|
+
fields?: string[];
|
|
57
|
+
/** Read-only checks of the changes, asked when the card is made and again when it is pressed - one issue list per row. */
|
|
58
|
+
check?(rows: {
|
|
59
|
+
id: string;
|
|
60
|
+
record?: EntryRecord;
|
|
61
|
+
values: Record<string, unknown>;
|
|
62
|
+
}[], context: {
|
|
63
|
+
state: any;
|
|
64
|
+
}): EntryIssue[][] | Promise<EntryIssue[][]>;
|
|
65
|
+
}
|
|
66
|
+
export interface EntryTargetDeclaration {
|
|
67
|
+
key: string;
|
|
68
|
+
labelKey: string;
|
|
69
|
+
description: string;
|
|
70
|
+
fields: EntryField[];
|
|
71
|
+
mutation: string;
|
|
72
|
+
variables(row: EntryRow): Record<string, unknown>;
|
|
73
|
+
appliedOf?(data: any, row: EntryRow): {
|
|
74
|
+
id?: string;
|
|
75
|
+
label?: string;
|
|
76
|
+
} | undefined;
|
|
77
|
+
maxRows?: number;
|
|
78
|
+
check?(rows: EntryRow[], context: {
|
|
79
|
+
state: any;
|
|
80
|
+
}): EntryIssue[][] | Promise<EntryIssue[][]>;
|
|
81
|
+
update?: EntryUpdate;
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-target-shape.js","sourceRoot":"","sources":["../../server/ai-entry/entry-target-shape.ts"],"names":[],"mappings":"","sourcesContent":["/*\n * The shape of an AI 입력 (data entry) target - the same shape as `EntryTarget` in\n * @things-factory/ai-assistant (server/service/data-entry/entry-target.ts).\n *\n * Kept here as a structural copy so that this package declares its targets as plain data and does not\n * depend on the assistant at run time. An application that runs the assistant passes these declarations\n * to `registerEntryTarget`; the assistant validates them at boot.\n */\nexport type EntryFieldType = 'string' | 'text' | 'number' | 'integer' | 'date' | 'datetime' | 'boolean' | 'enum' | 'reference'\n\nexport interface EntryReference {\n query: string\n variables(text: string): Record<string, unknown>\n recordsOf(data: any, text: string): { id: string; label?: string }[]\n}\n\nexport interface EntryField {\n name: string\n type: EntryFieldType\n required?: boolean\n values?: string[]\n description?: string\n labelKey?: string\n total?: boolean\n valueKeyPrefix?: string\n reference?: EntryReference\n}\n\nexport interface EntryIssue {\n field?: string\n code: string\n messageKey?: string\n params?: Record<string, unknown>\n blocking: boolean\n}\n\nexport interface EntryRow {\n index: number\n values: Record<string, unknown>\n sources: unknown[]\n issues: EntryIssue[]\n}\n\nexport type EntryRecord = { id: string } & Record<string, unknown>\n\nexport interface EntryUpdate {\n read: { query: string; variables(ids: string[]): Record<string, unknown>; recordsOf(data: any, ids: string[]): EntryRecord[] }\n find?: {\n query: string\n variables(criteria: Record<string, unknown>): Record<string, unknown>\n recordsOf(data: any, criteria: Record<string, unknown>): EntryRecord[]\n criteria: EntryField[]\n }\n mutation: string\n variables(change: { id: string; record: EntryRecord; values: Record<string, unknown> }): Record<string, unknown>\n labelOf?(record: EntryRecord): string\n fields?: string[]\n /** Read-only checks of the changes, asked when the card is made and again when it is pressed - one issue list per row. */\n check?(rows: { id: string; record?: EntryRecord; values: Record<string, unknown> }[], context: { state: any }): EntryIssue[][] | Promise<EntryIssue[][]>\n}\n\nexport interface EntryTargetDeclaration {\n key: string\n labelKey: string\n description: string\n fields: EntryField[]\n mutation: string\n variables(row: EntryRow): Record<string, unknown>\n appliedOf?(data: any, row: EntryRow): { id?: string; label?: string } | undefined\n maxRows?: number\n check?(rows: EntryRow[], context: { state: any }): EntryIssue[][] | Promise<EntryIssue[][]>\n update?: EntryUpdate\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { EntryIssue, EntryRecord } from './entry-target-shape';
|
|
2
|
+
type Row = {
|
|
3
|
+
values: Record<string, unknown>;
|
|
4
|
+
};
|
|
5
|
+
/** Only the fields given, and a reference as its id alone - `{ id }` is what the mutation's ObjectRef takes. */
|
|
6
|
+
export declare function inputOf(values: Record<string, unknown>, names: string[], references?: string[]): Record<string, unknown>;
|
|
7
|
+
/**
|
|
8
|
+
* A department row against what the domain already has and the other rows of the proposal.
|
|
9
|
+
*
|
|
10
|
+
* Name and 관리번호 are each unique in a domain (ix_department_0, ix_department_1) - one already there keeps the
|
|
11
|
+
* row out, and so does a row that repeats an earlier row of the same proposal (the later one is kept out).
|
|
12
|
+
* Rows are entered one by one in no order the framework promises, so a parent named by another row of the
|
|
13
|
+
* same proposal does not exist yet when this row runs: the row is kept out and the person enters the parent first.
|
|
14
|
+
*/
|
|
15
|
+
export declare function departmentIssues(rows: Row[], existing: {
|
|
16
|
+
name?: string | null;
|
|
17
|
+
controlNo?: string | null;
|
|
18
|
+
}[]): EntryIssue[][];
|
|
19
|
+
/** An employee row - the 사번 (controlNo) is unique in a domain among the employees not deleted (ix_employee_0). */
|
|
20
|
+
export declare function employeeIssues(rows: Row[], existing: {
|
|
21
|
+
controlNo?: string | null;
|
|
22
|
+
}[]): EntryIssue[][];
|
|
23
|
+
/** The departments a name or a 관리번호 names - exactly, never a piece of it. */
|
|
24
|
+
export declare function departmentsNamed(items: {
|
|
25
|
+
id: string;
|
|
26
|
+
name?: string | null;
|
|
27
|
+
controlNo?: string | null;
|
|
28
|
+
}[], said: string): {
|
|
29
|
+
id: string;
|
|
30
|
+
label: string;
|
|
31
|
+
}[];
|
|
32
|
+
/** The records whose every given criterion is a piece of the field - "영업" finds 영업1팀 and 영업2팀. */
|
|
33
|
+
export declare function matching(records: EntryRecord[], criteria: Record<string, unknown>): EntryRecord[];
|
|
34
|
+
export declare function codeAndName(record: Record<string, unknown>): string;
|
|
35
|
+
/** What the department's own parent rule (checkedParent) said of a changed parent - read only, before the change runs. */
|
|
36
|
+
export type ParentVerdict = 'loop' | 'not-found' | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* A change of a department against the other departments of the domain and the other changes of the card.
|
|
39
|
+
*
|
|
40
|
+
* A new name or 관리번호 another department already has (the record itself excepted) keeps the change out, and so
|
|
41
|
+
* does one an earlier change of the same card gives. A parent that is the department itself or one of its own
|
|
42
|
+
* sub-departments - or one the person cannot see - keeps it out too, as `updateDepartment` would refuse it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function departmentChangeIssues(rows: {
|
|
45
|
+
id: string;
|
|
46
|
+
values: Record<string, unknown>;
|
|
47
|
+
}[], existing: {
|
|
48
|
+
id: string;
|
|
49
|
+
name?: string | null;
|
|
50
|
+
controlNo?: string | null;
|
|
51
|
+
}[], parents?: ParentVerdict[]): EntryIssue[][];
|
|
52
|
+
/** The id of a changed parent - `{ id }` when the reference was resolved; anything else is not asked about. */
|
|
53
|
+
export declare function changedParentId(values: Record<string, unknown>): string | undefined;
|
|
54
|
+
/** checkedParent's refusal as a verdict - any other error (the database, say) is not a verdict and is passed on. */
|
|
55
|
+
export declare function parentVerdictOf(error: unknown): ParentVerdict;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inputOf = inputOf;
|
|
4
|
+
exports.departmentIssues = departmentIssues;
|
|
5
|
+
exports.employeeIssues = employeeIssues;
|
|
6
|
+
exports.departmentsNamed = departmentsNamed;
|
|
7
|
+
exports.matching = matching;
|
|
8
|
+
exports.codeAndName = codeAndName;
|
|
9
|
+
exports.departmentChangeIssues = departmentChangeIssues;
|
|
10
|
+
exports.changedParentId = changedParentId;
|
|
11
|
+
exports.parentVerdictOf = parentVerdictOf;
|
|
12
|
+
const text = (value) => (typeof value === 'string' || typeof value === 'number' ? String(value).trim() : '');
|
|
13
|
+
/** A reference the row still names in words - `{ text }` - because the query found nothing (or several). */
|
|
14
|
+
const unresolvedText = (value) => (value && typeof value === 'object' && typeof value.text === 'string' ? value.text.trim() : '');
|
|
15
|
+
/** Only the fields given, and a reference as its id alone - `{ id }` is what the mutation's ObjectRef takes. */
|
|
16
|
+
function inputOf(values, names, references = []) {
|
|
17
|
+
const input = {};
|
|
18
|
+
for (const name of names) {
|
|
19
|
+
if (!(name in values) || values[name] === undefined)
|
|
20
|
+
continue;
|
|
21
|
+
const value = values[name];
|
|
22
|
+
if (references.includes(name))
|
|
23
|
+
input[name] = value && typeof value === 'object' && typeof value.id === 'string' ? { id: value.id } : null;
|
|
24
|
+
else
|
|
25
|
+
input[name] = value;
|
|
26
|
+
}
|
|
27
|
+
return input;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A department row against what the domain already has and the other rows of the proposal.
|
|
31
|
+
*
|
|
32
|
+
* Name and 관리번호 are each unique in a domain (ix_department_0, ix_department_1) - one already there keeps the
|
|
33
|
+
* row out, and so does a row that repeats an earlier row of the same proposal (the later one is kept out).
|
|
34
|
+
* Rows are entered one by one in no order the framework promises, so a parent named by another row of the
|
|
35
|
+
* same proposal does not exist yet when this row runs: the row is kept out and the person enters the parent first.
|
|
36
|
+
*/
|
|
37
|
+
function departmentIssues(rows, existing) {
|
|
38
|
+
const names = new Set(existing.map(one => text(one.name)).filter(Boolean));
|
|
39
|
+
const codes = new Set(existing.map(one => text(one.controlNo)).filter(Boolean));
|
|
40
|
+
return rows.map((row, i) => {
|
|
41
|
+
const issues = [];
|
|
42
|
+
const name = text(row.values.name);
|
|
43
|
+
const controlNo = text(row.values.controlNo);
|
|
44
|
+
if (name && names.has(name))
|
|
45
|
+
issues.push({ field: 'name', code: 'duplicate', messageKey: 'text.organization-entry-department-name-exists', blocking: true });
|
|
46
|
+
if (controlNo && codes.has(controlNo))
|
|
47
|
+
issues.push({ field: 'controlNo', code: 'duplicate', messageKey: 'text.organization-entry-department-control-no-exists', blocking: true });
|
|
48
|
+
const earlier = rows.slice(0, i);
|
|
49
|
+
if ((name && earlier.some(other => text(other.values.name) === name)) ||
|
|
50
|
+
(controlNo && earlier.some(other => text(other.values.controlNo) === controlNo)))
|
|
51
|
+
issues.push({ code: 'duplicate-in-proposal', messageKey: 'text.organization-entry-department-repeated', blocking: true });
|
|
52
|
+
const parent = unresolvedText(row.values.parent);
|
|
53
|
+
if (parent && rows.some((other, j) => j !== i && (text(other.values.name) === parent || text(other.values.controlNo) === parent)))
|
|
54
|
+
issues.push({ field: 'parent', code: 'parent-in-proposal', messageKey: 'text.organization-entry-department-parent-first', params: { parent }, blocking: true });
|
|
55
|
+
return issues;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** An employee row - the 사번 (controlNo) is unique in a domain among the employees not deleted (ix_employee_0). */
|
|
59
|
+
function employeeIssues(rows, existing) {
|
|
60
|
+
const codes = new Set(existing.map(one => text(one.controlNo)).filter(Boolean));
|
|
61
|
+
return rows.map((row, i) => {
|
|
62
|
+
const controlNo = text(row.values.controlNo);
|
|
63
|
+
if (!controlNo)
|
|
64
|
+
return [];
|
|
65
|
+
if (codes.has(controlNo))
|
|
66
|
+
return [{ field: 'controlNo', code: 'duplicate', messageKey: 'text.organization-entry-employee-exists', blocking: true }];
|
|
67
|
+
if (rows.slice(0, i).some(other => text(other.values.controlNo) === controlNo))
|
|
68
|
+
return [{ field: 'controlNo', code: 'duplicate-in-proposal', messageKey: 'text.organization-entry-employee-repeated', blocking: true }];
|
|
69
|
+
return [];
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** The departments a name or a 관리번호 names - exactly, never a piece of it. */
|
|
73
|
+
function departmentsNamed(items, said) {
|
|
74
|
+
const wanted = said.trim().toLowerCase();
|
|
75
|
+
if (!wanted)
|
|
76
|
+
return [];
|
|
77
|
+
return items
|
|
78
|
+
.filter(one => text(one.name).toLowerCase() === wanted || text(one.controlNo).toLowerCase() === wanted)
|
|
79
|
+
.map(one => ({ id: one.id, label: codeAndName(one) }));
|
|
80
|
+
}
|
|
81
|
+
/** The records whose every given criterion is a piece of the field - "영업" finds 영업1팀 and 영업2팀. */
|
|
82
|
+
function matching(records, criteria) {
|
|
83
|
+
const given = Object.entries(criteria ?? {}).filter(([, value]) => text(value));
|
|
84
|
+
return records.filter(record => given.every(([name, value]) => text(record[name]).toLowerCase().includes(text(value).toLowerCase())));
|
|
85
|
+
}
|
|
86
|
+
function codeAndName(record) {
|
|
87
|
+
return [text(record.controlNo), text(record.name)].filter(Boolean).join(' · ');
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A change of a department against the other departments of the domain and the other changes of the card.
|
|
91
|
+
*
|
|
92
|
+
* A new name or 관리번호 another department already has (the record itself excepted) keeps the change out, and so
|
|
93
|
+
* does one an earlier change of the same card gives. A parent that is the department itself or one of its own
|
|
94
|
+
* sub-departments - or one the person cannot see - keeps it out too, as `updateDepartment` would refuse it.
|
|
95
|
+
*/
|
|
96
|
+
function departmentChangeIssues(rows, existing, parents = []) {
|
|
97
|
+
return rows.map((row, i) => {
|
|
98
|
+
const issues = [];
|
|
99
|
+
const name = 'name' in row.values ? text(row.values.name) : '';
|
|
100
|
+
const controlNo = 'controlNo' in row.values ? text(row.values.controlNo) : '';
|
|
101
|
+
const others = existing.filter(one => one.id !== row.id);
|
|
102
|
+
if (name && others.some(one => text(one.name) === name))
|
|
103
|
+
issues.push({ field: 'name', code: 'duplicate', messageKey: 'text.organization-entry-department-name-exists', blocking: true });
|
|
104
|
+
if (controlNo && others.some(one => text(one.controlNo) === controlNo))
|
|
105
|
+
issues.push({ field: 'controlNo', code: 'duplicate', messageKey: 'text.organization-entry-department-control-no-exists', blocking: true });
|
|
106
|
+
const earlier = rows.slice(0, i).filter(other => other.id !== row.id);
|
|
107
|
+
if ((name && earlier.some(other => text(other.values.name) === name)) || (controlNo && earlier.some(other => text(other.values.controlNo) === controlNo)))
|
|
108
|
+
issues.push({ code: 'duplicate-in-proposal', messageKey: 'text.organization-entry-department-repeated', blocking: true });
|
|
109
|
+
if (parents[i] === 'loop')
|
|
110
|
+
issues.push({ field: 'parent', code: 'parent-loop', messageKey: 'text.organization-entry-department-parent-loop', blocking: true });
|
|
111
|
+
if (parents[i] === 'not-found')
|
|
112
|
+
issues.push({ field: 'parent', code: 'parent-not-found', messageKey: 'text.organization-entry-department-parent-not-found', blocking: true });
|
|
113
|
+
return issues;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/** The id of a changed parent - `{ id }` when the reference was resolved; anything else is not asked about. */
|
|
117
|
+
function changedParentId(values) {
|
|
118
|
+
const parent = values.parent;
|
|
119
|
+
return parent && typeof parent === 'object' && typeof parent.id === 'string' ? parent.id : undefined;
|
|
120
|
+
}
|
|
121
|
+
/** checkedParent's refusal as a verdict - any other error (the database, say) is not a verdict and is passed on. */
|
|
122
|
+
function parentVerdictOf(error) {
|
|
123
|
+
const message = String(error?.message ?? '');
|
|
124
|
+
if (/cannot be placed under itself/.test(message))
|
|
125
|
+
return 'loop';
|
|
126
|
+
if (/parent department not found/.test(message))
|
|
127
|
+
return 'not-found';
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=organization-entry-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization-entry-rules.js","sourceRoot":"","sources":["../../server/ai-entry/organization-entry-rules.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The rules a proposed department or employee row passes - pure, so they are tested without a server.\n * The targets (organization-entry-targets.ts) read what is already entered and hand it here.\n */\nimport type { EntryIssue, EntryRecord } from './entry-target-shape'\n\ntype Row = { values: Record<string, unknown> }\n\nconst text = (value: unknown) => (typeof value === 'string' || typeof value === 'number' ? String(value).trim() : '')\n\n/** A reference the row still names in words - `{ text }` - because the query found nothing (or several). */\nconst unresolvedText = (value: unknown) => (value && typeof value === 'object' && typeof (value as any).text === 'string' ? (value as any).text.trim() : '')\n\n/** Only the fields given, and a reference as its id alone - `{ id }` is what the mutation's ObjectRef takes. */\nexport function inputOf(values: Record<string, unknown>, names: string[], references: string[] = []): Record<string, unknown> {\n const input: Record<string, unknown> = {}\n for (const name of names) {\n if (!(name in values) || values[name] === undefined) continue\n const value = values[name]\n if (references.includes(name)) input[name] = value && typeof value === 'object' && typeof (value as any).id === 'string' ? { id: (value as any).id } : null\n else input[name] = value\n }\n return input\n}\n\n/**\n * A department row against what the domain already has and the other rows of the proposal.\n *\n * Name and 관리번호 are each unique in a domain (ix_department_0, ix_department_1) - one already there keeps the\n * row out, and so does a row that repeats an earlier row of the same proposal (the later one is kept out).\n * Rows are entered one by one in no order the framework promises, so a parent named by another row of the\n * same proposal does not exist yet when this row runs: the row is kept out and the person enters the parent first.\n */\nexport function departmentIssues(rows: Row[], existing: { name?: string | null; controlNo?: string | null }[]): EntryIssue[][] {\n const names = new Set(existing.map(one => text(one.name)).filter(Boolean))\n const codes = new Set(existing.map(one => text(one.controlNo)).filter(Boolean))\n\n return rows.map((row, i) => {\n const issues: EntryIssue[] = []\n const name = text(row.values.name)\n const controlNo = text(row.values.controlNo)\n\n if (name && names.has(name)) issues.push({ field: 'name', code: 'duplicate', messageKey: 'text.organization-entry-department-name-exists', blocking: true })\n if (controlNo && codes.has(controlNo))\n issues.push({ field: 'controlNo', code: 'duplicate', messageKey: 'text.organization-entry-department-control-no-exists', blocking: true })\n\n const earlier = rows.slice(0, i)\n if (\n (name && earlier.some(other => text(other.values.name) === name)) ||\n (controlNo && earlier.some(other => text(other.values.controlNo) === controlNo))\n )\n issues.push({ code: 'duplicate-in-proposal', messageKey: 'text.organization-entry-department-repeated', blocking: true })\n\n const parent = unresolvedText(row.values.parent)\n if (parent && rows.some((other, j) => j !== i && (text(other.values.name) === parent || text(other.values.controlNo) === parent)))\n issues.push({ field: 'parent', code: 'parent-in-proposal', messageKey: 'text.organization-entry-department-parent-first', params: { parent }, blocking: true })\n\n return issues\n })\n}\n\n/** An employee row - the 사번 (controlNo) is unique in a domain among the employees not deleted (ix_employee_0). */\nexport function employeeIssues(rows: Row[], existing: { controlNo?: string | null }[]): EntryIssue[][] {\n const codes = new Set(existing.map(one => text(one.controlNo)).filter(Boolean))\n return rows.map((row, i) => {\n const controlNo = text(row.values.controlNo)\n if (!controlNo) return []\n if (codes.has(controlNo)) return [{ field: 'controlNo', code: 'duplicate', messageKey: 'text.organization-entry-employee-exists', blocking: true }]\n if (rows.slice(0, i).some(other => text(other.values.controlNo) === controlNo))\n return [{ field: 'controlNo', code: 'duplicate-in-proposal', messageKey: 'text.organization-entry-employee-repeated', blocking: true }]\n return []\n })\n}\n\n/** The departments a name or a 관리번호 names - exactly, never a piece of it. */\nexport function departmentsNamed(items: { id: string; name?: string | null; controlNo?: string | null }[], said: string): { id: string; label: string }[] {\n const wanted = said.trim().toLowerCase()\n if (!wanted) return []\n return items\n .filter(one => text(one.name).toLowerCase() === wanted || text(one.controlNo).toLowerCase() === wanted)\n .map(one => ({ id: one.id, label: codeAndName(one) }))\n}\n\n/** The records whose every given criterion is a piece of the field - \"영업\" finds 영업1팀 and 영업2팀. */\nexport function matching(records: EntryRecord[], criteria: Record<string, unknown>): EntryRecord[] {\n const given = Object.entries(criteria ?? {}).filter(([, value]) => text(value))\n return records.filter(record => given.every(([name, value]) => text(record[name]).toLowerCase().includes(text(value).toLowerCase())))\n}\n\nexport function codeAndName(record: Record<string, unknown>): string {\n return [text(record.controlNo), text(record.name)].filter(Boolean).join(' · ')\n}\n\n/** What the department's own parent rule (checkedParent) said of a changed parent - read only, before the change runs. */\nexport type ParentVerdict = 'loop' | 'not-found' | undefined\n\n/**\n * A change of a department against the other departments of the domain and the other changes of the card.\n *\n * A new name or 관리번호 another department already has (the record itself excepted) keeps the change out, and so\n * does one an earlier change of the same card gives. A parent that is the department itself or one of its own\n * sub-departments - or one the person cannot see - keeps it out too, as `updateDepartment` would refuse it.\n */\nexport function departmentChangeIssues(\n rows: { id: string; values: Record<string, unknown> }[],\n existing: { id: string; name?: string | null; controlNo?: string | null }[],\n parents: ParentVerdict[] = []\n): EntryIssue[][] {\n return rows.map((row, i) => {\n const issues: EntryIssue[] = []\n const name = 'name' in row.values ? text(row.values.name) : ''\n const controlNo = 'controlNo' in row.values ? text(row.values.controlNo) : ''\n const others = existing.filter(one => one.id !== row.id)\n\n if (name && others.some(one => text(one.name) === name))\n issues.push({ field: 'name', code: 'duplicate', messageKey: 'text.organization-entry-department-name-exists', blocking: true })\n if (controlNo && others.some(one => text(one.controlNo) === controlNo))\n issues.push({ field: 'controlNo', code: 'duplicate', messageKey: 'text.organization-entry-department-control-no-exists', blocking: true })\n\n const earlier = rows.slice(0, i).filter(other => other.id !== row.id)\n if ((name && earlier.some(other => text(other.values.name) === name)) || (controlNo && earlier.some(other => text(other.values.controlNo) === controlNo)))\n issues.push({ code: 'duplicate-in-proposal', messageKey: 'text.organization-entry-department-repeated', blocking: true })\n\n if (parents[i] === 'loop') issues.push({ field: 'parent', code: 'parent-loop', messageKey: 'text.organization-entry-department-parent-loop', blocking: true })\n if (parents[i] === 'not-found') issues.push({ field: 'parent', code: 'parent-not-found', messageKey: 'text.organization-entry-department-parent-not-found', blocking: true })\n\n return issues\n })\n}\n\n/** The id of a changed parent - `{ id }` when the reference was resolved; anything else is not asked about. */\nexport function changedParentId(values: Record<string, unknown>): string | undefined {\n const parent = values.parent\n return parent && typeof parent === 'object' && typeof (parent as any).id === 'string' ? (parent as any).id : undefined\n}\n\n/** checkedParent's refusal as a verdict - any other error (the database, say) is not a verdict and is passed on. */\nexport function parentVerdictOf(error: unknown): ParentVerdict {\n const message = String((error as any)?.message ?? '')\n if (/cannot be placed under itself/.test(message)) return 'loop'\n if (/parent department not found/.test(message)) return 'not-found'\n return undefined\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EntryTargetDeclaration } from './entry-target-shape';
|
|
2
|
+
export declare const ORGANIZATION_DEPARTMENT = "organization.department";
|
|
3
|
+
export declare const ORGANIZATION_EMPLOYEE = "organization.employee";
|
|
4
|
+
export declare function departmentEntryTarget(): EntryTargetDeclaration;
|
|
5
|
+
export declare const EMPLOYEE_FORBIDDEN_FIELDS: string[];
|
|
6
|
+
export declare function employeeEntryTarget(): EntryTargetDeclaration;
|
|
7
|
+
/**
|
|
8
|
+
* The organization's AI 입력 targets an application may register as a whole - the department. The employee is
|
|
9
|
+
* left out on purpose: it is taken only by name, `employeeEntryTarget()`, after a product decision (see the note
|
|
10
|
+
* above `EMPLOYEE_FORBIDDEN_FIELDS`), so registering this list never brings it along.
|
|
11
|
+
*/
|
|
12
|
+
export declare function organizationEntryTargets(): EntryTargetDeclaration[];
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.EMPLOYEE_FORBIDDEN_FIELDS = exports.ORGANIZATION_EMPLOYEE = exports.ORGANIZATION_DEPARTMENT = void 0;
|
|
37
|
+
exports.departmentEntryTarget = departmentEntryTarget;
|
|
38
|
+
exports.employeeEntryTarget = employeeEntryTarget;
|
|
39
|
+
exports.organizationEntryTargets = organizationEntryTargets;
|
|
40
|
+
const organization_entry_rules_1 = require("./organization-entry-rules");
|
|
41
|
+
exports.ORGANIZATION_DEPARTMENT = 'organization.department';
|
|
42
|
+
exports.ORGANIZATION_EMPLOYEE = 'organization.employee';
|
|
43
|
+
/** Departments of the person's domain whose name or 관리번호 holds the text - `search` filters are OR'd by the list builder. */
|
|
44
|
+
const DEPARTMENTS_BY_TEXT = 'query ($filters: [Filter!], $pagination: Pagination) { departments(filters: $filters, pagination: $pagination) { items { id controlNo name } } }';
|
|
45
|
+
const DEPARTMENTS = 'query ($filters: [Filter!], $pagination: Pagination) { departments(filters: $filters, pagination: $pagination) { items { id controlNo name description active extension parent { id controlNo name } } } }';
|
|
46
|
+
/** A department the model names in words, found as the person through `departments` - the id is what the mutation takes. */
|
|
47
|
+
const departmentReference = {
|
|
48
|
+
query: DEPARTMENTS_BY_TEXT,
|
|
49
|
+
variables: (said) => ({
|
|
50
|
+
filters: [
|
|
51
|
+
{ name: 'name', operator: 'search', value: said },
|
|
52
|
+
{ name: 'controlNo', operator: 'search', value: said }
|
|
53
|
+
],
|
|
54
|
+
pagination: { page: 1, limit: 200 }
|
|
55
|
+
}),
|
|
56
|
+
recordsOf: (data, said) => (0, organization_entry_rules_1.departmentsNamed)(data?.departments?.items ?? [], said)
|
|
57
|
+
};
|
|
58
|
+
const DEPARTMENT_FIELDS = [
|
|
59
|
+
{ name: 'controlNo', type: 'string', required: true, labelKey: 'field.control-no', description: "the department's code (관리번호) as the material writes it" },
|
|
60
|
+
{ name: 'name', type: 'string', required: true, labelKey: 'field.name', description: 'the department name' },
|
|
61
|
+
{ name: 'description', type: 'string', labelKey: 'field.description' },
|
|
62
|
+
{ name: 'active', type: 'boolean', labelKey: 'field.active', description: 'false only when the material says the department is closed or not in use' },
|
|
63
|
+
{ name: 'extension', type: 'string', labelKey: 'field.extension', description: "the department's phone extension (내선 번호)" },
|
|
64
|
+
{
|
|
65
|
+
name: 'parent',
|
|
66
|
+
type: 'reference',
|
|
67
|
+
labelKey: 'field.parent-department',
|
|
68
|
+
description: 'the department it belongs under, by its name or 관리번호. Leave it out for a top-level department.',
|
|
69
|
+
reference: departmentReference
|
|
70
|
+
}
|
|
71
|
+
];
|
|
72
|
+
/* DepartmentPatch has no `extension` - the update changes only what the patch can carry. */
|
|
73
|
+
const DEPARTMENT_UPDATE_FIELDS = ['controlNo', 'name', 'description', 'active', 'parent'];
|
|
74
|
+
/*
|
|
75
|
+
* Never taken: `manager` (the mutation looks the employee up without the domain - a separate security fix is
|
|
76
|
+
* pending) and `picture` (a file upload).
|
|
77
|
+
*/
|
|
78
|
+
const DEPARTMENT_CREATE_FIELDS = DEPARTMENT_FIELDS.map(field => field.name);
|
|
79
|
+
function departmentEntryTarget() {
|
|
80
|
+
return {
|
|
81
|
+
key: exports.ORGANIZATION_DEPARTMENT,
|
|
82
|
+
labelKey: 'label.organization-entry-department',
|
|
83
|
+
description: 'one department of the organization: its code (관리번호), its name, a description, its phone extension, whether it is active, and the department it belongs under.',
|
|
84
|
+
mutation: 'mutation ($department: NewDepartment!) { createDepartment(department: $department) { id controlNo name } }',
|
|
85
|
+
fields: DEPARTMENT_FIELDS,
|
|
86
|
+
maxRows: 200,
|
|
87
|
+
variables: (row) => ({ department: (0, organization_entry_rules_1.inputOf)(row.values, DEPARTMENT_CREATE_FIELDS, ['parent']) }),
|
|
88
|
+
appliedOf: (data, row) => ({ id: data?.createDepartment?.id, label: (0, organization_entry_rules_1.codeAndName)(data?.createDepartment ?? row.values) }),
|
|
89
|
+
check: async (rows, { state }) => {
|
|
90
|
+
const domainId = state?.domain?.id;
|
|
91
|
+
const names = [...new Set(rows.map(row => row.values.name).filter((v) => typeof v === 'string' && !!v.trim()).map(v => v.trim()))];
|
|
92
|
+
const codes = [...new Set(rows.map(row => row.values.controlNo).filter((v) => typeof v === 'string' && !!v.trim()).map(v => v.trim()))];
|
|
93
|
+
if (!domainId || (!names.length && !codes.length))
|
|
94
|
+
return (0, organization_entry_rules_1.departmentIssues)(rows, []);
|
|
95
|
+
const [{ getRepository }, { Department }, { In }] = await Promise.all([Promise.resolve().then(() => __importStar(require('@things-factory/shell'))), Promise.resolve().then(() => __importStar(require('../service/department/department'))), Promise.resolve().then(() => __importStar(require('typeorm')))]);
|
|
96
|
+
const existing = await getRepository(Department).find({
|
|
97
|
+
where: [
|
|
98
|
+
...(names.length ? [{ domain: { id: domainId }, name: In(names) }] : []),
|
|
99
|
+
...(codes.length ? [{ domain: { id: domainId }, controlNo: In(codes) }] : [])
|
|
100
|
+
],
|
|
101
|
+
select: ['id', 'name', 'controlNo']
|
|
102
|
+
});
|
|
103
|
+
return (0, organization_entry_rules_1.departmentIssues)(rows, existing);
|
|
104
|
+
},
|
|
105
|
+
update: {
|
|
106
|
+
read: {
|
|
107
|
+
query: DEPARTMENTS,
|
|
108
|
+
variables: ids => ({ filters: [{ name: 'id', operator: 'in', value: ids }], pagination: { page: 1, limit: ids.length } }),
|
|
109
|
+
recordsOf: data => data?.departments?.items ?? []
|
|
110
|
+
},
|
|
111
|
+
find: {
|
|
112
|
+
query: DEPARTMENTS,
|
|
113
|
+
variables: criteria => ({
|
|
114
|
+
filters: ['name', 'controlNo'].filter(name => typeof criteria?.[name] === 'string' && criteria[name]).map(name => ({ name, operator: 'search', value: criteria[name] })),
|
|
115
|
+
pagination: { page: 1, limit: 200 }
|
|
116
|
+
}),
|
|
117
|
+
recordsOf: (data, criteria) => (0, organization_entry_rules_1.matching)(data?.departments?.items ?? [], criteria),
|
|
118
|
+
criteria: [
|
|
119
|
+
{ name: 'name', type: 'string', description: 'a piece of the department name' },
|
|
120
|
+
{ name: 'controlNo', type: 'string', description: "a piece of the department's 관리번호" }
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
mutation: 'mutation ($id: String!, $patch: DepartmentPatch!) { updateDepartment(id: $id, patch: $patch) { id } }',
|
|
124
|
+
variables: ({ id, values }) => ({ id, patch: (0, organization_entry_rules_1.inputOf)(values, DEPARTMENT_UPDATE_FIELDS, ['parent']) }),
|
|
125
|
+
labelOf: record => (0, organization_entry_rules_1.codeAndName)(record),
|
|
126
|
+
fields: DEPARTMENT_UPDATE_FIELDS,
|
|
127
|
+
/*
|
|
128
|
+
* A new name or 관리번호 another department has, and a parent the department's own rule refuses - asked through
|
|
129
|
+
* `checkedParent` itself, which only reads: it walks up from the parent within the domain and throws.
|
|
130
|
+
*/
|
|
131
|
+
check: async (rows, { state }) => {
|
|
132
|
+
const domain = state?.domain;
|
|
133
|
+
if (!domain?.id)
|
|
134
|
+
return (0, organization_entry_rules_1.departmentChangeIssues)(rows, []);
|
|
135
|
+
const given = (name) => [...new Set(rows.filter(row => name in row.values).map(row => row.values[name]).filter((v) => typeof v === 'string' && !!v.trim()).map(v => v.trim()))];
|
|
136
|
+
const [names, codes] = [given('name'), given('controlNo')];
|
|
137
|
+
const [{ getRepository }, { Department }, { checkedParent }, { In }] = await Promise.all([
|
|
138
|
+
Promise.resolve().then(() => __importStar(require('@things-factory/shell'))),
|
|
139
|
+
Promise.resolve().then(() => __importStar(require('../service/department/department'))),
|
|
140
|
+
Promise.resolve().then(() => __importStar(require('../service/department/department-parent'))),
|
|
141
|
+
Promise.resolve().then(() => __importStar(require('typeorm')))
|
|
142
|
+
]);
|
|
143
|
+
const repository = getRepository(Department);
|
|
144
|
+
const existing = names.length || codes.length
|
|
145
|
+
? await repository.find({
|
|
146
|
+
where: [
|
|
147
|
+
...(names.length ? [{ domain: { id: domain.id }, name: In(names) }] : []),
|
|
148
|
+
...(codes.length ? [{ domain: { id: domain.id }, controlNo: In(codes) }] : [])
|
|
149
|
+
],
|
|
150
|
+
select: ['id', 'name', 'controlNo']
|
|
151
|
+
})
|
|
152
|
+
: [];
|
|
153
|
+
const parents = [];
|
|
154
|
+
for (const row of rows) {
|
|
155
|
+
const parentId = (0, organization_entry_rules_1.changedParentId)(row.values);
|
|
156
|
+
if (!parentId) {
|
|
157
|
+
parents.push(undefined);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
await checkedParent(row.id, { parent: { id: parentId } }, repository, domain);
|
|
162
|
+
parents.push(undefined);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
const verdict = (0, organization_entry_rules_1.parentVerdictOf)(error);
|
|
166
|
+
if (!verdict)
|
|
167
|
+
throw error;
|
|
168
|
+
parents.push(verdict);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return (0, organization_entry_rules_1.departmentChangeIssues)(rows, existing, parents);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/*
|
|
177
|
+
* ── 직원 (employee) ──────────────────────────────────────────────────────────────────────────────────
|
|
178
|
+
*
|
|
179
|
+
* PERSONAL DATA. A row of this target is a person's name, position and hire day, and proposing it sends that
|
|
180
|
+
* material to an external model. It is declared here so the shape is settled, and **no application opens it**:
|
|
181
|
+
* a mode may name `organization.employee` only by an explicit product decision.
|
|
182
|
+
*
|
|
183
|
+
* Kept to the fields a roster needs. Never taken: user, bankAccount, bankName, emergencyContact,
|
|
184
|
+
* emergencyContactPhone, contact, photo, supervisor. No update this round.
|
|
185
|
+
*
|
|
186
|
+
* The department is passed as an id the assistant resolved through the domain-scoped `departments` query.
|
|
187
|
+
* createEmployee itself looks the department (and the supervisor) up by id without the domain - that is the
|
|
188
|
+
* server's to fix, separately.
|
|
189
|
+
*/
|
|
190
|
+
exports.EMPLOYEE_FORBIDDEN_FIELDS = ['user', 'bankAccount', 'bankName', 'emergencyContact', 'emergencyContactPhone', 'contact', 'photo', 'supervisor'];
|
|
191
|
+
const EMPLOYEE_FIELDS = [
|
|
192
|
+
{ name: 'controlNo', type: 'string', required: true, labelKey: 'field.control-no', description: 'the employee number (사번) as the material writes it' },
|
|
193
|
+
{ name: 'name', type: 'string', required: true, labelKey: 'field.name', description: "the person's name" },
|
|
194
|
+
{ name: 'jobPosition', type: 'string', labelKey: 'label.job-position', description: 'the rank (직급) - 사원, 대리, 과장...' },
|
|
195
|
+
{ name: 'jobResponsibility', type: 'string', labelKey: 'label.job-responsibility', description: 'the role (직책) - 팀장, 파트장...' },
|
|
196
|
+
{ name: 'type', type: 'enum', values: ['FULLTIME', 'PARTTIME', 'TEMPORARY'], labelKey: 'field.type', valueKeyPrefix: 'text.employee-type-' },
|
|
197
|
+
{ name: 'hiredOn', type: 'date', labelKey: 'field.hired-on', description: 'the day the person was hired' },
|
|
198
|
+
{ name: 'department', type: 'reference', labelKey: 'field.department', description: 'the department, by its name or 관리번호', reference: departmentReference }
|
|
199
|
+
];
|
|
200
|
+
function employeeEntryTarget() {
|
|
201
|
+
return {
|
|
202
|
+
key: exports.ORGANIZATION_EMPLOYEE,
|
|
203
|
+
labelKey: 'label.organization-entry-employee',
|
|
204
|
+
description: 'one employee: the employee number (사번), the name, the rank and the role, the kind of employment, the day hired, and the department.',
|
|
205
|
+
mutation: 'mutation ($employee: NewEmployee!) { createEmployee(employee: $employee) { id controlNo name } }',
|
|
206
|
+
fields: EMPLOYEE_FIELDS,
|
|
207
|
+
maxRows: 200,
|
|
208
|
+
variables: (row) => ({ employee: (0, organization_entry_rules_1.inputOf)(row.values, EMPLOYEE_FIELDS.map(field => field.name), ['department']) }),
|
|
209
|
+
appliedOf: (data, row) => ({ id: data?.createEmployee?.id, label: (0, organization_entry_rules_1.codeAndName)(data?.createEmployee ?? row.values) }),
|
|
210
|
+
check: async (rows, { state }) => {
|
|
211
|
+
const domainId = state?.domain?.id;
|
|
212
|
+
const codes = [...new Set(rows.map(row => row.values.controlNo).filter((v) => typeof v === 'string' && !!v.trim()).map(v => v.trim()))];
|
|
213
|
+
if (!domainId || !codes.length)
|
|
214
|
+
return (0, organization_entry_rules_1.employeeIssues)(rows, []);
|
|
215
|
+
const [{ getRepository }, { Employee }, { In }] = await Promise.all([Promise.resolve().then(() => __importStar(require('@things-factory/shell'))), Promise.resolve().then(() => __importStar(require('../service/employee/employee'))), Promise.resolve().then(() => __importStar(require('typeorm')))]);
|
|
216
|
+
const existing = await getRepository(Employee).find({ where: { domain: { id: domainId }, controlNo: In(codes) }, select: ['id', 'controlNo'] });
|
|
217
|
+
return (0, organization_entry_rules_1.employeeIssues)(rows, existing);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The organization's AI 입력 targets an application may register as a whole - the department. The employee is
|
|
223
|
+
* left out on purpose: it is taken only by name, `employeeEntryTarget()`, after a product decision (see the note
|
|
224
|
+
* above `EMPLOYEE_FORBIDDEN_FIELDS`), so registering this list never brings it along.
|
|
225
|
+
*/
|
|
226
|
+
function organizationEntryTargets() {
|
|
227
|
+
return [departmentEntryTarget()];
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=organization-entry-targets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization-entry-targets.js","sourceRoot":"","sources":["../../server/ai-entry/organization-entry-targets.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * AI 입력 targets of the organization - what an application may hand its assistant so that departments\n * (and, only by an explicit product decision, employees) are entered from material\n * (operato-application/design/ai/data-entry.md).\n *\n * Plain declarations: this package does not depend on @things-factory/ai-assistant. An application that runs\n * the assistant passes them to `registerEntryTarget` and names their keys in its modes. Every row is entered\n * through the organization's own mutation, run as the person - the same `@privilege` and checks as the screens.\n *\n * The checks read through the repository of the request's domain only, and import it when they run, so that\n * reading these declarations stays light.\n */\nimport type { EntryField, EntryRow, EntryTargetDeclaration } from './entry-target-shape'\nimport {\n changedParentId,\n codeAndName,\n departmentChangeIssues,\n departmentIssues,\n departmentsNamed,\n employeeIssues,\n inputOf,\n matching,\n parentVerdictOf,\n type ParentVerdict\n} from './organization-entry-rules'\n\nexport const ORGANIZATION_DEPARTMENT = 'organization.department'\nexport const ORGANIZATION_EMPLOYEE = 'organization.employee'\n\n/** Departments of the person's domain whose name or 관리번호 holds the text - `search` filters are OR'd by the list builder. */\nconst DEPARTMENTS_BY_TEXT =\n 'query ($filters: [Filter!], $pagination: Pagination) { departments(filters: $filters, pagination: $pagination) { items { id controlNo name } } }'\n\nconst DEPARTMENTS =\n 'query ($filters: [Filter!], $pagination: Pagination) { departments(filters: $filters, pagination: $pagination) { items { id controlNo name description active extension parent { id controlNo name } } } }'\n\n/** A department the model names in words, found as the person through `departments` - the id is what the mutation takes. */\nconst departmentReference = {\n query: DEPARTMENTS_BY_TEXT,\n variables: (said: string) => ({\n filters: [\n { name: 'name', operator: 'search', value: said },\n { name: 'controlNo', operator: 'search', value: said }\n ],\n pagination: { page: 1, limit: 200 }\n }),\n recordsOf: (data: any, said: string) => departmentsNamed(data?.departments?.items ?? [], said)\n}\n\nconst DEPARTMENT_FIELDS: EntryField[] = [\n { name: 'controlNo', type: 'string', required: true, labelKey: 'field.control-no', description: \"the department's code (관리번호) as the material writes it\" },\n { name: 'name', type: 'string', required: true, labelKey: 'field.name', description: 'the department name' },\n { name: 'description', type: 'string', labelKey: 'field.description' },\n { name: 'active', type: 'boolean', labelKey: 'field.active', description: 'false only when the material says the department is closed or not in use' },\n { name: 'extension', type: 'string', labelKey: 'field.extension', description: \"the department's phone extension (내선 번호)\" },\n {\n name: 'parent',\n type: 'reference',\n labelKey: 'field.parent-department',\n description: 'the department it belongs under, by its name or 관리번호. Leave it out for a top-level department.',\n reference: departmentReference\n }\n]\n\n/* DepartmentPatch has no `extension` - the update changes only what the patch can carry. */\nconst DEPARTMENT_UPDATE_FIELDS = ['controlNo', 'name', 'description', 'active', 'parent']\n\n/*\n * Never taken: `manager` (the mutation looks the employee up without the domain - a separate security fix is\n * pending) and `picture` (a file upload).\n */\nconst DEPARTMENT_CREATE_FIELDS = DEPARTMENT_FIELDS.map(field => field.name)\n\nexport function departmentEntryTarget(): EntryTargetDeclaration {\n return {\n key: ORGANIZATION_DEPARTMENT,\n labelKey: 'label.organization-entry-department',\n description:\n 'one department of the organization: its code (관리번호), its name, a description, its phone extension, whether it is active, and the department it belongs under.',\n mutation: 'mutation ($department: NewDepartment!) { createDepartment(department: $department) { id controlNo name } }',\n fields: DEPARTMENT_FIELDS,\n maxRows: 200,\n variables: (row: EntryRow) => ({ department: inputOf(row.values, DEPARTMENT_CREATE_FIELDS, ['parent']) }),\n appliedOf: (data, row) => ({ id: data?.createDepartment?.id, label: codeAndName(data?.createDepartment ?? row.values) }),\n\n check: async (rows, { state }) => {\n const domainId = state?.domain?.id\n const names = [...new Set(rows.map(row => row.values.name).filter((v): v is string => typeof v === 'string' && !!v.trim()).map(v => v.trim()))]\n const codes = [...new Set(rows.map(row => row.values.controlNo).filter((v): v is string => typeof v === 'string' && !!v.trim()).map(v => v.trim()))]\n if (!domainId || (!names.length && !codes.length)) return departmentIssues(rows, [])\n const [{ getRepository }, { Department }, { In }] = await Promise.all([import('@things-factory/shell'), import('../service/department/department'), import('typeorm')])\n const existing = await getRepository(Department).find({\n where: [\n ...(names.length ? [{ domain: { id: domainId }, name: In(names) }] : []),\n ...(codes.length ? [{ domain: { id: domainId }, controlNo: In(codes) }] : [])\n ] as any,\n select: ['id', 'name', 'controlNo'] as any\n })\n return departmentIssues(rows, existing)\n },\n\n update: {\n read: {\n query: DEPARTMENTS,\n variables: ids => ({ filters: [{ name: 'id', operator: 'in', value: ids }], pagination: { page: 1, limit: ids.length } }),\n recordsOf: data => data?.departments?.items ?? []\n },\n find: {\n query: DEPARTMENTS,\n variables: criteria => ({\n filters: ['name', 'controlNo'].filter(name => typeof criteria?.[name] === 'string' && criteria[name]).map(name => ({ name, operator: 'search', value: criteria[name] })),\n pagination: { page: 1, limit: 200 }\n }),\n recordsOf: (data, criteria) => matching(data?.departments?.items ?? [], criteria),\n criteria: [\n { name: 'name', type: 'string', description: 'a piece of the department name' },\n { name: 'controlNo', type: 'string', description: \"a piece of the department's 관리번호\" }\n ]\n },\n mutation: 'mutation ($id: String!, $patch: DepartmentPatch!) { updateDepartment(id: $id, patch: $patch) { id } }',\n variables: ({ id, values }) => ({ id, patch: inputOf(values, DEPARTMENT_UPDATE_FIELDS, ['parent']) }),\n labelOf: record => codeAndName(record),\n fields: DEPARTMENT_UPDATE_FIELDS,\n\n /*\n * A new name or 관리번호 another department has, and a parent the department's own rule refuses - asked through\n * `checkedParent` itself, which only reads: it walks up from the parent within the domain and throws.\n */\n check: async (rows, { state }) => {\n const domain = state?.domain\n if (!domain?.id) return departmentChangeIssues(rows, [])\n const given = (name: string) => [...new Set(rows.filter(row => name in row.values).map(row => row.values[name]).filter((v): v is string => typeof v === 'string' && !!v.trim()).map(v => v.trim()))]\n const [names, codes] = [given('name'), given('controlNo')]\n const [{ getRepository }, { Department }, { checkedParent }, { In }] = await Promise.all([\n import('@things-factory/shell'),\n import('../service/department/department'),\n import('../service/department/department-parent'),\n import('typeorm')\n ])\n const repository = getRepository(Department)\n const existing =\n names.length || codes.length\n ? await repository.find({\n where: [\n ...(names.length ? [{ domain: { id: domain.id }, name: In(names) }] : []),\n ...(codes.length ? [{ domain: { id: domain.id }, controlNo: In(codes) }] : [])\n ] as any,\n select: ['id', 'name', 'controlNo'] as any\n })\n : []\n const parents: ParentVerdict[] = []\n for (const row of rows) {\n const parentId = changedParentId(row.values)\n if (!parentId) {\n parents.push(undefined)\n continue\n }\n try {\n await checkedParent(row.id, { parent: { id: parentId } }, repository, domain)\n parents.push(undefined)\n } catch (error: any) {\n const verdict = parentVerdictOf(error)\n if (!verdict) throw error\n parents.push(verdict)\n }\n }\n return departmentChangeIssues(rows, existing as any, parents)\n }\n }\n }\n}\n\n/*\n * ── 직원 (employee) ──────────────────────────────────────────────────────────────────────────────────\n *\n * PERSONAL DATA. A row of this target is a person's name, position and hire day, and proposing it sends that\n * material to an external model. It is declared here so the shape is settled, and **no application opens it**:\n * a mode may name `organization.employee` only by an explicit product decision.\n *\n * Kept to the fields a roster needs. Never taken: user, bankAccount, bankName, emergencyContact,\n * emergencyContactPhone, contact, photo, supervisor. No update this round.\n *\n * The department is passed as an id the assistant resolved through the domain-scoped `departments` query.\n * createEmployee itself looks the department (and the supervisor) up by id without the domain - that is the\n * server's to fix, separately.\n */\nexport const EMPLOYEE_FORBIDDEN_FIELDS = ['user', 'bankAccount', 'bankName', 'emergencyContact', 'emergencyContactPhone', 'contact', 'photo', 'supervisor']\n\nconst EMPLOYEE_FIELDS: EntryField[] = [\n { name: 'controlNo', type: 'string', required: true, labelKey: 'field.control-no', description: 'the employee number (사번) as the material writes it' },\n { name: 'name', type: 'string', required: true, labelKey: 'field.name', description: \"the person's name\" },\n { name: 'jobPosition', type: 'string', labelKey: 'label.job-position', description: 'the rank (직급) - 사원, 대리, 과장...' },\n { name: 'jobResponsibility', type: 'string', labelKey: 'label.job-responsibility', description: 'the role (직책) - 팀장, 파트장...' },\n { name: 'type', type: 'enum', values: ['FULLTIME', 'PARTTIME', 'TEMPORARY'], labelKey: 'field.type', valueKeyPrefix: 'text.employee-type-' },\n { name: 'hiredOn', type: 'date', labelKey: 'field.hired-on', description: 'the day the person was hired' },\n { name: 'department', type: 'reference', labelKey: 'field.department', description: 'the department, by its name or 관리번호', reference: departmentReference }\n]\n\nexport function employeeEntryTarget(): EntryTargetDeclaration {\n return {\n key: ORGANIZATION_EMPLOYEE,\n labelKey: 'label.organization-entry-employee',\n description: 'one employee: the employee number (사번), the name, the rank and the role, the kind of employment, the day hired, and the department.',\n mutation: 'mutation ($employee: NewEmployee!) { createEmployee(employee: $employee) { id controlNo name } }',\n fields: EMPLOYEE_FIELDS,\n maxRows: 200,\n variables: (row: EntryRow) => ({ employee: inputOf(row.values, EMPLOYEE_FIELDS.map(field => field.name), ['department']) }),\n appliedOf: (data, row) => ({ id: data?.createEmployee?.id, label: codeAndName(data?.createEmployee ?? row.values) }),\n check: async (rows, { state }) => {\n const domainId = state?.domain?.id\n const codes = [...new Set(rows.map(row => row.values.controlNo).filter((v): v is string => typeof v === 'string' && !!v.trim()).map(v => v.trim()))]\n if (!domainId || !codes.length) return employeeIssues(rows, [])\n const [{ getRepository }, { Employee }, { In }] = await Promise.all([import('@things-factory/shell'), import('../service/employee/employee'), import('typeorm')])\n const existing = await getRepository(Employee).find({ where: { domain: { id: domainId }, controlNo: In(codes) } as any, select: ['id', 'controlNo'] as any })\n return employeeIssues(rows, existing)\n }\n }\n}\n\n/**\n * The organization's AI 입력 targets an application may register as a whole - the department. The employee is\n * left out on purpose: it is taken only by name, `employeeEntryTarget()`, after a product decision (see the note\n * above `EMPLOYEE_FORBIDDEN_FIELDS`), so registering this list never brings it along.\n */\nexport function organizationEntryTargets(): EntryTargetDeclaration[] {\n return [departmentEntryTarget()]\n}\n"]}
|
package/dist-server/index.d.ts
CHANGED
package/dist-server/index.js
CHANGED
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
tslib_1.__exportStar(require("./service"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./ai-entry/organization-entry-targets"), exports);
|
|
5
6
|
require("./routes");
|
|
6
7
|
//# sourceMappingURL=index.js.map
|