@things-factory/organization 10.1.228 → 10.1.230
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/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 +10 -4
- package/dist-server/service/employee-profile/employee-profile-document.js +84 -39
- 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 +2 -2
- package/tests/department-open-fields.test.ts +72 -0
- package/tests/employee-profile-db.test.ts +14 -0
- package/tests/employee-profile-document.test.ts +66 -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":";;;;AAAA,+CAA8F;AAC9F,qCAAgC;AAChC,qEAA4D;AAC5D,iDAAuG;AACvG,yDAAgD;AAChD,6CAAyC;AACzC,uDAAkD;AAClD,mDAA+C;AAE/C;;;;GAIG;AACH,MAAM,qBAAqB,GAAG;IAC5B,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,CAAC;IACjD,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,CAAC;IAClH,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,CAAC;CACjG,CAAA;AAGM,IAAM,eAAe,GAArB,MAAM,eAAe;IAGpB,AAAN,KAAK,CAAC,eAAe,CACM,MAAiB,EACnC,OAAwB;QAE/B,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEhC,MAAM,YAAY,GAAG,IAAA,qCAA6B,EAAC;YACjD,MAAM;YACN,MAAM;YACN,UAAU,EAAE,MAAM,IAAA,qBAAa,EAAC,uBAAU,CAAC;YAC3C,GAAG,qBAAqB;SACzB,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAA,gBAAM,GAAE,EAAE,EAAE,CAAC,CAAA;QAEpE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,YAAY,CAAC,eAAe,EAAE,CAAA;QAE3D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;IACzB,CAAC;IAIK,AAAN,KAAK,CAAC,UAAU,CAAY,EAAU,EAAS,OAAwB;QACrE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEhC,OAAO,MAAM,IAAA,qBAAa,EAAC,uBAAU,CAAC,CAAC,OAAO,CAAC;YAC7C,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE;SACzC,CAAC,CAAA;IACJ,CAAC;IAIK,AAAN,KAAK,CAAC,WAAW,CACU,MAAiB,EACnC,OAAwB;QAE/B,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEhC,MAAM,YAAY,GAAG,IAAA,qCAA6B,EAAC;YACjD,MAAM;YACN,MAAM;YACN,UAAU,EAAE,MAAM,IAAA,qBAAa,EAAC,uBAAU,CAAC;YAC3C,GAAG,qBAAqB;SACzB,CAAC,CAAA;QAEF,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,YAAY,CAAC,eAAe,EAAE,CAAA;QAE3D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;IACzB,CAAC;IAGK,AAAN,KAAK,CAAC,OAAO,CAAS,UAAsB;QAC1C,OAAO,CACL,UAAU,CAAC,SAAS;YACpB,CAAC,MAAM,IAAA,qBAAa,EAAC,mBAAQ,CAAC,CAAC,SAAS,CAAC;gBACvC,EAAE,EAAE,UAAU,CAAC,SAAS;aACzB,CAAC,CAAC,CACJ,CAAA;IACH,CAAC;IAGK,AAAN,KAAK,CAAC,OAAO,CAAS,UAAsB;QAC1C,OAAO,MAAM,IAAA,qBAAa,EAAC,mBAAQ,CAAC,CAAC,IAAI,CAAC;YACxC,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,UAAU,CAAC,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE,EAAE;YACjF,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;SAC5B,CAAC,CAAA;IACJ,CAAC;IAGK,AAAN,KAAK,CAAC,MAAM,CAAS,UAAsB;QACzC,OAAO,CACL,UAAU,CAAC,QAAQ;YACnB,CAAC,MAAM,IAAA,qBAAa,EAAC,uBAAU,CAAC,CAAC,SAAS,CAAC;gBACzC,EAAE,EAAE,UAAU,CAAC,QAAQ;aACxB,CAAC,CAAC,CACJ,CAAA;IACH,CAAC;IAGK,AAAN,KAAK,CAAC,QAAQ,CACJ,UAAsB,EACL,MAAiB,EACnC,OAAwB;QAE/B,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEhC,MAAM,YAAY,GAAG,IAAA,qCAA6B,EAAC;YACjD,MAAM;YACN,MAAM;YACN,UAAU,EAAE,MAAM,IAAA,qBAAa,EAAC,uBAAU,CAAC;YAC3C,GAAG,qBAAqB;SACzB,CAAC,CAAC,QAAQ,CAAC;YACV,MAAM,EAAE,EAAE,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE;SAC9B,CAAC,CAAA;QAEF,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,YAAY,CAAC,eAAe,EAAE,CAAA;QAE3D,OAAO,KAAK,CAAA;IACd,CAAC;IAGK,AAAN,KAAK,CAAC,OAAO,CAAS,UAAsB;QAC1C,MAAM,UAAU,GAAe,MAAM,IAAA,qBAAa,EAAC,4BAAU,CAAC,CAAC,OAAO,CAAC;YACrE,KAAK,EAAE;gBACL,MAAM,EAAE,EAAE,EAAE,EAAE,UAAU,CAAC,QAAQ,EAAE;gBACnC,OAAO,EAAE,uBAAU,CAAC,IAAI;gBACxB,KAAK,EAAE,UAAU,CAAC,EAAE;aACrB;SACF,CAAC,CAAA;QAEF,OAAO,UAAU,EAAE,QAAQ,CAAA;IAC7B,CAAC;IAGK,AAAN,KAAK,CAAC,MAAM,CAAS,UAAsB;QACzC,OAAO,MAAM,IAAA,qBAAa,EAAC,cAAM,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAA;IAC3E,CAAC;IAGK,AAAN,KAAK,CAAC,OAAO,CAAS,UAAsB;QAC1C,OAAO,MAAM,IAAA,qBAAa,EAAC,gBAAI,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,UAAU,CAAC,SAAS,EAAE,CAAC,CAAA;IAC1E,CAAC;IAGK,AAAN,KAAK,CAAC,OAAO,CAAS,UAAsB;QAC1C,OAAO,MAAM,IAAA,qBAAa,EAAC,gBAAI,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,UAAU,CAAC,SAAS,EAAE,CAAC,CAAA;IAC1E,CAAC;CACF,CAAA;AAhIY,0CAAe;AAGpB;IAFL,IAAA,wBAAS,EAAC,kFAAkF,CAAC;IAC7F,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,gCAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,6BAA6B,EAAE,CAAC;IAE9F,mBAAA,IAAA,mBAAI,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAS,CAAC,CAAA;IACvB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;6CAD2B,iBAAS;;sDAe3C;AAIK;IAFL,IAAA,wBAAS,EAAC,kFAAkF,CAAC;IAC7F,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,uBAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC;IACtE,mBAAA,IAAA,kBAAG,EAAC,IAAI,CAAC,CAAA;IAAc,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;iDAM7C;AAIK;IAFL,IAAA,wBAAS,EAAC,kFAAkF,CAAC;IAC7F,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,gCAAc,EAAE,EAAE,WAAW,EAAE,+BAA+B,EAAE,CAAC;IAEhF,mBAAA,IAAA,mBAAI,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAS,CAAC,CAAA;IACvB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;6CAD2B,iBAAS;;kDAe3C;AAGK;IADL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,mBAAQ,CAAC;IACjB,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAa,uBAAU;;8CAO3C;AAGK;IADL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,mBAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvC,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAa,uBAAU;;8CAK3C;AAGK;IADL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,uBAAU,CAAC;IACpB,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAa,uBAAU;;6CAO1C;AAGK;IADL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,uBAAU,CAAC,CAAC;IAEjC,mBAAA,IAAA,mBAAI,GAAE,CAAA;IACN,mBAAA,IAAA,mBAAI,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAS,CAAC,CAAA;IACvB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;6CAFc,uBAAU;QACG,iBAAS;;+CAiB3C;AAGK;IADL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC;IACf,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAa,uBAAU;;8CAU3C;AAGK;IADL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;IAChB,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAa,uBAAU;;6CAE1C;AAGK;IADL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,CAAC;IACb,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAa,uBAAU;;8CAE3C;AAGK;IADL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,CAAC;IACb,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAa,uBAAU;;8CAE3C;0BA/HU,eAAe;IAD3B,IAAA,uBAAQ,EAAC,uBAAU,CAAC;GACR,eAAe,CAgI3B","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":"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","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,6 +64,8 @@ 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. */
|
|
@@ -96,13 +102,13 @@ export declare const PALETTE: {
|
|
|
96
102
|
* 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
103
|
* for wrapped text written by a program. A full-width character is about two of the column's character units.
|
|
98
104
|
*/
|
|
99
|
-
export declare function heightOf(line: LayoutLine): number | undefined;
|
|
105
|
+
export declare function heightOf(line: LayoutLine, widths?: number[]): number | undefined;
|
|
100
106
|
/** 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;
|
|
107
|
+
export declare function paintSheet(sheet: Worksheet, lines: LayoutLine[], footer?: string, widths?: number[]): void;
|
|
102
108
|
/** The workbook of the people given, in their order - opened by a roster when there are several. */
|
|
103
109
|
export declare function profileWorkbook(people: ProfilePerson[], words: ProfileSheetWords, company: string, today?: Date): Workbook;
|
|
104
110
|
/** 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;
|
|
111
|
+
export declare function layoutHtml(lines: LayoutLine[], widths?: number[]): string;
|
|
106
112
|
/** How the roles look on paper - the workbook's paint in CSS. */
|
|
107
113
|
export declare const PROFILE_CSS: string;
|
|
108
114
|
/** 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,6 +1,6 @@
|
|
|
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
6
|
exports.careerMonths = careerMonths;
|
|
@@ -24,6 +24,8 @@ const exceljs_1 = require("exceljs");
|
|
|
24
24
|
const employee_profile_rules_1 = require("./employee-profile-rules");
|
|
25
25
|
/** The grid's six columns, in characters - together about the width of A4 in portrait. */
|
|
26
26
|
exports.COLUMN_WIDTHS = [18, 20, 15, 12, 21, 18];
|
|
27
|
+
/** The roster's six columns: a narrow number, then who - its career spans the last two, its skills the whole line. */
|
|
28
|
+
exports.ROSTER_WIDTHS = [6, 24, 24, 18, 16, 16];
|
|
27
29
|
/** 「2023.03」 from 「2023-03」 or 「2023-03-02」 - the dotted form a staffing profile is read in. */
|
|
28
30
|
function dotted(day) {
|
|
29
31
|
return (day ?? '').trim().replace(/-/g, '.');
|
|
@@ -130,14 +132,14 @@ function profileSheetLayout(person, words, company, today = new Date()) {
|
|
|
130
132
|
{ col: 2, span: 5, value: (profile.skills ?? []).join(', '), role: 'skills' }
|
|
131
133
|
]
|
|
132
134
|
});
|
|
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 => [
|
|
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 => [
|
|
136
138
|
dotted(row.acquiredOn),
|
|
137
139
|
row.name,
|
|
138
140
|
row.issuer
|
|
139
141
|
]);
|
|
140
|
-
table(words.educations, [[1, 1, words.period], [2, 2, words.school], [4, 2, words.major], [6, 1, words.degree]], 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 => [
|
|
141
143
|
periodOf(row.startedOn, row.endedOn, words.ongoing),
|
|
142
144
|
row.school,
|
|
143
145
|
row.major,
|
|
@@ -157,19 +159,38 @@ function rosterLayout(people, words, company, today = new Date()) {
|
|
|
157
159
|
...headOf(company, `${words.writtenOn} ${dotted((0, employee_profile_rules_1.todayOf)(today))}`, words.rosterTitle, rosterTaglineOf(people, words, today)),
|
|
158
160
|
{ cells: [], height: 16 },
|
|
159
161
|
{
|
|
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
162
|
cells: [
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
163
|
+
...[words.number, words.name, words.department, words.jobPosition].map((value, index) => ({ col: index + 1, value, role: 'head' })),
|
|
164
|
+
{ col: 5, span: 2, value: words.careerLength, role: 'head' }
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
/*
|
|
168
|
+
* Two lines a person: who, then the skills under the name across the rest of the page - a list too long for one
|
|
169
|
+
* column of six. The number runs down both lines, so it marks where a person starts; a person with no skills
|
|
170
|
+
* written keeps one line.
|
|
171
|
+
*/
|
|
172
|
+
...people.flatMap((person, index) => {
|
|
173
|
+
const shade = index % 2 ? { zebra: true } : {};
|
|
174
|
+
const skills = (person.profile?.skills ?? []).join(', ');
|
|
175
|
+
return [
|
|
176
|
+
{
|
|
177
|
+
cells: [
|
|
178
|
+
{ col: 1, rows: skills ? 2 : 1, value: String(index + 1), role: 'value' },
|
|
179
|
+
...[person.name ?? '', person.departmentName ?? '', person.jobPosition ?? ''].map((value, column) => ({ col: column + 2, value, role: 'value' })),
|
|
180
|
+
{ col: 5, span: 2, value: careerLengthOf(person, words, today), role: 'value' }
|
|
181
|
+
],
|
|
182
|
+
...shade
|
|
183
|
+
},
|
|
184
|
+
...(skills
|
|
185
|
+
? [
|
|
186
|
+
{
|
|
187
|
+
cells: [{ col: 2, span: 5, value: skills, role: 'value' }],
|
|
188
|
+
...shade
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
: [])
|
|
192
|
+
];
|
|
193
|
+
})
|
|
173
194
|
];
|
|
174
195
|
}
|
|
175
196
|
/**
|
|
@@ -233,12 +254,12 @@ const PAINT = {
|
|
|
233
254
|
* 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
255
|
* for wrapped text written by a program. A full-width character is about two of the column's character units.
|
|
235
256
|
*/
|
|
236
|
-
function heightOf(line) {
|
|
257
|
+
function heightOf(line, widths = exports.COLUMN_WIDTHS) {
|
|
237
258
|
const boxed = line.cells.filter(cell => ['label', 'skills', 'summary', 'head', 'value', 'none'].includes(cell.role));
|
|
238
259
|
if (!boxed.length)
|
|
239
260
|
return undefined;
|
|
240
261
|
const wraps = boxed.map(cell => {
|
|
241
|
-
const width =
|
|
262
|
+
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
263
|
return cell.value.split('\n').reduce((lines, text) => {
|
|
243
264
|
const units = [...text].reduce((sum, char) => sum + (char.charCodeAt(0) > 0x2e7f ? 1.9 : 0.9), 0);
|
|
244
265
|
return lines + Math.max(1, Math.ceil(units / Math.max(width, 1)));
|
|
@@ -248,21 +269,24 @@ function heightOf(line) {
|
|
|
248
269
|
return boxed.some(cell => cell.role === 'summary') ? lines * 16 + 14 : lines * 15 + 7;
|
|
249
270
|
}
|
|
250
271
|
/** 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
|
-
|
|
272
|
+
function paintSheet(sheet, lines, footer = '', widths = exports.COLUMN_WIDTHS) {
|
|
273
|
+
widths.forEach((width, index) => (sheet.getColumn(index + 1).width = width));
|
|
253
274
|
lines.forEach((line, index) => {
|
|
254
275
|
const row = sheet.getRow(index + 1);
|
|
255
|
-
const height = Math.max(line.height ?? 0, heightOf(line) ?? 0);
|
|
276
|
+
const height = Math.max(line.height ?? 0, heightOf(line, widths) ?? 0);
|
|
256
277
|
if (height)
|
|
257
278
|
row.height = height;
|
|
258
279
|
for (const cell of line.cells) {
|
|
259
280
|
const span = cell.span ?? 1;
|
|
260
281
|
const style = line.zebra && cell.role === 'value' ? { ...PAINT.value, fill: ground(exports.PALETTE.zebra) } : PAINT[cell.role];
|
|
261
|
-
|
|
262
|
-
|
|
282
|
+
const rows = cell.rows ?? 1;
|
|
283
|
+
const painted = rows > 1 ? { ...style, alignment: { ...style.alignment, vertical: 'top' } } : style;
|
|
284
|
+
for (let at = 0; at < rows; at++)
|
|
285
|
+
for (let col = cell.col; col < cell.col + span; col++)
|
|
286
|
+
Object.assign(sheet.getRow(index + 1 + at).getCell(col), { style: painted });
|
|
263
287
|
row.getCell(cell.col).value = cell.value;
|
|
264
|
-
if (span > 1)
|
|
265
|
-
sheet.mergeCells(index + 1, cell.col, index +
|
|
288
|
+
if (span > 1 || rows > 1)
|
|
289
|
+
sheet.mergeCells(index + 1, cell.col, index + rows, cell.col + span - 1);
|
|
266
290
|
}
|
|
267
291
|
});
|
|
268
292
|
sheet.views = [{ showGridLines: false }];
|
|
@@ -285,7 +309,7 @@ function profileWorkbook(people, words, company, today = new Date()) {
|
|
|
285
309
|
/* The roster's name follows Excel's rules as the people's do. */
|
|
286
310
|
const roster = people.length > 1 ? sheetNamesOf([words.rosterTitle]) : [];
|
|
287
311
|
if (roster.length)
|
|
288
|
-
paintSheet(book.addWorksheet(roster[0]), rosterLayout(people, words, company, today), footer);
|
|
312
|
+
paintSheet(book.addWorksheet(roster[0]), rosterLayout(people, words, company, today), footer, exports.ROSTER_WIDTHS);
|
|
289
313
|
const names = sheetNamesOf(people.map(person => person.name), roster);
|
|
290
314
|
people.forEach((person, index) => paintSheet(book.addWorksheet(names[index]), profileSheetLayout(person, words, company, today), footer));
|
|
291
315
|
return book;
|
|
@@ -303,24 +327,45 @@ function cellHtml(cell) {
|
|
|
303
327
|
return escapeHtml(cell.value).replace(/\n/g, '<br>');
|
|
304
328
|
}
|
|
305
329
|
/** 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 =
|
|
330
|
+
function layoutHtml(lines, widths = exports.COLUMN_WIDTHS) {
|
|
331
|
+
const total = widths.reduce((sum, width) => sum + width, 0);
|
|
332
|
+
const columns = widths.map(width => `<col style="width:${((width / total) * 100).toFixed(2)}%">`).join('');
|
|
333
|
+
/* How many more lines each column is still held by a cell from a line above. */
|
|
334
|
+
const held = [0, 0, 0, 0, 0, 0, 0];
|
|
309
335
|
const rows = lines.map(line => {
|
|
310
336
|
const style = line.height ? ` style="height:${Math.round(line.height * 1.33)}px"` : '';
|
|
337
|
+
const covered = held.map(lines => lines > 0);
|
|
338
|
+
for (let col = 1; col <= 6; col++)
|
|
339
|
+
held[col] = Math.max(0, held[col] - 1);
|
|
311
340
|
if (!line.cells.length)
|
|
312
341
|
return `<tr class="gap"${style}><td colspan="6"></td></tr>`;
|
|
313
|
-
let next = 1;
|
|
314
342
|
const cells = [];
|
|
343
|
+
/* Empty columns between cells, as runs that leave out the columns a cell above still holds. */
|
|
344
|
+
const fill = (from, to) => {
|
|
345
|
+
for (let col = from; col < to;) {
|
|
346
|
+
if (covered[col]) {
|
|
347
|
+
col++;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
let end = col;
|
|
351
|
+
while (end < to && !covered[end])
|
|
352
|
+
end++;
|
|
353
|
+
cells.push(`<td colspan="${end - col}"></td>`);
|
|
354
|
+
col = end;
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
let next = 1;
|
|
315
358
|
for (const cell of line.cells) {
|
|
316
|
-
|
|
317
|
-
cells.push(`<td colspan="${cell.col - next}"></td>`);
|
|
359
|
+
fill(next, cell.col);
|
|
318
360
|
const span = cell.span ?? 1;
|
|
319
|
-
|
|
361
|
+
const rows = cell.rows ?? 1;
|
|
362
|
+
for (let col = cell.col; col < cell.col + span; col++)
|
|
363
|
+
held[col] = rows - 1;
|
|
364
|
+
const attributes = `${span > 1 ? ` colspan="${span}"` : ''}${rows > 1 ? ` rowspan="${rows}" style="vertical-align:top"` : ''}`;
|
|
365
|
+
cells.push(`<td class="${cell.role}"${attributes}>${cellHtml(cell)}</td>`);
|
|
320
366
|
next = cell.col + span;
|
|
321
367
|
}
|
|
322
|
-
|
|
323
|
-
cells.push(`<td colspan="${7 - next}"></td>`);
|
|
368
|
+
fill(next, 7);
|
|
324
369
|
return `<tr${line.zebra ? ' class="zebra"' : ''}${style}>${cells.join('')}</tr>`;
|
|
325
370
|
});
|
|
326
371
|
return `<table><colgroup>${columns}</colgroup>${rows.join('')}</table>`;
|
|
@@ -369,10 +414,10 @@ function profilePreviewHtml(people, words, company, today = new Date()) {
|
|
|
369
414
|
/** The document as one HTML page set: the roster first when there are several, then a page a person. */
|
|
370
415
|
function profileHtml(people, words, company, today = new Date()) {
|
|
371
416
|
const sheets = [
|
|
372
|
-
...(people.length > 1 ? [rosterLayout(people, words, company, today)] : []),
|
|
373
|
-
...people.map(person => profileSheetLayout(person, words, company, today))
|
|
417
|
+
...(people.length > 1 ? [[rosterLayout(people, words, company, today), exports.ROSTER_WIDTHS]] : []),
|
|
418
|
+
...people.map(person => [profileSheetLayout(person, words, company, today), exports.COLUMN_WIDTHS])
|
|
374
419
|
];
|
|
375
|
-
return sheets.map(lines => `<section class="sheet">${layoutHtml(lines)}</section>`).join('');
|
|
420
|
+
return sheets.map(([lines, widths]) => `<section class="sheet">${layoutHtml(lines, widths)}</section>`).join('');
|
|
376
421
|
}
|
|
377
422
|
/** The foot of every printed PDF page: the employer and the document at the left, the page of how many at the right. */
|
|
378
423
|
function profileFooterHtml(company, words) {
|