|
@@ -24,7 +24,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
|
|
|
private MovementAction _action;
|
|
|
private StockHolding? _holding;
|
|
|
|
|
|
- private readonly Guid _employeeid = Guid.Empty;
|
|
|
+
|
|
|
|
|
|
private Button IssueButton;
|
|
|
|
|
@@ -35,12 +35,14 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
|
|
|
//Button ReserveButton = null;
|
|
|
|
|
|
private Button TransferButton;
|
|
|
+
|
|
|
+ private Button RecalculateButton;
|
|
|
|
|
|
public StockHoldingGrid() : base()
|
|
|
{
|
|
|
ColumnsTag = "StockHolding";
|
|
|
|
|
|
- _employeeid = GetEmployeeID();
|
|
|
+
|
|
|
}
|
|
|
protected override void Init()
|
|
|
{
|
|
@@ -60,6 +62,9 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
|
|
|
TransferButton.Margin = new Thickness(20, TransferButton.Margin.Top, TransferButton.Margin.Right, TransferButton.Margin.Bottom);
|
|
|
TransferButton.IsEnabled = false;
|
|
|
|
|
|
+ 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);
|
|
@@ -84,11 +89,152 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
|
|
|
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.Value);
|
|
|
+ HiddenColumns.Add(x => x.Dimensions.UnitSize);
|
|
|
|
|
|
ActionColumns.Add(new DynamicMenuColumn(BuildMenu) { Position = DynamicActionColumnPosition.End });
|
|
|
}
|
|
|
|
|
|
+ private bool RecalculateHoldings(Button arg1, CoreRow[] arg2)
|
|
|
+ {
|
|
|
+ Dictionary<String, int> 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<StockHolding>(x => x.Location.ID).IsEqualTo(Location.ID),
|
|
|
+ new Columns<StockHolding>(x => x.ID)
|
|
|
+ .Add(x => x.Product.ID)
|
|
|
+ .Add(x => x.Job.ID)
|
|
|
+ .Add(x => x.Style.ID)
|
|
|
+ .Add(x => x.Dimensions.Unit.ID)
|
|
|
+ .Add(x => x.Dimensions.Length)
|
|
|
+ .Add(x => x.Dimensions.Width)
|
|
|
+ .Add(x => x.Dimensions.Height)
|
|
|
+ .Add(x => x.Dimensions.Quantity)
|
|
|
+ .Add(x => x.Dimensions.Value)
|
|
|
+ .Add(x => x.Dimensions.UnitSize)
|
|
|
+ .Add(x => x.Units)
|
|
|
+ .Add(x => x.AverageValue)
|
|
|
+ );
|
|
|
+
|
|
|
+ query.Add(
|
|
|
+ new Filter<StockMovement>(x => x.Location.ID).IsEqualTo(Location.ID),
|
|
|
+ new Columns<StockMovement>(x => x.ID)
|
|
|
+ .Add(x => x.Product.ID)
|
|
|
+ .Add(x => x.Job.ID)
|
|
|
+ .Add(x => x.Style.ID)
|
|
|
+ .Add(x => x.Dimensions.Unit.ID)
|
|
|
+ .Add(x => x.Dimensions.Length)
|
|
|
+ .Add(x => x.Dimensions.Width)
|
|
|
+ .Add(x => x.Dimensions.Height)
|
|
|
+ .Add(x => x.Dimensions.Quantity)
|
|
|
+ .Add(x => x.Dimensions.Value)
|
|
|
+ .Add(x => x.Dimensions.UnitSize)
|
|
|
+ .Add(x => x.Units)
|
|
|
+ .Add(x => x.Cost)
|
|
|
+ );
|
|
|
+ query.Query();
|
|
|
+ var holdings = query.Get<StockHolding>().ToObjects<StockHolding>().ToList();
|
|
|
+ var movements = query.Get<StockMovement>().ToObjects<StockMovement>().ToList();
|
|
|
+
|
|
|
+ progress.Report("Processing");
|
|
|
+ var updates = new List<StockHolding>();
|
|
|
+
|
|
|
+ while (movements.Any())
|
|
|
+ {
|
|
|
+ var first = movements.First();
|
|
|
+ var selected = movements.Where(x =>
|
|
|
+ x.Product.ID == first.Product.ID
|
|
|
+ && x.Job.ID == first.Job.ID
|
|
|
+ && x.Style.ID == first.Style.ID
|
|
|
+ && x.Dimensions.Unit.ID == first.Dimensions.Unit.ID
|
|
|
+ && x.Dimensions.Length.EqualsWithTolerance(first.Dimensions.Length)
|
|
|
+ && x.Dimensions.Width.EqualsWithTolerance(first.Dimensions.Width)
|
|
|
+ && x.Dimensions.Height.EqualsWithTolerance(first.Dimensions.Height)
|
|
|
+ && x.Dimensions.Quantity.EqualsWithTolerance(first.Dimensions.Quantity)
|
|
|
+ && x.Dimensions.Weight.EqualsWithTolerance(first.Dimensions.Weight)
|
|
|
+ && x.Dimensions.Value.EqualsWithTolerance(first.Dimensions.Value)
|
|
|
+ && String.Equals(x.Dimensions.UnitSize, first.Dimensions.UnitSize)
|
|
|
+ );
|
|
|
+ var units = selected.Aggregate(0.0d, (t, s) => t += s.Units);
|
|
|
+ var cost = selected.Aggregate(0.0d, (t, s) => t += (s.Units * s.Cost));
|
|
|
+
|
|
|
+ var holding = holdings.FirstOrDefault(x =>
|
|
|
+ x.Product.ID == first.Product.ID
|
|
|
+ && x.Job.ID == first.Job.ID
|
|
|
+ && x.Style.ID == first.Style.ID
|
|
|
+ && x.Dimensions.Unit.ID == first.Dimensions.Unit.ID
|
|
|
+ && x.Dimensions.Length.EqualsWithTolerance(first.Dimensions.Length)
|
|
|
+ && x.Dimensions.Width.EqualsWithTolerance(first.Dimensions.Width)
|
|
|
+ && x.Dimensions.Height.EqualsWithTolerance(first.Dimensions.Height)
|
|
|
+ && x.Dimensions.Quantity.EqualsWithTolerance(first.Dimensions.Quantity)
|
|
|
+ && x.Dimensions.Weight.EqualsWithTolerance(first.Dimensions.Weight)
|
|
|
+ && x.Dimensions.Length.EqualsWithTolerance(first.Dimensions.Length)
|
|
|
+ && String.Equals(x.Dimensions.UnitSize, first.Dimensions.UnitSize)
|
|
|
+ );
|
|
|
+ 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.Unit.ID = first.Dimensions.Unit.ID;
|
|
|
+ holding.Dimensions.Length = first.Dimensions.Length;
|
|
|
+ holding.Dimensions.Width = first.Dimensions.Width;
|
|
|
+ holding.Dimensions.Height = first.Dimensions.Height;
|
|
|
+ holding.Dimensions.Quantity = first.Dimensions.Quantity;
|
|
|
+ holding.Dimensions.Weight = first.Dimensions.Weight;
|
|
|
+ holding.Dimensions.Value = first.Dimensions.Value;
|
|
|
+ holding.Dimensions.UnitSize = first.Dimensions.UnitSize;
|
|
|
+ }
|
|
|
+ holding.Units = units;
|
|
|
+ holding.AverageValue = units.EqualsWithTolerance(0.0F) ? 0.0d : cost / units;
|
|
|
+
|
|
|
+ if (holdings.Contains(holding))
|
|
|
+ holdings.Remove(holding);
|
|
|
+
|
|
|
+ if (holding.IsChanged() && !holding.Units.EqualsWithTolerance(0.0f))
|
|
|
+ {
|
|
|
+ AddMessage(holding.ID != Guid.Empty ? "updated" : "added");
|
|
|
+ updates.Add(holding);
|
|
|
+ }
|
|
|
+
|
|
|
+ movements.RemoveAll(x => selected.Any(s => s.ID == x.ID));
|
|
|
+ }
|
|
|
+ foreach (var holding in holdings)
|
|
|
+ AddMessage("deleted");
|
|
|
+
|
|
|
+ if (updates.Any())
|
|
|
+ {
|
|
|
+ progress.Report($"Updating {updates.Count} Holdings");
|
|
|
+ new Client<StockHolding>().Save(updates.Where(x => x.IsChanged()), "Updated by Recalculation");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (holdings.Any())
|
|
|
+ {
|
|
|
+ progress.Report($"Deleting {holdings.Count} Holdings");
|
|
|
+ new Client<StockHolding>().Delete(holdings, "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<StockHolding>();
|
|
@@ -107,14 +253,139 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
|
|
|
protected override void DoReconfigure(FluentList<DynamicGridOption> options)
|
|
|
{
|
|
|
base.DoReconfigure(options);
|
|
|
- options.AddRange(DynamicGridOption.RecordCount, DynamicGridOption.SelectColumns, DynamicGridOption.FilterRows);
|
|
|
+ options
|
|
|
+ .BeginUpdate()
|
|
|
+ .Remove(DynamicGridOption.AddRows)
|
|
|
+ .Remove(DynamicGridOption.EditRows)
|
|
|
+ .Remove(DynamicGridOption.DeleteRows)
|
|
|
+ .Add(DynamicGridOption.RecordCount)
|
|
|
+ .Add(DynamicGridOption.SelectColumns)
|
|
|
+ .Add(DynamicGridOption.FilterRows)
|
|
|
+ .EndUpdate();
|
|
|
}
|
|
|
|
|
|
private void BuildMenu(DynamicMenuColumn column, CoreRow? row)
|
|
|
{
|
|
|
if (row is null) return;
|
|
|
+ var holding = row.ToObject<StockHolding>();
|
|
|
|
|
|
- column.AddItem("View Requisition Items", null, ViewRequisitions_Click);
|
|
|
+ if (holding.Available.EqualsWithTolerance(holding.Units))
|
|
|
+ column.AddItem("(No Requisitions in this Holding", null, null).IsEnabled = false;
|
|
|
+ else
|
|
|
+ column.AddItem("View Requisition Items", null, ViewRequisitions_Click);
|
|
|
+
|
|
|
+ column.AddSeparator();
|
|
|
+
|
|
|
+ var requiitems = new Client<JobRequisitionItem>()
|
|
|
+ .Query(
|
|
|
+ new Filter<JobRequisitionItem>(x => x.ID).InQuery(StockHolding.GetFilter(holding), x => x.JobRequisitionItem.ID),
|
|
|
+ new Columns<JobRequisitionItem>(x=>x.ID)
|
|
|
+ .Add(x=>x.Job.JobNumber)
|
|
|
+ .Add(x=>x.Requisition.Number)
|
|
|
+ .Add(x=>x.Requisition.Description)
|
|
|
+ .Add(x=>x.Qty)
|
|
|
+ ).ToObjects<JobRequisitionItem>()
|
|
|
+ .ToList();
|
|
|
+ if (!holding.Available.EqualsWithTolerance(0.0F))
|
|
|
+ requiitems.Insert(0, new JobRequisitionItem() { Qty = holding.Available });
|
|
|
+
|
|
|
+ if (requiitems.Count <= 1)
|
|
|
+ column.AddItem("Relocate Items", null, r => RelocateItems(holding, requiitems.ToArray()));
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var header = column.AddItem("Relocate Items", null, null);
|
|
|
+ column.AddItem("All Items", null, r => RelocateItems(holding, requiitems.ToArray()), header);
|
|
|
+ column.AddSeparator(header);
|
|
|
+
|
|
|
+ if (!holding.Available.EqualsWithTolerance(0.0F))
|
|
|
+ column.AddItem("Un-Requisitioned Items", null, r => RelocateItems(holding, requiitems.Where(x=>x.ID == Guid.Empty).ToArray()), header);
|
|
|
+
|
|
|
+ foreach (var requiitem in requiitems)
|
|
|
+ {
|
|
|
+ string name =
|
|
|
+ $"{requiitem.Job.JobNumber}:{requiitem.Requisition.Number} {requiitem.Requisition.Description} ({requiitem.Qty})";
|
|
|
+ column.AddItem(name, null, r => RelocateItems(holding, requiitems.Where(x=>x.ID == requiitem.ID).ToArray()), header);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private class StockLocationSelection : BaseObject
|
|
|
+ {
|
|
|
+ [EditorSequence(1)]
|
|
|
+ public StockLocationLink Location { get; set; }
|
|
|
+
|
|
|
+ [EditorSequence(2)]
|
|
|
+ public double Qty { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ private class StockJobSelection : BaseObject
|
|
|
+ {
|
|
|
+ [EditorSequence(1)]
|
|
|
+ public JobLink Job { get; set; }
|
|
|
+
|
|
|
+ [EditorSequence(2)]
|
|
|
+ public double Qty { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private void RelocateItems(StockHolding holding, JobRequisitionItem[] requiitems)
|
|
|
+ {
|
|
|
+ var max = requiitems.Aggregate(0.0d, (t, x) => t += x.Qty);
|
|
|
+ var grid = new DynamicItemsListGrid<StockLocationSelection>();
|
|
|
+ grid.OnValidate += (sender, items, errors) =>
|
|
|
+ {
|
|
|
+ var total = items.Aggregate(0.0d, (t, x) => t += x.Qty);
|
|
|
+ if (total > max)
|
|
|
+ errors.Add($"Qty must not exceed {max}");
|
|
|
+ };
|
|
|
+ grid.OnCustomiseEditor += (sender, items, column, editor) =>
|
|
|
+ {
|
|
|
+ if (column.ColumnName.Equals(nameof(StockLocationSelection.Qty)) && requiitems?.Length != 1)
|
|
|
+ editor.Editable = Editable.Disabled;
|
|
|
+ };
|
|
|
+ var selection = new StockLocationSelection() { Qty = max };
|
|
|
+ if (grid.EditItems(new StockLocationSelection[] { selection }))
|
|
|
+ {
|
|
|
+ List<StockMovement> updates = new List<StockMovement>();
|
|
|
+ foreach (var requiitem in requiitems)
|
|
|
+ {
|
|
|
+ var mout = new StockMovement();
|
|
|
+ mout.Location.ID = holding.Location.ID;
|
|
|
+ mout.Product.ID = holding.Product.ID;
|
|
|
+ mout.Style.ID = holding.Style.ID;
|
|
|
+ mout.Dimensions.CopyFrom(holding.Dimensions);
|
|
|
+ mout.Job.ID = holding.Job.ID;
|
|
|
+ mout.Issued = Math.Min(requiitem.Qty, selection.Qty);
|
|
|
+ mout.Cost = holding.AverageValue;
|
|
|
+ mout.JobRequisitionItem.ID = requiitem.ID;
|
|
|
+ mout.Transaction = Guid.NewGuid();
|
|
|
+ mout.Type = StockMovementType.TransferOut;
|
|
|
+ mout.Date = DateTime.Now;
|
|
|
+ mout.IsTransfer = true;
|
|
|
+ mout.Employee.ID = App.EmployeeID;
|
|
|
+ mout.Notes = $"Moved to {selection.Location.Code} by {App.EmployeeName}";
|
|
|
+ updates.Add(mout);
|
|
|
+
|
|
|
+ var min = new StockMovement();
|
|
|
+ min.Location.ID = selection.Location.ID;
|
|
|
+ min.Product.ID = holding.Product.ID;
|
|
|
+ min.Style.ID = holding.Style.ID;
|
|
|
+ min.Dimensions.CopyFrom(holding.Dimensions);
|
|
|
+ min.Job.ID = holding.Job.ID;
|
|
|
+ min.Received = mout.Issued;
|
|
|
+ min.Cost = holding.AverageValue;
|
|
|
+ min.JobRequisitionItem.ID = requiitem.ID;
|
|
|
+ min.Transaction = mout.Transaction;
|
|
|
+ min.Type = StockMovementType.TransferIn;
|
|
|
+ min.Date = mout.Date;
|
|
|
+ min.IsTransfer = true;
|
|
|
+ min.Employee.ID = App.EmployeeID;
|
|
|
+ min.Notes = $"Moved From {holding.Location.Code} by {App.EmployeeName}";
|
|
|
+ updates.Add(min);
|
|
|
+ }
|
|
|
+ new Client<StockMovement>().Save(updates, "Relocated from Stock Locations Screen");
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
private void ViewRequisitions_Click(CoreRow? row)
|
|
@@ -165,16 +436,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
|
|
|
}
|
|
|
return smg;
|
|
|
}
|
|
|
-
|
|
|
- private Guid GetEmployeeID()
|
|
|
- {
|
|
|
- var employee = new Client<Employee>().Query(
|
|
|
- new Filter<Employee>(x => x.UserLink.ID).IsEqualTo(ClientFactory.UserGuid),
|
|
|
- new Columns<Employee>(x => x.ID)
|
|
|
- );
|
|
|
- return employee.Rows.Any() ? employee.Rows.First().Get<Employee, Guid>(x => x.ID) : Guid.Empty;
|
|
|
- }
|
|
|
-
|
|
|
+
|
|
|
private Dictionary<string, object?> StockMovementValueChanged(IDynamicEditorForm form, string name, object value)
|
|
|
{
|
|
|
var result = new Dictionary<string, object?>();
|
|
@@ -192,8 +454,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
private void StockMovementValidate(object sender, StockMovement[] items, List<string> errors)
|
|
|
{
|
|
|
if (items.Any(x => x.Received == 0 && x.Issued == 0))
|
|
@@ -241,39 +502,33 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
|
|
|
{
|
|
|
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("Received"))
|
|
|
- {
|
|
|
- editor.Editable = _action == MovementAction.Receive || _action == MovementAction.Transfer ? Editable.Enabled : Editable.Hidden;
|
|
|
- editor.Caption = "Quantity";
|
|
|
- }
|
|
|
-
|
|
|
- if (column.ColumnName.Equals("Product.ID"))
|
|
|
- editor.Editable = _action == MovementAction.Receive ? 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("Issued"))
|
|
|
+
|
|
|
+ if (column.ColumnName.Equals(nameof(StockMovement.Received)))
|
|
|
{
|
|
|
- editor.Editable = _action == MovementAction.Issue ? Editable.Enabled : Editable.Hidden;
|
|
|
+ editor.Editable = _action == MovementAction.Receive ? Editable.Enabled : Editable.Hidden;
|
|
|
editor.Caption = "Quantity";
|
|
|
}
|
|
|
-
|
|
|
- if (column.ColumnName.Equals("Employee.ID"))
|
|
|
- editor.Editable = _action == MovementAction.Transfer ? Editable.Hidden : Editable.Enabled;
|
|
|
-
|
|
|
- if (column.ColumnName.Equals("Units"))
|
|
|
- editor.Editable = _action == MovementAction.Receive ? Editable.Enabled : Editable.Hidden;
|
|
|
+ if (column.ColumnName.Equals(nameof(StockMovement.Issued)))
|
|
|
+ {
|
|
|
+ editor.Editable = _action == MovementAction.Issue || _action == MovementAction.Transfer ? Editable.Enabled : Editable.Hidden;
|
|
|
+ editor.Caption = "Quantity";
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
private bool ReceiveStock(Button arg1, CoreRow[] rows)
|
|
@@ -287,7 +542,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
|
|
|
movement.Location.Description = Location.Description;
|
|
|
movement.Date = DateTime.Now;
|
|
|
movement.IsTransfer = false;
|
|
|
- movement.Employee.ID = _employeeid;
|
|
|
+ movement.Employee.ID = App.EmployeeID;
|
|
|
movement.Type = StockMovementType.Receive;
|
|
|
|
|
|
movement.CommitChanges();
|
|
@@ -310,7 +565,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
|
|
|
var batch = new StockMovementBatch();
|
|
|
batch.Type = type;
|
|
|
batch.Notes = batch.Type + " batch created from Desktop Stock Location Screen";
|
|
|
- batch.Employee.ID = _employeeid;
|
|
|
+ batch.Employee.ID = App.EmployeeID;
|
|
|
new Client<StockMovementBatch>().Save(batch, "created from Desktop Stock Location Screen");
|
|
|
|
|
|
foreach (var mvt in movements)
|
|
@@ -323,140 +578,183 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
|
|
|
|
|
|
private bool IssueStock(Button arg1, CoreRow[] rows)
|
|
|
{
|
|
|
- if (!rows.Any())
|
|
|
+ if (rows?.Length != 1)
|
|
|
{
|
|
|
MessageWindow.ShowMessage("Please select an item to issue", "No selected items");
|
|
|
return false;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
var holding = rows.First().ToObject<StockHolding>();
|
|
|
+ SelectAllocation(holding, (h, items) => DoIssue(h, items));
|
|
|
+ return false;
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
- if(holding.Available <= 0)
|
|
|
- {
|
|
|
- MessageWindow.ShowMessage("There is no available stock in this holding to issue.", "No available stock");
|
|
|
- return false;
|
|
|
- }
|
|
|
+ private void DoIssue(StockHolding holding, JobRequisitionItem[] requiitems)
|
|
|
+ {
|
|
|
|
|
|
- // var jobid = rows.First().Get<StockHolding, Guid>(x => x.Job.ID);
|
|
|
- // var jobno = rows.First().Get<StockHolding, string>(x => x.Job.JobNumber);
|
|
|
- // var productid = rows.First().Get<StockHolding, Guid>(x => x.Product.ID);
|
|
|
- // var styleid = rows.First().Get<StockHolding, Guid>(x => x.Style.ID);
|
|
|
- // var stylecode = rows.First().Get<StockHolding, string>(x => x.Style.Code);
|
|
|
- // var size = rows.First().Get<StockHolding, double>(x => x.UnitSize);
|
|
|
+ var updates = new List<StockMovement>();
|
|
|
+ // At this point we either have
|
|
|
+ // 1. All Items (Multiple requiitems) -> no editing available
|
|
|
+ // 2. UnRequid Items (single requiitem, requiid = empty) - Full Editor
|
|
|
+ // 3. Requi'd Item (single requiitem, requiid != empty - Quantity only
|
|
|
|
|
|
- var movement = new StockMovement
|
|
|
+ if (requiitems.Length > 1)
|
|
|
{
|
|
|
- Date = DateTime.Now
|
|
|
+ if (MessageWindow.ShowOKCancel(
|
|
|
+ "This will issue everything from this holding!\nAre you sure you wish to continue?",
|
|
|
+ "Confirm Issue", null) == true)
|
|
|
+ {
|
|
|
+ foreach (var requiitem in requiitems)
|
|
|
+ {
|
|
|
+ var movement = CreateMovementFromHolding(holding);
|
|
|
+ movement.Type = StockMovementType.Issue;
|
|
|
+ movement.JobRequisitionItem.ID = requiitem.ID;
|
|
|
+ movement.Issued = requiitem.Qty;
|
|
|
+ movement.Transaction = Guid.NewGuid();
|
|
|
+ movement.Notes = $"Issued by {App.EmployeeName}";
|
|
|
+ updates.Add(movement);
|
|
|
+ }
|
|
|
+ SaveBatch(StockMovementBatchType.Issue, updates.ToArray());
|
|
|
+ DoChanged();
|
|
|
+ Refresh(false,true);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var sjs = new StockJobSelection();
|
|
|
+ sjs.Job.ID = holding.Job.ID;
|
|
|
+ sjs.Qty = requiitems[0].Qty;
|
|
|
+
|
|
|
+ var sjg = new DynamicItemsListGrid<StockJobSelection>();
|
|
|
+ sjg.OnValidate += (sender, items, errors) =>
|
|
|
+ {
|
|
|
+ if (items[0].Qty > requiitems[0].Qty)
|
|
|
+ errors.Add($"Qty must not exceed {requiitems[0].Qty}");
|
|
|
};
|
|
|
- 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.Dimensions.CopyFrom(holding.Dimensions);
|
|
|
- movement.IsTransfer = false;
|
|
|
- movement.Employee.ID = _employeeid;
|
|
|
- movement.Cost = holding.AverageValue;
|
|
|
- movement.Type = StockMovementType.Issue;
|
|
|
-
|
|
|
- movement.CommitChanges();
|
|
|
-
|
|
|
- var smg = CheckStockMovementGrid(MovementAction.Issue, holding);
|
|
|
- var result = smg.EditItems(new[] { movement });
|
|
|
-
|
|
|
- var mvts = new List<StockMovement>
|
|
|
+ sjg.OnCustomiseEditor += (sender, items, column, editor) =>
|
|
|
{
|
|
|
- movement
|
|
|
+ if (column.ColumnName.Equals(CoreUtils.GetFullPropertyName<StockJobSelection, Guid>(x => x.Job.ID, ".")))
|
|
|
+ editor.Editable = requiitems[0].ID == Guid.Empty
|
|
|
+ ? Editable.Enabled
|
|
|
+ : Editable.Disabled;
|
|
|
};
|
|
|
-
|
|
|
- // Issuing to a different job than you received into?
|
|
|
- if (result && holding.Job.ID != movement.Job.ID)
|
|
|
+ if (sjg.EditItems(new StockJobSelection[] { sjs }))
|
|
|
{
|
|
|
- // Issue from Old Job (Hidden)
|
|
|
- var issue = new StockMovement
|
|
|
+ var issue = CreateMovementFromHolding(holding);
|
|
|
+ issue.Job.ID = sjs.Job.ID;
|
|
|
+ issue.Type = StockMovementType.Issue;
|
|
|
+ issue.JobRequisitionItem.ID = requiitems[0].ID;
|
|
|
+ issue.Issued = sjs.Qty;
|
|
|
+ issue.Transaction = Guid.NewGuid();
|
|
|
+ issue.Notes = $"Issued by {App.EmployeeName}";
|
|
|
+ updates.Add(issue);
|
|
|
+ if (holding.Job.ID != issue.Job.ID)
|
|
|
{
|
|
|
- Date = movement.Date
|
|
|
- };
|
|
|
- issue.Product.ID = holding.Product.ID;
|
|
|
- issue.Job.ID = holding.Job.ID;
|
|
|
- issue.Location.ID = Location.ID;
|
|
|
- issue.Style.ID = holding.Style.ID;
|
|
|
- issue.Issued = movement.Issued;
|
|
|
- issue.Transaction = movement.Transaction;
|
|
|
- issue.Employee.ID = movement.Employee.ID;
|
|
|
- issue.Dimensions.CopyFrom(holding.Dimensions);
|
|
|
- issue.IsTransfer = true;
|
|
|
- issue.Notes = string.Format("Transferred from {0}", holding.Job.ID.Equals(Guid.Empty) ? "General Stock" : "Job " + holding.Job.JobNumber);
|
|
|
- issue.System = true;
|
|
|
- issue.Cost = holding.AverageValue;
|
|
|
- issue.Type = StockMovementType.TransferOut;
|
|
|
-
|
|
|
- // Receive to New Job (Hidden)
|
|
|
- var receive = new StockMovement();
|
|
|
- receive.Date = movement.Date;
|
|
|
- receive.Product.ID = holding.Product.ID;
|
|
|
- receive.Job.ID = movement.Job.ID;
|
|
|
- receive.Location.ID = Location.ID;
|
|
|
- receive.Style.ID = holding.Style.ID;
|
|
|
- receive.Received = movement.Issued;
|
|
|
- receive.Transaction = movement.Transaction;
|
|
|
- receive.Employee.ID = movement.Employee.ID;
|
|
|
- receive.Dimensions.CopyFrom(holding.Dimensions);
|
|
|
- receive.IsTransfer = true;
|
|
|
- receive.Notes = string.Format("Transferred to {0}",
|
|
|
- !movement.Job.IsValid() ? "General Stock" : "Job " + movement.Job.JobNumber);
|
|
|
- receive.System = true;
|
|
|
- receive.Cost = holding.AverageValue;
|
|
|
- receive.Type = StockMovementType.TransferIn;
|
|
|
-
|
|
|
- new Client<StockMovement>().Save(new[] { issue, receive }, "");
|
|
|
- mvts.Add(issue);
|
|
|
- mvts.Add(receive);
|
|
|
+ var xferout = CreateMovementFromHolding(holding);
|
|
|
+ xferout.Type = StockMovementType.TransferOut;
|
|
|
+ xferout.JobRequisitionItem.ID = requiitems[0].ID;
|
|
|
+ xferout.Issued = sjs.Qty;
|
|
|
+ xferout.Transaction = issue.Transaction;
|
|
|
+ xferout.IsTransfer = true;
|
|
|
+ xferout.Notes = $"Issued by {App.EmployeeName}";
|
|
|
+ updates.Add(xferout);
|
|
|
+
|
|
|
+ var xferin = CreateMovementFromHolding(holding);
|
|
|
+ xferin.Job.ID = sjs.Job.ID;
|
|
|
+ xferin.Type = StockMovementType.TransferOut;
|
|
|
+ xferin.JobRequisitionItem.ID = requiitems[0].ID;
|
|
|
+ xferin.Received = sjs.Qty;
|
|
|
+ xferin.Transaction = issue.Transaction;
|
|
|
+ xferin.IsTransfer = true;
|
|
|
+ xferin.Notes = $"Issued by {App.EmployeeName}";
|
|
|
+ updates.Add(xferin);
|
|
|
+
|
|
|
+ }
|
|
|
+ SaveBatch(StockMovementBatchType.Issue, updates.ToArray());
|
|
|
+ DoChanged();
|
|
|
+ Refresh(false,true);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if (result)
|
|
|
+ private void SelectAllocation(StockHolding holding, Action<StockHolding,JobRequisitionItem[]> action)
|
|
|
+ {
|
|
|
+
|
|
|
+ var requiitems = new Client<JobRequisitionItem>()
|
|
|
+ .Query(
|
|
|
+ new Filter<JobRequisitionItem>(x => x.ID).InQuery(StockHolding.GetFilter(holding), x => x.JobRequisitionItem.ID),
|
|
|
+ new Columns<JobRequisitionItem>(x=>x.ID)
|
|
|
+ .Add(x=>x.Job.JobNumber)
|
|
|
+ .Add(x=>x.Requisition.Number)
|
|
|
+ .Add(x=>x.Requisition.Description)
|
|
|
+ .Add(x=>x.Qty)
|
|
|
+ ).ToObjects<JobRequisitionItem>()
|
|
|
+ .ToList();
|
|
|
+ if (!holding.Available.EqualsWithTolerance(0.0F))
|
|
|
+ requiitems.Insert(0, new JobRequisitionItem() { Qty = holding.Available });
|
|
|
+
|
|
|
+ if (requiitems.Count <= 1)
|
|
|
+ action(holding, requiitems.ToArray());
|
|
|
+ else
|
|
|
{
|
|
|
- DoChanged();
|
|
|
- SaveBatch(StockMovementBatchType.Issue, mvts.ToArray());
|
|
|
+
|
|
|
+ ContextMenu menu = new ContextMenu();
|
|
|
+
|
|
|
+ MenuItem all = new MenuItem();
|
|
|
+ all.Header =
|
|
|
+ $"All Items ({holding.Units})";
|
|
|
+ all.Click += (sender, args) => action(holding,requiitems.ToArray());
|
|
|
+ menu.Items.Add(all);
|
|
|
+ menu.Items.Add(new Separator());
|
|
|
+
|
|
|
+ if (!holding.Available.EqualsWithTolerance(0.0F))
|
|
|
+ {
|
|
|
+ MenuItem item = new MenuItem();
|
|
|
+ item.Header =
|
|
|
+ $"Un-Requisitioned Items ({holding.Available})";
|
|
|
+ item.Click += (sender, args) =>
|
|
|
+ action(holding, requiitems.Where(x => x.ID == Guid.Empty).ToArray());
|
|
|
+ menu.Items.Add(item);
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var requiitem in requiitems)
|
|
|
+ {
|
|
|
+ MenuItem item = new MenuItem();
|
|
|
+ item.Header =
|
|
|
+ $"{requiitem.Job.JobNumber}:{requiitem.Requisition.Number} {requiitem.Requisition.Description} ({requiitem.Qty})";
|
|
|
+ item.Click += (sender, args) => action(holding, requiitems.Where(x=>x.ID == requiitem.ID).ToArray());
|
|
|
+ menu.Items.Add(item);
|
|
|
+
|
|
|
+ }
|
|
|
+ menu.IsOpen = true;
|
|
|
}
|
|
|
- return result;
|
|
|
+
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
private bool TransferStock(Button arg1, CoreRow[] rows)
|
|
|
{
|
|
|
+ if (rows?.Length != 1)
|
|
|
+ return false;
|
|
|
+
|
|
|
var holding = rows.First().ToObject<StockHolding>();
|
|
|
+ SelectAllocation(holding, (h,items) => DoTransfer(h, items) );
|
|
|
+ return false;
|
|
|
+ }
|
|
|
|
|
|
- if(holding.Available <= 0)
|
|
|
+ private void DoTransfer(StockHolding holding, JobRequisitionItem[] requiitems)
|
|
|
+ {
|
|
|
+ if (requiitems.Length > 1 || requiitems[0].Requisition.ID != Guid.Empty)
|
|
|
{
|
|
|
- MessageWindow.ShowMessage("There is no available stock in this holding to transfer.", "No available stock");
|
|
|
- return false;
|
|
|
+ RelocateItems(holding, requiitems);
|
|
|
+ return;
|
|
|
}
|
|
|
-
|
|
|
- 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 = _employeeid;
|
|
|
- // Must happen before Received gets set so that OnPropertyChanged has stuff to work with and thus Qty is not zero.
|
|
|
- movement.Dimensions.CopyFrom(holding.Dimensions);
|
|
|
+
|
|
|
+ var movement = CreateMovementFromHolding(holding);
|
|
|
+ movement.JobRequisitionItem.ID = requiitems[0].ID;
|
|
|
movement.Received = holding.Units;
|
|
|
movement.IsTransfer = true;
|
|
|
- movement.Cost = holding.AverageValue;
|
|
|
movement.Type = StockMovementType.TransferIn;
|
|
|
|
|
|
- movement.CommitChanges();
|
|
|
-
|
|
|
var smg = CheckStockMovementGrid(MovementAction.Transfer, holding);
|
|
|
var result = smg.EditItems(new[] { movement });
|
|
|
|
|
@@ -467,23 +765,12 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
|
|
|
|
|
|
if (result)
|
|
|
{
|
|
|
- var other = new StockMovement();
|
|
|
- other.Date = movement.Date;
|
|
|
- other.Product.ID = holding.Product.ID;
|
|
|
- other.Job.ID = holding.Job.ID;
|
|
|
- other.Cost = holding.AverageValue;
|
|
|
-
|
|
|
- other.Location.ID = Location.ID;
|
|
|
- //other.Location.Code = Location.Code;
|
|
|
- //other.Location.Description = Location.Description;
|
|
|
-
|
|
|
- other.Style.ID = holding.Style.ID;
|
|
|
+ var other = CreateMovementFromHolding(holding);
|
|
|
other.Issued = movement.Received;
|
|
|
other.Transaction = movement.Transaction;
|
|
|
- other.Employee.ID = movement.Employee.ID;
|
|
|
- other.Dimensions.CopyFrom(holding.Dimensions);
|
|
|
other.IsTransfer = true;
|
|
|
other.Type = StockMovementType.TransferOut;
|
|
|
+ movement.JobRequisitionItem.ID = requiitems[0].ID;
|
|
|
|
|
|
var changes = new List<string>();
|
|
|
if (movement.Location.ID != other.Location.ID)
|
|
@@ -511,7 +798,26 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
|
|
|
DoChanged();
|
|
|
SaveBatch(StockMovementBatchType.Transfer, mvts.ToArray());
|
|
|
}
|
|
|
- return result;
|
|
|
+ Refresh(true,false);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
}
|
|
|
|
|
|
protected override void Reload(Filters<StockHolding> criteria, Columns<StockHolding> columns, ref SortOrder<StockHolding>? sort, Action<CoreTable?, Exception?> action)
|
|
@@ -523,13 +829,5 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
|
|
|
criteria.Add(new Filter<StockHolding>(x => x.Location.ID).IsEqualTo(Location.ID));
|
|
|
base.Reload(criteria, columns, ref sort, action);
|
|
|
}
|
|
|
-
|
|
|
- protected override bool FilterRecord(CoreRow row)
|
|
|
- {
|
|
|
- // Hackety Hackety Hack Hack stupid doubles not totalling zero when they're supposed to
|
|
|
- var result = base.FilterRecord(row);
|
|
|
- if (result)
|
|
|
- result = Math.Abs(row.Get<StockHolding, double>(x => x.Qty)) >= 0.000001;
|
|
|
- return result;
|
|
|
- }
|
|
|
+
|
|
|
}
|