@things-factory/work-shift 10.1.34 → 10.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist-client/pages/work-shift.d.ts +18 -102
  2. package/dist-client/pages/work-shift.js +165 -781
  3. package/dist-client/pages/work-shift.js.map +1 -1
  4. package/dist-client/tsconfig.tsbuildinfo +1 -1
  5. package/dist-server/controllers/work-shift-range.d.ts +25 -9
  6. package/dist-server/controllers/work-shift-range.js +49 -196
  7. package/dist-server/controllers/work-shift-range.js.map +1 -1
  8. package/dist-server/controllers/work-shift-schedule.d.ts +2 -0
  9. package/dist-server/controllers/work-shift-schedule.js +9 -18
  10. package/dist-server/controllers/work-shift-schedule.js.map +1 -1
  11. package/dist-server/service/index.d.ts +5 -2
  12. package/dist-server/service/index.js +3 -0
  13. package/dist-server/service/index.js.map +1 -1
  14. package/dist-server/service/work-shift/index.d.ts +4 -4
  15. package/dist-server/service/work-shift/index.js +4 -3
  16. package/dist-server/service/work-shift/index.js.map +1 -1
  17. package/dist-server/service/work-shift/shift-occurrence.d.ts +19 -0
  18. package/dist-server/service/work-shift/shift-occurrence.js +34 -0
  19. package/dist-server/service/work-shift/shift-occurrence.js.map +1 -0
  20. package/dist-server/service/work-shift/work-shift-arrangement.d.ts +10 -0
  21. package/dist-server/service/work-shift/work-shift-arrangement.js +40 -0
  22. package/dist-server/service/work-shift/work-shift-arrangement.js.map +1 -0
  23. package/dist-server/service/work-shift/work-shift-content.d.ts +15 -0
  24. package/dist-server/service/work-shift/work-shift-content.js +55 -0
  25. package/dist-server/service/work-shift/work-shift-content.js.map +1 -0
  26. package/dist-server/service/work-shift/work-shift-lifecycle-resolver.d.ts +60 -0
  27. package/dist-server/service/work-shift/work-shift-lifecycle-resolver.js +77 -0
  28. package/dist-server/service/work-shift/work-shift-lifecycle-resolver.js.map +1 -0
  29. package/dist-server/service/work-shift/work-shift-lifecycle.d.ts +130 -0
  30. package/dist-server/service/work-shift/work-shift-lifecycle.js +508 -0
  31. package/dist-server/service/work-shift/work-shift-lifecycle.js.map +1 -0
  32. package/dist-server/service/work-shift/work-shift-mutation.js +9 -42
  33. package/dist-server/service/work-shift/work-shift-mutation.js.map +1 -1
  34. package/dist-server/service/work-shift/work-shift-query.js +30 -15
  35. package/dist-server/service/work-shift/work-shift-query.js.map +1 -1
  36. package/dist-server/service/work-shift/work-shift-revision-service.d.ts +29 -65
  37. package/dist-server/service/work-shift/work-shift-revision-service.js +67 -167
  38. package/dist-server/service/work-shift/work-shift-revision-service.js.map +1 -1
  39. package/dist-server/service/work-shift/work-shift-revision.d.ts +78 -62
  40. package/dist-server/service/work-shift/work-shift-revision.js +303 -82
  41. package/dist-server/service/work-shift/work-shift-revision.js.map +1 -1
  42. package/dist-server/service/work-shift/work-shift-type.d.ts +0 -5
  43. package/dist-server/service/work-shift/work-shift-type.js +1 -16
  44. package/dist-server/service/work-shift/work-shift-type.js.map +1 -1
  45. package/dist-server/tsconfig.tsbuildinfo +1 -1
  46. package/helps/page/work-shift.ko.md +19 -3
  47. package/helps/page/work-shift.md +17 -9
  48. package/package.json +6 -3
  49. package/dist-server/service/work-shift/work-shift-revision-query.d.ts +0 -20
  50. package/dist-server/service/work-shift/work-shift-revision-query.js +0 -74
  51. package/dist-server/service/work-shift/work-shift-revision-query.js.map +0 -1
@@ -3,37 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WorkShiftMutation = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const type_graphql_1 = require("type-graphql");
6
- const typeorm_1 = require("typeorm");
6
+ const shell_1 = require("@things-factory/shell");
7
+ const work_shift_lifecycle_1 = require("./work-shift-lifecycle");
7
8
  const work_shift_1 = require("./work-shift");
8
9
  const work_shift_type_1 = require("./work-shift-type");
9
- const work_shift_revision_service_1 = require("./work-shift-revision-service");
10
- const work_shift_revision_1 = require("./work-shift-revision");
11
10
  let WorkShiftMutation = class WorkShiftMutation {
12
11
  async updateMultipleWorkShift(patches, context) {
13
- const { domain, user, tx } = context.state;
14
- let results = [];
15
- const workShiftRepo = tx.getRepository(work_shift_1.WorkShift);
16
- /*
17
- * 지우기 **전에** 지금 편성을 남긴다.
18
- *
19
- * 이 저장은 그 도메인의 교대를 전부 지우고 다시 넣는다. 그래서 오탈자 하나를 고쳐도 이전 편성이
20
- * 데이터베이스에서 없어졌고, 「6월의 주간은 몇 시부터였나」에 답할 방법이 없었다.
21
- *
22
- * 지운 뒤에 남기려 하면 남길 것이 없다. 순서가 이 한 줄의 전부다.
23
- */
24
- await (0, work_shift_revision_service_1.recordRevision)(tx, domain, (0, work_shift_revision_service_1.shiftSetOf)(patches), user);
25
- await workShiftRepo.delete({ domain: { id: domain.id } });
26
- for (let i = 0; i < patches.length; i++) {
27
- const patch = patches[i];
28
- const result = await workShiftRepo.save({
29
- ...patch,
30
- domain,
31
- creator: user,
32
- updater: user
33
- });
34
- results.push({ ...result, cuFlag: '+' });
35
- }
36
- return results;
12
+ throw new work_shift_lifecycle_1.WorkShiftRefusal('REVISION_REQUIRED');
37
13
  }
38
14
  /**
39
15
  * 앞으로 쓸 편성을 미리 적어 둔다 — 「다음 달 1일부터 3교대」.
@@ -42,24 +18,14 @@ let WorkShiftMutation = class WorkShiftMutation {
42
18
  * 편성은 제 시각에 바뀐다.
43
19
  */
44
20
  async announceWorkShift(effectiveFrom, patches, context) {
45
- const { domain, user, tx } = context.state;
46
- return await (0, work_shift_revision_service_1.announceShiftSet)(tx, domain, effectiveFrom, (0, work_shift_revision_service_1.shiftSetOf)(patches), user);
21
+ throw new work_shift_lifecycle_1.WorkShiftRefusal('REVISION_REQUIRED');
47
22
  }
48
23
  /** 예고를 물린다. 아직 시작하지 않은 것만 물릴 수 있다. */
49
24
  async cancelWorkShiftAnnouncement(id, context) {
50
- const { domain, tx } = context.state;
51
- return await (0, work_shift_revision_service_1.cancelAnnouncement)(tx, domain, id);
25
+ throw new work_shift_lifecycle_1.WorkShiftRefusal('REVISION_REQUIRED');
52
26
  }
53
27
  async deleteWorkShifts(ids, context) {
54
- const { domain, tx, user } = context.state;
55
- /* 지우는 것도 편성이 바뀌는 것이다. 이 길로 지우면 이력이 안 남던 구멍이 있었다. */
56
- const remaining = (await tx.getRepository(work_shift_1.WorkShift).find({ where: { domain: { id: domain.id } } })).filter(shift => !ids.includes(shift.id));
57
- await (0, work_shift_revision_service_1.recordRevision)(tx, domain, (0, work_shift_revision_service_1.shiftSetOf)(remaining), user);
58
- await tx.getRepository(work_shift_1.WorkShift).delete({
59
- domain: { id: domain.id },
60
- id: (0, typeorm_1.In)(ids)
61
- });
62
- return true;
28
+ throw new work_shift_lifecycle_1.WorkShiftRefusal('REVISION_REQUIRED');
63
29
  }
64
30
  };
65
31
  exports.WorkShiftMutation = WorkShiftMutation;
@@ -76,7 +42,7 @@ tslib_1.__decorate([
76
42
  tslib_1.__decorate([
77
43
  (0, type_graphql_1.Directive)('@privilege(category: "work-shift", privilege: "mutation", domainOwnerGranted: true)'),
78
44
  (0, type_graphql_1.Directive)('@transaction'),
79
- (0, type_graphql_1.Mutation)(returns => work_shift_revision_1.WorkShiftRevision, { description: 'To announce a work shift arrangement that starts later' }),
45
+ (0, type_graphql_1.Mutation)(returns => shell_1.ScalarObject, { description: 'To announce a work shift arrangement that starts later' }),
80
46
  tslib_1.__param(0, (0, type_graphql_1.Arg)('effectiveFrom')),
81
47
  tslib_1.__param(1, (0, type_graphql_1.Arg)('patches', type => [work_shift_type_1.WorkShiftPatch])),
82
48
  tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
@@ -105,6 +71,7 @@ tslib_1.__decorate([
105
71
  tslib_1.__metadata("design:returntype", Promise)
106
72
  ], WorkShiftMutation.prototype, "deleteWorkShifts", null);
107
73
  exports.WorkShiftMutation = WorkShiftMutation = tslib_1.__decorate([
108
- (0, type_graphql_1.Resolver)(work_shift_1.WorkShift)
74
+ (0, type_graphql_1.Resolver)(work_shift_1.WorkShift),
75
+ (0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals)
109
76
  ], WorkShiftMutation);
110
77
  //# sourceMappingURL=work-shift-mutation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"work-shift-mutation.js","sourceRoot":"","sources":["../../../server/service/work-shift/work-shift-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Mutation, Resolver } from 'type-graphql'\nimport { In } from 'typeorm'\n\nimport { WorkShift } from './work-shift'\nimport { WorkShiftPatch } from './work-shift-type'\nimport {\n announceShiftSet,\n cancelAnnouncement,\n recordRevision,\n shiftSetOf\n} from './work-shift-revision-service'\nimport { WorkShiftRevision } from './work-shift-revision'\n\n@Resolver(WorkShift)\nexport class WorkShiftMutation {\n @Directive('@privilege(category: \"work-shift\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [WorkShift], { description: \"To modify multiple WorkShifts' information\" })\n async updateMultipleWorkShift(\n @Arg('patches', type => [WorkShiftPatch]) patches: WorkShiftPatch[],\n @Ctx() context: ResolverContext\n ): Promise<WorkShift[]> {\n const { domain, user, tx } = context.state\n\n let results = []\n const workShiftRepo = tx.getRepository(WorkShift)\n\n /*\n * 지우기 **전에** 지금 편성을 남긴다.\n *\n * 이 저장은 그 도메인의 교대를 전부 지우고 다시 넣는다. 그래서 오탈자 하나를 고쳐도 이전 편성이\n * 데이터베이스에서 없어졌고, 「6월의 주간은 몇 시부터였나」에 답할 방법이 없었다.\n *\n * 지운 뒤에 남기려 하면 남길 것이 없다. 순서가 이 한 줄의 전부다.\n */\n await recordRevision(tx, domain, shiftSetOf(patches as unknown as WorkShift[]), user)\n\n await workShiftRepo.delete({ domain: { id: domain.id } })\n\n for (let i = 0; i < patches.length; i++) {\n const patch = patches[i]\n\n const result = await workShiftRepo.save({\n ...patch,\n domain,\n creator: user,\n updater: user\n })\n\n results.push({ ...result, cuFlag: '+' })\n }\n\n return results\n }\n\n /**\n * 앞으로 쓸 편성을 미리 적어 둔다 — 「다음 달 1일부터 3교대」.\n *\n * 그날이 오면 **저절로** 그 편성이 답이 된다. 옮기는 일감이 없으므로 그날 서버가 내려가 있어도\n * 편성은 제 시각에 바뀐다.\n */\n @Directive('@privilege(category: \"work-shift\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => WorkShiftRevision, { description: 'To announce a work shift arrangement that starts later' })\n async announceWorkShift(\n @Arg('effectiveFrom') effectiveFrom: Date,\n @Arg('patches', type => [WorkShiftPatch]) patches: WorkShiftPatch[],\n @Ctx() context: ResolverContext\n ): Promise<WorkShiftRevision> {\n const { domain, user, tx } = context.state\n\n return await announceShiftSet(tx, domain, effectiveFrom, shiftSetOf(patches as unknown as WorkShift[]), user)\n }\n\n /** 예고를 물린다. 아직 시작하지 않은 것만 물릴 수 있다. */\n @Directive('@privilege(category: \"work-shift\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Boolean, { description: 'To cancel an announced work shift arrangement' })\n async cancelWorkShiftAnnouncement(\n @Arg('id') id: string,\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n const { domain, tx } = context.state\n\n return await cancelAnnouncement(tx, domain, id)\n }\n\n @Directive('@privilege(category: \"work-shift\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Boolean, { description: 'To delete multiple workShifts' })\n async deleteWorkShifts(\n @Arg('ids', type => [String]) ids: string[],\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n const { domain, tx, user } = context.state\n\n /* 지우는 것도 편성이 바뀌는 것이다. 이 길로 지우면 이력이 안 남던 구멍이 있었다. */\n const remaining = (await tx.getRepository(WorkShift).find({ where: { domain: { id: domain.id } } })).filter(\n shift => !ids.includes(shift.id)\n )\n\n await recordRevision(tx, domain, shiftSetOf(remaining), user)\n\n await tx.getRepository(WorkShift).delete({\n domain: { id: domain.id },\n id: In(ids)\n })\n\n return true\n }\n}\n"]}
1
+ {"version":3,"file":"work-shift-mutation.js","sourceRoot":"","sources":["../../../server/service/work-shift/work-shift-mutation.ts"],"names":[],"mappings":";;;;AAAA,+CAAqF;AACrF,iDAAmE;AACnE,iEAAyD;AAEzD,6CAAwC;AACxC,uDAAkD;AAW3C,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAItB,AAAN,KAAK,CAAC,uBAAuB,CACe,OAAyB,EAC5D,OAAwB;QAE/B,MAAM,IAAI,uCAAgB,CAAC,mBAAmB,CAAC,CAAA;IACjD,CAAC;IAED;;;;;OAKG;IAIG,AAAN,KAAK,CAAC,iBAAiB,CACC,aAAmB,EACC,OAAyB,EAC5D,OAAwB;QAE/B,MAAM,IAAI,uCAAgB,CAAC,mBAAmB,CAAC,CAAA;IACjD,CAAC;IAED,sCAAsC;IAIhC,AAAN,KAAK,CAAC,2BAA2B,CACpB,EAAU,EACd,OAAwB;QAE/B,MAAM,IAAI,uCAAgB,CAAC,mBAAmB,CAAC,CAAA;IACjD,CAAC;IAKK,AAAN,KAAK,CAAC,gBAAgB,CACU,GAAa,EACpC,OAAwB;QAE/B,MAAM,IAAI,uCAAgB,CAAC,mBAAmB,CAAC,CAAA;IACjD,CAAC;CACF,CAAA;AAhDY,8CAAiB;AAItB;IAHL,IAAA,wBAAS,EAAC,qFAAqF,CAAC;IAChG,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,sBAAS,CAAC,EAAE,EAAE,WAAW,EAAE,4CAA4C,EAAE,CAAC;IAE7F,mBAAA,IAAA,kBAAG,EAAC,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,gCAAc,CAAC,CAAC,CAAA;IACxC,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;gEAGP;AAWK;IAHL,IAAA,wBAAS,EAAC,qFAAqF,CAAC;IAChG,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,WAAW,EAAE,wDAAwD,EAAE,CAAC;IAE1G,mBAAA,IAAA,kBAAG,EAAC,eAAe,CAAC,CAAA;IACpB,mBAAA,IAAA,kBAAG,EAAC,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,gCAAc,CAAC,CAAC,CAAA;IACxC,mBAAA,IAAA,kBAAG,GAAE,CAAA;;6CAF+B,IAAI;;0DAK1C;AAMK;IAHL,IAAA,wBAAS,EAAC,qFAAqF,CAAC;IAChG,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,+CAA+C,EAAE,CAAC;IAE5F,mBAAA,IAAA,kBAAG,EAAC,IAAI,CAAC,CAAA;IACT,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;oEAGP;AAKK;IAHL,IAAA,wBAAS,EAAC,qFAAqF,CAAC;IAChG,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,+BAA+B,EAAE,CAAC;IAE5E,mBAAA,IAAA,kBAAG,EAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;IAC5B,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;yDAGP;4BA/CU,iBAAiB;IAF7B,IAAA,uBAAQ,EAAC,sBAAS,CAAC;IACnB,IAAA,4BAAa,EAAC,qBAAa,CAAC;GAChB,iBAAiB,CAgD7B","sourcesContent":["import { Arg, Ctx, Directive, Mutation, Resolver, UseMiddleware } from 'type-graphql'\nimport { ScalarObject, SpeakRefusals } from '@things-factory/shell'\nimport { WorkShiftRefusal } from './work-shift-lifecycle'\n\nimport { WorkShift } from './work-shift'\nimport { WorkShiftPatch } from './work-shift-type'\nimport {\n announceShiftSet,\n cancelAnnouncement,\n recordRevision,\n shiftSetOf\n} from './work-shift-revision-service'\nimport { WorkShiftRevision } from './work-shift-revision'\n\n@Resolver(WorkShift)\n@UseMiddleware(SpeakRefusals)\nexport class WorkShiftMutation {\n @Directive('@privilege(category: \"work-shift\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [WorkShift], { description: \"To modify multiple WorkShifts' information\" })\n async updateMultipleWorkShift(\n @Arg('patches', type => [WorkShiftPatch]) patches: WorkShiftPatch[],\n @Ctx() context: ResolverContext\n ): Promise<WorkShift[]> {\n throw new WorkShiftRefusal('REVISION_REQUIRED')\n }\n\n /**\n * 앞으로 쓸 편성을 미리 적어 둔다 — 「다음 달 1일부터 3교대」.\n *\n * 그날이 오면 **저절로** 그 편성이 답이 된다. 옮기는 일감이 없으므로 그날 서버가 내려가 있어도\n * 편성은 제 시각에 바뀐다.\n */\n @Directive('@privilege(category: \"work-shift\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => ScalarObject, { description: 'To announce a work shift arrangement that starts later' })\n async announceWorkShift(\n @Arg('effectiveFrom') effectiveFrom: Date,\n @Arg('patches', type => [WorkShiftPatch]) patches: WorkShiftPatch[],\n @Ctx() context: ResolverContext\n ): Promise<WorkShiftRevision> {\n throw new WorkShiftRefusal('REVISION_REQUIRED')\n }\n\n /** 예고를 물린다. 아직 시작하지 않은 것만 물릴 수 있다. */\n @Directive('@privilege(category: \"work-shift\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Boolean, { description: 'To cancel an announced work shift arrangement' })\n async cancelWorkShiftAnnouncement(\n @Arg('id') id: string,\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n throw new WorkShiftRefusal('REVISION_REQUIRED')\n }\n\n @Directive('@privilege(category: \"work-shift\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Boolean, { description: 'To delete multiple workShifts' })\n async deleteWorkShifts(\n @Arg('ids', type => [String]) ids: string[],\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n throw new WorkShiftRefusal('REVISION_REQUIRED')\n }\n}\n"]}
@@ -7,6 +7,7 @@ const auth_base_1 = require("@things-factory/auth-base");
7
7
  const shell_1 = require("@things-factory/shell");
8
8
  const index_1 = require("../../controllers/index");
9
9
  const work_shift_1 = require("./work-shift");
10
+ const work_shift_revision_service_1 = require("./work-shift-revision-service");
10
11
  const work_shift_type_1 = require("./work-shift-type");
11
12
  let DomainWorkShiftQuery = class DomainWorkShiftQuery {
12
13
  async getWorkDateAndShift(dateTime, context) {
@@ -14,15 +15,7 @@ let DomainWorkShiftQuery = class DomainWorkShiftQuery {
14
15
  return await (0, index_1.getWorkDateAndShift)(domain, dateTime);
15
16
  }
16
17
  async workShifts(domain) {
17
- return await (0, shell_1.getRepository)(work_shift_1.WorkShift).find({
18
- where: {
19
- domain: { id: domain.id }
20
- },
21
- order: {
22
- fromDate: 'ASC',
23
- fromTime: 'ASC'
24
- }
25
- });
18
+ return (0, work_shift_revision_service_1.effectiveWorkShifts)((0, shell_1.getRepository)(work_shift_1.WorkShift).manager, domain);
26
19
  }
27
20
  };
28
21
  exports.DomainWorkShiftQuery = DomainWorkShiftQuery;
@@ -47,13 +40,35 @@ exports.DomainWorkShiftQuery = DomainWorkShiftQuery = tslib_1.__decorate([
47
40
  let WorkShiftQuery = class WorkShiftQuery {
48
41
  async workShifts(params, context) {
49
42
  const { domain } = context.state;
50
- const queryBuilder = (0, shell_1.getQueryBuilderFromListParams)({
51
- repository: (0, shell_1.getRepository)(work_shift_1.WorkShift),
52
- params,
53
- domain
43
+ let rows = await (0, work_shift_revision_service_1.effectiveWorkShifts)((0, shell_1.getRepository)(work_shift_1.WorkShift).manager, domain);
44
+ if (params.pagination?.after)
45
+ throw new Error('Work shift projection uses page pagination');
46
+ if (params.search) {
47
+ const search = params.search.toLocaleLowerCase();
48
+ rows = rows.filter(row => [row.name, row.description].some(value => value?.toLocaleLowerCase().includes(search)));
49
+ }
50
+ for (const filter of params.filters ?? []) {
51
+ if (filter.name !== 'name' || !['eq', 'like'].includes(filter.operator))
52
+ throw new Error('Unsupported work shift filter');
53
+ rows = rows.filter(row => filter.operator === 'eq' ? row.name === filter.value : row.name.includes(String(filter.value)));
54
+ }
55
+ const sortings = params.sortings ?? [];
56
+ for (const sorting of sortings) {
57
+ if (!['name', 'description', 'fromDate', 'fromTime', 'toDate', 'toTime'].includes(sorting.name))
58
+ throw new Error('Unsupported work shift sorting');
59
+ }
60
+ rows.sort((a, b) => {
61
+ for (const sorting of sortings) {
62
+ const left = a[sorting.name], right = b[sorting.name];
63
+ const difference = typeof left === 'number' ? left - right : String(left ?? '').localeCompare(String(right ?? ''));
64
+ if (difference)
65
+ return sorting.desc ? -difference : difference;
66
+ }
67
+ return 0;
54
68
  });
55
- const [items, total] = await queryBuilder.getManyAndCount();
56
- return { items, total };
69
+ const total = rows.length, limit = Math.max(1, Math.min(params.pagination?.limit ?? 64, 64));
70
+ const offset = Math.max(0, ((params.pagination?.page ?? 1) - 1) * limit);
71
+ return { items: rows.slice(offset, offset + limit), total };
57
72
  }
58
73
  async domain(workShift) {
59
74
  return await (0, shell_1.getRepository)(shell_1.Domain).findOneBy({ id: workShift.domainId });
@@ -1 +1 @@
1
- {"version":3,"file":"work-shift-query.js","sourceRoot":"","sources":["../../../server/service/work-shift/work-shift-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Args, Ctx, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { User } from '@things-factory/auth-base'\nimport { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'\n\nimport { getWorkDateAndShift } from '../../controllers/index'\nimport { WorkShift } from './work-shift'\nimport { WorkShiftInfo, WorkShiftList } from './work-shift-type'\n\n@Resolver(Domain)\nexport class DomainWorkShiftQuery {\n @Query(returns => WorkShiftInfo, { description: 'To fetch a work date and work shift for given datetime' })\n async getWorkDateAndShift(@Arg('dateTime') dateTime: Date, @Ctx() context: ResolverContext): Promise<WorkShiftInfo> {\n const { domain } = context.state\n\n return await getWorkDateAndShift(domain, dateTime)\n }\n\n @FieldResolver(type => [WorkShift])\n async workShifts(@Root() domain: Domain): Promise<WorkShift[]> {\n return await getRepository(WorkShift).find({\n where: {\n domain: { id: domain.id }\n },\n order: {\n fromDate: 'ASC',\n fromTime: 'ASC'\n }\n })\n }\n}\n\n@Resolver(WorkShift)\nexport class WorkShiftQuery {\n @Query(returns => WorkShiftList, { description: 'To fetch multiple WorkShifts' })\n async workShifts(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<WorkShiftList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n repository: getRepository(WorkShift),\n params,\n domain\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n @FieldResolver(type => Domain)\n async domain(@Root() workShift: WorkShift): Promise<Domain> {\n return await getRepository(Domain).findOneBy({ id: workShift.domainId })\n }\n\n @FieldResolver(type => User)\n async updater(@Root() workShift: WorkShift): Promise<User> {\n return await getRepository(User).findOneBy({ id: workShift.updaterId })\n }\n\n @FieldResolver(type => User)\n async creator(@Root() workShift: WorkShift): Promise<User> {\n return await getRepository(User).findOneBy({ id: workShift.creatorId })\n }\n}\n"]}
1
+ {"version":3,"file":"work-shift-query.js","sourceRoot":"","sources":["../../../server/service/work-shift/work-shift-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Args, Ctx, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { User } from '@things-factory/auth-base'\nimport { Domain, getRepository, ListParam } from '@things-factory/shell'\n\nimport { getWorkDateAndShift } from '../../controllers/index'\nimport { WorkShift } from './work-shift'\nimport { effectiveWorkShifts } from './work-shift-revision-service'\nimport { WorkShiftInfo, WorkShiftList } from './work-shift-type'\n\n@Resolver(Domain)\nexport class DomainWorkShiftQuery {\n @Query(returns => WorkShiftInfo, { description: 'To fetch a work date and work shift for given datetime' })\n async getWorkDateAndShift(@Arg('dateTime') dateTime: Date, @Ctx() context: ResolverContext): Promise<WorkShiftInfo> {\n const { domain } = context.state\n\n return await getWorkDateAndShift(domain, dateTime)\n }\n\n @FieldResolver(type => [WorkShift])\n async workShifts(@Root() domain: Domain): Promise<WorkShift[]> {\n return effectiveWorkShifts(getRepository(WorkShift).manager, domain)\n }\n}\n\n@Resolver(WorkShift)\nexport class WorkShiftQuery {\n @Query(returns => WorkShiftList, { description: 'To fetch multiple WorkShifts' })\n async workShifts(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<WorkShiftList> {\n const { domain } = context.state\n\n let rows = await effectiveWorkShifts(getRepository(WorkShift).manager, domain)\n if (params.pagination?.after) throw new Error('Work shift projection uses page pagination')\n if (params.search) {\n const search = params.search.toLocaleLowerCase()\n rows = rows.filter(row => [row.name, row.description].some(value => value?.toLocaleLowerCase().includes(search)))\n }\n for (const filter of params.filters ?? []) {\n if (filter.name !== 'name' || !['eq','like'].includes(filter.operator)) throw new Error('Unsupported work shift filter')\n rows = rows.filter(row => filter.operator === 'eq' ? row.name === filter.value : row.name.includes(String(filter.value)))\n }\n const sortings = params.sortings ?? []\n for (const sorting of sortings) {\n if (!['name', 'description', 'fromDate', 'fromTime', 'toDate', 'toTime'].includes(sorting.name)) throw new Error('Unsupported work shift sorting')\n }\n rows.sort((a, b) => {\n for (const sorting of sortings) {\n const left = a[sorting.name], right = b[sorting.name]\n const difference = typeof left === 'number' ? left - right : String(left ?? '').localeCompare(String(right ?? ''))\n if (difference) return sorting.desc ? -difference : difference\n }\n return 0\n })\n const total = rows.length, limit = Math.max(1, Math.min(params.pagination?.limit ?? 64, 64))\n const offset = Math.max(0, ((params.pagination?.page ?? 1) - 1) * limit)\n return { items: rows.slice(offset, offset + limit), total }\n }\n\n @FieldResolver(type => Domain)\n async domain(@Root() workShift: WorkShift): Promise<Domain> {\n return await getRepository(Domain).findOneBy({ id: workShift.domainId })\n }\n\n @FieldResolver(type => User)\n async updater(@Root() workShift: WorkShift): Promise<User> {\n return await getRepository(User).findOneBy({ id: workShift.updaterId })\n }\n\n @FieldResolver(type => User)\n async creator(@Root() workShift: WorkShift): Promise<User> {\n return await getRepository(User).findOneBy({ id: workShift.creatorId })\n }\n}\n"]}
@@ -1,69 +1,33 @@
1
1
  import { EntityManager } from 'typeorm';
2
2
  import { Domain } from '@things-factory/shell';
3
- import { User } from '@things-factory/auth-base';
4
3
  import { WorkShift } from './work-shift';
5
- import { WorkShiftRevision } from './work-shift-revision';
6
- import { SetOrigin } from './work-shift-selection';
7
- /**
8
- * 편성이 바뀔 지나간 것을 남기고, 지난 시각의 편성을 되찾는다.
9
- */
10
- /** 편성 한 벌에서 남길 것만 뽑는다. id·감사 칸은 지워진 행의 것이라 되살려도 쓸 데가 없다. */
11
- export declare function shiftSetOf(shifts: WorkShift[]): Record<string, unknown>[];
12
- /** 두 편성이 같은가. 같으면 남기지 않는다 — 바뀌지 않은 저장으로 이력이 늘면 읽을 수 없게 된다. */
13
- export declare function sameShiftSet(before: Record<string, unknown>[], after: Record<string, unknown>[]): boolean;
14
- /**
15
- * 지금 편성이 **언제부터**인가.
16
- *
17
- * 저장이 전량 삭제 후 재삽입이라, 지금 행들 중 가장 이른 `createdAt` 이 곧 설치 시각이다. 행이 없으면
18
- * 편성이 없는 것이므로 없다고 답한다 — 지어내면 그 시각 이전의 사실이 지금 편성으로 읽힌다.
19
- */
20
- export declare function inForceSince(shifts: WorkShift[]): Date | undefined;
21
- /**
22
- * 바꾸기 **전에** 지금 쓰던 편성을 남긴다.
23
- *
24
- * ── 순서가 이 함수의 전부다 ────────────────────────────────────────────────
25
- * 저장은 그 도메인의 교대를 전부 지우고 다시 넣는다. 지운 뒤에 남기려 하면 남길 것이 없다.
26
- *
27
- * ── 지금 쓰던 것이 편집기의 내용이 아닐 수 있다 ────────────────────────────
28
- * 예고한 시각이 지났으면 **예고분이 적용 중**이고, 편집기(`WorkShift`)의 내용은 그때 이미 답이 아니다.
29
- * 그 상태에서 편집기 내용만 이력에 남기면 실제로 쓰이던 편성이 기록에서 빠진다.
30
- *
31
- * 그리고 그 열린 벌을 닫지 않으면 **저장한 것이 적용되지 않는다** — 시각으로 고를 때 열린 벌이 계속
32
- * 이기기 때문이다. 사람은 저장했는데 아무것도 안 바뀌는 것을 보게 된다.
33
- *
34
- * 적용 중이던 예고분은 닫는 것만으로 이력이 된다. 따로 한 줄 더 남기면 같은 편성이 두 번 적힌다.
35
- */
36
- export declare function recordRevision(manager: EntityManager, domain: Domain, next: Record<string, unknown>[], user?: User, now?: Date): Promise<WorkShiftRevision | undefined>;
37
- /**
38
- * 그 시각에 쓰던 편성 — **지금 편성이 아닐 수 있다.**
39
- *
40
- * 지나간 시각이면 이력에서, 예고한 시각이 지났으면 예고분에서 고른다. 고르는 규칙은
41
- * `work-shift-selection.ts` 에 순수하게 있다.
42
- *
43
- * **이력보다 앞선 시각은 없다고 답한다.** 그때 무엇을 쓰고 있었는지 이 시스템은 모른다.
44
- */
4
+ import { WorkShiftSegment } from './work-shift-revision';
5
+ /** Every field affecting execution belongs to the signed arrangement. */
6
+ export declare function shiftSetOf(shifts: Partial<WorkShift>[]): Record<string, unknown>[];
7
+ export declare function sameShiftSet(a: Record<string, unknown>[], b: Record<string, unknown>[]): boolean;
45
8
  export declare function shiftsInForceAt(manager: EntityManager, domain: Domain, at: Date): Promise<{
46
- shifts: Record<string, unknown>[];
47
- origin: SetOrigin;
48
- from?: Date;
49
- } | undefined>;
50
- /**
51
- * 앞으로 쓸 편성을 미리 적어 둔다.
52
- *
53
- * ── 지난 시각으로는 예고하지 않는다 ────────────────────────────────────────
54
- * 지난 시각으로 넣으면 이미 적힌 사실들의 교대 이름이 통째로 바뀐다. 그것은 예고가 아니라 **기록을
55
- * 고치는 것**이고, 이 문으로 할 일이 아니다.
56
- *
57
- * ── 앞의 열린 벌을 닫는다 ──────────────────────────────────────────────────
58
- * 끝이 없는 벌이 둘이면 어느 것이 답인지 갈린다. 예고의 시작이 앞엣것의 끝이 된다.
59
- */
60
- export declare function announceShiftSet(manager: EntityManager, domain: Domain, effectiveFrom: Date, shifts: Record<string, unknown>[], user?: User, now?: Date): Promise<WorkShiftRevision>;
61
- /**
62
- * 예고를 물린다.
63
- *
64
- * **아직 시작하지 않은 것만** 물릴 수 있다. 이미 적용 중인 벌을 지우면 그 사이에 적힌 사실들이 가리키던
65
- * 편성이 사라진다 — 그때는 지우는 것이 아니라 새로 예고하거나 지금 편성을 저장한다.
66
- */
67
- export declare function cancelAnnouncement(manager: EntityManager, domain: Domain, id: string, now?: Date): Promise<boolean>;
68
- /** 아직 시작하지 않은 벌들 — 화면이 「예고된 편성」으로 보여 줄 것. */
69
- export declare function announcementsOf(manager: EntityManager, domain: Domain, now?: Date): Promise<WorkShiftRevision[]>;
9
+ shifts: import("./work-shift-content").ShiftDefinition[];
10
+ timezone: string;
11
+ origin: "revision";
12
+ from: Date;
13
+ to: Date;
14
+ revisionId: string;
15
+ revisionNumber: number;
16
+ creatorId: string;
17
+ createdAt: Date;
18
+ }>;
19
+ export declare function effectiveWorkShifts(manager: EntityManager, domain: Domain, at?: Date): Promise<WorkShift[]>;
20
+ /** Bounded expansion for OEE/planning windows; absolute intervals preserve zone/DST and revision boundaries. */
21
+ export declare function shiftArrangementsDuring(manager: EntityManager, domain: Domain, from: Date, to: Date): Promise<{
22
+ revision: import("./work-shift-revision").WorkShiftRevision;
23
+ segment: WorkShiftSegment;
24
+ occurrences: {
25
+ name: string;
26
+ from: Date;
27
+ to: Date;
28
+ }[];
29
+ }[]>;
30
+ /** Old mutation/service entry points must never rewrite a signed schedule. */
31
+ export declare function recordRevision(..._args: any[]): Promise<never>;
32
+ export declare function announceShiftSet(..._args: any[]): Promise<never>;
33
+ export declare function cancelAnnouncement(..._args: any[]): Promise<never>;
@@ -2,185 +2,85 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.shiftSetOf = shiftSetOf;
4
4
  exports.sameShiftSet = sameShiftSet;
5
- exports.inForceSince = inForceSince;
6
- exports.recordRevision = recordRevision;
7
5
  exports.shiftsInForceAt = shiftsInForceAt;
6
+ exports.effectiveWorkShifts = effectiveWorkShifts;
7
+ exports.shiftArrangementsDuring = shiftArrangementsDuring;
8
+ exports.recordRevision = recordRevision;
8
9
  exports.announceShiftSet = announceShiftSet;
9
10
  exports.cancelAnnouncement = cancelAnnouncement;
10
- exports.announcementsOf = announcementsOf;
11
- const typeorm_1 = require("typeorm");
11
+ const tslib_1 = require("tslib");
12
12
  const work_shift_1 = require("./work-shift");
13
+ const work_shift_arrangement_1 = require("./work-shift-arrangement");
13
14
  const work_shift_revision_1 = require("./work-shift-revision");
14
- const work_shift_selection_1 = require("./work-shift-selection");
15
- /**
16
- * 편성이 바뀔 때 지나간 것을 남기고, 지난 시각의 편성을 되찾는다.
17
- */
18
- /** 편성 한 벌에서 남길 것만 뽑는다. id·감사 칸은 지워진 행의 것이라 되살려도 쓸 데가 없다. */
15
+ const work_shift_lifecycle_1 = require("./work-shift-lifecycle");
16
+ const shift_occurrence_1 = require("./shift-occurrence");
17
+ const moment_timezone_1 = tslib_1.__importDefault(require("moment-timezone"));
18
+ /** Every field affecting execution belongs to the signed arrangement. */
19
19
  function shiftSetOf(shifts) {
20
- return shifts
21
- .map(shift => ({
22
- name: shift.name,
23
- fromDate: shift.fromDate,
24
- fromTime: shift.fromTime,
25
- toDate: shift.toDate,
26
- toTime: shift.toTime
27
- }))
28
- .sort((a, b) => a.fromDate - b.fromDate || String(a.fromTime).localeCompare(String(b.fromTime)));
29
- }
30
- /** 두 편성이 같은가. 같으면 남기지 않는다 — 바뀌지 않은 저장으로 이력이 늘면 읽을 수 없게 된다. */
31
- function sameShiftSet(before, after) {
32
- return JSON.stringify(before) === JSON.stringify(after);
33
- }
34
- /**
35
- * 지금 편성이 **언제부터**인가.
36
- *
37
- * 저장이 전량 삭제 후 재삽입이라, 지금 행들 중 가장 이른 `createdAt` 이 곧 설치 시각이다. 행이 없으면
38
- * 편성이 없는 것이므로 없다고 답한다 — 지어내면 그 시각 이전의 사실이 지금 편성으로 읽힌다.
39
- */
40
- function inForceSince(shifts) {
41
- const times = shifts.map(shift => shift.createdAt?.getTime()).filter((time) => Boolean(time));
42
- return times.length ? new Date(Math.min(...times)) : undefined;
20
+ return shifts.map(s => ({ name: s.name, description: s.description ?? '', fromDate: s.fromDate,
21
+ fromTime: s.fromTime, toDate: s.toDate, toTime: s.toTime, daysOfWeek: s.daysOfWeek ?? '' }))
22
+ .sort((a, b) => a.fromDate - b.fromDate || a.fromTime.localeCompare(b.fromTime));
43
23
  }
44
- /**
45
- * 바꾸기 **전에** 지금 쓰던 편성을 남긴다.
46
- *
47
- * ── 순서가 이 함수의 전부다 ────────────────────────────────────────────────
48
- * 저장은 그 도메인의 교대를 전부 지우고 다시 넣는다. 지운 뒤에 남기려 하면 남길 것이 없다.
49
- *
50
- * ── 지금 쓰던 것이 편집기의 내용이 아닐 수 있다 ────────────────────────────
51
- * 예고한 시각이 지났으면 **예고분이 적용 중**이고, 편집기(`WorkShift`)의 내용은 그때 이미 답이 아니다.
52
- * 그 상태에서 편집기 내용만 이력에 남기면 실제로 쓰이던 편성이 기록에서 빠진다.
53
- *
54
- * 그리고 그 열린 벌을 닫지 않으면 **저장한 것이 적용되지 않는다** — 시각으로 고를 때 열린 벌이 계속
55
- * 이기기 때문이다. 사람은 저장했는데 아무것도 안 바뀌는 것을 보게 된다.
56
- *
57
- * 적용 중이던 예고분은 닫는 것만으로 이력이 된다. 따로 한 줄 더 남기면 같은 편성이 두 번 적힌다.
58
- */
59
- async function recordRevision(manager, domain, next, user, now = new Date()) {
60
- /* 지금 실제로 적용 중인 벌 — 편집기의 것일 수도, 시작된 예고분일 수도 있다. */
61
- const started = await manager.findOne(work_shift_revision_1.WorkShiftRevision, {
62
- where: { domain: { id: domain.id }, effectiveTo: (0, typeorm_1.IsNull)() },
63
- order: { effectiveFrom: 'DESC' }
64
- });
65
- if (started && started.effectiveFrom <= now) {
66
- if (sameShiftSet(started.shifts, next)) {
67
- /* 적용 중인 예고분과 같은 내용이다. 닫으면 뒤가 빈 구간이 되므로 그대로 둔다. */
68
- return undefined;
69
- }
70
- /* 닫는 것만으로 이력이 된다. 그리고 닫아야 이번 저장이 적용된다. */
71
- await manager.update(work_shift_revision_1.WorkShiftRevision, { id: started.id }, { effectiveTo: now });
72
- return { ...started, effectiveTo: now };
73
- }
74
- const current = await manager.find(work_shift_1.WorkShift, { where: { domain: { id: domain.id } } });
75
- if (!current.length) {
76
- /* 첫 설치다. 지나간 편성이 없다. */
24
+ function sameShiftSet(a, b) { return JSON.stringify(a) === JSON.stringify(b); }
25
+ async function shiftsInForceAt(manager, domain, at) {
26
+ const root = await manager.findOneBy(work_shift_arrangement_1.WorkShiftArrangement, { domain: { id: domain.id } });
27
+ if (!root || !root.planGeneration)
77
28
  return undefined;
29
+ try {
30
+ const selected = await (0, work_shift_lifecycle_1.resolveWorkShift)(manager, domain.id, root.id, at);
31
+ return { shifts: selected.revision.shifts, timezone: selected.revision.timezone,
32
+ origin: 'revision', from: selected.segment.validFrom, to: selected.segment.validTo,
33
+ revisionId: selected.revision.id, revisionNumber: selected.revision.revisionNumber,
34
+ creatorId: selected.revision.creatorId, createdAt: selected.revision.createdAt };
78
35
  }
79
- const before = shiftSetOf(current);
80
- if (sameShiftSet(before, next)) {
81
- /* 내용이 그대로다. 남기면 이력이 「바뀌지 않음」으로 채워져 읽을 수 없게 된다. */
82
- return undefined;
36
+ catch (error) {
37
+ if (error instanceof work_shift_lifecycle_1.WorkShiftRefusal && error.code === 'NO_EFFECTIVE_REVISION')
38
+ return undefined;
39
+ throw error;
83
40
  }
84
- return await manager.save(work_shift_revision_1.WorkShiftRevision, {
85
- domain,
86
- /* 언제부터 쓰던 것인가 — 모르면 그만둔 시각으로 둔다(길이 0인 구간). 지어내지 않는다. */
87
- effectiveFrom: inForceSince(current) ?? now,
88
- effectiveTo: now,
89
- shifts: before,
90
- creator: user
91
- });
92
41
  }
93
- /** 도메인의 지나간 벌과 예고된 전부. 고르는 일은 순수 함수가 한다. */
94
- async function revisionsOf(manager, domain) {
95
- return await manager.find(work_shift_revision_1.WorkShiftRevision, {
96
- where: { domain: { id: domain.id } },
97
- order: { effectiveFrom: 'ASC' }
98
- });
42
+ async function effectiveWorkShifts(manager, domain, at = new Date()) {
43
+ const selected = await shiftsInForceAt(manager, domain, at);
44
+ return (selected?.shifts ?? []).map(shift => Object.assign(new work_shift_1.WorkShift(), shift, {
45
+ id: selected.revisionId + '/' + shift.name, domain, domainId: domain.id,
46
+ creatorId: selected.creatorId, updaterId: selected.creatorId,
47
+ createdAt: selected.createdAt, updatedAt: selected.createdAt
48
+ }));
99
49
  }
100
- /**
101
- * 시각에 쓰던 편성 — **지금 편성이 아닐 수 있다.**
102
- *
103
- * 지나간 시각이면 이력에서, 예고한 시각이 지났으면 예고분에서 고른다. 고르는 규칙은
104
- * `work-shift-selection.ts` 순수하게 있다.
105
- *
106
- * **이력보다 앞선 시각은 없다고 답한다.** 그때 무엇을 쓰고 있었는지 이 시스템은 모른다.
107
- */
108
- async function shiftsInForceAt(manager, domain, at) {
109
- const current = await manager.find(work_shift_1.WorkShift, {
110
- where: { domain: { id: domain.id } },
111
- order: { fromDate: 'ASC', fromTime: 'ASC' }
50
+ /** Bounded expansion for OEE/planning windows; absolute intervals preserve zone/DST and revision boundaries. */
51
+ async function shiftArrangementsDuring(manager, domain, from, to) {
52
+ if (!(to > from) || to.getTime() - from.getTime() > 366 * 86400000)
53
+ throw new work_shift_lifecycle_1.WorkShiftRefusal('INVALID_INSTANT', 'Choose a window of at most 366 days.');
54
+ const root = await manager.findOneBy(work_shift_arrangement_1.WorkShiftArrangement, { domain: { id: domain.id } });
55
+ if (!root?.planGeneration)
56
+ return [];
57
+ const plan = await manager.findOneByOrFail(work_shift_revision_1.WorkShiftPlan, { domain: { id: domain.id }, arrangement: { id: root.id }, generation: root.planGeneration });
58
+ const segments = await manager.createQueryBuilder(work_shift_revision_1.WorkShiftSegment, 's').innerJoinAndSelect('s.revision', 'r')
59
+ .where('s.plan_id = :id AND s.domain_id = :domainId', { id: plan.id, domainId: domain.id })
60
+ .andWhere('s.validFrom < :to AND (s.validTo IS NULL OR s.validTo > :from)', { from, to }).getMany();
61
+ return segments.map(segment => {
62
+ const revision = segment.revision;
63
+ if (!revision.frozenAt || revision.contentHash !== (0, work_shift_lifecycle_1.workShiftSubmissionHash)(revision))
64
+ throw new work_shift_lifecycle_1.WorkShiftRefusal('REVISION_INTEGRITY_FAILURE');
65
+ const begin = new Date(Math.max(from.getTime(), segment.validFrom.getTime()));
66
+ const end = new Date(Math.min(to.getTime(), segment.validTo?.getTime() ?? Infinity));
67
+ const cursor = (0, moment_timezone_1.default)(begin).tz(revision.timezone).startOf('day').subtract(1, 'day');
68
+ const last = (0, moment_timezone_1.default)(end).tz(revision.timezone).startOf('day').add(1, 'day');
69
+ const occurrences = [];
70
+ for (; cursor <= last; cursor.add(1, 'day'))
71
+ for (const shift of revision.shifts) {
72
+ const w = (0, shift_occurrence_1.shiftOccurrence)(shift, cursor.format('YYYY-MM-DD'), revision.timezone);
73
+ if (!w)
74
+ continue;
75
+ const left = new Date(Math.max(begin.getTime(), w.from.getTime())), right = new Date(Math.min(end.getTime(), w.to.getTime()));
76
+ if (right > left)
77
+ occurrences.push({ name: w.name, from: left, to: right });
78
+ }
79
+ return { revision, segment, occurrences };
112
80
  });
113
- const revisions = await revisionsOf(manager, domain);
114
- const selected = (0, work_shift_selection_1.selectSetAt)(at, current.length ? shiftSetOf(current) : undefined, inForceSince(current), revisions.map(revision => ({
115
- effectiveFrom: revision.effectiveFrom,
116
- effectiveTo: revision.effectiveTo,
117
- shifts: revision.shifts
118
- })));
119
- return selected.shifts ? { shifts: selected.shifts, origin: selected.origin, from: selected.from } : undefined;
120
- }
121
- /**
122
- * 앞으로 쓸 편성을 미리 적어 둔다.
123
- *
124
- * ── 지난 시각으로는 예고하지 않는다 ────────────────────────────────────────
125
- * 지난 시각으로 넣으면 이미 적힌 사실들의 교대 이름이 통째로 바뀐다. 그것은 예고가 아니라 **기록을
126
- * 고치는 것**이고, 이 문으로 할 일이 아니다.
127
- *
128
- * ── 앞의 열린 벌을 닫는다 ──────────────────────────────────────────────────
129
- * 끝이 없는 벌이 둘이면 어느 것이 답인지 갈린다. 새 예고의 시작이 앞엣것의 끝이 된다.
130
- */
131
- async function announceShiftSet(manager, domain, effectiveFrom, shifts, user, now = new Date()) {
132
- if (effectiveFrom <= now) {
133
- throw new Error('announceShiftSet: 예고는 앞으로의 시각으로만 한다 — 지난 시각으로 넣으면 이미 적힌 사실의 교대가 바뀐다');
134
- }
135
- if (!shifts.length) {
136
- throw new Error('announceShiftSet: 빈 편성은 예고할 수 없다 — 그 시각부터 교대가 없다는 뜻이 되어 버린다');
137
- }
138
- const open = await manager.findOne(work_shift_revision_1.WorkShiftRevision, {
139
- where: { domain: { id: domain.id }, effectiveTo: (0, typeorm_1.IsNull)() },
140
- order: { effectiveFrom: 'DESC' }
141
- });
142
- if (open) {
143
- if (open.effectiveFrom >= effectiveFrom) {
144
- throw new Error('announceShiftSet: 이미 더 늦은 예고가 있다 — 그것을 먼저 물리고 다시 예고한다');
145
- }
146
- /* 앞의 열린 벌은 새 예고가 시작하는 시각에 끝난다. */
147
- await manager.update(work_shift_revision_1.WorkShiftRevision, { id: open.id }, { effectiveTo: effectiveFrom });
148
- }
149
- return await manager.save(work_shift_revision_1.WorkShiftRevision, {
150
- domain,
151
- effectiveFrom,
152
- effectiveTo: null,
153
- shifts,
154
- creator: user
155
- });
156
- }
157
- /**
158
- * 예고를 물린다.
159
- *
160
- * **아직 시작하지 않은 것만** 물릴 수 있다. 이미 적용 중인 벌을 지우면 그 사이에 적힌 사실들이 가리키던
161
- * 편성이 사라진다 — 그때는 지우는 것이 아니라 새로 예고하거나 지금 편성을 저장한다.
162
- */
163
- async function cancelAnnouncement(manager, domain, id, now = new Date()) {
164
- const found = await manager.findOne(work_shift_revision_1.WorkShiftRevision, { where: { id, domain: { id: domain.id } } });
165
- if (!found) {
166
- return false;
167
- }
168
- if (found.effectiveFrom <= now) {
169
- throw new Error('cancelAnnouncement: 이미 적용된 편성은 물릴 수 없다 — 그 사이에 적힌 사실이 이것을 가리킨다');
170
- }
171
- await manager.delete(work_shift_revision_1.WorkShiftRevision, { id: found.id });
172
- /* 앞의 벌이 이 예고 때문에 닫혀 있었다면 다시 열어 준다 — 아니면 그 뒤가 빈 구간이 된다. */
173
- const previous = await manager.findOne(work_shift_revision_1.WorkShiftRevision, {
174
- where: { domain: { id: domain.id }, effectiveTo: found.effectiveFrom },
175
- order: { effectiveFrom: 'DESC' }
176
- });
177
- if (previous) {
178
- await manager.update(work_shift_revision_1.WorkShiftRevision, { id: previous.id }, { effectiveTo: null });
179
- }
180
- return true;
181
- }
182
- /** 아직 시작하지 않은 벌들 — 화면이 「예고된 편성」으로 보여 줄 것. */
183
- async function announcementsOf(manager, domain, now = new Date()) {
184
- return (0, work_shift_selection_1.announcedSets)(now, await revisionsOf(manager, domain));
185
81
  }
82
+ /** Old mutation/service entry points must never rewrite a signed schedule. */
83
+ async function recordRevision(..._args) { throw new work_shift_lifecycle_1.WorkShiftRefusal('REVISION_REQUIRED'); }
84
+ async function announceShiftSet(..._args) { throw new work_shift_lifecycle_1.WorkShiftRefusal('REVISION_REQUIRED'); }
85
+ async function cancelAnnouncement(..._args) { throw new work_shift_lifecycle_1.WorkShiftRefusal('REVISION_REQUIRED'); }
186
86
  //# sourceMappingURL=work-shift-revision-service.js.map