@rsc-labs/medusa-store-analytics-v2
Version:
Get analytics data about your store
578 lines • 53.6 kB
JavaScript
"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 });
exports.CustomersAnalyticsService = void 0;
const dateTransformations_1 = require("../utils/dateTransformations");
class CustomersAnalyticsService {
constructor({ __pg_connection__ }) {
this.pgConnection = __pg_connection__;
}
async getHistory(from, to, dateRangeFromCompareTo, dateRangeToCompareTo) {
if (dateRangeFromCompareTo && from && to && dateRangeToCompareTo) {
const resolution = (0, dateTransformations_1.calculateResolution)(from);
const customers = await this.pgConnection
.select([
this.pgConnection.raw(`CASE
WHEN customer.created_at < ? AND customer.created_at >= ? THEN 'previous'
WHEN customer.created_at < ? AND customer.created_at >= ? THEN 'current'
END AS type`, [dateRangeToCompareTo, dateRangeFromCompareTo, to, from]),
this.pgConnection.raw(`date_trunc(?, customer.created_at) AS date`, [resolution]),
this.pgConnection.raw(`COUNT(customer.id) AS customerCount`)
])
.from('customer')
.where('customer.created_at', '>=', dateRangeFromCompareTo)
.groupBy(['type', 'date'])
.orderBy([{ column: 'date', order: 'ASC' }, { column: 'type', order: 'ASC' }]);
const finalCustomers = customers.reduce((acc, entry) => {
const type = entry.type;
const date = entry.date;
const customerCount = entry.customercount;
if (!acc[type]) {
acc[type] = [];
}
acc[type].push({ date, customerCount });
return acc;
}, {});
return {
dateRangeFrom: from.getTime(),
dateRangeTo: to.getTime(),
dateRangeFromCompareTo: dateRangeFromCompareTo.getTime(),
dateRangeToCompareTo: dateRangeToCompareTo.getTime(),
current: finalCustomers.current ? finalCustomers.current : [],
previous: finalCustomers.previous ? finalCustomers.previous : [],
};
}
let startQueryFrom;
if (!dateRangeFromCompareTo) {
if (from) {
startQueryFrom = from;
}
else {
const lastCustomer = await this.pgConnection('customer')
.select('created_at')
.orderBy('created_at', 'ASC')
.first();
if (lastCustomer) {
startQueryFrom = lastCustomer.created_at;
}
}
}
else {
startQueryFrom = dateRangeFromCompareTo;
}
if (startQueryFrom) {
const endQuery = to ? to : new Date(Date.now());
const resolution = (0, dateTransformations_1.calculateResolution)(startQueryFrom, endQuery);
const customers = await this.pgConnection('customer')
.select([
this.pgConnection.raw(`date_trunc(?, customer.created_at) AS date`, [resolution]),
this.pgConnection.raw(`COUNT(customer.id) AS customerCount`)
])
.where('customer.created_at', '>=', startQueryFrom)
.andWhere('customer.created_at', '<=', endQuery)
.groupBy('date')
.orderBy('date', 'ASC');
const finalCustomers = customers.map(customer => {
const rawCustomer = customer;
return {
customerCount: rawCustomer.customercount,
date: rawCustomer.date
};
});
return {
dateRangeFrom: startQueryFrom.getTime(),
dateRangeTo: endQuery.getTime(),
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: finalCustomers,
previous: []
};
}
return {
dateRangeFrom: undefined,
dateRangeTo: undefined,
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: [],
previous: []
};
}
async getNewCount(from, to, dateRangeFromCompareTo, dateRangeToCompareTo) {
if (dateRangeFromCompareTo && from && to && dateRangeToCompareTo) {
const customers = await this.pgConnection
.select([
this.pgConnection.raw(`CASE
WHEN customer.created_at < ? AND customer.created_at >= ? THEN 'previous'
WHEN customer.created_at < ? AND customer.created_at >= ? THEN 'current'
END AS type`, [dateRangeToCompareTo, dateRangeFromCompareTo, to, from]),
this.pgConnection.raw(`COUNT(customer.id) AS customerCount`)
])
.from('customer')
.where('customer.created_at', '>=', dateRangeFromCompareTo)
.groupBy(['type'])
.orderBy([{ column: 'type', order: 'ASC' }]);
const finalCustomers = customers.reduce((acc, entry) => {
const type = entry.type;
const customerCount = entry.customercount;
if (!acc[type]) {
acc[type] = [];
}
acc[type].push({ customerCount });
return acc;
}, {});
return {
dateRangeFrom: from.getTime(),
dateRangeTo: to.getTime(),
dateRangeFromCompareTo: dateRangeFromCompareTo.getTime(),
dateRangeToCompareTo: dateRangeToCompareTo.getTime(),
current: finalCustomers.current ? parseInt(finalCustomers.current[0].customerCount) : 0,
previous: finalCustomers.previous ? parseInt(finalCustomers.previous[0].customerCount) : 0,
};
}
let startQueryFrom;
if (!dateRangeFromCompareTo) {
if (from) {
startQueryFrom = from;
}
else {
const lastCustomer = await this.pgConnection('customer')
.select('created_at')
.orderBy('created_at', 'ASC')
.first();
if (lastCustomer) {
startQueryFrom = lastCustomer.created_at;
}
}
}
else {
startQueryFrom = dateRangeFromCompareTo;
}
if (startQueryFrom) {
const endQuery = to ? to : new Date(Date.now());
const customersCount = await this.pgConnection('customer')
.count({ count: '*' })
.where('created_at', '>=', startQueryFrom)
.andWhere('created_at', '<=', endQuery)
.first();
const totalCustomersCount = parseInt(customersCount?.count?.toString() || '0', 10);
return {
dateRangeFrom: startQueryFrom.getTime(),
dateRangeTo: endQuery.getTime(),
dateRangeFromCompareTo: dateRangeFromCompareTo ? dateRangeFromCompareTo.getTime() : undefined,
dateRangeToCompareTo: dateRangeToCompareTo ? dateRangeToCompareTo.getTime() : undefined,
current: totalCustomersCount,
previous: 0
};
}
return {
dateRangeFrom: undefined,
dateRangeTo: undefined,
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: 0,
previous: 0
};
}
async getNumberOfReturningCustomers() {
const customersData = await this.pgConnection('customer')
.where('orders', '>', 1)
.count({ count: '*' })
.first();
const totalCustomersCount = parseInt(customersData?.count, 10) || 0;
return totalCustomersCount;
}
async getRepeatCustomerRate(orderStatuses, from, to, dateRangeFromCompareTo, dateRangeToCompareTo) {
// Use the same query like finding for Orders, but include Customers
let startQueryFrom;
const orderStatusesAsStrings = Object.values(orderStatuses);
if (orderStatusesAsStrings.length) {
if (dateRangeFromCompareTo && from && to && dateRangeToCompareTo) {
const orders = await this.pgConnection
.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]))
.select([
'id',
'created_at',
'updated_at',
'customer_id',
])
.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');
const previousOrderCountByCustomer = previousOrders.reduce((acc, order) => {
acc[order.customer_id] = (acc[order.customer_id] || 0) + 1;
return acc;
}, new Map());
const currentOrderCountByCustomer = currentOrders.reduce((acc, order) => {
acc[order.customer_id] = (acc[order.customer_id] || 0) + 1;
return acc;
}, new Map());
const returningCustomersForCurrentOrders = Object.values(currentOrderCountByCustomer).filter(count => parseInt(count) > 1).length;
const totalCustomersForCurrentOrders = Object.keys(currentOrderCountByCustomer).length;
const returningCustomersForPreviousOrders = Object.values(previousOrderCountByCustomer).filter(count => parseInt(count) > 1).length;
const totalCustomersForPreviousOrders = Object.keys(previousOrderCountByCustomer).length;
// Return Customer Rate
const returnCustomerRateCurrentValue = totalCustomersForCurrentOrders > 0 ? returningCustomersForCurrentOrders * 100 / totalCustomersForCurrentOrders : undefined;
const returnCustomerRatePreviousValue = totalCustomersForPreviousOrders > 0 ? returningCustomersForPreviousOrders * 100 / totalCustomersForPreviousOrders : undefined;
// Order frequency distribution
let currentOneTimeOrders = 0;
let currentRepeatOrders = 0;
for (const count of Object.values(currentOrderCountByCustomer)) {
if (parseInt(count) === 1) {
currentOneTimeOrders += count;
}
else if (parseInt(count) > 1) {
currentRepeatOrders += count;
}
}
let previousOneTimeOrders = 0;
let previousRepeatOrders = 0;
for (const count of Object.values(previousOrderCountByCustomer)) {
if (parseInt(count) === 1) {
previousOneTimeOrders += count;
}
else if (parseInt(count) > 1) {
previousRepeatOrders += count;
}
}
return {
dateRangeFrom: from.getTime(),
dateRangeTo: to.getTime(),
dateRangeFromCompareTo: dateRangeFromCompareTo.getTime(),
dateRangeToCompareTo: dateRangeToCompareTo.getTime(),
current: {
returnCustomerRate: returnCustomerRateCurrentValue,
orderOneTimeFrequency: currentOneTimeOrders * 100 / currentOrders.length,
orderRepeatFrequency: currentRepeatOrders * 100 / currentOrders.length
},
previous: {
returnCustomerRate: returnCustomerRatePreviousValue,
orderOneTimeFrequency: previousOneTimeOrders * 100 / previousOrders.length,
orderRepeatFrequency: previousRepeatOrders * 100 / 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);
const orderCountByCustomer = orders.reduce((acc, order) => {
acc[order.customer_id] = (acc[order.customer_id] || 0) + 1;
return acc;
}, new Map());
const returningCustomersForCurrentOrders = Object.values(orderCountByCustomer).filter(count => parseInt(count) > 1).length;
const totalCustomersForCurrentOrders = Object.keys(orderCountByCustomer).length;
// Return Customer Rate
const returnCustomerRateCurrentValue = totalCustomersForCurrentOrders > 0 ? returningCustomersForCurrentOrders * 100 / totalCustomersForCurrentOrders : undefined;
// Order frequency distribution
let currentOneTimeOrders = 0;
let currentRepeatOrders = 0;
for (const count of Object.values(orderCountByCustomer)) {
if (parseInt(count) === 1) {
currentOneTimeOrders += count;
}
else if (parseInt(count) > 1) {
currentRepeatOrders += count;
}
}
return {
dateRangeFrom: startQueryFrom.getTime(),
dateRangeTo: endQuery.getTime(),
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: {
returnCustomerRate: returnCustomerRateCurrentValue,
orderOneTimeFrequency: currentOneTimeOrders * 100 / orders.length,
orderRepeatFrequency: currentRepeatOrders * 100 / orders.length
},
previous: undefined
};
}
}
return {
dateRangeFrom: undefined,
dateRangeTo: undefined,
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: {
returnCustomerRate: 0
},
previous: {
returnCustomerRate: 0
}
};
}
async getCumulativeHistory(from, to, dateRangeFromCompareTo, dateRangeToCompareTo) {
if (dateRangeFromCompareTo && from && to && dateRangeToCompareTo) {
const resolution = (0, dateTransformations_1.calculateResolution)(from);
const afterCustomers = await this.pgConnection('customer')
.select([
this.pgConnection.raw(`date_trunc(?, customer.created_at) AS date`, [resolution]),
this.pgConnection.raw(`COUNT(customer.id) AS customerCount`)
])
.where('customer.created_at', '>=', from)
.groupBy('date')
.orderBy('date', 'ASC');
let cumulativeCount = 0;
const customersWithCumulativeCount = afterCustomers.map((customer) => {
const row = customer;
cumulativeCount += parseInt(row.customercount); // Add current count to cumulative sum
return {
...row,
cumulative_count: cumulativeCount
};
});
const beforeCustomers = await this.pgConnection('customer')
.count({ cumulative_count: '*' })
.where('created_at', '<', dateRangeToCompareTo)
.first();
// Start from 0 as customer count will be added from beforeCustomers, so first entry will include past count
customersWithCumulativeCount.push({
date: dateRangeFromCompareTo,
cumulative_count: '0'
});
for (const customerWithCumulativeCount of customersWithCumulativeCount) {
customerWithCumulativeCount.cumulative_count = parseInt(customerWithCumulativeCount.cumulative_count);
customerWithCumulativeCount.cumulative_count += parseInt(beforeCustomers ? beforeCustomers.cumulative_count?.toString() || '0' : '0');
}
const previousCustomers = customersWithCumulativeCount.filter(customer => customer.date < from);
const currentCustomers = customersWithCumulativeCount.filter(customer => customer.date >= from);
const finalCustomers = {
dateRangeFrom: from.getTime(),
dateRangeTo: to.getTime(),
dateRangeFromCompareTo: dateRangeFromCompareTo.getTime(),
dateRangeToCompareTo: dateRangeToCompareTo.getTime(),
current: currentCustomers.map(currentCustomer => {
return {
date: currentCustomer.date,
customerCount: currentCustomer.cumulative_count.toString()
};
}),
previous: previousCustomers.map(previousCustomers => {
return {
date: previousCustomers.date,
customerCount: previousCustomers.cumulative_count.toString()
};
})
};
return finalCustomers;
}
let startQueryFrom;
if (!dateRangeFromCompareTo) {
if (from) {
startQueryFrom = from;
}
else {
const lastCustomer = await this.pgConnection('customer')
.select('created_at')
.orderBy('created_at', 'ASC')
.first();
if (lastCustomer) {
startQueryFrom = lastCustomer.created_at;
}
}
}
else {
startQueryFrom = dateRangeFromCompareTo;
}
if (startQueryFrom) {
const endQuery = to ? to : new Date(Date.now());
const resolution = (0, dateTransformations_1.calculateResolution)(startQueryFrom, endQuery);
const afterCustomers = await this.pgConnection('customer')
.select([
this.pgConnection.raw(`date_trunc(?, customer.created_at) AS date`, [resolution]),
this.pgConnection.raw(`COUNT(customer.id) AS customerCount`)
])
.where('customer.created_at', '>=', startQueryFrom)
.andWhere('customer.created_at', '<=', endQuery)
.groupBy('date')
.orderBy('date', 'ASC');
let cumulativeCount = 0;
const customersWithCumulativeCount = afterCustomers.map((customer) => {
const row = customer;
cumulativeCount += parseInt(row.customercount);
return {
...row,
cumulative_count: cumulativeCount
};
});
const finalCustomers = {
dateRangeFrom: startQueryFrom.getTime(),
dateRangeTo: endQuery.getTime(),
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: customersWithCumulativeCount.map(currentCustomer => {
return {
date: currentCustomer.date,
customerCount: currentCustomer.cumulative_count.toString()
};
}),
previous: []
};
return finalCustomers;
}
return {
dateRangeFrom: undefined,
dateRangeTo: undefined,
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: [],
previous: []
};
}
// Customers which purchased something in the time period / Total customers
async getRetentionRate(orderStatuses, from, to, dateRangeFromCompareTo, dateRangeToCompareTo) {
// Use the same query like finding for Orders, but include Customers
let startQueryFrom;
const orderStatusesAsStrings = Object.values(orderStatuses);
if (orderStatusesAsStrings.length) {
const totalNumberCustomers = await this.pgConnection('customer')
.count('* as total')
.then(result => result[0].total);
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');
const previousCustomersSet = previousOrders.reduce((acc, order) => {
acc.add(order.customer_id);
return acc;
}, new Set());
const currentCustomersSet = currentOrders.reduce((acc, order) => {
acc.add(order.customer_id);
return acc;
}, new Set());
const retentionCustomerRatePreviousValue = previousCustomersSet.size * 100 / parseInt(totalNumberCustomers.toString());
const retentionCustomerRateCurrentValue = currentCustomersSet.size * 100 / parseInt(totalNumberCustomers.toString());
return {
dateRangeFrom: from.getTime(),
dateRangeTo: to.getTime(),
dateRangeFromCompareTo: dateRangeFromCompareTo.getTime(),
dateRangeToCompareTo: dateRangeToCompareTo.getTime(),
current: retentionCustomerRateCurrentValue,
previous: retentionCustomerRatePreviousValue
};
}
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);
const currentCustomersSet = orders.reduce((acc, order) => {
acc.add(order.customer_id);
return acc;
}, new Set());
const retentionCustomerRateCurrentValue = currentCustomersSet.size * 100 / parseInt(totalNumberCustomers.toString());
return {
dateRangeFrom: startQueryFrom.getTime(),
dateRangeTo: endQuery.getTime(),
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: retentionCustomerRateCurrentValue,
previous: undefined
};
}
}
return {
dateRangeFrom: undefined,
dateRangeTo: undefined,
dateRangeFromCompareTo: undefined,
dateRangeToCompareTo: undefined,
current: undefined,
previous: undefined
};
}
}
exports.CustomersAnalyticsService = CustomersAnalyticsService;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3VzdG9tZXJzQW5hbHl0aWNzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL21vZHVsZXMvc3RvcmUtYW5hbHl0aWNzL3NlcnZpY2VzL2N1c3RvbWVyc0FuYWx5dGljcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7Ozs7R0FVRzs7O0FBR0gsc0VBQWtFO0FBc0RsRSxNQUFhLHlCQUF5QjtJQUlwQyxZQUFZLEVBQUUsaUJBQWlCLEVBQXdCO1FBQ3JELElBQUksQ0FBQyxZQUFZLEdBQUcsaUJBQWlCLENBQUE7SUFDdkMsQ0FBQztJQUVELEtBQUssQ0FBQyxVQUFVLENBQUMsSUFBVyxFQUFFLEVBQVMsRUFBRSxzQkFBNkIsRUFBRSxvQkFBMkI7UUFDakcsSUFBSSxzQkFBc0IsSUFBSSxJQUFJLElBQUksRUFBRSxJQUFJLG9CQUFvQixFQUFFLENBQUM7WUFDL0QsTUFBTSxVQUFVLEdBQUcsSUFBQSx5Q0FBbUIsRUFBQyxJQUFJLENBQUMsQ0FBQztZQUM3QyxNQUFNLFNBQVMsR0FBRyxNQUFNLElBQUksQ0FBQyxZQUFZO2lCQUN4QyxNQUFNLENBQUM7Z0JBQ04sSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQ25COzs7eUJBR2EsRUFDYixDQUFDLG9CQUFvQixFQUFFLHNCQUFzQixFQUFFLEVBQUUsRUFBRSxJQUFJLENBQUMsQ0FDekQ7Z0JBQ0QsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsNENBQTRDLEVBQUUsQ0FBQyxVQUFVLENBQUMsQ0FBQztnQkFDakYsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMscUNBQXFDLENBQUM7YUFDN0QsQ0FBQztpQkFDRCxJQUFJLENBQUMsVUFBVSxDQUFDO2lCQUNoQixLQUFLLENBQUMscUJBQXFCLEVBQUUsSUFBSSxFQUFFLHNCQUFzQixDQUFDO2lCQUMxRCxPQUFPLENBQUMsQ0FBQyxNQUFNLEVBQUUsTUFBTSxDQUFDLENBQUM7aUJB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