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