namespace Modules.WMS.Picking.Shared; public enum PickingScanDecisionKind { RejectLocation, AcceptLocation, RejectItem, AcceptItem } public sealed record PickingScanState( bool LocationConfirmed, string LocationCode, string LocationBarcode, string ItemBarcode, decimal RequiredQty, decimal PickedQty); public sealed record PickingScanDecision( PickingScanDecisionKind Kind, bool Accepted, string Feedback, string Message, decimal? NewPickedQty = null, bool LineCompleted = false) { public string AuditAction => Kind switch { PickingScanDecisionKind.RejectLocation => "LocationRejected", PickingScanDecisionKind.AcceptLocation => "LocationAccepted", PickingScanDecisionKind.RejectItem => "ItemRejected", PickingScanDecisionKind.AcceptItem => "ItemAccepted", _ => "BarcodeScanned" }; } public static class PickingScanStateMachine { public static PickingScanDecision Decide(PickingScanState state, string scannedBarcode) { var barcode = scannedBarcode.Trim(); if (!state.LocationConfirmed) { if (!string.Equals(barcode, state.LocationBarcode, StringComparison.OrdinalIgnoreCase)) return new( PickingScanDecisionKind.RejectLocation, false, "error", $"잘못된 위치입니다. {state.LocationCode} 위치로 이동하세요."); return new( PickingScanDecisionKind.AcceptLocation, true, "success", "위치를 확인했습니다. 상품을 스캔하세요."); } if (!string.Equals(barcode, state.ItemBarcode, StringComparison.OrdinalIgnoreCase)) return new( PickingScanDecisionKind.RejectItem, false, "error", "다른 상품입니다. 화면의 품목과 바코드를 확인하세요."); var nextQty = Math.Min(state.PickedQty + 1m, state.RequiredQty); var completed = nextQty >= state.RequiredQty; return new( PickingScanDecisionKind.AcceptItem, true, "success", completed ? "현재 품목을 완료했습니다. 다음 작업을 진행하세요." : "1개 피킹했습니다.", nextQty, completed); } }