using MediatR; using TakeoutSaaS.Application.App.Customers.Dto; using TakeoutSaaS.Application.App.Customers.Queries; using TakeoutSaaS.Domain.Orders.Repositories; using TakeoutSaaS.Domain.Products.Repositories; using TakeoutSaaS.Shared.Abstractions.Data; using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.App.Customers.Handlers; /// /// 客户画像查询处理器。 /// public sealed class GetCustomerProfileQueryHandler( IOrderRepository orderRepository, IProductRepository productRepository, IDapperExecutor dapperExecutor, ITenantProvider tenantProvider) : IRequestHandler { /// public async Task Handle( GetCustomerProfileQuery request, CancellationToken cancellationToken) { // 1. 参数与可见门店校验 var customerKey = CustomerAnalyticsSupport.NormalizePhone(request.CustomerKey); if (string.IsNullOrWhiteSpace(customerKey) || request.VisibleStoreIds.Count == 0) { return null; } // 2. 加载客户聚合并定位目标客户 var customers = await CustomerAnalyticsSupport.LoadCustomersAsync( orderRepository, dapperExecutor, tenantProvider, request.VisibleStoreIds, cancellationToken); var customer = customers.FirstOrDefault(item => string.Equals(item.CustomerKey, customerKey, StringComparison.Ordinal)); if (customer is null) { return null; } var tenantId = tenantProvider.GetCurrentTenantId(); if (tenantId <= 0) { return null; } // 3. 加载订单明细并计算画像数据 var orderIds = customer.Orders .Select(item => item.OrderId) .ToList(); var itemsLookup = await orderRepository.GetItemsByOrderIdsAsync(orderIds, tenantId, cancellationToken); var topProducts = CustomerAnalyticsSupport.BuildTopProducts(itemsLookup, orderIds, 5); var preferredCategories = await CustomerAnalyticsSupport.ResolvePreferredCategoriesAsync( productRepository, tenantId, itemsLookup, orderIds, cancellationToken); var preferredDelivery = CustomerAnalyticsSupport.ResolvePreferredDelivery(customer.Orders); var preferredPaymentMethod = await CustomerAnalyticsSupport.ResolvePreferredPaymentMethodAsync( orderRepository, tenantId, customer.Orders, cancellationToken); var preferredOrderPeaks = CustomerAnalyticsSupport.ResolvePreferredOrderPeaks(customer.Orders); var recentOrders = CustomerAnalyticsSupport.BuildRecentOrders(customer.Orders, itemsLookup, 5); var trend = CustomerAnalyticsSupport.BuildMonthlyTrend(customer.Orders, DateTime.UtcNow, 12); var repurchaseRatePercent = CustomerAnalyticsSupport.ToRatePercent( Math.Max(0, customer.OrderCount - 1), customer.OrderCount); var averageOrderIntervalDays = CustomerAnalyticsSupport.CalculateAverageIntervalDays(customer.Orders); return new CustomerProfileDto { CustomerKey = customer.CustomerKey, Name = customer.Name, PhoneMasked = customer.PhoneMasked, RegisteredAt = customer.RegisteredAt, FirstOrderAt = customer.FirstOrderAt, Source = customer.Source, Tags = customer.Tags, Member = customer.Member, TotalOrders = customer.OrderCount, TotalAmount = customer.TotalAmount, AverageAmount = customer.AverageAmount, RepurchaseRatePercent = repurchaseRatePercent, AverageOrderIntervalDays = averageOrderIntervalDays, Preference = new CustomerPreferenceDto { PreferredCategories = preferredCategories, PreferredOrderPeaks = preferredOrderPeaks, PreferredDelivery = preferredDelivery, PreferredPaymentMethod = preferredPaymentMethod, AverageDeliveryDistance = string.Empty }, TopProducts = topProducts, Trend = trend, RecentOrders = recentOrders }; } }