浏览代码

Obsoleted PurchaseOrder.Status and made PurchaseOrders mergeable
Fixed JRI Calculation when Receiving Purchase Order Items
Added Indicator columns to PurchaseOrder Grid
Tightened Up "Receive Items" on PurchaseOrderItem Grid
Re-enabled Purchase Link on Purchase Order tems
Added ReceievdQty Aggregate on Purchase Order
Added Code to Employee Lookup Columns
Improved Purchase Order Editor Layout

frogsoftware 11 月之前
父节点
当前提交
2aa5682903

+ 6 - 3
prs.classes/Entities/Employee/EmployeeLink.cs

@@ -5,14 +5,17 @@ namespace Comal.Classes
 {
     public class EmployeeLink : EntityLink<Employee>, IEmployee
     {
-        [LookupEditor(typeof(Employee))]
-        [RequiredColumn]
-        [LoggableProperty]
+        [EditorSequence(1)]
+        [CodePopupEditor(typeof(Employee))]
+        [RequiredColumn,LoggableProperty]
         public override Guid ID { get; set; }
 
+        [EditorSequence(2)]
+        [RequiredColumn]
         [CodeEditor(Visible = Visible.Default, Editable = Editable.Hidden)]
         public string Code { get; set; }
 
+        [RequiredColumn]
         [TextBoxEditor(Visible = Visible.Optional, Editable = Editable.Hidden)]
         public string Name { get; set; }
 

+ 1 - 0
prs.classes/Entities/Employee/EmployeeLookups.cs

@@ -10,6 +10,7 @@ namespace Comal.Classes
         {
             return Columns.None<Employee>().Add(
                 x => x.ID,
+                x => x.Code,
                 x => x.Name
             );
         }

+ 81 - 79
prs.classes/Entities/PurchaseOrder/PurchaseOrder.cs

@@ -2,6 +2,8 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq.Expressions;
+using System.Runtime.InteropServices;
+using InABox.Clients;
 using InABox.Core;
 
 namespace Comal.Classes
@@ -34,7 +36,22 @@ namespace Comal.Classes
             };
 
         public override Filter<PurchaseOrderItem>? Filter => new Filter<PurchaseOrderItem>(x => x.ReceivedDate).IsEqualTo(DateTime.MinValue);
-    }    
+    }
+    
+    public class PurchaseOrderReceivedQtyAggregate : CoreAggregate<PurchaseOrder, PurchaseOrderItem, double>
+    {
+        public override Expression<Func<PurchaseOrderItem, double>> Aggregate => x => x.Qty;
+
+        public override AggregateCalculation Calculation => AggregateCalculation.Sum;
+
+        public override Dictionary<Expression<Func<PurchaseOrderItem, object?>>, Expression<Func<PurchaseOrder, object?>>> Links =>
+            new Dictionary<Expression<Func<PurchaseOrderItem, object?>>, Expression<Func<PurchaseOrder, object?>>>()
+            {
+                { PurchaseOrderItem => PurchaseOrderItem.PurchaseOrderLink.ID, PurchaseOrder => PurchaseOrder.ID }
+            };
+        
+        public override Filter<PurchaseOrderItem>? Filter => new Filter<PurchaseOrderItem>(x => x.ReceivedDate).IsNotEqualTo(DateTime.MinValue);
+    }  
     
     public class PurchaseOrderExTax : CoreAggregate<PurchaseOrder, PurchaseOrderItem, double>
     {
@@ -90,7 +107,7 @@ namespace Comal.Classes
 
     [UserTracking(typeof(Bill))]
     public class PurchaseOrder : Entity, IRemotable, IPersistent, IStringAutoIncrement<PurchaseOrder>, IOneToMany<Supplier>,
-        ILicense<AccountsPayableLicense>, IDataEntryInstance, IPostable
+        ILicense<AccountsPayableLicense>, IDataEntryInstance, IPostable, IMergeable
     {
         private bool bChanging;
 
@@ -106,68 +123,83 @@ namespace Comal.Classes
         [MemoEditor]
         [EditorSequence(2)]
         public string Description { get; set; }
-
-        [NotesEditor]
-        [EditorSequence("Notes",1)]
-        public string[] Notes { get; set; }
         
         [EditorSequence(3)]
         public PurchaseOrderCategoryLink Category { get; set; }
-        
-        [DateTimeEditor]
+
         [EditorSequence(4)]
-        public DateTime IssuedDate { get; set; }
+        public EmployeeLink RaisedBy { get; set; }
 
         [DateEditor]
         [EditorSequence(5)]
         public DateTime DueDate { get; set; }
         
-        [EditorSequence("Additional",1)]
-        public EmployeeLink RaisedBy { get; set; }
+        private class IssuedDateInformation : EditorInformation<PurchaseOrder>
+        {
+            public override string GetInfo(PurchaseOrder item) => item.IssuedBy.Name;
+        }
         
-        [EditorSequence("Additional",2)]
-        [TimestampEditor]
-        public DateTime DataEntered { get; set; }
+        [DateTimeEditor(Information = typeof(IssuedDateInformation))]
+        [RequiredColumn,LoggableProperty]
+        [EditorSequence(6)]
+        public DateTime IssuedDate { get; set; }
         
-        [EditorSequence("Additional",3)]
+        // This information might be printed out on the PO report, so we store this in the database
+        // Other properties uch as Closed/Cancelled/Checked etc are simply logged in the Audit Trail
+        [RequiredColumn]
+        [NullEditor]
         public EmployeeLink IssuedBy { get; set; }
         
-        [TimestampEditor]
-        [EditorSequence("Additional",4)]
-        public DateTime ClosedDate { get; set; }
-        
-        [TimestampEditor]
-        [EditorSequence("Additional",5)]
-        public DateTime CancelledDate { get; set; }
-
-        [EditorSequence(10)]
         [DoubleEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
         [Aggregate(typeof(PurchaseOrderExTax))]
         public double ExTax { get; set; }
-
-        [EditorSequence(11)]
+        
         [DoubleEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
         [Aggregate(typeof(PurchaseOrderTax))]
         public double Tax { get; set; }
-
-        [EditorSequence(12)]
+        
         [DoubleEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
         [Aggregate(typeof(PurchaseOrderIncTax))]
         public double IncTax { get; set; }
-
-        [EditorSequence(13)]
+        
         [DoubleEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
         [Aggregate(typeof(PurchaseOrderBalance))]
         public double Balance { get; set; }
-
-        [EditorSequence(13)]
+        
+        [EditorSequence("Additional",2)]
+        [LoggableProperty]
+        [TimestampEditor]
+        public DateTime DataEntered { get; set; }
+        
+        [EditorSequence("Additional",3)]
+        [TimestampEditor]
+        [LoggableProperty]
+        public DateTime CheckedDate { get; set; }
+        
+        [RequiredColumn, LoggableProperty]
+        [TimestampEditor]
+        [EditorSequence("Additional",4)]
+        public DateTime ClosedDate { get; set; }
+        
+        [TimestampEditor]
+        [LoggableProperty]
+        [EditorSequence("Additional",5)]
+        public DateTime CancelledDate { get; set; }
+        
+        [NotesEditor]
+        [EditorSequence("Notes",1)]
+        public string[] Notes { get; set; }
+        
+        
         [DoubleEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
         [Aggregate(typeof(PurchaseOrderUnreceivedQtyAggregate))]
         public double Unreceived { get; set; }
+        
+        [DoubleEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
+        [Aggregate(typeof(PurchaseOrderReceivedQtyAggregate))]
+        public double Received { get; set; }
+
 
-        [EditorSequence(14)]
-        [EnumLookupEditor(typeof(PurchaseOrderStatus), Editable = Editable.Hidden)]
-        public PurchaseOrderStatus Status { get; set; }
         
         //[CodeEditor(Visible = Visible.Default, Editable = Editable.Hidden)]
         [NullEditor]
@@ -195,7 +227,11 @@ namespace Comal.Classes
 
         [NullEditor]
         public string PostedReference { get; set; }
-
+        
+        [Obsolete("Replaced by multiple Datetime stamps", true)]
+        [NullEditor]
+        public PurchaseOrderStatus Status { get; set; }
+        
         public Expression<Func<PurchaseOrder, string>> AutoIncrementField() => x => x.PONumber;
         public Filter<PurchaseOrder> AutoIncrementFilter() => null;
         public string AutoIncrementPrefix() => PONumberPrefix;
@@ -207,51 +243,17 @@ namespace Comal.Classes
             if (bChanging)
                 return;
             bChanging = true;
-
-            if (name.Equals("Status"))
+            if (name.Equals(nameof(IssuedDate)) && before is DateTime _old && after is DateTime _new && _new.IsEmpty() != _old.IsEmpty())
             {
-                var status = (PurchaseOrderStatus)after;
-                if (status.Equals(PurchaseOrderStatus.Closed))
-                {
-                    if (ClosedDate == DateTime.MinValue)
-                        ClosedDate = DateTime.Now;
-                    if (IssuedDate == DateTime.MinValue)
-                        IssuedDate = DateTime.Now;
-                }
-                else if (status == PurchaseOrderStatus.Issued)
-                {
-                    if (ClosedDate != DateTime.MinValue)
-                        ClosedDate = DateTime.MinValue;
-                    if (IssuedDate == DateTime.MinValue)
-                        IssuedDate = DateTime.Now;
-                }
-                else if(status == PurchaseOrderStatus.Cancelled)
-                {
-                    CancelledDate = DateTime.Now;
-                }
-                else
-                {
-                    if (ClosedDate != DateTime.MinValue)
-                        ClosedDate = DateTime.MinValue;
-                    if (IssuedDate != DateTime.MinValue)
-                        IssuedDate = DateTime.MinValue;
-                }
+                Employee? _emp = _new.IsEmpty()
+                    ? new Employee()
+                    : Client.Query<Employee>(
+                        new Filter<Employee>(x => x.UserLink.ID).IsEqualTo(ClientFactory.UserGuid),
+                        Columns.None<Employee>().Add(x => x.ID).Add(x => x.Name)
+                    ).Rows.FirstOrDefault()?.ToObject<Employee>();
+                IssuedBy.ID = _emp.ID;
+                IssuedBy.Synchronise(_emp);
             }
-            else
-            {
-                var status = PurchaseOrderStatus.Closed;
-                if (CancelledDate != DateTime.MinValue)
-                    status = PurchaseOrderStatus.Cancelled;
-                else if (ClosedDate != DateTime.MinValue)
-                    status = PurchaseOrderStatus.Closed;
-                else if (IssuedDate != DateTime.MinValue)
-                    status = PurchaseOrderStatus.Issued;
-                else
-                    status = PurchaseOrderStatus.Draft;
-                if (Status != status)
-                    Status = status;
-            }
-
             bChanging = false;
         }
 

+ 0 - 1
prs.classes/Entities/PurchaseOrder/PurchaseOrderItem.cs

@@ -44,7 +44,6 @@ namespace Comal.Classes
         
         [RequiredColumn]
         [EntityRelationship(DeleteAction.Cascade)]
-        [NullEditor]
         public PurchaseOrderLink PurchaseOrderLink { get; set; }
         
         [EntityRelationship(DeleteAction.SetNull)]

+ 1 - 1
prs.desktop/Dashboards/Accounts/OpenPurchaseOrdersDashboard.cs

@@ -59,7 +59,7 @@ namespace PRSDesktop.Dashboards
 
         protected override void Reload(Filters<PurchaseOrder> criteria, Columns<PurchaseOrder> columns, ref SortOrder<PurchaseOrder>? sort, Action<CoreTable?, Exception?> action)
         {
-            criteria.Add(new Filter<PurchaseOrder>(x => x.Status).IsEqualTo(PurchaseOrderStatus.Issued));
+            criteria.Add(new Filter<PurchaseOrder>(x => x.IssuedDate).IsNotEqualTo(DateTime.MinValue));
             base.Reload(criteria, columns, ref sort, action);
         }
         

+ 30 - 13
prs.desktop/Panels/PurchaseOrders/SupplierPurchaseOrderItemOneToMany.cs

@@ -419,8 +419,9 @@ public class SupplierPurchaseOrderItemOneToMany : DynamicOneToManyGrid<PurchaseO
         {
             var items = LoadItems(rows);
             foreach (var item in items)
-                item.ReceivedDate = now;
-            new Client<PurchaseOrderItem>().Save(items, "Consignment Items Received");
+                item.ReceivedDate = item.ReceivedDate.IsEmpty() ? now : DateTime.MinValue;
+            now = items.Select(x => x.ReceivedDate).FirstOrDefault();
+            new Client<PurchaseOrderItem>().Save(items, now.IsEmpty() ? "Cleared Received Date" : $"Updated Received Date to {now:g}");
         }
 
         return true;
@@ -477,25 +478,41 @@ public class SupplierPurchaseOrderItemOneToMany : DynamicOneToManyGrid<PurchaseO
 
     protected override void SelectItems(CoreRow[]? rows)
     {
+        // Check if we can actually edit the selected lines
+        var _editable = !ReadOnly && Security.CanEdit<Consignment>();
+        
+        // Check to ensure NO selected lines are received
+        var _allunreceived = rows?.All(r => r.Get<PurchaseOrderItem, DateTime>(c => c.ReceivedDate).IsEmpty()) == true;
+        
+        // Check to ensure ALL select lines are received
+        var _allreceived = rows?.All(r => !r.Get<PurchaseOrderItem, DateTime>(c => c.ReceivedDate).IsEmpty()) == true;
+        
+        // Check to ensure NO selected lines are already linked to a PO
+        var _anyconsigmments = rows?.Any(r => r.Get<PurchaseOrderItem,Guid>(c=>c.Consignment.ID) != Guid.Empty) == true;
+        
+        // Check to Ensure NO selected lines are already linked to a Bill
+        var _anybills = rows?.Any(r => r.Get<PurchaseOrderItem,Guid>(c=>c.BillLine.ID) != Guid.Empty) == true;
+
         if (createConsignment != null)
         {
             createConsignment.IsEnabled =
-                rows != null
-                && !rows.Any(r => Entity.IsEntityLinkValid<PurchaseOrderItem, ConsignmentLink>(x => x.Consignment, r))
-                && !rows.Any(r => !r.Get<PurchaseOrderItem, DateTime>(c => c.ReceivedDate).IsEmpty())
-                && !ReadOnly && Security.CanEdit<Consignment>();
+                _anyconsigmments == false
+                && _allunreceived
+                && _editable;
         }
 
+        receive.Content = _allreceived
+            ? "Un-Receive Items"
+            : "Receive Items";
+        
         receive.IsEnabled =
-            rows != null
-            && !rows.Any(r => Entity.IsEntityLinkValid<PurchaseOrderItem, ConsignmentLink>(x => x.Consignment, r))
-            && !rows.Any(r => r.Get<PurchaseOrderItem, DateTime>(c => c.ReceivedDate).IsEmpty() == false)
-            && !ReadOnly && Security.CanEdit<PurchaseOrderItem>();
+            _anyconsigmments == false
+            && (_allreceived || _allunreceived)
+            && _editable;
 
         bill.IsEnabled =
-            rows != null
-            && !rows.Any(r => r.IsEntityLinkValid<PurchaseOrderItem, BillLineLink>(x => x.BillLine))
-            && !ReadOnly && Security.CanEdit<PurchaseOrderItem>();
+            _anybills == false
+            && _editable;
 
         assignLocation.IsEnabled =
             rows != null && !ReadOnly && Security.CanEdit<PurchaseOrderItem>();

+ 32 - 1
prs.desktop/Panels/PurchaseOrders/SupplierPurchaseOrders.cs

@@ -42,6 +42,10 @@ public class SupplierPurchaseOrders : DynamicDataGrid<PurchaseOrder>
     
     private SupplierPurchaseOrdersSettings _settings;
 
+    public BitmapImage _truck = PRSDesktop.Resources.truck.AsBitmapImage();
+    public BitmapImage _tick = PRSDesktop.Resources.tick.AsBitmapImage();
+    public BitmapImage _warning = PRSDesktop.Resources.warning.AsBitmapImage();
+
     public SupplierPurchaseOrders()
     {
         _settings = new UserConfiguration<SupplierPurchaseOrdersSettings>().Load();
@@ -51,14 +55,41 @@ public class SupplierPurchaseOrders : DynamicDataGrid<PurchaseOrder>
         OnEditorValueChanged += SupplierPurchaseOrders_OnEditorValueChanged;
         OnCustomiseEditor += SupplierPurchaseOrders_OnCustomiseEditor;
         
+        HiddenColumns.Add(x => x.IssuedDate);
         HiddenColumns.Add(x => x.ClosedDate);
         HiddenColumns.Add(x => x.Balance);
-
+        HiddenColumns.Add(x => x.Unreceived);
+        HiddenColumns.Add(x => x.Received);
+        
+        ActionColumns.Add(new DynamicImageColumn(IssuedStatus));
+        ActionColumns.Add(new DynamicImageColumn(ReceivedStatus));
+        
         PostUtils.AddPostColumn(this);
 
         close = AddButton("Close Order", null, CloseOrder);
         close.IsEnabled = false;
     }
+
+    private BitmapImage? IssuedStatus(CoreRow? row)
+    {
+        return row == null
+            ? _tick
+            : !row.Get<PurchaseOrder, DateTime>(x => x.IssuedDate).IsEmpty()
+                ? _tick
+                : null;
+    }
+
+    private BitmapImage? ReceivedStatus(CoreRow? row)
+    {
+        return row == null
+            ? _truck
+            : row.Get<PurchaseOrder, double>(x => x.Unreceived).IsEffectivelyEqual(0.0)
+                ? _tick
+                : !row.Get<PurchaseOrder, double>(x => x.Received).IsEffectivelyEqual(0.0)
+                    ? _warning
+                    : null;
+    }
+
     protected override void DoReconfigure(DynamicGridOptions options)
     {
         base.DoReconfigure(options);

+ 1 - 1
prs.desktop/prsdesktop.iss

@@ -8,7 +8,7 @@
 #define public Dependency_Path_NetCoreCheck "dependencies\"
 
 #define MyAppName "PRS Desktop"
-#define MyAppVersion "8.08b"
+#define MyAppVersion "8.08e"
 #define MyAppPublisher "PRS Digital"
 #define MyAppURL "https://www.prs-software.com.au"
 #define MyAppExeName "PRSDesktop.exe"

+ 1 - 1
prs.licensing/PRSLicensing.iss

@@ -8,7 +8,7 @@
 #define public Dependency_Path_NetCoreCheck "dependencies\"
 
 #define MyAppName "PRS Licensing"
-#define MyAppVersion "8.08b"
+#define MyAppVersion "8.08e"
 #define MyAppPublisher "PRS Digital"
 #define MyAppURL "https://www.prs-software.com.au"
 #define MyAppExeName "PRSLicensing.exe"

+ 1 - 1
prs.server/PRSServer.iss

@@ -8,7 +8,7 @@
 #define public Dependency_Path_NetCoreCheck "dependencies\"
 
 #define MyAppName "PRS Server"
-#define MyAppVersion "8.08b"
+#define MyAppVersion "8.08e"
 #define MyAppPublisher "PRS Digital"
 #define MyAppURL "https://www.prs-software.com.au"
 #define MyAppExeName "PRSServer.exe"

+ 12 - 2
prs.stores/PurchaseOrderItemStore.cs

@@ -324,11 +324,21 @@ internal class PurchaseOrderItemStore : BaseStore<PurchaseOrderItem>
         { 
             poCost += entity.Cost * entity.Consignment.ExTax / consigntask.Result;
         }        
+        
         foreach (var jri in jris)
         {
-            CreateMovement(entity, locationid, movements, jri, jri.Qty, poCost);
-            _pototal -= jri.Qty;
+            // Going through each jri, make sure we don't allocate more than the po line allows
+            var jriQty = Math.Min(jri.Qty, _pototal);
+            // And reduce the po balance by the jri Allocation
+            _pototal -= jriQty;
+            
+            // Let's not make zero-quantity transactions
+            if (!jriQty.IsEffectivelyEqual(0.0))
+                CreateMovement(entity, locationid, movements, jri, jriQty, poCost);
         }
+        
+        // If there is any left over (ie over-ordered), now we can create a
+        // second transaction to receive the unallocated stock
         if (!_pototal.IsEffectivelyEqual(0.0F))
             CreateMovement(entity, locationid, movements, null, _pototal, poCost);
 

+ 2 - 7
prs.stores/PurchaseOrderStore.cs

@@ -14,11 +14,6 @@ namespace Comal.Stores
         {
             base.BeforeSave(entity);
             Guid? empid = null;
-            if (entity.HasOriginalValue(x => x.IssuedDate) && entity.GetOriginalValue(x => x.IssuedDate).IsEmpty())
-            {
-                empid ??= GetEmployeeIDFromUserGuid();
-                entity.IssuedBy.ID = empid.Value;
-            }
             if (entity.ID == Guid.Empty)
             {
                 empid ??= GetEmployeeIDFromUserGuid();
@@ -32,9 +27,9 @@ namespace Comal.Stores
 
             UpdateTrackingKanban<PurchaseOrderKanban, PurchaseOrder, PurchaseOrderLink>(entity, p =>
             {
-                return p.Status == PurchaseOrderStatus.Closed
+                return !p.ClosedDate.IsEmpty() 
                     ? KanbanStatus.Complete
-                    : p.Status.Equals(PurchaseOrderStatus.Issued)
+                    : !p.IssuedDate.IsEmpty()
                         ? KanbanStatus.Waiting
                         : KanbanStatus.Open;
             });