UNPKG

@chevre/domain

Version:

Chevre Domain Library for Node.js

306 lines (305 loc) 17.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.aggregateOffers = aggregateOffers; const createDebug = require("debug"); const moment = require("moment-timezone"); const factory = require("../../../factory"); const findEventOffers_1 = require("./findEventOffers"); const debug = createDebug('chevre-domain:service:aggregation'); /** * イベントに対するオファー集計 */ function aggregateOffers(params) { return (repos) => __awaiter(this, void 0, void 0, function* () { // 集計対象イベント検索 const event = yield repos.event.projectEventFieldsById({ id: params.id }, [ 'location', 'project', 'startDate', 'typeOf', 'superEvent.location.id', 'offers.itemOffered' ] // projection(2024-07-21~) ); const eventCapacity = (yield repos.event.projectEventFields({ limit: 1, page: 1, id: { $eq: params.id }, typeOf: params.typeOf }, ['maximumAttendeeCapacity', 'remainingAttendeeCapacity'])).shift(); debug('processing aggregateOffersByEvent...', eventCapacity); yield aggregateOffersByEvent({ event: Object.assign(Object.assign(Object.assign({}, event), (typeof (eventCapacity === null || eventCapacity === void 0 ? void 0 : eventCapacity.maximumAttendeeCapacity) === 'number') ? { maximumAttendeeCapacity: eventCapacity.maximumAttendeeCapacity } : undefined), (typeof (eventCapacity === null || eventCapacity === void 0 ? void 0 : eventCapacity.remainingAttendeeCapacity) === 'number') ? { remainingAttendeeCapacity: eventCapacity.remainingAttendeeCapacity } : undefined) })(repos); }); } function aggregateOffersByEvent(params) { return (repos) => __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 aggregateOffer = yield aggregateOfferByEvent({ aggregateDate: now, event, screeningRoom: screeningRoom })(repos); // const update: IUpdateAggregateReservationParams = { // $set: { // // updatedAt: new Date(), // $setオブジェクトが空だとMongoエラーになるので // ...(typeof aggregateOffer?.typeOf === 'string') ? { aggregateOffer: aggregateOffer } : undefined // } // // $unset: { // // noExistingAttributeName: 1 // $unsetは空だとエラーになるので // // } // }; // aggregateReservationsに保管(2024-03-25~) yield repos.aggregateReservation.save({ project: event.project, reservationFor: { id: event.id, typeOf: event.typeOf, startDate: moment(event.startDate) .toDate() } }, { $set: { aggregateOffer } }); }); } 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* () { var _a; const availableOffers = yield (0, findEventOffers_1.findEventOffers)(params)(repos); // 集計対象のオファーにフィルター(2024-11-09~) const offersIncludedInAggregateReservation = availableOffers.filter(({ settings }) => (settings === null || settings === void 0 ? void 0 : settings.includedInAggregateReservation) === true); // オファーごとの予約集計 const offersWithAggregateReservation = []; for (const o of offersIncludedInAggregateReservation) { const { maximumAttendeeCapacity, remainingAttendeeCapacity, aggregateReservation } = yield aggregateReservationByOffer({ aggregateDate: params.aggregateDate, event: params.event, screeningRoom: params.screeningRoom, offer: o, availableOffers })(repos); offersWithAggregateReservation.push(Object.assign(Object.assign(Object.assign({ typeOf: o.typeOf, id: o.id, identifier: o.identifier, aggregateReservation: aggregateReservation }, (typeof maximumAttendeeCapacity === 'number') ? { maximumAttendeeCapacity } : undefined), (typeof remainingAttendeeCapacity === 'number') ? { remainingAttendeeCapacity } : undefined), (typeof ((_a = o.category) === null || _a === void 0 ? void 0 : _a.codeValue) === 'string') ? { category: o.category } : undefined)); } return { typeOf: factory.offerType.AggregateOffer, aggregateDate: params.aggregateDate, offerCount: offersIncludedInAggregateReservation.length, offers: offersWithAggregateReservation }; }); } function aggregateReservationByOffer(params) { return (repos) => __awaiter(this, void 0, void 0, function* () { let reservationCount4offer; // let attendeeCount4offer: number | undefined; // let checkInCount4offer: number | undefined; let reservationType = factory.reservationType.EventReservation; if (params.event.typeOf === factory.eventType.Event) { reservationType = factory.reservationType.BusReservation; } const offerIdsWithSameCategory = params.availableOffers.filter(({ category }) => { var _a; return typeof (category === null || category === void 0 ? void 0 : category.codeValue) === 'string' && category.codeValue === ((_a = params.offer.category) === null || _a === void 0 ? void 0 : _a.codeValue); }) .map(({ id }) => id); if (offerIdsWithSameCategory.length > 0) { reservationCount4offer = yield repos.reservation.count({ typeOf: reservationType, reservationFor: { id: { $eq: params.event.id } }, reservationStatuses: [factory.reservationStatusType.ReservationConfirmed], // オファーカテゴリーごとに集計する(2024-11-10~) // reservedTicket: { ticketType: { ids: [<string>params.offer.id] } } reservedTicket: { ticketType: { ids: offerIdsWithSameCategory } } }); // attendeeCount4offer = await repos.reservation.count({ // typeOf: reservationType, // reservationFor: { id: { $eq: params.event.id } }, // reservationStatuses: [factory.reservationStatusType.ReservationConfirmed], // // オファーカテゴリーごとに集計する(2024-11-10~) // // reservedTicket: { ticketType: { ids: [<string>params.offer.id] } }, // reservedTicket: { ticketType: { ids: offerIdsWithSameCategory } }, // attended: true // }); // checkInCount4offer = await repos.reservation.count({ // typeOf: reservationType, // reservationFor: { id: { $eq: params.event.id } }, // reservationStatuses: [factory.reservationStatusType.ReservationConfirmed], // // オファーカテゴリーごとに集計する(2024-11-10~) // // reservedTicket: { ticketType: { ids: [<string>params.offer.id] } }, // reservedTicket: { ticketType: { ids: offerIdsWithSameCategory } }, // checkedIn: true // }); } const { maximumAttendeeCapacity, remainingAttendeeCapacity } = yield calculateCapacityByOffer(params)(repos); return Object.assign(Object.assign({ aggregateReservation: { typeOf: 'AggregateReservation', reservationCount: reservationCount4offer // attendeeCount: attendeeCount4offer, // discontinue(2024-11-10~) // checkInCount: checkInCount4offer // discontinue(2024-11-10~) } }, (typeof maximumAttendeeCapacity === 'number') ? { maximumAttendeeCapacity } : undefined), (typeof remainingAttendeeCapacity === 'number') ? { remainingAttendeeCapacity } : undefined); }); } /** * オファーごとのキャパシティを算出する */ function calculateCapacityByOffer(params) { return (repos) => __awaiter(this, void 0, void 0, function* () { var _a; let maximumAttendeeCapacity; let remainingAttendeeCapacity; if (reservedSeatsAvailable({ event: params.event })) { // 基本的にはイベントのキャパシティに同じ maximumAttendeeCapacity = params.event.maximumAttendeeCapacity; remainingAttendeeCapacity = params.event.remainingAttendeeCapacity; // 座席タイプ制約のあるオファーの場合 const eligibleSeatingTypes = params.offer.eligibleSeatingType; if (Array.isArray(eligibleSeatingTypes)) { const filterByEligibleSeatingTypeResult = yield filterByEligibleSeatingType({ event: params.event, screeningRoom: params.screeningRoom, eligibleSeatingTypes: eligibleSeatingTypes.map((e) => e.codeValue) })(repos); maximumAttendeeCapacity = filterByEligibleSeatingTypeResult.maximumAttendeeCapacity; remainingAttendeeCapacity = filterByEligibleSeatingTypeResult.remainingAttendeeCapacity; } // 適用サブ予約がある場合 const eligibleSubReservation = params.offer.eligibleSubReservation; if (Array.isArray(eligibleSubReservation)) { // 適用サブ予約の座席タイプごとにキャパシティ算出 const capacities = yield Promise.all(eligibleSubReservation .filter((subReservation) => typeof subReservation.amountOfThisGood === 'number' && subReservation.amountOfThisGood > 0) .map((subReservation) => __awaiter(this, void 0, void 0, function* () { const filterByEligibleSeatingTypeResult = yield filterByEligibleSeatingType({ event: params.event, screeningRoom: params.screeningRoom, eligibleSeatingTypes: [subReservation.typeOfGood.seatingType] })(repos); return { maximumAttendeeCapacity: Math.floor(filterByEligibleSeatingTypeResult.maximumAttendeeCapacity / subReservation.amountOfThisGood), remainingAttendeeCapacity: Math.floor(filterByEligibleSeatingTypeResult.remainingAttendeeCapacity / subReservation.amountOfThisGood) }; }))); // 座席タイプごとのキャパシティの中から、最小数を選択する maximumAttendeeCapacity = Math.min(...(typeof maximumAttendeeCapacity === 'number') ? [maximumAttendeeCapacity] : [], ...capacities.map((c) => c.maximumAttendeeCapacity)); remainingAttendeeCapacity = Math.min(...(typeof remainingAttendeeCapacity === 'number') ? [remainingAttendeeCapacity] : [], ...capacities.map((c) => c.remainingAttendeeCapacity)); } // 単価スペックの単位が1より大きい場合 const referenceQuantityValue = (_a = params.offer.priceSpecification) === null || _a === void 0 ? void 0 : _a.referenceQuantity.value; if (typeof referenceQuantityValue === 'number' && referenceQuantityValue > 1) { if (typeof maximumAttendeeCapacity === 'number') { maximumAttendeeCapacity -= maximumAttendeeCapacity % referenceQuantityValue; } if (typeof remainingAttendeeCapacity === 'number') { remainingAttendeeCapacity -= remainingAttendeeCapacity % referenceQuantityValue; } } } // レート制限がある場合、考慮する if (yield isHoldByRateLimit(params)({ offerRateLimit: repos.offerRateLimit })) { remainingAttendeeCapacity = 0; } return { maximumAttendeeCapacity, remainingAttendeeCapacity }; }); } function isHoldByRateLimit(params) { return (repos) => __awaiter(this, void 0, void 0, function* () { var _a, _b; let isHold = false; const scope = (_a = params.offer.validRateLimit) === null || _a === void 0 ? void 0 : _a.scope; const unitInSeconds = (_b = params.offer.validRateLimit) === null || _b === void 0 ? void 0 : _b.unitInSeconds; if (typeof scope === 'string' && typeof unitInSeconds === 'number') { const rateLimitKey = { project: { id: params.event.project.id }, reservedTicket: { ticketType: { validRateLimit: { scope: scope, unitInSeconds: unitInSeconds } } }, reservationFor: { startDate: moment(params.event.startDate) .toDate() }, reservationNumber: '' }; const holder = yield repos.offerRateLimit.getHolder(rateLimitKey); // ロックされていれば在庫0 if (typeof holder === 'string' && holder.length > 0) { isHold = true; } } return isHold; }); } function filterByEligibleSeatingType(params) { return (repos) => __awaiter(this, void 0, void 0, function* () { // 適用座席タイプに絞る const eligibleSeatOffers = (Array.isArray(params.screeningRoom.containsPlace)) ? params.screeningRoom.containsPlace.reduce((a, b) => { return [ ...a, ...(Array.isArray(b.containsPlace)) ? b.containsPlace.filter((place) => { const seatingTypes = (Array.isArray(place.seatingType)) ? place.seatingType : (typeof place.seatingType === 'string') ? [place.seatingType] : []; return seatingTypes.some((seatingTypeCodeValue) => params.eligibleSeatingTypes.some((eligibleSeatingType) => eligibleSeatingType === seatingTypeCodeValue)); }) .map((place) => { return { seatSection: b.branchCode, seatNumber: place.branchCode }; }) : [] ]; }, []) : []; const maximumAttendeeCapacity = eligibleSeatOffers.length; let remainingAttendeeCapacity; if (maximumAttendeeCapacity > 0) { const availabilities = yield repos.stockHolder.searchHolders({ project: { id: params.event.project.id }, eventId: params.event.id, startDate: moment(params.event.startDate) .toDate(), hasTicketedSeat: true, offers: eligibleSeatOffers }); remainingAttendeeCapacity = availabilities.filter((holder) => typeof holder !== 'string').length; } else { remainingAttendeeCapacity = 0; } return { maximumAttendeeCapacity, remainingAttendeeCapacity }; }); }