| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 | using InABox.Core;using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace Comal.Classes{    /// <summary>    /// The <see cref="PurchaseOrderItemAllocation"/> is the way that we are allowing a single <see cref="PurchaseOrderItem"/> to, when received,    /// have its stock distributed to multiple sources. The current idea is this:    ///     /// <list type="bullet">    ///     <item>    ///         The <see cref="PurchaseOrderItem"/> itself has a <see cref="PurchaseOrderItem.Job"/> and a <see cref="PurchaseOrderItem.Qty"/>.    ///         In most cases, this will be the target for received stock, and    ///         in this simple case, there are no POIAs. If the <see cref="PurchaseOrderItem.Job"/> is blank, it goes to general stock;    ///         otherwise, it goes to a specific allocation.    ///     </item>    ///     <item>    ///         If there are allocations, this cuts into the <see cref="PurchaseOrderItem.Qty"/>, such that after any allocations have been filled,    ///         the remainder of the stock goes to the <see cref="PurchaseOrderItem.Job"/>.    ///         If the allocations total more than <see cref="PurchaseOrderItem.Qty"/>, then a negative stock movement    ///         is registered against <see cref="PurchaseOrderItem.Job"/>.    ///     </item>    ///     <item>    ///         In the case of Dimension conversions (see <see cref="DimensionUnit.Conversion"/>), the quantity on the allocation is after the conversion has    ///         happened.<br/>    ///         This is important, because in this case, <b>there will be a discrepancy</b> between <see cref="PurchaseOrderItem.Qty"/> and the total    ///         of <see cref="Quantity"/>.    ///         <br/>    ///         As an example, suppose we are purchasing 2 boxes of 1000 screws. Then, the allocations are by numbers of screws, so that 800 might go to    ///         Job A, 600 to Job B and 600 to general stock. So the allocation quantities are the actual numbers that will be on the stock movements.    ///     </item>    /// </list>    /// </summary>    [Caption("Allocation")]    public class PurchaseOrderItemAllocation : Entity, IRemotable, IPersistent, ILicense<ProjectManagementLicense>    , IOneToMany<JobRequisitionItem>, IOneToMany<Job>, IOneToMany<PurchaseOrderItem>, IPostableFragment<PurchaseOrder>    {        [EntityRelationship(DeleteAction.Cascade)]        public PurchaseOrderItemLink Item { get; set; }                [EntityRelationship(DeleteAction.Cascade)]        [EditorSequence(1)]        public JobLink Job { get; set; }                private class JobRequisitionItemLookup : LookupDefinitionGenerator<JobRequisitionItem, PurchaseOrderItemAllocation>        {            public override Columns<PurchaseOrderItemAllocation> DefineFilterColumns()            {                return base.DefineFilterColumns().Add(x => x.Job.ID).Add(x => x.Item.Product.ID);            }            public override Filter<JobRequisitionItem>? DefineFilter(PurchaseOrderItemAllocation[] items)            {                var jobIDs = items.Select(x => x.Job.ID).Distinct().ToArray();                if(jobIDs.Length > 1)                {                    return new Filter<JobRequisitionItem>().None();                }                var jobID = jobIDs.FirstOrDefault();                var productID = items.Select(x => x.Item.Product.ID).FirstOrDefault();                if(productID == Guid.Empty)                {                    return new Filter<JobRequisitionItem>().None();                }                return new Filter<JobRequisitionItem>(x => x.Job.ID).IsEqualTo(jobID)                    .And(x => x.Product.ID).IsEqualTo(productID);            }            public override Columns<JobRequisitionItem> DefineColumns()            {                return Columns.None<JobRequisitionItem>().Add(x => x.Job.JobNumber).Add(x => x.Requisition.Number).Add(x => x.Requisition.Description);            }            public override string FormatDisplay(CoreRow row)            {                var jobNumber = row.Get<JobRequisitionItem, string>(x => x.Job.JobNumber);                var requiNumber = row.Get<JobRequisitionItem, int>(x => x.Requisition.Number);                var requiDesc = row.Get<JobRequisitionItem, string>(x => x.Requisition.Description);                return $"{jobNumber}: #{requiNumber} ({requiDesc})";            }        }        /// <summary>        /// This may be an empty link. The interface is as such: if there is no JRI, then we are creating a reserve and allocation just against the job. If there is a JRI,        /// then received stock is reserved and allocated for the JRI.        /// </summary>        [EntityRelationship(DeleteAction.Cascade)]        [LookupDefinition(typeof(JobRequisitionItemLookup))]        [EditorSequence(2)]        public JobRequisitionItemLink JobRequisitionItem { get; set; }                [EditorSequence(3)]        public double Quantity { get; set; }        [NullEditor]        public string PostedReference { get; set; }        protected override void DoPropertyChanged(string name, object? before, object? after)        {            base.DoPropertyChanged(name, before, after);            if(name == $"{nameof(Job)}.{nameof(Job.ID)}")            {                JobRequisitionItem.ID = Guid.Empty;                JobRequisitionItem.Clear();            }        }    }            public class PurchaseOrderItemAllocationJobLink : EntityLink<PurchaseOrderItemAllocation>    {        [NullEditor]        public override Guid ID { get; set; }                public LightJobLink Job { get; set; }                public LightPurchaseOrderItemLink Item { get; set; }            }}
 |