/// <summary> /// Gets the oldest lot in a facility of the specified <paramref name="gtin"/> /// </summary> /// <param name="facility">The facility in which the oldest lot should be found</param> /// <param name="gtin">The GTIN of the item to be found</param> /// <returns>The <see cref="T:GIIS.DataLayer.ItemLot"/> with the earliest expiry date</returns> public ItemLot GetOldestLot(HealthFacility facility, String gtin) { var oldestLot = from l in (from v in HealthFacilityBalance.GetHealthFacilityBalanceByHealthFacilityCode(facility.Code) where gtin == v.Gtin select new { il = ItemLot.GetItemLotByGtinAndLotNo(gtin, v.LotNumber), hfb = v }) where l.il != null orderby l.il.ExpireDate select l; var candidateLot = (oldestLot.FirstOrDefault(o => o.hfb.Balance > 0) ?? oldestLot.FirstOrDefault()); // Candidate lot is null when there is demand (from a kit item) with no lot available! if (candidateLot == null) { // Is it becaues there is no lot in existence? var itemLot = ItemLot.GetItemLotByGtin(gtin); if (itemLot == null) { itemLot = new ItemLot() { Gtin = gtin, LotNumber = String.Format("N/A-{0}", gtin), Notes = "Automatically inserted by order system", ItemId = ItemManufacturer.GetItemManufacturerByGtin(gtin).ItemId }; itemLot.Id = ItemLot.Insert(itemLot); } // Is there no balance? var balanceLot = HealthFacilityBalance.GetHealthFacilityBalanceByLotNumber(itemLot.LotNumber); if (balanceLot == null) { HealthFacilityBalance.Insert(new HealthFacilityBalance() { Gtin = gtin, HealthFacilityCode = facility.Code, LotNumber = itemLot.LotNumber }); } return(itemLot); } else { return(candidateLot.il); } }
/// <summary> /// Initialize the test data /// </summary> public static void InitializeTestDataset() { /// Transaction type 999 = ID var transactionTypes = TransactionType.GetTransactionTypeList(); if (!transactionTypes.Exists(o => o.Name == "Allocation")) { TransactionType.Insert(new TransactionType() { Name = "Allocation" }); } if (!transactionTypes.Exists(o => o.Name == "Transfer")) { TransactionType.Insert(new TransactionType() { Name = "Transfer" }); } // GTIN 12345 - PHARMACO OPV if (ItemManufacturer.GetItemManufacturerByGtin(GTIN_UNDER_TEST) == null) { // Item Category 999 = Vaccine if (ItemCategory.GetItemCategoryById(VACCINE_CATEGORY_ID) == null) { VACCINE_CATEGORY_ID = ItemCategory.Insert(new ItemCategory() { Id = VACCINE_CATEGORY_ID, Code = "VACCINE", IsActive = true, ModifiedBy = 1, ModifiedOn = DateTime.Now, Name = "Vaccine" }); } // Item 999 - OPV if (Item.GetItemById(OPV_ITEM_ID) == null) { OPV_ITEM_ID = Item.Insert(new Item() { Code = "OPV", EntryDate = DateTime.Now, Id = OPV_ITEM_ID, ItemCategoryId = VACCINE_CATEGORY_ID, IsActive = true, ModifiedBy = 1, ModifiedOn = DateTime.Now, Name = "OPV" }); } // Unit of Measure if (Uom.GetUomById(DOSE_UOM_ID) == null) { DOSE_UOM_ID = Uom.Insert(new Uom() { Id = DOSE_UOM_ID, Name = "DOSE" }); } // Manufacturer 999 - PHARMACO if (Manufacturer.GetManufacturerById(PHARMACO_MANUFACTURER_ID) == null) { PHARMACO_MANUFACTURER_ID = Manufacturer.Insert(new Manufacturer() { Id = PHARMACO_MANUFACTURER_ID, Code = "PHX", IsActive = true, ModifiedBy = 1, ModifiedOn = DateTime.Now, Name = "PHARMACO INC." }); } ItemManufacturer.Insert(new ItemManufacturer() { Alt1QtyPer = 20, BaseUom = "DOSE", BaseUomChildPerBaseUomParent = 10, Gtin = GTIN_UNDER_TEST, IsActive = true, ItemId = OPV_ITEM_ID, ManufacturerId = PHARMACO_MANUFACTURER_ID, ModifiedBy = 1, ModifiedOn = DateTime.Now, Price = 9.99, StorageSpace = 1, Alt1Uom = "DOSE", Alt2QtyPer = 30, Alt2Uom = "DOSE" }); } if (ItemLot.GetItemLotByGtin(GTIN_UNDER_TEST) == null) { // Item 999 - OPV if (Item.GetItemById(OPV_ITEM_ID) == null) { OPV_ITEM_ID = Item.Insert(new Item() { Code = "OPV", EntryDate = DateTime.Now, Id = OPV_ITEM_ID, ItemCategoryId = VACCINE_CATEGORY_ID, IsActive = true, ModifiedBy = 1, ModifiedOn = DateTime.Now, Name = "OPV" }); } ItemLot.Insert(new ItemLot() { ExpireDate = DateTime.Now.AddDays(10), Gtin = GTIN_UNDER_TEST, ItemId = OPV_ITEM_ID, LotNumber = GTIN_LOT_USE_FIRST }); // Item Lot 2 - Will be more stock and expires later ItemLot.Insert(new ItemLot() { ExpireDate = DateTime.Now.AddDays(40), Gtin = GTIN_UNDER_TEST, ItemId = OPV_ITEM_ID, LotNumber = GTIN_LOT_USE_LAST }); // Item Lot 3 - Will trigger low stock ItemLot.Insert(new ItemLot() { ExpireDate = DateTime.Now.AddDays(80), Gtin = GTIN_UNDER_TEST, ItemId = OPV_ITEM_ID, LotNumber = GTIN_LOT_LOW_BAL }); } // Type 3 = DISTRICT if (HealthFacilityType.GetHealthFacilityTypeById(6) == null) { HealthFacilityType.Insert(new HealthFacilityType() { Id = 3, Code = "DISTRICT", IsActive = true, ModifiedBy = 1, ModifiedOn = DateTime.Now, Name = "DISTRICT LEVEL" }); } // Type 6 = SDP if (HealthFacilityType.GetHealthFacilityTypeById(6) == null) { HealthFacilityType.Insert(new HealthFacilityType() { Id = 6, Code = "SDP", IsActive = true, ModifiedBy = 1, ModifiedOn = DateTime.Now, Name = "SDP" }); } // HF888 = DISTRICT if (HealthFacility.GetHealthFacilityByCode("HF888") == null) { HealthFacility.Insert(new HealthFacility() { Id = 888, Code = "HF888", ColdStorageCapacity = 1000, IsActive = true, Leaf = false, ParentId = HealthFacility.GetHealthFacilityByParentId(0)[0].Id, TopLevel = false, TypeId = 3, VaccinationPoint = false, VaccineStore = true, ModifiedBy = 1, ModifiedOn = DateTime.Now }); } // GIVE HEALTH FACILITY SOME BALANCE var hf888Balances = HealthFacilityBalance.GetHealthFacilityBalanceByHealthFacilityCode("HF888"); HealthFacilityBalance useFirst = hf888Balances.Find(o => o.Gtin == GTIN_UNDER_TEST && o.LotNumber == GTIN_LOT_USE_FIRST), useLast = hf888Balances.Find(o => o.Gtin == GTIN_UNDER_TEST && o.LotNumber == GTIN_LOT_USE_LAST), lowStock = hf888Balances.Find(o => o.Gtin == GTIN_UNDER_TEST && o.LotNumber == GTIN_LOT_LOW_BAL); if (useFirst == null) { useFirst = new HealthFacilityBalance() { Allocated = 0, Balance = 500, Distributed = 0, Gtin = GTIN_UNDER_TEST, HealthFacilityCode = "HF888", LotNumber = GTIN_LOT_USE_FIRST, Received = 0, StockCount = 500, Used = 0, Wasted = 0 }; hf888Balances.Add(useFirst); HealthFacilityBalance.Insert(useFirst); } else { useFirst.Balance = 500; useFirst.StockCount = 500; HealthFacilityBalance.Update(useFirst); } if (useLast == null) { useLast = new HealthFacilityBalance() { Allocated = 0, Balance = 1000, Distributed = 0, Gtin = GTIN_UNDER_TEST, HealthFacilityCode = "HF888", LotNumber = GTIN_LOT_USE_LAST, Received = 0, StockCount = 1000, Used = 0, Wasted = 0 }; hf888Balances.Add(useLast); HealthFacilityBalance.Insert(useLast); } else { useLast.Balance = 1000; useLast.StockCount = 1000; HealthFacilityBalance.Update(useLast); } if (lowStock == null) { lowStock = new HealthFacilityBalance() { Allocated = 0, Balance = 10, Distributed = 0, Gtin = GTIN_UNDER_TEST, HealthFacilityCode = "HF888", LotNumber = GTIN_LOT_LOW_BAL, Received = 0, StockCount = 10, Used = 0, Wasted = 0 }; hf888Balances.Add(lowStock); HealthFacilityBalance.Insert(lowStock); } else { useLast.Balance = 10; useLast.StockCount = 10; HealthFacilityBalance.Update(lowStock); } // HF999 = SDP if (HealthFacility.GetHealthFacilityByCode("HF999") == null) { HealthFacility.Insert(new HealthFacility() { Id = 999, Code = "HF999", ColdStorageCapacity = 100, IsActive = true, Leaf = true, ParentId = HealthFacility.GetHealthFacilityByCode("HF888").Id, TopLevel = false, TypeId = 6, VaccinationPoint = true, VaccineStore = true, ModifiedOn = DateTime.Now, ModifiedBy = 1 }); } }
/// <summary> /// Add an order line to the order defined by <paramref name="order"/> /// </summary> /// <param name="order">The order to which the line should be added</param> /// <param name="gtin">The global trade identification number of the item in the line</param> /// <param name="lot">(Optional) The lot number to be used. Note if null is passed in <paramref name="lot"/> then the oldest lot is used first</param> /// <param name="qty">The quantity of the item to be added to the order</param> /// <param name="uom">(Optional) The base unit of measure. If <paramref name="uom"/> is null then the default UOM for the item described by <paramref name="gtin"/> is used</param> /// <returns>The constructed and saved <see cref="T:GIIS.DataLayer.TransferOrderDetail"/></returns> /// <remarks> /// The add order line function is responsible for adding a new order line to the order detail. This function operates in the following manner: /// <list type="ordered"> /// <item> /// <description>[Guard Condition] If the order passed into the function IS in the “Packed” or “Shipped” state then throw an invalid state exception (sanity check)</description> /// </item> /// <item> /// <description>Lookup the item by the GTIN provided in the function call.</description> /// </item> /// <item> /// <description>[Guard Condition] If the lot number is provided, and the lot number is not a valid lot number for the item provided then throw an error code</description> /// </item> /// <item> /// <description>If the current status of the order to which the detail is to be attached is “Released” then /// <list type="ordered"> /// <item> /// <description>Instantiate a StockManagementLogic BLL class</description> /// </item> /// <item>Allocate the stock using the Allocate method</item> /// </list> /// </description> /// </item> /// <item> /// <description>Set the unit of measure, quantity, gtin, etc. fields of the TransferOrderDetail instance to the parameters and fields derived from loaded Item.</description> /// </item> /// <item> /// <description>Save the order line.</description> /// </item> ///</list> /// </remarks> public TransferOrderDetail AddOrderLine(TransferOrderHeader order, String gtin, String lot, Int32 qty, Uom uom, Int32 modifiedBy) { if (order == null) { throw new ArgumentNullException("order"); } if (String.IsNullOrEmpty(gtin)) { throw new ArgumentNullException("gtin"); } // Sanity check if (order.OrderStatus == (int)OrderStatusType.Shipped || order.OrderStatus == (int)OrderStatusType.Cancelled) { throw new IllegalStateException((OrderStatusType)order.OrderStatus, "TransferOrderHeader", "AddOrderLine"); } // Lookup item by GTIN and optionally lot ItemLot item = null; if (!String.IsNullOrEmpty(lot)) { item = ItemLot.GetItemLotByGtinAndLotNo(gtin, lot); } if (item == null) { // not found - We get the item by lot item = ItemLot.GetItemLotByGtin(gtin); lot = "*"; // null; } // Item still null? if (item == null) { throw new InvalidOperationException(String.Format("Cannot locate item with GTIN {0}", gtin)); } // Construct the order detail TransferOrderDetail retVal = new TransferOrderDetail() { ModifiedBy = modifiedBy, ModifiedOn = DateTime.Now, OrderCustomItem = false, OrderDetailDescription = item.ItemObject.Name, OrderDetailStatus = order.OrderStatus, OrderGtin = gtin, OrderGtinLotnum = lot, OrderNum = order.OrderNum, OrderQty = qty, OrderQtyInBaseUom = qty, OrderUom = uom.Name }; // HACK: Overcome lack of transaction processing we have to be careful about how we do this ItemTransaction allocateTransaction = null; // Current state of order is released? We need to allocate this line item if (order.OrderStatus == (int)OrderStatusType.Released) { StockManagementLogic stockLogic = new StockManagementLogic(); // We need to release this order ... If the lot was null we'll use the oldest lot number if (String.IsNullOrEmpty(lot)) { item = this.GetOldestLot(order.OrderFacilityFromObject, gtin); } // Allocation of specified lot allocateTransaction = stockLogic.Allocate(order.OrderFacilityFromObject, gtin, item.LotNumber, qty, null, modifiedBy); // Update order retVal.OrderGtinLotnum = item.LotNumber; } // Save retVal.OrderDetailNum = TransferOrderDetail.Insert(retVal); // HACK: Update the allocate transaction. This would be cleaned up with a transaction to back out changes (basically do the order detail then allocate) if (allocateTransaction != null) { allocateTransaction.RefId = retVal.OrderNum.ToString(); allocateTransaction.RefIdNum = retVal.OrderDetailNum; ItemTransaction.Update(allocateTransaction); } return(retVal); }