UNPKG

@rsc-labs/medusa-store-analytics

Version:
437 lines (436 loc) 21.1 kB
"use strict"; /* * 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;