UNPKG

@chevre/domain

Version:

Chevre Domain Library for Node.js

291 lines (290 loc) 15.2 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.aggregateScreeningEvent = aggregateScreeningEvent; /** * イベントの予約集計サービス */ const createDebug = require("debug"); const moment = require("moment-timezone"); const factory = require("../../../factory"); // import { Settings } from '../../../settings'; const findEventOffers_1 = require("./findEventOffers"); const debug = createDebug('chevre-domain:service:aggregation'); /** * イベントデータをID指定で集計する */ function aggregateScreeningEvent(params) { return (repos // settings: Settings ) => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d; // 集計対象イベント検索 const event = yield repos.event.projectEventFieldsById({ id: params.id }, ['location', 'project', 'startDate', 'typeOf', 'superEvent.id', 'superEvent.location.id', 'offers.itemOffered']); // プロジェクト設定検索 const project = yield repos.project.findById({ id: event.project.id, inclusion: ['settings'] }); const useOfferRateLimit = ((_a = project.settings) === null || _a === void 0 ? void 0 : _a.useOfferRateLimit) === true; let useAggregateOffer = false; if (event.typeOf === factory.eventType.ScreeningEvent) { const eventSeriesIdsByProject = (_c = (_b = project.settings) === null || _b === void 0 ? void 0 : _b.useAggregateReservation) === null || _c === void 0 ? void 0 : _c.eventSeriesIds; const eventSeriesIdIncluded = Array.isArray(eventSeriesIdsByProject) && eventSeriesIdsByProject.includes(event.superEvent.id); const useAggregateOfferByProject = ((_d = project.settings) === null || _d === void 0 ? void 0 : _d.useAggregateOffer) === true; useAggregateOffer = eventSeriesIdIncluded && useAggregateOfferByProject; } let aggregatingEvents = [event]; // dependent on project settings(2024-10-29~) if (useOfferRateLimit) { const availableOffers = yield (0, findEventOffers_1.findEventOffers)({ event })(repos); const offerRateLimitExists = availableOffers.some((o) => { var _a; return typeof ((_a = o.validRateLimit) === null || _a === void 0 ? void 0 : _a.scope) === 'string'; }); if (offerRateLimitExists) { // 同location、かつ同時間帯、のイベントに関しても集計する(ttts暫定対応) const startFrom = moment(event.startDate) .startOf('hour') .toDate(); const startThrough = moment(startFrom) .add(1, 'hour') .add(-1, 'second') .toDate(); if (event.typeOf === factory.eventType.Event) { aggregatingEvents = yield repos.event.projectEventFields({ limit: 100, page: 1, project: { id: { $eq: event.project.id } }, typeOf: event.typeOf, eventStatuses: [factory.eventStatusType.EventScheduled], startFrom: startFrom, startThrough: startThrough, location: { branchCode: { $eq: event.location.branchCode } } }, ['location', 'project', 'startDate', 'typeOf', 'superEvent.location.id', 'offers.itemOffered']); } else if (event.typeOf === factory.eventType.ScreeningEvent) { aggregatingEvents = yield repos.event.projectEventFields({ limit: 100, page: 1, project: { id: { $eq: event.project.id } }, typeOf: event.typeOf, eventStatuses: [factory.eventStatusType.EventScheduled], startFrom: startFrom, startThrough: startThrough, location: { branchCode: { $eq: event.location.branchCode } } }, ['location', 'project', 'startDate', 'typeOf', 'superEvent.location.id', 'offers.itemOffered']); } // ID指定されたイベントについてはEventScheduledでなくても集計したいので、集計対象を調整 aggregatingEvents = aggregatingEvents.filter((e) => e.id !== event.id); aggregatingEvents = [event, ...aggregatingEvents]; } } debug(aggregatingEvents.length, 'aggregatingEvents found', aggregatingEvents.map((e) => e.id)); for (const aggregatingEvent of aggregatingEvents) { yield aggregateByEvent({ event: aggregatingEvent }, { useAggregateOffer })(repos); } }); } function aggregateByEvent(params, options) { return (repos // settings: Settings ) => __awaiter(this, void 0, void 0, function* () { var _a; const now = new Date(); const event = params.event; // 施設取得は冗長なので、ルーム検索に変更(2023-01-30~) let movieTheaterId; if (params.event.typeOf === factory.eventType.ScreeningEvent) { movieTheaterId = params.event.superEvent.location.id; } else { movieTheaterId = String((_a = params.event.offers) === null || _a === void 0 ? void 0 : _a.itemOffered.availableChannel.serviceLocation.containedInPlace.id); } const screeningRoom = yield repos.screeningRoom.findScreeningRoomsByBranchCode({ project: { id: event.project.id }, branchCode: { $eq: event.location.branchCode }, containedInPlace: { id: { $eq: movieTheaterId } } }); // 予約集計 const { maximumAttendeeCapacity, remainingAttendeeCapacity, aggregateReservation } = yield aggregateReservationByEvent({ aggregateDate: now, event: event, screeningRoom })(repos); // オファーごとの集計 const aggregateOffer = yield aggregateOfferByEvent({ aggregateDate: now, event, screeningRoom })(repos); debug('offers aggregated', aggregateOffer); // 値がundefinedの場合に更新しないように注意 const update = { $set: Object.assign(Object.assign(Object.assign(Object.assign({ // updatedAt: new Date(), // $setオブジェクトが空だとMongoエラーになるので aggregateReservation, aggregateOffer }, (maximumAttendeeCapacity !== undefined) ? { maximumAttendeeCapacity: maximumAttendeeCapacity } : undefined), (remainingAttendeeCapacity !== undefined) ? { remainingAttendeeCapacity: remainingAttendeeCapacity } : undefined), (aggregateReservation.checkInCount !== undefined) ? { checkInCount: aggregateReservation.checkInCount } : undefined), (aggregateReservation.attendeeCount !== undefined) ? { attendeeCount: aggregateReservation.attendeeCount } : undefined), $unset: Object.assign(Object.assign({ noExistingAttributeName: 1 }, (maximumAttendeeCapacity === undefined) ? { maximumAttendeeCapacity: '' } : undefined), (remainingAttendeeCapacity === undefined) ? { remainingAttendeeCapacity: '' } : undefined) }; debug('update:', update); // 保管 yield repos.event.updateAggregationById({ id: event.id }, update); yield onAggregated({ event }, options)({ task: repos.task } // settings ); }); } /** * 集計後アクション */ function onAggregated(params, options) { return (repos) => __awaiter(this, void 0, void 0, function* () { // dependent on project settings(2024-10-29~) if (options.useAggregateOffer) { // AggregateOffersタスクへ移行(2024-03-25~) const aggregateOffersTaskAttributes = { project: params.event.project, name: factory.taskName.AggregateOffers, status: factory.taskStatus.Ready, runsAt: new Date(), remainingNumberOfTries: 10, numberOfTried: 0, executionResults: [], data: { typeOf: factory.eventType.ScreeningEvent, id: params.event.id } }; yield repos.task.saveMany([aggregateOffersTaskAttributes], { emitImmediately: true }); } }); } function reservedSeatsAvailable(params) { var _a, _b, _c, _d; return ((_d = (_c = (_b = (_a = params.event.offers) === null || _a === void 0 ? void 0 : _a.itemOffered) === null || _b === void 0 ? void 0 : _b.serviceOutput) === null || _c === void 0 ? void 0 : _c.reservedTicket) === null || _d === void 0 ? void 0 : _d.ticketedSeat) !== undefined; } function aggregateOfferByEvent(params) { return (repos) => __awaiter(this, void 0, void 0, function* () { const { offerCount } = yield calculateOfferCount({ event: params.event })(repos); return { typeOf: factory.offerType.AggregateOffer, aggregateDate: params.aggregateDate, offerCount }; }); } function calculateOfferCount(params) { return (repos) => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d; let offerCount = 0; try { const eventOffers = params.event.offers; if (typeof ((_a = eventOffers === null || eventOffers === void 0 ? void 0 : eventOffers.itemOffered) === null || _a === void 0 ? void 0 : _a.id) === 'string') { const eventService = (yield repos.product.projectFields({ limit: 1, page: 1, id: { $eq: eventOffers.itemOffered.id } }, ['hasOfferCatalog'] // [] )).shift(); if (eventService === undefined) { throw new factory.errors.NotFound(factory.product.ProductType.EventService); } // const firstCatalogIdOfProduct = eventService.hasOfferCatalog?.id; // migrate to itemListElement(2024-09-30~) const firstCatalogIdOfProduct = (_c = (_b = eventService.hasOfferCatalog) === null || _b === void 0 ? void 0 : _b.itemListElement.at(0)) === null || _c === void 0 ? void 0 : _c.id; if (typeof firstCatalogIdOfProduct === 'string') { const catalogs = yield repos.offerCatalog.projectFields({ limit: 1, page: 1, id: { $in: [firstCatalogIdOfProduct] } }, ['numberOfItems']); const numberOfItems = (_d = catalogs.shift()) === null || _d === void 0 ? void 0 : _d.numberOfItems; if (typeof numberOfItems === 'number') { offerCount = numberOfItems; } } } } catch (error) { throw error; } return { offerCount }; }); } function aggregateReservationByEvent(params) { return (repos) => __awaiter(this, void 0, void 0, function* () { // 収容人数を集計 let maximumAttendeeCapacity; let remainingAttendeeCapacity; let attendeeCount; let checkInCount; let reservationCount; let reservationType = factory.reservationType.EventReservation; if (params.event.typeOf === factory.eventType.Event) { reservationType = factory.reservationType.BusReservation; } reservationCount = yield repos.reservation.count({ typeOf: reservationType, reservationFor: { id: { $eq: params.event.id } }, reservationStatuses: [factory.reservationStatusType.ReservationConfirmed] }); // maximumAttendeeCapacityを決定 const eventLocationMaximumAttendeeCapacity = params.event.location.maximumAttendeeCapacity; if (typeof eventLocationMaximumAttendeeCapacity === 'number') { maximumAttendeeCapacity = eventLocationMaximumAttendeeCapacity; } const hasTicketedSeat = reservedSeatsAvailable({ event: params.event }); if (hasTicketedSeat) { // seatCountを利用する(2023-06-24~) const screeningRoomSeatCount = (typeof params.screeningRoom.seatCount === 'number') ? params.screeningRoom.seatCount : 0; maximumAttendeeCapacity = screeningRoomSeatCount; // イベントのキャパシティ設定がスクリーン座席数より小さければmaximumAttendeeCapacityを上書き if (typeof eventLocationMaximumAttendeeCapacity === 'number' && eventLocationMaximumAttendeeCapacity < screeningRoomSeatCount) { maximumAttendeeCapacity = eventLocationMaximumAttendeeCapacity; } } // remainingAttendeeCapacityを決定 if (typeof maximumAttendeeCapacity === 'number') { // 残席数を座席ロック数から計算 const unavailableOfferCount = yield repos.stockHolder.countUnavailableOffers({ project: { id: params.event.project.id }, event: { id: params.event.id, startDate: moment(params.event.startDate) .toDate(), hasTicketedSeat } }); remainingAttendeeCapacity = maximumAttendeeCapacity - unavailableOfferCount; if (remainingAttendeeCapacity < 0) { remainingAttendeeCapacity = 0; } } attendeeCount = yield repos.reservation.count({ typeOf: reservationType, reservationFor: { id: { $eq: params.event.id } }, attended: true }); checkInCount = yield repos.reservation.count({ typeOf: reservationType, reservationFor: { id: { $eq: params.event.id } }, checkedIn: true }); return { maximumAttendeeCapacity, remainingAttendeeCapacity, aggregateReservation: { typeOf: 'AggregateReservation', aggregateDate: params.aggregateDate, attendeeCount, checkInCount, reservationCount } }; }); }