UNPKG

@rsc-labs/medusa-store-analytics-v2

Version:
578 lines 53.6 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 }); 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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