@things-factory/organization 10.1.229 → 10.1.231
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/pages/employee/employee-list-page.d.ts +4 -0
- package/dist-client/pages/employee/employee-list-page.js +28 -0
- package/dist-client/pages/employee/employee-list-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +4 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/role-tasks.js +3 -1
- package/dist-server/role-tasks.js.map +1 -1
- package/dist-server/service/certificate/employment-certificate-rules.d.ts +39 -0
- package/dist-server/service/certificate/employment-certificate-rules.js +40 -0
- package/dist-server/service/certificate/employment-certificate-rules.js.map +1 -0
- package/dist-server/service/certificate/employment-certificate-templates.d.ts +2 -0
- package/dist-server/service/certificate/employment-certificate-templates.js +47 -0
- package/dist-server/service/certificate/employment-certificate-templates.js.map +1 -0
- package/dist-server/service/certificate/employment-certificates.d.ts +2 -0
- package/dist-server/service/certificate/employment-certificates.js +66 -0
- package/dist-server/service/certificate/employment-certificates.js.map +1 -0
- package/dist-server/service/employee-profile/employee-profile-document.d.ts +0 -10
- package/dist-server/service/employee-profile/employee-profile-document.js +4 -21
- package/dist-server/service/employee-profile/employee-profile-document.js.map +1 -1
- package/dist-server/service/employee-profile/employee-profile-mutation.js.map +1 -1
- package/dist-server/service/employee-profile/employee-profile-rules.d.ts +12 -0
- package/dist-server/service/employee-profile/employee-profile-rules.js +25 -0
- package/dist-server/service/employee-profile/employee-profile-rules.js.map +1 -1
- package/dist-server/service/employee-profile/employee-profile.d.ts +1 -0
- package/dist-server/service/employee-profile/employee-profile.js +14 -1
- package/dist-server/service/employee-profile/employee-profile.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -5
- package/tests/employee-profile-db.test.ts +14 -0
- package/tests/employee-profile-document.test.ts +10 -1
- package/translations/en.json +11 -1
- package/translations/es.json +11 -1
- package/translations/ja.json +11 -1
- package/translations/ko.json +11 -1
- package/translations/ms.json +11 -1
- package/translations/zh.json +11 -1
package/dist-server/index.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ export * from './service';
|
|
|
2
2
|
export * from './ai-entry/organization-entry-targets';
|
|
3
3
|
export type { EntryTargetDeclaration as OrganizationEntryTarget } from './ai-entry/entry-target-shape';
|
|
4
4
|
import './routes';
|
|
5
|
+
export { CAREER_CERTIFICATE_KIND, EMPLOYMENT_CERTIFICATE_KIND } from './service/certificate/employment-certificates';
|
package/dist-server/index.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EMPLOYMENT_CERTIFICATE_KIND = exports.CAREER_CERTIFICATE_KIND = void 0;
|
|
3
4
|
const tslib_1 = require("tslib");
|
|
4
5
|
tslib_1.__exportStar(require("./service"), exports);
|
|
5
6
|
tslib_1.__exportStar(require("./ai-entry/organization-entry-targets"), exports);
|
|
6
7
|
require("./routes");
|
|
8
|
+
var employment_certificates_1 = require("./service/certificate/employment-certificates");
|
|
9
|
+
Object.defineProperty(exports, "CAREER_CERTIFICATE_KIND", { enumerable: true, get: function () { return employment_certificates_1.CAREER_CERTIFICATE_KIND; } });
|
|
10
|
+
Object.defineProperty(exports, "EMPLOYMENT_CERTIFICATE_KIND", { enumerable: true, get: function () { return employment_certificates_1.EMPLOYMENT_CERTIFICATE_KIND; } });
|
|
7
11
|
const auth_base_1 = require("@things-factory/auth-base");
|
|
8
12
|
const role_tasks_1 = require("./role-tasks");
|
|
9
13
|
for (const task of role_tasks_1.ROLE_TASKS)
|
package/dist-server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;;AAAA,oDAAyB;AACzB,gFAAqD;AAGrD,oBAAiB;AACjB,yFAAoH;AAA3G,kIAAA,uBAAuB,OAAA;AAAE,sIAAA,2BAA2B,OAAA;AAE7D,yDAA4D;AAC5D,6CAAyC;AAEzC,KAAK,MAAM,IAAI,IAAI,uBAAU;IAAE,IAAA,4BAAgB,EAAC,IAAI,CAAC,CAAA","sourcesContent":["export * from './service'\nexport * from './ai-entry/organization-entry-targets'\nexport type { EntryTargetDeclaration as OrganizationEntryTarget } from './ai-entry/entry-target-shape'\n\nimport './routes'\nexport { CAREER_CERTIFICATE_KIND, EMPLOYMENT_CERTIFICATE_KIND } from './service/certificate/employment-certificates'\n\nimport { registerRoleTask } from '@things-factory/auth-base'\nimport { ROLE_TASKS } from './role-tasks'\n\nfor (const task of ROLE_TASKS) registerRoleTask(task)\n"]}
|
|
@@ -17,6 +17,8 @@ exports.ROLE_TASKS = [
|
|
|
17
17
|
{ id: 'organization.pay-accounts', grants: [{ category: 'employee-private', axes: ['query', 'mutation'] }] },
|
|
18
18
|
/* One's own profile, written by the employee; every active employee's, read to attach to a quotation (ADR-0169 decision 3). */
|
|
19
19
|
{ id: 'organization.own-profile', grants: [{ category: 'employee-self', axes: ['query', 'mutation'] }] },
|
|
20
|
-
{ id: 'organization.profiles-reading', grants: [{ category: 'employee-profile', axes: ['query'] }] }
|
|
20
|
+
{ id: 'organization.profiles-reading', grants: [{ category: 'employee-profile', axes: ['query'] }] },
|
|
21
|
+
/* 재직증명서 · 경력증명서 of others, and every 경력증명서 - one's own 재직증명서 is `employee-self`'s (ADR-0176 decision 5). */
|
|
22
|
+
{ id: 'organization.certificates-issuing', grants: [{ category: 'employee-certificate', axes: ['query', 'mutation'] }] }
|
|
21
23
|
];
|
|
22
24
|
//# sourceMappingURL=role-tasks.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role-tasks.js","sourceRoot":"","sources":["../server/role-tasks.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAIU,QAAA,UAAU,GAAe;IACpC,EAAE,EAAE,EAAE,6BAA6B,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;IACvI,6HAA6H;IAC7H,EAAE,EAAE,EAAE,kCAAkC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;IACjG,EAAE,EAAE,EAAE,6BAA6B,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE;IAC/J,EAAE,EAAE,EAAE,2BAA2B,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE;IAC5G,+HAA+H;IAC/H,EAAE,EAAE,EAAE,0BAA0B,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE;IACxG,EAAE,EAAE,EAAE,+BAA+B,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;
|
|
1
|
+
{"version":3,"file":"role-tasks.js","sourceRoot":"","sources":["../server/role-tasks.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAIU,QAAA,UAAU,GAAe;IACpC,EAAE,EAAE,EAAE,6BAA6B,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;IACvI,6HAA6H;IAC7H,EAAE,EAAE,EAAE,kCAAkC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;IACjG,EAAE,EAAE,EAAE,6BAA6B,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE;IAC/J,EAAE,EAAE,EAAE,2BAA2B,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE;IAC5G,+HAA+H;IAC/H,EAAE,EAAE,EAAE,0BAA0B,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE;IACxG,EAAE,EAAE,EAAE,+BAA+B,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;IACpG,4GAA4G;IAC5G,EAAE,EAAE,EAAE,mCAAmC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,sBAAsB,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,EAAE;CACzH,CAAA","sourcesContent":["/*\n * The work this module names, for the seats applications build (ADR-0139 decision 11).\n *\n * A task holds whole privileges: each opens everything its doors open, and the words under `task.<id>` in this\n * module's translations say what that is. Seats are put together by applications (`RoleTemplate.tasks`).\n *\n * Data only - `server/index.ts` registers it - so an application's tests read it without starting the framework.\n */\n\nimport type { RoleTask } from '@things-factory/auth-base'\n\nexport const ROLE_TASKS: RoleTask[] = [\n { id: 'organization.people-reading', grants: [{ category: 'employee', axes: ['query'] }, { category: 'department', axes: ['query'] }] },\n /* The departments alone, for every seat that picks or shows one; `manager` and `members` still ask `employee` (ADR-0139). */\n { id: 'organization.departments-reading', grants: [{ category: 'department', axes: ['query'] }] },\n { id: 'organization.people-keeping', grants: [{ category: 'employee', axes: ['query', 'mutation'] }, { category: 'department', axes: ['query', 'mutation'] }] },\n { id: 'organization.pay-accounts', grants: [{ category: 'employee-private', axes: ['query', 'mutation'] }] },\n /* One's own profile, written by the employee; every active employee's, read to attach to a quotation (ADR-0169 decision 3). */\n { id: 'organization.own-profile', grants: [{ category: 'employee-self', axes: ['query', 'mutation'] }] },\n { id: 'organization.profiles-reading', grants: [{ category: 'employee-profile', axes: ['query'] }] },\n /* 재직증명서 · 경력증명서 of others, and every 경력증명서 - one's own 재직증명서 is `employee-self`'s (ADR-0176 decision 5). */\n { id: 'organization.certificates-issuing', grants: [{ category: 'employee-certificate', axes: ['query', 'mutation'] }] }\n]\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Where the caller stands to an employee: the record is theirs, and what they hold. */
|
|
2
|
+
export interface CertificateStanding {
|
|
3
|
+
own: boolean;
|
|
4
|
+
/** `employee-self:mutation` - every employee's, for their own records. */
|
|
5
|
+
self: boolean;
|
|
6
|
+
/** `employee-certificate:mutation` - `organization.certificates-issuing`. */
|
|
7
|
+
issuing: boolean;
|
|
8
|
+
/** `employee-certificate:query`. */
|
|
9
|
+
reading: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** One's own 재직증명서 is one's own to issue; anyone else's, and every 경력증명서, is the issuing task's (Q3 · Q4). */
|
|
12
|
+
export declare function mayIssueCertificate(kind: string, standing: CertificateStanding): boolean;
|
|
13
|
+
/** The person it is about reads it; so does whoever reads the certificates. */
|
|
14
|
+
export declare function mayReadCertificate(standing: CertificateStanding): boolean;
|
|
15
|
+
/** What a certificate prints of the employee - no birth date or address (Q2). */
|
|
16
|
+
export interface CertificateFacts {
|
|
17
|
+
name: string;
|
|
18
|
+
department: string;
|
|
19
|
+
jobPosition: string;
|
|
20
|
+
jobResponsibility: string;
|
|
21
|
+
hiredOn: string;
|
|
22
|
+
/** The last day - the day of issue (현재) for a 재직증명서, the retirement day for one who left. */
|
|
23
|
+
until: string;
|
|
24
|
+
current: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The facts of an employee as of `today`. A 재직증명서 is for one still employed; a 경력증명서 is for anyone with a hire
|
|
28
|
+
* day - one who left ends on the retirement day, one still employed on today. Refuses what the record cannot certify.
|
|
29
|
+
*/
|
|
30
|
+
export declare function certificateFacts(kind: string, employee: {
|
|
31
|
+
name?: string | null;
|
|
32
|
+
department?: {
|
|
33
|
+
name?: string | null;
|
|
34
|
+
} | null;
|
|
35
|
+
jobPosition?: string | null;
|
|
36
|
+
jobResponsibility?: string | null;
|
|
37
|
+
hiredOn?: any;
|
|
38
|
+
retiredOn?: any;
|
|
39
|
+
}, today: string): CertificateFacts;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mayIssueCertificate = mayIssueCertificate;
|
|
4
|
+
exports.mayReadCertificate = mayReadCertificate;
|
|
5
|
+
exports.certificateFacts = certificateFacts;
|
|
6
|
+
const shell_1 = require("@things-factory/shell");
|
|
7
|
+
/** One's own 재직증명서 is one's own to issue; anyone else's, and every 경력증명서, is the issuing task's (Q3 · Q4). */
|
|
8
|
+
function mayIssueCertificate(kind, standing) {
|
|
9
|
+
if (standing.issuing)
|
|
10
|
+
return true;
|
|
11
|
+
return kind === 'organization.employment-certificate' && standing.own && standing.self;
|
|
12
|
+
}
|
|
13
|
+
/** The person it is about reads it; so does whoever reads the certificates. */
|
|
14
|
+
function mayReadCertificate(standing) {
|
|
15
|
+
return standing.reading || standing.issuing || standing.own;
|
|
16
|
+
}
|
|
17
|
+
const day = (value) => (typeof value === 'string' ? value.slice(0, 10) : value instanceof Date ? value.toISOString().slice(0, 10) : '');
|
|
18
|
+
/**
|
|
19
|
+
* The facts of an employee as of `today`. A 재직증명서 is for one still employed; a 경력증명서 is for anyone with a hire
|
|
20
|
+
* day - one who left ends on the retirement day, one still employed on today. Refuses what the record cannot certify.
|
|
21
|
+
*/
|
|
22
|
+
function certificateFacts(kind, employee, today) {
|
|
23
|
+
const hiredOn = day(employee.hiredOn);
|
|
24
|
+
const retiredOn = day(employee.retiredOn);
|
|
25
|
+
if (!hiredOn)
|
|
26
|
+
throw new shell_1.Refusal('certificate-hired-on-missing', {}, 'record the hire day first');
|
|
27
|
+
const current = !retiredOn || retiredOn >= today;
|
|
28
|
+
if (kind === 'organization.employment-certificate' && !current)
|
|
29
|
+
throw new shell_1.Refusal('certificate-not-employed', {}, 'a 재직증명서 is for one still employed - issue a 경력증명서');
|
|
30
|
+
return {
|
|
31
|
+
name: employee.name ?? '',
|
|
32
|
+
department: employee.department?.name ?? '',
|
|
33
|
+
jobPosition: employee.jobPosition ?? '',
|
|
34
|
+
jobResponsibility: employee.jobResponsibility ?? '',
|
|
35
|
+
hiredOn,
|
|
36
|
+
until: current ? today : retiredOn,
|
|
37
|
+
current
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=employment-certificate-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"employment-certificate-rules.js","sourceRoot":"","sources":["../../../server/service/certificate/employment-certificate-rules.ts"],"names":[],"mappings":";;AAmBA,kDAGC;AAGD,gDAEC;AAoBD,4CAeC;AA9DD,iDAA+C;AAkB/C,8GAA8G;AAC9G,SAAgB,mBAAmB,CAAC,IAAY,EAAE,QAA6B;IAC7E,IAAI,QAAQ,CAAC,OAAO;QAAE,OAAO,IAAI,CAAA;IACjC,OAAO,IAAI,KAAK,qCAAqC,IAAI,QAAQ,CAAC,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAA;AACxF,CAAC;AAED,+EAA+E;AAC/E,SAAgB,kBAAkB,CAAC,QAA6B;IAC9D,OAAO,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAA;AAC7D,CAAC;AAcD,MAAM,GAAG,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;AAEhJ;;;GAGG;AACH,SAAgB,gBAAgB,CAAC,IAAY,EAAE,QAAgL,EAAE,KAAa;IAC5O,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;IACrC,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;IACzC,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,eAAO,CAAC,8BAA8B,EAAE,EAAE,EAAE,2BAA2B,CAAC,CAAA;IAChG,MAAM,OAAO,GAAG,CAAC,SAAS,IAAI,SAAS,IAAI,KAAK,CAAA;IAChD,IAAI,IAAI,KAAK,qCAAqC,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,eAAO,CAAC,0BAA0B,EAAE,EAAE,EAAE,mDAAmD,CAAC,CAAA;IACtK,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,EAAE;QACzB,UAAU,EAAE,QAAQ,CAAC,UAAU,EAAE,IAAI,IAAI,EAAE;QAC3C,WAAW,EAAE,QAAQ,CAAC,WAAW,IAAI,EAAE;QACvC,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB,IAAI,EAAE;QACnD,OAAO;QACP,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;QAClC,OAAO;KACR,CAAA;AACH,CAAC","sourcesContent":["import { Refusal } from '@things-factory/shell'\n\n/*\n * Who may issue and read a 재직증명서 · 경력증명서, and what it says (ADR-0176 decision 5) - pure, so it is tested\n * without a server.\n */\n\n/** Where the caller stands to an employee: the record is theirs, and what they hold. */\nexport interface CertificateStanding {\n own: boolean\n /** `employee-self:mutation` - every employee's, for their own records. */\n self: boolean\n /** `employee-certificate:mutation` - `organization.certificates-issuing`. */\n issuing: boolean\n /** `employee-certificate:query`. */\n reading: boolean\n}\n\n/** One's own 재직증명서 is one's own to issue; anyone else's, and every 경력증명서, is the issuing task's (Q3 · Q4). */\nexport function mayIssueCertificate(kind: string, standing: CertificateStanding): boolean {\n if (standing.issuing) return true\n return kind === 'organization.employment-certificate' && standing.own && standing.self\n}\n\n/** The person it is about reads it; so does whoever reads the certificates. */\nexport function mayReadCertificate(standing: CertificateStanding): boolean {\n return standing.reading || standing.issuing || standing.own\n}\n\n/** What a certificate prints of the employee - no birth date or address (Q2). */\nexport interface CertificateFacts {\n name: string\n department: string\n jobPosition: string\n jobResponsibility: string\n hiredOn: string\n /** The last day - the day of issue (현재) for a 재직증명서, the retirement day for one who left. */\n until: string\n current: boolean\n}\n\nconst day = (value: unknown) => (typeof value === 'string' ? value.slice(0, 10) : value instanceof Date ? value.toISOString().slice(0, 10) : '')\n\n/**\n * The facts of an employee as of `today`. A 재직증명서 is for one still employed; a 경력증명서 is for anyone with a hire\n * day - one who left ends on the retirement day, one still employed on today. Refuses what the record cannot certify.\n */\nexport function certificateFacts(kind: string, employee: { name?: string | null; department?: { name?: string | null } | null; jobPosition?: string | null; jobResponsibility?: string | null; hiredOn?: any; retiredOn?: any }, today: string): CertificateFacts {\n const hiredOn = day(employee.hiredOn)\n const retiredOn = day(employee.retiredOn)\n if (!hiredOn) throw new Refusal('certificate-hired-on-missing', {}, 'record the hire day first')\n const current = !retiredOn || retiredOn >= today\n if (kind === 'organization.employment-certificate' && !current) throw new Refusal('certificate-not-employed', {}, 'a 재직증명서 is for one still employed - issue a 경력증명서')\n return {\n name: employee.name ?? '',\n department: employee.department?.name ?? '',\n jobPosition: employee.jobPosition ?? '',\n jobResponsibility: employee.jobResponsibility ?? '',\n hiredOn,\n until: current ? today : retiredOn,\n current\n }\n}\n"]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* The templates in code of 재직증명서 · 경력증명서 (ADR-0176 decision 5) - a domain's own `PDFTemplate` of the kind prints
|
|
4
|
+
* in their place. They read `employee` (the facts), `issue` (number, day, purpose, recipient) and `company` (「우리 회사」
|
|
5
|
+
* with its seal as a data: URL).
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.CAREER_TEMPLATE = exports.EMPLOYMENT_TEMPLATE = void 0;
|
|
9
|
+
const page = (title, rows, statement) => `<!doctype html><html lang="ko"><head><meta charset="utf-8"><style>
|
|
10
|
+
@page { size: A4; margin: 0 }
|
|
11
|
+
body { margin: 0; font-family: "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", sans-serif; color: #222 }
|
|
12
|
+
.page { position: relative; box-sizing: border-box; width: 210mm; height: 297mm; padding: 30mm 24mm }
|
|
13
|
+
.number { position: absolute; top: 16mm; left: 24mm; font-size: 9.5pt; color: #666 }
|
|
14
|
+
h1 { margin: 6mm 0 16mm; text-align: center; font-size: 26pt; letter-spacing: 12pt }
|
|
15
|
+
table { width: 100%; border-collapse: collapse; font-size: 11.5pt }
|
|
16
|
+
th, td { border: 1px solid #444; padding: 3.2mm 4mm; text-align: left; vertical-align: middle }
|
|
17
|
+
th { width: 30mm; background: #f2f2f2; font-weight: 600; text-align: center }
|
|
18
|
+
.statement { margin: 22mm 0 0; text-align: center; font-size: 13pt; line-height: 2 }
|
|
19
|
+
.date { margin-top: 18mm; text-align: center; font-size: 13pt; letter-spacing: 1pt }
|
|
20
|
+
.issuer { position: relative; margin: 20mm auto 0; width: max-content; font-size: 14pt; line-height: 2; text-align: left; padding-right: 22mm }
|
|
21
|
+
.issuer img { position: absolute; right: 0; top: 2mm; width: 22mm; opacity: .9 }
|
|
22
|
+
</style></head><body><div class="page">
|
|
23
|
+
<div class="number">발급번호 {{issue.number}}</div>
|
|
24
|
+
<h1>${title}</h1>
|
|
25
|
+
<table>
|
|
26
|
+
${rows}
|
|
27
|
+
<tr><th>용도</th><td colspan="3">{{issue.purpose}}</td></tr>
|
|
28
|
+
<tr><th>제출처</th><td colspan="3">{{issue.submittedTo}}</td></tr>
|
|
29
|
+
</table>
|
|
30
|
+
<p class="statement">${statement}</p>
|
|
31
|
+
<div class="date">{{issue.issuedOn}}</div>
|
|
32
|
+
<div class="issuer">
|
|
33
|
+
<div>상호 : {{company.name}}</div>
|
|
34
|
+
{{#if company.address}}<div>주소 : {{company.address}}</div>{{/if}}
|
|
35
|
+
{{#if company.brn}}<div>사업자등록번호 : {{company.brn}}</div>{{/if}}
|
|
36
|
+
<div>대표이사 : {{company.representative}}</div>
|
|
37
|
+
{{#if company.seal}}<img src="{{company.seal}}" alt="">{{/if}}
|
|
38
|
+
</div>
|
|
39
|
+
</div></body></html>`;
|
|
40
|
+
exports.EMPLOYMENT_TEMPLATE = page('재직증명서', `<tr><th>성명</th><td colspan="3">{{employee.name}}</td></tr>
|
|
41
|
+
<tr><th>소속</th><td>{{employee.department}}</td><th>직위</th><td>{{employee.jobPosition}}</td></tr>
|
|
42
|
+
<tr><th>재직 기간</th><td colspan="3">{{employee.hiredOn}} ~ {{employee.until}} (현재)</td></tr>`, '위 사람은 현재 당사에 재직하고 있음을 증명합니다.');
|
|
43
|
+
exports.CAREER_TEMPLATE = page('경력증명서', `<tr><th>성명</th><td colspan="3">{{employee.name}}</td></tr>
|
|
44
|
+
<tr><th>소속</th><td>{{employee.department}}</td><th>직위</th><td>{{employee.jobPosition}}</td></tr>
|
|
45
|
+
<tr><th>담당 업무</th><td colspan="3">{{employee.jobResponsibility}}</td></tr>
|
|
46
|
+
<tr><th>근무 기간</th><td colspan="3">{{employee.hiredOn}} ~ {{employee.until}}{{#if employee.current}} (현재){{/if}}</td></tr>`, '위 사람은 위와 같이 당사에 근무하였음을 증명합니다.');
|
|
47
|
+
//# sourceMappingURL=employment-certificate-templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"employment-certificate-templates.js","sourceRoot":"","sources":["../../../server/service/certificate/employment-certificate-templates.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,IAAY,EAAE,SAAiB,EAAE,EAAE,CAAC;;;;;;;;;;;;;;;MAe3D,KAAK;;EAET,IAAI;;;;uBAIiB,SAAS;;;;;;;;;qBASX,CAAA;AAER,QAAA,mBAAmB,GAAG,IAAI,CACrC,OAAO,EACP;;2FAEyF,EACzF,8BAA8B,CAC/B,CAAA;AAEY,QAAA,eAAe,GAAG,IAAI,CACjC,OAAO,EACP;;;0HAGwH,EACxH,+BAA+B,CAChC,CAAA","sourcesContent":["/*\n * The templates in code of 재직증명서 · 경력증명서 (ADR-0176 decision 5) - a domain's own `PDFTemplate` of the kind prints\n * in their place. They read `employee` (the facts), `issue` (number, day, purpose, recipient) and `company` (「우리 회사」\n * with its seal as a data: URL).\n */\n\nconst page = (title: string, rows: string, statement: string) => `<!doctype html><html lang=\"ko\"><head><meta charset=\"utf-8\"><style>\n@page { size: A4; margin: 0 }\nbody { margin: 0; font-family: \"Apple SD Gothic Neo\", \"Malgun Gothic\", \"Noto Sans KR\", sans-serif; color: #222 }\n.page { position: relative; box-sizing: border-box; width: 210mm; height: 297mm; padding: 30mm 24mm }\n.number { position: absolute; top: 16mm; left: 24mm; font-size: 9.5pt; color: #666 }\nh1 { margin: 6mm 0 16mm; text-align: center; font-size: 26pt; letter-spacing: 12pt }\ntable { width: 100%; border-collapse: collapse; font-size: 11.5pt }\nth, td { border: 1px solid #444; padding: 3.2mm 4mm; text-align: left; vertical-align: middle }\nth { width: 30mm; background: #f2f2f2; font-weight: 600; text-align: center }\n.statement { margin: 22mm 0 0; text-align: center; font-size: 13pt; line-height: 2 }\n.date { margin-top: 18mm; text-align: center; font-size: 13pt; letter-spacing: 1pt }\n.issuer { position: relative; margin: 20mm auto 0; width: max-content; font-size: 14pt; line-height: 2; text-align: left; padding-right: 22mm }\n.issuer img { position: absolute; right: 0; top: 2mm; width: 22mm; opacity: .9 }\n</style></head><body><div class=\"page\">\n<div class=\"number\">발급번호 {{issue.number}}</div>\n<h1>${title}</h1>\n<table>\n${rows}\n<tr><th>용도</th><td colspan=\"3\">{{issue.purpose}}</td></tr>\n<tr><th>제출처</th><td colspan=\"3\">{{issue.submittedTo}}</td></tr>\n</table>\n<p class=\"statement\">${statement}</p>\n<div class=\"date\">{{issue.issuedOn}}</div>\n<div class=\"issuer\">\n<div>상호 : {{company.name}}</div>\n{{#if company.address}}<div>주소 : {{company.address}}</div>{{/if}}\n{{#if company.brn}}<div>사업자등록번호 : {{company.brn}}</div>{{/if}}\n<div>대표이사 : {{company.representative}}</div>\n{{#if company.seal}}<img src=\"{{company.seal}}\" alt=\"\">{{/if}}\n</div>\n</div></body></html>`\n\nexport const EMPLOYMENT_TEMPLATE = page(\n '재직증명서',\n `<tr><th>성명</th><td colspan=\"3\">{{employee.name}}</td></tr>\n<tr><th>소속</th><td>{{employee.department}}</td><th>직위</th><td>{{employee.jobPosition}}</td></tr>\n<tr><th>재직 기간</th><td colspan=\"3\">{{employee.hiredOn}} ~ {{employee.until}} (현재)</td></tr>`,\n '위 사람은 현재 당사에 재직하고 있음을 증명합니다.'\n)\n\nexport const CAREER_TEMPLATE = page(\n '경력증명서',\n `<tr><th>성명</th><td colspan=\"3\">{{employee.name}}</td></tr>\n<tr><th>소속</th><td>{{employee.department}}</td><th>직위</th><td>{{employee.jobPosition}}</td></tr>\n<tr><th>담당 업무</th><td colspan=\"3\">{{employee.jobResponsibility}}</td></tr>\n<tr><th>근무 기간</th><td colspan=\"3\">{{employee.hiredOn}} ~ {{employee.until}}{{#if employee.current}} (현재){{/if}}</td></tr>`,\n '위 사람은 위와 같이 당사에 근무하였음을 증명합니다.'\n)\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CAREER_CERTIFICATE_KIND = exports.EMPLOYMENT_CERTIFICATE_KIND = void 0;
|
|
4
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
5
|
+
const pdf_1 = require("@things-factory/pdf");
|
|
6
|
+
const shell_1 = require("@things-factory/shell");
|
|
7
|
+
const employee_1 = require("../employee/employee");
|
|
8
|
+
const my_employee_1 = require("../employee/my-employee");
|
|
9
|
+
const employment_certificate_rules_1 = require("./employment-certificate-rules");
|
|
10
|
+
const employment_certificate_templates_1 = require("./employment-certificate-templates");
|
|
11
|
+
/*
|
|
12
|
+
* 재직증명서 · 경력증명서 - issued documents of the employment, which organization owns (ADR-0176 decision 5). An employee
|
|
13
|
+
* issues their own 재직증명서 (`employee-self`); everyone else's, and every 경력증명서, under `employee-certificate`
|
|
14
|
+
* (`organization.certificates-issuing`). No birth date or address is printed (Q2).
|
|
15
|
+
*/
|
|
16
|
+
exports.EMPLOYMENT_CERTIFICATE_KIND = 'organization.employment-certificate';
|
|
17
|
+
exports.CAREER_CERTIFICATE_KIND = 'organization.career-certificate';
|
|
18
|
+
/** What the caller holds, asked as the doors ask it. */
|
|
19
|
+
async function holds(context, category, privilege) {
|
|
20
|
+
const { domain, user, unsafeIP, prohibitedPrivileges } = context?.state ?? {};
|
|
21
|
+
if (!domain || !user)
|
|
22
|
+
return false;
|
|
23
|
+
return !!(await (0, auth_base_1.checkPermission)({ category, privilege, owner: true }, user, domain, unsafeIP, prohibitedPrivileges));
|
|
24
|
+
}
|
|
25
|
+
/** Who is asking about whom: theirs, and what they hold. */
|
|
26
|
+
async function standingOf(context, employeeId) {
|
|
27
|
+
return {
|
|
28
|
+
own: !!employeeId && (await (0, my_employee_1.myEmployeeIds)(context)).includes(employeeId),
|
|
29
|
+
self: await holds(context, 'employee-self', 'mutation'),
|
|
30
|
+
issuing: await holds(context, 'employee-certificate', 'mutation'),
|
|
31
|
+
reading: await holds(context, 'employee-certificate', 'query')
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async function employeeOf(context, id) {
|
|
35
|
+
const { domain, tx } = context.state;
|
|
36
|
+
const employee = await (0, shell_1.getRepository)(employee_1.Employee, tx).findOne({ where: { id, domain: { id: domain.id } }, relations: ['department'] });
|
|
37
|
+
if (!employee)
|
|
38
|
+
throw new shell_1.Refusal('employee-not-found', {}, 'the employee was not found');
|
|
39
|
+
return employee;
|
|
40
|
+
}
|
|
41
|
+
for (const kind of [exports.EMPLOYMENT_CERTIFICATE_KIND, exports.CAREER_CERTIFICATE_KIND]) {
|
|
42
|
+
const career = kind === exports.CAREER_CERTIFICATE_KIND;
|
|
43
|
+
(0, pdf_1.registerIssuedDocumentKind)({
|
|
44
|
+
kind,
|
|
45
|
+
prefix: career ? '경력' : '재직',
|
|
46
|
+
refType: 'Employee',
|
|
47
|
+
template: { content_template: career ? employment_certificate_templates_1.CAREER_TEMPLATE : employment_certificate_templates_1.EMPLOYMENT_TEMPLATE, page_size: 'A4' },
|
|
48
|
+
async dataOf(context, refId, input) {
|
|
49
|
+
const employee = await employeeOf(context, refId);
|
|
50
|
+
const facts = (0, employment_certificate_rules_1.certificateFacts)(kind, employee, (0, shell_1.calendarDay)(context.state.domain.timezone));
|
|
51
|
+
return { subjectName: employee.name ?? '', data: { employee: facts } };
|
|
52
|
+
},
|
|
53
|
+
/* One's own employee record - issued from 내 정보 › 내 증명서 (the issuance ledger). */
|
|
54
|
+
async ownSubject(context) {
|
|
55
|
+
const mine = await (0, my_employee_1.myEmployeeIds)(context);
|
|
56
|
+
return mine.length === 1 ? mine[0] : null;
|
|
57
|
+
},
|
|
58
|
+
async mayIssue(context, refId) {
|
|
59
|
+
return (0, employment_certificate_rules_1.mayIssueCertificate)(kind, await standingOf(context, refId));
|
|
60
|
+
},
|
|
61
|
+
async mayRead(context, row) {
|
|
62
|
+
return (0, employment_certificate_rules_1.mayReadCertificate)(await standingOf(context, row.refId));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=employment-certificates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"employment-certificates.js","sourceRoot":"","sources":["../../../server/service/certificate/employment-certificates.ts"],"names":[],"mappings":"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","sourcesContent":["import { checkPermission } from '@things-factory/auth-base'\nimport { registerIssuedDocumentKind } from '@things-factory/pdf'\nimport { calendarDay, getRepository, Refusal } from '@things-factory/shell'\n\nimport { Employee } from '../employee/employee'\nimport { myEmployeeIds } from '../employee/my-employee'\nimport { certificateFacts, mayIssueCertificate, mayReadCertificate } from './employment-certificate-rules'\nimport { CAREER_TEMPLATE, EMPLOYMENT_TEMPLATE } from './employment-certificate-templates'\n\n/*\n * 재직증명서 · 경력증명서 - issued documents of the employment, which organization owns (ADR-0176 decision 5). An employee\n * issues their own 재직증명서 (`employee-self`); everyone else's, and every 경력증명서, under `employee-certificate`\n * (`organization.certificates-issuing`). No birth date or address is printed (Q2).\n */\nexport const EMPLOYMENT_CERTIFICATE_KIND = 'organization.employment-certificate'\nexport const CAREER_CERTIFICATE_KIND = 'organization.career-certificate'\n\n/** What the caller holds, asked as the doors ask it. */\nasync function holds(context: any, category: string, privilege: 'query' | 'mutation'): Promise<boolean> {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context?.state ?? {}\n if (!domain || !user) return false\n return !!(await checkPermission({ category, privilege, owner: true } as any, user, domain, unsafeIP, prohibitedPrivileges))\n}\n\n/** Who is asking about whom: theirs, and what they hold. */\nasync function standingOf(context: any, employeeId: string | null | undefined) {\n return {\n own: !!employeeId && (await myEmployeeIds(context)).includes(employeeId),\n self: await holds(context, 'employee-self', 'mutation'),\n issuing: await holds(context, 'employee-certificate', 'mutation'),\n reading: await holds(context, 'employee-certificate', 'query')\n }\n}\n\nasync function employeeOf(context: any, id: string): Promise<Employee> {\n const { domain, tx } = context.state\n const employee = await getRepository(Employee, tx).findOne({ where: { id, domain: { id: domain.id } }, relations: ['department'] })\n if (!employee) throw new Refusal('employee-not-found', {}, 'the employee was not found')\n return employee\n}\n\nfor (const kind of [EMPLOYMENT_CERTIFICATE_KIND, CAREER_CERTIFICATE_KIND] as const) {\n const career = kind === CAREER_CERTIFICATE_KIND\n registerIssuedDocumentKind({\n kind,\n prefix: career ? '경력' : '재직',\n refType: 'Employee',\n template: { content_template: career ? CAREER_TEMPLATE : EMPLOYMENT_TEMPLATE, page_size: 'A4' },\n async dataOf(context, refId, input) {\n const employee = await employeeOf(context, refId)\n const facts = certificateFacts(kind, employee, calendarDay(context.state.domain.timezone))\n return { subjectName: employee.name ?? '', data: { employee: facts } }\n },\n /* One's own employee record - issued from 내 정보 › 내 증명서 (the issuance ledger). */\n async ownSubject(context) {\n const mine = await myEmployeeIds(context)\n return mine.length === 1 ? mine[0] : null\n },\n async mayIssue(context, refId) {\n return mayIssueCertificate(kind, await standingOf(context, refId))\n },\n async mayRead(context, row) {\n return mayReadCertificate(await standingOf(context, row.refId))\n }\n })\n}\n"]}
|
|
@@ -70,16 +70,6 @@ export declare const ROSTER_WIDTHS: number[];
|
|
|
70
70
|
export declare function dotted(day?: string | null): string;
|
|
71
71
|
/** A period as one cell - an open end is still going on. */
|
|
72
72
|
export declare function periodOf(startedOn: string | undefined, endedOn: string | undefined, ongoing: string): string;
|
|
73
|
-
/**
|
|
74
|
-
* Newest first, as a résumé reads. A period is ordered by its end - one still going (no end) on top - and two that ended
|
|
75
|
-
* together by the later start; a day (a certificate's) by the day. A row with no date keeps its place, after the dated
|
|
76
|
-
* ones. Only the document is ordered; the profile keeps the rows as they were written.
|
|
77
|
-
*/
|
|
78
|
-
export declare function newestFirst<R extends {
|
|
79
|
-
startedOn?: string;
|
|
80
|
-
endedOn?: string;
|
|
81
|
-
acquiredOn?: string;
|
|
82
|
-
}>(rows: R[] | null | undefined): R[];
|
|
83
73
|
/**
|
|
84
74
|
* The months a person has worked: the careers before this company and the time here since the hire day, counted
|
|
85
75
|
* once where they overlap. A career with no end runs to `today`; one with no start is left out.
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PROFILE_SCREEN_CSS = exports.PROFILE_CSS = exports.PALETTE = exports.ROSTER_WIDTHS = exports.COLUMN_WIDTHS = void 0;
|
|
4
4
|
exports.dotted = dotted;
|
|
5
5
|
exports.periodOf = periodOf;
|
|
6
|
-
exports.newestFirst = newestFirst;
|
|
7
6
|
exports.careerMonths = careerMonths;
|
|
8
7
|
exports.careerLengthOf = careerLengthOf;
|
|
9
8
|
exports.taglineOf = taglineOf;
|
|
@@ -37,22 +36,6 @@ function periodOf(startedOn, endedOn, ongoing) {
|
|
|
37
36
|
return '';
|
|
38
37
|
return `${dotted(startedOn)} ~ ${endedOn ? dotted(endedOn) : ongoing}`.trim();
|
|
39
38
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Newest first, as a résumé reads. A period is ordered by its end - one still going (no end) on top - and two that ended
|
|
42
|
-
* together by the later start; a day (a certificate's) by the day. A row with no date keeps its place, after the dated
|
|
43
|
-
* ones. Only the document is ordered; the profile keeps the rows as they were written.
|
|
44
|
-
*/
|
|
45
|
-
function newestFirst(rows) {
|
|
46
|
-
const ONGOING = '9999-99';
|
|
47
|
-
const endOf = (row) => (row.acquiredOn ?? row.endedOn ?? (row.startedOn ? ONGOING : '')).trim();
|
|
48
|
-
const startOf = (row) => (row.acquiredOn ?? row.startedOn ?? '').trim();
|
|
49
|
-
return [...(rows ?? [])].sort((a, b) => {
|
|
50
|
-
const [endA, endB] = [endOf(a), endOf(b)];
|
|
51
|
-
if (!endA || !endB)
|
|
52
|
-
return Number(!endA) - Number(!endB);
|
|
53
|
-
return endB.localeCompare(endA) || startOf(b).localeCompare(startOf(a));
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
39
|
const monthIndex = (day) => {
|
|
57
40
|
const match = /^(\d{4})-(\d{2})/.exec((day ?? '').trim());
|
|
58
41
|
return match ? Number(match[1]) * 12 + Number(match[2]) - 1 : null;
|
|
@@ -149,14 +132,14 @@ function profileSheetLayout(person, words, company, today = new Date()) {
|
|
|
149
132
|
{ col: 2, span: 5, value: (profile.skills ?? []).join(', '), role: 'skills' }
|
|
150
133
|
]
|
|
151
134
|
});
|
|
152
|
-
table(words.projects, [[1, 1, words.period], [2, 1, words.project], [3, 1, words.client], [4, 1, words.role], [5, 1, words.duties], [6, 1, words.technologies]], newestFirst(profile.projects), row => [periodOf(row.startedOn, row.endedOn, words.ongoing), row.name, row.client, row.role, row.duties, row.technologies]);
|
|
153
|
-
table(words.careers, [[1, 1, words.period], [2, 1, words.company], [3, 1, words.department], [4, 1, words.position], [5, 2, words.duties]], newestFirst(profile.careers), row => [periodOf(row.startedOn, row.endedOn, words.ongoing), row.company, row.department, row.position, row.duties]);
|
|
154
|
-
table(words.certificates, [[1, 1, words.acquiredOn], [2, 3, words.certificate], [5, 2, words.issuer]], newestFirst(profile.certificates), row => [
|
|
135
|
+
table(words.projects, [[1, 1, words.period], [2, 1, words.project], [3, 1, words.client], [4, 1, words.role], [5, 1, words.duties], [6, 1, words.technologies]], (0, employee_profile_rules_1.newestFirst)(profile.projects), row => [periodOf(row.startedOn, row.endedOn, words.ongoing), row.name, row.client, row.role, row.duties, row.technologies]);
|
|
136
|
+
table(words.careers, [[1, 1, words.period], [2, 1, words.company], [3, 1, words.department], [4, 1, words.position], [5, 2, words.duties]], (0, employee_profile_rules_1.newestFirst)(profile.careers), row => [periodOf(row.startedOn, row.endedOn, words.ongoing), row.company, row.department, row.position, row.duties]);
|
|
137
|
+
table(words.certificates, [[1, 1, words.acquiredOn], [2, 3, words.certificate], [5, 2, words.issuer]], (0, employee_profile_rules_1.newestFirst)(profile.certificates), row => [
|
|
155
138
|
dotted(row.acquiredOn),
|
|
156
139
|
row.name,
|
|
157
140
|
row.issuer
|
|
158
141
|
]);
|
|
159
|
-
table(words.educations, [[1, 1, words.period], [2, 2, words.school], [4, 2, words.major], [6, 1, words.degree]], newestFirst(profile.educations), row => [
|
|
142
|
+
table(words.educations, [[1, 1, words.period], [2, 2, words.school], [4, 2, words.major], [6, 1, words.degree]], (0, employee_profile_rules_1.newestFirst)(profile.educations), row => [
|
|
160
143
|
periodOf(row.startedOn, row.endedOn, words.ongoing),
|
|
161
144
|
row.school,
|
|
162
145
|
row.major,
|