Files

682 lines
29 KiB
C#

using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus;
using Sarand.BusinessMgr.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Sarand.BusinessMgr.Net.Standard.Base.Mgr
{
//=======================================================================
// Stock manager - Allocations.
//=======================================================================
partial class SbmStockMgr
{
//-------------------------------------------------------------------
// Definitions.
// Stock movement data.
// Used to plan and store movements to represent allocation.
private class StockMovementData
{
public StockMovementData()
{
}
public StockMovementData(DateTime movementDate, decimal quantity, bool actioned, SysId locationIdFrom, SysId locationIdTo)
{
this.MovementDate = movementDate;
this.Quantity = quantity;
this.Actioned = actioned;
this.LocationIdFrom = locationIdFrom;
this.LocationIdTo = locationIdTo;
}
// Properties.
public DateTime MovementDate { get; set; }
public decimal Quantity { get; set; }
public bool Actioned { get; set; }
public SysId LocationIdFrom { get; set; }
public SysId LocationIdTo { get; set; }
public String Notes { get; set; }
// Utilities.
public static decimal GetQuantityTotal(IEnumerable<StockMovementData> values)
{
return Math.Max(0.0m, values.Sum(item => item.Quantity));
}
};
//-------------------------------------------------------------------
// Allocations.
//-------------------------------------------------------------------
public bool AllowStockAllocations(SbmTransaction transaction)
{
// Transaction must exist:
if (transaction == null || !SysId.IsValid(transaction.Id)) return false;
// Commodity Stock must be allowed:
if (StockControlConfig.AllowStockControl()) return false;
if (AllowConfigOption(Sbm.ConfigOption.Commodities)) return false;
// Transaction must be a sales or purchase order:
if (transaction.TransactionType != Sbm.TransactionType.SalesOrder && transaction.TransactionType != Sbm.TransactionType.PurchaseOrder) return false;
// Transaction's allocations must not be managed by accounts system.
if (transaction.TransactionType != Sbm.TransactionType.SalesOrder && AccountsDfn.IsAllocationManagedByAccounts(Sbm.TransactionType.SalesOrder)) return false;
if (transaction.TransactionType != Sbm.TransactionType.PurchaseOrder && AccountsDfn.IsAllocationManagedByAccounts(Sbm.TransactionType.PurchaseOrder)) return false;
return true;
}
public bool AutoAllocateTransaction(SbmTransaction transaction)
{
if (!AllowStockAllocations(transaction)) return false;
if (transaction.GetStockAllocationStatus() != Sbm.ItemStockAllocationStatus.None) return false; // Already allocated.
bool bAutoAllocateTrans = false;
SaStatus eStatusTrans = transaction.Status;
SaStatus eStatusAutoAllocate = StockControlConfig.GetAutomaticStockAllocationStatus(transaction.TransactionType);
if (!bAutoAllocateTrans)
bAutoAllocateTrans = (eStatusAutoAllocate != SaStatus.Unknown && eStatusTrans == eStatusAutoAllocate);
if (!bAutoAllocateTrans)
bAutoAllocateTrans = (eStatusAutoAllocate == SaStatus.Preparation && (eStatusTrans == SaStatus.InProgress || eStatusTrans == SaStatus.Completed));
if (!bAutoAllocateTrans)
bAutoAllocateTrans = (eStatusAutoAllocate == SaStatus.InProgress && eStatusTrans == SaStatus.Completed);
return bAutoAllocateTrans;
}
public bool IsStockControlAllowed(SbmTransaction transaction, SbmRequirement requirement)
{
// Stock allocations allowed, commodity is stock type.
if (!AllowStockAllocations(transaction)) return false;
if (requirement == null || !SysId.IsValid(requirement.CommodityId)) return false;
// Determine whether stock should be managed for this item:
Sbm.CommodityType commodityType = CommodityMgr.GetTypeForCommodityId(requirement.CommodityId);
if (!IsStockManagedFor(commodityType)) return false;
return true;
}
public bool IsStockControlComplete(SbmTransaction transaction, SbmRequirement requirement)
{
// Either item completed.
if (transaction != null && transaction.Status == SaStatus.Completed) return true;
if (requirement != null && requirement.Status == SaStatus.Completed) return true;
return false;
}
public bool IsStockControlCancelled(SbmTransaction transaction, SbmRequirement requirement)
{
// Either item cancelled.
if (transaction == null || transaction.Status == SaStatus.Cancelled) return true;
if (requirement == null || requirement.Status == SaStatus.Cancelled) return true;
if (!SysId.IsValid(requirement.CommodityId)) return true; // Any previous allocation will be cancelled.
return false;
}
public bool AllocateStockByProductionQuantity(SbmTransaction transaction)
{
// For sales orders if stock is allocated and production quantity used.
if (!AllowStockAllocations(transaction)) return false;
if (!transaction.IsSalesType()) return false;
if (!StockControlConfig.AllocateByProductionQuantity()) return false;
return true;
}
public bool ActionStockByDispatchedQuantity(SbmTransaction transaction)
{
// For sales orders all completed dispatched items are actioned movements.
if (!AllowStockAllocations(transaction)) return false;
if (!transaction.IsSalesType()) return false;
if (!StockControlConfig.ActionByDispatchedQuantity()) return false;
return true;
}
public decimal GetAllocationForSourceItem(SbmItemId itemId)
{
ClearErrorState();
decimal allocatedQuantity = 0.0m;
SbmMovement.Params prmMovement = new SbmMovement.Params();
prmMovement.ItemTypeSource = (Sbm.ItemType)itemId.Type;
prmMovement.ItemIdsSource.Add(itemId.Id);
foreach (SbmMovement movement in GetMovementList(prmMovement))
{
if (movement.Status == SaStatus.Awaiting || movement.Status == SaStatus.Actioned)
allocatedQuantity += movement.Quantity;
}
return allocatedQuantity;
}
//===========================================================================
// T r a n s a c t i o n A l l o c a t i o n
//===========================================================================
public bool AllocateTransaction(SbmTransaction transaction, DbUpdateInfo dbUpdateInfo)
{
ClearErrorState();
if (transaction == null || !SysId.IsValid(transaction.Id))
return SetErrorState("Existing transaction must be supplied");
#if DEBUG
SaLogger.Debug($"{SysUtil.Fn} ### Allocating transaction ref {transaction.Ref} [Id={SysId.ToLong(transaction.Id)}]");
#endif
List<SbmRequirement> requirements = TransactionMgr.GetRequirementsForTransaction(transaction.Id);
RefreshAllocations(transaction, requirements, true, dbUpdateInfo);
return OK;
}
public bool UnAllocateTransaction(SbmTransaction transaction, DbUpdateInfo dbUpdateInfo)
{
ClearErrorState();
if (transaction == null || !SysId.IsValid(transaction.Id))
return SetErrorState("Existing transaction must be supplied");
#if DEBUG
SaLogger.Debug($"{SysUtil.Fn} ### Unallocating transaction ref {transaction.Ref} [Id={SysId.ToLong(transaction.Id)}]");
#endif
// Clear transaction movements:
ClearMovementsForSourceItem(Sbm.ItemType.Transaction, transaction.Id);
// Clear requirement movements:
List<SbmRequirement> requirements = TransactionMgr.GetRequirementsForTransaction(transaction.Id);
foreach (SbmRequirement requirement in requirements)
{
ClearMovementsForSourceItem(Sbm.ItemType.Requirement, requirement.Id);
requirement.ClearAllocationStatus();
}
TransactionMgr.UpdateRequirements(transaction, requirements, dbUpdateInfo);
// Update the transaction:
transaction.ClearAllocationStatus();
TransactionMgr.UpdateTransaction(transaction, dbUpdateInfo);
return OK;
}
//===========================================================================
// A l l o c a t e S t o c k
//===========================================================================
public bool AllocateStock(SbmTransaction transaction, SbmRequirement requirement, DbUpdateInfo dbUpdateInfo)
{
ClearErrorState();
if (transaction == null || !SysId.IsValid(transaction.Id) || requirement == null || !SysId.IsValid(requirement.Id)) return SetErrorState("Transaction and requirement must be supplied");
//-----------------------------------------------------------------------
if (AllowStockAllocations(transaction))
{
// This transaction can have stock allocated.
// Transactions may need to be auto-allocated under some circumstances:
if (AutoAllocateTransaction(transaction))
{
AllocateTransaction(transaction, dbUpdateInfo);
}
// Allocate stock against item:
if (transaction.GetStockAllocationStatus() == Sbm.ItemStockAllocationStatus.Full)
requirement.FullAllocation = true;
if (RefreshAllocation(transaction, requirement, false, dbUpdateInfo))
{
#if DEBUG
SaLogger.Debug($"{SysUtil.Fn} ### Allocating stock for '{requirement.ProductCode}' [Id={SysId.ToLong(requirement.Id)}] on transaction ref {transaction.Ref} [Id={SysId.ToLong(transaction.Id)}]");
#endif
// Shouldn't need to update transaction as its allocation status should not have changed.
}
}
//-----------------------------------------------------------------------
else
{
// Stock is not allocated directly against this item.
// Check if allocation of associated transactions is affected.
}
//-----------------------------------------------------------------------
return OK;
}
//===========================================================================
// R e f r e s h A l l o c a t i o n s
//===========================================================================
public bool RefreshAllocations(SbmTransaction transaction, List<SbmRequirement> requirements, bool fullAllocation, DbUpdateInfo dbUpdateInfo)
{
ClearErrorState();
if (!AllowStockAllocations(transaction)) return true;
bool autoAllocateTrans = (fullAllocation || AutoAllocateTransaction(transaction));
bool transFullyAllocated = transaction.FullAllocation || requirements.IsFullAllocation();
bool transPartiallyAllocated = transaction.PartialAllocation || requirements.IsPartialAllocation();
if (!autoAllocateTrans && !transFullyAllocated && !transPartiallyAllocated) return true;
// Initialise location information:
if (!StockDatabaseIsValid()) return true;
if (!SysId.IsValid(LocationIdDefaultSales)) SetErrorState("Default sales location not specified");
// Identify the stock location for this transaction.
bool bTransIntoStock = transaction.IsPurchaseType();
HashSet<SysId> commodityIdsToCalculate = new HashSet<SysId>();
SysId locationIdStockTransaction = transaction.LocationIdStock;
if (!SysId.IsValid(locationIdStockTransaction)) locationIdStockTransaction = transaction.IsSalesType() ? LocationIdDefaultSales : LocationIdDefaultPurchase;
if (!HasErrorState())
{
//-------------------------------------------------------------------
// Verify Stock Movements that are directly associated with the transaction:
SbmMovement.Params prmMovement = new SbmMovement.Params();
prmMovement.ItemType = Sbm.ItemType.Commodity;
prmMovement.ItemTypeSource = Sbm.ItemType.Transaction;
prmMovement.ItemIdsSource.Add(transaction.Id);
foreach (SbmMovement movement in GetMovementList(prmMovement))
{
// Set the location specified by the transaction:
if (bTransIntoStock)
{
if (SysId.IsValid(movement.LocationIdTo) && !movement.LocationIdTo.Equals(locationIdStockTransaction))
movement.LocationIdTo = locationIdStockTransaction;
}
else
{
if (SysId.IsValid(movement.LocationIdFrom) && movement.LocationIdFrom.Equals(locationIdStockTransaction))
movement.LocationIdFrom = locationIdStockTransaction;
}
// Set the status:
if (transaction.TransactionStatusCat == SbmTransaction.StatusCat.Open)
movement.Status = SaStatus.Awaiting;
else if (transaction.Status == SaStatus.Completed)
movement.Status = SaStatus.Actioned;
else if (transaction.Status == SaStatus.Cancelled)
movement.Status = SaStatus.Cancelled;
if (movement.Changed)
{
if (UpdateMovement(movement, dbUpdateInfo))
commodityIdsToCalculate.Add(movement.ItemId);
else SetErrorState($"Failed to update stock movement - {ErrorStateMessage}");
}
}
//-------------------------------------------------------------------
// Verify stock movements associated with each requirement:
if (requirements == null)
requirements = TransactionMgr.GetRequirementsForTransaction(transaction.Id);
foreach (SbmRequirement requirement in requirements)
{
RefreshAllocation(transaction, requirement, fullAllocation, dbUpdateInfo);
if (HasErrorState()) break;
}
//-------------------------------------------------------------------
// Update transaction allocation status:
if (!HasErrorState())
{
transaction.ClearAllocationStatus();
if (requirements.IsFullAllocation()) transaction.FullAllocation = true;
else if (requirements.IsPartialAllocation()) transaction.PartialAllocation = true;
TransactionMgr.UpdateTransaction(transaction, dbUpdateInfo);
}
//-------------------------------------------------------------------
// Recalculate stock levels if required:
if (!commodityIdsToCalculate.IsEmpty())
{
CalculateStockLevels(commodityIdsToCalculate, dbUpdateInfo);
}
}
return OK;
}
//===========================================================================
// R e f r e s h A l l o c a t i o n
//===========================================================================
public bool RefreshAllocation(SbmTransaction transaction, SbmRequirement requirement, bool fullAllocation, DbUpdateInfo dbUpdateInfo)
{
ClearErrorState();
if (!AllowStockAllocations(transaction)) return true;
if (requirement == null || !SysId.IsValid(requirement.Id)) return SetErrorState("Invalid requirement specified.");
// Requirement allocation status:
// Check whether stock allocation is automatic:
bool autoAllocateTrans = (fullAllocation || AutoAllocateTransaction(transaction));
bool reqntFullyAllocated = (requirement.FullAllocation || autoAllocateTrans);
// Identify default stock location for the transaction:
SysId locationIdStockTransaction = transaction.LocationIdStock;
if (!SysId.IsValid(locationIdStockTransaction)) locationIdStockTransaction = transaction.IsSalesType() ? LocationIdDefaultSales : LocationIdDefaultPurchase;
// Identify the stock location for this requirement:
SysId locationIdStockRequirement = requirement.LocationIdStock;
if (!SysId.IsValid(locationIdStockRequirement)) locationIdStockRequirement = locationIdStockTransaction;
// Calculate required movements:
List<StockMovementData> lMovement = GetRequiredMovements(transaction, requirement, locationIdStockRequirement, reqntFullyAllocated);
// Store the movements:
if (StoreAllocation(transaction, requirement, lMovement, dbUpdateInfo))
{
// Derive requirement's allocation status:
if (reqntFullyAllocated)
requirement.FullAllocation = true;
else
requirement.PartialAllocation = true;
// NB 'StoreAllocation' recalculates commodity stock if necessary - doesn't need to be done here.
}
// Update the requirement if necessary (eg. allocation status changed):
if (requirement.Changed)
{
TransactionMgr.UpdateRequirement(requirement, dbUpdateInfo);
}
return OK;
}
//===========================================================================
// R e q u i r e d M o v e m e n t s
//===========================================================================
// Stock Control.
// 1. Awaiting movement quantity:
// - None if order completed.
// - If 'Allocation by production qty (Sales Orders only):
// - Production quantity into stock.
// NB Actioned on last dispatch date if qty actioned by dispatch.
// - Otherwise:
// - Sales transaction:
// - Order quantity out of stock.
// - Purchase transaction:
// - Order quantity into stock.
// 2. Actioned movement quantity
// - If 'Actioned by dispatched quantity':
// - Actioned movements for each completed dispatched quantity (Sales Orders only) by dispatch completion date.
// - Otherwise, if order completed:
// - Actioned movement for order quantoty by order completion date.
// - In either case, if order completed and 'Allocation by production qty:
// - Additional actioned movement for production quantity into stock
//---------------------------------------------------------------------------
private List<StockMovementData> GetRequiredMovements(SbmTransaction transaction, SbmRequirement requirement, SysId locationId, bool fullyAllocate)
{
List<StockMovementData> movements = new List<StockMovementData>();
if (transaction == null || requirement == null) return movements;
if (!IsStockControlAllowed(transaction, requirement) || IsStockControlCancelled(transaction, requirement)) return movements;
// Stock location.
SysId locationIdStock = locationId;
if (!SysId.IsValid(locationIdStock))
locationIdStock = (transaction.IsPurchaseType() ? LocationIdDefaultPurchase : LocationIdDefaultSales);
if (!SysId.IsValid(locationIdStock)) return movements;
// Stock Control configuration:
bool movementIntoStock = transaction.IsPurchaseType();
bool stockControlComplete = IsStockControlComplete(transaction, requirement);
bool allocateByProductionQty = AllocateStockByProductionQuantity(transaction);
bool actionByDispatchedQuantity = ActionStockByDispatchedQuantity(transaction);
// Production quantity.
decimal quantityProduction = requirement.QuantityProd;
// Action movements for completed dispatched items:
decimal quantityActionedDispatchedTotal = 0.0m;
DateTime finalDispatchDate = DateTime.MinValue;
if (actionByDispatchedQuantity)
{
// Stock is 'actioned' by quantities on associated completed sales dispatch items.
SbmRequirement.Params prmRequirement = new SbmRequirement.Params();
prmRequirement.TransactionType = Sbm.TransactionType.SalesDispatch;
prmRequirement.RequirementIdsSource.Add(requirement.Id);
prmRequirement.TransactionStatusCat = SbmTransaction.StatusCat.Completed;
prmRequirement.StatusCat = SbmRequirement.StatusCat.NotCancelled;
// Define columns to retrieve:
ColumnDfn colTransRef = TransactionMgr.GetRequirementColumnDfn(SbmRequirement.Column.Trans_Ref);
ColumnDfn colTransClosed = TransactionMgr.GetRequirementColumnDfn(SbmRequirement.Column.Trans_Closed);
ColumnDfn colReqntQty = TransactionMgr.GetRequirementColumnDfn(SbmRequirement.Column.Quantity);
List<ColumnDfn> queryColumns = new List<ColumnDfn>() { colTransRef, colTransClosed, colReqntQty };
// Sort columns.
prmRequirement.SortColumns.Add(colTransClosed);
// Query dispatch items.
using (DbResultSet resultSet = TransactionMgr.ExecuteRequirementQuery(queryColumns, prmRequirement))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
// Create actioned movement for this completed dispatched quantity.
StockMovementData stockMovementData = new StockMovementData()
{
MovementDate = values.GetDate(colTransClosed.DataName),
Quantity = values.GetDecimal(colReqntQty.DataName),
LocationIdFrom = locationIdStock,
Actioned = true,
Notes = "Dispatch ref. " + values.GetString(colTransRef.DataName)
};
movements.Add(stockMovementData);
// Update totals.
quantityActionedDispatchedTotal += stockMovementData.Quantity;
if (!DateUtil.IsValid(finalDispatchDate) || finalDispatchDate < stockMovementData.MovementDate) finalDispatchDate = stockMovementData.MovementDate;
}
}
}
// Derive stock allocation date from transaction and requirement.
// If the stock is actioned by dispatched qty and at least one completed
// dispatch was found, use the latest dispatch date.
DateTime stockAllocationDate = transaction.GetStockAllocationDate(requirement);
if (stockControlComplete && DateUtil.IsValid(finalDispatchDate))
stockAllocationDate = finalDispatchDate;
// Additional movements.
if (!allocateByProductionQty)
{
// Movement for order quantity (reduced by actioned dispatched quantity).
decimal fQtyMovement = 0.0m;
if (fullyAllocate)
fQtyMovement = requirement.Quantity;
else
// Requirement is not fully allocated - allocated quantity remains as it is already.
fQtyMovement = GetAllocationForSourceItem(requirement.GetItemId());
fQtyMovement -= quantityActionedDispatchedTotal;
if (fQtyMovement > 0.01m)
{
StockMovementData stockMovementData = new StockMovementData()
{
MovementDate = stockAllocationDate,
Quantity = fQtyMovement,
Actioned = stockControlComplete,
Notes = "Order ref. " + transaction.Ref
};
if (movementIntoStock)
stockMovementData.LocationIdTo = locationIdStock;
else stockMovementData.LocationIdFrom = locationIdStock;
movements.Add(stockMovementData);
}
}
else
{
// Movement for remaining production quantity.
if (quantityProduction > 0.01m)
{
StockMovementData stockMovementData = new StockMovementData()
{
MovementDate = stockAllocationDate,
Quantity = quantityProduction,
LocationIdTo = locationIdStock,
Actioned = stockControlComplete,
Notes = "Production qty"
};
movements.Add(stockMovementData);
}
}
return movements;
}
//===========================================================================
// S t o r e A l l o c a t i o n
//===========================================================================
// Public version.
// Single stock location specified and rules determine whether movement is into or out of stock.
// Only used by Sage Import?
private bool StoreAllocation(SbmTransaction transaction, SbmRequirement requirement, decimal quantityAwaiting, decimal quantityActioned, SysId locationIdStockPreferred, DbUpdateInfo dbUpdateInfo)
{
ClearErrorState();
if (transaction == null || requirement == null) return SetErrorState("Item not specified for stock allocation.");
// Delivered quantity cannot be negative:
if (quantityActioned < 0.0m)
{
quantityActioned = 0.0m;
#if DEBUG
SaLogger.Debug($"{SysUtil.Fn} ### Negative delivered quantity specified.");
#endif
}
// Stock location.
SysId locationIdStockFrom = new SysId();
SysId locationIdStockTo = new SysId();
PopulateDefaultLocations();
SysId locationIdStock = locationIdStockPreferred;
if (!SysId.IsValid(locationIdStock))
locationIdStock = transaction.IsPurchaseType() ? LocationIdDefaultPurchase : LocationIdDefaultSales;
if (SysId.IsValid(locationIdStock))
{
bool bIntoStock = transaction.IsPurchaseType();
if (quantityAwaiting < 0)
{
bIntoStock = !bIntoStock;
quantityAwaiting *= -1;
}
if (bIntoStock)
locationIdStockTo = locationIdStock;
else locationIdStockFrom = locationIdStock;
}
else return SetErrorState("Invalid stock location specified.");
// Calculate the required movements:
List<StockMovementData> movements = new List<StockMovementData>();
if (quantityActioned > 0.01m)
{
// Actioned quantity movement:
StockMovementData stockMovementData = new StockMovementData()
{
MovementDate = transaction.GetStockAllocationDate(requirement),
Quantity = quantityActioned,
LocationIdFrom = locationIdStockFrom,
LocationIdTo = locationIdStockTo,
Actioned = true,
Notes = ""
};
movements.Add(stockMovementData);
}
if (quantityAwaiting > 0.01m)
{
// Awaiting quantity movement:
StockMovementData stockMovementData = new StockMovementData()
{
MovementDate = transaction.GetStockAllocationDate(requirement),
Quantity = quantityAwaiting,
LocationIdFrom = locationIdStockFrom,
LocationIdTo = locationIdStockTo,
Actioned = false,
Notes = ""
};
movements.Add(stockMovementData);
}
return StoreAllocation(transaction, requirement, movements, dbUpdateInfo);
}
//---------------------------------------------------------------------------
// Internal version.
// To or From stock location is specified as direction of movement has been pre-determined.
private bool StoreAllocation(SbmTransaction transaction, SbmRequirement requirement, List<StockMovementData> requiredMovements, DbUpdateInfo dbUpdateInfo)
{
ClearErrorState();
if (transaction == null || requirement == null) return SetErrorState("Item not specified for stock allocation.");
if (!AllowStockAllocations(transaction)) return true;
// Commodity list - those that need recalculation once the movements have been updated:
List<SysId> commodityIds = new List<SysId>();
commodityIds.AddId(requirement.CommodityId);
bool movementsUpdated = false;
// Movement type:
Sbm.MovementType movementTypeAllocation = transaction.IsPurchaseType() ? Sbm.MovementType.PurchaseAllocation : Sbm.MovementType.SalesAllocation;
if (!AllocateStockByProductionQuantity(transaction) && !ActionStockByDispatchedQuantity(transaction) && requiredMovements.Count > 1)
{
#if DEBUG
SaLogger.Debug($"{SysUtil.Fn} ### More than 1 required movement specified for 'Standard' stock control.");
#endif
}
//-----------------------------------------------------------------------
// Retrieve stock movements for this requirement:
List<SbmMovement> movements = new List<SbmMovement>();
SbmMovement.Params prmMovement = new SbmMovement.Params();
prmMovement.ItemType = Sbm.ItemType.Commodity;
prmMovement.ItemTypeSource = Sbm.ItemType.Requirement;
prmMovement.ItemIdsSource.Add(requirement.Id);
foreach (SbmMovement stockMovement in GetMovementList(prmMovement))
{
if (SysId.IsValid(requirement.CommodityId) && stockMovement.ItemType == Sbm.ItemType.Commodity && stockMovement.ItemId == requirement.CommodityId)
{
// Movement is for correct commodity:
movements.Add(stockMovement);
}
else
{
if (stockMovement.ItemType == Sbm.ItemType.Commodity && !commodityIds.Contains(stockMovement.ItemId))
commodityIds.Add(stockMovement.ItemId);
// Movement no longer required:
DeleteMovement(stockMovement, dbUpdateInfo);
}
}
movements.Sort(SbmMovementList.SortMode.MovementSortAscend);
//---------------------------------------------------------------
// Store the required movements:
foreach (StockMovementData movementRequired in requiredMovements)
{
// Reuse existing movement if available.
SbmMovement movementStock = null;
if (movements.Any())
{
movementStock = movements.ElementAt(0);
movements.RemoveAt(0);
}
// Create new movement if none available:
if (movementStock == null)
movementStock = CreateMovement(Sbm.ItemType.Commodity, requirement.CommodityId, movementTypeAllocation);
if (movementStock != null)
{
movementStock.Movement = movementRequired.MovementDate;
movementStock.Quantity = movementRequired.Quantity;
movementStock.LocationIdFrom = movementRequired.LocationIdFrom;
movementStock.LocationIdTo = movementRequired.LocationIdTo;
movementStock.ItemTypeSource = Sbm.ItemType.Requirement;
movementStock.ItemIdSource = requirement.Id;
if (!StringUtil.IsBlank(movementRequired.Notes))
movementStock.Notes = movementRequired.Notes;
movementStock.Status = movementRequired.Actioned ? SaStatus.Actioned : SaStatus.Awaiting;
if (SysId.IsValid(movementStock.Id))
{
// Update existing movement:
if (movementStock.Changed)
{
if (UpdateMovement(movementStock, dbUpdateInfo))
movementsUpdated = true;
else SetErrorState("Failed to update movement - " + ErrorStateMessage);
}
}
else
{
if (InsertMovement(movementStock, dbUpdateInfo))
movementsUpdated = true;
else SetErrorState("Failed to add movement - " + ErrorStateMessage);
}
}
else SetErrorState("Failed to create movement - " + ErrorStateMessage);
}
//-------------------------------------------------------------------
// Remove any remaining movements:
foreach (SbmMovement movement in movements)
{
DeleteMovement(movement, dbUpdateInfo);
}
//---------------------------------------------------------------------
// Calculate the current stock take for this commodity (and any others touched above):
if (!HasErrorState() && movementsUpdated)
{
CalculateStockLevels(commodityIds, dbUpdateInfo);
}
return OK;
}
//-------------------------------------------------------------------
}
//=======================================================================
}