@chevre/domain
Version:
Chevre Domain Library for Node.js
495 lines (494 loc) • 26.3 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.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;