using LiteCharms.Features.Extensions; using LiteCharms.Features.Models; using LiteCharms.Features.Shop.Orders.Models; using LiteCharms.Features.Shop.Postgres; namespace LiteCharms.Features.Shop.Orders; public class OrderService(IDbContextFactory contextFactory) { public async ValueTask> CreateOrderAsync(CreateOrder request, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Customers.AnyAsync(c => c.Id == request.CustomerId, cancellationToken)) return Result.Fail(new Error($"Customer {request.CustomerId} does not exist.")); if (!await context.ShoppingCarts.AnyAsync(sc => sc.Id == request.ShoppingCartId, cancellationToken)) return Result.Fail(new Error($"Shopping cart {request.ShoppingCartId} does not exist.")); if (request.QuoteId.HasValue && !await context.Quotes.AnyAsync(q => q.Id == request.QuoteId.Value, cancellationToken)) return Result.Fail(new Error($"Quote {request.QuoteId.Value} does not exist.")); var newOrder = context.Orders.Add(new Entities.Order { Status = OrderStatus.Pending, CustomerId = request.CustomerId, Requirements = request.Requirements, Notes = request.Notes, Terms = request.Terms }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok(newOrder.Entity.Id) : Result.Fail(new Error($"Failed to create customer {request.CustomerId} order using shopping cart {request.ShoppingCartId}.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetCustomerOrderRefundsAsync(Guid customerId, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken)) return Result.Fail(new Error($"Customer with Id {customerId} does not exist.")); var refunds = await context.OrderRefunds.AsNoTracking().AsSplitQuery() .OrderByDescending(o => o.CreatedAt) .Where(r => r.Order!.CustomerId == customerId).ToArrayAsync(cancellationToken); return refunds?.Length > 0 ? Result.Ok(refunds.Select(r => r.ToModel()).ToArray()) : Result.Fail(new Error($"No refunds found for customer with Id {customerId}.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetCustomerOrdersAsync(Guid customerId, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Customers.AsNoTracking().AnyAsync(c => c.Id == customerId, cancellationToken)) return Result.Fail(new Error($"Customer with Id {customerId} does not exist.")); var orders = await context.Orders.AsNoTracking() .OrderByDescending(o => o.CreatedAt) .Where(o => o.CustomerId == customerId) .ToArrayAsync(cancellationToken); return orders?.Length > 0 ? Result.Ok(orders.Select(o => o.ToModel()).ToArray()) : Result.Fail(new Error($"No orders found for customer with Id {customerId}.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetOrderRefundAsync(Guid orderId, Guid orderRefundId, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var refund = await context.OrderRefunds.AsNoTracking() .FirstOrDefaultAsync(r => r.OrderId == orderId && r.Id == orderRefundId, cancellationToken); return refund is not null ? Result.Ok(refund.ToModel()) : Result.Fail(new Error($"Refund {orderRefundId} not found for the given OrderId: {orderId}")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetOrderRefundAsync(Guid orderRefundId, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var refund = await context.OrderRefunds.AsNoTracking().FirstOrDefaultAsync(r => r.Id == orderRefundId, cancellationToken); return refund is not null ? Result.Ok(refund.ToModel()) : Result.Fail($"Order refund could not be found with id {orderRefundId}"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetOrderRefundsAsync(Guid orderId, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var refunds = await context.OrderRefunds.AsNoTracking() .OrderByDescending(o => o.CreatedAt) .Where(r => r.OrderId == orderId) .ToArrayAsync(cancellationToken); return refunds?.Length > 0 ? Result.Ok(refunds.Select(r => r.ToModel()).ToArray()) : Result.Fail($"Order refunds could not be found with order id {orderId}"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetOrdersAsync(DateRange range, CancellationToken cancellationToken = default) { try { var fromDate = range.From.ToDateTime(TimeOnly.MinValue); var toDate = range.To.ToDateTime(TimeOnly.MaxValue); using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var orders = await context.Orders .AsNoTracking() .OrderByDescending(o => o.CreatedAt) .Where(o => o.CreatedAt >= fromDate && o.CreatedAt <= toDate) .Take(range.MaxRecords) .ToArrayAsync(cancellationToken); return orders?.Length > 0 ? Result.Ok(orders.Select(o => o.ToModel()).ToArray()) : Result.Fail(new Error($"No orders found for the specified date range {range.From} - {range.To}.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> RefundCustomerAsync(RefundCustomer request, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Orders.AnyAsync(o => o.Id == request.OrderId, cancellationToken)) return Result.Fail(new Error($"Order with Id: {request.OrderId} does not exist")); if (!await context.Customers.AnyAsync(c => c.Id == request.CustomerId, cancellationToken)) return Result.Fail(new Error($"Customer with Id: {request.CustomerId} does not exist")); if (!await context.Orders.AnyAsync(o => o.Id == request.OrderId && o.CustomerId == request.CustomerId, cancellationToken)) return Result.Fail(new Error($"Order with Id: {request.OrderId} does not belong to Customer with Id: {request.CustomerId}")); var refund = context.OrderRefunds.Add(new Entities.OrderRefund { OrderId = request.OrderId, Reason = request.Reason, Amount = request.Amount }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok(refund.Entity.Id) : Result.Fail(new Error($"Failed to create refund for OrderId: {request.OrderId}")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask UpdateOrderRefundAsync(Guid orderRefundId, string reason, decimal amount, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var refund = await context.OrderRefunds.FirstOrDefaultAsync(r => r.Id == orderRefundId, cancellationToken); if (refund is null) return Result.Fail($"Order refund not found with id {orderRefundId}"); refund.Reason = reason; refund.Amount = amount; return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail($"Failed to update order refund {orderRefundId}"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask UpdateOrderStatusAsync(UpdateOrder request, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var order = await context.Orders.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken); if (order is null) return Result.Fail(new Error($"Order {request.OrderId} not found")); order.Status = request.Status; order.UpdatedAt = DateTime.UtcNow; if(!string.IsNullOrWhiteSpace(request.InvoiceUrl)) order.InvoiceUrl = request.InvoiceUrl; if(request.Requirements?.Length > 0) order.Requirements = request.Requirements; if(request.Notes?.Length > 0) order.Notes = request.Notes; return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail(new Error($"Failed to update order {request.OrderId}")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } }