using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using MiGu.Server.Wms; namespace MiGu.Server.Controllers; [ApiController] [Authorize] [TypeFilter(typeof(WmsExceptionFilter))] [Route("api/wms")] public sealed class WmsController : ControllerBase { private readonly WmsService _service; private readonly WmsTransportRuleService _transportRules; private readonly WmsTransportTaskService _transportTasks; public WmsController(WmsService service, WmsTransportRuleService transportRules, WmsTransportTaskService transportTasks) { _service = service; _transportRules = transportRules; _transportTasks = transportTasks; } [HttpGet("warehouses")] public Task> Warehouses([FromQuery] string? q) => _service.Warehouses(q); [HttpPost("warehouses")] public async Task SaveWarehouse([FromBody] MasterDataRequest req) => Ok(await _service.SaveWarehouse(req, User.ActorName())); [HttpPut("warehouses/{id:guid}")] public async Task UpdateWarehouse(Guid id, [FromBody] MasterDataRequest req) => Ok(await _service.SaveWarehouse(req with { Id = id }, User.ActorName())); [HttpDelete("warehouses/{id:guid}")] public async Task DeleteWarehouse(Guid id, [FromQuery] long? version) { await _service.DeleteEntity(id, version, User.ActorName()); return NoContent(); } [HttpGet("areas")] public Task> Areas([FromQuery] string? q, [FromQuery] Guid? warehouseId) => _service.Areas(q, warehouseId); [HttpPost("areas")] public async Task SaveArea([FromBody] AreaRequest req) => Ok(await _service.SaveArea(req, User.ActorName())); [HttpPut("areas/{id:guid}")] public async Task UpdateArea(Guid id, [FromBody] AreaRequest req) => Ok(await _service.SaveArea(req with { Id = id }, User.ActorName())); [HttpDelete("areas/{id:guid}")] public async Task DeleteArea(Guid id, [FromQuery] long? version) { await _service.DeleteEntity(id, version, User.ActorName()); return NoContent(); } [HttpGet("storages")] public Task> Storages([FromQuery] string? q, [FromQuery] Guid? areaId, [FromQuery] string? locationKind) => _service.Storages(q, areaId, locationKind); [HttpPost("storages")] public async Task SaveStorage([FromBody] StorageRequest req) => Ok(await _service.SaveStorage(req, User.ActorName())); [HttpPut("storages/{id:guid}")] public async Task UpdateStorage(Guid id, [FromBody] StorageRequest req) => Ok(await _service.SaveStorage(req with { Id = id }, User.ActorName())); [HttpDelete("storages/{id:guid}")] public async Task DeleteStorage(Guid id, [FromQuery] long? version) { await _service.DeleteEntity(id, version, User.ActorName()); return NoContent(); } [HttpPost("areas/{id:guid}/generate-bins")] public async Task GenerateBins(Guid id, [FromBody] GenerateBinsRequest req) => Ok(new { created = await _service.GenerateBins(id, req, User.ActorName()) }); [HttpPost("storages/{id:guid}/lock")] public async Task LockStorage(Guid id, [FromQuery] long? version) => Ok(await _service.SetStorageLock(id, true, version, User.ActorName())); [HttpPost("storages/{id:guid}/unlock")] public async Task UnlockStorage(Guid id, [FromQuery] long? version) => Ok(await _service.SetStorageLock(id, false, version, User.ActorName())); [HttpPost("storages/{id:guid}/enable")] public async Task EnableStorage(Guid id, [FromQuery] long? version) => Ok(await _service.SetStorageEnabled(id, true, version, User.ActorName())); [HttpPost("storages/{id:guid}/disable")] public async Task DisableStorage(Guid id, [FromQuery] long? version) => Ok(await _service.SetStorageEnabled(id, false, version, User.ActorName())); [HttpGet("containers")] public Task> Containers([FromQuery] string? q) => _service.Containers(q); [HttpPost("containers")] public async Task SaveContainer([FromBody] ContainerRequest req) => Ok(await _service.SaveContainer(req, User.ActorName())); [HttpPut("containers/{id:guid}")] public async Task UpdateContainer(Guid id, [FromBody] ContainerRequest req) => Ok(await _service.SaveContainer(req with { Id = id }, User.ActorName())); [HttpDelete("containers/{id:guid}")] public async Task DeleteContainer(Guid id, [FromQuery] long? version) { await _service.DeleteEntity(id, version, User.ActorName()); return NoContent(); } [HttpGet("material-types")] public Task> MaterialTypes([FromQuery] string? q) => _service.MaterialTypes(q); [HttpPost("material-types")] public async Task SaveMaterialType([FromBody] MaterialTypeRequest req) => Ok(await _service.SaveMaterialType(req, User.ActorName())); [HttpPut("material-types/{id:guid}")] public async Task UpdateMaterialType(Guid id, [FromBody] MaterialTypeRequest req) => Ok(await _service.SaveMaterialType(req with { Id = id }, User.ActorName())); [HttpDelete("material-types/{id:guid}")] public async Task DeleteMaterialType(Guid id, [FromQuery] long? version) { await _service.DeleteEntity(id, version, User.ActorName()); return NoContent(); } [HttpGet("materials")] public Task> Materials( [FromQuery] string? q, [FromQuery] string? lifecycle, [FromQuery] bool? onlyUnbound, [FromQuery] bool? onlyBound) => _service.Materials(q, lifecycle, onlyUnbound, onlyBound); [HttpPost("materials")] public async Task SaveMaterial([FromBody] MaterialRequest req) => Ok(await _service.SaveMaterial(req, User.ActorName())); [HttpPut("materials/{id:guid}")] public async Task UpdateMaterial(Guid id, [FromBody] MaterialRequest req) => Ok(await _service.SaveMaterial(req with { Id = id }, User.ActorName())); [HttpDelete("materials/{id:guid}")] public async Task DeleteMaterial(Guid id, [FromQuery] long? version) { await _service.DeleteEntity(id, version, User.ActorName()); return NoContent(); } [HttpPost("materials/{id:guid}/archive")] public async Task ArchiveMaterial(Guid id, [FromQuery] long? version) => Ok(await _service.ArchiveMaterial(id, version, User.ActorName())); [HttpPost("materials/purge-archived")] public async Task PurgeArchivedMaterials([FromQuery] int take = 200) { await _service.PurgeArchivedMaterials(User.ActorName(), take); return NoContent(); } [HttpGet("container-locations")] public Task> ContainerLocations([FromQuery] string? locationType, [FromQuery] string? q) => _service.ContainerLocations(locationType, q); [HttpPost("container-locations")] public async Task SaveContainerLocation([FromBody] ContainerLocationRequest req) => Ok(await _service.BindOrTransferLocation(req, User.ActorName())); [HttpPost("container-locations/transfer")] public async Task TransferContainerLocation([FromBody] ContainerLocationRequest req) => Ok(await _service.BindOrTransferLocation(req, User.ActorName())); [HttpDelete("container-locations/{containerId:guid}")] public async Task UnbindContainerLocation(Guid containerId, [FromQuery] string? reason) { await _service.UnbindLocation(containerId, User.ActorName(), reason ?? ""); return NoContent(); } [HttpGet("container-materials")] public Task> ContainerMaterials([FromQuery] string? q, [FromQuery] Guid? containerId) => _service.ContainerMaterials(q, containerId); [HttpPost("container-materials")] public async Task SaveContainerMaterial([FromBody] BindMaterialRequest req) => Ok(await _service.BindMaterial(req, User.ActorName())); [HttpPost("container-materials/bind")] public async Task BindMaterial([FromBody] BindMaterialRequest req) => Ok(await _service.BindMaterial(req, User.ActorName())); [HttpDelete("container-materials/{id:guid}")] public async Task UnbindMaterial(Guid id, [FromQuery] string? reason, [FromQuery] bool archive = false) { await _service.UnbindMaterial(id, User.ActorName(), reason ?? "", archive); return NoContent(); } [HttpGet("inventory/materials")] public Task> InventoryMaterials( [FromQuery] Guid? areaId, [FromQuery] Guid? storageId, [FromQuery] string? q) => _service.InventoryMaterials(areaId, storageId, q); [HttpGet("stock-events")] public Task> StockEvents( [FromQuery] string? eventType, [FromQuery] Guid? materialId, [FromQuery] Guid? containerId) => _service.StockEvents(eventType, materialId, containerId); [HttpGet("container-location-history")] public Task> ContainerLocationHistory([FromQuery] Guid? containerId) => _service.LocationHistory(containerId); [HttpGet("container-material-history")] public Task> ContainerMaterialHistory([FromQuery] Guid? containerId, [FromQuery] Guid? materialId) => _service.MaterialHistory(containerId, materialId); [HttpGet("transport-rules")] public Task> TransportRules([FromQuery] string? triggerType, [FromQuery] string? q) => _transportRules.ListAsync(triggerType, q); [HttpPost("transport-rules")] public async Task SaveTransportRule([FromBody] TransportRuleRequest req) => Ok(await _transportRules.SaveAsync(req, User.ActorName())); [HttpPut("transport-rules/{id:guid}")] public async Task UpdateTransportRule(Guid id, [FromBody] TransportRuleRequest req) => Ok(await _transportRules.SaveAsync(req with { Id = id }, User.ActorName())); [HttpDelete("transport-rules/{id:guid}")] public async Task DeleteTransportRule(Guid id, [FromQuery] long? version) { await _transportRules.DeleteAsync(id, version, User.ActorName()); return NoContent(); } [HttpGet("transport-candidates")] public async Task TransportCandidates([FromQuery] TransportRequestQuery q) => Ok(await _transportTasks.PreviewCandidatesAsync(q.ToRequest())); [HttpPost("transport-tasks/generate")] public async Task GenerateTransportTask([FromBody] TransportRequestQuery q) => Ok(await _transportTasks.GenerateAsync(q.ToRequest(), User.ActorName())); [HttpGet("transport-tasks")] public Task> TransportTasks([FromQuery] string? status, [FromQuery] string? triggerType) => _transportTasks.ListAsync(status, triggerType); [HttpPost("transport-tasks/{id:guid}/reserve")] public async Task ReserveTransportTask(Guid id, [FromQuery] string? reason) => Ok(await _transportTasks.ReserveAsync(id, User.ActorName(), reason ?? "")); [HttpPost("transport-tasks/{id:guid}/cancel")] public async Task CancelTransportTask(Guid id, [FromQuery] string? reason) => Ok(await _transportTasks.CancelAsync(id, User.ActorName(), reason ?? "")); [HttpPost("transport-tasks/{id:guid}/complete")] public async Task CompleteTransportTask(Guid id, [FromQuery] string? reason) => Ok(await _transportTasks.CompleteAsync(id, User.ActorName(), reason ?? "")); [HttpPost("transport-tasks/{id:guid}/dispatch")] public async Task DispatchTransportTask(Guid id) => Ok(await _transportTasks.DispatchAsync(id, User.ActorName())); } public sealed class WmsExceptionFilter : IExceptionFilter { public void OnException(ExceptionContext context) { if (context.Exception is not InvalidOperationException ex) return; context.Result = new BadRequestObjectResult(new { message = ex.Message }); context.ExceptionHandled = true; } } public sealed class TransportRequestQuery { public string TriggerType { get; set; } = WmsTransportTriggerTypes.MaterialCall; public Guid? RuleId { get; set; } public string? RequestSiteId { get; set; } public Guid? MaterialId { get; set; } public decimal? Quantity { get; set; } public Guid? ContainerId { get; set; } public Guid? SourceStorageId { get; set; } public Guid? TargetStorageId { get; set; } public string? BatchNo { get; set; } public int Priority { get; set; } public string Reason { get; set; } = ""; public WmsTransportRequest ToRequest() => new( TriggerType, RuleId, RequestSiteId, MaterialId, Quantity, ContainerId, SourceStorageId, TargetStorageId, BatchNo, Priority, Reason, null); }