@chevre/domain
Version:
Chevre Domain Library for Node.js
291 lines (290 loc) • 15.2 kB
JavaScript
"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
}
};
});
}