@rsc-labs/medusa-store-analytics-v2
Version:
Get analytics data about your store
324 lines • 29.2 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 });
exports.OrdersAnalyticsService = void 0;
const dateTransformations_1 = require("./../utils/dateTransformations");
class OrdersAnalyticsService {
constructor({ __pg_connection__ }) {
this.pgConnection = __pg_connection__;
}
async getOrdersHistory(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);
const orders = await this.pgConnection('order')
.select(this.pgConnection.raw(`
CASE
WHEN "order".created_at < ? AND "order".created_at >= ? THEN 'previous'
WHEN "order".created_at < ? AND "order".created_at >= ? THEN 'current'
END AS type,
date_trunc(?, "order".created_at) AS date
`, [dateRangeToCompareTo, dateRangeFromCompareTo, to, from, resolution]))
.count('order.id AS orderCount')
.where('order.created_at', '>=', dateRangeFromCompareTo)
.andWhere('order.status', 'IN', orderStatusesAsStrings)
.groupBy(['type', 'date'])
.orderByRaw('"date" ASC, "type" ASC');
const theOrders = orders;
const finalOrders = theOrders.reduce((acc, entry) => {
const { type, date, orderCount } = entry;
if (!acc[type]) {
acc[type] = [];
}
acc[type].push({ date, orderCount });
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 {
const lastOrder = await this.pgConnection('order')
.select('created_at')
.whereIn('status', orderStatusesAsStrings)
.orderBy('created_at', 'ASC')
.limit(1)
.then(result => result[0]);
if (lastOrder) {
startQueryFrom = lastOrder.created_at;
}
}
}
else {
startQueryFrom = dateRangeFromCompareTo;
}
if (startQueryFrom) {
const endQuery = to ? to : new Date(Date.now());
const resolution = (0, dateTransformations_1.calculateResolution)(startQueryFrom, endQuery);
const rawOrders = await this.pgConnection('order')
.select(this.pgConnection.raw(`date_trunc(?, "order".created_at) AS date`, [resolution]))
.count('order.id AS orderCount')
.where('order.created_at', '>=', startQueryFrom)
.andWhere('order.created_at', '<=', endQuery)
.andWhere('order.status', 'IN', orderStatusesAsStrings)
.groupBy('date')
.orderBy('date', 'ASC');
const orders = rawOrders;
return {
dateRangeFrom: startQueryFrom.getTime(),
dateRangeTo: endQuery.getTime(),
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: orders,
previous: []
};
}
}
return {
dateRangeFrom: undefined,
dateRangeTo: undefined,
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: [],
previous: []
};
}
async getOrdersCount(orderStatuses, from, to, dateRangeFromCompareTo, dateRangeToCompareTo) {
let startQueryFrom;
const orderStatusesAsStrings = Object.values(orderStatuses);
if (orderStatusesAsStrings.length) {
if (dateRangeFromCompareTo && from && to && dateRangeToCompareTo) {
const orders = await this.pgConnection
.select([
'id',
'created_at',
'updated_at',
'customer_id',
])
.select(this.pgConnection.raw(`CASE
WHEN "order".created_at < ? AND "order".created_at >= ? THEN 'previous'
WHEN "order".created_at < ? AND "order".created_at >= ? THEN 'current'
END AS type
`, [dateRangeToCompareTo, dateRangeFromCompareTo, to, from]))
.from('order')
.whereIn('status', orderStatusesAsStrings)
.andWhere('order.created_at', '>=', dateRangeFromCompareTo)
.groupBy(['type', 'id'])
.orderBy([{ column: 'type', order: 'ASC' }])
.then(result => result);
const previousOrders = orders.filter(order => order.type == 'previous');
const currentOrders = orders.filter(order => order.type == 'current');
return {
dateRangeFrom: from.getTime(),
dateRangeTo: to.getTime(),
dateRangeFromCompareTo: dateRangeFromCompareTo.getTime(),
dateRangeToCompareTo: dateRangeToCompareTo.getTime(),
current: currentOrders.length,
previous: previousOrders.length
};
}
if (!dateRangeFromCompareTo) {
if (from) {
startQueryFrom = from;
}
else {
const lastOrder = await this.pgConnection('order')
.select('created_at')
.whereIn('status', orderStatusesAsStrings)
.orderBy('created_at', 'ASC')
.limit(1)
.then(result => result[0]);
if (lastOrder) {
startQueryFrom = lastOrder.created_at;
}
}
}
else {
startQueryFrom = dateRangeFromCompareTo;
}
if (startQueryFrom) {
const endQuery = to ? to : new Date(Date.now());
const orders = await this.pgConnection('order')
.select([
'id',
'created_at',
'updated_at',
'customer_id',
])
.whereIn('status', orderStatusesAsStrings)
.andWhere('created_at', '>=', startQueryFrom)
.andWhere('created_at', '<=', endQuery)
.orderBy('created_at', 'DESC')
.then(result => result);
return {
dateRangeFrom: startQueryFrom.getTime(),
dateRangeTo: to ? to.getTime() : new Date(Date.now()).getTime(),
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: orders.length,
previous: 0
};
}
}
return {
dateRangeFrom: undefined,
dateRangeTo: undefined,
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: 0,
previous: 0
};
}
async getPaymentProviderPopularity(from, to, dateRangeFromCompareTo, dateRangeToCompareTo) {
function calculateSumAndPercentageOfResults(results) {
const orderMap = new Map();
let allSum = 0;
results.forEach(result => {
const { orderCount, paymentProviderId } = result;
if (orderMap.has(paymentProviderId)) {
const sum = parseInt(orderMap.get(paymentProviderId)) + parseInt(orderCount);
orderMap.set(paymentProviderId, sum.toFixed());
}
else {
orderMap.set(paymentProviderId, orderCount);
}
});
const newArray = [];
orderMap.forEach((value) => {
allSum += parseInt(value);
});
orderMap.forEach((value, key) => {
newArray.push({
orderCount: value,
percentage: (parseInt(value) * 100 / allSum).toFixed(2),
paymentProviderId: key
});
});
return newArray;
}
if (dateRangeFromCompareTo && from && to && dateRangeToCompareTo) {
const resolution = (0, dateTransformations_1.calculateResolution)(from);
const rawOrdersCountWithPayments = await this.pgConnection('order')
.select(this.pgConnection.raw(`
CASE
WHEN "order".created_at < ? AND "order".created_at >= ? THEN 'previous'
WHEN "order".created_at < ? AND "order".created_at >= ? THEN 'current'
END AS type,
date_trunc(?, "order".created_at) AS date
`, [dateRangeToCompareTo, dateRangeFromCompareTo, to, from, resolution]))
.count('order.id AS orderCount')
.select('payment.provider_id AS paymentProviderId')
.innerJoin('order_payment_collection', 'order.id', 'order_payment_collection.order_id')
.innerJoin('payment_collection', 'order_payment_collection.payment_collection_id', 'payment_collection.id')
.innerJoin('payment', 'payment.payment_collection_id', 'payment_collection.id')
.where('order.created_at', '>=', dateRangeFromCompareTo)
.groupByRaw(`date, type, payment.provider_id`)
.orderBy('date', 'ASC');
const ordersCountWithPayments = rawOrdersCountWithPayments;
const finalOrders = ordersCountWithPayments.reduce((acc, entry) => {
const type = entry.type;
const orderCount = entry.orderCount;
const paymentProviderId = entry.paymentProviderId;
if (!acc[type]) {
acc[type] = [];
}
acc[type].push({
orderCount,
paymentProviderId,
});
return acc;
}, {});
const finalOrdersCurrentGrouped = calculateSumAndPercentageOfResults(finalOrders.current ? finalOrders.current : []);
const finalOrdersPreviousGrouped = calculateSumAndPercentageOfResults(finalOrders.previous ? finalOrders.previous : []);
return {
dateRangeFrom: from.getTime(),
dateRangeTo: to.getTime(),
dateRangeFromCompareTo: dateRangeFromCompareTo.getTime(),
dateRangeToCompareTo: dateRangeToCompareTo.getTime(),
current: finalOrdersCurrentGrouped ? finalOrdersCurrentGrouped : [],
previous: finalOrdersPreviousGrouped ? finalOrdersPreviousGrouped : [],
};
}
let startQueryFrom;
if (!dateRangeFromCompareTo) {
if (from) {
startQueryFrom = from;
}
else {
const lastOrder = await this.pgConnection('order')
.select('created_at')
.orderBy('created_at', 'ASC')
.limit(1)
.then(result => result[0]);
if (lastOrder) {
startQueryFrom = lastOrder.created_at;
}
}
}
else {
startQueryFrom = dateRangeFromCompareTo;
}
if (startQueryFrom) {
const endQuery = to ? to : new Date(Date.now());
const resolution = (0, dateTransformations_1.calculateResolution)(startQueryFrom, endQuery);
const rawOrdersCountWithPayments = await this.pgConnection('order')
.select(this.pgConnection.raw(`date_trunc(?, "order".created_at) AS date`, [resolution]))
.count('order.id AS orderCount')
.select('payment.provider_id AS paymentProviderId')
.innerJoin('order_payment_collection', 'order.id', 'order_payment_collection.order_id')
.innerJoin('payment_collection', 'order_payment_collection.payment_collection_id', 'payment_collection.id')
.innerJoin('payment', 'payment.payment_collection_id', 'payment_collection.id')
.where('order.created_at', '>=', startQueryFrom)
.andWhere('order.created_at', '<=', endQuery)
.groupByRaw(`date, payment.provider_id`)
.orderBy('date', 'ASC');
const ordersCountWithPayments = rawOrdersCountWithPayments;
const initialOrders = ordersCountWithPayments.map(order => {
return {
orderCount: order.orderCount,
paymentProviderId: order.paymentProviderId,
};
});
const finalOrdersGrouped = calculateSumAndPercentageOfResults(initialOrders ? initialOrders : []);
return {
dateRangeFrom: startQueryFrom.getTime(),
dateRangeTo: to ? to.getTime() : new Date(Date.now()).getTime(),
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: finalOrdersGrouped,
previous: []
};
}
return {
dateRangeFrom: undefined,
dateRangeTo: undefined,
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: [],
previous: []
};
}
}
exports.OrdersAnalyticsService = OrdersAnalyticsService;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JkZXJzQW5hbHl0aWNzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL21vZHVsZXMvc3RvcmUtYW5hbHl0aWNzL3NlcnZpY2VzL29yZGVyc0FuYWx5dGljcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7Ozs7R0FVRzs7O0FBR0gsd0VBQW9FO0FBbURwRSxNQUFhLHNCQUFzQjtJQUlqQyxZQUFZLEVBQUUsaUJBQWlCLEVBQXdCO1FBQ3JELElBQUksQ0FBQyxZQUFZLEdBQUcsaUJBQWlCLENBQUE7SUFDdkMsQ0FBQztJQUVELEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxhQUE0QixFQUFFLElBQVcsRUFBRSxFQUFTLEVBQUUsc0JBQTZCLEVBQUUsb0JBQTJCO1FBQ3JJLE1BQU0sc0JBQXNCLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUM1RCxJQUFJLHNCQUFzQixDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ2xDLElBQUksc0JBQXNCLElBQUksSUFBSSxJQUFJLEVBQUUsSUFBSSxvQkFBb0IsRUFBRSxDQUFDO2dCQUNqRSxNQUFNLFVBQVUsR0FBRyxJQUFBLHlDQUFtQixFQUFDLElBQUksQ0FBQyxDQUFDO2dCQUM3QyxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDO3FCQUM1QyxNQUFNLENBQ0wsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUM7Ozs7OzthQU1yQixFQUFFLENBQUMsb0JBQW9CLEVBQUUsc0JBQXNCLEVBQUUsRUFBRSxFQUFFLElBQUksRUFBRSxVQUFVLENBQUMsQ0FBQyxDQUN6RTtxQkFDQSxLQUFLLENBQUMsd0JBQXdCLENBQUM7cUJBQy9CLEtBQUssQ0FBQyxrQkFBa0IsRUFBRSxJQUFJLEVBQUUsc0JBQXNCLENBQUM7cUJBQ3ZELFFBQVEsQ0FBQyxjQUFjLEVBQUUsSUFBSSxFQUFFLHNCQUFzQixD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