using Comal.Classes; using InABox.Clients; using InABox.Configuration; using InABox.Core; using InABox.DynamicGrid; using InABox.WPF; using Microsoft.Win32; using NPOI.SS.Formula.Functions; using NPOI.SS.UserModel; using NPOI.SS.Util; using NPOI.XSSF.UserModel; using org.omg.PortableInterceptor; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; namespace PRSDesktop { /// /// Interaction logic for StockTakeWindow.xaml /// public partial class StockTakeWindow : Window { #region Fields / constructor CoreRow[] Rows; List StockTakeHoldings = new List(); List OriginalHoldings = new List(); Guid EmployeeID = Guid.Empty; public StockTakeWindow(CoreRow[] rows) { InitializeComponent(); Rows = rows; Setup(); } #endregion #region Setup private void Setup() { SetupStockHoldingGrid(); Progress.ShowModal("Loading", (progress) => { EmployeeID = new Client().Query(new Filter(x => x.UserLink.ID).IsEqualTo(ClientFactory.UserGuid)).Rows.FirstOrDefault().Get(x => x.ID); QueryList(Rows.Select(x => x.Get(c => c.ID))); Dispatcher.Invoke(() => { Holdings.Items = StockTakeHoldings; Holdings.Refresh(true, true); }); }); } private void SetupStockHoldingGrid() { Holdings.Reconfigure(options => { options.Remove(DynamicGridOption.EditRows); options.Remove(DynamicGridOption.DeleteRows); options.Remove(DynamicGridOption.ImportData); options.Remove(DynamicGridOption.ExportData); options.Remove(DynamicGridOption.SelectColumns); options.Add(DynamicGridOption.RecordCount); options.Add(DynamicGridOption.AddRows); options.Add(DynamicGridOption.DirectEdit); }); Holdings.OnCustomiseEditor += Page_OnCustomiseEditor; Holdings.OnValidate += Page_OnValidate; Holdings.OnBeforeSave += Page_OnSave; Holdings.AddButton("Pre-Fill Actual Qty", PRSDesktop.Resources.copy.AsBitmapImage(), FillValues); Holdings.AddButton("Reset Actual Qty to Zero", PRSDesktop.Resources.refresh.AsBitmapImage(), ClearValues); Holdings.AddButton("Export", PRSDesktop.Resources.doc_xls.AsBitmapImage(), Export); } private void QueryList(IEnumerable ids) { CoreTable table = new Client().Query(new Filter(x => x.Location.ID).InList(ids.ToArray()) .And(x => x.Dimensions.UnitSize).IsNotEqualTo(null) .And(x => x.Qty).IsGreaterThan(0.1), new Columns( x => x.ID, x => x.Qty, x => x.Units, x => x.Job.ID, x => x.Job.JobNumber, x => x.Product.ID, x => x.Product.Code, x => x.Product.Name, x => x.Style.ID, x => x.Style.Code, x => x.Style.Description, x => x.Location.ID, x => x.Location.Code, x => x.Location.Description, x => x.Dimensions.Unit.ID, x => x.Dimensions.UnitSize, x => x.Dimensions.Unit.Format, x => x.Dimensions.Unit.Formula, x => x.Dimensions.Unit.HasLength, x => x.Dimensions.Unit.HasHeight, x => x.Dimensions.Unit.HasWeight, x => x.Dimensions.Unit.HasWidth, x => x.Dimensions.Unit.HasQuantity, x => x.Dimensions.Quantity, x => x.Dimensions.Length, x => x.Dimensions.Height, x => x.Dimensions.Width, x => x.Dimensions.Weight )); foreach (CoreRow row in table.Rows) { StockHolding holding = row.ToObject(); OriginalHoldings.Add(holding); StockTakeHolding displayHolding = new StockTakeHolding(); displayHolding.ID = holding.ID; displayHolding.OriginalQty = holding.Units; displayHolding.Job.ID = holding.Job.ID; displayHolding.Job.JobNumber = holding.Job.JobNumber; displayHolding.Product.ID = holding.Product.ID; displayHolding.Product.Code = holding.Product.Code; displayHolding.Product.Name = holding.Product.Name; displayHolding.Style.ID = holding.Style.ID; displayHolding.Style.Code = holding.Style.Code; displayHolding.Style.Description = holding.Style.Description; displayHolding.Location.ID = holding.Location.ID; displayHolding.Location.Code = holding.Location.Code; displayHolding.Location.Description = holding.Location.Description; displayHolding = CopyDimensions(displayHolding, holding); StockTakeHoldings.Add(displayHolding); } } private StockTakeHolding CopyDimensions(StockTakeHolding display, StockHolding holding) { display.Dimensions.Unit.ID = holding.Dimensions.Unit.ID; display.Dimensions.Unit.HasQuantity = holding.Dimensions.Unit.HasQuantity; display.Dimensions.Unit.HasLength = holding.Dimensions.Unit.HasLength; display.Dimensions.Unit.HasHeight = holding.Dimensions.Unit.HasHeight; display.Dimensions.Unit.HasWeight = holding.Dimensions.Unit.HasWeight; display.Dimensions.Unit.HasWidth = holding.Dimensions.Unit.HasWidth; display.Dimensions.Quantity = holding.Dimensions.Quantity; display.Dimensions.Length = holding.Dimensions.Length; display.Dimensions.Height = holding.Dimensions.Height; display.Dimensions.Weight = holding.Dimensions.Weight; display.Dimensions.Width = holding.Dimensions.Width; display.Dimensions.Unit.Format = holding.Dimensions.Unit.Format; display.Dimensions.Unit.Formula = holding.Dimensions.Unit.Formula; return display; } #region Editor Setup private void Page_OnSave(IDynamicEditorForm editor, BaseObject[] items) { foreach (StockTakeHolding item in items.Cast()) { item.UpdateDimensions(); } } private void Page_OnValidate(object sender, StockTakeHolding[] items, List errors) { var bQty = false; var bProd = false; var bLocn = false; foreach (var item in items) { bQty = bQty || item.NewQty <= 0.0001F; bProd = bProd || !item.Product.IsValid(); bLocn = bLocn || !item.Location.IsValid(); } if (bQty) errors.Add("Quantity may not be zero"); if (bProd) errors.Add("Product may not be blank"); if (bLocn) errors.Add("Location may not be blank"); } private void Page_OnCustomiseEditor(IDynamicEditorForm sender, StockTakeHolding[]? items, DynamicGridColumn column, BaseEditor editor) { if (column.ColumnName.Equals("OriginalQty")) editor.Editable = Editable.Hidden; if (column.ColumnName.Equals("NewQty")) editor.Caption = "Qty"; if (column.ColumnName.Equals("Dimensions.Quantity")) editor.Editable = Editable.Hidden; if (column.ColumnName.Equals("Dimensions.Length")) editor.Editable = Editable.Hidden; if (column.ColumnName.Equals("Dimensions.Weight")) editor.Editable = Editable.Hidden; if (column.ColumnName.Equals("Dimensions.Height")) editor.Editable = Editable.Hidden; if (column.ColumnName.Equals("Dimensions.Width")) editor.Editable = Editable.Hidden; } #endregion #region Added Buttons private bool ClearValues(Button arg1, CoreRow[] arg2) { var result = MessageBox.Show("This will clear all new quantities entered. Proceed?", "Warning", MessageBoxButton.YesNo); if (result != MessageBoxResult.Yes) return false; foreach (var holding in Holdings.Items) holding.ClearQty(); return true; } private bool FillValues(Button arg1, CoreRow[] rows) { var result = MessageBox.Show("This will prefill all new quantities with the original quantity. Proceed?", "Warning", MessageBoxButton.YesNo); if (result != MessageBoxResult.Yes) return false; foreach (var holding in Holdings.Items) holding.PreFillQty(); return true; } private void Cancel_Button_Click(object sender, RoutedEventArgs e) { bool changes = false; foreach (var holding in StockTakeHoldings) { if (holding.NewQty != 0) changes = true; } if (changes) { var result = MessageBox.Show("Warning - any unsaved changes will be lost. Continue?", "Warning", MessageBoxButton.YesNo); if (result == MessageBoxResult.Yes) Close(); } else Close(); } private bool Export(Button arg1, CoreRow[] arg2) { var result = AddColumns(); result.LoadRows(Holdings.Items); ExcelExporter.DoExport(result, "Stock Take " + DateTime.Now.ToString("HH:mm dd MMM yy")); return true; } private CoreTable AddColumns() { var result = new CoreTable(); result.Columns.Add( new CoreColumn() { ColumnName = "Location.Code", DataType = typeof(string) }); result.Columns.Add( new CoreColumn() { ColumnName = "Product.Code", DataType = typeof(string) }); result.Columns.Add( new CoreColumn() { ColumnName = "Product.Name", DataType = typeof(string) }); result.Columns.Add( new CoreColumn() { ColumnName = "Dimensions.UnitSize", DataType = typeof(string) }); result.Columns.Add( new CoreColumn() { ColumnName = "Job.JobNumber", DataType = typeof(string) }); result.Columns.Add( new CoreColumn() { ColumnName = "Job.Name", DataType = typeof(string) }); result.Columns.Add( new CoreColumn() { ColumnName = "Style.Description", DataType = typeof(string) }); result.Columns.Add( new CoreColumn() { ColumnName = "OriginalQty", DataType = typeof(double) }); result.Columns.Add( new CoreColumn() { ColumnName = "NewQty", DataType = typeof(double) }); return result; } #endregion #endregion #region Saving Stocktake #region Button Press / Validate private void Ok_Button_Click(object sender, RoutedEventArgs e) { var result = MessageBox.Show("This will now generate stock movements for each line, whether the quantity has changed or not. Proceed?", "Information", MessageBoxButton.YesNo); if (result != MessageBoxResult.Yes) return; if (!ValidateQty()) return; CreateMovements(); MessageBox.Show("Success - Stocktake complete"); Close(); } private bool ValidateQty() { bool zerosPresent = false; foreach (var holding in StockTakeHoldings) { if (holding.NewQty < 0) { MessageBox.Show("Negative quantities are not allowed!"); return false; } if (holding.NewQty < 0.001) zerosPresent = true; } if (zerosPresent) { var result = MessageBox.Show("One or more rows have been left as Zero. This will create an issuing stock movement of any existing stock for that row. Proceed?", "Warning", MessageBoxButton.YesNo); if (result != MessageBoxResult.Yes) return false; } return true; } #endregion #region Stock Movements private void CreateMovements() { var batch = CreateBatch(); var movements = CompareHoldingsAndCreateMovements(batch.ID); new Client().Save(movements, "Created on Desktop Stocktake"); } private StockMovementBatch CreateBatch() { StockMovementBatch batch = new StockMovementBatch(); batch.Notes = "Stocktake"; batch.Employee.ID = EmployeeID; batch.Type = StockMovementBatchType.Stocktake; new Client().Save(batch, "Created on Desktop Stocktake"); return batch; } private List CompareHoldingsAndCreateMovements(Guid batchID) { List movements = new List(); foreach (var holding in StockTakeHoldings) { StockHolding original = OriginalHoldings.Find(x => x.ID == holding.ID); if (holding.NewQty < 0.001 && holding.NewQty > 0) holding.NewQty = 0; if (original != null) movements.Add(CreateMovement(holding, holding.NewQty - holding.OriginalQty, batchID)); else movements.Add(CreateMovement(holding, holding.NewQty, batchID)); } return movements; } private StockMovement CreateMovement(StockTakeHolding holding, double qty, Guid batchID) { var movement = CreateBaseMovement(holding, batchID); return DetermineMovementType(movement, qty); } private StockMovement CreateBaseMovement(StockTakeHolding holding, Guid batchID) { var movement = new StockMovement(); movement.Batch.ID = batchID; movement.IsTransfer = false; movement.Issued = 0; movement.Received = 0; movement.Product.ID = holding.Product.ID; movement.Job.ID = holding.Job.ID; movement.Style.ID = holding.Style.ID; movement.Employee.ID = EmployeeID; movement.Notes = holding.Notes; movement = CopyDimensions(movement, holding); movement.Date = DateTime.Now; movement.Location.ID = holding.Location.ID; movement.IsRemnant = false; movement.Type = StockMovementType.StockTake; return movement; } private static StockMovement CopyDimensions(StockMovement movement, StockTakeHolding holding) { movement.Dimensions.Unit.ID = holding.Dimensions.Unit.ID; movement.Dimensions.Unit.HasQuantity = holding.Dimensions.Unit.HasQuantity; movement.Dimensions.Unit.HasLength = holding.Dimensions.Unit.HasLength; movement.Dimensions.Unit.HasHeight = holding.Dimensions.Unit.HasHeight; movement.Dimensions.Unit.HasWeight = holding.Dimensions.Unit.HasWeight; movement.Dimensions.Unit.HasWidth = holding.Dimensions.Unit.HasWidth; movement.Dimensions.Quantity = holding.Dimensions.Quantity; movement.Dimensions.Length = holding.Dimensions.Length; movement.Dimensions.Height = holding.Dimensions.Height; movement.Dimensions.Weight = holding.Dimensions.Weight; movement.Dimensions.Width = holding.Dimensions.Width; movement.Dimensions.Unit.Format = holding.Dimensions.Unit.Format; movement.Dimensions.Unit.Formula = holding.Dimensions.Unit.Formula; return movement; } private static StockMovement DetermineMovementType(StockMovement movement, double qty) { if (qty < 0) { movement.Issued = 0 - qty; } if (qty > 0) { movement.Received = qty; } if (movement.Issued != 0 || movement.Received != 0) movement.Notes = string.IsNullOrWhiteSpace(movement.Notes) ? "Updated Qty on Stocktake" : movement.Notes + ". Updated Qty on Stocktake"; else movement.Notes = "Correct Qty during Stocktake"; return movement; } #endregion #endregion } #region Intermediate Class public class StockTakeHolding : BaseObject { [DoubleEditor(Editable = Editable.Disabled)] public double OriginalQty { get; set; } = 0; [DoubleEditor(Editable = Editable.Enabled)] public double NewQty { get; set; } = 0.00000000000000000000001; [NullEditor] public Guid ID { get; set; } = Guid.Empty; public ProductLink Product { get; set; } public ProductStyleLink Style { get; set; } public StockLocationLink Location { get; set; } public JobLink Job { get; set; } public StockDimensions Dimensions { get; set; } [MemoEditor(Editable = Editable.Enabled)] public string Notes { get; set; } = "Stocktake: "; public void UpdateDimensions() { if (Product.ID != Guid.Empty) { CoreTable table = new Client().Query(new Filter(x => x.ID).IsEqualTo(Product.ID), new Columns( x => x.Dimensions.Unit.ID, x => x.Dimensions.Unit.HasQuantity, x => x.Dimensions.Unit.HasLength, x => x.Dimensions.Unit.HasHeight, x => x.Dimensions.Unit.HasWeight, x => x.Dimensions.Unit.HasWidth, x => x.Dimensions.Quantity, x => x.Dimensions.Length, x => x.Dimensions.Height, x => x.Dimensions.Weight, x => x.Dimensions.Width, x => x.Dimensions.Unit.Format, x => x.Dimensions.Unit.Formula, x => x.Dimensions.UnitSize )); Product product = table.Rows.FirstOrDefault().ToObject(); Dimensions.Unit.ID = product.Dimensions.Unit.ID; Dimensions.Unit.HasQuantity = product.Dimensions.Unit.HasQuantity; Dimensions.Unit.HasLength = product.Dimensions.Unit.HasLength; Dimensions.Unit.HasHeight = product.Dimensions.Unit.HasHeight; Dimensions.Unit.HasWeight = product.Dimensions.Unit.HasWeight; Dimensions.Unit.HasWidth = product.Dimensions.Unit.HasWidth; Dimensions.Quantity = product.Dimensions.Quantity; Dimensions.Length = product.Dimensions.Length; Dimensions.Height = product.Dimensions.Height; Dimensions.Weight = product.Dimensions.Weight; Dimensions.Width = product.Dimensions.Width; Dimensions.Unit.Format = product.Dimensions.Unit.Format; Dimensions.Unit.Formula = product.Dimensions.Unit.Formula; Dimensions.UnitSize = product.Dimensions.UnitSize; } } public void PreFillQty() { if (OriginalQty != 0) NewQty = OriginalQty; } public void ClearQty() { NewQty = 0.00000000000000000000001; } } #endregion #region Grid public class StockTakeHoldingGrid : DynamicItemsListGrid { protected override void Reload(Filters criteria, Columns columns, ref SortOrder? sort, Action action) { base.Reload(criteria, columns, ref sort, action); } protected override void DoAdd(bool OpenEditorOnDirectEdit = false) { base.DoAdd(true); } protected override DynamicGridColumns LoadColumns() { var columns = new DynamicGridColumns(); columns.Add(x => x.Location.Code, 150, "Location Code", "", Alignment.MiddleCenter); columns.Add(x => x.Product.Code, 100, "Product Code", "", Alignment.MiddleCenter); columns.Add(x => x.Product.Name, 0, "Product Name", "", Alignment.MiddleCenter); columns.Add(x => x.Dimensions.UnitSize, 80, "Dimensions Unit", "", Alignment.MiddleCenter); columns.Add(x => x.Job.JobNumber, 80, "Job Number", "", Alignment.MiddleCenter); columns.Add(x => x.Job.Name, 80, "Job Name", "", Alignment.MiddleCenter); columns.Add(x => x.Style.Description, 80, "Style", "", Alignment.MiddleCenter); columns.Add(x => x.OriginalQty, 80, "Original Qty", "", Alignment.MiddleCenter); columns.Add(x => x.NewQty, 80, "Actual Qty", "", Alignment.MiddleCenter); columns.Add(x => x.Notes, 200, "Notes (Optional)", "", Alignment.MiddleCenter); return columns; } } #endregion }