@things-factory/organization 10.1.227 → 10.1.229
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/component/department-selector.js +0 -7
- package/dist-client/component/department-selector.js.map +1 -1
- package/dist-client/pages/department/department-list-page.js +1 -1
- package/dist-client/pages/department/department-list-page.js.map +1 -1
- package/dist-client/pages/employee-profile/employee-profile-list-page.js +1 -0
- package/dist-client/pages/employee-profile/employee-profile-list-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/role-tasks.js +2 -0
- package/dist-server/role-tasks.js.map +1 -1
- package/dist-server/service/department/department-query.d.ts +1 -0
- package/dist-server/service/department/department-query.js +26 -3
- package/dist-server/service/department/department-query.js.map +1 -1
- package/dist-server/service/department/department.js +3 -1
- package/dist-server/service/department/department.js.map +1 -1
- package/dist-server/service/employee-profile/employee-profile-document.d.ts +20 -4
- package/dist-server/service/employee-profile/employee-profile-document.js +101 -39
- package/dist-server/service/employee-profile/employee-profile-document.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/tests/department-open-fields.test.ts +72 -0
- package/tests/employee-profile-document.test.ts +57 -3
- package/translations/en.json +2 -0
- package/translations/es.json +2 -0
- package/translations/ja.json +2 -0
- package/translations/ko.json +2 -0
- package/translations/ms.json +2 -0
- package/translations/zh.json +2 -0
|
@@ -11,6 +11,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
11
11
|
exports.ROLE_TASKS = void 0;
|
|
12
12
|
exports.ROLE_TASKS = [
|
|
13
13
|
{ id: 'organization.people-reading', grants: [{ category: 'employee', axes: ['query'] }, { category: 'department', axes: ['query'] }] },
|
|
14
|
+
/* The departments alone, for every seat that picks or shows one; `manager` and `members` still ask `employee` (ADR-0139). */
|
|
15
|
+
{ id: 'organization.departments-reading', grants: [{ category: 'department', axes: ['query'] }] },
|
|
14
16
|
{ id: 'organization.people-keeping', grants: [{ category: 'employee', axes: ['query', 'mutation'] }, { category: 'department', axes: ['query', 'mutation'] }] },
|
|
15
17
|
{ id: 'organization.pay-accounts', grants: [{ category: 'employee-private', axes: ['query', 'mutation'] }] },
|
|
16
18
|
/* One's own profile, written by the employee; every active employee's, read to attach to a quotation (ADR-0169 decision 3). */
|
|
@@ -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,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;CACrG,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 { 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]\n"]}
|
|
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;CACrG,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]\n"]}
|
|
@@ -8,6 +8,7 @@ export declare class DepartmentQuery {
|
|
|
8
8
|
department(id: string, context: ResolverContext): Promise<Department>;
|
|
9
9
|
departments(params: ListParam, context: ResolverContext): Promise<DepartmentList>;
|
|
10
10
|
manager(department: Department): Promise<Employee>;
|
|
11
|
+
members(department: Department): Promise<Employee[]>;
|
|
11
12
|
parent(department: Department): Promise<Department>;
|
|
12
13
|
children(department: Department, params: ListParam, context: ResolverContext): Promise<Department[]>;
|
|
13
14
|
picture(department: Department): Promise<string | undefined>;
|
|
@@ -10,6 +10,16 @@ const auth_base_1 = require("@things-factory/auth-base");
|
|
|
10
10
|
const department_1 = require("./department");
|
|
11
11
|
const department_type_1 = require("./department-type");
|
|
12
12
|
const employee_1 = require("../employee/employee");
|
|
13
|
+
/*
|
|
14
|
+
* Every seat reads departments, while `manager` and `members` ask `employee:query` (department.ts). A list is searched,
|
|
15
|
+
* filtered and sorted only by the fields it shows to everybody: filtering by a closed field would tell, by whether a
|
|
16
|
+
* department stays in the list, what that field holds (ADR-0139 §10).
|
|
17
|
+
*/
|
|
18
|
+
const DEPARTMENT_LIST_RULES = {
|
|
19
|
+
searchables: ['name', 'description', 'controlNo'],
|
|
20
|
+
filterables: ['id', 'name', 'description', 'controlNo', 'active', 'extension', 'parent', 'createdAt', 'updatedAt'],
|
|
21
|
+
sortables: ['name', 'description', 'controlNo', 'active', 'extension', 'createdAt', 'updatedAt']
|
|
22
|
+
};
|
|
13
23
|
let DepartmentQuery = class DepartmentQuery {
|
|
14
24
|
async departmentRoots(params, context) {
|
|
15
25
|
const { domain } = context.state;
|
|
@@ -17,7 +27,7 @@ let DepartmentQuery = class DepartmentQuery {
|
|
|
17
27
|
domain,
|
|
18
28
|
params,
|
|
19
29
|
repository: await (0, shell_1.getRepository)(department_1.Department),
|
|
20
|
-
|
|
30
|
+
...DEPARTMENT_LIST_RULES
|
|
21
31
|
}).andWhere({ domain: { id: domain.id }, parent: { id: (0, typeorm_1.IsNull)() } });
|
|
22
32
|
const [items, total] = await queryBuilder.getManyAndCount();
|
|
23
33
|
return { items, total };
|
|
@@ -34,7 +44,7 @@ let DepartmentQuery = class DepartmentQuery {
|
|
|
34
44
|
domain,
|
|
35
45
|
params,
|
|
36
46
|
repository: await (0, shell_1.getRepository)(department_1.Department),
|
|
37
|
-
|
|
47
|
+
...DEPARTMENT_LIST_RULES
|
|
38
48
|
});
|
|
39
49
|
const [items, total] = await queryBuilder.getManyAndCount();
|
|
40
50
|
return { items, total };
|
|
@@ -45,6 +55,12 @@ let DepartmentQuery = class DepartmentQuery {
|
|
|
45
55
|
id: department.managerId
|
|
46
56
|
})));
|
|
47
57
|
}
|
|
58
|
+
async members(department) {
|
|
59
|
+
return await (0, shell_1.getRepository)(employee_1.Employee).find({
|
|
60
|
+
where: { domain: { id: department.domainId }, department: { id: department.id } },
|
|
61
|
+
order: { controlNo: 'ASC' }
|
|
62
|
+
});
|
|
63
|
+
}
|
|
48
64
|
async parent(department) {
|
|
49
65
|
return (department.parentId &&
|
|
50
66
|
(await (0, shell_1.getRepository)(department_1.Department).findOneBy({
|
|
@@ -57,7 +73,7 @@ let DepartmentQuery = class DepartmentQuery {
|
|
|
57
73
|
domain,
|
|
58
74
|
params,
|
|
59
75
|
repository: await (0, shell_1.getRepository)(department_1.Department),
|
|
60
|
-
|
|
76
|
+
...DEPARTMENT_LIST_RULES
|
|
61
77
|
}).andWhere({
|
|
62
78
|
parent: { id: department.id }
|
|
63
79
|
});
|
|
@@ -119,6 +135,13 @@ tslib_1.__decorate([
|
|
|
119
135
|
tslib_1.__metadata("design:paramtypes", [department_1.Department]),
|
|
120
136
|
tslib_1.__metadata("design:returntype", Promise)
|
|
121
137
|
], DepartmentQuery.prototype, "manager", null);
|
|
138
|
+
tslib_1.__decorate([
|
|
139
|
+
(0, type_graphql_1.FieldResolver)(type => [employee_1.Employee], { nullable: true }),
|
|
140
|
+
tslib_1.__param(0, (0, type_graphql_1.Root)()),
|
|
141
|
+
tslib_1.__metadata("design:type", Function),
|
|
142
|
+
tslib_1.__metadata("design:paramtypes", [department_1.Department]),
|
|
143
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
144
|
+
], DepartmentQuery.prototype, "members", null);
|
|
122
145
|
tslib_1.__decorate([
|
|
123
146
|
(0, type_graphql_1.FieldResolver)(type => department_1.Department),
|
|
124
147
|
tslib_1.__param(0, (0, type_graphql_1.Root)()),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"department-query.js","sourceRoot":"","sources":["../../../server/service/department/department-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Resolver, Query, FieldResolver, Root, Args, Arg, Ctx, Directive } from 'type-graphql'\nimport { IsNull } from 'typeorm'\nimport { Attachment } from '@things-factory/attachment-base'\nimport { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { Department } from './department'\nimport { DepartmentList } from './department-type'\nimport { Employee } from '../employee/employee'\n\n@Resolver(Department)\nexport class DepartmentQuery {\n @Directive('@privilege(category: \"department\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => DepartmentList, { nullable: true, description: 'To fetch a Root Departments' })\n async departmentRoots(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<DepartmentList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n params,\n repository: await getRepository(Department),\n searchables: ['name', 'description', 'controlNo']\n }).andWhere({ domain: { id: domain.id }, parent: { id: IsNull() } })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n @Directive('@privilege(category: \"department\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => Department!, { nullable: true, description: 'To fetch a Department' })\n async department(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Department> {\n const { domain } = context.state\n\n return await getRepository(Department).findOne({\n where: { domain: { id: domain.id }, id }\n })\n }\n\n @Directive('@privilege(category: \"department\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => DepartmentList, { description: 'To fetch multiple Departments' })\n async departments(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<DepartmentList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n params,\n repository: await getRepository(Department),\n searchables: ['name', 'description', 'controlNo']\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n @FieldResolver(type => Employee)\n async manager(@Root() department: Department): Promise<Employee> {\n return (\n department.managerId &&\n (await getRepository(Employee).findOneBy({\n id: department.managerId\n }))\n )\n }\n\n @FieldResolver(type => Department)\n async parent(@Root() department: Department): Promise<Department> {\n return (\n department.parentId &&\n (await getRepository(Department).findOneBy({\n id: department.parentId\n }))\n )\n }\n\n @FieldResolver(type => [Department])\n async children(\n @Root() department: Department,\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<Department[]> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n params,\n repository: await getRepository(Department),\n searchables: ['name', 'description', 'controlNo']\n }).andWhere({\n parent: { id: department.id }\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return items\n }\n\n @FieldResolver(type => String)\n async picture(@Root() department: Department): Promise<string | undefined> {\n const attachment: Attachment = await getRepository(Attachment).findOne({\n where: {\n domain: { id: department.domainId },\n refType: Department.name,\n refBy: department.id\n }\n })\n\n return attachment?.fullpath\n }\n\n @FieldResolver(type => Domain)\n async domain(@Root() department: Department): Promise<Domain> {\n return await getRepository(Domain).findOneBy({ id: department.domainId })\n }\n\n @FieldResolver(type => User)\n async updater(@Root() department: Department): Promise<User> {\n return await getRepository(User).findOneBy({ id: department.updaterId })\n }\n\n @FieldResolver(type => User)\n async creator(@Root() department: Department): Promise<User> {\n return await getRepository(User).findOneBy({ id: department.creatorId })\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"department-query.js","sourceRoot":"","sources":["../../../server/service/department/department-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Resolver, Query, FieldResolver, Root, Args, Arg, Ctx, Directive } from 'type-graphql'\nimport { IsNull } from 'typeorm'\nimport { Attachment } from '@things-factory/attachment-base'\nimport { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { Department } from './department'\nimport { DepartmentList } from './department-type'\nimport { Employee } from '../employee/employee'\n\n/*\n * Every seat reads departments, while `manager` and `members` ask `employee:query` (department.ts). A list is searched,\n * filtered and sorted only by the fields it shows to everybody: filtering by a closed field would tell, by whether a\n * department stays in the list, what that field holds (ADR-0139 §10).\n */\nconst DEPARTMENT_LIST_RULES = {\n searchables: ['name', 'description', 'controlNo'],\n filterables: ['id', 'name', 'description', 'controlNo', 'active', 'extension', 'parent', 'createdAt', 'updatedAt'],\n sortables: ['name', 'description', 'controlNo', 'active', 'extension', 'createdAt', 'updatedAt']\n}\n\n@Resolver(Department)\nexport class DepartmentQuery {\n @Directive('@privilege(category: \"department\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => DepartmentList, { nullable: true, description: 'To fetch a Root Departments' })\n async departmentRoots(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<DepartmentList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n params,\n repository: await getRepository(Department),\n ...DEPARTMENT_LIST_RULES\n }).andWhere({ domain: { id: domain.id }, parent: { id: IsNull() } })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n @Directive('@privilege(category: \"department\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => Department!, { nullable: true, description: 'To fetch a Department' })\n async department(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Department> {\n const { domain } = context.state\n\n return await getRepository(Department).findOne({\n where: { domain: { id: domain.id }, id }\n })\n }\n\n @Directive('@privilege(category: \"department\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => DepartmentList, { description: 'To fetch multiple Departments' })\n async departments(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<DepartmentList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n params,\n repository: await getRepository(Department),\n ...DEPARTMENT_LIST_RULES\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n @FieldResolver(type => Employee)\n async manager(@Root() department: Department): Promise<Employee> {\n return (\n department.managerId &&\n (await getRepository(Employee).findOneBy({\n id: department.managerId\n }))\n )\n }\n\n @FieldResolver(type => [Employee], { nullable: true })\n async members(@Root() department: Department): Promise<Employee[]> {\n return await getRepository(Employee).find({\n where: { domain: { id: department.domainId }, department: { id: department.id } },\n order: { controlNo: 'ASC' }\n })\n }\n\n @FieldResolver(type => Department)\n async parent(@Root() department: Department): Promise<Department> {\n return (\n department.parentId &&\n (await getRepository(Department).findOneBy({\n id: department.parentId\n }))\n )\n }\n\n @FieldResolver(type => [Department])\n async children(\n @Root() department: Department,\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<Department[]> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n params,\n repository: await getRepository(Department),\n ...DEPARTMENT_LIST_RULES\n }).andWhere({\n parent: { id: department.id }\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return items\n }\n\n @FieldResolver(type => String)\n async picture(@Root() department: Department): Promise<string | undefined> {\n const attachment: Attachment = await getRepository(Attachment).findOne({\n where: {\n domain: { id: department.domainId },\n refType: Department.name,\n refBy: department.id\n }\n })\n\n return attachment?.fullpath\n }\n\n @FieldResolver(type => Domain)\n async domain(@Root() department: Department): Promise<Domain> {\n return await getRepository(Domain).findOneBy({ id: department.domainId })\n }\n\n @FieldResolver(type => User)\n async updater(@Root() department: Department): Promise<User> {\n return await getRepository(User).findOneBy({ id: department.updaterId })\n }\n\n @FieldResolver(type => User)\n async creator(@Root() department: Department): Promise<User> {\n return await getRepository(User).findOneBy({ id: department.creatorId })\n }\n}\n"]}
|
|
@@ -63,11 +63,13 @@ tslib_1.__decorate([
|
|
|
63
63
|
], Department.prototype, "children", void 0);
|
|
64
64
|
tslib_1.__decorate([
|
|
65
65
|
(0, typeorm_1.OneToMany)(type => employee_1.Employee, employee => employee.department),
|
|
66
|
-
(0, type_graphql_1.
|
|
66
|
+
(0, type_graphql_1.Directive)('@privilege(category: "employee", privilege: "query", domainOwnerGranted: true)'),
|
|
67
|
+
(0, type_graphql_1.Field)(type => [employee_1.Employee], { nullable: true }),
|
|
67
68
|
tslib_1.__metadata("design:type", Array)
|
|
68
69
|
], Department.prototype, "members", void 0);
|
|
69
70
|
tslib_1.__decorate([
|
|
70
71
|
(0, typeorm_1.ManyToOne)(type => employee_1.Employee, manager => manager.manages, { nullable: true }),
|
|
72
|
+
(0, type_graphql_1.Directive)('@privilege(category: "employee", privilege: "query", domainOwnerGranted: true)'),
|
|
71
73
|
(0, type_graphql_1.Field)(type => employee_1.Employee, { nullable: true }),
|
|
72
74
|
tslib_1.__metadata("design:type", employee_1.Employee)
|
|
73
75
|
], Department.prototype, "manager", void 0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"department.js","sourceRoot":"","sources":["../../../server/service/department/department.ts"],"names":[],"mappings":";;;;AAAA,qCAYgB;AAChB,+
|
|
1
|
+
{"version":3,"file":"department.js","sourceRoot":"","sources":["../../../server/service/department/department.ts"],"names":[],"mappings":";;;;AAAA,qCAYgB;AAChB,+CAAsF;AAEtF,iDAA8C;AAC9C,yDAAgD;AAChD,mDAA+C;AAYxC,IAAM,UAAU,GAAhB,MAAM,UAAU;IAAhB;QAOL,YAAO,GAAY,CAAC,CAAA;IAuFtB,CAAC;CAAA,CAAA;AA9FY,gCAAU;AAGZ;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAE,CAAC;;sCACC;AAInB;IAFC,IAAA,uBAAa,GAAE;IACf,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CACN;AAIpB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;IACzB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;sCACb,cAAM;0CAAA;AAGf;IADC,IAAA,oBAAU,EAAC,CAAC,UAAsB,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;;4CACzC;AAIjB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,GAAE;;6CACS;AAIjB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;wCACb;AAIb;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CACN;AAIpB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACpF,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACjB,UAAU;0CAAA;AAGnB;IADC,IAAA,oBAAU,EAAC,CAAC,UAAsB,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;;4CACzC;AAIjB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;IAC9D,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;;4CACL;AAWvB;IAHC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,mBAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC5D,IAAA,wBAAS,EAAC,gFAAgF,CAAC;IAC3F,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,mBAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CAC1B;AAKpB;IAHC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,mBAAQ,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3E,IAAA,wBAAS,EAAC,gFAAgF,CAAC;IAC3F,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,mBAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCAClC,mBAAQ;2CAAA;AAGlB;IADC,IAAA,oBAAU,EAAC,CAAC,UAAsB,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;;6CACzC;AAIlB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0CACV;AAIhB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;6CACR;AAGlB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CAC1B;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;6CAAA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;6CAAA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;6CAAA;AAIhB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCAC9B,gBAAI;2CAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,UAAsB,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;;6CACzC;AAIlB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCAC9B,gBAAI;2CAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,UAAsB,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;;6CACzC;qBA7FP,UAAU;IAVtB,IAAA,gBAAM,GAAE;IACR,IAAA,eAAK,EAAC,iBAAiB,EAAE,CAAC,UAAsB,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE;QAC1F,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,sBAAsB;KAC9B,CAAC;IACD,IAAA,eAAK,EAAC,iBAAiB,EAAE,CAAC,UAAsB,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE;QAC/F,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,sBAAsB;KAC9B,CAAC;IACD,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC;GACxC,UAAU,CA8FtB","sourcesContent":["import {\n CreateDateColumn,\n UpdateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n Column,\n RelationId,\n ManyToOne,\n OneToMany,\n PrimaryGeneratedColumn,\n VersionColumn\n} from 'typeorm'\nimport { ObjectType, Field, Int, ID, registerEnumType, Directive } from 'type-graphql'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { Employee } from '../employee/employee'\n\n@Entity()\n@Index('ix_department_0', (department: Department) => [department.domain, department.name], {\n unique: true,\n where: '\"deleted_at\" IS NULL'\n})\n@Index('ix_department_1', (department: Department) => [department.domain, department.controlNo], {\n unique: true,\n where: '\"deleted_at\" IS NULL'\n})\n@ObjectType({ description: 'Entity for Department' })\nexport class Department {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @VersionColumn()\n @Field({ nullable: true })\n version?: number = 1\n\n @ManyToOne(type => Domain)\n @Field(type => Domain)\n domain?: Domain\n\n @RelationId((department: Department) => department.domain)\n domainId?: string\n\n @Column()\n @Field()\n controlNo: string\n\n @Column()\n @Field({ nullable: true })\n name?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true })\n description?: string\n\n @ManyToOne(type => Department, department => department.children, { nullable: true })\n @Field({ nullable: true })\n parent?: Department\n\n @RelationId((department: Department) => department.parent)\n parentId?: string\n\n @OneToMany(type => Department, department => department.parent)\n @Field(type => [Department])\n children?: Department[]\n\n /*\n * Every seat reads the departments; the people in them stay the roster's (ADR-0139). These two lead to employee\n * records, so they ask what the employee list asks - refused alone, the rest of the department still comes back.\n * A field declared here takes these directives even where `DepartmentQuery` resolves it. Both are nullable: a\n * refused non-null field would null the whole list above it.\n */\n @OneToMany(type => Employee, employee => employee.department)\n @Directive('@privilege(category: \"employee\", privilege: \"query\", domainOwnerGranted: true)')\n @Field(type => [Employee], { nullable: true })\n members?: Employee[]\n\n @ManyToOne(type => Employee, manager => manager.manages, { nullable: true })\n @Directive('@privilege(category: \"employee\", privilege: \"query\", domainOwnerGranted: true)')\n @Field(type => Employee, { nullable: true })\n manager?: Employee\n\n @RelationId((department: Department) => department.manager)\n managerId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true })\n active?: boolean\n\n @Column({ nullable: true })\n @Field({ nullable: true })\n extension?: string\n\n @Field(type => String, { nullable: true })\n picture?: string\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true })\n deletedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n creator?: User\n\n @RelationId((department: Department) => department.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n updater?: User\n\n @RelationId((department: Department) => department.updater)\n updaterId?: string\n}\n"]}
|
|
@@ -45,10 +45,14 @@ export type ProfileSheetWords = Record<'title' | 'rosterTitle' | 'expertise' | '
|
|
|
45
45
|
};
|
|
46
46
|
/** What a cell is - the painter styles by it. */
|
|
47
47
|
export type CellRole = 'company' | 'kicker' | 'name' | 'tagline' | 'rule' | 'statLabel' | 'statValue' | 'section' | 'summary' | 'label' | 'skills' | 'head' | 'value' | 'none';
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* A cell of the six-column grid: `col` from 1, spanning `span` columns and `rows` lines - the lines below leave its
|
|
50
|
+
* columns out. A cell over several lines keeps its text where the first line has it, at the top.
|
|
51
|
+
*/
|
|
49
52
|
export type LayoutCell = {
|
|
50
53
|
col: number;
|
|
51
54
|
span?: number;
|
|
55
|
+
rows?: number;
|
|
52
56
|
value: string;
|
|
53
57
|
role: CellRole;
|
|
54
58
|
};
|
|
@@ -60,10 +64,22 @@ export type LayoutLine = {
|
|
|
60
64
|
};
|
|
61
65
|
/** The grid's six columns, in characters - together about the width of A4 in portrait. */
|
|
62
66
|
export declare const COLUMN_WIDTHS: number[];
|
|
67
|
+
/** The roster's six columns: a narrow number, then who - its career spans the last two, its skills the whole line. */
|
|
68
|
+
export declare const ROSTER_WIDTHS: number[];
|
|
63
69
|
/** 「2023.03」 from 「2023-03」 or 「2023-03-02」 - the dotted form a staffing profile is read in. */
|
|
64
70
|
export declare function dotted(day?: string | null): string;
|
|
65
71
|
/** A period as one cell - an open end is still going on. */
|
|
66
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[];
|
|
67
83
|
/**
|
|
68
84
|
* The months a person has worked: the careers before this company and the time here since the hire day, counted
|
|
69
85
|
* once where they overlap. A career with no end runs to `today`; one with no start is left out.
|
|
@@ -96,13 +112,13 @@ export declare const PALETTE: {
|
|
|
96
112
|
* A line's height in points, for as many text lines as its longest boxed cell wraps into - Excel does not grow a row
|
|
97
113
|
* for wrapped text written by a program. A full-width character is about two of the column's character units.
|
|
98
114
|
*/
|
|
99
|
-
export declare function heightOf(line: LayoutLine): number | undefined;
|
|
115
|
+
export declare function heightOf(line: LayoutLine, widths?: number[]): number | undefined;
|
|
100
116
|
/** Paint a layout on a sheet: values, spans merged, each cell of a span painted so a boxed cell is boxed all round. */
|
|
101
|
-
export declare function paintSheet(sheet: Worksheet, lines: LayoutLine[], footer?: string): void;
|
|
117
|
+
export declare function paintSheet(sheet: Worksheet, lines: LayoutLine[], footer?: string, widths?: number[]): void;
|
|
102
118
|
/** The workbook of the people given, in their order - opened by a roster when there are several. */
|
|
103
119
|
export declare function profileWorkbook(people: ProfilePerson[], words: ProfileSheetWords, company: string, today?: Date): Workbook;
|
|
104
120
|
/** The same layout as HTML: a table of the six columns a page, the roles as classes. */
|
|
105
|
-
export declare function layoutHtml(lines: LayoutLine[]): string;
|
|
121
|
+
export declare function layoutHtml(lines: LayoutLine[], widths?: number[]): string;
|
|
106
122
|
/** How the roles look on paper - the workbook's paint in CSS. */
|
|
107
123
|
export declare const PROFILE_CSS: string;
|
|
108
124
|
/** On a screen, the pages as sheets of A4 on a grey desk - the preview reads as the PDF will (ADR-0169 decision 9). */
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PROFILE_SCREEN_CSS = exports.PROFILE_CSS = exports.PALETTE = exports.COLUMN_WIDTHS = void 0;
|
|
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;
|
|
6
7
|
exports.careerMonths = careerMonths;
|
|
7
8
|
exports.careerLengthOf = careerLengthOf;
|
|
8
9
|
exports.taglineOf = taglineOf;
|
|
@@ -24,6 +25,8 @@ const exceljs_1 = require("exceljs");
|
|
|
24
25
|
const employee_profile_rules_1 = require("./employee-profile-rules");
|
|
25
26
|
/** The grid's six columns, in characters - together about the width of A4 in portrait. */
|
|
26
27
|
exports.COLUMN_WIDTHS = [18, 20, 15, 12, 21, 18];
|
|
28
|
+
/** The roster's six columns: a narrow number, then who - its career spans the last two, its skills the whole line. */
|
|
29
|
+
exports.ROSTER_WIDTHS = [6, 24, 24, 18, 16, 16];
|
|
27
30
|
/** 「2023.03」 from 「2023-03」 or 「2023-03-02」 - the dotted form a staffing profile is read in. */
|
|
28
31
|
function dotted(day) {
|
|
29
32
|
return (day ?? '').trim().replace(/-/g, '.');
|
|
@@ -34,6 +37,22 @@ function periodOf(startedOn, endedOn, ongoing) {
|
|
|
34
37
|
return '';
|
|
35
38
|
return `${dotted(startedOn)} ~ ${endedOn ? dotted(endedOn) : ongoing}`.trim();
|
|
36
39
|
}
|
|
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
|
+
}
|
|
37
56
|
const monthIndex = (day) => {
|
|
38
57
|
const match = /^(\d{4})-(\d{2})/.exec((day ?? '').trim());
|
|
39
58
|
return match ? Number(match[1]) * 12 + Number(match[2]) - 1 : null;
|
|
@@ -130,14 +149,14 @@ function profileSheetLayout(person, words, company, today = new Date()) {
|
|
|
130
149
|
{ col: 2, span: 5, value: (profile.skills ?? []).join(', '), role: 'skills' }
|
|
131
150
|
]
|
|
132
151
|
});
|
|
133
|
-
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]], profile.projects, row => [periodOf(row.startedOn, row.endedOn, words.ongoing), row.name, row.client, row.role, row.duties, row.technologies]);
|
|
134
|
-
table(words.careers, [[1, 1, words.period], [2, 1, words.company], [3, 1, words.department], [4, 1, words.position], [5, 2, words.duties]], profile.careers, row => [periodOf(row.startedOn, row.endedOn, words.ongoing), row.company, row.department, row.position, row.duties]);
|
|
135
|
-
table(words.certificates, [[1, 1, words.acquiredOn], [2, 3, words.certificate], [5, 2, words.issuer]], profile.certificates, row => [
|
|
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 => [
|
|
136
155
|
dotted(row.acquiredOn),
|
|
137
156
|
row.name,
|
|
138
157
|
row.issuer
|
|
139
158
|
]);
|
|
140
|
-
table(words.educations, [[1, 1, words.period], [2, 2, words.school], [4, 2, words.major], [6, 1, words.degree]], profile.educations, row => [
|
|
159
|
+
table(words.educations, [[1, 1, words.period], [2, 2, words.school], [4, 2, words.major], [6, 1, words.degree]], newestFirst(profile.educations), row => [
|
|
141
160
|
periodOf(row.startedOn, row.endedOn, words.ongoing),
|
|
142
161
|
row.school,
|
|
143
162
|
row.major,
|
|
@@ -157,19 +176,38 @@ function rosterLayout(people, words, company, today = new Date()) {
|
|
|
157
176
|
...headOf(company, `${words.writtenOn} ${dotted((0, employee_profile_rules_1.todayOf)(today))}`, words.rosterTitle, rosterTaglineOf(people, words, today)),
|
|
158
177
|
{ cells: [], height: 16 },
|
|
159
178
|
{
|
|
160
|
-
cells: [words.number, words.name, words.department, words.jobPosition, words.careerLength, words.skills].map((value, index) => ({ col: index + 1, value, role: 'head' }))
|
|
161
|
-
},
|
|
162
|
-
...people.map((person, index) => ({
|
|
163
179
|
cells: [
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
180
|
+
...[words.number, words.name, words.department, words.jobPosition].map((value, index) => ({ col: index + 1, value, role: 'head' })),
|
|
181
|
+
{ col: 5, span: 2, value: words.careerLength, role: 'head' }
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
/*
|
|
185
|
+
* Two lines a person: who, then the skills under the name across the rest of the page - a list too long for one
|
|
186
|
+
* column of six. The number runs down both lines, so it marks where a person starts; a person with no skills
|
|
187
|
+
* written keeps one line.
|
|
188
|
+
*/
|
|
189
|
+
...people.flatMap((person, index) => {
|
|
190
|
+
const shade = index % 2 ? { zebra: true } : {};
|
|
191
|
+
const skills = (person.profile?.skills ?? []).join(', ');
|
|
192
|
+
return [
|
|
193
|
+
{
|
|
194
|
+
cells: [
|
|
195
|
+
{ col: 1, rows: skills ? 2 : 1, value: String(index + 1), role: 'value' },
|
|
196
|
+
...[person.name ?? '', person.departmentName ?? '', person.jobPosition ?? ''].map((value, column) => ({ col: column + 2, value, role: 'value' })),
|
|
197
|
+
{ col: 5, span: 2, value: careerLengthOf(person, words, today), role: 'value' }
|
|
198
|
+
],
|
|
199
|
+
...shade
|
|
200
|
+
},
|
|
201
|
+
...(skills
|
|
202
|
+
? [
|
|
203
|
+
{
|
|
204
|
+
cells: [{ col: 2, span: 5, value: skills, role: 'value' }],
|
|
205
|
+
...shade
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
: [])
|
|
209
|
+
];
|
|
210
|
+
})
|
|
173
211
|
];
|
|
174
212
|
}
|
|
175
213
|
/**
|
|
@@ -233,12 +271,12 @@ const PAINT = {
|
|
|
233
271
|
* A line's height in points, for as many text lines as its longest boxed cell wraps into - Excel does not grow a row
|
|
234
272
|
* for wrapped text written by a program. A full-width character is about two of the column's character units.
|
|
235
273
|
*/
|
|
236
|
-
function heightOf(line) {
|
|
274
|
+
function heightOf(line, widths = exports.COLUMN_WIDTHS) {
|
|
237
275
|
const boxed = line.cells.filter(cell => ['label', 'skills', 'summary', 'head', 'value', 'none'].includes(cell.role));
|
|
238
276
|
if (!boxed.length)
|
|
239
277
|
return undefined;
|
|
240
278
|
const wraps = boxed.map(cell => {
|
|
241
|
-
const width =
|
|
279
|
+
const width = widths.slice(cell.col - 1, cell.col - 1 + (cell.span ?? 1)).reduce((sum, one) => sum + one, 0) - (cell.role === 'summary' ? 4 : 1);
|
|
242
280
|
return cell.value.split('\n').reduce((lines, text) => {
|
|
243
281
|
const units = [...text].reduce((sum, char) => sum + (char.charCodeAt(0) > 0x2e7f ? 1.9 : 0.9), 0);
|
|
244
282
|
return lines + Math.max(1, Math.ceil(units / Math.max(width, 1)));
|
|
@@ -248,21 +286,24 @@ function heightOf(line) {
|
|
|
248
286
|
return boxed.some(cell => cell.role === 'summary') ? lines * 16 + 14 : lines * 15 + 7;
|
|
249
287
|
}
|
|
250
288
|
/** Paint a layout on a sheet: values, spans merged, each cell of a span painted so a boxed cell is boxed all round. */
|
|
251
|
-
function paintSheet(sheet, lines, footer = '') {
|
|
252
|
-
|
|
289
|
+
function paintSheet(sheet, lines, footer = '', widths = exports.COLUMN_WIDTHS) {
|
|
290
|
+
widths.forEach((width, index) => (sheet.getColumn(index + 1).width = width));
|
|
253
291
|
lines.forEach((line, index) => {
|
|
254
292
|
const row = sheet.getRow(index + 1);
|
|
255
|
-
const height = Math.max(line.height ?? 0, heightOf(line) ?? 0);
|
|
293
|
+
const height = Math.max(line.height ?? 0, heightOf(line, widths) ?? 0);
|
|
256
294
|
if (height)
|
|
257
295
|
row.height = height;
|
|
258
296
|
for (const cell of line.cells) {
|
|
259
297
|
const span = cell.span ?? 1;
|
|
260
298
|
const style = line.zebra && cell.role === 'value' ? { ...PAINT.value, fill: ground(exports.PALETTE.zebra) } : PAINT[cell.role];
|
|
261
|
-
|
|
262
|
-
|
|
299
|
+
const rows = cell.rows ?? 1;
|
|
300
|
+
const painted = rows > 1 ? { ...style, alignment: { ...style.alignment, vertical: 'top' } } : style;
|
|
301
|
+
for (let at = 0; at < rows; at++)
|
|
302
|
+
for (let col = cell.col; col < cell.col + span; col++)
|
|
303
|
+
Object.assign(sheet.getRow(index + 1 + at).getCell(col), { style: painted });
|
|
263
304
|
row.getCell(cell.col).value = cell.value;
|
|
264
|
-
if (span > 1)
|
|
265
|
-
sheet.mergeCells(index + 1, cell.col, index +
|
|
305
|
+
if (span > 1 || rows > 1)
|
|
306
|
+
sheet.mergeCells(index + 1, cell.col, index + rows, cell.col + span - 1);
|
|
266
307
|
}
|
|
267
308
|
});
|
|
268
309
|
sheet.views = [{ showGridLines: false }];
|
|
@@ -285,7 +326,7 @@ function profileWorkbook(people, words, company, today = new Date()) {
|
|
|
285
326
|
/* The roster's name follows Excel's rules as the people's do. */
|
|
286
327
|
const roster = people.length > 1 ? sheetNamesOf([words.rosterTitle]) : [];
|
|
287
328
|
if (roster.length)
|
|
288
|
-
paintSheet(book.addWorksheet(roster[0]), rosterLayout(people, words, company, today), footer);
|
|
329
|
+
paintSheet(book.addWorksheet(roster[0]), rosterLayout(people, words, company, today), footer, exports.ROSTER_WIDTHS);
|
|
289
330
|
const names = sheetNamesOf(people.map(person => person.name), roster);
|
|
290
331
|
people.forEach((person, index) => paintSheet(book.addWorksheet(names[index]), profileSheetLayout(person, words, company, today), footer));
|
|
291
332
|
return book;
|
|
@@ -303,24 +344,45 @@ function cellHtml(cell) {
|
|
|
303
344
|
return escapeHtml(cell.value).replace(/\n/g, '<br>');
|
|
304
345
|
}
|
|
305
346
|
/** The same layout as HTML: a table of the six columns a page, the roles as classes. */
|
|
306
|
-
function layoutHtml(lines) {
|
|
307
|
-
const total =
|
|
308
|
-
const columns =
|
|
347
|
+
function layoutHtml(lines, widths = exports.COLUMN_WIDTHS) {
|
|
348
|
+
const total = widths.reduce((sum, width) => sum + width, 0);
|
|
349
|
+
const columns = widths.map(width => `<col style="width:${((width / total) * 100).toFixed(2)}%">`).join('');
|
|
350
|
+
/* How many more lines each column is still held by a cell from a line above. */
|
|
351
|
+
const held = [0, 0, 0, 0, 0, 0, 0];
|
|
309
352
|
const rows = lines.map(line => {
|
|
310
353
|
const style = line.height ? ` style="height:${Math.round(line.height * 1.33)}px"` : '';
|
|
354
|
+
const covered = held.map(lines => lines > 0);
|
|
355
|
+
for (let col = 1; col <= 6; col++)
|
|
356
|
+
held[col] = Math.max(0, held[col] - 1);
|
|
311
357
|
if (!line.cells.length)
|
|
312
358
|
return `<tr class="gap"${style}><td colspan="6"></td></tr>`;
|
|
313
|
-
let next = 1;
|
|
314
359
|
const cells = [];
|
|
360
|
+
/* Empty columns between cells, as runs that leave out the columns a cell above still holds. */
|
|
361
|
+
const fill = (from, to) => {
|
|
362
|
+
for (let col = from; col < to;) {
|
|
363
|
+
if (covered[col]) {
|
|
364
|
+
col++;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
let end = col;
|
|
368
|
+
while (end < to && !covered[end])
|
|
369
|
+
end++;
|
|
370
|
+
cells.push(`<td colspan="${end - col}"></td>`);
|
|
371
|
+
col = end;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
let next = 1;
|
|
315
375
|
for (const cell of line.cells) {
|
|
316
|
-
|
|
317
|
-
cells.push(`<td colspan="${cell.col - next}"></td>`);
|
|
376
|
+
fill(next, cell.col);
|
|
318
377
|
const span = cell.span ?? 1;
|
|
319
|
-
|
|
378
|
+
const rows = cell.rows ?? 1;
|
|
379
|
+
for (let col = cell.col; col < cell.col + span; col++)
|
|
380
|
+
held[col] = rows - 1;
|
|
381
|
+
const attributes = `${span > 1 ? ` colspan="${span}"` : ''}${rows > 1 ? ` rowspan="${rows}" style="vertical-align:top"` : ''}`;
|
|
382
|
+
cells.push(`<td class="${cell.role}"${attributes}>${cellHtml(cell)}</td>`);
|
|
320
383
|
next = cell.col + span;
|
|
321
384
|
}
|
|
322
|
-
|
|
323
|
-
cells.push(`<td colspan="${7 - next}"></td>`);
|
|
385
|
+
fill(next, 7);
|
|
324
386
|
return `<tr${line.zebra ? ' class="zebra"' : ''}${style}>${cells.join('')}</tr>`;
|
|
325
387
|
});
|
|
326
388
|
return `<table><colgroup>${columns}</colgroup>${rows.join('')}</table>`;
|
|
@@ -369,10 +431,10 @@ function profilePreviewHtml(people, words, company, today = new Date()) {
|
|
|
369
431
|
/** The document as one HTML page set: the roster first when there are several, then a page a person. */
|
|
370
432
|
function profileHtml(people, words, company, today = new Date()) {
|
|
371
433
|
const sheets = [
|
|
372
|
-
...(people.length > 1 ? [rosterLayout(people, words, company, today)] : []),
|
|
373
|
-
...people.map(person => profileSheetLayout(person, words, company, today))
|
|
434
|
+
...(people.length > 1 ? [[rosterLayout(people, words, company, today), exports.ROSTER_WIDTHS]] : []),
|
|
435
|
+
...people.map(person => [profileSheetLayout(person, words, company, today), exports.COLUMN_WIDTHS])
|
|
374
436
|
];
|
|
375
|
-
return sheets.map(lines => `<section class="sheet">${layoutHtml(lines)}</section>`).join('');
|
|
437
|
+
return sheets.map(([lines, widths]) => `<section class="sheet">${layoutHtml(lines, widths)}</section>`).join('');
|
|
376
438
|
}
|
|
377
439
|
/** The foot of every printed PDF page: the employer and the document at the left, the page of how many at the right. */
|
|
378
440
|
function profileFooterHtml(company, words) {
|