@things-factory/organization 10.1.228 → 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.
@@ -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
- searchables: ['name', 'description', 'controlNo']
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
- searchables: ['name', 'description', 'controlNo']
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
- searchables: ['name', 'description', 'controlNo']
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.Field)(type => [employee_1.Employee]),
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":"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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 } 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 @OneToMany(type => Employee, employee => employee.department)\n @Field(type => [Employee])\n members?: Employee[]\n\n @ManyToOne(type => Employee, manager => manager.manages, { nullable: 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"]}
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
- /** A cell of the six-column grid: `col` from 1, spanning `span` columns. */
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
- String(index + 1),
165
- person.name ?? '',
166
- person.departmentName ?? '',
167
- person.jobPosition ?? '',
168
- careerLengthOf(person, words, today),
169
- (person.profile?.skills ?? []).join(', ')
170
- ].map((value, column) => ({ col: column + 1, value, role: 'value' })),
171
- ...(index % 2 ? { zebra: true } : {})
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 = exports.COLUMN_WIDTHS.slice(cell.col - 1, cell.col - 1 + (cell.span ?? 1)).reduce((sum, one) => sum + one, 0) - (cell.role === 'summary' ? 4 : 1);
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
- exports.COLUMN_WIDTHS.forEach((width, index) => (sheet.getColumn(index + 1).width = width));
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
- for (let col = cell.col; col < cell.col + span; col++)
262
- Object.assign(row.getCell(col), { style });
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 + 1, cell.col + span - 1);
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 = exports.COLUMN_WIDTHS.reduce((sum, width) => sum + width, 0);
308
- const columns = exports.COLUMN_WIDTHS.map(width => `<col style="width:${((width / total) * 100).toFixed(2)}%">`).join('');
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
- if (cell.col > next)
317
- cells.push(`<td colspan="${cell.col - next}"></td>`);
376
+ fill(next, cell.col);
318
377
  const span = cell.span ?? 1;
319
- cells.push(`<td class="${cell.role}"${span > 1 ? ` colspan="${span}"` : ''}>${cellHtml(cell)}</td>`);
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
- if (next <= 6)
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) {