using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Windows; using System.Windows.Controls; using com.sun.tools.@internal.ws.processor.util; using Comal.Classes; using InABox.Clients; using InABox.Core; using InABox.DynamicGrid; using InABox.Wpf; using InABox.WPF; using PRSDesktop.Panels.Products.Locations; using Syncfusion.Windows.Controls.RichTextBoxAdv; using Exception = System.Exception; namespace PRSDesktop; public class StockHoldingGrid : DynamicDataGrid { private enum MovementAction { Receive, Issue, Transfer } private MovementAction _action; private StockHolding? _holding; private Button IssueButton; private Button ReceiveButton; private DynamicDataGrid smg; //Button ReserveButton = null; private Button TransferButton; private Button RecalculateButton; private Button AdjustValueButton; private Button RelocateButton; public StockHoldingGrid() : base() { ColumnsTag = "StockHolding"; } protected override void Init() { base.Init(); ReceiveButton = AddButton("Receive", PRSDesktop.Resources.add.AsBitmapImage(), ReceiveStock); ReceiveButton.IsEnabled = false; IssueButton = AddButton("Issue", PRSDesktop.Resources.delete.AsBitmapImage(), IssueStock); IssueButton.IsEnabled = false; //ReserveButton = AddButton("Reserve", PRSDesktop.Resources.project.AsBitmapImage(), ReserveStock); //ReserveButton.Margin = new Thickness(20, ReserveButton.Margin.Top, ReserveButton.Margin.Right, ReserveButton.Margin.Bottom); //ReserveButton.IsEnabled = false; TransferButton = AddButton("Transfer", PRSDesktop.Resources.split.AsBitmapImage(), TransferStock); TransferButton.Margin = new Thickness(20, TransferButton.Margin.Top, TransferButton.Margin.Right, TransferButton.Margin.Bottom); TransferButton.IsEnabled = false; RelocateButton = AddButton("Relocate", PRSDesktop.Resources.box.AsBitmapImage(), RelocateStock); RelocateButton.IsEnabled = false; AdjustValueButton = AddButton("Adjust Value", PRSDesktop.Resources.receipt.AsBitmapImage(), AdjustValues, DynamicGridButtonPosition.Right); AdjustValueButton.Margin = new Thickness(AdjustValueButton.Margin.Left, AdjustValueButton.Margin.Top, 10, AdjustValueButton.Margin.Bottom); RecalculateButton = AddButton("Recalculate", PRSDesktop.Resources.service.AsBitmapImage(), RecalculateHoldings, DynamicGridButtonPosition.Right); HiddenColumns.Add(x => x.Product.ID); HiddenColumns.Add(x => x.Job.ID); HiddenColumns.Add(x => x.Job.JobNumber); HiddenColumns.Add(x => x.Job.Name); HiddenColumns.Add(x => x.Location.ID); HiddenColumns.Add(x => x.Location.Code); HiddenColumns.Add(x => x.Location.Description); HiddenColumns.Add(x => x.Style.ID); HiddenColumns.Add(x => x.Style.Code); HiddenColumns.Add(x => x.Qty); HiddenColumns.Add(x => x.Units); HiddenColumns.Add(x => x.Available); HiddenColumns.Add(x => x.AverageValue); HiddenColumns.Add(x => x.Dimensions.Unit.ID); HiddenColumns.Add(x => x.Dimensions.Unit.Description); HiddenColumns.Add(x => x.Dimensions.Unit.HasHeight); HiddenColumns.Add(x => x.Dimensions.Unit.HasLength); HiddenColumns.Add(x => x.Dimensions.Unit.HasWidth); HiddenColumns.Add(x => x.Dimensions.Unit.HasHeight); HiddenColumns.Add(x => x.Dimensions.Unit.HasQuantity); HiddenColumns.Add(x => x.Dimensions.Unit.Format); HiddenColumns.Add(x => x.Dimensions.Unit.Formula); HiddenColumns.Add(x => x.Dimensions.Length); HiddenColumns.Add(x => x.Dimensions.Width); HiddenColumns.Add(x => x.Dimensions.Height); HiddenColumns.Add(x => x.Dimensions.Quantity); HiddenColumns.Add(x => x.Dimensions.Value); HiddenColumns.Add(x => x.Dimensions.UnitSize); ActionColumns.Add(new DynamicMenuColumn(BuildMenu) { Position = DynamicActionColumnPosition.End }); } private bool AdjustValues(Button arg1, CoreRow[] rows) { if (rows?.Any() != true) return false; double _newvalue = 0.0; if (DoubleEdit.Execute("New Average Value", 0, double.MaxValue, ref _newvalue)) { Progress.ShowModal("Creating Batch", progress => { StockMovementBatch _batch = new StockMovementBatch() { Type = StockMovementBatchType.Transfer, Employee = new EmployeeLink() { ID = App.EmployeeID }, Notes = "Stock Value Adjustment" }; Client.Save(_batch,"Stock value adjusted from holding grid"); progress.Report("Creating Movements"); List _updates = new List(); foreach (var _row in rows) { var _holding = _row.ToObject(); _holding.AverageValue = _newvalue; _holding.Value = _newvalue * _holding.Units; Client.Save(_holding,"Stock value adjusted from holding grid"); _updates.AddRange(_holding.AdjustValue(_newvalue, _batch)); } progress.Report("Saving Movements"); Client.Save(_updates,"Stock value adjusted from holding grid"); }); MessageBox.Show("All Done"); return true; } return false; } private bool RecalculateHoldings(Button arg1, CoreRow[] arg2) { Dictionary messages = new(); void AddMessage(String type) { messages.TryGetValue(type, out int count); messages[type] = ++count; } Progress.ShowModal("Recalculating", progress => { progress.Report("Loading Data"); MultiQuery query = new MultiQuery(); query.Add( new Filter(x => x.Location.ID).IsEqualTo(Location.ID), Columns.Required().Add(x => x.ID) .Add(x => x.Product.ID) .Add(x => x.Job.ID) .Add(x => x.Style.ID) .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local) .Add(x => x.Units) .Add(x => x.AverageValue) .Add(x => x.Available) .Add(x => x.Qty) .Add(x => x.Weight) .Add(x => x.Value) ); query.Add( new Filter(x => x.Location.ID).IsEqualTo(Location.ID), Columns.None().Add(x => x.ID) .Add(x => x.Product.ID) .Add(x => x.Job.ID) .Add(x => x.Style.ID) .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local) .Add(x => x.Units) .Add(x => x.Cost) .Add(x => x.JobRequisitionItem.ID) ); query.Query(); var holdings = query.Get().ToObjects().ToList(); var toDelete = new List(); var movements = query.Get().ToObjects().ToList(); progress.Report("Processing"); var updates = new List(); while (movements.Any()) { var first = movements.First(); var selected = movements.Where(x => x.IsEqualTo(first)).ToList(); var holding = holdings.FirstOrDefault(x => x.IsEqualTo(first)); if (holding == null) { holding = new StockHolding(); holding.Location.ID = Location.ID; holding.Product.ID = first.Product.ID; holding.Style.ID = first.Style.ID; holding.Job.ID = first.Job.ID; holding.Dimensions.CopyFrom(first.Dimensions); } holding.Recalculate(selected); // Removing from the list so that it is not deleted. if (holdings.Contains(holding)) holdings.Remove(holding); if (holding.Units.IsEffectivelyEqual(0.0) && holding.Available.IsEffectivelyEqual(0.0)) { if(holding.ID != Guid.Empty) { toDelete.Add(holding); } } else if (holding.IsChanged()) { AddMessage(holding.ID != Guid.Empty ? "updated" : "added"); updates.Add(holding); } movements.RemoveAll(x => selected.Any(s => s.ID == x.ID)); } toDelete.AddRange(holdings); foreach (var holding in toDelete) AddMessage("deleted"); if (updates.Any()) { progress.Report($"Updating {updates.Count} Holdings"); new Client().Save(updates.Where(x => x.IsChanged()), "Updated by Recalculation"); } if (toDelete.Any()) { progress.Report($"Deleting {toDelete.Count} Holdings"); new Client().Delete(toDelete, "Removed by Recalculation"); } }); MessageWindow.ShowMessage( messages.Any() ? String.Join("\n", messages.Select(x => $"{x.Value} holdings {x.Key}")) : "Nothing to Update!" ,"Recalculate"); return true; } public override DynamicGridColumns GenerateColumns() { var columns = new DynamicGridColumns(); columns.Add(x => x.Product.Code, 120, "Product Code", "", Alignment.MiddleCenter); columns.Add(x => x.Product.Name, 0, "Product Name", "", Alignment.MiddleLeft); columns.Add(x => x.Job.JobNumber, 50, "Job", "", Alignment.MiddleCenter); columns.Add(x => x.Style.Description, 0, "Style", "", Alignment.MiddleLeft); columns.Add(x => x.Dimensions.UnitSize, 100, "Size", "", Alignment.MiddleCenter); columns.Add(x => x.Units, 70, "Units", "", Alignment.MiddleCenter); columns.Add(x => x.Available, 70, "Available", "", Alignment.MiddleCenter); return columns; } protected override void DoReconfigure(DynamicGridOptions options) { base.DoReconfigure(options); options.AddRows = false; options.EditRows = Security.CanEdit(); options.DeleteRows = false; options.RecordCount = true; options.SelectColumns = true; options.FilterRows = true; } private void BuildMenu(DynamicMenuColumn column, CoreRow? row) { if (row is null) return; var holding = row.ToObject(); if (holding.Available.IsEffectivelyEqual(holding.Units)) column.AddItem("(No Requisitions in this Holding", null, null).IsEnabled = false; else { column.AddItem("View Requisition Items", null, ViewRequisitions_Click); if (Security.IsAllowed()) { column.GetMenu().AddItem("Release allocated stock", null, holding, ReleaseAllocatedStock_Click); } } column.AddSeparator(); column.AddItem("Relocate Items", null, r => { var requiitems = holding.LoadRequisitionItems(true).ToArray(); RelocateItems(holding, requiitems); }); if (holding.Dimensions.Unit.HasDimensions() && holding.Available.IsEffectivelyGreaterThan(0.0)) column.AddItem("Convert Dimensions", null, r => { var calculator = new StockTransformWindow(holding); if (calculator.ShowDialog() == true) { var transferout = holding.CreateMovement(); transferout.Date = DateTime.Now; transferout.Issued = calculator.OldAvailable; transferout.Transaction = Guid.NewGuid(); transferout.Type = StockMovementType.TransferOut; var transferin = holding.CreateMovement(); transferin.Date = transferout.Date.AddTicks(1); transferin.Dimensions.CopyFrom(calculator.Dimensions); transferin.Received = calculator.NewAvailable; transferin.Transaction = transferout.Transaction; transferin.Type = StockMovementType.TransferIn; Client.Save([transferout,transferin], "Converted Dimensions"); Refresh(false,true); } }); } private void DoTransfer(StockHolding holding, IList requiitems, Func getQuantity, Action modify) { var updates = new List(); DoTransfer(holding, requiitems, getQuantity, modify, updates); SaveBatch(StockMovementBatchType.Transfer, updates); DoChanged(); Refresh(false, true); } private static void DoTransfer(StockHolding holding, IList requiitems, Func getQuantity, Action modify, List updates) { foreach(var requi in requiitems) { var qty = getQuantity(requi); if (!qty.HasValue || qty.Value == 0) continue; var mout = holding.CreateMovement(); mout.Cost = holding.AverageValue; mout.Date = DateTime.Now; mout.Employee.ID = App.EmployeeID; var min = mout.CreateMovement(); min.Cost = holding.AverageValue; min.Employee.ID = App.EmployeeID; min.Date = mout.Date; min.Transaction = mout.Transaction; if(qty.Value > 0) { mout.Issued = qty.Value; mout.Type = StockMovementType.TransferOut; min.Received = qty.Value; min.Type = StockMovementType.TransferIn; } else { min.Issued = -qty.Value; min.Type = StockMovementType.TransferOut; mout.Received = -qty.Value; mout.Type = StockMovementType.TransferIn; } modify(requi, mout, min); updates.Add(mout); updates.Add(min); } } private void ReleaseAllocatedStock_Click(StockHolding holding) { var requiitems = holding.LoadRequisitionItems(true).Where(x => x.ID != Guid.Empty).ToList(); var win = new StockHoldingRelocationWindow(holding, requiitems, StockHoldingRelocationWindow.RelocationMode.ReleaseAllocations); if (win.ShowDialog() == true) { var quantities = win.GetQuantities(); DoTransfer(holding, requiitems, x => quantities.TryGetValue(x.ID, out var value) ? value : null, (requi, mout, min) => { mout.JobRequisitionItem.ID = requi.ID; mout.Notes = $"Released from Job Requisition {requi.Requisition.Number}: {requi.Requisition.Description} for Job {requi.Job.JobNumber}"; min.JobRequisitionItem.ID = Guid.Empty; min.Notes = $"Released from Job Requisition {requi.Requisition.Number}: {requi.Requisition.Description} for Job {requi.Job.JobNumber}"; }); } } private void RelocateItems(StockHolding holding, JobRequisitionItem[] requiitems) { var win = new StockHoldingRelocationWindow(holding, requiitems, StockHoldingRelocationWindow.RelocationMode.Transfer) { OriginalTarget = new StockLocation { ID = holding.Location.ID, Code = holding.Location.Code, Description = holding.Location.Description, }, OriginalJob = new Job { ID = holding.Job.ID, JobNumber = holding.Job.JobNumber, Name = holding.Job.Name } }; if (win.ShowDialog() == true) { var quantities = win.GetQuantities(); var target = win.GetTargetLocation(); DoTransfer(holding, requiitems, x => quantities.TryGetValue(x.ID, out var qty) ? qty : null, (requi, mout, min) => { mout.JobRequisitionItem.ID = requi.ID; mout.Notes = $"Moved to {target.Code} by {App.EmployeeName}"; min.Location.Clear(); min.Location.ID = target.ID; min.Job.CopyFrom(win.Job ?? new()); min.JobRequisitionItem.ID = requi.ID; min.Notes = $"Moved From {holding.Location.Code} by {App.EmployeeName}"; }); } } private void ViewRequisitions_Click(CoreRow? row) { if (row is null) return; var holding = row.ToObject(); var grid = (DynamicGridUtils.CreateDynamicGrid(typeof(DynamicDataGrid<>), typeof(JobRequisitionItem)) as DynamicDataGrid)!; grid.OnDefineFilter += (type) => { if(type == typeof(JobRequisitionItem)) { return new Filter(x => x.ID) .InQuery( StockHolding.GetFilter(holding), x => x.JobRequisitionItem.ID) // We don't care about stuff which has nothing in stock, which means it's been either issued or cancelled. .And(x => x.InStock).IsNotEqualTo(FilterConstant.Zero); } else { return null; } }; DynamicGridUtils.CreateGridWindow("Job Requisition Items for stock holding", grid).ShowDialog(); } private bool ReceiveStock(Button arg1, CoreRow[] rows) { var movement = new StockMovement(); movement.Location.ID = Location.ID; movement.Location.Code = Location.Code; movement.Job.ID = Location.Job.ID; movement.Job.JobNumber = Location.Job.JobNumber; movement.Job.Name = Location.Job.Name; movement.Location.Description = Location.Description; movement.Date = DateTime.Now; movement.Employee.ID = App.EmployeeID; movement.Type = StockMovementType.Receive; movement.CommitChanges(); var holding = rows.FirstOrDefault()?.ToObject(); var smg = CheckStockMovementGrid(MovementAction.Receive, holding); var result = smg.EditItems(new[] { movement }); if (result) { DoChanged(); SaveBatch(StockMovementBatchType.Receipt, new StockMovement[] { movement}); } return result; } private bool IssueStock(Button arg1, CoreRow[] rows) { if (rows?.Length != 1) { MessageWindow.ShowMessage("Please select an item to issue", "No selected items"); return false; } var holding = rows.First().ToObject(); var items = holding.LoadRequisitionItems(true).AsArray(); DoIssue(holding, items); return false; } private IEnumerable CreateIssue(StockHolding holding, IJob? job, double qty, IJobRequisitionItem requi) { var issue = CreateMovementFromHolding(holding); issue.Job.ID = job?.ID ?? Guid.Empty; issue.Type = StockMovementType.Issue; issue.JobRequisitionItem.ID = requi.ID; issue.Issued = qty; issue.Notes = $"Issued by {App.EmployeeName}"; issue.Date = DateTime.Now; yield return issue; if (holding.Job.ID != issue.Job.ID) { var xferout = CreateMovementFromHolding(holding); xferout.Type = StockMovementType.TransferOut; xferout.JobRequisitionItem.ID = requi.ID; xferout.Issued = qty; xferout.Transaction = issue.Transaction; xferout.System = true; xferout.Notes = $"Issued by {App.EmployeeName}"; xferout.Date = issue.Date; yield return xferout; var xferin = CreateMovementFromHolding(holding); xferin.Job.ID = issue.Job.ID; xferin.Type = StockMovementType.TransferIn; xferin.JobRequisitionItem.ID = requi.ID; xferin.Received = qty; xferin.Transaction = issue.Transaction; xferin.System = true; xferin.Notes = $"Issued by {App.EmployeeName}"; xferin.Date = issue.Date; yield return xferin; } } private void DoIssue(StockHolding holding, JobRequisitionItem[] requiitems) { var updates = new List(); var win = new StockHoldingRelocationWindow(holding, requiitems, StockHoldingRelocationWindow.RelocationMode.Issue) { OriginalJob = new Job { ID = holding.Job.ID, JobNumber = holding.Job.JobNumber } }; if (win.ShowDialog() == true) { var quantities = win.GetQuantities(); foreach(var requi in requiitems) { if (!quantities.TryGetValue(requi.ID, out var qty) || qty <= 0) continue; updates.AddRange(CreateIssue(holding, win.Job, qty, requi)); } SaveBatch(StockMovementBatchType.Issue, updates); DoChanged(); Refresh(false,true); } } private bool TransferStock(Button arg1, CoreRow[] rows) { if (rows?.Length != 1) return false; var holding = rows.First().ToObject(); var items = holding.LoadRequisitionItems(true).AsArray(); RelocateItems(holding, items); return false; } private bool RelocateStock(Button btn, CoreRow[] rows) { StockLocation? target = null; while(true) { target = StockHoldingRelocationWindow.LookupLocation(); if(target is null) { return false; } else if(target.ID == Location.ID) { MessageWindow.ShowMessage($"These items are already in {target.Code}; please select a different location.", "Invalid transfer"); } else { break; } } var holdings = rows.ToArray(); var updates = new List(); foreach(var holding in holdings) { var items = holding.LoadRequisitionItems(true).AsIList(); var rIDs = items.Select(x => x.ID).Where(x => x != Guid.Empty).ToArray(); var quantities = Client.Query( StockHolding.GetFilter(holding) .Combine(new Filter(x => x.JobRequisitionItem.ID).InList(rIDs)), Columns.None().Add(x => x.Units).Add(x => x.JobRequisitionItem.ID)) .ToObjects().GroupBy(x => x.JobRequisitionItem.ID).ToDictionary(x => x.Key, x => x.Sum(x => x.Units)); DoTransfer(holding, items, x => x.ID == Guid.Empty ? x.Qty : quantities.GetValueOrDefault(x.ID), (requi, mThis, mThat) => { mThis.JobRequisitionItem.ID = requi.ID; mThis.Notes = $"Moved to {target.Code} by {App.EmployeeName}"; mThat.JobRequisitionItem.ID = requi.ID; mThat.Notes = $"Moved to {target.Code} by {App.EmployeeName}"; mThat.Location.Clear(); mThat.Location.ID = target.ID; }, updates); } SaveBatch(StockMovementBatchType.Transfer, updates); DoChanged(); Refresh(false, true); return true; } protected override void DoEdit() { var holding = SelectedRows.FirstOrDefault()?.ToObject(); if (holding is null) return; var movement = CreateMovementFromHolding(holding); movement.Received = holding.Available; movement.Type = StockMovementType.TransferIn; var smg = CheckStockMovementGrid(MovementAction.Transfer, holding); var result = smg.EditItems(new[] { movement }); var mvts = new List { movement }; if (result) { var other = CreateMovementFromHolding(holding); other.Issued = movement.Received; other.Transaction = movement.Transaction; other.Type = StockMovementType.TransferOut; var changes = new List(); if (movement.Location.ID != other.Location.ID) changes.Add(movement.Location.Code); if (movement.Job.ID != other.Job.ID) changes.Add(string.IsNullOrEmpty(movement.Job.JobNumber) ? "General Stock" : movement.Job.JobNumber); if (movement.Style.ID != other.Style.ID) changes.Add(movement.Style.Code); //other.Notes = "Transferred to "+String.Join(" / ",changes); other.Notes = string.Format("Transferred to {0}{1}{2}", string.Join(" / ", changes.Where(x => !string.IsNullOrWhiteSpace(x))), string.IsNullOrWhiteSpace(movement.Notes) ? "" : "\n", string.Join("\n", movement.Notes.Split('\n').Where(x => !x.StartsWith("Transferred from "))) ); other.System = true; Client.Save(other, ""); mvts.Add(other); } if (result) { DoChanged(); SaveBatch(StockMovementBatchType.Transfer, mvts.ToArray()); } Refresh(false, true); } public IStockLocation Location { get; set; } protected override void SelectItems(CoreRow[]? rows) { base.SelectItems(rows); var nRows = rows?.Length ?? 0; ReceiveButton.IsEnabled = Location != null && Location.ID != Guid.Empty; IssueButton.IsEnabled = Location != null && Location.ID != Guid.Empty && nRows > 0; if(Location is null || Location.ID == Guid.Empty || nRows == 0) { TransferButton.IsEnabled = false; RelocateButton.IsEnabled = false; } else if(nRows == 1) { TransferButton.IsEnabled = true; RelocateButton.IsEnabled = true; } else { TransferButton.IsEnabled = false; RelocateButton.IsEnabled = true; } var _groups = rows?.GroupBy(x => new Tuple( x.Get(c => c.Product.ID), x.Get(c => c.Dimensions.Value)) ); AdjustValueButton.IsEnabled = Location != null && Location.ID != Guid.Empty && _groups?.Count() == 1; RecalculateButton.IsEnabled = Location != null && Location.ID != Guid.Empty; } protected override void Reload( Filters criteria, Columns columns, ref SortOrder? sort, CancellationToken token, Action action) { ReceiveButton.IsEnabled = Location != null && Location.ID != Guid.Empty; if (Location == null) criteria.Add(new Filter().None()); else criteria.Add(new Filter(x => x.Location.ID).IsEqualTo(Location.ID)); base.Reload(criteria, columns, ref sort, token, action); } #region Internal Utilities private StockMovement CreateMovementFromHolding(StockHolding holding) { var movement = new StockMovement(); movement.Date = DateTime.Now; movement.Location.ID = Location.ID; movement.Location.Code = Location.Code; movement.Location.Description = Location.Description; movement.Product.ID = holding.Product.ID; movement.Job.ID = holding.Job.ID; movement.Job.JobNumber = holding.Job.JobNumber; movement.Style.ID = holding.Style.ID; movement.Style.Code = holding.Style.Code; movement.Employee.ID = App.EmployeeID; movement.Dimensions.CopyFrom(holding.Dimensions); movement.Cost = holding.AverageValue; movement.CommitChanges(); return movement; } private static void SaveBatch(StockMovementBatchType type, IList movements) { var batch = new StockMovementBatch(); batch.Type = type; batch.Notes = batch.Type + " batch created from Desktop Stock Location Screen"; batch.Employee.ID = App.EmployeeID; new Client().Save(batch, "created from Desktop Stock Location Screen"); foreach (var mvt in movements) { mvt.Batch.ID = batch.ID; } new Client().Save(movements, "Updating batch from Desktop Stock Location Screen"); } #endregion #region StockMovementGrid private DynamicDataGrid CheckStockMovementGrid(MovementAction action, StockHolding holding) { _action = action; _holding = holding; if (smg == null) { smg = new DynamicDataGrid(); smg.OnCustomiseEditor += StockMovementCustomiseEditor; smg.OnValidate += StockMovementValidate; smg.OnEditorValueChanged += StockMovementValueChanged; } return smg; } private Dictionary StockMovementValueChanged(IDynamicEditorForm form, string name, object value) { var result = new Dictionary(); if (name.Equals("Location.Job.ID")) { var editor = form.FindEditor("Job.ID"); if (!value.Equals(Guid.Empty)) result = DynamicGridUtils.UpdateEditorValue(form.Items, "Job.ID", value); else foreach (StockMovement item in form.Items) result = DynamicGridUtils.UpdateEditorValue(new[] { item }, "Job.ID", item.Job.HasOriginalValue("ID") ? item.Job.GetOriginalValue(x => x.ID) : item.Job.ID); editor.IsEnabled = value.Equals(Guid.Empty); } return result; } private void StockMovementValidate(object sender, StockMovement[] items, List errors) { if (items.Any(x => x.Received == 0 && x.Issued == 0)) { errors.Add("Quantity may not be zero"); } else if(_action == MovementAction.Issue && _holding is not null && items.Any(x => x.Issued > _holding.Available)) { errors.Add($"Quantity may not be greater than available stock ({_holding.Available})"); } if (items.Any(x => x.Product.ID == Guid.Empty)) errors.Add("Product may not be blank"); if (items.Any(x => x.Location.ID == Guid.Empty)) errors.Add("Location may not be blank"); if (!errors.Any() && _action == MovementAction.Transfer) foreach (var item in items) { var changes = new List(); if (item.Location.HasOriginalValue(x => x.ID)) changes.Add(item.Location.GetOriginalValue(x => x.Code)); if (item.Job.HasOriginalValue(x => x.ID)) { var job = item.Job.GetOriginalValue(x => x.JobNumber); if (string.IsNullOrEmpty(job)) job = "General Stock"; changes.Add(job); } if (item.Style.HasOriginalValue(x => x.ID)) changes.Add(item.Style.GetOriginalValue(x => x.Code)); if (changes.Any()) item.Notes = string.Format("Transferred from {0}{1}{2}", string.Join(" / ", changes.Where(x => !string.IsNullOrWhiteSpace(x))), string.IsNullOrWhiteSpace(item.Notes) ? "" : "\n", item.Notes); else errors.Add("Transfers must change either Location, Style or Job"); } } private void StockMovementCustomiseEditor(IDynamicEditorForm sender, StockMovement[]? items, DynamicGridColumn column, BaseEditor editor) { if (column.ColumnName.Equals("Location.ID")) editor.Editable = _action == MovementAction.Transfer ? Editable.Enabled : Editable.Hidden; if (column.ColumnName.Equals("Product.ID")) editor.Editable = _action == MovementAction.Receive ? Editable.Enabled : Editable.Disabled; if (column.ColumnName.Equals("Product.NettCost")) editor.Editable = Editable.Hidden; if (column.ColumnName.Equals("Style.ID")) editor.Editable = _action == MovementAction.Receive || _action == MovementAction.Transfer ? Editable.Enabled : Editable.Hidden; if (column.ColumnName.Equals("UnitSize")) editor.Editable = _action == MovementAction.Receive ? Editable.Enabled : Editable.Hidden; if (column.ColumnName.Equals("Job.ID")) editor.Editable = _action == MovementAction.Receive && items?.FirstOrDefault()?.Job.IsValid() == true ? Editable.Disabled : Editable.Enabled; if (column.ColumnName.Equals(nameof(StockMovement.Received))) { editor.Editable = _action == MovementAction.Receive || _action == MovementAction.Transfer ? Editable.Enabled : Editable.Hidden; editor.Caption = "Quantity"; } if (column.ColumnName.Equals(nameof(StockMovement.Issued))) { editor.Editable = _action == MovementAction.Issue ? Editable.Enabled : Editable.Hidden; editor.Caption = "Quantity"; } } #endregion }