@chevre/domain
Version:
Chevre Domain Library for Node.js
432 lines (431 loc) • 23.9 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.cancelPendingReservation = cancelPendingReservation;
exports.cancelReservation = cancelReservation;
exports.processUnlockOfferRateLimit = processUnlockOfferRateLimit;
/**
* 予約サービス
*/
const moment = require("moment");
const factory = require("../../factory");
const factory_1 = require("./factory");
const onPendingReservationCanceled_1 = require("./potentialActions/onPendingReservationCanceled");
const onReservationCanceled_1 = require("./potentialActions/onReservationCanceled");
/**
* 保留予約取消
*/
function cancelPendingReservation(actionAttributes) {
return (repos
// settings: Settings
) => __awaiter(this, void 0, void 0, function* () {
var _a;
const { cancelAction, reserveTransaction } = yield cancelPengindIfNotYet(actionAttributes)(repos);
yield (0, onPendingReservationCanceled_1.onPendingReservationCanceled)(Object.assign({ project: { id: reserveTransaction.project.id }, reservationFor: {
id: String((_a = reserveTransaction.object.reservationFor) === null || _a === void 0 ? void 0 : _a.id)
}, reservationNumber: reserveTransaction.object.reservationNumber }, (cancelAction !== undefined) ? { cancelAction } : undefined))(repos);
});
}
function cancelPengindIfNotYet(params) {
// tslint:disable-next-line:max-func-body-length
return (repos) => __awaiter(this, void 0, void 0, function* () {
var _a;
const reserveTransactionId = params.purpose.id;
// 予約取引を検索
const reserveTransactions = yield repos.assetTransaction.search({
limit: 1,
page: 1,
typeOf: factory.assetTransactionType.Reserve,
ids: [reserveTransactionId]
});
const reserveTransaction = reserveTransactions.shift();
if (reserveTransaction === undefined) {
throw new factory.errors.NotFound(factory.assetTransactionType.Reserve);
}
let actionId;
const actionAttributes = (0, factory_1.createCancelPendingReservationAction)({ transaction: reserveTransaction });
// 冪等性を担保(2023-06-05~)
const completedActions = yield repos.action.search({
limit: 1,
page: 1,
actionStatus: { $in: [factory.actionStatusType.CompletedActionStatus] },
typeOf: { $eq: factory.actionType.CancelAction },
object: {
typeOf: { $eq: factory.reservationType.ReservationPackage },
reservationNumber: { $eq: reserveTransaction.transactionNumber }
},
purpose: {
id: { $in: [params.purpose.id] },
typeOf: { $in: [params.purpose.typeOf] }
}
}, ['id']);
if (completedActions.length === 0) {
if (actionAttributes !== undefined) {
// アクション開始
const action = yield repos.action.start(actionAttributes);
actionId = action.id;
const actionObject = actionAttributes.object;
// let cancelResult: ICancelResult | undefined;
try {
if (reserveTransaction !== undefined) {
const reservationFor = reserveTransaction.object.reservationFor;
if (reservationFor === undefined) {
throw new factory.errors.NotFound('transaction.object.reservationFor');
}
// ReservationPackageに対応(2022-12-23~)
if (actionObject.typeOf === factory.reservationType.ReservationPackage) {
const subReservation = reserveTransaction.object.subReservation;
if (Array.isArray(subReservation) && subReservation.length > 0) {
yield Promise.all(subReservation.map((cancelingSubReservation) => __awaiter(this, void 0, void 0, function* () {
yield processUnlockSeat({
reservation: {
id: cancelingSubReservation.id,
project: { id: reserveTransaction.project.id },
reservedTicket: cancelingSubReservation.reservedTicket,
subReservation: cancelingSubReservation.subReservation,
reservationFor: {
id: String(reservationFor.id),
startDate: (reservationFor.typeOf === factory.eventType.ScreeningEvent)
? reservationFor.startDate
: reservationFor.departureTime
}
},
// holder:取引番号に対応(2023-06-05~)
expectedHolder: (reserveTransaction.object.useHoldStockByTransactionNumber === true)
? reserveTransaction.transactionNumber
: reserveTransactionId
})(repos);
yield processUnlockOfferRateLimit({
project: { id: reserveTransaction.project.id },
reservation: {
reservationNumber: reserveTransaction.object.reservationNumber,
reservedTicket: cancelingSubReservation.reservedTicket
},
reservationFor
})(repos);
})));
}
if (reserveTransaction.object.disablePendingReservations === true) {
// disablePendingReservationsの場合は処理不要(2023-06-05~)
}
else {
// 完全廃止(2023-07-19~)
throw new factory.errors.NotImplemented('disablePendingReservations must be true');
}
}
else {
// 廃止(2022-12-27~)
throw new factory.errors.NotImplemented(`object.typeOf '${actionObject.typeOf}' not implemented`);
}
}
}
catch (error) {
try {
yield repos.action.giveUp({ typeOf: action.typeOf, id: action.id, error });
}
catch (__) {
// 失敗したら仕方ない
}
throw error;
}
const actionResult = {};
yield repos.action.completeWithVoid({ typeOf: action.typeOf, id: action.id, result: actionResult });
}
}
else {
actionId = (_a = completedActions.at(0)) === null || _a === void 0 ? void 0 : _a.id;
}
return Object.assign({ reserveTransaction }, (actionAttributes !== undefined && typeof actionId === 'string')
? {
cancelAction: Object.assign(Object.assign({}, actionAttributes), { id: actionId })
}
: undefined);
});
}
/**
* 予約をキャンセルする
*/
function cancelReservation(actionAttributesList) {
// tslint:disable-next-line:max-func-body-length
return (repos
// settings: Settings
) => __awaiter(this, void 0, void 0, function* () {
const now = new Date();
if (actionAttributesList.length > 0) {
// tslint:disable-next-line:max-func-body-length
yield Promise.all(actionAttributesList.map((actionAttributes) => __awaiter(this, void 0, void 0, function* () {
const action = yield repos.action.start(actionAttributes);
let cancelResult;
let canceledReservationForId;
try {
if (actionAttributes.object.typeOf === factory.reservationType.ReservationPackage) {
// 予約取引を検索
const reserveTransactions = yield repos.assetTransaction.search({
limit: 1,
page: 1,
typeOf: factory.assetTransactionType.Reserve,
transactionNumber: { $eq: actionAttributes.object.reservationNumber }
});
const reserveTransaction = reserveTransactions.shift();
if (reserveTransaction === undefined) {
throw new factory.errors.NotFound(factory.assetTransactionType.Reserve);
}
const reservationFor = reserveTransaction.object.reservationFor;
if (reservationFor === undefined) {
throw new factory.errors.NotFound('transaction.object.reservationFor');
}
canceledReservationForId = String(reservationFor.id);
const subReservation = reserveTransaction.object.subReservation;
if (Array.isArray(subReservation) && subReservation.length > 0) {
yield Promise.all(subReservation.map((cancelingSubReservation) => __awaiter(this, void 0, void 0, function* () {
yield processUnlockSeat({
reservation: {
id: cancelingSubReservation.id,
project: { id: reserveTransaction.project.id },
reservedTicket: cancelingSubReservation.reservedTicket,
subReservation: cancelingSubReservation.subReservation,
reservationFor: {
id: String(reservationFor.id),
startDate: (reservationFor.typeOf === factory.eventType.ScreeningEvent)
? reservationFor.startDate
: reservationFor.departureTime
}
},
// holder:取引番号に対応(2023-06-05~)
expectedHolder: (reserveTransaction.object.useHoldStockByTransactionNumber === true)
? reserveTransaction.transactionNumber
: reserveTransaction.id
})(repos);
yield processUnlockOfferRateLimit({
project: { id: reserveTransaction.project.id },
reservation: {
reservationNumber: reserveTransaction.object.reservationNumber,
reservedTicket: cancelingSubReservation.reservedTicket
},
reservationFor
})(repos);
})));
}
// 予約番号単位でキャンセル状態に変更する
cancelResult = yield repos.reservation.cancelByReservationNumber({
reservationNumber: actionAttributes.object.reservationNumber,
previousReservationStatus: actionAttributes.object.reservationStatus,
modifiedTime: now
});
}
else {
const reservation = yield repos.reservation.projectFieldsById({
id: actionAttributes.object.id,
inclusion: ['reservationNumber', 'reservationStatus', 'project', 'reservedTicket', 'subReservation', 'reservationFor']
});
// 予約取引を検索
const reserveTransactions = yield repos.assetTransaction.search({
limit: 1,
page: 1,
typeOf: factory.assetTransactionType.Reserve,
object: { reservations: { id: { $in: [reservation.id] } } }
});
const reserveTransaction = reserveTransactions.shift();
canceledReservationForId = reservation.reservationFor.id;
// holder:取引番号に対応(2023-06-05~)
const expectedHolder = ((reserveTransaction === null || reserveTransaction === void 0 ? void 0 : reserveTransaction.object.useHoldStockByTransactionNumber) === true)
? reserveTransaction.transactionNumber
: reserveTransaction === null || reserveTransaction === void 0 ? void 0 : reserveTransaction.id;
if (typeof expectedHolder === 'string') {
yield processUnlockSeat({
reservation: {
id: reservation.id,
project: { id: reservation.project.id },
reservedTicket: reservation.reservedTicket,
subReservation: reservation.subReservation,
reservationFor: {
id: reservation.reservationFor.id,
startDate: reservation.reservationFor.startDate
}
},
expectedHolder
})(repos);
}
yield processUnlockOfferRateLimit({
project: { id: reservation.project.id },
reservation: {
reservationNumber: reservation.reservationNumber,
reservedTicket: reservation.reservedTicket
},
reservationFor: reservation.reservationFor
})(repos);
// 予約をキャンセル状態に変更する
yield repos.reservation.cancelById({
id: reservation.id,
previousReservationStatus: actionAttributes.object.reservationStatus,
modifiedTime: now
});
}
}
catch (error) {
try {
yield repos.action.giveUp({ typeOf: action.typeOf, id: action.id, error });
}
catch (__) {
// 失敗したら仕方ない
}
throw error;
}
const actionResult = Object.assign({}, (cancelResult !== undefined) ? {
cancelResult: {
// n: cancelResult.n,
// nModified: cancelResult.nModified,
// ok: cancelResult.ok
matchedCount: cancelResult.matchedCount,
modifiedCount: cancelResult.modifiedCount
}
} : undefined
// canceledReservationId: canceledReservation?.id
);
yield repos.action.completeWithVoid({ typeOf: action.typeOf, id: action.id, result: actionResult });
let canceledReservations = [];
if (actionAttributes.object.typeOf === factory.reservationType.ReservationPackage) {
const reservationNumber = actionAttributes.object.reservationNumber;
if (typeof reservationNumber === 'string' && reservationNumber.length > 0) {
// 最新のconfirmedReservationsを検索
canceledReservations = yield repos.reservation.projectFields({
reservationNumber: { $eq: reservationNumber },
typeOf: factory.reservationType.EventReservation
}, { project: 1, typeOf: 1, modifiedTime: 1, reservationNumber: 1 });
canceledReservations = canceledReservations.map((r) => {
// _idは不要であり、存在すると予期せぬ影響を及ぼす可能性がある
delete r._id;
return r;
});
}
}
else {
const canceledReservation = yield repos.reservation.projectFieldsById({
id: actionAttributes.object.id,
inclusion: ['project', 'typeOf', 'modifiedTime', 'reservationNumber']
});
canceledReservations = [canceledReservation];
}
yield (0, onReservationCanceled_1.onReservationCanceled)(canceledReservations,
// true,
{
project: { id: actionAttributes.project.id },
id: canceledReservationForId
}, actionAttributes)(repos);
})));
}
});
}
/**
* 座席ロック解除プロセス
*/
function processUnlockSeat(params) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
const reservation = params.reservation;
const eventStartDate = moment(reservation.reservationFor.startDate)
.toDate();
// 予約IDでロックされていれば解除
let lockKey = {
project: reservation.project,
eventId: reservation.reservationFor.id,
startDate: eventStartDate,
hasTicketedSeat: false,
offer: {
itemOffered: { serviceOutput: { id: reservation.id } },
seatNumber: '',
seatSection: ''
},
holder: params.expectedHolder
};
let holder = yield repos.stockHolder.getHolder(lockKey);
if (holder === params.expectedHolder) {
yield repos.stockHolder.unlock(lockKey);
}
// 予約取引がまだ座席を保持していれば座席ロック解除
const ticketedSeat = reservation.reservedTicket.ticketedSeat;
if (ticketedSeat !== undefined) {
lockKey = {
project: reservation.project,
eventId: reservation.reservationFor.id,
startDate: eventStartDate,
hasTicketedSeat: true,
offer: {
seatNumber: ticketedSeat.seatNumber,
seatSection: ticketedSeat.seatSection
},
holder: params.expectedHolder
};
holder = yield repos.stockHolder.getHolder(lockKey);
if (holder === params.expectedHolder) {
yield repos.stockHolder.unlock(lockKey);
}
}
// subReservationがあれば、そちらも解除(順不同)
const subReservations = reservation.subReservation;
if (Array.isArray(subReservations)) {
yield Promise.all(subReservations.map((subReservation) => __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d;
const seatSection4sub = (_b = (_a = subReservation.reservedTicket) === null || _a === void 0 ? void 0 : _a.ticketedSeat) === null || _b === void 0 ? void 0 : _b.seatSection;
const seatNumber4sub = (_d = (_c = subReservation.reservedTicket) === null || _c === void 0 ? void 0 : _c.ticketedSeat) === null || _d === void 0 ? void 0 : _d.seatNumber;
if (typeof seatSection4sub === 'string' && typeof seatNumber4sub === 'string') {
const lockKey4sub = {
project: reservation.project,
eventId: reservation.reservationFor.id,
startDate: eventStartDate,
hasTicketedSeat: true,
offer: {
seatNumber: seatNumber4sub,
seatSection: seatSection4sub
},
holder: params.expectedHolder
};
const holder4sub = yield repos.stockHolder.getHolder(lockKey4sub);
if (holder4sub === params.expectedHolder) {
yield repos.stockHolder.unlock(lockKey4sub);
}
}
})));
}
});
}
function processUnlockOfferRateLimit(params) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const reservation = params.reservation;
const scope = (_a = reservation.reservedTicket.ticketType.validRateLimit) === null || _a === void 0 ? void 0 : _a.scope;
const unitInSeconds = (_b = reservation.reservedTicket.ticketType.validRateLimit) === null || _b === void 0 ? void 0 : _b.unitInSeconds;
if (typeof scope === 'string' && typeof unitInSeconds === 'number') {
const rateLimitKey = {
project: { id: params.project.id },
reservedTicket: {
ticketType: {
validRateLimit: {
scope: scope,
unitInSeconds: unitInSeconds
}
}
},
reservationFor: {
startDate: (params.reservationFor.typeOf === factory.eventType.ScreeningEvent)
? moment(params.reservationFor.startDate)
.toDate()
: moment(params.reservationFor.departureTime)
.toDate()
},
reservationNumber: reservation.reservationNumber
};
const holder = yield repos.offerRateLimit.getHolder(rateLimitKey);
if (holder === rateLimitKey.reservationNumber) {
yield repos.offerRateLimit.unlock(rateLimitKey);
}
}
});
}