"Royal-Mail-shipping-integration-and-test-improvements"
This commit is contained in:
284
LittleShop/Controllers/ShippingController.cs
Normal file
284
LittleShop/Controllers/ShippingController.cs
Normal file
@@ -0,0 +1,284 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using LittleShop.Services;
|
||||
using LittleShop.DTOs;
|
||||
using LittleShop.Enums;
|
||||
|
||||
namespace LittleShop.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||
public class ShippingController : ControllerBase
|
||||
{
|
||||
private readonly IRoyalMailService _royalMailService;
|
||||
private readonly IShippingRateService _shippingRateService;
|
||||
private readonly IOrderService _orderService;
|
||||
private readonly ILogger<ShippingController> _logger;
|
||||
|
||||
public ShippingController(
|
||||
IRoyalMailService royalMailService,
|
||||
IShippingRateService shippingRateService,
|
||||
IOrderService orderService,
|
||||
ILogger<ShippingController> logger)
|
||||
{
|
||||
_royalMailService = royalMailService;
|
||||
_shippingRateService = shippingRateService;
|
||||
_orderService = orderService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpPost("calculate")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult<ShippingCalculationResult>> CalculateShipping([FromBody] CalculateShippingRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
// First try Royal Mail services
|
||||
var services = await _royalMailService.GetAvailableServicesAsync(request.WeightInGrams, request.Country);
|
||||
|
||||
if (services.Any())
|
||||
{
|
||||
var result = new ShippingCalculationResult
|
||||
{
|
||||
Success = true,
|
||||
Services = services.Select(s => new ShippingOption
|
||||
{
|
||||
ServiceCode = s.Code,
|
||||
ServiceName = s.Name,
|
||||
Description = s.Description,
|
||||
Cost = s.Price,
|
||||
EstimatedDeliveryDays = s.EstimatedDeliveryDays,
|
||||
SupportsTracking = s.SupportsTracking
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
// Fallback to local shipping rates
|
||||
var fallbackRate = await _shippingRateService.CalculateShippingAsync(request.WeightInGrams / 1000, request.Country);
|
||||
if (fallbackRate != null)
|
||||
{
|
||||
var fallbackResult = new ShippingCalculationResult
|
||||
{
|
||||
Success = true,
|
||||
Services = new List<ShippingOption>
|
||||
{
|
||||
new ShippingOption
|
||||
{
|
||||
ServiceCode = "Standard",
|
||||
ServiceName = fallbackRate.Name,
|
||||
Description = fallbackRate.Description ?? "Standard shipping",
|
||||
Cost = fallbackRate.Price,
|
||||
EstimatedDeliveryDays = (fallbackRate.MinDeliveryDays + fallbackRate.MaxDeliveryDays) / 2,
|
||||
SupportsTracking = false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(fallbackResult);
|
||||
}
|
||||
|
||||
return BadRequest(new { Error = "No shipping options available for this destination" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error calculating shipping for weight {Weight}g to {Country}",
|
||||
request.WeightInGrams, request.Country);
|
||||
return StatusCode(500, new { Error = "Error calculating shipping costs" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("create")]
|
||||
[Authorize(Roles = "Admin")]
|
||||
public async Task<ActionResult<CreateShipmentResult>> CreateShipment([FromBody] CreateShipmentRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _royalMailService.CreateShipmentAsync(request);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.LogInformation("Created shipment {ShipmentId} with tracking {TrackingNumber}",
|
||||
result.ShipmentId, result.TrackingNumber);
|
||||
return Ok(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(new { Error = result.ErrorMessage });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error creating shipment");
|
||||
return StatusCode(500, new { Error = "Error creating shipment" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("orders/{orderId}/ship")]
|
||||
[Authorize(Roles = "Admin")]
|
||||
public async Task<ActionResult<CreateShipmentResult>> ShipOrder(Guid orderId, [FromBody] ShipOrderRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the order
|
||||
var order = await _orderService.GetOrderByIdAsync(orderId);
|
||||
if (order == null)
|
||||
{
|
||||
return NotFound(new { Error = "Order not found" });
|
||||
}
|
||||
|
||||
// Calculate total weight from order items
|
||||
var totalWeight = order.Items.Sum(item =>
|
||||
{
|
||||
// Estimate weight if not available (you might want to add weight to ProductDto)
|
||||
return item.Quantity * 200; // Default 200g per item
|
||||
});
|
||||
|
||||
// Create shipment request
|
||||
var shipmentRequest = new CreateShipmentRequest
|
||||
{
|
||||
RecipientName = order.ShippingName,
|
||||
AddressLine1 = order.ShippingAddress,
|
||||
City = order.ShippingCity,
|
||||
PostCode = order.ShippingPostCode,
|
||||
Country = order.ShippingCountry,
|
||||
WeightInGrams = totalWeight,
|
||||
Value = order.TotalAmount,
|
||||
ServiceCode = request.ServiceCode ?? "1st Class",
|
||||
Reference = $"Order-{order.Id}"
|
||||
};
|
||||
|
||||
var result = await _royalMailService.CreateShipmentAsync(shipmentRequest);
|
||||
|
||||
if (result.Success && !string.IsNullOrEmpty(result.TrackingNumber))
|
||||
{
|
||||
// Update order with tracking information
|
||||
var updateRequest = new UpdateOrderStatusDto
|
||||
{
|
||||
Status = Enums.OrderStatus.Shipped,
|
||||
TrackingNumber = result.TrackingNumber
|
||||
};
|
||||
|
||||
await _orderService.UpdateOrderStatusAsync(orderId, updateRequest);
|
||||
|
||||
_logger.LogInformation("Order {OrderId} shipped with tracking {TrackingNumber}",
|
||||
orderId, result.TrackingNumber);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error shipping order {OrderId}", orderId);
|
||||
return StatusCode(500, new { Error = "Error creating shipment for order" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("track/{trackingNumber}")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult<TrackingResult>> GetTrackingInfo(string trackingNumber)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _royalMailService.GetTrackingInfoAsync(trackingNumber);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting tracking info for {TrackingNumber}", trackingNumber);
|
||||
return StatusCode(500, new { Error = "Error retrieving tracking information" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("services")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult<List<RoyalMailServiceOption>>> GetAvailableServices(
|
||||
[FromQuery] decimal weight = 500,
|
||||
[FromQuery] string country = "United Kingdom")
|
||||
{
|
||||
try
|
||||
{
|
||||
var services = await _royalMailService.GetAvailableServicesAsync(weight, country);
|
||||
return Ok(services);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting Royal Mail services");
|
||||
return StatusCode(500, new { Error = "Error retrieving shipping services" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("labels/{shipmentId}")]
|
||||
[Authorize(Roles = "Admin")]
|
||||
public async Task<IActionResult> GetShippingLabel(string shipmentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var labelData = await _royalMailService.GenerateShippingLabelAsync(shipmentId);
|
||||
|
||||
if (labelData != null)
|
||||
{
|
||||
return File(labelData, "application/pdf", $"shipping-label-{shipmentId}.pdf");
|
||||
}
|
||||
|
||||
return NotFound(new { Error = "Shipping label not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error generating shipping label for {ShipmentId}", shipmentId);
|
||||
return StatusCode(500, new { Error = "Error generating shipping label" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{shipmentId}")]
|
||||
[Authorize(Roles = "Admin")]
|
||||
public async Task<IActionResult> CancelShipment(string shipmentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _royalMailService.CancelShipmentAsync(shipmentId);
|
||||
|
||||
if (result)
|
||||
{
|
||||
return Ok(new { Message = "Shipment cancelled successfully" });
|
||||
}
|
||||
|
||||
return BadRequest(new { Error = "Failed to cancel shipment" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error cancelling shipment {ShipmentId}", shipmentId);
|
||||
return StatusCode(500, new { Error = "Error cancelling shipment" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CalculateShippingRequest
|
||||
{
|
||||
public decimal WeightInGrams { get; set; }
|
||||
public string Country { get; set; } = "United Kingdom";
|
||||
}
|
||||
|
||||
public class ShipOrderRequest
|
||||
{
|
||||
public string? ServiceCode { get; set; }
|
||||
}
|
||||
|
||||
public class ShippingCalculationResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public List<ShippingOption> Services { get; set; } = new();
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
public class ShippingOption
|
||||
{
|
||||
public string ServiceCode { get; set; } = string.Empty;
|
||||
public string ServiceName { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public decimal Cost { get; set; }
|
||||
public int EstimatedDeliveryDays { get; set; }
|
||||
public bool SupportsTracking { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user