@chevre/domain 27.1.0-alpha.1 → 27.1.0-alpha.3

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 (45) hide show
  1. package/lib/chevre/repo/acceptedOfferInReserve.d.ts +2 -1
  2. package/lib/chevre/repo/adminAcceptedOffer.d.ts +25 -0
  3. package/lib/chevre/repo/adminAcceptedOffer.js +52 -1
  4. package/lib/chevre/repo/adminEvent.d.ts +236 -0
  5. package/lib/chevre/repo/adminEvent.js +388 -0
  6. package/lib/chevre/repo/aggregateEvent.d.ts +42 -0
  7. package/lib/chevre/repo/aggregateEvent.js +221 -0
  8. package/lib/chevre/repo/event.d.ts +5 -263
  9. package/lib/chevre/repo/event.js +3 -636
  10. package/lib/chevre/repository.d.ts +10 -0
  11. package/lib/chevre/repository.js +24 -2
  12. package/lib/chevre/service/aggregation/event/aggregateScreeningEvent.d.ts +2 -0
  13. package/lib/chevre/service/aggregation/event/aggregateScreeningEvent.js +1 -1
  14. package/lib/chevre/service/aggregation/event/importFromCOA.d.ts +2 -2
  15. package/lib/chevre/service/aggregation/event/importFromCOA.js +1 -1
  16. package/lib/chevre/service/aggregation/system.d.ts +2 -2
  17. package/lib/chevre/service/aggregation/system.js +1 -1
  18. package/lib/chevre/service/event/saveScreeningEvents.d.ts +2 -2
  19. package/lib/chevre/service/event/saveScreeningEvents.js +1 -1
  20. package/lib/chevre/service/event.d.ts +2 -0
  21. package/lib/chevre/service/event.js +1 -1
  22. package/lib/chevre/service/offer/eventServiceByCOA/acceptOffer.js +2 -3
  23. package/lib/chevre/service/offer/onEventChanged.d.ts +2 -0
  24. package/lib/chevre/service/offer/onEventChanged.js +1 -1
  25. package/lib/chevre/service/project.d.ts +3 -3
  26. package/lib/chevre/service/project.js +1 -1
  27. package/lib/chevre/service/task/aggregateScreeningEvent.js +2 -0
  28. package/lib/chevre/service/task/importEventCapacitiesFromCOA.js +2 -2
  29. package/lib/chevre/service/task/importEventsFromCOA.js +2 -0
  30. package/lib/chevre/service/task/onEventChanged.js +2 -0
  31. package/lib/chevre/service/task/onResourceDeleted/deleteResourcesByEventSeries.d.ts +2 -2
  32. package/lib/chevre/service/task/onResourceDeleted/deleteResourcesByEventSeries.js +1 -1
  33. package/lib/chevre/service/task/onResourceDeleted/deleteResourcesByMovieTheater.d.ts +2 -2
  34. package/lib/chevre/service/task/onResourceDeleted/deleteResourcesByMovieTheater.js +1 -1
  35. package/lib/chevre/service/task/onResourceDeleted/deleteResourcesByOfferCatalog.d.ts +2 -2
  36. package/lib/chevre/service/task/onResourceDeleted/deleteResourcesByOfferCatalog.js +1 -1
  37. package/lib/chevre/service/task/onResourceDeleted/deleteResourcesByProduct.d.ts +2 -2
  38. package/lib/chevre/service/task/onResourceDeleted/deleteResourcesByProduct.js +1 -1
  39. package/lib/chevre/service/task/onResourceDeleted/deleteResourcesByRoom.d.ts +2 -2
  40. package/lib/chevre/service/task/onResourceDeleted/deleteResourcesByRoom.js +1 -1
  41. package/lib/chevre/service/task/onResourceDeleted/deleteResourcesBySeller.d.ts +2 -2
  42. package/lib/chevre/service/task/onResourceDeleted/deleteResourcesBySeller.js +1 -1
  43. package/lib/chevre/service/task/onResourceDeleted.js +2 -2
  44. package/lib/chevre/service/task/syncResourcesFromCOA.js +2 -2
  45. package/package.json +1 -1
@@ -0,0 +1,388 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.AdminEventRepo = void 0;
37
+ const mongoose_1 = require("mongoose");
38
+ const errorHandler_1 = require("../errorHandler");
39
+ const factory_1 = require("../factory");
40
+ const EventFactory = __importStar(require("../factory/event"));
41
+ const event_1 = require("./mongoose/schemas/event");
42
+ // export type ICreatingEvent4ttts = Pick<
43
+ // factory.event.screeningEvent.IAttributes,
44
+ // 'additionalProperty' | 'doorTime' | 'endDate' | 'eventStatus'
45
+ // | 'location' | 'name' | 'offers' | 'organizer' | 'project' | 'startDate' | 'superEvent' | 'typeOf'
46
+ // > & {
47
+ // identifier: string;
48
+ // };
49
+ /**
50
+ * イベント管理リポジトリ
51
+ */
52
+ class AdminEventRepo {
53
+ eventModel;
54
+ constructor(connection) {
55
+ this.eventModel = connection.model(event_1.modelName, (0, event_1.createSchema)());
56
+ }
57
+ /**
58
+ * イベントIDを生成する
59
+ */
60
+ static CREATE_ID() {
61
+ // implement using ObjectId(2025-10-17~)
62
+ // return (USE_OBJECT_ID_AS_EVENT_ID) ? new Types.ObjectId().toHexString() : uniqid();
63
+ return new mongoose_1.Types.ObjectId().toHexString(); // 設定を廃止(2025-11-20~)
64
+ }
65
+ /**
66
+ * イベントコードをキーにして冪等置換
67
+ */
68
+ async upsertManyScreeningEventByIdentifier(params, options) {
69
+ const { update } = options;
70
+ const bulkWriteOps = [];
71
+ const queryFilters = [];
72
+ if (Array.isArray(params)) {
73
+ params.forEach(({ $set, $unset }) => {
74
+ const { project, identifier } = $set;
75
+ if (typeof identifier !== 'string' || identifier.length === 0) {
76
+ throw new factory_1.factory.errors.ArgumentNull('identifier');
77
+ }
78
+ // リソースのユニークネスを保証するfilter
79
+ const filter = {
80
+ 'project.id': { $eq: project.id },
81
+ identifier: { $exists: true, $eq: identifier }
82
+ };
83
+ queryFilters.push({
84
+ 'project.id': { $eq: project.id },
85
+ identifier: { $exists: true, $eq: identifier }
86
+ });
87
+ if (update === true) {
88
+ const { maximumPhysicalAttendeeCapacity, additionalProperty, eventStatus, location, name, superEvent, offers, doorTime, endDate, startDate } = $set;
89
+ const setFields = {
90
+ maximumPhysicalAttendeeCapacity, additionalProperty,
91
+ eventStatus, location, name, superEvent, offers,
92
+ doorTime, endDate, startDate
93
+ };
94
+ const updateOne = {
95
+ filter,
96
+ update: {
97
+ $set: setFields,
98
+ ...($unset !== undefined) ? { $unset } : undefined
99
+ },
100
+ upsert: false
101
+ };
102
+ bulkWriteOps.push({ updateOne });
103
+ }
104
+ else {
105
+ const { id, coaInfo, description, // eslint-disable-line @typescript-eslint/no-unused-vars
106
+ maximumAttendeeCapacity, remainingAttendeeCapacity, checkInCount, attendeeCount, aggregateReservation, // eslint-disable-line @typescript-eslint/no-unused-vars
107
+ ...setOnInsertFields } = $set;
108
+ const setOnInsert = {
109
+ ...setOnInsertFields,
110
+ identifier,
111
+ _id: AdminEventRepo.CREATE_ID()
112
+ };
113
+ const updateOne = {
114
+ filter,
115
+ update: {
116
+ $setOnInsert: setOnInsert
117
+ },
118
+ upsert: true
119
+ };
120
+ bulkWriteOps.push({ updateOne });
121
+ }
122
+ });
123
+ }
124
+ if (bulkWriteOps.length > 0) {
125
+ const bulkWriteResult = await this.eventModel.bulkWrite(bulkWriteOps, { ordered: false });
126
+ // update:falseの場合、upsertedIdsのみmodifiedEventsとして返せばよい(2026-01-16~)
127
+ if (!update) {
128
+ // BulkWriteResult -> upsertedIds: { '0': '7iri6p4m54k0r3g' }
129
+ return {
130
+ bulkWriteResult,
131
+ modifiedEvents: Object.values(bulkWriteResult.upsertedIds)
132
+ .map((id) => ({ id: String(id) }))
133
+ };
134
+ }
135
+ else {
136
+ // modifiedの場合upsertedIdsに含まれないので、idを検索する
137
+ const modifiedEvents = await this.eventModel.find({ $or: queryFilters }, {
138
+ _id: 0,
139
+ id: { $toString: '$_id' }
140
+ })
141
+ .lean()
142
+ .exec();
143
+ return { bulkWriteResult, modifiedEvents };
144
+ }
145
+ }
146
+ return { modifiedEvents: [] };
147
+ }
148
+ /**
149
+ * イベント部分更新
150
+ */
151
+ async updatePartiallyById(params) {
152
+ // let doc: Document | null;
153
+ const { typeOf, ...updateFields } = params.attributes;
154
+ if (typeof typeOf !== 'string' || typeOf.length === 0) {
155
+ throw new factory_1.factory.errors.ArgumentNull('attributes.typeOf');
156
+ }
157
+ const doc = await this.eventModel.findOneAndUpdate({
158
+ _id: { $eq: params.id },
159
+ 'project.id': { $eq: params.project.id },
160
+ typeOf: { $eq: typeOf }
161
+ }, {
162
+ $set: updateFields
163
+ }, { upsert: false, new: false, projection: { _id: 1 } })
164
+ .lean()
165
+ .exec();
166
+ if (doc === null) {
167
+ throw new factory_1.factory.errors.NotFound(this.eventModel.modelName);
168
+ }
169
+ }
170
+ /**
171
+ * 単一イベント編集
172
+ */
173
+ async updateEventById(params) {
174
+ let doc;
175
+ const { identifier, project, typeOf, // eslint-disable-line @typescript-eslint/no-unused-vars
176
+ organizer, aggregateReservation, maximumAttendeeCapacity, remainingAttendeeCapacity, checkInCount, attendeeCount, // eslint-disable-line @typescript-eslint/no-unused-vars
177
+ coaInfo, // eslint-disable-line @typescript-eslint/no-unused-vars
178
+ ...updateFields
179
+ // maximumPhysicalAttendeeCapacity, additionalProperty,
180
+ // eventStatus, location, name, superEvent, offers,
181
+ // doorTime, endDate, startDate
182
+ } = params.attributes;
183
+ if (typeof typeOf !== 'string' || typeOf.length === 0) {
184
+ throw new factory_1.factory.errors.ArgumentNull('attributes.typeOf');
185
+ }
186
+ if (typeof params.id !== 'string' || params.id === '') {
187
+ throw new factory_1.factory.errors.ArgumentNull('id');
188
+ }
189
+ try {
190
+ doc = await this.eventModel.findOneAndUpdate({
191
+ _id: { $eq: params.id },
192
+ typeOf: { $eq: typeOf }
193
+ }, {
194
+ // 編集のためのみのメソッドになったので、setOnInsertは廃止(2025-10-27~)
195
+ // 上書き禁止属性を除外(2022-08-24~)
196
+ // $setOnInsert: {
197
+ // typeOf,
198
+ // project,
199
+ // ...(typeof identifier === 'string' && identifier !== '') ? { identifier } : undefined
200
+ // },
201
+ $set: updateFields,
202
+ ...(params.$unset !== undefined) ? { $unset: params.$unset } : undefined
203
+ }, {
204
+ upsert: false,
205
+ new: true,
206
+ projection: { _id: 1 }
207
+ })
208
+ .lean()
209
+ .exec();
210
+ }
211
+ catch (error) {
212
+ if (await (0, errorHandler_1.isMongoDuplicateError)(error)) {
213
+ throw new factory_1.factory.errors.AlreadyInUse(factory_1.factory.eventType.ScreeningEvent, ['offeredBy.member.identifier']);
214
+ }
215
+ throw error;
216
+ }
217
+ if (doc === null) {
218
+ throw new factory_1.factory.errors.NotFound(this.eventModel.modelName);
219
+ }
220
+ }
221
+ /**
222
+ * sskts専用
223
+ */
224
+ async saveManyEvents(params) {
225
+ const bulkWriteOps = []; // eslint-disable-line @typescript-eslint/no-explicit-any
226
+ if (Array.isArray(params)) {
227
+ params.forEach((p) => {
228
+ const upsert = p.upsert === true;
229
+ if (p.attributes.typeOf === factory_1.factory.eventType.ScreeningEvent) {
230
+ // 上書き禁止属性を除外(2022-08-24~)
231
+ const { identifier, project, typeOf, remainingAttendeeCapacity, ...updateFields } = p.attributes; // eslint-disable-line @typescript-eslint/no-unused-vars
232
+ bulkWriteOps.push({
233
+ updateOne: {
234
+ filter: {
235
+ _id: p.id,
236
+ typeOf: p.attributes.typeOf
237
+ },
238
+ update: {
239
+ $setOnInsert: {
240
+ typeOf: p.attributes.typeOf,
241
+ project: p.attributes.project,
242
+ ...(typeof p.attributes.identifier === 'string')
243
+ ? { identifier: p.attributes.identifier } : undefined,
244
+ ...(typeof p.attributes.remainingAttendeeCapacity === 'number')
245
+ ? { remainingAttendeeCapacity: p.attributes.remainingAttendeeCapacity }
246
+ : undefined
247
+ },
248
+ $set: updateFields,
249
+ // $unsetに対応(2022-08-31~)
250
+ ...(p.$unset !== undefined) ? { $unset: p.$unset } : undefined
251
+ },
252
+ upsert
253
+ }
254
+ });
255
+ }
256
+ else if (p.attributes.typeOf === factory_1.factory.eventType.ScreeningEventSeries) {
257
+ throw new factory_1.factory.errors.Internal('typeOf: ScreeningEventSeries discontinued');
258
+ }
259
+ });
260
+ }
261
+ if (bulkWriteOps.length > 0) {
262
+ await this.eventModel.bulkWrite(bulkWriteOps, { ordered: false });
263
+ }
264
+ }
265
+ /**
266
+ * イベントをキャンセルする
267
+ */
268
+ async cancelEvent(params) {
269
+ return this.eventModel.updateOne({
270
+ _id: { $eq: params.id },
271
+ 'project.id': { $eq: params.project.id }
272
+ }, { $set: { eventStatus: factory_1.factory.eventStatusType.EventCancelled } }, {
273
+ // includeResultMetadata: true
274
+ })
275
+ .exec();
276
+ }
277
+ /**
278
+ * 興行イベントのsuperEventを最新の情報に同期する
279
+ */
280
+ async syncScreeningEventSeries2screeningEvents(params) {
281
+ const superEvent = EventFactory.minimizeSuperEvent({ superEventFromDB: params.superEventFromDB });
282
+ await this.eventModel.updateMany({
283
+ typeOf: { $eq: factory_1.factory.eventType.ScreeningEvent },
284
+ 'project.id': { $eq: params.project.id },
285
+ 'superEvent.id': { $exists: true, $eq: params.superEventFromDB.id },
286
+ startDate: { $gte: params.startDate.$gte }
287
+ }, {
288
+ $set: {
289
+ superEvent
290
+ }
291
+ })
292
+ .exec();
293
+ }
294
+ async deleteEventById(params) {
295
+ await this.eventModel.findOneAndDelete({
296
+ _id: params.id,
297
+ 'project.id': { $eq: params.project.id }
298
+ })
299
+ .exec();
300
+ }
301
+ async deleteManyEventByOrganizerId(params) {
302
+ return this.eventModel.deleteMany({
303
+ 'project.id': { $eq: params.project.id },
304
+ 'organizer.id': { $exists: true, $eq: params.organizer.id }
305
+ })
306
+ .exec();
307
+ }
308
+ async deleteManyEventsByScreeningRoom(params) {
309
+ return this.eventModel.deleteMany({
310
+ 'project.id': { $eq: params.project.id },
311
+ 'location.branchCode': { $exists: true, $eq: params.location.branchCode },
312
+ 'superEvent.location.id': { $exists: true, $eq: params.location.containedInPlace.id }
313
+ })
314
+ .exec();
315
+ }
316
+ async deleteManyBySuperEventId(params) {
317
+ return this.eventModel.deleteMany({
318
+ 'project.id': { $eq: params.project.id },
319
+ 'superEvent.id': { $exists: true, $eq: params.superEvent.id }
320
+ })
321
+ .exec();
322
+ }
323
+ async deleteManyBySuperEventLocationId(params) {
324
+ return this.eventModel.deleteMany({
325
+ 'project.id': { $eq: params.project.id },
326
+ 'superEvent.location.id': { $exists: true, $eq: params.superEvent.location.id }
327
+ })
328
+ .exec();
329
+ }
330
+ /**
331
+ * 興行(プロダクト)から削除する
332
+ */
333
+ async deleteManyEventsByItemOfferedId(params) {
334
+ return this.eventModel.deleteMany({
335
+ 'project.id': { $eq: params.project.id },
336
+ 'offers.itemOffered.id': { $exists: true, $in: params.offers.itemOffered.id.$in }
337
+ })
338
+ .exec();
339
+ }
340
+ async deleteManyEventsEndedByProject(params) {
341
+ return this.eventModel.deleteMany({
342
+ typeOf: { $in: params.typeOf.$in },
343
+ 'project.id': { $eq: params.project.id },
344
+ endDate: { $lte: params.endDate.$lte }
345
+ })
346
+ .exec();
347
+ }
348
+ async deleteManyEventsEnded(params) {
349
+ return this.eventModel.deleteMany({
350
+ typeOf: { $eq: params.typeOf.$eq },
351
+ endDate: { $lte: params.endDate.$lte }
352
+ })
353
+ .exec();
354
+ }
355
+ async deleteByProject(params) {
356
+ await this.eventModel.deleteMany({
357
+ 'project.id': { $eq: params.project.id }
358
+ })
359
+ .exec();
360
+ }
361
+ /**
362
+ * 集計属性を更新する
363
+ */
364
+ async updateAggregationById(params, update) {
365
+ const doc = await this.eventModel.findOneAndUpdate({ _id: { $eq: params.id } }, update, {
366
+ new: false,
367
+ projection: { _id: 1 }
368
+ })
369
+ .lean()
370
+ .exec();
371
+ if (doc === null) {
372
+ throw new factory_1.factory.errors.NotFound(this.eventModel.modelName);
373
+ }
374
+ }
375
+ async bulkWrite(bulkWriteOps) {
376
+ return this.eventModel.bulkWrite(bulkWriteOps, { ordered: false });
377
+ }
378
+ getCursor(conditions, projection) {
379
+ return this.eventModel.find(conditions, projection)
380
+ .sort({ startDate: factory_1.factory.sortType.Descending })
381
+ .cursor();
382
+ }
383
+ async unsetUnnecessaryFields(params) {
384
+ return this.eventModel.updateMany(params.filter, { $unset: params.$unset }, { timestamps: false })
385
+ .exec();
386
+ }
387
+ }
388
+ exports.AdminEventRepo = AdminEventRepo;
@@ -0,0 +1,42 @@
1
+ import type { Connection } from 'mongoose';
2
+ import { factory } from '../factory';
3
+ interface IAggregationByStatus {
4
+ eventCount: number;
5
+ reservationCount: number;
6
+ avgOfferCount: number;
7
+ maximumAttendeeCapacity: number;
8
+ remainingAttendeeCapacity: number;
9
+ avgRemainingCapacityRate: number;
10
+ maxRemainingCapacityRate: number;
11
+ minRemainingCapacityRate: number;
12
+ percentilesRemainingCapacityRate: {
13
+ name: string;
14
+ value: number;
15
+ }[];
16
+ }
17
+ interface IStatus {
18
+ status: factory.eventStatusType;
19
+ aggregation: IAggregationByStatus;
20
+ }
21
+ interface IAggregateEvent {
22
+ statuses: IStatus[];
23
+ }
24
+ /**
25
+ * イベント集計リポジトリ
26
+ */
27
+ export declare class AggregateEventRepo {
28
+ private readonly eventModel;
29
+ constructor(connection: Connection);
30
+ aggregateEvent(params: {
31
+ project?: {
32
+ id?: {
33
+ $ne?: string;
34
+ };
35
+ };
36
+ startFrom: Date;
37
+ startThrough: Date;
38
+ typeOf: factory.eventType.ScreeningEvent;
39
+ }): Promise<IAggregateEvent>;
40
+ private agggregateByStatus;
41
+ }
42
+ export {};
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AggregateEventRepo = void 0;
4
+ const factory_1 = require("../factory");
5
+ const event_1 = require("./mongoose/schemas/event");
6
+ /**
7
+ * イベント集計リポジトリ
8
+ */
9
+ class AggregateEventRepo {
10
+ eventModel;
11
+ constructor(connection) {
12
+ this.eventModel = connection.model(event_1.modelName, (0, event_1.createSchema)());
13
+ }
14
+ async aggregateEvent(params) {
15
+ const statuses = await Promise.all([
16
+ factory_1.factory.eventStatusType.EventScheduled,
17
+ factory_1.factory.eventStatusType.EventCancelled,
18
+ factory_1.factory.eventStatusType.EventPostponed
19
+ ].map(async (eventStatus) => {
20
+ const matchConditions = {
21
+ startDate: {
22
+ $gte: params.startFrom,
23
+ $lte: params.startThrough
24
+ },
25
+ typeOf: { $eq: params.typeOf },
26
+ eventStatus: { $eq: eventStatus },
27
+ ...(typeof params.project?.id?.$ne === 'string')
28
+ ? { 'project.id': { $ne: params.project.id.$ne } }
29
+ : undefined
30
+ };
31
+ return this.agggregateByStatus({ matchConditions, status: eventStatus });
32
+ }));
33
+ return { statuses };
34
+ }
35
+ async agggregateByStatus(params) {
36
+ const matchConditions = params.matchConditions;
37
+ const eventStatus = params.status;
38
+ const aggregations = await this.eventModel.aggregate([
39
+ {
40
+ $match: matchConditions
41
+ },
42
+ {
43
+ $project: {
44
+ remainingCapacityRate: {
45
+ $cond: {
46
+ if: {
47
+ $and: [
48
+ { $isNumber: '$maximumAttendeeCapacity' },
49
+ { $isNumber: '$remainingAttendeeCapacity' }
50
+ ]
51
+ },
52
+ then: {
53
+ $cond: {
54
+ if: { $gt: ['$maximumAttendeeCapacity', 0] },
55
+ then: {
56
+ $multiply: [
57
+ { $divide: ['$remainingAttendeeCapacity', '$maximumAttendeeCapacity'] },
58
+ 100
59
+ ]
60
+ },
61
+ else: 0
62
+ }
63
+ },
64
+ else: 0
65
+ }
66
+ },
67
+ eventStatus: '$eventStatus',
68
+ startDate: '$startDate',
69
+ endDate: '$endDate',
70
+ typeOf: '$typeOf',
71
+ maximumAttendeeCapacity: {
72
+ $cond: {
73
+ if: { $isNumber: '$maximumAttendeeCapacity' },
74
+ then: '$maximumAttendeeCapacity',
75
+ else: 0
76
+ }
77
+ },
78
+ remainingAttendeeCapacity: {
79
+ $cond: {
80
+ if: { $isNumber: '$remainingAttendeeCapacity' },
81
+ then: '$remainingAttendeeCapacity',
82
+ else: 0
83
+ }
84
+ },
85
+ reservationCount: {
86
+ $cond: {
87
+ if: { $isNumber: '$aggregateReservation.reservationCount' },
88
+ then: '$aggregateReservation.reservationCount',
89
+ else: 0
90
+ }
91
+ },
92
+ offerCount: {
93
+ $cond: {
94
+ if: { $isNumber: '$aggregateOffer.offerCount' },
95
+ then: '$aggregateOffer.offerCount',
96
+ else: 0
97
+ }
98
+ }
99
+ }
100
+ },
101
+ {
102
+ $group: {
103
+ _id: '$typeOf',
104
+ eventCount: { $sum: 1 },
105
+ reservationCount: { $sum: '$reservationCount' },
106
+ avgOfferCount: { $avg: '$offerCount' },
107
+ maximumAttendeeCapacity: { $sum: '$maximumAttendeeCapacity' },
108
+ remainingAttendeeCapacity: { $sum: '$remainingAttendeeCapacity' },
109
+ avgRemainingCapacityRate: { $avg: '$remainingCapacityRate' },
110
+ maxRemainingCapacityRate: { $max: '$remainingCapacityRate' },
111
+ minRemainingCapacityRate: { $min: '$remainingCapacityRate' }
112
+ }
113
+ },
114
+ {
115
+ $project: {
116
+ _id: 0,
117
+ eventCount: '$eventCount',
118
+ reservationCount: '$reservationCount',
119
+ avgOfferCount: '$avgOfferCount',
120
+ maximumAttendeeCapacity: '$maximumAttendeeCapacity',
121
+ remainingAttendeeCapacity: '$remainingAttendeeCapacity',
122
+ avgRemainingCapacityRate: '$avgRemainingCapacityRate',
123
+ maxRemainingCapacityRate: '$maxRemainingCapacityRate',
124
+ minRemainingCapacityRate: '$minRemainingCapacityRate'
125
+ }
126
+ }
127
+ ])
128
+ .exec();
129
+ const percents = [50, 95, 99];
130
+ if (aggregations.length === 0) {
131
+ return {
132
+ status: eventStatus,
133
+ aggregation: {
134
+ eventCount: 0,
135
+ reservationCount: 0,
136
+ avgOfferCount: 0,
137
+ maximumAttendeeCapacity: 0,
138
+ remainingAttendeeCapacity: 0,
139
+ avgRemainingCapacityRate: 0,
140
+ maxRemainingCapacityRate: 0,
141
+ minRemainingCapacityRate: 0,
142
+ percentilesRemainingCapacityRate: percents.map((percent) => {
143
+ return {
144
+ name: String(percent),
145
+ value: 0
146
+ };
147
+ })
148
+ }
149
+ };
150
+ }
151
+ const ranks4percentile = percents.map((percentile) => {
152
+ return {
153
+ percentile,
154
+ rank: Math.floor(aggregations[0].eventCount * percentile / 100)
155
+ };
156
+ });
157
+ const aggregations2 = await this.eventModel.aggregate([
158
+ {
159
+ $match: matchConditions
160
+ },
161
+ {
162
+ $project: {
163
+ remainingCapacityRate: {
164
+ $cond: {
165
+ if: {
166
+ $and: [
167
+ { $isNumber: '$maximumAttendeeCapacity' },
168
+ { $isNumber: '$remainingAttendeeCapacity' }
169
+ ]
170
+ },
171
+ then: {
172
+ $cond: {
173
+ if: { $gt: ['$maximumAttendeeCapacity', 0] },
174
+ then: {
175
+ $multiply: [
176
+ { $divide: ['$remainingAttendeeCapacity', '$maximumAttendeeCapacity'] },
177
+ 100
178
+ ]
179
+ },
180
+ else: 0
181
+ }
182
+ },
183
+ else: 0
184
+ }
185
+ },
186
+ eventStatus: '$eventStatus',
187
+ startDate: '$startDate',
188
+ endDate: '$endDate',
189
+ typeOf: '$typeOf'
190
+ }
191
+ },
192
+ { $sort: { remainingCapacityRate: 1 } },
193
+ {
194
+ $group: {
195
+ _id: '$typeOf',
196
+ remainingCapacityRates: { $push: '$remainingCapacityRate' }
197
+ }
198
+ },
199
+ {
200
+ $project: {
201
+ _id: 0,
202
+ percentilesRemainingCapacityRate: ranks4percentile.map((rank) => {
203
+ return {
204
+ name: String(rank.percentile),
205
+ value: { $arrayElemAt: ['$remainingCapacityRates', rank.rank] }
206
+ };
207
+ })
208
+ }
209
+ }
210
+ ])
211
+ .exec();
212
+ return {
213
+ status: eventStatus,
214
+ aggregation: {
215
+ ...aggregations[0],
216
+ ...aggregations2[0]
217
+ }
218
+ };
219
+ }
220
+ }
221
+ exports.AggregateEventRepo = AggregateEventRepo;