@rsc-labs/medusa-store-analytics
Version:
Get analytics data about your store
437 lines (436 loc) • 21.1 kB
JavaScript
;
/*
* Copyright 2024 RSC-Labs, https://rsoftcon.com/
*
* MIT License
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const medusa_1 = require("@medusajs/medusa");
const medusa_2 = require("@medusajs/medusa");
const dateTransformations_1 = require("./utils/dateTransformations");
const typeorm_1 = require("typeorm");
const currency_1 = require("./utils/currency");
function groupPerDate(orders, resolution) {
const funcTruncateDate = (0, dateTransformations_1.getTruncateFunction)(resolution);
return orders.reduce((accumulator, order) => {
const truncatedDate = funcTruncateDate(order.created_at);
if (!accumulator[truncatedDate.toISOString()]) {
if (resolution == dateTransformations_1.DateResolutionType.Day) {
accumulator[truncatedDate.toISOString()] = { date: new Date(new Date(order.created_at).setHours(0, 0, 0, 0)), total: 0 };
}
else {
accumulator[truncatedDate.toISOString()] = { date: new Date(new Date(new Date(order.created_at).setDate(1)).setHours(0, 0, 0, 0)), total: 0 };
}
}
accumulator[truncatedDate.toISOString()].total += order.total;
return accumulator;
}, {});
}
class SalesAnalyticsService extends medusa_1.TransactionBaseService {
constructor(container) {
super(container);
this.orderService = container.orderService;
}
async getOrdersSales(orderStatuses, currencyCode, from, to, dateRangeFromCompareTo, dateRangeToCompareTo) {
let startQueryFrom;
const orderStatusesAsStrings = Object.values(orderStatuses);
if (orderStatusesAsStrings.length) {
if (!dateRangeFromCompareTo) {
if (from) {
startQueryFrom = from;
}
else {
// All time
const lastOrder = await this.activeManager_.getRepository(medusa_2.Order).find({
skip: 0,
take: 1,
order: { created_at: "ASC" },
where: { status: (0, typeorm_1.In)(orderStatusesAsStrings) }
});
if (lastOrder.length > 0) {
startQueryFrom = lastOrder[0].created_at;
}
}
}
else {
startQueryFrom = dateRangeFromCompareTo;
}
const endQuery = (0, dateTransformations_1.getQueryEndDate)(to);
const orders = await this.orderService.list({
created_at: startQueryFrom ? { gte: startQueryFrom, lte: endQuery } : undefined,
currency_code: currencyCode,
status: (0, typeorm_1.In)(orderStatusesAsStrings)
}, {
select: [
"id",
"total",
"created_at",
"updated_at"
],
order: { created_at: "DESC" },
});
if (startQueryFrom) {
if (dateRangeFromCompareTo && from && to && dateRangeToCompareTo) {
const previousOrders = orders.filter(order => order.created_at < from);
const currentOrders = orders.filter(order => order.created_at >= from);
const resolution = (0, dateTransformations_1.calculateResolution)(from, to);
const groupedCurrentOrders = groupPerDate(currentOrders, resolution);
const groupedPreviousOrders = groupPerDate(previousOrders, resolution);
const currentSales = Object.values(groupedCurrentOrders);
const previousSales = Object.values(groupedPreviousOrders);
return {
dateRangeFrom: from.getTime(),
dateRangeTo: to.getTime(),
dateRangeFromCompareTo: dateRangeFromCompareTo.getTime(),
dateRangeToCompareTo: dateRangeToCompareTo.getTime(),
currencyCode: currencyCode,
currencyDecimalDigits: (0, currency_1.getDecimalDigits)(currencyCode),
current: currentSales.sort((a, b) => a.date.getTime() - b.date.getTime()),
previous: previousSales.sort((a, b) => a.date.getTime() - b.date.getTime())
};
}
const resolution = (0, dateTransformations_1.calculateResolution)(startQueryFrom, endQuery);
const currentOrders = orders;
const groupedCurrentOrders = groupPerDate(currentOrders, resolution);
const currentSales = Object.values(groupedCurrentOrders);
return {
dateRangeFrom: startQueryFrom.getTime(),
dateRangeTo: to ? to.getTime() : new Date(Date.now()).getTime(),
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
currencyCode: currencyCode,
currencyDecimalDigits: (0, currency_1.getDecimalDigits)(currencyCode),
current: currentSales.sort((a, b) => a.date.getTime() - b.date.getTime()),
previous: []
};
}
}
return {
dateRangeFrom: undefined,
dateRangeTo: undefined,
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
currencyCode: currencyCode,
currencyDecimalDigits: (0, currency_1.getDecimalDigits)(currencyCode),
current: [],
previous: []
};
}
async getSalesChannelsPopularity(orderStatuses, from, to, dateRangeFromCompareTo, dateRangeToCompareTo) {
const orderStatusesAsStrings = Object.values(orderStatuses);
if (orderStatusesAsStrings.length) {
if (dateRangeFromCompareTo && from && to && dateRangeToCompareTo) {
const resolution = (0, dateTransformations_1.calculateResolution)(from, to);
const query = this.activeManager_
.getRepository(medusa_2.Order)
.createQueryBuilder('order')
.select(`
CASE
WHEN order.created_at < :from AND order.created_at >= :dateRangeFromCompareTo THEN 'previous'
ELSE 'current'
END AS type`)
.addSelect(`date_trunc('${resolution}', order.created_at)`, 'date')
.addSelect('COUNT(order.id)', 'orderCount')
.leftJoinAndSelect('order.sales_channel', 'sales_channel')
.where('order.created_at >= :dateRangeFromCompareTo', { dateRangeFromCompareTo })
.andWhere('order.created_at <= :to', { to })
.andWhere(`status IN(:...orderStatusesAsStrings)`, { orderStatusesAsStrings });
const ordersCountBySalesChannel = await query
.groupBy('date, type, sales_channel.id')
.orderBy('date', 'ASC')
.setParameters({ from, dateRangeFromCompareTo })
.getRawMany();
const finalOrders = ordersCountBySalesChannel.reduce((acc, entry) => {
const type = entry.type;
const date = entry.date;
const orderCount = entry.orderCount;
const salesChannelId = entry.sales_channel_id;
const salesChannelName = entry.sales_channel_name;
if (!acc[type]) {
acc[type] = [];
}
acc[type].push({
date,
orderCount,
salesChannelId,
salesChannelName
});
return acc;
}, {});
return {
dateRangeFrom: from.getTime(),
dateRangeTo: to.getTime(),
dateRangeFromCompareTo: dateRangeFromCompareTo.getTime(),
dateRangeToCompareTo: dateRangeToCompareTo.getTime(),
current: finalOrders.current ? finalOrders.current : [],
previous: finalOrders.previous ? finalOrders.previous : [],
};
}
let startQueryFrom;
if (!dateRangeFromCompareTo) {
if (from) {
startQueryFrom = from;
}
else {
// All time
const lastOrder = await this.activeManager_.getRepository(medusa_2.Order).find({
skip: 0,
take: 1,
order: { created_at: "ASC" },
where: { status: (0, typeorm_1.In)(orderStatusesAsStrings) }
});
if (lastOrder.length > 0) {
startQueryFrom = lastOrder[0].created_at;
}
}
}
else {
startQueryFrom = dateRangeFromCompareTo;
}
if (startQueryFrom) {
const endQuery = (0, dateTransformations_1.getQueryEndDate)(to);
const resolution = (0, dateTransformations_1.calculateResolution)(startQueryFrom, endQuery);
const query = this.activeManager_
.getRepository(medusa_2.Order)
.createQueryBuilder('order')
.select(`date_trunc('${resolution}', order.created_at)`, 'date')
.addSelect('COUNT(order.id)', 'orderCount')
.leftJoinAndSelect('order.sales_channel', 'sales_channel')
.where('order.created_at >= :startQueryFrom', { startQueryFrom })
.andWhere('order.created_at <= :endQuery', { endQuery })
.andWhere(`status IN(:...orderStatusesAsStrings)`, { orderStatusesAsStrings });
const ordersCountBySalesChannel = await query
.groupBy('date, sales_channel.id')
.orderBy('date', 'ASC')
.getRawMany();
const finalOrders = ordersCountBySalesChannel.map(order => {
return {
date: order.date,
orderCount: order.orderCount,
salesChannelId: order.sales_channel_id,
salesChannelName: order.sales_channel_name
};
});
return {
dateRangeFrom: startQueryFrom.getTime(),
dateRangeTo: to ? to.getTime() : new Date(Date.now()).getTime(),
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: finalOrders,
previous: []
};
}
}
return {
dateRangeFrom: undefined,
dateRangeTo: undefined,
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: [],
previous: []
};
}
async getRegionsPopularity(orderStatuses, from, to, dateRangeFromCompareTo, dateRangeToCompareTo) {
const orderStatusesAsStrings = Object.values(orderStatuses);
if (orderStatusesAsStrings.length) {
if (dateRangeFromCompareTo && from && to && dateRangeToCompareTo) {
const resolution = (0, dateTransformations_1.calculateResolution)(from, to);
const query = this.activeManager_
.getRepository(medusa_2.Order)
.createQueryBuilder('order')
.select(`
CASE
WHEN order.created_at < :from AND order.created_at >= :dateRangeFromCompareTo THEN 'previous'
ELSE 'current'
END AS type`)
.addSelect(`date_trunc('${resolution}', order.created_at)`, 'date')
.addSelect('COUNT(order.id)', 'orderCount')
.leftJoinAndSelect('order.region', 'region')
.where('order.created_at >= :dateRangeFromCompareTo', { dateRangeFromCompareTo })
.andWhere('order.created_at <= :to', { to })
.andWhere(`status IN(:...orderStatusesAsStrings)`, { orderStatusesAsStrings });
const ordersCountByRegion = await query
.groupBy('date, type, region.id')
.orderBy('date', 'ASC')
.setParameters({ from, dateRangeFromCompareTo })
.getRawMany();
const finalOrders = ordersCountByRegion.reduce((acc, entry) => {
const type = entry.type;
const date = entry.date;
const orderCount = entry.orderCount;
const regionId = entry.region_id;
const regionName = entry.region_name;
if (!acc[type]) {
acc[type] = [];
}
acc[type].push({
date,
orderCount,
regionId,
regionName
});
return acc;
}, {});
return {
dateRangeFrom: from.getTime(),
dateRangeTo: to.getTime(),
dateRangeFromCompareTo: dateRangeFromCompareTo.getTime(),
dateRangeToCompareTo: dateRangeToCompareTo.getTime(),
current: finalOrders.current ? finalOrders.current : [],
previous: finalOrders.previous ? finalOrders.previous : [],
};
}
let startQueryFrom;
if (!dateRangeFromCompareTo) {
if (from) {
startQueryFrom = from;
}
else {
// All time
const lastOrder = await this.activeManager_.getRepository(medusa_2.Order).find({
skip: 0,
take: 1,
order: { created_at: "ASC" },
where: { status: (0, typeorm_1.In)(orderStatusesAsStrings) }
});
if (lastOrder.length > 0) {
startQueryFrom = lastOrder[0].created_at;
}
}
}
else {
startQueryFrom = dateRangeFromCompareTo;
}
if (startQueryFrom) {
const endQuery = (0, dateTransformations_1.getQueryEndDate)(to);
const resolution = (0, dateTransformations_1.calculateResolution)(startQueryFrom, endQuery);
const query = this.activeManager_
.getRepository(medusa_2.Order)
.createQueryBuilder('order')
.select(`date_trunc('${resolution}', order.created_at)`, 'date')
.addSelect('COUNT(order.id)', 'orderCount')
.leftJoinAndSelect('order.region', 'region')
.where('order.created_at >= :startQueryFrom', { startQueryFrom })
.andWhere('order.created_at <= :endQuery', { endQuery })
.andWhere(`status IN(:...orderStatusesAsStrings)`, { orderStatusesAsStrings });
const ordersCountByRegion = await query
.groupBy('date, region.id')
.orderBy('date', 'ASC')
.getRawMany();
const finalOrders = ordersCountByRegion.map(order => {
return {
date: order.date,
orderCount: order.orderCount,
regionId: order.region_id,
regionName: order.region_name
};
});
return {
dateRangeFrom: startQueryFrom.getTime(),
dateRangeTo: to ? to.getTime() : new Date(Date.now()).getTime(),
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: finalOrders,
previous: []
};
}
}
return {
dateRangeFrom: undefined,
dateRangeTo: undefined,
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: [],
previous: []
};
}
async getRefunds(currencyCode, from, to, dateRangeFromCompareTo, dateRangeToCompareTo) {
if (dateRangeFromCompareTo && from && to && dateRangeToCompareTo) {
const query = this.activeManager_.getRepository(medusa_1.Refund)
.createQueryBuilder('refund')
.select(`
CASE
WHEN refund.created_at < :from AND refund.created_at >= :dateRangeFromCompareTo THEN 'previous'
ELSE 'current'
END AS type
`)
.setParameters({ from, dateRangeFromCompareTo })
.addSelect("SUM(refund.amount)", "sum")
.innerJoin('refund.order', 'order')
.where(`refund.created_at >= :dateRangeFromCompareTo`, { dateRangeFromCompareTo })
.andWhere(`refund.created_at <= :to`, { to })
.andWhere(`order.currency_code = :currencyCode`, { currencyCode });
const refunds = await query.groupBy('type').getRawMany();
const currentRefunds = refunds.find(refund => refund.type == 'current');
const previousRefunds = refunds.find(refund => refund.type == 'previous');
return {
currencyCode: currencyCode,
currencyDecimalDigits: (0, currency_1.getDecimalDigits)(currencyCode),
dateRangeFrom: from.getTime(),
dateRangeTo: to.getTime(),
dateRangeFromCompareTo: dateRangeFromCompareTo.getTime(),
dateRangeToCompareTo: dateRangeToCompareTo.getTime(),
current: currentRefunds !== undefined ? currentRefunds.sum : '0',
previous: previousRefunds !== undefined ? previousRefunds.sum : '0'
};
}
let startQueryFrom;
if (!dateRangeFromCompareTo) {
if (from) {
startQueryFrom = from;
}
else {
// All time
const lastRefund = await this.activeManager_.getRepository(medusa_1.Refund).find({
skip: 0,
take: 1,
order: { created_at: "ASC" },
});
if (lastRefund.length > 0) {
startQueryFrom = lastRefund[0].created_at;
}
}
}
else {
startQueryFrom = dateRangeFromCompareTo;
}
if (startQueryFrom) {
const endQuery = (0, dateTransformations_1.getQueryEndDate)(to);
const query = this.activeManager_.getRepository(medusa_1.Refund)
.createQueryBuilder('refund')
.select("SUM(refund.amount)", "sum")
.innerJoin('refund.order', 'order')
.where(`refund.created_at >= :startQueryFrom`, { startQueryFrom })
.andWhere(`refund.created_at <= :endQuery`, { endQuery })
.andWhere(`order.currency_code = :currencyCode`, { currencyCode });
const refunds = await query.getRawOne();
return {
currencyCode: currencyCode,
currencyDecimalDigits: (0, currency_1.getDecimalDigits)(currencyCode),
dateRangeFrom: startQueryFrom.getTime(),
dateRangeTo: to ? to.getTime() : new Date(Date.now()).getTime(),
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: refunds !== undefined ? refunds.sum : '0',
previous: undefined
};
}
return {
currencyCode: undefined,
currencyDecimalDigits: (0, currency_1.getDecimalDigits)(currencyCode),
dateRangeFrom: undefined,
dateRangeTo: undefined,
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: undefined,
previous: undefined
};
}
}
exports.default = SalesAnalyticsService;