Get some data in
This commit is contained in:
@@ -1294,38 +1294,70 @@ export class EventsService {
|
||||
|
||||
return events.map(event => {
|
||||
try {
|
||||
// Extract metric ID from the relationships field if it exists
|
||||
// Extract metric ID from all possible locations
|
||||
const metricId = event.relationships?.metric?.data?.id ||
|
||||
event.attributes?.metric?.id ||
|
||||
event.attributes?.metric_id;
|
||||
|
||||
// Extract properties from all possible locations in the Klaviyo event structure
|
||||
const eventProps = {
|
||||
// Extract properties from all possible locations
|
||||
const rawProps = {
|
||||
...(event.attributes?.event_properties || {}),
|
||||
...(event.attributes?.properties || {}),
|
||||
...(event.attributes?.profile || {}),
|
||||
value: event.attributes?.value,
|
||||
datetime: event.attributes?.datetime
|
||||
};
|
||||
|
||||
|
||||
// Normalize shipping data
|
||||
const shippingData = {
|
||||
name: eventProps.ShippingName || eventProps.shipping_name || eventProps.shipping?.name,
|
||||
street1: eventProps.ShippingStreet1 || eventProps.shipping_street1 || eventProps.shipping?.street1,
|
||||
street2: eventProps.ShippingStreet2 || eventProps.shipping_street2 || eventProps.shipping?.street2,
|
||||
city: eventProps.ShippingCity || eventProps.shipping_city || eventProps.shipping?.city,
|
||||
state: eventProps.ShippingState || eventProps.shipping_state || eventProps.shipping?.state,
|
||||
zip: eventProps.ShippingZip || eventProps.shipping_zip || eventProps.shipping?.zip,
|
||||
country: eventProps.ShippingCountry || eventProps.shipping_country || eventProps.shipping?.country,
|
||||
method: eventProps.ShipMethod || eventProps.shipping_method || eventProps.shipping?.method,
|
||||
tracking: eventProps.TrackingNumber || eventProps.tracking_number
|
||||
name: rawProps.ShippingName || rawProps.shipping_name || rawProps.shipping?.name,
|
||||
street1: rawProps.ShippingStreet1 || rawProps.shipping_street1 || rawProps.shipping?.street1,
|
||||
street2: rawProps.ShippingStreet2 || rawProps.shipping_street2 || rawProps.shipping?.street2,
|
||||
city: rawProps.ShippingCity || rawProps.shipping_city || rawProps.shipping?.city,
|
||||
state: rawProps.ShippingState || rawProps.shipping_state || rawProps.shipping?.state,
|
||||
zip: rawProps.ShippingZip || rawProps.shipping_zip || rawProps.shipping?.zip,
|
||||
country: rawProps.ShippingCountry || rawProps.shipping_country || rawProps.shipping?.country,
|
||||
method: rawProps.ShipMethod || rawProps.shipping_method || rawProps.shipping?.method,
|
||||
tracking: rawProps.TrackingNumber || rawProps.tracking_number
|
||||
};
|
||||
|
||||
// Normalize payment data
|
||||
const paymentData = {
|
||||
method: rawProps.PaymentMethod || rawProps.payment_method || rawProps.payment?.method,
|
||||
name: rawProps.PaymentName || rawProps.payment_name || rawProps.payment?.name,
|
||||
amount: Number(rawProps.PaymentAmount || rawProps.payment_amount || rawProps.payment?.amount || 0)
|
||||
};
|
||||
|
||||
// Normalize order flags
|
||||
const orderFlags = {
|
||||
type: rawProps.OrderType || rawProps.order_type || 'standard',
|
||||
hasPreorder: Boolean(rawProps.HasPreorder || rawProps.has_preorder || rawProps.preorder),
|
||||
localPickup: Boolean(rawProps.LocalPickup || rawProps.local_pickup || rawProps.pickup),
|
||||
isOnHold: Boolean(rawProps.IsOnHold || rawProps.is_on_hold || rawProps.on_hold),
|
||||
hasDigiItem: Boolean(rawProps.HasDigiItem || rawProps.has_digital_item || rawProps.digital_item),
|
||||
hasNotions: Boolean(rawProps.HasNotions || rawProps.has_notions || rawProps.notions),
|
||||
hasDigitalGC: Boolean(rawProps.HasDigitalGC || rawProps.has_digital_gc || rawProps.gift_card),
|
||||
stillOwes: Boolean(rawProps.StillOwes || rawProps.still_owes || rawProps.balance_due)
|
||||
};
|
||||
|
||||
// Normalize refund/cancel data
|
||||
const refundData = {
|
||||
reason: rawProps.CancelReason || rawProps.cancel_reason || rawProps.reason,
|
||||
message: rawProps.CancelMessage || rawProps.cancel_message || rawProps.message,
|
||||
orderMessage: rawProps.OrderMessage || rawProps.order_message || rawProps.note
|
||||
};
|
||||
|
||||
// Transform items
|
||||
const items = this._transformItems(rawProps.Items || rawProps.items || rawProps.line_items || []);
|
||||
|
||||
// Calculate totals
|
||||
const totalAmount = Number(rawProps.TotalAmount || rawProps.PaymentAmount || rawProps.total_amount || rawProps.value || 0);
|
||||
const itemCount = items.reduce((sum, item) => sum + Number(item.Quantity || item.QuantityOrdered || 1), 0);
|
||||
|
||||
const transformed = {
|
||||
id: event.id,
|
||||
type: event.type,
|
||||
metric_id: metricId,
|
||||
// Preserve the original attributes structure for compatibility
|
||||
attributes: {
|
||||
...event.attributes,
|
||||
datetime: event.attributes?.datetime,
|
||||
@@ -1337,42 +1369,29 @@ export class EventsService {
|
||||
},
|
||||
relationships: event.relationships,
|
||||
event_properties: {
|
||||
...eventProps,
|
||||
// Transform common properties
|
||||
EmailAddress: eventProps.EmailAddress || eventProps.email,
|
||||
FirstName: eventProps.FirstName || eventProps.first_name,
|
||||
LastName: eventProps.LastName || eventProps.last_name,
|
||||
OrderId: eventProps.OrderId || eventProps.FromOrder || eventProps.order_id,
|
||||
TotalAmount: Number(eventProps.TotalAmount || eventProps.PaymentAmount || eventProps.total_amount || eventProps.value || 0),
|
||||
Items: this._transformItems(eventProps.Items || eventProps.items || eventProps.line_items || []),
|
||||
// Add normalized shipping information
|
||||
ShippingName: shippingData.name,
|
||||
ShippingStreet1: shippingData.street1,
|
||||
ShippingStreet2: shippingData.street2,
|
||||
ShippingCity: shippingData.city,
|
||||
ShippingState: shippingData.state,
|
||||
ShippingZip: shippingData.zip,
|
||||
ShippingCountry: shippingData.country,
|
||||
ShippingMethod: shippingData.method,
|
||||
TrackingNumber: shippingData.tracking,
|
||||
ShipMethod: shippingData.method,
|
||||
// Add payment information
|
||||
PaymentMethod: eventProps.PaymentMethod || eventProps.payment_method || eventProps.payment?.method,
|
||||
PaymentName: eventProps.PaymentName || eventProps.payment_name || eventProps.payment?.name,
|
||||
PaymentAmount: Number(eventProps.PaymentAmount || eventProps.payment_amount || eventProps.payment?.amount || 0),
|
||||
// Add order flags
|
||||
OrderType: eventProps.OrderType || eventProps.order_type || 'standard',
|
||||
HasPreorder: Boolean(eventProps.HasPreorder || eventProps.has_preorder || eventProps.preorder),
|
||||
LocalPickup: Boolean(eventProps.LocalPickup || eventProps.local_pickup || eventProps.pickup),
|
||||
IsOnHold: Boolean(eventProps.IsOnHold || eventProps.is_on_hold || eventProps.on_hold),
|
||||
HasDigiItem: Boolean(eventProps.HasDigiItem || eventProps.has_digital_item || eventProps.digital_item),
|
||||
HasNotions: Boolean(eventProps.HasNotions || eventProps.has_notions || eventProps.notions),
|
||||
HasDigitalGC: Boolean(eventProps.HasDigitalGC || eventProps.has_digital_gc || eventProps.gift_card),
|
||||
StillOwes: Boolean(eventProps.StillOwes || eventProps.still_owes || eventProps.balance_due),
|
||||
// Add refund/cancel information
|
||||
CancelReason: eventProps.CancelReason || eventProps.cancel_reason || eventProps.reason,
|
||||
CancelMessage: eventProps.CancelMessage || eventProps.cancel_message || eventProps.message,
|
||||
OrderMessage: eventProps.OrderMessage || eventProps.order_message || eventProps.note
|
||||
// Basic properties
|
||||
EmailAddress: rawProps.EmailAddress || rawProps.email,
|
||||
FirstName: rawProps.FirstName || rawProps.first_name,
|
||||
LastName: rawProps.LastName || rawProps.last_name,
|
||||
OrderId: rawProps.OrderId || rawProps.FromOrder || rawProps.order_id,
|
||||
TotalAmount: totalAmount,
|
||||
ItemCount: itemCount,
|
||||
Items: items,
|
||||
|
||||
// Shipping information
|
||||
...shippingData,
|
||||
|
||||
// Payment information
|
||||
...paymentData,
|
||||
|
||||
// Order flags
|
||||
...orderFlags,
|
||||
|
||||
// Refund/cancel information
|
||||
...refundData,
|
||||
|
||||
// Original properties (for backward compatibility)
|
||||
...rawProps
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1398,23 +1417,55 @@ export class EventsService {
|
||||
}
|
||||
|
||||
return items.map(item => {
|
||||
const transformed = {
|
||||
...item,
|
||||
ProductID: item.ProductID || item.product_id,
|
||||
ProductName: item.ProductName || item.product_name,
|
||||
SKU: item.SKU || item.sku,
|
||||
Brand: item.Brand || item.brand,
|
||||
Categories: item.Categories || item.categories || [],
|
||||
ItemPrice: Number(item.ItemPrice || item.item_price || 0),
|
||||
Quantity: Number(item.Quantity || item.QuantityOrdered || item.quantity || 1),
|
||||
QuantityOrdered: Number(item.QuantityOrdered || item.Quantity || item.quantity_ordered || 1),
|
||||
QuantitySent: Number(item.QuantitySent || item.quantity_sent || 0),
|
||||
QuantityBackordered: Number(item.QuantityBackordered || item.quantity_backordered || 0),
|
||||
RowTotal: Number(item.RowTotal || item.row_total || (item.ItemPrice * (item.Quantity || item.QuantityOrdered || 1))),
|
||||
ItemStatus: item.ItemStatus || item.item_status || 'In Stock',
|
||||
ImgThumb: item.ImgThumb || item.img_thumb
|
||||
};
|
||||
return transformed;
|
||||
try {
|
||||
const quantity = Number(item.Quantity || item.QuantityOrdered || item.quantity || item.quantity_ordered || 1);
|
||||
const price = Number(item.ItemPrice || item.item_price || item.price || 0);
|
||||
const rowTotal = Number(item.RowTotal || item.row_total || (price * quantity) || 0);
|
||||
|
||||
const transformed = {
|
||||
// Basic item information
|
||||
ProductID: item.ProductID || item.product_id || item.id,
|
||||
ProductName: item.ProductName || item.product_name || item.name,
|
||||
SKU: item.SKU || item.sku,
|
||||
Brand: item.Brand || item.brand,
|
||||
Categories: Array.isArray(item.Categories) ? item.Categories :
|
||||
Array.isArray(item.categories) ? item.categories : [],
|
||||
|
||||
// Pricing
|
||||
ItemPrice: price,
|
||||
RowTotal: rowTotal,
|
||||
|
||||
// Quantities
|
||||
Quantity: quantity,
|
||||
QuantityOrdered: quantity,
|
||||
QuantitySent: Number(item.QuantitySent || item.quantity_sent || 0),
|
||||
QuantityBackordered: Number(item.QuantityBackordered || item.quantity_backordered || 0),
|
||||
|
||||
// Status and images
|
||||
ItemStatus: item.ItemStatus || item.item_status || item.status || 'In Stock',
|
||||
ImgThumb: item.ImgThumb || item.img_thumb || item.thumbnail,
|
||||
|
||||
// Additional properties
|
||||
IsPreorder: Boolean(item.IsPreorder || item.is_preorder || item.preorder),
|
||||
IsDigital: Boolean(item.IsDigital || item.is_digital || item.digital),
|
||||
IsGiftCard: Boolean(item.IsGiftCard || item.is_gift_card || item.gift_card),
|
||||
|
||||
// Original properties (for backward compatibility)
|
||||
...item
|
||||
};
|
||||
|
||||
return transformed;
|
||||
} catch (error) {
|
||||
console.error('[EventsService] Error transforming item:', error, item);
|
||||
// Return a minimal valid item structure
|
||||
return {
|
||||
ProductID: item.ProductID || item.product_id || 'unknown',
|
||||
ProductName: item.ProductName || item.product_name || 'Unknown Product',
|
||||
Quantity: 1,
|
||||
ItemPrice: 0,
|
||||
RowTotal: 0
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1439,128 +1490,67 @@ export class EventsService {
|
||||
prevPeriodEnd = periodStart.minus({ milliseconds: 1 });
|
||||
prevPeriodStart = prevPeriodEnd.minus(duration);
|
||||
} else if (params.timeRange) {
|
||||
// Handle both current and previous period time ranges
|
||||
const timeRange = params.timeRange;
|
||||
const isPreviousPeriod = timeRange.startsWith('previous');
|
||||
const normalizedTimeRange = isPreviousPeriod ? timeRange.replace('previous', 'last') : timeRange;
|
||||
const range = this.timeManager.getDateRange(params.timeRange);
|
||||
const prevRange = this.timeManager.getPreviousPeriod(params.timeRange);
|
||||
|
||||
console.log('[EventsService] Time range details:', {
|
||||
originalTimeRange: timeRange,
|
||||
isPreviousPeriod,
|
||||
normalizedTimeRange
|
||||
});
|
||||
|
||||
// Get current period range
|
||||
const range = this.timeManager.getDateRange(normalizedTimeRange);
|
||||
if (!range) {
|
||||
throw new Error(`Invalid time range specified: ${timeRange}`);
|
||||
}
|
||||
|
||||
// Get previous period range using TimeManager
|
||||
const prevRange = this.timeManager.getPreviousPeriod(normalizedTimeRange);
|
||||
if (!prevRange) {
|
||||
throw new Error(`Could not calculate previous period for: ${timeRange}`);
|
||||
if (!range || !prevRange) {
|
||||
throw new Error(`Invalid time range specified: ${params.timeRange}`);
|
||||
}
|
||||
|
||||
periodStart = range.start;
|
||||
periodEnd = range.end;
|
||||
prevPeriodStart = prevRange.start;
|
||||
prevPeriodEnd = prevRange.end;
|
||||
|
||||
console.log('[EventsService] Calculated date ranges:', {
|
||||
timeRange,
|
||||
current: {
|
||||
start: periodStart.toISO(),
|
||||
end: periodEnd.toISO(),
|
||||
duration: periodEnd.diff(periodStart).as('days')
|
||||
},
|
||||
previous: {
|
||||
start: prevPeriodStart.toISO(),
|
||||
end: prevPeriodEnd.toISO(),
|
||||
duration: prevPeriodEnd.diff(prevPeriodStart).as('days')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Load both current and previous period data
|
||||
console.log('[EventsService] Fetching events with params:', {
|
||||
current: {
|
||||
startDate: periodStart.toISO(),
|
||||
endDate: periodEnd.toISO(),
|
||||
metricId: METRIC_IDS.PLACED_ORDER,
|
||||
...params
|
||||
},
|
||||
previous: {
|
||||
startDate: prevPeriodStart.toISO(),
|
||||
endDate: prevPeriodEnd.toISO(),
|
||||
metricId: METRIC_IDS.PLACED_ORDER
|
||||
}
|
||||
});
|
||||
|
||||
const [currentResponse, prevResponse] = await Promise.all([
|
||||
this.getEvents({
|
||||
...params,
|
||||
startDate: periodStart.toISO(),
|
||||
endDate: periodEnd.toISO(),
|
||||
metricId: METRIC_IDS.PLACED_ORDER,
|
||||
customFilters: params.metric === 'pre_orders' ? ['equals(event_properties.HasPreorder,true)'] :
|
||||
params.metric === 'local_pickup' ? ['equals(event_properties.LocalPickup,true)'] :
|
||||
params.metric === 'on_hold' ? ['equals(event_properties.IsOnHold,true)'] : undefined
|
||||
metricId: METRIC_IDS.PLACED_ORDER
|
||||
}),
|
||||
this.getEvents({
|
||||
..._.omit(params, ['timeRange', 'startDate', 'endDate']),
|
||||
startDate: prevPeriodStart.toISO(),
|
||||
endDate: prevPeriodEnd.toISO(),
|
||||
metricId: METRIC_IDS.PLACED_ORDER,
|
||||
timeRange: undefined,
|
||||
isPreviousPeriod: true,
|
||||
cacheKey: `prev_${prevPeriodStart.toISO()}_${prevPeriodEnd.toISO()}`,
|
||||
customFilters: params.metric === 'pre_orders' ? ['equals(event_properties.HasPreorder,true)'] :
|
||||
params.metric === 'local_pickup' ? ['equals(event_properties.LocalPickup,true)'] :
|
||||
params.metric === 'on_hold' ? ['equals(event_properties.IsOnHold,true)'] : undefined
|
||||
metricId: METRIC_IDS.PLACED_ORDER
|
||||
})
|
||||
]);
|
||||
|
||||
// Add debug logging for request params and filters
|
||||
console.log('[EventsService] Request details with filters:', {
|
||||
current: {
|
||||
params: {
|
||||
...params,
|
||||
startDate: periodStart.toISO(),
|
||||
endDate: periodEnd.toISO(),
|
||||
customFilters: params.metric === 'pre_orders' ? ['equals(event_properties.HasPreorder,true)'] :
|
||||
params.metric === 'local_pickup' ? ['equals(event_properties.LocalPickup,true)'] :
|
||||
params.metric === 'on_hold' ? ['equals(event_properties.IsOnHold,true)'] : undefined
|
||||
},
|
||||
responseLength: currentResponse?.data?.length
|
||||
},
|
||||
previous: {
|
||||
params: {
|
||||
..._.omit(params, ['timeRange', 'startDate', 'endDate']),
|
||||
startDate: prevPeriodStart.toISO(),
|
||||
endDate: prevPeriodEnd.toISO(),
|
||||
customFilters: params.metric === 'pre_orders' ? ['equals(event_properties.HasPreorder,true)'] :
|
||||
params.metric === 'local_pickup' ? ['equals(event_properties.LocalPickup,true)'] :
|
||||
params.metric === 'on_hold' ? ['equals(event_properties.IsOnHold,true)'] : undefined
|
||||
},
|
||||
responseLength: prevResponse?.data?.length
|
||||
}
|
||||
});
|
||||
|
||||
// Transform events
|
||||
const currentEvents = this._transformEvents(currentResponse.data || []);
|
||||
const prevEvents = this._transformEvents(prevResponse.data || []);
|
||||
|
||||
console.log('[EventsService] Transformed events:', {
|
||||
current: {
|
||||
count: currentEvents.length,
|
||||
revenue: currentEvents.reduce((sum, event) => sum + (Number(event.event_properties?.TotalAmount) || 0), 0)
|
||||
},
|
||||
previous: {
|
||||
count: prevEvents.length,
|
||||
revenue: prevEvents.reduce((sum, event) => sum + (Number(event.event_properties?.TotalAmount) || 0), 0)
|
||||
// Filter events based on metric type
|
||||
const filterEvents = (events) => {
|
||||
switch (metric) {
|
||||
case 'pre_orders':
|
||||
return events.filter(event =>
|
||||
Boolean(event.event_properties?.HasPreorder ||
|
||||
event.event_properties?.has_preorder ||
|
||||
event.event_properties?.preorder)
|
||||
);
|
||||
case 'local_pickup':
|
||||
return events.filter(event =>
|
||||
Boolean(event.event_properties?.LocalPickup ||
|
||||
event.event_properties?.local_pickup ||
|
||||
event.event_properties?.pickup)
|
||||
);
|
||||
case 'on_hold':
|
||||
return events.filter(event =>
|
||||
Boolean(event.event_properties?.IsOnHold ||
|
||||
event.event_properties?.is_on_hold ||
|
||||
event.event_properties?.on_hold)
|
||||
);
|
||||
default:
|
||||
return events;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const filteredCurrentEvents = filterEvents(currentEvents);
|
||||
const filteredPrevEvents = filterEvents(prevEvents);
|
||||
|
||||
// Initialize daily stats map with all dates in range
|
||||
const dailyStats = new Map();
|
||||
@@ -1575,6 +1565,10 @@ export class EventsService {
|
||||
itemCount: 0,
|
||||
averageOrderValue: 0,
|
||||
averageItemsPerOrder: 0,
|
||||
count: 0,
|
||||
value: 0,
|
||||
percentage: 0,
|
||||
totalOrders: 0,
|
||||
prevRevenue: 0,
|
||||
prevOrders: 0,
|
||||
prevItemCount: 0,
|
||||
@@ -1583,8 +1577,11 @@ export class EventsService {
|
||||
currentDate = currentDate.plus({ days: 1 });
|
||||
}
|
||||
|
||||
// Get total orders for the period (needed for percentages)
|
||||
const totalOrders = currentEvents.length;
|
||||
|
||||
// Process current period events
|
||||
for (const event of currentEvents) {
|
||||
for (const event of filteredCurrentEvents) {
|
||||
const datetime = this.timeManager.toDateTime(event.attributes?.datetime);
|
||||
if (!datetime) continue;
|
||||
|
||||
@@ -1599,6 +1596,10 @@ export class EventsService {
|
||||
dayStats.revenue += totalAmount;
|
||||
dayStats.orders++;
|
||||
dayStats.itemCount += items.length;
|
||||
dayStats.value += totalAmount;
|
||||
dayStats.count++;
|
||||
dayStats.totalOrders = totalOrders;
|
||||
dayStats.percentage = (dayStats.count / totalOrders) * 100;
|
||||
dayStats.averageOrderValue = dayStats.revenue / dayStats.orders;
|
||||
dayStats.averageItemsPerOrder = dayStats.itemCount / dayStats.orders;
|
||||
}
|
||||
@@ -1613,13 +1614,18 @@ export class EventsService {
|
||||
timestamp: dateKey,
|
||||
revenue: 0,
|
||||
orders: 0,
|
||||
itemCount: 0
|
||||
itemCount: 0,
|
||||
value: 0,
|
||||
count: 0
|
||||
});
|
||||
prevDate = prevDate.plus({ days: 1 });
|
||||
}
|
||||
|
||||
// Aggregate previous period data
|
||||
for (const event of prevEvents) {
|
||||
// Get total orders for previous period
|
||||
const prevTotalOrders = prevEvents.length;
|
||||
|
||||
// Process previous period events
|
||||
for (const event of filteredPrevEvents) {
|
||||
const datetime = this.timeManager.toDateTime(event.attributes?.datetime);
|
||||
if (!datetime) continue;
|
||||
|
||||
@@ -1634,21 +1640,15 @@ export class EventsService {
|
||||
dayStats.revenue += totalAmount;
|
||||
dayStats.orders++;
|
||||
dayStats.itemCount += items.length;
|
||||
prevDailyStats.set(dateKey, dayStats);
|
||||
dayStats.value += totalAmount;
|
||||
dayStats.count++;
|
||||
dayStats.percentage = (dayStats.count / prevTotalOrders) * 100;
|
||||
}
|
||||
|
||||
// Map previous period data to current period days
|
||||
const prevPeriodDays = Array.from(prevDailyStats.values()).sort((a, b) => a.date.localeCompare(b.date));
|
||||
const currentPeriodDays = Array.from(dailyStats.values()).sort((a, b) => a.date.localeCompare(b.date));
|
||||
|
||||
// Add debug logging for data before mapping
|
||||
console.log('[EventsService] Data before mapping:', {
|
||||
currentPeriod: currentPeriodDays.slice(0, 3),
|
||||
previousPeriod: prevPeriodDays.slice(0, 3),
|
||||
currentLength: currentPeriodDays.length,
|
||||
prevLength: prevPeriodDays.length
|
||||
});
|
||||
|
||||
// Map the data using array indices
|
||||
for (let i = 0; i < currentPeriodDays.length && i < prevPeriodDays.length; i++) {
|
||||
const currentDayStats = currentPeriodDays[i];
|
||||
@@ -1660,19 +1660,15 @@ export class EventsService {
|
||||
dayStats.prevRevenue = prevDayStats.revenue;
|
||||
dayStats.prevOrders = prevDayStats.orders;
|
||||
dayStats.prevItemCount = prevDayStats.itemCount;
|
||||
dayStats.prevValue = prevDayStats.value;
|
||||
dayStats.prevCount = prevDayStats.count;
|
||||
dayStats.prevPercentage = prevDayStats.percentage;
|
||||
dayStats.prevAvgOrderValue = prevDayStats.orders > 0 ? prevDayStats.revenue / prevDayStats.orders : 0;
|
||||
dailyStats.set(currentDayStats.timestamp, dayStats);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add debug logging for mapped data
|
||||
console.log('[EventsService] Sample of mapped data:', {
|
||||
firstDay: dailyStats.get(currentPeriodDays[0]?.timestamp),
|
||||
lastDay: dailyStats.get(currentPeriodDays[currentPeriodDays.length - 1]?.timestamp),
|
||||
totalDays: dailyStats.size
|
||||
});
|
||||
|
||||
// Convert to array and sort by date
|
||||
const stats = Array.from(dailyStats.values())
|
||||
.sort((a, b) => a.date.localeCompare(b.date))
|
||||
@@ -1683,9 +1679,16 @@ export class EventsService {
|
||||
itemCount: Number(day.itemCount || 0),
|
||||
averageOrderValue: Number(day.averageOrderValue || 0),
|
||||
averageItemsPerOrder: Number(day.averageItemsPerOrder || 0),
|
||||
count: Number(day.count || 0),
|
||||
value: Number(day.value || 0),
|
||||
percentage: Number(day.percentage || 0),
|
||||
totalOrders: Number(day.totalOrders || 0),
|
||||
prevRevenue: Number(day.prevRevenue || 0),
|
||||
prevOrders: Number(day.prevOrders || 0),
|
||||
prevItemCount: Number(day.prevItemCount || 0),
|
||||
prevValue: Number(day.prevValue || 0),
|
||||
prevCount: Number(day.prevCount || 0),
|
||||
prevPercentage: Number(day.prevPercentage || 0),
|
||||
prevAvgOrderValue: Number(day.prevAvgOrderValue || 0)
|
||||
}));
|
||||
|
||||
|
||||
@@ -141,14 +141,27 @@ _getCacheKey(type, params = {}) {
|
||||
metric,
|
||||
daily,
|
||||
cacheKey,
|
||||
isPreviousPeriod
|
||||
isPreviousPeriod,
|
||||
customFilters
|
||||
} = params;
|
||||
|
||||
let key = `klaviyo:${type}`;
|
||||
|
||||
// Handle "stats:details" for daily or metric-based keys
|
||||
if (type === 'stats:details') {
|
||||
key += `:${metric}${daily ? ':daily' : ''}`;
|
||||
// Add metric to key
|
||||
key += `:${metric || 'all'}`;
|
||||
|
||||
// Add daily flag if present
|
||||
if (daily) {
|
||||
key += ':daily';
|
||||
}
|
||||
|
||||
// Add custom filters hash if present
|
||||
if (customFilters?.length) {
|
||||
const filterHash = customFilters.join('').replace(/[^a-zA-Z0-9]/g, '');
|
||||
key += `:${filterHash}`;
|
||||
}
|
||||
}
|
||||
|
||||
// If a specific cache key is provided, use it (highest priority)
|
||||
@@ -157,15 +170,27 @@ _getCacheKey(type, params = {}) {
|
||||
}
|
||||
// Otherwise, build a default cache key
|
||||
else if (timeRange) {
|
||||
key += `:${timeRange}${metricId ? `:${metricId}` : ''}`;
|
||||
key += `:${timeRange}`;
|
||||
if (metricId) {
|
||||
key += `:${metricId}`;
|
||||
}
|
||||
if (isPreviousPeriod) {
|
||||
key += ':prev';
|
||||
key += ':prev';
|
||||
}
|
||||
} else if (startDate && endDate) {
|
||||
key += `:custom:${startDate}:${endDate}${metricId ? `:${metricId}` : ''}`;
|
||||
if (isPreviousPeriod) {
|
||||
key += ':prev';
|
||||
// For custom date ranges, include both dates in the key
|
||||
key += `:custom:${startDate}:${endDate}`;
|
||||
if (metricId) {
|
||||
key += `:${metricId}`;
|
||||
}
|
||||
if (isPreviousPeriod) {
|
||||
key += ':prev';
|
||||
}
|
||||
}
|
||||
|
||||
// Add order type to key if present
|
||||
if (['pre_orders', 'local_pickup', 'on_hold'].includes(metric)) {
|
||||
key += `:${metric}`;
|
||||
}
|
||||
|
||||
return key;
|
||||
|
||||
@@ -163,8 +163,9 @@ export class TimeManager {
|
||||
return null;
|
||||
}
|
||||
case 'today': {
|
||||
const dayStart = this.getDayStart(now);
|
||||
return {
|
||||
start: this.getDayStart(now),
|
||||
start: dayStart,
|
||||
end: this.getDayEnd(now)
|
||||
};
|
||||
}
|
||||
@@ -177,27 +178,29 @@ export class TimeManager {
|
||||
}
|
||||
case 'last7days': {
|
||||
// For last 7 days, we want to include today and the previous 6 days
|
||||
// So if today is 12/17, we want 12/11 1am to 12/17 12:59am
|
||||
const dayStart = this.getDayStart(now);
|
||||
const weekStart = dayStart.minus({ days: 6 });
|
||||
return {
|
||||
start: dayStart.minus({ days: 6 }), // 6 days ago from start of today
|
||||
end: this.getDayEnd(now) // end of today
|
||||
start: weekStart,
|
||||
end: this.getDayEnd(now)
|
||||
};
|
||||
}
|
||||
case 'last30days': {
|
||||
// Include today and previous 29 days
|
||||
const dayStart = this.getDayStart(now);
|
||||
const monthStart = dayStart.minus({ days: 29 });
|
||||
return {
|
||||
start: dayStart.minus({ days: 29 }), // 29 days ago from start of today
|
||||
end: this.getDayEnd(now) // end of today
|
||||
start: monthStart,
|
||||
end: this.getDayEnd(now)
|
||||
};
|
||||
}
|
||||
case 'last90days': {
|
||||
// Include today and previous 89 days
|
||||
const dayStart = this.getDayStart(now);
|
||||
const start = dayStart.minus({ days: 89 });
|
||||
return {
|
||||
start: dayStart.minus({ days: 89 }), // 89 days ago from start of today
|
||||
end: this.getDayEnd(now) // end of today
|
||||
start,
|
||||
end: this.getDayEnd(now)
|
||||
};
|
||||
}
|
||||
case 'thisWeek': {
|
||||
@@ -212,7 +215,6 @@ export class TimeManager {
|
||||
const lastWeek = now.minus({ weeks: 1 });
|
||||
const weekStart = this.getWeekStart(lastWeek);
|
||||
const weekEnd = weekStart.plus({ days: 6 }); // 6 days after start = Saturday
|
||||
|
||||
return {
|
||||
start: weekStart,
|
||||
end: this.getDayEnd(weekEnd)
|
||||
@@ -221,7 +223,6 @@ export class TimeManager {
|
||||
case 'thisMonth': {
|
||||
const dayStart = this.getDayStart(now);
|
||||
const monthStart = dayStart.startOf('month').set({ hour: this.dayStartHour });
|
||||
|
||||
return {
|
||||
start: monthStart,
|
||||
end: this.getDayEnd(now)
|
||||
@@ -231,7 +232,6 @@ export class TimeManager {
|
||||
const lastMonth = now.minus({ months: 1 });
|
||||
const monthStart = lastMonth.startOf('month').set({ hour: this.dayStartHour });
|
||||
const monthEnd = monthStart.plus({ months: 1 }).minus({ days: 1 });
|
||||
|
||||
return {
|
||||
start: monthStart,
|
||||
end: this.getDayEnd(monthEnd)
|
||||
@@ -298,7 +298,9 @@ export class TimeManager {
|
||||
* @returns {Object} Object with start and end DateTime objects
|
||||
*/
|
||||
getPreviousPeriod(period, now = this.getNow()) {
|
||||
switch (period) {
|
||||
const normalizedPeriod = period.startsWith('previous') ? period.replace('previous', 'last') : period;
|
||||
|
||||
switch (normalizedPeriod) {
|
||||
case 'today': {
|
||||
const yesterday = now.minus({ days: 1 });
|
||||
return {
|
||||
@@ -313,8 +315,7 @@ export class TimeManager {
|
||||
end: this.getDayEnd(twoDaysAgo)
|
||||
};
|
||||
}
|
||||
case 'last7days':
|
||||
case 'previous7days': {
|
||||
case 'last7days': {
|
||||
const dayStart = this.getDayStart(now);
|
||||
const currentStart = dayStart.minus({ days: 6 });
|
||||
const prevEnd = currentStart.minus({ milliseconds: 1 });
|
||||
@@ -324,8 +325,7 @@ export class TimeManager {
|
||||
end: prevEnd
|
||||
};
|
||||
}
|
||||
case 'last30days':
|
||||
case 'previous30days': {
|
||||
case 'last30days': {
|
||||
const dayStart = this.getDayStart(now);
|
||||
const currentStart = dayStart.minus({ days: 29 });
|
||||
const prevEnd = currentStart.minus({ milliseconds: 1 });
|
||||
@@ -335,8 +335,7 @@ export class TimeManager {
|
||||
end: prevEnd
|
||||
};
|
||||
}
|
||||
case 'last90days':
|
||||
case 'previous90days': {
|
||||
case 'last90days': {
|
||||
const dayStart = this.getDayStart(now);
|
||||
const currentStart = dayStart.minus({ days: 89 });
|
||||
const prevEnd = currentStart.minus({ milliseconds: 1 });
|
||||
@@ -382,13 +381,6 @@ export class TimeManager {
|
||||
end: prevEnd
|
||||
};
|
||||
}
|
||||
case 'twoDaysAgo': {
|
||||
const twoDaysAgo = now.minus({ days: 2 });
|
||||
return {
|
||||
start: this.getDayStart(twoDaysAgo),
|
||||
end: this.getDayEnd(twoDaysAgo)
|
||||
};
|
||||
}
|
||||
default:
|
||||
console.warn(`[TimeManager] No previous period defined for: ${period}`);
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user