247 lines
9.4 KiB
C#
247 lines
9.4 KiB
C#
using AutoMapper;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using LittleShop.Data;
|
|
using LittleShop.DTOs;
|
|
using LittleShop.Models;
|
|
|
|
namespace LittleShop.Services;
|
|
|
|
public class CustomerMessageService : ICustomerMessageService
|
|
{
|
|
private readonly LittleShopContext _context;
|
|
private readonly IMapper _mapper;
|
|
private readonly ILogger<CustomerMessageService> _logger;
|
|
|
|
public CustomerMessageService(LittleShopContext context, IMapper mapper, ILogger<CustomerMessageService> logger)
|
|
{
|
|
_context = context;
|
|
_mapper = mapper;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<CustomerMessageDto?> CreateMessageAsync(CreateCustomerMessageDto createMessageDto)
|
|
{
|
|
try
|
|
{
|
|
var message = _mapper.Map<CustomerMessage>(createMessageDto);
|
|
message.Id = Guid.NewGuid();
|
|
message.Direction = MessageDirection.AdminToCustomer;
|
|
message.CreatedAt = DateTime.UtcNow;
|
|
message.Status = MessageStatus.Pending;
|
|
message.Platform = "Telegram";
|
|
|
|
// Use customer-based threading - all messages for a customer are in one conversation
|
|
message.ThreadId = message.CustomerId;
|
|
|
|
_context.CustomerMessages.Add(message);
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("Created message {MessageId} for customer {CustomerId}",
|
|
message.Id, message.CustomerId);
|
|
|
|
// Return the created message with includes
|
|
var createdMessage = await GetMessageByIdAsync(message.Id);
|
|
return createdMessage;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error creating message for customer {CustomerId}", createMessageDto.CustomerId);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task<CustomerMessageDto?> GetMessageByIdAsync(Guid id)
|
|
{
|
|
var message = await _context.CustomerMessages
|
|
.Include(m => m.Customer)
|
|
.Include(m => m.Order)
|
|
.Include(m => m.AdminUser)
|
|
.Include(m => m.ParentMessage)
|
|
.Include(m => m.Replies)
|
|
.FirstOrDefaultAsync(m => m.Id == id);
|
|
|
|
if (message == null) return null;
|
|
|
|
return _mapper.Map<CustomerMessageDto>(message);
|
|
}
|
|
|
|
public async Task<IEnumerable<CustomerMessageDto>> GetCustomerMessagesAsync(Guid customerId)
|
|
{
|
|
var messages = await _context.CustomerMessages
|
|
.Include(m => m.Customer)
|
|
.Include(m => m.Order)
|
|
.Include(m => m.AdminUser)
|
|
.Where(m => m.CustomerId == customerId && !m.IsArchived)
|
|
.OrderByDescending(m => m.CreatedAt)
|
|
.ToListAsync();
|
|
|
|
return messages.Select(m => _mapper.Map<CustomerMessageDto>(m));
|
|
}
|
|
|
|
public async Task<IEnumerable<CustomerMessageDto>> GetOrderMessagesAsync(Guid orderId)
|
|
{
|
|
var messages = await _context.CustomerMessages
|
|
.Include(m => m.Customer)
|
|
.Include(m => m.Order)
|
|
.Include(m => m.AdminUser)
|
|
.Where(m => m.OrderId == orderId && !m.IsArchived)
|
|
.OrderByDescending(m => m.CreatedAt)
|
|
.ToListAsync();
|
|
|
|
return messages.Select(m => _mapper.Map<CustomerMessageDto>(m));
|
|
}
|
|
|
|
public async Task<IEnumerable<CustomerMessageDto>> GetPendingMessagesAsync(string platform = "Telegram")
|
|
{
|
|
var messages = await _context.CustomerMessages
|
|
.Include(m => m.Customer)
|
|
.Include(m => m.Order)
|
|
.Include(m => m.AdminUser)
|
|
.Where(m => m.Status == MessageStatus.Pending &&
|
|
m.Platform == platform &&
|
|
m.Direction == MessageDirection.AdminToCustomer &&
|
|
(m.ScheduledFor == null || m.ScheduledFor <= DateTime.UtcNow) &&
|
|
(m.ExpiresAt == null || m.ExpiresAt > DateTime.UtcNow))
|
|
.OrderBy(m => m.Priority)
|
|
.ThenBy(m => m.CreatedAt)
|
|
.Take(50) // Limit for performance
|
|
.ToListAsync();
|
|
|
|
return messages.Select(m => _mapper.Map<CustomerMessageDto>(m));
|
|
}
|
|
|
|
public async Task<bool> MarkMessageAsSentAsync(Guid messageId, string? platformMessageId = null)
|
|
{
|
|
var message = await _context.CustomerMessages.FindAsync(messageId);
|
|
if (message == null) return false;
|
|
|
|
message.MarkAsSent();
|
|
if (!string.IsNullOrEmpty(platformMessageId))
|
|
{
|
|
message.PlatformMessageId = platformMessageId;
|
|
}
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("Marked message {MessageId} as sent", messageId);
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> MarkMessageAsDeliveredAsync(Guid messageId)
|
|
{
|
|
var message = await _context.CustomerMessages.FindAsync(messageId);
|
|
if (message == null) return false;
|
|
|
|
message.MarkAsDelivered();
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("Marked message {MessageId} as delivered", messageId);
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> MarkMessageAsFailedAsync(Guid messageId, string reason)
|
|
{
|
|
var message = await _context.CustomerMessages.FindAsync(messageId);
|
|
if (message == null) return false;
|
|
|
|
message.MarkAsFailed(reason);
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogWarning("Marked message {MessageId} as failed: {Reason}", messageId, reason);
|
|
return true;
|
|
}
|
|
|
|
public async Task<MessageThreadDto?> GetMessageThreadAsync(Guid customerId)
|
|
{
|
|
// Get all messages for this customer (customer-based conversation)
|
|
var messages = await _context.CustomerMessages
|
|
.Include(m => m.Customer)
|
|
.Include(m => m.Order)
|
|
.Include(m => m.AdminUser)
|
|
.Where(m => m.CustomerId == customerId && !m.IsArchived)
|
|
.OrderBy(m => m.CreatedAt)
|
|
.ToListAsync();
|
|
|
|
if (!messages.Any()) return null;
|
|
|
|
var customer = messages.First().Customer;
|
|
var thread = new MessageThreadDto
|
|
{
|
|
ThreadId = customerId, // Use CustomerId as thread identifier
|
|
Subject = customer?.DisplayName ?? "Unknown Customer", // Customer name as subject
|
|
CustomerId = customerId,
|
|
CustomerName = customer?.DisplayName ?? "Unknown",
|
|
OrderId = null, // No single order - conversation spans multiple orders
|
|
OrderReference = null, // No single order reference
|
|
StartedAt = messages.Min(m => m.CreatedAt),
|
|
LastMessageAt = messages.Max(m => m.CreatedAt),
|
|
MessageCount = messages.Count,
|
|
HasUnreadMessages = messages.Any(m => m.Direction == MessageDirection.CustomerToAdmin && m.Status != MessageStatus.Read),
|
|
RequiresResponse = messages.Any(m => m.RequiresResponse && m.Status != MessageStatus.Read),
|
|
Messages = messages.Select(m => _mapper.Map<CustomerMessageDto>(m)).ToList()
|
|
};
|
|
|
|
return thread;
|
|
}
|
|
|
|
public async Task<IEnumerable<MessageThreadDto>> GetActiveThreadsAsync()
|
|
{
|
|
// Group by Customer instead of ThreadId for customer-based conversations
|
|
var threads = await _context.CustomerMessages
|
|
.Include(m => m.Customer)
|
|
.Include(m => m.Order)
|
|
.Where(m => !m.IsArchived)
|
|
.GroupBy(m => m.CustomerId)
|
|
.Select(g => new MessageThreadDto
|
|
{
|
|
ThreadId = g.First().CustomerId, // Use CustomerId as conversation identifier
|
|
Subject = g.First().Customer != null ? g.First().Customer.DisplayName : "Unknown Customer",
|
|
CustomerId = g.First().CustomerId,
|
|
CustomerName = g.First().Customer != null ? g.First().Customer.DisplayName : "Unknown",
|
|
OrderId = null, // No single order - will show multiple orders in thread
|
|
OrderReference = null, // No single order reference
|
|
StartedAt = g.Min(m => m.CreatedAt),
|
|
LastMessageAt = g.Max(m => m.CreatedAt),
|
|
MessageCount = g.Count(),
|
|
HasUnreadMessages = g.Any(m => m.Direction == MessageDirection.CustomerToAdmin && m.Status != MessageStatus.Read),
|
|
RequiresResponse = g.Any(m => m.RequiresResponse && m.Status != MessageStatus.Read)
|
|
})
|
|
.OrderByDescending(t => t.LastMessageAt)
|
|
.Take(100) // Limit for performance
|
|
.ToListAsync();
|
|
|
|
return threads;
|
|
}
|
|
|
|
public async Task<bool> ValidateCustomerExistsAsync(Guid customerId)
|
|
{
|
|
return await _context.Customers.AnyAsync(c => c.Id == customerId);
|
|
}
|
|
|
|
public async Task<bool> ValidateOrderBelongsToCustomerAsync(Guid orderId, Guid customerId)
|
|
{
|
|
return await _context.Orders.AnyAsync(o => o.Id == orderId && o.CustomerId == customerId);
|
|
}
|
|
|
|
public async Task<bool> CreateCustomerToAdminMessageAsync(CustomerMessage message)
|
|
{
|
|
try
|
|
{
|
|
// Use customer-based threading for all messages
|
|
message.ThreadId = message.CustomerId;
|
|
|
|
_context.CustomerMessages.Add(message);
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("Created customer message {MessageId} from customer {CustomerId}",
|
|
message.Id, message.CustomerId);
|
|
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error creating customer message from customer {CustomerId}", message.CustomerId);
|
|
return false;
|
|
}
|
|
}
|
|
} |