@things-factory/organization 10.1.223 → 10.1.227

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 (69) hide show
  1. package/dist-client/pages/employee-profile/employee-profile-files.d.ts +23 -0
  2. package/dist-client/pages/employee-profile/employee-profile-files.js +50 -0
  3. package/dist-client/pages/employee-profile/employee-profile-files.js.map +1 -0
  4. package/dist-client/pages/employee-profile/employee-profile-form.d.ts +63 -0
  5. package/dist-client/pages/employee-profile/employee-profile-form.js +132 -0
  6. package/dist-client/pages/employee-profile/employee-profile-form.js.map +1 -0
  7. package/dist-client/pages/employee-profile/employee-profile-list-page.d.ts +52 -0
  8. package/dist-client/pages/employee-profile/employee-profile-list-page.js +206 -0
  9. package/dist-client/pages/employee-profile/employee-profile-list-page.js.map +1 -0
  10. package/dist-client/pages/employee-profile/employee-profile-preview.d.ts +28 -0
  11. package/dist-client/pages/employee-profile/employee-profile-preview.js +179 -0
  12. package/dist-client/pages/employee-profile/employee-profile-preview.js.map +1 -0
  13. package/dist-client/pages/employee-profile/my-employee-profile-page.d.ts +56 -0
  14. package/dist-client/pages/employee-profile/my-employee-profile-page.js +656 -0
  15. package/dist-client/pages/employee-profile/my-employee-profile-page.js.map +1 -0
  16. package/dist-client/route.d.ts +1 -1
  17. package/dist-client/route.js +7 -0
  18. package/dist-client/route.js.map +1 -1
  19. package/dist-client/tsconfig.tsbuildinfo +1 -1
  20. package/dist-server/ai-entry/employee-profile-entry.d.ts +10 -0
  21. package/dist-server/ai-entry/employee-profile-entry.js +88 -0
  22. package/dist-server/ai-entry/employee-profile-entry.js.map +1 -0
  23. package/dist-server/ai-entry/entry-target-shape.d.ts +11 -1
  24. package/dist-server/ai-entry/entry-target-shape.js.map +1 -1
  25. package/dist-server/ai-entry/organization-entry-targets.d.ts +8 -0
  26. package/dist-server/ai-entry/organization-entry-targets.js +26 -1
  27. package/dist-server/ai-entry/organization-entry-targets.js.map +1 -1
  28. package/dist-server/role-tasks.js +4 -1
  29. package/dist-server/role-tasks.js.map +1 -1
  30. package/dist-server/service/employee-profile/employee-profile-document.d.ts +122 -0
  31. package/dist-server/service/employee-profile/employee-profile-document.js +439 -0
  32. package/dist-server/service/employee-profile/employee-profile-document.js.map +1 -0
  33. package/dist-server/service/employee-profile/employee-profile-mutation.d.ts +6 -0
  34. package/dist-server/service/employee-profile/employee-profile-mutation.js +74 -0
  35. package/dist-server/service/employee-profile/employee-profile-mutation.js.map +1 -0
  36. package/dist-server/service/employee-profile/employee-profile-query.d.ts +14 -0
  37. package/dist-server/service/employee-profile/employee-profile-query.js +137 -0
  38. package/dist-server/service/employee-profile/employee-profile-query.js.map +1 -0
  39. package/dist-server/service/employee-profile/employee-profile-rows.d.ts +29 -0
  40. package/dist-server/service/employee-profile/employee-profile-rows.js +122 -0
  41. package/dist-server/service/employee-profile/employee-profile-rows.js.map +1 -0
  42. package/dist-server/service/employee-profile/employee-profile-rules.d.ts +61 -0
  43. package/dist-server/service/employee-profile/employee-profile-rules.js +104 -0
  44. package/dist-server/service/employee-profile/employee-profile-rules.js.map +1 -0
  45. package/dist-server/service/employee-profile/employee-profile-type.d.ts +35 -0
  46. package/dist-server/service/employee-profile/employee-profile-type.js +110 -0
  47. package/dist-server/service/employee-profile/employee-profile-type.js.map +1 -0
  48. package/dist-server/service/employee-profile/employee-profile.d.ts +24 -0
  49. package/dist-server/service/employee-profile/employee-profile.js +112 -0
  50. package/dist-server/service/employee-profile/employee-profile.js.map +1 -0
  51. package/dist-server/service/employee-profile/index.d.ts +5 -0
  52. package/dist-server/service/employee-profile/index.js +9 -0
  53. package/dist-server/service/employee-profile/index.js.map +1 -0
  54. package/dist-server/service/index.d.ts +3 -2
  55. package/dist-server/service/index.js +6 -2
  56. package/dist-server/service/index.js.map +1 -1
  57. package/dist-server/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +8 -6
  59. package/tests/employee-profile-db.test.ts +247 -0
  60. package/tests/employee-profile-document.test.ts +219 -0
  61. package/tests/employee-profile-entry.test.ts +72 -0
  62. package/tests/employee-profile-rules.test.ts +103 -0
  63. package/things-factory.config.js +3 -0
  64. package/translations/en.json +78 -1
  65. package/translations/es.json +78 -1
  66. package/translations/ja.json +78 -1
  67. package/translations/ko.json +78 -1
  68. package/translations/ms.json +78 -1
  69. package/translations/zh.json +78 -1
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EmployeeProfileMutation = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const type_graphql_1 = require("type-graphql");
6
+ const shell_1 = require("@things-factory/shell");
7
+ const my_employee_js_1 = require("../employee/my-employee.js");
8
+ const employee_profile_1 = require("./employee-profile");
9
+ const employee_profile_rules_1 = require("./employee-profile-rules");
10
+ const employee_profile_type_1 = require("./employee-profile-type");
11
+ /*
12
+ * The employee writes their own profile and nobody else's (ADR-0169 decisions 3 · 4) - no door changes another
13
+ * person's. It is saved as one document, as the screen holds it.
14
+ */
15
+ let EmployeeProfileMutation = class EmployeeProfileMutation {
16
+ async saveMyEmployeeProfile(profile, context) {
17
+ const { domain, user, tx } = context.state;
18
+ const employee = await (0, my_employee_js_1.requireMyEmployee)(context);
19
+ const repository = (0, shell_1.getRepository)(employee_profile_1.EmployeeProfile, tx);
20
+ const held = await repository.findOne({ where: { domain: { id: domain.id }, employee: { id: employee.id } } });
21
+ const saved = await repository.save({
22
+ ...(held ?? { domain, employee, creator: user }),
23
+ ...(0, employee_profile_rules_1.profileDocumentOf)(profile),
24
+ updater: user
25
+ });
26
+ return await repository.findOne({ where: { id: saved.id } });
27
+ }
28
+ /*
29
+ * What the assistant read from a résumé the person attached, added to their own profile once they press the card
30
+ * (ADR-0169 decision 10): nothing kept is taken away - a given summary takes the summary's place, skills and rows
31
+ * are added unless already there.
32
+ */
33
+ async addToMyEmployeeProfile(profile, context) {
34
+ const { domain, user, tx } = context.state;
35
+ const employee = await (0, my_employee_js_1.requireMyEmployee)(context);
36
+ const repository = (0, shell_1.getRepository)(employee_profile_1.EmployeeProfile, tx);
37
+ const held = await repository.findOne({ where: { domain: { id: domain.id }, employee: { id: employee.id } } });
38
+ const saved = await repository.save({
39
+ ...(held ?? { domain, employee, creator: user }),
40
+ ...(0, employee_profile_rules_1.addedProfileOf)(held, profile),
41
+ updater: user
42
+ });
43
+ return await repository.findOne({ where: { id: saved.id } });
44
+ }
45
+ };
46
+ exports.EmployeeProfileMutation = EmployeeProfileMutation;
47
+ tslib_1.__decorate([
48
+ (0, type_graphql_1.Directive)('@transaction'),
49
+ (0, type_graphql_1.Directive)('@privilege(category: "employee-self", privilege: "mutation", domainOwnerGranted: true)'),
50
+ (0, type_graphql_1.Mutation)(returns => employee_profile_1.EmployeeProfile, {
51
+ description: "Saves the caller's own profile whole - what is left out is emptied. Refused when the account is not linked to exactly one employee."
52
+ }),
53
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('profile', type => employee_profile_type_1.EmployeeProfileInput)),
54
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
55
+ tslib_1.__metadata("design:type", Function),
56
+ tslib_1.__metadata("design:paramtypes", [employee_profile_type_1.EmployeeProfileInput, Object]),
57
+ tslib_1.__metadata("design:returntype", Promise)
58
+ ], EmployeeProfileMutation.prototype, "saveMyEmployeeProfile", null);
59
+ tslib_1.__decorate([
60
+ (0, type_graphql_1.Directive)('@transaction'),
61
+ (0, type_graphql_1.Directive)('@privilege(category: "employee-self", privilege: "mutation", domainOwnerGranted: true)'),
62
+ (0, type_graphql_1.Mutation)(returns => employee_profile_1.EmployeeProfile, {
63
+ description: "Adds to the caller's own profile: a given summary replaces the summary, skills and rows are added after those held unless already there. Refused when the account is not linked to exactly one employee."
64
+ }),
65
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('profile', type => employee_profile_type_1.EmployeeProfileInput)),
66
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
67
+ tslib_1.__metadata("design:type", Function),
68
+ tslib_1.__metadata("design:paramtypes", [employee_profile_type_1.EmployeeProfileInput, Object]),
69
+ tslib_1.__metadata("design:returntype", Promise)
70
+ ], EmployeeProfileMutation.prototype, "addToMyEmployeeProfile", null);
71
+ exports.EmployeeProfileMutation = EmployeeProfileMutation = tslib_1.__decorate([
72
+ (0, type_graphql_1.Resolver)(employee_profile_1.EmployeeProfile)
73
+ ], EmployeeProfileMutation);
74
+ //# sourceMappingURL=employee-profile-mutation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"employee-profile-mutation.js","sourceRoot":"","sources":["../../../server/service/employee-profile/employee-profile-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Mutation, Resolver } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { requireMyEmployee } from '../employee/my-employee.js'\nimport { EmployeeProfile } from './employee-profile'\nimport { addedProfileOf, profileDocumentOf } from './employee-profile-rules'\nimport { EmployeeProfileInput } from './employee-profile-type'\n\n/*\n * The employee writes their own profile and nobody else's (ADR-0169 decisions 3 · 4) - no door changes another\n * person's. It is saved as one document, as the screen holds it.\n */\n@Resolver(EmployeeProfile)\nexport class EmployeeProfileMutation {\n @Directive('@transaction')\n @Directive('@privilege(category: \"employee-self\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => EmployeeProfile, {\n description: \"Saves the caller's own profile whole - what is left out is emptied. Refused when the account is not linked to exactly one employee.\"\n })\n async saveMyEmployeeProfile(@Arg('profile', type => EmployeeProfileInput) profile: EmployeeProfileInput, @Ctx() context: ResolverContext): Promise<EmployeeProfile> {\n const { domain, user, tx } = context.state\n const employee = await requireMyEmployee(context)\n const repository = getRepository(EmployeeProfile, tx)\n const held = await repository.findOne({ where: { domain: { id: domain.id }, employee: { id: employee.id } } })\n\n const saved = await repository.save({\n ...(held ?? { domain, employee, creator: user }),\n ...profileDocumentOf(profile),\n updater: user\n } as EmployeeProfile)\n\n return await repository.findOne({ where: { id: saved.id } })\n }\n\n /*\n * What the assistant read from a résumé the person attached, added to their own profile once they press the card\n * (ADR-0169 decision 10): nothing kept is taken away - a given summary takes the summary's place, skills and rows\n * are added unless already there.\n */\n @Directive('@transaction')\n @Directive('@privilege(category: \"employee-self\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => EmployeeProfile, {\n description: \"Adds to the caller's own profile: a given summary replaces the summary, skills and rows are added after those held unless already there. Refused when the account is not linked to exactly one employee.\"\n })\n async addToMyEmployeeProfile(@Arg('profile', type => EmployeeProfileInput) profile: EmployeeProfileInput, @Ctx() context: ResolverContext): Promise<EmployeeProfile> {\n const { domain, user, tx } = context.state\n const employee = await requireMyEmployee(context)\n const repository = getRepository(EmployeeProfile, tx)\n const held = await repository.findOne({ where: { domain: { id: domain.id }, employee: { id: employee.id } } })\n\n const saved = await repository.save({\n ...(held ?? { domain, employee, creator: user }),\n ...addedProfileOf(held as any, profile),\n updater: user\n } as EmployeeProfile)\n\n return await repository.findOne({ where: { id: saved.id } })\n }\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import { EmployeeProfile } from './employee-profile';
2
+ import { EmployeeProfileFile, EmployeeProfileFormat, EmployeeWithProfile } from './employee-profile-type';
3
+ export declare class EmployeeProfileQuery {
4
+ myEmployeeProfile(context: ResolverContext): Promise<EmployeeProfile | null>;
5
+ employeeProfiles(context: ResolverContext): Promise<EmployeeWithProfile[]>;
6
+ employeeProfileDocument(employeeIds: string[], format: EmployeeProfileFormat, language: string, context: ResolverContext): Promise<EmployeeProfileFile>;
7
+ myEmployeeProfileDocument(format: EmployeeProfileFormat, language: string, context: ResolverContext): Promise<EmployeeProfileFile>;
8
+ }
9
+ /**
10
+ * The translator of the document's own words: the language asked for, or the reader's. Only the words the document
11
+ * itself prints follow it - names, summaries and projects stay as their employees wrote them. A language the
12
+ * application does not run (`i18n.languages`) is refused rather than printed half in another.
13
+ */
14
+ export declare function documentTranslator(context: any, language?: string | null): (key: string, options?: any) => string;
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EmployeeProfileQuery = void 0;
4
+ exports.documentTranslator = documentTranslator;
5
+ const tslib_1 = require("tslib");
6
+ const type_graphql_1 = require("type-graphql");
7
+ const env_1 = require("@things-factory/env");
8
+ const shell_1 = require("@things-factory/shell");
9
+ const pdf_1 = require("@things-factory/pdf");
10
+ const employee_1 = require("../employee/employee");
11
+ const my_employee_js_1 = require("../employee/my-employee.js");
12
+ const employee_profile_1 = require("./employee-profile");
13
+ const employee_profile_rules_1 = require("./employee-profile-rules");
14
+ const employee_profile_document_1 = require("./employee-profile-document");
15
+ const employee_profile_type_1 = require("./employee-profile-type");
16
+ /*
17
+ * Reading profiles (ADR-0169 decisions 3 · 4): one's own under `employee-self`, every active employee's under
18
+ * `employee-profile` - the seats that attach profiles to quotations. Neither reads the employee record beyond the
19
+ * basics a profile sheet shows.
20
+ */
21
+ let EmployeeProfileQuery = class EmployeeProfileQuery {
22
+ async myEmployeeProfile(context) {
23
+ const { domain, tx } = context.state;
24
+ const mine = await (0, my_employee_js_1.myEmployees)(context);
25
+ if (!mine.length)
26
+ return null;
27
+ const employee = (0, my_employee_js_1.theOneEmployee)(mine, context);
28
+ return await (0, shell_1.getRepository)(employee_profile_1.EmployeeProfile, tx).findOne({ where: { domain: { id: domain.id }, employee: { id: employee.id } } });
29
+ }
30
+ async employeeProfiles(context) {
31
+ return await activeWithProfiles(context);
32
+ }
33
+ /*
34
+ * The chosen people's profiles as one file to hand in with a quotation - Excel or PDF, from one layout
35
+ * (employee-profile-document.ts). Only active employees of the domain, in the order chosen; an id that is none of
36
+ * them is left out. The employer's name is the domain's description, as the sign-in page shows it.
37
+ */
38
+ async employeeProfileDocument(employeeIds, format, language, context) {
39
+ const everyone = await activeWithProfiles(context);
40
+ return await documentOf(employeeIds.map(id => everyone.find(one => one.employeeId === id)).filter(Boolean), format, context, language);
41
+ }
42
+ /* One's own profile as the document sales will hand in - to see it while writing it. */
43
+ async myEmployeeProfileDocument(format, language, context) {
44
+ const mine = (0, my_employee_js_1.theOneEmployee)(await (0, my_employee_js_1.myEmployees)(context), context);
45
+ const everyone = await activeWithProfiles(context);
46
+ return await documentOf(everyone.filter(one => one.employeeId === mine.id), format, context, language);
47
+ }
48
+ };
49
+ exports.EmployeeProfileQuery = EmployeeProfileQuery;
50
+ tslib_1.__decorate([
51
+ (0, type_graphql_1.Directive)('@privilege(category: "employee-self", privilege: "query", domainOwnerGranted: true)'),
52
+ (0, type_graphql_1.Query)(returns => employee_profile_1.EmployeeProfile, {
53
+ nullable: true,
54
+ description: "Fetches the caller's own profile. Null when the caller has written none, or when the account is not linked to an employee (myEmployees tells which)."
55
+ }),
56
+ tslib_1.__param(0, (0, type_graphql_1.Ctx)()),
57
+ tslib_1.__metadata("design:type", Function),
58
+ tslib_1.__metadata("design:paramtypes", [Object]),
59
+ tslib_1.__metadata("design:returntype", Promise)
60
+ ], EmployeeProfileQuery.prototype, "myEmployeeProfile", null);
61
+ tslib_1.__decorate([
62
+ (0, type_graphql_1.Directive)('@privilege(category: "employee-profile", privilege: "query", domainOwnerGranted: true)'),
63
+ (0, type_graphql_1.Query)(returns => [employee_profile_type_1.EmployeeWithProfile], {
64
+ description: 'Fetches every active employee of the domain - not retired, not marked inactive - with their name, department, position, responsibility, hire day and profile; the profile is empty when they have written none.'
65
+ }),
66
+ tslib_1.__param(0, (0, type_graphql_1.Ctx)()),
67
+ tslib_1.__metadata("design:type", Function),
68
+ tslib_1.__metadata("design:paramtypes", [Object]),
69
+ tslib_1.__metadata("design:returntype", Promise)
70
+ ], EmployeeProfileQuery.prototype, "employeeProfiles", null);
71
+ tslib_1.__decorate([
72
+ (0, type_graphql_1.Directive)('@privilege(category: "employee-profile", privilege: "query", domainOwnerGranted: true)'),
73
+ (0, type_graphql_1.Query)(returns => employee_profile_type_1.EmployeeProfileFile, { description: "Prints the chosen active employees' profiles as one Excel workbook or PDF, in the order chosen." }),
74
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('employeeIds', type => [String], { description: 'The employees, in the order their profiles are printed.' })),
75
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('format', type => employee_profile_type_1.EmployeeProfileFormat, { description: 'Excel or PDF.' })),
76
+ tslib_1.__param(2, (0, type_graphql_1.Arg)('language', { nullable: true, description: "The language of the document's own words, one the application runs - the reader's when empty." })),
77
+ tslib_1.__param(3, (0, type_graphql_1.Ctx)()),
78
+ tslib_1.__metadata("design:type", Function),
79
+ tslib_1.__metadata("design:paramtypes", [Array, String, String, Object]),
80
+ tslib_1.__metadata("design:returntype", Promise)
81
+ ], EmployeeProfileQuery.prototype, "employeeProfileDocument", null);
82
+ tslib_1.__decorate([
83
+ (0, type_graphql_1.Directive)('@privilege(category: "employee-self", privilege: "query", domainOwnerGranted: true)'),
84
+ (0, type_graphql_1.Query)(returns => employee_profile_type_1.EmployeeProfileFile, { description: "Prints the caller's own profile as the Excel workbook or PDF a quotation attaches." }),
85
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('format', type => employee_profile_type_1.EmployeeProfileFormat, { description: 'Excel or PDF.' })),
86
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('language', { nullable: true, description: "The language of the document's own words, one the application runs - the reader's when empty." })),
87
+ tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
88
+ tslib_1.__metadata("design:type", Function),
89
+ tslib_1.__metadata("design:paramtypes", [String, String, Object]),
90
+ tslib_1.__metadata("design:returntype", Promise)
91
+ ], EmployeeProfileQuery.prototype, "myEmployeeProfileDocument", null);
92
+ exports.EmployeeProfileQuery = EmployeeProfileQuery = tslib_1.__decorate([
93
+ (0, type_graphql_1.Resolver)(employee_profile_1.EmployeeProfile)
94
+ ], EmployeeProfileQuery);
95
+ /**
96
+ * The translator of the document's own words: the language asked for, or the reader's. Only the words the document
97
+ * itself prints follow it - names, summaries and projects stay as their employees wrote them. A language the
98
+ * application does not run (`i18n.languages`) is refused rather than printed half in another.
99
+ */
100
+ function documentTranslator(context, language) {
101
+ if (!language)
102
+ return (key, options) => String(context.t(key, options));
103
+ const runs = (env_1.config.get('i18n/languages', []) ?? []).map(one => one?.code);
104
+ if (!runs.includes(language))
105
+ throw new Error(`The application does not run the language "${language}".`);
106
+ const fixed = context.i18next.getFixedT(language);
107
+ return (key, options) => String(fixed(key, options));
108
+ }
109
+ /** The people's profiles as one file - Excel or PDF from one layout, the employer named as the sign-in page names it. */
110
+ async function documentOf(people, format, context, language) {
111
+ const t = documentTranslator(context, language);
112
+ const words = (0, employee_profile_document_1.profileWordsOf)(t);
113
+ const company = context.state.domain.description || context.state.domain.name;
114
+ const word = t('text.employee-profile-file');
115
+ if (format === employee_profile_type_1.EmployeeProfileFormat.HTML) {
116
+ return { fileName: (0, employee_profile_document_1.profileFileName)(word, 'html'), mimeType: 'text/html', content: Buffer.from((0, employee_profile_document_1.profilePreviewHtml)(people, words, company), 'utf8').toString('base64') };
117
+ }
118
+ if (format === employee_profile_type_1.EmployeeProfileFormat.PDF) {
119
+ const body = `<style>${employee_profile_document_1.PROFILE_CSS}</style>${(0, employee_profile_document_1.profileHtml)(people, words, company)}`;
120
+ const pdf = await new pdf_1.PdfService().generatePdf({ content_template: '{{{body}}}', footer_template: (0, employee_profile_document_1.profileFooterHtml)(company, words), page_size: 'A4' }, { body });
121
+ return { fileName: (0, employee_profile_document_1.profileFileName)(word, 'pdf'), mimeType: 'application/pdf', content: pdf.toString('base64') };
122
+ }
123
+ const buffer = await (0, employee_profile_document_1.profileWorkbook)(people, words, company).xlsx.writeBuffer();
124
+ return {
125
+ fileName: (0, employee_profile_document_1.profileFileName)(word, 'xlsx'),
126
+ mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
127
+ content: Buffer.from(buffer).toString('base64')
128
+ };
129
+ }
130
+ /** Every active employee of the domain with their profile - the list and the document read the same people. */
131
+ async function activeWithProfiles(context) {
132
+ const { domain, tx } = context.state;
133
+ const employees = await (0, shell_1.getRepository)(employee_1.Employee, tx).find({ where: { domain: { id: domain.id } }, relations: ['department'] });
134
+ const profiles = await (0, shell_1.getRepository)(employee_profile_1.EmployeeProfile, tx).find({ where: { domain: { id: domain.id } } });
135
+ return (0, employee_profile_rules_1.employeesWithProfiles)(employees, profiles, (0, employee_profile_rules_1.todayOf)());
136
+ }
137
+ //# sourceMappingURL=employee-profile-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"employee-profile-query.js","sourceRoot":"","sources":["../../../server/service/employee-profile/employee-profile-query.ts"],"names":[],"mappings":";;;AA+EA,gDAMC;;AArFD,+CAAmE;AAEnE,6CAA4C;AAC5C,iDAAqD;AACrD,6CAAgD;AAEhD,mDAA+C;AAC/C,+DAAwE;AACxE,yDAAoD;AACpD,qEAAyE;AACzE,2EAA+J;AAC/J,mEAAyG;AAEzG;;;;GAIG;AAEI,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAMzB,AAAN,KAAK,CAAC,iBAAiB,CAAQ,OAAwB;QACrD,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QACpC,MAAM,IAAI,GAAG,MAAM,IAAA,4BAAW,EAAC,OAAO,CAAC,CAAA;QACvC,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAA;QAE7B,MAAM,QAAQ,GAAG,IAAA,+BAAc,EAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAC9C,OAAO,MAAM,IAAA,qBAAa,EAAC,kCAAe,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;IAClI,CAAC;IAOK,AAAN,KAAK,CAAC,gBAAgB,CAAQ,OAAwB;QACpD,OAAO,MAAM,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAC1C,CAAC;IAED;;;;OAIG;IAGG,AAAN,KAAK,CAAC,uBAAuB,CACuF,WAAqB,EACvD,MAA6B,EACsC,QAAgB,EAC5J,OAAwB;QAE/B,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,OAAO,CAAC,CAAA;QAClD,OAAO,MAAM,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAA;IACxI,CAAC;IAED,wFAAwF;IAGlF,AAAN,KAAK,CAAC,yBAAyB,CACmD,MAA6B,EACsC,QAAgB,EAC5J,OAAwB;QAE/B,MAAM,IAAI,GAAG,IAAA,+BAAc,EAAC,MAAM,IAAA,4BAAW,EAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAA;QAChE,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,OAAO,CAAC,CAAA;QAClD,OAAO,MAAM,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,KAAK,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAA;IACxG,CAAC;CACF,CAAA;AArDY,oDAAoB;AAMzB;IALL,IAAA,wBAAS,EAAC,qFAAqF,CAAC;IAChG,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,kCAAe,EAAE;QACjC,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,sJAAsJ;KACpK,CAAC;IACuB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;6DAO7B;AAOK;IALL,IAAA,wBAAS,EAAC,wFAAwF,CAAC;IACnG,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,2CAAmB,CAAC,EAAE;QACvC,WAAW,EACT,iNAAiN;KACpN,CAAC;IACsB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;4DAE5B;AASK;IAFL,IAAA,wBAAS,EAAC,wFAAwF,CAAC;IACnG,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,2CAAmB,EAAE,EAAE,WAAW,EAAE,iGAAiG,EAAE,CAAC;IAEvJ,mBAAA,IAAA,kBAAG,EAAC,aAAa,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,WAAW,EAAE,yDAAyD,EAAE,CAAC,CAAA;IAChH,mBAAA,IAAA,kBAAG,EAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC,6CAAqB,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC,CAAA;IAC9E,mBAAA,IAAA,kBAAG,EAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,+FAA+F,EAAE,CAAC,CAAA;IACjJ,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;mEAIP;AAKK;IAFL,IAAA,wBAAS,EAAC,qFAAqF,CAAC;IAChG,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,2CAAmB,EAAE,EAAE,WAAW,EAAE,oFAAoF,EAAE,CAAC;IAE1I,mBAAA,IAAA,kBAAG,EAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC,6CAAqB,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC,CAAA;IAC9E,mBAAA,IAAA,kBAAG,EAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,+FAA+F,EAAE,CAAC,CAAA;IACjJ,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;qEAKP;+BApDU,oBAAoB;IADhC,IAAA,uBAAQ,EAAC,kCAAe,CAAC;GACb,oBAAoB,CAqDhC;AAED;;;;GAIG;AACH,SAAgB,kBAAkB,CAAC,OAAY,EAAE,QAAwB;IACvE,IAAI,CAAC,QAAQ;QAAE,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAA;IACvE,MAAM,IAAI,GAAG,CAAE,YAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,EAAE,CAAyB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IACpG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,QAAQ,IAAI,CAAC,CAAA;IACzG,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;IACjD,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAA;AACtD,CAAC;AAED,yHAAyH;AACzH,KAAK,UAAU,UAAU,CAAC,MAA6B,EAAE,MAA6B,EAAE,OAAwB,EAAE,QAAiB;IACjI,MAAM,CAAC,GAAG,kBAAkB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;IAC/C,MAAM,KAAK,GAAG,IAAA,0CAAc,EAAC,CAAC,CAAC,CAAA;IAC/B,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAA;IAC7E,MAAM,IAAI,GAAG,CAAC,CAAC,4BAA4B,CAAC,CAAA;IAE5C,IAAI,MAAM,KAAK,6CAAqB,CAAC,IAAI,EAAE,CAAC;QAC1C,OAAO,EAAE,QAAQ,EAAE,IAAA,2CAAe,EAAC,IAAI,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,IAAA,8CAAkB,EAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAA;IACxK,CAAC;IAED,IAAI,MAAM,KAAK,6CAAqB,CAAC,GAAG,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG,UAAU,uCAAW,WAAW,IAAA,uCAAW,EAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,CAAA;QAClF,MAAM,GAAG,GAAG,MAAM,IAAI,gBAAU,EAAE,CAAC,WAAW,CAAC,EAAE,gBAAgB,EAAE,YAAY,EAAE,eAAe,EAAE,IAAA,6CAAiB,EAAC,OAAO,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;QACjK,OAAO,EAAE,QAAQ,EAAE,IAAA,2CAAe,EAAC,IAAI,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,iBAAiB,EAAE,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAA;IACjH,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,IAAA,2CAAe,EAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAA;IAC/E,OAAO;QACL,QAAQ,EAAE,IAAA,2CAAe,EAAC,IAAI,EAAE,MAAM,CAAC;QACvC,QAAQ,EAAE,mEAAmE;QAC7E,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,MAAqB,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;KAC/D,CAAA;AACH,CAAC;AAED,+GAA+G;AAC/G,KAAK,UAAU,kBAAkB,CAAC,OAAwB;IACxD,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IACpC,MAAM,SAAS,GAAG,MAAM,IAAA,qBAAa,EAAC,mBAAQ,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAA;IAC7H,MAAM,QAAQ,GAAG,MAAM,IAAA,qBAAa,EAAC,kCAAe,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;IAExG,OAAO,IAAA,8CAAqB,EAAC,SAAS,EAAE,QAAQ,EAAE,IAAA,gCAAO,GAAE,CAAQ,CAAA;AACrE,CAAC","sourcesContent":["import { Arg, Ctx, Directive, Query, Resolver } from 'type-graphql'\n\nimport { config } from '@things-factory/env'\nimport { getRepository } from '@things-factory/shell'\nimport { PdfService } from '@things-factory/pdf'\n\nimport { Employee } from '../employee/employee'\nimport { myEmployees, theOneEmployee } from '../employee/my-employee.js'\nimport { EmployeeProfile } from './employee-profile'\nimport { employeesWithProfiles, todayOf } from './employee-profile-rules'\nimport { profileFileName, profileFooterHtml, PROFILE_CSS, profileHtml, profilePreviewHtml, profileWordsOf, profileWorkbook } from './employee-profile-document'\nimport { EmployeeProfileFile, EmployeeProfileFormat, EmployeeWithProfile } from './employee-profile-type'\n\n/*\n * Reading profiles (ADR-0169 decisions 3 · 4): one's own under `employee-self`, every active employee's under\n * `employee-profile` - the seats that attach profiles to quotations. Neither reads the employee record beyond the\n * basics a profile sheet shows.\n */\n@Resolver(EmployeeProfile)\nexport class EmployeeProfileQuery {\n @Directive('@privilege(category: \"employee-self\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => EmployeeProfile, {\n nullable: true,\n description: \"Fetches the caller's own profile. Null when the caller has written none, or when the account is not linked to an employee (myEmployees tells which).\"\n })\n async myEmployeeProfile(@Ctx() context: ResolverContext): Promise<EmployeeProfile | null> {\n const { domain, tx } = context.state\n const mine = await myEmployees(context)\n if (!mine.length) return null\n\n const employee = theOneEmployee(mine, context)\n return await getRepository(EmployeeProfile, tx).findOne({ where: { domain: { id: domain.id }, employee: { id: employee.id } } })\n }\n\n @Directive('@privilege(category: \"employee-profile\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => [EmployeeWithProfile], {\n description:\n 'Fetches every active employee of the domain - not retired, not marked inactive - with their name, department, position, responsibility, hire day and profile; the profile is empty when they have written none.'\n })\n async employeeProfiles(@Ctx() context: ResolverContext): Promise<EmployeeWithProfile[]> {\n return await activeWithProfiles(context)\n }\n\n /*\n * The chosen people's profiles as one file to hand in with a quotation - Excel or PDF, from one layout\n * (employee-profile-document.ts). Only active employees of the domain, in the order chosen; an id that is none of\n * them is left out. The employer's name is the domain's description, as the sign-in page shows it.\n */\n @Directive('@privilege(category: \"employee-profile\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => EmployeeProfileFile, { description: \"Prints the chosen active employees' profiles as one Excel workbook or PDF, in the order chosen.\" })\n async employeeProfileDocument(\n @Arg('employeeIds', type => [String], { description: 'The employees, in the order their profiles are printed.' }) employeeIds: string[],\n @Arg('format', type => EmployeeProfileFormat, { description: 'Excel or PDF.' }) format: EmployeeProfileFormat,\n @Arg('language', { nullable: true, description: \"The language of the document's own words, one the application runs - the reader's when empty.\" }) language: string,\n @Ctx() context: ResolverContext\n ): Promise<EmployeeProfileFile> {\n const everyone = await activeWithProfiles(context)\n return await documentOf(employeeIds.map(id => everyone.find(one => one.employeeId === id)).filter(Boolean), format, context, language)\n }\n\n /* One's own profile as the document sales will hand in - to see it while writing it. */\n @Directive('@privilege(category: \"employee-self\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => EmployeeProfileFile, { description: \"Prints the caller's own profile as the Excel workbook or PDF a quotation attaches.\" })\n async myEmployeeProfileDocument(\n @Arg('format', type => EmployeeProfileFormat, { description: 'Excel or PDF.' }) format: EmployeeProfileFormat,\n @Arg('language', { nullable: true, description: \"The language of the document's own words, one the application runs - the reader's when empty.\" }) language: string,\n @Ctx() context: ResolverContext\n ): Promise<EmployeeProfileFile> {\n const mine = theOneEmployee(await myEmployees(context), context)\n const everyone = await activeWithProfiles(context)\n return await documentOf(everyone.filter(one => one.employeeId === mine.id), format, context, language)\n }\n}\n\n/**\n * The translator of the document's own words: the language asked for, or the reader's. Only the words the document\n * itself prints follow it - names, summaries and projects stay as their employees wrote them. A language the\n * application does not run (`i18n.languages`) is refused rather than printed half in another.\n */\nexport function documentTranslator(context: any, language?: string | null): (key: string, options?: any) => string {\n if (!language) return (key, options) => String(context.t(key, options))\n const runs = ((config.get('i18n/languages', []) as { code?: string }[]) ?? []).map(one => one?.code)\n if (!runs.includes(language)) throw new Error(`The application does not run the language \"${language}\".`)\n const fixed = context.i18next.getFixedT(language)\n return (key, options) => String(fixed(key, options))\n}\n\n/** The people's profiles as one file - Excel or PDF from one layout, the employer named as the sign-in page names it. */\nasync function documentOf(people: EmployeeWithProfile[], format: EmployeeProfileFormat, context: ResolverContext, language?: string): Promise<EmployeeProfileFile> {\n const t = documentTranslator(context, language)\n const words = profileWordsOf(t)\n const company = context.state.domain.description || context.state.domain.name\n const word = t('text.employee-profile-file')\n\n if (format === EmployeeProfileFormat.HTML) {\n return { fileName: profileFileName(word, 'html'), mimeType: 'text/html', content: Buffer.from(profilePreviewHtml(people, words, company), 'utf8').toString('base64') }\n }\n\n if (format === EmployeeProfileFormat.PDF) {\n const body = `<style>${PROFILE_CSS}</style>${profileHtml(people, words, company)}`\n const pdf = await new PdfService().generatePdf({ content_template: '{{{body}}}', footer_template: profileFooterHtml(company, words), page_size: 'A4' }, { body })\n return { fileName: profileFileName(word, 'pdf'), mimeType: 'application/pdf', content: pdf.toString('base64') }\n }\n\n const buffer = await profileWorkbook(people, words, company).xlsx.writeBuffer()\n return {\n fileName: profileFileName(word, 'xlsx'),\n mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n content: Buffer.from(buffer as ArrayBuffer).toString('base64')\n }\n}\n\n/** Every active employee of the domain with their profile - the list and the document read the same people. */\nasync function activeWithProfiles(context: ResolverContext): Promise<EmployeeWithProfile[]> {\n const { domain, tx } = context.state\n const employees = await getRepository(Employee, tx).find({ where: { domain: { id: domain.id } }, relations: ['department'] })\n const profiles = await getRepository(EmployeeProfile, tx).find({ where: { domain: { id: domain.id } } })\n\n return employeesWithProfiles(employees, profiles, todayOf()) as any\n}\n"]}
@@ -0,0 +1,29 @@
1
+ export declare class EmployeeProfileEducation {
2
+ school?: string;
3
+ major?: string;
4
+ degree?: string;
5
+ startedOn?: string;
6
+ endedOn?: string;
7
+ }
8
+ export declare class EmployeeProfileCertificate {
9
+ name?: string;
10
+ issuer?: string;
11
+ acquiredOn?: string;
12
+ }
13
+ export declare class EmployeeProfileCareer {
14
+ company?: string;
15
+ department?: string;
16
+ position?: string;
17
+ startedOn?: string;
18
+ endedOn?: string;
19
+ duties?: string;
20
+ }
21
+ export declare class EmployeeProfileProject {
22
+ name?: string;
23
+ client?: string;
24
+ startedOn?: string;
25
+ endedOn?: string;
26
+ role?: string;
27
+ duties?: string;
28
+ technologies?: string;
29
+ }
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EmployeeProfileProject = exports.EmployeeProfileCareer = exports.EmployeeProfileCertificate = exports.EmployeeProfileEducation = void 0;
4
+ const tslib_1 = require("tslib");
5
+ /*
6
+ * The rows of an employee profile (ADR-0169 decision 2). Each shape is read and written alike - the same fields come back
7
+ * as were saved - so it is one class, declared as an object type and, under another name, as its input.
8
+ */
9
+ const type_graphql_1 = require("type-graphql");
10
+ let EmployeeProfileEducation = class EmployeeProfileEducation {
11
+ };
12
+ exports.EmployeeProfileEducation = EmployeeProfileEducation;
13
+ tslib_1.__decorate([
14
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The school.' }),
15
+ tslib_1.__metadata("design:type", String)
16
+ ], EmployeeProfileEducation.prototype, "school", void 0);
17
+ tslib_1.__decorate([
18
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The major.' }),
19
+ tslib_1.__metadata("design:type", String)
20
+ ], EmployeeProfileEducation.prototype, "major", void 0);
21
+ tslib_1.__decorate([
22
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The degree or the stage finished - e.g. bachelor, master, high school.' }),
23
+ tslib_1.__metadata("design:type", String)
24
+ ], EmployeeProfileEducation.prototype, "degree", void 0);
25
+ tslib_1.__decorate([
26
+ (0, type_graphql_1.Field)({ nullable: true, description: 'When it started, YYYY-MM.' }),
27
+ tslib_1.__metadata("design:type", String)
28
+ ], EmployeeProfileEducation.prototype, "startedOn", void 0);
29
+ tslib_1.__decorate([
30
+ (0, type_graphql_1.Field)({ nullable: true, description: 'When it ended, YYYY-MM - empty while still at it.' }),
31
+ tslib_1.__metadata("design:type", String)
32
+ ], EmployeeProfileEducation.prototype, "endedOn", void 0);
33
+ exports.EmployeeProfileEducation = EmployeeProfileEducation = tslib_1.__decorate([
34
+ (0, type_graphql_1.ObjectType)('EmployeeProfileEducation', { description: 'A school the employee went to.' }),
35
+ (0, type_graphql_1.InputType)('EmployeeProfileEducationInput', { description: 'A school the employee went to.' })
36
+ ], EmployeeProfileEducation);
37
+ let EmployeeProfileCertificate = class EmployeeProfileCertificate {
38
+ };
39
+ exports.EmployeeProfileCertificate = EmployeeProfileCertificate;
40
+ tslib_1.__decorate([
41
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The name of the certificate.' }),
42
+ tslib_1.__metadata("design:type", String)
43
+ ], EmployeeProfileCertificate.prototype, "name", void 0);
44
+ tslib_1.__decorate([
45
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Who issued it.' }),
46
+ tslib_1.__metadata("design:type", String)
47
+ ], EmployeeProfileCertificate.prototype, "issuer", void 0);
48
+ tslib_1.__decorate([
49
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The day it was acquired, YYYY-MM-DD.' }),
50
+ tslib_1.__metadata("design:type", String)
51
+ ], EmployeeProfileCertificate.prototype, "acquiredOn", void 0);
52
+ exports.EmployeeProfileCertificate = EmployeeProfileCertificate = tslib_1.__decorate([
53
+ (0, type_graphql_1.ObjectType)('EmployeeProfileCertificate', { description: 'A certificate or licence the employee holds.' }),
54
+ (0, type_graphql_1.InputType)('EmployeeProfileCertificateInput', { description: 'A certificate or licence the employee holds.' })
55
+ ], EmployeeProfileCertificate);
56
+ let EmployeeProfileCareer = class EmployeeProfileCareer {
57
+ };
58
+ exports.EmployeeProfileCareer = EmployeeProfileCareer;
59
+ tslib_1.__decorate([
60
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The company.' }),
61
+ tslib_1.__metadata("design:type", String)
62
+ ], EmployeeProfileCareer.prototype, "company", void 0);
63
+ tslib_1.__decorate([
64
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The department there.' }),
65
+ tslib_1.__metadata("design:type", String)
66
+ ], EmployeeProfileCareer.prototype, "department", void 0);
67
+ tslib_1.__decorate([
68
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The position there.' }),
69
+ tslib_1.__metadata("design:type", String)
70
+ ], EmployeeProfileCareer.prototype, "position", void 0);
71
+ tslib_1.__decorate([
72
+ (0, type_graphql_1.Field)({ nullable: true, description: 'When it started, YYYY-MM.' }),
73
+ tslib_1.__metadata("design:type", String)
74
+ ], EmployeeProfileCareer.prototype, "startedOn", void 0);
75
+ tslib_1.__decorate([
76
+ (0, type_graphql_1.Field)({ nullable: true, description: 'When it ended, YYYY-MM.' }),
77
+ tslib_1.__metadata("design:type", String)
78
+ ], EmployeeProfileCareer.prototype, "endedOn", void 0);
79
+ tslib_1.__decorate([
80
+ (0, type_graphql_1.Field)({ nullable: true, description: 'What the employee did there.' }),
81
+ tslib_1.__metadata("design:type", String)
82
+ ], EmployeeProfileCareer.prototype, "duties", void 0);
83
+ exports.EmployeeProfileCareer = EmployeeProfileCareer = tslib_1.__decorate([
84
+ (0, type_graphql_1.ObjectType)('EmployeeProfileCareer', { description: 'A job the employee held before - at another company.' }),
85
+ (0, type_graphql_1.InputType)('EmployeeProfileCareerInput', { description: 'A job the employee held before - at another company.' })
86
+ ], EmployeeProfileCareer);
87
+ let EmployeeProfileProject = class EmployeeProfileProject {
88
+ };
89
+ exports.EmployeeProfileProject = EmployeeProfileProject;
90
+ tslib_1.__decorate([
91
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The name of the project.' }),
92
+ tslib_1.__metadata("design:type", String)
93
+ ], EmployeeProfileProject.prototype, "name", void 0);
94
+ tslib_1.__decorate([
95
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Who ordered it - the client.' }),
96
+ tslib_1.__metadata("design:type", String)
97
+ ], EmployeeProfileProject.prototype, "client", void 0);
98
+ tslib_1.__decorate([
99
+ (0, type_graphql_1.Field)({ nullable: true, description: 'When the employee joined it, YYYY-MM.' }),
100
+ tslib_1.__metadata("design:type", String)
101
+ ], EmployeeProfileProject.prototype, "startedOn", void 0);
102
+ tslib_1.__decorate([
103
+ (0, type_graphql_1.Field)({ nullable: true, description: 'When the employee left it, YYYY-MM - empty while still on it.' }),
104
+ tslib_1.__metadata("design:type", String)
105
+ ], EmployeeProfileProject.prototype, "endedOn", void 0);
106
+ tslib_1.__decorate([
107
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The role on it - e.g. PM, developer.' }),
108
+ tslib_1.__metadata("design:type", String)
109
+ ], EmployeeProfileProject.prototype, "role", void 0);
110
+ tslib_1.__decorate([
111
+ (0, type_graphql_1.Field)({ nullable: true, description: 'What the employee did on it.' }),
112
+ tslib_1.__metadata("design:type", String)
113
+ ], EmployeeProfileProject.prototype, "duties", void 0);
114
+ tslib_1.__decorate([
115
+ (0, type_graphql_1.Field)({ nullable: true, description: 'The technologies used, as written.' }),
116
+ tslib_1.__metadata("design:type", String)
117
+ ], EmployeeProfileProject.prototype, "technologies", void 0);
118
+ exports.EmployeeProfileProject = EmployeeProfileProject = tslib_1.__decorate([
119
+ (0, type_graphql_1.ObjectType)('EmployeeProfileProject', { description: 'A project the employee worked on.' }),
120
+ (0, type_graphql_1.InputType)('EmployeeProfileProjectInput', { description: 'A project the employee worked on.' })
121
+ ], EmployeeProfileProject);
122
+ //# sourceMappingURL=employee-profile-rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"employee-profile-rows.js","sourceRoot":"","sources":["../../../server/service/employee-profile/employee-profile-rows.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The rows of an employee profile (ADR-0169 decision 2). Each shape is read and written alike - the same fields come back\n * as were saved - so it is one class, declared as an object type and, under another name, as its input.\n */\nimport { Field, InputType, ObjectType } from 'type-graphql'\n\n@ObjectType('EmployeeProfileEducation', { description: 'A school the employee went to.' })\n@InputType('EmployeeProfileEducationInput', { description: 'A school the employee went to.' })\nexport class EmployeeProfileEducation {\n @Field({ nullable: true, description: 'The school.' })\n school?: string\n\n @Field({ nullable: true, description: 'The major.' })\n major?: string\n\n @Field({ nullable: true, description: 'The degree or the stage finished - e.g. bachelor, master, high school.' })\n degree?: string\n\n @Field({ nullable: true, description: 'When it started, YYYY-MM.' })\n startedOn?: string\n\n @Field({ nullable: true, description: 'When it ended, YYYY-MM - empty while still at it.' })\n endedOn?: string\n}\n\n@ObjectType('EmployeeProfileCertificate', { description: 'A certificate or licence the employee holds.' })\n@InputType('EmployeeProfileCertificateInput', { description: 'A certificate or licence the employee holds.' })\nexport class EmployeeProfileCertificate {\n @Field({ nullable: true, description: 'The name of the certificate.' })\n name?: string\n\n @Field({ nullable: true, description: 'Who issued it.' })\n issuer?: string\n\n @Field({ nullable: true, description: 'The day it was acquired, YYYY-MM-DD.' })\n acquiredOn?: string\n}\n\n@ObjectType('EmployeeProfileCareer', { description: 'A job the employee held before - at another company.' })\n@InputType('EmployeeProfileCareerInput', { description: 'A job the employee held before - at another company.' })\nexport class EmployeeProfileCareer {\n @Field({ nullable: true, description: 'The company.' })\n company?: string\n\n @Field({ nullable: true, description: 'The department there.' })\n department?: string\n\n @Field({ nullable: true, description: 'The position there.' })\n position?: string\n\n @Field({ nullable: true, description: 'When it started, YYYY-MM.' })\n startedOn?: string\n\n @Field({ nullable: true, description: 'When it ended, YYYY-MM.' })\n endedOn?: string\n\n @Field({ nullable: true, description: 'What the employee did there.' })\n duties?: string\n}\n\n@ObjectType('EmployeeProfileProject', { description: 'A project the employee worked on.' })\n@InputType('EmployeeProfileProjectInput', { description: 'A project the employee worked on.' })\nexport class EmployeeProfileProject {\n @Field({ nullable: true, description: 'The name of the project.' })\n name?: string\n\n @Field({ nullable: true, description: 'Who ordered it - the client.' })\n client?: string\n\n @Field({ nullable: true, description: 'When the employee joined it, YYYY-MM.' })\n startedOn?: string\n\n @Field({ nullable: true, description: 'When the employee left it, YYYY-MM - empty while still on it.' })\n endedOn?: string\n\n @Field({ nullable: true, description: 'The role on it - e.g. PM, developer.' })\n role?: string\n\n @Field({ nullable: true, description: 'What the employee did on it.' })\n duties?: string\n\n @Field({ nullable: true, description: 'The technologies used, as written.' })\n technologies?: string\n}\n"]}
@@ -0,0 +1,61 @@
1
+ type Row = Record<string, unknown>;
2
+ export type ProfileDocument = {
3
+ summary: string | null;
4
+ skills: string[];
5
+ educations: Row[];
6
+ certificates: Row[];
7
+ careers: Row[];
8
+ projects: Row[];
9
+ };
10
+ /** The fields each kind of row keeps; anything else a caller sends is not stored. */
11
+ export declare const PROFILE_ROW_FIELDS: {
12
+ readonly educations: readonly ["school", "major", "degree", "startedOn", "endedOn"];
13
+ readonly certificates: readonly ["name", "issuer", "acquiredOn"];
14
+ readonly careers: readonly ["company", "department", "position", "startedOn", "endedOn", "duties"];
15
+ readonly projects: readonly ["name", "client", "startedOn", "endedOn", "role", "duties", "technologies"];
16
+ };
17
+ /**
18
+ * The profile as it is saved - whole, as the screen sent it (ADR-0169 decision 2). Blank rows the screen added and did
19
+ * not fill go, and a skill written twice is kept once.
20
+ */
21
+ export declare function profileDocumentOf(input: any): ProfileDocument;
22
+ /**
23
+ * What is added to a profile kept so far (ADR-0169 decision 10 - the assistant's 입력안 from a résumé): a summary given
24
+ * takes the place of the one kept, skills are added after those held (a skill already held is not added twice), and
25
+ * rows are added after those of their section unless the very same row is there. Nothing kept is taken away.
26
+ */
27
+ export declare function addedProfileOf(held: Partial<ProfileDocument> | null | undefined, addition: any): ProfileDocument;
28
+ export type ProfileEmployee = {
29
+ id: string;
30
+ controlNo?: string;
31
+ name?: string;
32
+ active?: boolean | null;
33
+ retiredOn?: string | null;
34
+ jobPosition?: string;
35
+ jobResponsibility?: string;
36
+ hiredOn?: string;
37
+ department?: {
38
+ name?: string;
39
+ } | null;
40
+ };
41
+ /** Still working here: not marked inactive, and no retire day that has come. `today` is YYYY-MM-DD. */
42
+ export declare function isActiveEmployee(employee: ProfileEmployee, today: string): boolean;
43
+ /**
44
+ * Every active employee with their profile, by name (ADR-0169 decision 4) - an employee who has written none is listed
45
+ * with an empty one, so the list says who has not written it rather than leaving them out.
46
+ */
47
+ export declare function employeesWithProfiles<P extends {
48
+ employeeId?: string;
49
+ }>(employees: ProfileEmployee[], profiles: P[], today: string): {
50
+ employeeId: string;
51
+ controlNo: string;
52
+ name: string;
53
+ departmentName: string;
54
+ jobPosition: string;
55
+ jobResponsibility: string;
56
+ hiredOn: string;
57
+ profile: P;
58
+ }[];
59
+ /** Today in the server's calendar, YYYY-MM-DD - the form `retiredOn` is kept in. */
60
+ export declare function todayOf(now?: Date): string;
61
+ export {};
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ /*
3
+ * The judgements of the profile's doors (ADR-0169) - pure, apart from the store, so the tests read them without the
4
+ * entities (the employee record pulls in contact, which reads the configuration when it is loaded).
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.PROFILE_ROW_FIELDS = void 0;
8
+ exports.profileDocumentOf = profileDocumentOf;
9
+ exports.addedProfileOf = addedProfileOf;
10
+ exports.isActiveEmployee = isActiveEmployee;
11
+ exports.employeesWithProfiles = employeesWithProfiles;
12
+ exports.todayOf = todayOf;
13
+ /** The fields each kind of row keeps; anything else a caller sends is not stored. */
14
+ exports.PROFILE_ROW_FIELDS = {
15
+ educations: ['school', 'major', 'degree', 'startedOn', 'endedOn'],
16
+ certificates: ['name', 'issuer', 'acquiredOn'],
17
+ careers: ['company', 'department', 'position', 'startedOn', 'endedOn', 'duties'],
18
+ projects: ['name', 'client', 'startedOn', 'endedOn', 'role', 'duties', 'technologies']
19
+ };
20
+ const text = (value) => (typeof value === 'string' ? value.trim() : '');
21
+ /** A row as kept: its own fields trimmed, empty ones left out. A row with nothing in it is no row. */
22
+ function rowsOf(rows, fields) {
23
+ if (!Array.isArray(rows))
24
+ return [];
25
+ return rows.flatMap(row => {
26
+ const kept = {};
27
+ for (const field of fields) {
28
+ const value = text(row?.[field]);
29
+ if (value)
30
+ kept[field] = value;
31
+ }
32
+ return Object.keys(kept).length ? [kept] : [];
33
+ });
34
+ }
35
+ /**
36
+ * The profile as it is saved - whole, as the screen sent it (ADR-0169 decision 2). Blank rows the screen added and did
37
+ * not fill go, and a skill written twice is kept once.
38
+ */
39
+ function profileDocumentOf(input) {
40
+ const skills = [...new Set((Array.isArray(input?.skills) ? input.skills : []).map(text).filter(Boolean))];
41
+ return {
42
+ summary: text(input?.summary) || null,
43
+ skills,
44
+ educations: rowsOf(input?.educations, exports.PROFILE_ROW_FIELDS.educations),
45
+ certificates: rowsOf(input?.certificates, exports.PROFILE_ROW_FIELDS.certificates),
46
+ careers: rowsOf(input?.careers, exports.PROFILE_ROW_FIELDS.careers),
47
+ projects: rowsOf(input?.projects, exports.PROFILE_ROW_FIELDS.projects)
48
+ };
49
+ }
50
+ /** Two rows are the same entry when every field they keep is the same - spacing and letter case aside. */
51
+ const sameRow = (a, b) => {
52
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
53
+ return [...keys].every(key => text(a[key]).toLowerCase() === text(b[key]).toLowerCase());
54
+ };
55
+ /**
56
+ * What is added to a profile kept so far (ADR-0169 decision 10 - the assistant's 입력안 from a résumé): a summary given
57
+ * takes the place of the one kept, skills are added after those held (a skill already held is not added twice), and
58
+ * rows are added after those of their section unless the very same row is there. Nothing kept is taken away.
59
+ */
60
+ function addedProfileOf(held, addition) {
61
+ const kept = profileDocumentOf(held ?? {});
62
+ const added = profileDocumentOf(addition);
63
+ const rows = (key) => [...kept[key], ...added[key].filter(row => !kept[key].some(one => sameRow(one, row)))];
64
+ return {
65
+ summary: added.summary ?? kept.summary,
66
+ skills: [...kept.skills, ...added.skills.filter(skill => !kept.skills.some(one => one.toLowerCase() === skill.toLowerCase()))],
67
+ educations: rows('educations'),
68
+ certificates: rows('certificates'),
69
+ careers: rows('careers'),
70
+ projects: rows('projects')
71
+ };
72
+ }
73
+ /** Still working here: not marked inactive, and no retire day that has come. `today` is YYYY-MM-DD. */
74
+ function isActiveEmployee(employee, today) {
75
+ if (employee.active === false)
76
+ return false;
77
+ return !employee.retiredOn || employee.retiredOn > today;
78
+ }
79
+ /**
80
+ * Every active employee with their profile, by name (ADR-0169 decision 4) - an employee who has written none is listed
81
+ * with an empty one, so the list says who has not written it rather than leaving them out.
82
+ */
83
+ function employeesWithProfiles(employees, profiles, today) {
84
+ const byEmployee = new Map(profiles.map(profile => [profile.employeeId, profile]));
85
+ return employees
86
+ .filter(employee => isActiveEmployee(employee, today))
87
+ .map(employee => ({
88
+ employeeId: employee.id,
89
+ controlNo: employee.controlNo,
90
+ name: employee.name,
91
+ departmentName: employee.department?.name,
92
+ jobPosition: employee.jobPosition,
93
+ jobResponsibility: employee.jobResponsibility,
94
+ hiredOn: employee.hiredOn,
95
+ profile: byEmployee.get(employee.id) ?? null
96
+ }))
97
+ .sort((a, b) => (a.name ?? '').localeCompare(b.name ?? '', 'ko') || (a.controlNo ?? '').localeCompare(b.controlNo ?? ''));
98
+ }
99
+ /** Today in the server's calendar, YYYY-MM-DD - the form `retiredOn` is kept in. */
100
+ function todayOf(now = new Date()) {
101
+ const pad = (n) => String(n).padStart(2, '0');
102
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
103
+ }
104
+ //# sourceMappingURL=employee-profile-rules.js.map