using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Comal.Classes; using HarfBuzzSharp; using InABox.Core; using InABox.Mobile; using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.Forms; namespace PRS.Mobile { public enum StockTransactionType { Transfer, Issue, Receive, StockTake } public enum StockTransactionView { Source, Target } public enum StockTransactionProperty { Location, Style, Job } public class StockTransactionHolding : BindableObject { public StockHoldingShell? Shell { get; set; } public Guid LocationID { get; set; } public String LocationCode { get; set; } public String LocationDescription { get; set; } public String LocationDisplay => LocationID == Guid.Empty ? "" : $"{LocationCode}: {LocationDescription}"; public Guid StyleID { get; set; } public String StyleCode { get; set; } public String StyleDescription { get; set; } public String StyleDisplay => StyleID == Guid.Empty ? "" : $"{StyleCode}: {StyleDescription}"; public Guid JobID { get; set; } public String JobNumber { get; set; } public String JobName { get; set; } public String JobDisplay => JobID == Guid.Empty ? "" : $"{JobNumber}: {JobName}"; public double Units { get; set; } public bool HasAllocations => !(Shell?.Units ?? 0.0).IsEffectivelyEqual(Shell?.Available ?? 0.0); public StockTransactionHolding(StockHoldingShell? shell) { Shell = shell; LocationID = shell?.LocationID ?? Guid.Empty; LocationCode = shell?.LocationCode ?? string.Empty; LocationDescription = shell?.LocationDescription ?? string.Empty; StyleID = shell?.StyleID ?? Guid.Empty; StyleCode = shell?.StyleCode ?? string.Empty; StyleDescription = shell?.StyleDescription ?? string.Empty; JobID = shell?.JobID ?? Guid.Empty; JobNumber = shell?.JobNumber ?? string.Empty; JobName = shell?.JobName ?? string.Empty; Units = shell?.Units ?? 0.0d; } } public class StockTransactionAllocation : BindableObject { public Guid ID { get; set; } public Guid JobID { get; set; } public String Description { get; set; } public double Quantity { get; set; } public double Maximum { get; set; } } public class StockTransaction : BindableObject { private double _dimensionsQuantity; private double _dimensionsLength; private double _dimensionsWidth; private double _dimensionsHeight; private double _dimensionsWeight; public StockTransactionType Type { get; set; } public Guid TransactionID { get; private set; } public Guid EmployeeID { get; private set; } public Guid ProductID { get; set; } public String ProductCode { get; set; } public String ProductName { get; set; } public String ProductDisplay => ProductID == Guid.Empty ? "" : $"{ProductCode}: {ProductName}"; public Guid ImageID { get; set; } public byte[]? Image { get; set; } public Guid DimensionsUnitID { get; set; } public double DimensionsQuantity { get => _dimensionsQuantity; set { _dimensionsQuantity = value; CalculateDimensions(); } } public double DimensionsLength { get => _dimensionsLength; set { _dimensionsLength = value; CalculateDimensions(); } } public double DimensionsWidth { get => _dimensionsWidth; set { _dimensionsWidth = value; CalculateDimensions(); } } public double DimensionsHeight { get => _dimensionsHeight; set { _dimensionsHeight = value; CalculateDimensions(); } } public double DimensionsWeight { get => _dimensionsWeight; set { _dimensionsWeight = value; CalculateDimensions(); } } public String DimensionsUnitSize { get; set; } public double DimensionsValue { get; set; } public double Cost { get; set; } public StockTransactionAllocation[]? Allocations { get; set; } public double Quantity => Allocations?.Aggregate(0.0, (t, a) => t += a.Quantity) ?? 0.0; public void RefreshQuantity() => OnPropertyChanged(nameof(Quantity)); public bool HasAllocations => Allocations?.Any(x => x.ID != Guid.Empty) ?? false; public StockTransactionHolding Source { get; private set; } public StockTransactionHolding Target { get; private set; } public bool LocationChanged => (Source?.LocationID ?? Guid.Empty) != (Target?.LocationID ?? Guid.Empty); public bool StyleChanged => (Source?.StyleID ?? Guid.Empty) != (Target?.StyleID ?? Guid.Empty); public bool JobChanged => (Source?.JobID ?? Guid.Empty) != (Target?.JobID ?? Guid.Empty); public StockTransaction(StockTransactionType type, StockHoldingShell? source, StockHoldingShell? target) { var shell = source ?? target; Type = type; TransactionID = Guid.NewGuid(); EmployeeID = App.Data.Me.ID; ProductID = shell?.ProductID ?? Guid.Empty; ProductCode = shell?.ProductCode ?? string.Empty; ProductName = shell?.ProductName ?? string.Empty; ImageID = shell?.ImageID ?? Guid.Empty; Image = shell?.Image ?? new byte[] { }; DimensionsUnitID = shell?.DimensionsUnitID ?? Guid.Empty; DimensionsQuantity = shell?.DimensionsQuantity ?? 0.0; DimensionsLength = shell?.DimensionsLength ?? 0.0; DimensionsWidth = shell?.DimensionsWidth ?? 0.0; DimensionsHeight = shell?.DimensionsHeight ?? 0.0; DimensionsWeight = shell?.DimensionsWeight ?? 0.0; DimensionsUnitSize = shell?.DimensionsUnitSize ?? string.Empty; DimensionsValue = shell?.DimensionsValue ?? 0.0; Cost = shell?.AverageCost ?? 0.0; Source = new StockTransactionHolding(source); Source.PropertyChanged += (sender, args) => OnPropertyChanged(nameof(Source)); Target = new StockTransactionHolding(target); Target.PropertyChanged += (sender, args) => OnPropertyChanged(nameof(Target)); } private void CalculateDimensions() { var dimensions = App.Data.ProductDimensionUnits.FirstOrDefault(x => x.ID == DimensionsUnitID); Dictionary variables = new Dictionary(); variables["Length"] = DimensionsLength; variables["Width"] = DimensionsWidth; variables["Height"] = DimensionsHeight; variables["Quantity"] = DimensionsQuantity; variables["Weight"] = DimensionsHeight; if (! String.IsNullOrWhiteSpace(dimensions?.Format)) DimensionsUnitSize = new CoreExpression(dimensions.Format).Evaluate(variables)?.ToString() ?? string.Empty; if (!String.IsNullOrWhiteSpace(dimensions?.Formula)) { var value = new CoreExpression(dimensions.Format).Evaluate(variables)?.ToString() ?? "0.0"; DimensionsValue = double.TryParse(value, out double val) ? val : 0.0; } } public string Display(StockTransactionView? view = null) { List results = new List(); if ((Source?.LocationID ?? Guid.Empty) != (Target?.LocationID ?? Guid.Empty)) { string location = Type switch { StockTransactionType.Issue => "Issued", StockTransactionType.Receive => "Received", _ => "Transferred" }; string fromlocation = ""; string tolocation = ""; if ((view == null) || (view == StockTransactionView.Target)) fromlocation = (Source?.LocationID ?? Guid.Empty) != Guid.Empty ? $" From {Source?.LocationCode ?? "--"}" : " From --"; if ((view == null) || (view == StockTransactionView.Source)) tolocation = (Target?.LocationID ?? Guid.Empty) != Guid.Empty ? $" To {Target?.LocationCode ?? "--"}" : " To --"; results.Add($"{location}{fromlocation}{tolocation}"); } if ((Source?.StyleID ?? Guid.Empty) != (Target?.StyleID ?? Guid.Empty)) { string fromstyle = ""; string tostyle = ""; if ((view == null) || (view == StockTransactionView.Target)) fromstyle = (Source?.StyleID ?? Guid.Empty) != Guid.Empty ? $" From {Source?.StyleCode ?? "(none)"}" : " From (none)"; if ((view == null) || (view == StockTransactionView.Source)) tostyle = (Target?.StyleID ?? Guid.Empty) != Guid.Empty ? $" To {Target?.StyleCode ?? "(none)"}" : " To (none)"; results.Add($"Changed Style{fromstyle}{tostyle}"); } if ((Source?.JobID ?? Guid.Empty) != (Target?.JobID ?? Guid.Empty)) { string fromjob = ""; string tojob = ""; if ((view == null) || (view == StockTransactionView.Target)) fromjob = (Source?.JobID ?? Guid.Empty) != Guid.Empty ? $" From {Source?.JobNumber ?? "(none)"}" : " From (none)"; if ((view == null) || (view == StockTransactionView.Source)) tojob = (Target?.JobID ?? Guid.Empty) != Guid.Empty ? $" To {Target?.JobNumber ?? "(none)"}" : " To (none)"; results.Add($"Changed Job{fromjob}{tojob}"); } return String.Join("\n", results); } } public class StockTransactionImage : BindableObject { public MobileDocument Document { get; private set; } public byte[] Thumbnail { get; set; } public StockTransactionImage(MobileDocument document, byte[] thumbnail) { Document = document; Thumbnail = thumbnail; } } public class StockTransactions : ObservableRangeCollection { private enum TransactionDirection { Issue, Receive } public void ProcessTransactions(StockMovementModel model, Guid batchid) { void CreateMovement( StockMovementType type, StockTransaction transaction, Guid locationid, Guid styleid, Guid jobid, DateTime timestamp, TransactionDirection direction, Action? oncreate = null ) { foreach (var allocation in transaction.Allocations) { var movement = model.AddItem(); movement.BatchID = batchid; movement.Date = timestamp; movement.Type = type; movement.TransactionID = transaction.TransactionID; movement.ProductID = transaction.ProductID; movement.DimensionsUnitID = transaction.DimensionsUnitID; movement.DimensionsQuantity = transaction.DimensionsQuantity; movement.DimensionsLength = transaction.DimensionsLength; movement.DimensionsWidth = transaction.DimensionsWidth; movement.DimensionsHeight = transaction.DimensionsHeight; movement.DimensionsWeight = transaction.DimensionsWeight; movement.DimensionsValue = transaction.DimensionsValue; movement.DimensionsUnitSize = transaction.DimensionsUnitSize; movement.LocationID = locationid; movement.StyleID = styleid; movement.JobID = jobid; movement.RequisitionItemID = allocation.ID; movement.Received = direction == TransactionDirection.Receive ? allocation.Quantity : 0.0F; movement.Issued = direction == TransactionDirection.Issue ? allocation.Quantity : 0.0F; movement.Cost = transaction.Cost; movement.EmployeeID = transaction.EmployeeID; oncreate?.Invoke(transaction, allocation, movement); } } var now = DateTime.Now; foreach (var transaction in this) { if (transaction.Type == StockTransactionType.Receive) { CreateMovement( StockMovementType.Receive, transaction, transaction.Target.LocationID, transaction.Target.StyleID, transaction.Target.JobID, now, TransactionDirection.Receive ); } else if (transaction.Type == StockTransactionType.Issue) { if (transaction.Source.StyleID != transaction.Target.StyleID || transaction.Source.JobID != transaction.Target.JobID) { CreateMovement( StockMovementType.TransferOut, transaction, transaction.Source.LocationID, transaction.Source.StyleID, transaction.Source.JobID, now, TransactionDirection.Issue); CreateMovement( StockMovementType.TransferIn, transaction, transaction.Source.LocationID, transaction.Target.StyleID, transaction.Target.JobID, now, TransactionDirection.Receive); } CreateMovement( StockMovementType.Issue, transaction, transaction.Source.LocationID, transaction.Target.StyleID, transaction.Target.JobID, now, TransactionDirection.Issue); } else if (transaction.Type == StockTransactionType.Transfer) { CreateMovement( StockMovementType.TransferOut, transaction, transaction.Source.LocationID, transaction.Source.StyleID, transaction.Source.JobID, now, TransactionDirection.Issue); CreateMovement( StockMovementType.TransferIn, transaction, transaction.Target.LocationID, transaction.Target.StyleID, transaction.Target.JobID, now, TransactionDirection.Receive); } else if (transaction.Type == StockTransactionType.StockTake) { CreateMovement( StockMovementType.StockTake, transaction, transaction.Target.LocationID, transaction.Target.StyleID, transaction.Target.JobID, now, transaction.Quantity > 0 ? TransactionDirection.Receive : TransactionDirection.Issue, (t, a, m) => { var srcAlloc = t.Source.Shell.Allocations.FirstOrDefault(x => x.ID == a.ID); m.Issued = Math.Max(0, (srcAlloc?.Quantity ?? t.Source.Units) - a.Quantity); m.Received = Math.Max(0, a.Quantity - (srcAlloc?.Quantity ?? t.Source.Units)); m.Notes = m.Issued.IsEffectivelyEqual(0.0F) && m.Received.IsEffectivelyEqual(0.0F) ? $"Correct Quantity Confirmed" : $"Quantity Adjusted from {(srcAlloc?.Quantity ?? t.Source.Units):F2} to {a.Quantity:F2}"; m.Balance = a.Quantity; } ); } } } public IEnumerable Get(StockHoldingShell shell) { bool IsHoldingReferenced(StockTransactionHolding holding) => holding.LocationID == shell.LocationID && holding.StyleID == shell.StyleID && holding.JobID == shell.JobID; var results = this.Where(x => x.ProductID == shell.ProductID && String.Equals(x.DimensionsUnitSize, shell.DimensionsUnitSize) && (IsHoldingReferenced(x.Source) || IsHoldingReferenced(x.Target))).ToArray(); return results; } } }