AnalyticsModels.java
package com.ecommerce.analytics.application.model;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Instant;
import java.time.LocalDate;
import java.util.List;
public final class AnalyticsModels {
private AnalyticsModels() {
}
public record DomainEvent(
String eventId,
String eventType,
String producer,
String aggregateType,
String aggregateId,
long aggregateVersion,
Instant occurredAt,
long userId,
String orderNo,
BigDecimal amount,
String fingerprint,
List<ProductLine> productLines) {
public DomainEvent {
amount = money(amount);
productLines = productLines == null ? List.of() : List.copyOf(productLines);
}
}
public record ProductLine(
int lineNo,
long productId,
long skuId,
String productTitle,
String skuCode,
long quantity,
BigDecimal payableAmount) {
public ProductLine {
payableAmount = payableAmount == null ? null : money(payableAmount);
}
}
public record ProductContribution(
long productId,
String productTitle,
long units,
BigDecimal netRevenue,
boolean revenueCovered) {
public ProductContribution {
netRevenue = money(netRevenue);
}
}
public record DailySummary(
LocalDate businessDate,
long createdOrderCount,
BigDecimal createdOrderAmount,
long paymentCount,
BigDecimal paymentAmount,
long completedOrderCount,
BigDecimal completedOrderAmount,
long closedOrderCount,
long afterSaleCount,
BigDecimal afterSaleAmount,
long refundCount,
BigDecimal refundAmount,
Instant updatedAt) {
public DailySummary {
createdOrderAmount = money(createdOrderAmount);
paymentAmount = money(paymentAmount);
completedOrderAmount = money(completedOrderAmount);
afterSaleAmount = money(afterSaleAmount);
refundAmount = money(refundAmount);
}
}
public record ProductSummary(
@JsonSerialize(using = ToStringSerializer.class) long productId,
String productTitle,
long completedOrderCount,
long unitsSold,
BigDecimal netRevenue,
long revenueCoveredOrderCount) {
public ProductSummary {
netRevenue = money(netRevenue);
}
}
public record OverviewTotals(
long createdOrderCount,
BigDecimal createdOrderAmount,
long paymentCount,
BigDecimal paymentAmount,
long completedOrderCount,
BigDecimal completedOrderAmount,
long closedOrderCount,
long afterSaleCount,
BigDecimal afterSaleAmount,
long refundCount,
BigDecimal refundAmount,
long uniqueCustomers) {
public OverviewTotals {
createdOrderAmount = money(createdOrderAmount);
paymentAmount = money(paymentAmount);
completedOrderAmount = money(completedOrderAmount);
afterSaleAmount = money(afterSaleAmount);
refundAmount = money(refundAmount);
}
}
public record ProjectionFreshness(
long sourceEventCount,
Instant lastConsumedAt,
Instant generatedAt) {
}
public record DashboardView(
LocalDate from,
LocalDate to,
OverviewTotals totals,
List<DailySummary> daily,
List<ProductSummary> topProducts,
ProjectionFreshness freshness) {
public DashboardView {
daily = List.copyOf(daily);
topProducts = List.copyOf(topProducts);
}
}
public record ProjectionIssue(
String projection,
String issueType,
String key,
String expected,
String actual) {
}
public record ReconciliationView(
LocalDate from,
LocalDate to,
int checkedDailyRows,
int checkedProductRows,
long issueCount,
boolean saturated,
List<ProjectionIssue> issues,
Instant generatedAt) {
public ReconciliationView {
issues = List.copyOf(issues);
}
}
public record RebuildView(
String commandId,
@JsonSerialize(using = ToStringSerializer.class) long operatorId,
String reason,
LocalDate from,
LocalDate to,
long sourceEventCount,
long beforeIssueCount,
long afterIssueCount,
Instant createdAt) {
}
private static BigDecimal money(BigDecimal value) {
return (value == null ? BigDecimal.ZERO : value).setScale(2, RoundingMode.UNNECESSARY);
}
}