UNPKG

@chevre/domain

Version:

Chevre Domain Library for Node.js

495 lines (494 loc) 26.3 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.OfferRepo = void 0; const mongoose_1 = require("mongoose"); const factory = require("../../factory"); const aggregateOffer_1 = require("../aggregateOffer"); const aggregateOffer_2 = require("../mongoose/schemas/aggregateOffer"); const offerCatalog_1 = require("../mongoose/schemas/offerCatalog"); const offerCatalogItem_1 = require("../mongoose/schemas/offerCatalogItem"); const OFFERS_ARRAY_INDEX_NAME = 'offerIndex'; /** * 単価オファー読取リポジトリ */ class OfferRepo { constructor(connection) { this.aggregateOfferModel = connection.model(aggregateOffer_2.modelName, (0, aggregateOffer_2.createSchema)()); this.offerCatalogModel = connection.model(offerCatalog_1.modelName, (0, offerCatalog_1.createSchema)()); this.offerCatalogItemModel = connection.model(offerCatalogItem_1.modelName, (0, offerCatalogItem_1.createSchema)()); } static CREATE_AGGREGATE_OFFERS_PROJECTION(params) { let projectStage = { _id: 0, offerIndex: `$${OFFERS_ARRAY_INDEX_NAME}`, parentOffer: { id: '$_id' }, typeOf: '$offers.typeOf', project: '$project', // id: '$offers.id', id: '$_id', // 集計オファーIDに変更(2023-12-14~) identifier: '$offers.identifier', name: '$offers.name', description: '$offers.description', category: '$offers.category', color: '$offers.color', acceptedPaymentMethod: '$offers.acceptedPaymentMethod', additionalProperty: '$offers.additionalProperty', advanceBookingRequirement: '$offers.advanceBookingRequirement', alternateName: '$offers.alternateName', addOn: '$offers.addOn', availability: '$offers.availability', availableAtOrFrom: '$offers.availableAtOrFrom', hasMerchantReturnPolicy: '$offers.hasMerchantReturnPolicy', itemOffered: '$offers.itemOffered', priceCurrency: '$offers.priceCurrency', priceSpecification: '$offers.priceSpecification', eligibleCustomerType: '$offers.eligibleCustomerType', eligibleDuration: '$offers.eligibleDuration', eligibleMembershipType: '$offers.eligibleMembershipType', eligibleMonetaryAmount: '$offers.eligibleMonetaryAmount', eligibleQuantity: '$offers.eligibleQuantity', eligibleRegion: '$offers.eligibleRegion', eligibleSeatingType: '$offers.eligibleSeatingType', eligibleSubReservation: '$offers.eligibleSubReservation', settings: '$offers.settings', validFrom: '$offers.validFrom', validThrough: '$offers.validThrough', validRateLimit: '$offers.validRateLimit' }; const positiveProjectionFields = Object.keys(params) .filter((key) => params[key] !== 0); const negativeProjectionFields = Object.keys(params) .filter((key) => params[key] === 0); if (positiveProjectionFields.length > 0) { projectStage = { _id: 0, offerIndex: `$${OFFERS_ARRAY_INDEX_NAME}`, parentOffer: { id: '$_id' }, id: '$_id' // 強制的に集計オファーIDに変更(2023-12-14~) }; positiveProjectionFields.forEach((field) => { // idは上書きしない(2023-12-14~) if (field !== 'id') { if (field === 'project') { projectStage[field] = `$project`; } else { projectStage[field] = `$offers.${field}`; } } }); } else if (negativeProjectionFields.length > 0) { negativeProjectionFields.forEach((field) => { // idは除外しない(2023-12-14~) if (field !== 'id') { if (typeof projectStage[field] === 'string') { // tslint:disable-next-line:no-dynamic-delete delete projectStage[field]; } } }); } return projectStage; } /** * カタログIDで単価オファーを全て検索する * 必ずカタログデータから単価オファーIDを参照する */ searchAllByOfferCatalogId(params) { return __awaiter(this, void 0, void 0, function* () { const sortedOfferIds = yield this.searchAggregateOfferIdsBySubOfferCatalog({ id: params.subOfferCatalog.id, isOfferCatalogItem: params.subOfferCatalog.isOfferCatalogItem }); let offers = []; if (sortedOfferIds.length > 0) { const searchOffersConditions = { parentOffer: { id: { $in: sortedOfferIds } } }; offers = yield this.search(searchOffersConditions, params.projection); } return { offers }; }); } /** * 単価オファーIDとカタログIDで単価オファーを全て検索する * 単価オファーIDを一定数指定する想定なのでpagingは不要 */ searchAllByIdsAndOfferCatalogId(params) { return __awaiter(this, void 0, void 0, function* () { var _a; if (!Array.isArray(params.ids) || params.ids.length === 0) { throw new factory.errors.ArgumentNull('offer.ids'); } // 解釈を集計オファーIDに変更する必要がある(2023-09-11~) // 単価オファーIDリスト→集計オファーIDに変換→カタログ条件にセット const aggregateOfferIds = yield this.aggregateOfferModel.distinct('_id', // { 'offers.id': { $in: params.ids } } { _id: { $in: params.ids } } // _idに変更(2023-12-22~) ) .exec(); if (aggregateOfferIds.length === 0) { throw new factory.errors.NotFound(factory.offerType.AggregateOffer); } const aggregateOfferIdsInDataCatalog = yield this.searchAggregateOfferIdsBySubOfferCatalogs({ id: params.includedInDataCatalog.id, isOfferCatalogItem: params.includedInDataCatalog.isOfferCatalogItem, itemListElementIds: aggregateOfferIds }); let offers = []; if (aggregateOfferIdsInDataCatalog.length > 0) { const searchOffersConditions = Object.assign({ // aggregateOffer.idで検索する(2023-09-09~) parentOffer: { id: { $in: aggregateOfferIdsInDataCatalog } }, id: { $in: params.ids }, priceSpecification: { appliesToMovieTicket: Object.assign(Object.assign({}, (Array.isArray(params.unacceptedPaymentMethod) && params.unacceptedPaymentMethod.length > 0) ? { serviceOutput: { typeOf: { $nin: params.unacceptedPaymentMethod } } } : undefined), (params.excludeAppliesToMovieTicket) ? { serviceType: { $exists: false } } : undefined) }, onlyValid: params.onlyValid === true }, (typeof ((_a = params.availableAtOrFrom) === null || _a === void 0 ? void 0 : _a.id) === 'string') ? { availableAtOrFrom: { id: { $eq: params.availableAtOrFrom.id } } } // store.idでのフィルターをmongoで処理(2023-01-27~) : undefined); offers = yield this.search(searchOffersConditions, params.projection); } return offers; }); } /** * カタログに含まれるオファーを検索する(カタログ内ソートは保証しない) * カタログ内ソートインデックスはsortedOfferIdsで判断する */ searchByOfferCatalogIdWithSortIndex(params) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; const sortedOfferIds = yield this.searchAggregateOfferIdsBySubOfferCatalog({ id: params.offerCatalog.id, isOfferCatalogItem: params.offerCatalog.isOfferCatalogItem }); let offers = []; if (sortedOfferIds.length > 0) { const appliesToMovieTicketServiceOutputTypeOfEq = (_d = (_c = (_b = (_a = params.priceSpecification) === null || _a === void 0 ? void 0 : _a.appliesToMovieTicket) === null || _b === void 0 ? void 0 : _b.serviceOutput) === null || _c === void 0 ? void 0 : _c.typeOf) === null || _d === void 0 ? void 0 : _d.$eq; const appliesToMovieTicketServiceOutputTypeOfAll = (_h = (_g = (_f = (_e = params.priceSpecification) === null || _e === void 0 ? void 0 : _e.appliesToMovieTicket) === null || _f === void 0 ? void 0 : _f.serviceOutput) === null || _g === void 0 ? void 0 : _g.typeOf) === null || _h === void 0 ? void 0 : _h.$all; const appliesToMovieTicketServiceTypeExists = (_l = (_k = (_j = params.priceSpecification) === null || _j === void 0 ? void 0 : _j.appliesToMovieTicket) === null || _k === void 0 ? void 0 : _k.serviceType) === null || _l === void 0 ? void 0 : _l.$exists; // 適用決済カード条件なしのみを検索するかどうか const onlyNoAppliesToMovieTicket = params.excludeAppliesToMovieTicket || (appliesToMovieTicketServiceTypeExists === false); const onlyAppliesToMovieTicket = !params.excludeAppliesToMovieTicket && (appliesToMovieTicketServiceTypeExists === true); const priceSpecificationConditions = { appliesToMovieTicket: Object.assign(Object.assign({ serviceOutput: { typeOf: Object.assign(Object.assign(Object.assign({}, (Array.isArray(params.unacceptedPaymentMethod) && params.unacceptedPaymentMethod.length > 0) // 利用不可決済方法区分条件を追加(2023-02-21~) ? { $nin: params.unacceptedPaymentMethod } : undefined), (typeof appliesToMovieTicketServiceOutputTypeOfEq === 'string') ? { $eq: appliesToMovieTicketServiceOutputTypeOfEq } : undefined), (Array.isArray(appliesToMovieTicketServiceOutputTypeOfAll)) ? { $all: appliesToMovieTicketServiceOutputTypeOfAll } : undefined) } }, (onlyNoAppliesToMovieTicket) ? { serviceType: { $exists: false } } : undefined), (onlyAppliesToMovieTicket) ? { serviceType: { $exists: true } } : undefined) }; const searchOffersConditions = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ priceSpecification: priceSpecificationConditions, onlyValid: params.onlyValid === true }, (params.useIncludeInDataCatalog) ? { includedInDataCatalog: { id: { $in: [params.offerCatalog.id] } } } : { parentOffer: { id: { $in: sortedOfferIds } } }), (typeof ((_m = params.availableAtOrFrom) === null || _m === void 0 ? void 0 : _m.id) === 'string') ? { availableAtOrFrom: { id: { $eq: params.availableAtOrFrom.id } } } // store.idでのフィルターをmongoで処理(2023-01-27~) : undefined), (typeof params.limit === 'number' && typeof params.page === 'number') // 明示的なソート指定を調整(決して重複しない属性が相応)(2023-09-07~) ? { sort: { identifier: factory.sortType.Ascending } } : undefined), (typeof params.limit === 'number') ? { limit: params.limit } : undefined), (typeof params.page === 'number') ? { page: params.page } : undefined); offers = yield this.search(searchOffersConditions, params.projection); } return { offers, sortedOfferIds }; }); } /** * カタログに記載されたオファーにおいて利用可能な決済方法区分を検索する */ searchAvaialbleAppliesToMovieTicketByOfferCatalogId(params) { return __awaiter(this, void 0, void 0, function* () { var _a; const { useIncludeInDataCatalog } = params; // カタログのみ対応でよい? let offerIds; if (!useIncludeInDataCatalog) { if (params.subOfferCatalog.isOfferCatalogItem) { const offerCatalog = yield this.offerCatalogItemModel.findOne({ _id: { $eq: params.subOfferCatalog.id } }, { _id: 0, itemListElement: 1 }) .lean() .exec(); offerIds = offerCatalog === null || offerCatalog === void 0 ? void 0 : offerCatalog.itemListElement.map(({ id }) => id); } else { const offerCatalog = yield this.offerCatalogModel.findOne({ _id: { $eq: params.subOfferCatalog.id } }, { _id: 0, itemListElement: 1 }) .lean() .exec(); offerIds = offerCatalog === null || offerCatalog === void 0 ? void 0 : offerCatalog.itemListElement.map(({ id }) => id); } if (!Array.isArray(offerIds) || offerIds.length === 0) { return []; } } // support useIncludeInDataCatalog(2025-05-08~) const searchOffersConditions = Object.assign(Object.assign(Object.assign({}, (Array.isArray(offerIds)) ? { parentOffer: { id: { $in: offerIds } } } : { includedInDataCatalog: { id: { $in: [params.subOfferCatalog.id] } } }), (typeof ((_a = params.availableAtOrFrom) === null || _a === void 0 ? void 0 : _a.id) === 'string') ? { availableAtOrFrom: { id: { $eq: params.availableAtOrFrom.id } } } : undefined), { priceSpecification: { appliesToMovieTicket: Object.assign({ // 基本は適用決済カード区分有のみ検索 serviceType: { $exists: (!params.excludeAppliesToMovieTicket === true) } }, (Array.isArray(params.unacceptedPaymentMethod) && params.unacceptedPaymentMethod.length > 0) ? { serviceOutput: { typeOf: { $nin: params.unacceptedPaymentMethod } } } : undefined) }, onlyValid: params.onlyValid === true }); const matchStages = aggregateOffer_1.AggregateOfferRepo.CREATE_MATCH_STAGE(searchOffersConditions); const aggregate = this.aggregateOfferModel.aggregate([ { $unwind: { path: '$offers' } }, { $unwind: { path: '$offers.priceSpecification.appliesToMovieTicket' } }, ...matchStages, { $group: { _id: '$offers.priceSpecification.appliesToMovieTicket.serviceOutput.typeOf', appliesToMovieTicket: { $first: '$offers.priceSpecification.appliesToMovieTicket' } } }, { $sort: { _id: factory.sortType.Ascending } }, { $project: { _id: 0, serviceOutput: { typeOf: '$_id' } } } ]); if (typeof params.limit === 'number' && params.limit > 0) { const page = (typeof params.page === 'number' && params.page > 0) ? params.page : 1; aggregate.limit(params.limit * page) .skip(params.limit * (page - 1)); } return aggregate.exec(); }); } /** * クライアントの利用可能カタログを検索する */ searchAvailableCatalogs(params) { return __awaiter(this, void 0, void 0, function* () { const { unacceptedPaymentMethod } = params; const aggregate = this.aggregateOfferModel.aggregate([ { $unwind: { path: '$offers' } }, { $unwind: { path: '$includedInDataCatalog' } }, { $match: Object.assign({ 'project.id': { $eq: params.project.id }, 'includedInDataCatalog.id': { $exists: true, $in: params.includedInDataCatalog.id }, 'offers.availableAtOrFrom.id': { $exists: true, $eq: params.availableAtOrFrom.id } }, (Array.isArray(unacceptedPaymentMethod) && unacceptedPaymentMethod.length > 0) ? { 'offers.priceSpecification.appliesToMovieTicket.serviceOutput.typeOf': { $nin: unacceptedPaymentMethod } } : undefined) }, { $group: { _id: '$includedInDataCatalog.id' } }, { $project: { _id: 0, id: '$_id' } } ]); return aggregate.exec(); }); } /** * サブカタログがクライアントで利用可能かどうか */ isCatalogAvailable(params) { return __awaiter(this, void 0, void 0, function* () { const { unacceptedPaymentMethod } = params; // サブカタログのみ対応でよい const offerCatalogItem = yield this.offerCatalogItemModel.findOne({ _id: { $eq: params.includedInDataCatalog.id } }, { _id: 0, itemListElement: 1 }) .lean() .exec(); const offerIds = offerCatalogItem === null || offerCatalogItem === void 0 ? void 0 : offerCatalogItem.itemListElement.map(({ id }) => id); if (!Array.isArray(offerIds) || offerIds.length === 0) { return false; } const doc = yield this.aggregateOfferModel.findOne(Object.assign({ // 'project.id': { $eq: params.project.id }, _id: { $in: offerIds }, 'offers.availableAtOrFrom.id': { $exists: true, $eq: params.availableAtOrFrom.id } }, (Array.isArray(unacceptedPaymentMethod) && unacceptedPaymentMethod.length > 0) ? { 'offers.priceSpecification.appliesToMovieTicket.serviceOutput.typeOf': { $nin: unacceptedPaymentMethod } } : undefined), { _id: 1 }) .lean() .exec(); return doc !== null; }); } count(params) { return __awaiter(this, void 0, void 0, function* () { const matchStages = aggregateOffer_1.AggregateOfferRepo.CREATE_MATCH_STAGE(params); const result = yield this.aggregateOfferModel.aggregate([ { $unwind: { path: '$offers' } }, ...matchStages, { $count: 'offerCount' } ]) .exec(); return (result.length > 0) ? result[0].offerCount : 0; }); } search(params, projection) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; const matchStages = aggregateOffer_1.AggregateOfferRepo.CREATE_MATCH_STAGE(params); const projectStage = OfferRepo.CREATE_AGGREGATE_OFFERS_PROJECTION(Object.assign({}, projection)); const sortByPrice = (_a = params.sort) === null || _a === void 0 ? void 0 : _a['priceSpecification.price']; const sortByIdentifier = (_b = params.sort) === null || _b === void 0 ? void 0 : _b.identifier; const aggregate = this.aggregateOfferModel.aggregate([ { $unwind: { path: '$offers', includeArrayIndex: OFFERS_ARRAY_INDEX_NAME } }, ...matchStages, ...(typeof sortByPrice === 'number' || typeof sortByIdentifier === 'number') ? [ { $sort: Object.assign(Object.assign({}, (typeof sortByPrice === 'number') ? { 'offers.priceSpecification.price': sortByPrice } : undefined), (typeof sortByIdentifier === 'number') ? { 'offers.identifier': sortByIdentifier } : undefined) } ] : [], { $project: projectStage } ]); // tslint:disable-next-line:no-single-line-block-comment /* istanbul ignore else */ if (typeof params.limit === 'number' && params.limit > 0) { const page = (typeof params.page === 'number' && params.page > 0) ? params.page : 1; aggregate.limit(params.limit * page) .skip(params.limit * (page - 1)); } return aggregate.exec(); }); } /** * サブカタログから集計オファーIDリストを検索する */ searchAggregateOfferIdsBySubOfferCatalog(params) { return __awaiter(this, void 0, void 0, function* () { let itemListElements; if (params.isOfferCatalogItem) { const matchStages = [{ $match: { _id: { $eq: new mongoose_1.Types.ObjectId(params.id) } } }]; // ObjectIdなので注意 itemListElements = yield this.offerCatalogItemModel.aggregate([ { $unwind: '$itemListElement' }, ...matchStages, { $project: { _id: 0, id: '$itemListElement.id' } } ]) .exec(); } else { const matchStages = [{ $match: { _id: { $eq: params.id } } }]; itemListElements = yield this.offerCatalogModel.aggregate([ { $unwind: '$itemListElement' }, ...matchStages, { $project: { _id: 0, id: '$itemListElement.id' } } ]) .exec(); } return (Array.isArray(itemListElements)) ? itemListElements.map((element) => element.id) : []; }); } /** * 複数サブカタログから集計オファーIDリストを検索する */ searchAggregateOfferIdsBySubOfferCatalogs(params) { return __awaiter(this, void 0, void 0, function* () { if (!Array.isArray(params.itemListElementIds) || params.itemListElementIds.length === 0) { throw new factory.errors.ArgumentNull('itemListElementIds'); } let itemListElements; if (params.isOfferCatalogItem) { const matchStages = [ { $match: { _id: { $in: params.id.map((id) => new mongoose_1.Types.ObjectId(id)) } } }, // ObjectIdなので注意 { $match: { 'itemListElement.id': { $exists: true, $in: params.itemListElementIds } } } ]; itemListElements = yield this.offerCatalogItemModel.aggregate([ { $unwind: '$itemListElement' }, ...matchStages, { $group: { _id: '$itemListElement.id' } } ]) .exec(); } else { // 1カタログしかありえないはず if (params.id.length !== 1) { throw new factory.errors.Argument('id', 'length must be 1'); } const matchStages = [ { $match: { _id: { $in: params.id } } }, { $match: { 'itemListElement.id': { $exists: true, $in: params.itemListElementIds } } } ]; itemListElements = yield this.offerCatalogModel.aggregate([ { $unwind: '$itemListElement' }, ...matchStages, { $project: { _id: '$itemListElement.id' } } ]) .exec(); } return (Array.isArray(itemListElements)) ? itemListElements.map((element) => element._id) : []; }); } } exports.OfferRepo = OfferRepo;