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; }
-
- }
- }
|