/////////////////////////////////////////////////////////// // PartInventoryAdjustmentItem.cs // Implementation of Class PartInventoryAdjustmentItem // CSLA type: Editable Child // Created on: 15-Nov-2004 8:41:17 AM // Object design: Joyce // Coded: John 15-Nov-2004 /////////////////////////////////////////////////////////// using System; using System.Data; using CSLA.Data; using GZTW.Data; using CSLA; using System.Threading; using CSLA.Security; namespace GZTW.AyaNova.BLL { /// /// PartInventoryAdjustmentItem item /// [Serializable] public class PartInventoryAdjustmentItem : BusinessBase { //CustomFields #region Attributes private bool bReadOnly; private Guid mID; private Guid mCreator; private Guid mModifier; private SmartDate mCreated; private SmartDate mModified; private Guid mPartInventoryAdjustmentID; private Guid mPartID; private PartSerials mSerialNumbers; private Guid mPartWarehouseID; private decimal mQuantityAdjustment; //read only from parent //used to set date in partserial objects //when they are updated internal SmartDate mDateAdjusted; #endregion #region Constructor private PartInventoryAdjustmentItem() { //Child object MarkAsChild(); //Set to read / write initially so that properties //can be set bReadOnly=false; //New ID mID = Guid.NewGuid(); mSerialNumbers=PartSerials.NewItems(); //Default warehouse mPartWarehouseID=PartWarehouse.DefaultWarehouseID; //Set record history to defaults mCreated = new SmartDate(DBUtil.CurrentWorkingDateTime); mModified=new SmartDate(); mCreator=Guid.Empty; mModifier=Guid.Empty; } #endregion #region Business properties /// /// Get internal id number Read only property because it's set internally, not /// externally /// public Guid ID { get { return mID; } } /// /// Parent /// public Guid PartInventoryAdjustmentID { get { return mPartInventoryAdjustmentID; } set { if(bReadOnly) ThrowSetError(); else { if(mPartInventoryAdjustmentID!=value) { mPartInventoryAdjustmentID = value; MarkDirty(); } } } } /// /// PartID /// public Guid PartID { get { return mPartID; } set { if(bReadOnly) ThrowSetError(); else { if(mPartID!=value) { mPartID = value; SetSerialNumberCollection(true); VerifyQuantity(); MarkDirty(); } } } } /// ///Ensures that serial number collection ///has as many elements as absolute value of ///Quantity adjustment ///And also ensures that all serial number items have correct part ID ///and Adjustment item ID set /// ///This function tries to preserve existing serial number entries as much ///as possible unless a Major change is indicated. ///this is important because user may have just selected wrong part initially, ///but *had* correctly entered all serial numbers /// /// If true, then wipe all serial numbers previously entered private void SetSerialNumberCollection(bool MajorChange) { if(MajorChange) mSerialNumbers.Clear(); //Is there a part selected? if(mPartID==Guid.Empty) return; //Is current part serialized? BoolFetcher b = BoolFetcher.GetItem("aPart","aTrackSerialNumber",mPartID); if(b.BoolValue) { //Yes, current part is serialized, so //adjust the number of serial numbers collection //More serial numbers than adjustment calls for? if(mSerialNumbers.Count > System.Math.Abs(mQuantityAdjustment)) { //add more decimal dRemove=mSerialNumbers.Count-(decimal)System.Math.Abs(mQuantityAdjustment); for(int x=0;x0) mSerialNumbers.Clear(); return; } //ensure all serial number items have correct //part ID and adjustment ID foreach(PartSerial ps in mSerialNumbers) { ps.AdjustmentID=mID; ps.PurchaseOrderReceiptItemID=Guid.Empty; ps.PartWarehouseID=mPartWarehouseID; ps.WorkorderItemPartID=Guid.Empty; ps.PartID=mPartID; } } /// /// Sets broken rule if quantity of adjustment is unacceptible /// (negative quantity but no accompanying stock available) /// Safe to call from anywhere /// /// private void VerifyQuantity() { //Is there a part selected? if(mPartID==Guid.Empty) return; //Is there a warehouse selected? if(mPartWarehouseID==Guid.Empty) return; //reset potential broken rule if previously tried to remove a quantity that //isn't present if(mQuantityAdjustment>-1) { //ensure any previously broken rule is removed BrokenRules.Assert("QuantityAdjustmentInvalid", "PartInventoryAdjustmentItem.Label.Error.NegativeQuantityInvalid", "QuantityAdjustment",false); } //Check if inventory sufficient when REMOVING inventory by adjustment: if(mQuantityAdjustment <0) { decimal dOnHand=PartByWarehouseInventoryValuesFetcher.GetItem(mPartID,mPartWarehouseID).QuantityOnHand; //trying to remove more than exist? BrokenRules.Assert( "QuantityAdjustmentInvalid", LocalizedTextTable.GetLocalizedTextDirect("PartInventoryAdjustmentItem.Label.Error.NegativeQuantityInvalid"), "QuantityAdjustment", dOnHand /// set by collection if this item /// is a duplicate of another in the collection /// so that broken rule will be set /// /// (duplicate meaning same part id and warehouse) /// internal bool DuplicatePart { set { BrokenRules.Assert("PartDuplicate","PartInventoryAdjustmentItem.Label.Error.PartNotUnique", "PartNumber",value); } } /// /// User ID who created this PartInventoryAdjustmentItem record /// public Guid Creator { get { return mCreator; } } /// /// Date this PartInventoryAdjustmentItem was created /// public string Created { get { return mCreated.ToString(); } } /// /// User ID who last modified this record /// public Guid Modifier { get { return mModifier; } } /// /// Last date this PartInventoryAdjustmentItem was modified /// public string Modified { get { return mModified.ToString(); } } /// /// Serial numbers collection for adjusted part, 0 to many /// NOTE: if this is a negative quantity adjustment (remove from inventory) /// and the part is serialized then the serial number should be set to the Guid /// value of the existing PartSerial record ID value as a string. /// public PartSerials SerialNumbers { get { return mSerialNumbers; } } /// /// GUID of Warehouse this adjustment is for /// (Defaults to "default" warehouse in any case) /// public Guid PartWarehouseID { get { return mPartWarehouseID; } set { if(bReadOnly) ThrowSetError(); else { if(mPartWarehouseID!=value) { mPartWarehouseID = value; SetSerialNumberCollection(true); //change of warehouse can affect quantity //validity VerifyQuantity(); BrokenRules.Assert("WarehouseRequired","Error.Object.RequiredFieldEmpty,O.PartWarehouse","PartWarehouseID",value==Guid.Empty); MarkDirty(); } } } } /// /// Quantity change /// public decimal QuantityAdjustment { get { return mQuantityAdjustment; } set { if(bReadOnly) ThrowSetError(); else { if(mQuantityAdjustment!=value) { mQuantityAdjustment = value; SetSerialNumberCollection(false); VerifyQuantity(); MarkDirty(); } } } } /// /// Throw an error when a read only user /// tries to set a property /// (this should normally never be called unless someone is using the developer api since the UI /// should prevent it from happening initially) /// private void ThrowSetError() { throw new System.Security.SecurityException ( string.Format ( LocalizedTextTable.GetLocalizedTextDirect("Error.Security.NotAuthorizedToChange"), LocalizedTextTable.GetLocalizedTextDirect("O.PartInventoryAdjustmentItem") ) ); } #endregion #region System.object overrides /// /// /// /// public override string ToString() { return "PartInventoryAdjustmentItem" + mID.ToString(); } /// /// /// /// /// public override bool Equals(Object obj) { if ( obj == null || GetType ( ) != obj.GetType ( ) ) return false; PartInventoryAdjustmentItem c=(PartInventoryAdjustmentItem)obj; return (mID==c.ID); } /// /// /// /// public override int GetHashCode() { return ("PartInventoryAdjustmentItem" + mID.ToString()).GetHashCode(); } #endregion #region Static methods /// /// Create new PartInventoryAdjustmentItem /// Sets RootObjectID, RootObjectType /// internal static PartInventoryAdjustmentItem NewItem(Guid ParentID) { if(AyaBizUtils.Right("Object.PartInventoryAdjustment")>(int)SecurityLevelTypes.ReadOnly) { PartInventoryAdjustmentItem c= new PartInventoryAdjustmentItem(); c.mPartInventoryAdjustmentID=ParentID; return c; } else throw new System.Security.SecurityException( string.Format( LocalizedTextTable.GetLocalizedTextDirect("Error.Security.NotAuthorizedToCreate"), LocalizedTextTable.GetLocalizedTextDirect("O.PartInventoryAdjustmentItem"))); } /// /// Get an PartInventoryAdjustmentItem from passed in data reader /// /// /// internal static PartInventoryAdjustmentItem GetItem(SafeDataReader dr) { if(AyaBizUtils.Right("Object.PartInventoryAdjustment")>(int)SecurityLevelTypes.NoAccess) { PartInventoryAdjustmentItem child = new PartInventoryAdjustmentItem(); child.Fetch(dr); return child; } else throw new System.Security.SecurityException( string.Format( LocalizedTextTable.GetLocalizedTextDirect("Error.Security.NotAuthorizedToRetrieve"), LocalizedTextTable.GetLocalizedTextDirect("O.PartInventoryAdjustmentItem"))); } #endregion #region DAL DATA ACCESS #region Update /// /// Update /// /// /// internal void Update( Guid ParentID, IDbTransaction tr) { //PartInventoryAdjustmentItem is unchangeable once saved, add only: if(!IsNew) throw new System.ApplicationException ( string.Format ( LocalizedTextTable.GetLocalizedTextDirect("Error.Object.NotChangeable"), LocalizedTextTable.GetLocalizedTextDirect("O.PartInventoryAdjustmentItem") ) ); #region Delete (Is new so allowed here) if(IsDeleted) { //This is a little different than normal because //this is always a new object at this point. We check for that at the //top of this update method (see above if you don't believe me! ). this.mSerialNumbers.Clear(); MarkNew(); return; } #endregion //get modification time temporarily, if update succeeds then //set to this time System.DateTime dtModified = DBUtil.CurrentWorkingDateTime; #region Add part inventory adjustment item to database DBCommandWrapper cm = null; //It's always an add, there can be no updates for this object once it's been saved //so normal rules don't applyh here for updating cm=DBUtil.GetCommandFromSQL( "INSERT INTO aPartInventoryAdjustmentItem ( " + "aID, aPartInventoryAdjustmentID, aPartID, aPartWarehouseID, " + "aQuantityAdjustment, aCreated,aModified,aCreator,aModifier) values " + "(@ID,@PartInventoryAdjustmentID,@PartID,@PartWarehouseID, " + "@QuantityAdjustment,@Created,@Modified,@CurrentUserID,@CurrentUserID)" ); //PartInventoryAdjustmentItem fields cm.AddInParameter("@PartInventoryAdjustmentID",DbType.Guid, mPartInventoryAdjustmentID); cm.AddInParameter("@PartID",DbType.Guid, mPartID); cm.AddInParameter("@QuantityAdjustment",DbType.Decimal,mQuantityAdjustment); cm.AddInParameter("@PartWarehouseID",DbType.Guid, mPartWarehouseID); //Standard fields cm.AddInParameter("@ID",DbType.Guid,this.mID); cm.AddInParameter("@CurrentUserID",DbType.Guid, CurrentUserID); cm.AddInParameter("@Created",DbType.DateTime, DBUtil.ToUTC(mCreated.Date)); cm.AddInParameter("@Modified",DbType.DateTime, DBUtil.ToUTC(dtModified)); DBUtil.DB.ExecuteNonQuery(cm, tr); #endregion #region serial numbers update //Update Serial numbers, either removing availability of existing ones if negative adjustment //by doing a direct update query //or if positive adjustment then setting data in the serial number collection //correctly and saving the collection as normal foreach(PartSerial ps in mSerialNumbers) { //Removing? if(this.mQuantityAdjustment<0) { DBCommandWrapper cmSerialUpdate = null; //removing, so fire off a query to set available //to false, adjustmentid to this id etc//AAVAILABLE //Changed: 31-Aug-2006 when selecting a pre-existing serial //number for a negative adjustment, the info form is //putting the partserial record ID value in the SerialNumber field //instead of the actual serial number as it is when it's entered manually //for a positive adjustment, no idea why, but it was causing serial //numbers to not be set to available=false cmSerialUpdate=DBUtil.GetCommandFromSQL( "UPDATE aPartSerial SET " + "AADJUSTMENTID=@AdjustmentID, aModified=@Modified, aModifier=@CurrentUserID, " + " AAVAILABLE=@False "+ " WHERE aID = @ID "); cmSerialUpdate.AddInParameter("@AdjustmentID",DbType.Guid, this.mID); cmSerialUpdate.AddInParameter("@False",DbType.Boolean, false); cmSerialUpdate.AddInParameter("@ID",DbType.Guid, new Guid(ps.SerialNumber)); cmSerialUpdate.AddInParameter("@CurrentUserID",DbType.Guid, CurrentUserID); cmSerialUpdate.AddInParameter("@Modified",DbType.DateTime, DBUtil.ToUTC(dtModified)); DBUtil.DB.ExecuteNonQuery(cmSerialUpdate, tr); } else { //Adding ps.Available=true; ps.sdDateReceived=this.mDateAdjusted; ps.PartID=this.mPartID; ps.PurchaseOrderReceiptItemID=Guid.Empty; ps.PartWarehouseID=this.mPartWarehouseID; ps.WorkorderItemPartID=Guid.Empty; ps.AdjustmentID=this.mID; } } //SerialNumbers is a grandchild collection //it must be called to update regardless of whether //the receipt item record is dirty or not, otherwise it would never get saved //(But only if there is a positive adjustment happening (adding more)) if(this.mQuantityAdjustment>0) mSerialNumbers.Update(tr); #endregion #region adjust inventory //v8port: Why is this done in code instead of in the query itself avoiding the fetch? //Get current quantity on hand for this item decimal newQuantity=PartByWarehouseInventoryValuesFetcher.GetItem(mPartID,mPartWarehouseID,tr).QuantityOnHand; //adjust... newQuantity=newQuantity+mQuantityAdjustment; //Update... DBCommandWrapper cmUpdateInventory = null; cmUpdateInventory = DBUtil.GetCommandFromSQL( "UPDATE aPartByWarehouseInventory SET aQuantityOnHand=@NewQuantity, " + "aModifier=@CurrentUserID, aModified=@Modified " + "WHERE (aPartID = @PartID) AND (aPartWarehouseID " + "= @PartWarehouseID)" ); cmUpdateInventory.AddInParameter("@PartID",DbType.Guid,mPartID); cmUpdateInventory.AddInParameter("@PartWarehouseID",DbType.Guid,mPartWarehouseID); cmUpdateInventory.AddInParameter("@CurrentUserID",DbType.Guid, CurrentUserID); cmUpdateInventory.AddInParameter("@Modified",DbType.DateTime, DBUtil.ToUTC(dtModified)); cmUpdateInventory.AddInParameter("@NewQuantity",DbType.Decimal,newQuantity); DBUtil.DB.ExecuteNonQuery(cmUpdateInventory, tr); #endregion MarkOld();//db is now synched with object //Successful update so //change modification time to match this.mModified.Date=dtModified; } #endregion update #region Fetch /// /// Populate this object from the values in the datareader passed to it /// /// private void Fetch(SafeDataReader dr) { //Standard items mCreated=DBUtil.ToLocal(dr.GetSmartDate("aCreated")); mCreator=dr.GetGuid("aCreator"); mModified=DBUtil.ToLocal(dr.GetSmartDate("aModified")); mModifier=dr.GetGuid("aModifier"); //PartInventoryAdjustmentItem specific items mID=dr.GetGuid("aID"); mPartInventoryAdjustmentID=dr.GetGuid("aPartInventoryAdjustmentID"); mPartID=dr.GetGuid("aPartID"); mQuantityAdjustment=dr.GetDecimal("aQuantityAdjustment"); mPartWarehouseID=dr.GetGuid("aPartWarehouseID"); //Populate child serial numbers collection //Since SerialNumbers is a grandchild collection //will be loaded by the SerialNumbers collection //object who will do the database call based on //the ID of this object and it's root object type mSerialNumbers=PartSerials.GetItems(RootObjectTypes.PartInventoryAdjustment,mID); //Get access rights level bReadOnly=AyaBizUtils.Right("Object.PartInventoryAdjustment")<(int)SecurityLevelTypes.ReadWrite; MarkOld(); } #endregion fetch #endregion }//end PartInventoryAdjustmentItem }//end namespace GZTW.AyaNova.BLL