331 lines
14 KiB
C#
331 lines
14 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.BusinessMgr.Net.Standard.Base.Mgr;
|
|
using Sarand.BusinessMgr.Net.Standard.Dbas.Data;
|
|
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 Sarand.Net.Standard.Dbas.Base;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace Sarand.BusinessMgr.Net.Standard.Dbas.Mgr
|
|
{
|
|
//=======================================================================
|
|
internal partial class SbmStockMgr_Dbas : SbmStockMgr
|
|
{
|
|
//-------------------------------------------------------------------
|
|
// Data members.
|
|
public class DataMembers
|
|
{
|
|
public SacsDatabase db = null;
|
|
public SbmDbDataItem.SbmDatabaseInfo dbInfo;
|
|
}
|
|
private readonly DataMembers mbr = new DataMembers();
|
|
|
|
//-------------------------------------------------------------------
|
|
// Construction.
|
|
public SbmStockMgr_Dbas(SbmInternals internals) :
|
|
base(internals)
|
|
{
|
|
Initialise();
|
|
}
|
|
public SbmStockMgr_Dbas(ISbmUserOrganisationContext userContext) :
|
|
base(userContext)
|
|
{
|
|
Initialise();
|
|
}
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
}
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Initialisation.
|
|
private void Initialise()
|
|
{
|
|
mbr.db = (SbmGlobals.Application.GetBusMgrDatabase(SessionContext) as SacsDatabase);
|
|
mbr.dbInfo = new SbmDbDataItem.SbmDatabaseInfo(mbr.db.DatabaseName, mbr.db.GetTableDfns());
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Properties.
|
|
protected ISacsDatabase Database { get { return mbr.db; } }
|
|
protected SbmDbDataItem.SbmDatabaseInfo DbInfo { get { return mbr.dbInfo; } }
|
|
|
|
//-------------------------------------------------------------------
|
|
// Utilities.
|
|
public override bool StockDatabaseIsValid()
|
|
{
|
|
ClearErrorState();
|
|
if (!Database.TableExists(SbmDbDfn.GetTableName(SbmDbDfn.TableIdx.Location))) return SetErrorState("Location table does not exist");
|
|
if (!Database.TableExists(SbmDbDfn.GetTableName(SbmDbDfn.TableIdx.Movement))) return SetErrorState("Movement table does not exist");
|
|
if (!Database.TableExists(SbmDbDfn.GetTableName(SbmDbDfn.TableIdx.StockLevel))) return SetErrorState("Stock Level table does not exist");
|
|
return true;
|
|
}
|
|
|
|
public override bool ColumnExists(SbmDbDfn.ColumnIdx columnIdx)
|
|
{
|
|
SbmDbDfn.TableIdx tableIdx = SbmDbDfn.GetTableForColumn(columnIdx);
|
|
String schemaName = SbmDbDfn.GetSchemaName(tableIdx, false);
|
|
String tableName = SbmDbDfn.GetTableName(tableIdx, false);
|
|
String columnName = SbmDbDfn.GetColumnName(columnIdx, false);
|
|
return DbInfo.ColumnExists(schemaName, tableName, columnName);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Stock Calculations.
|
|
|
|
public override bool CalculateStockLevels(IEnumerable<SysId> commodityIds, DbUpdateInfo dbUpdateInfo)
|
|
{
|
|
bool calculate = true;
|
|
List<SysId> commodityIdsDone = new List<SysId>();
|
|
foreach (SysId commodityId in commodityIds)
|
|
{
|
|
if (SysId.IsValid(commodityId) && !commodityIdsDone.Contains(commodityId))
|
|
{
|
|
calculate = CalculateStockLevels(commodityId, dbUpdateInfo);
|
|
commodityIdsDone.Add(commodityId);
|
|
}
|
|
}
|
|
return calculate;
|
|
}
|
|
|
|
public override bool CalculateStockLevels(SysId commodityId, DbUpdateInfo dbUpdateInfo)
|
|
{
|
|
ClearErrorState();
|
|
if (!SysId.IsValid(commodityId)) return SetErrorState("Commodity must be identified for stock calculation");
|
|
if (!TableExists(SbmDbDfn.TableIdx.Movement)) return true;
|
|
|
|
// Create a local Stock Manager to avoid interferring with this one:
|
|
DateTime dToday = DateTime.Today;
|
|
DateTime dMovementsFrom = DateTime.MinValue;
|
|
List<SbmStockLevel> stockTakes = new List<SbmStockLevel>();
|
|
Dictionary<SysId, Decimal> lLocationQuantity = new Dictionary<SysId, Decimal>();
|
|
|
|
//---------------------------------------------------------------------
|
|
// Identify the most recent Stock Take by location.
|
|
if (!HasErrorState())
|
|
{
|
|
SbmStockLevel.Params prmStockLevel = new SbmStockLevel.Params();
|
|
prmStockLevel.CommodityId = commodityId;
|
|
prmStockLevel.RestrictToCalculateStockLocations = true;
|
|
prmStockLevel.Type = Sbm.StockLevelType.StockTake;
|
|
prmStockLevel.RecordedTo = dToday;
|
|
prmStockLevel.MostRecentOnly = true;
|
|
prmStockLevel.StatusCat = SbmStockLevel.StatusCat.ConfirmedOnly;
|
|
stockTakes.AddRange(GetStockLevelList(prmStockLevel));
|
|
// Identify the most recent stock take for each location.
|
|
// NB The filter parameters above should already have done this:
|
|
for (int i = 0; i < stockTakes.Count; i++)
|
|
{
|
|
// Retrieve the details for this stock take:
|
|
SbmStockLevel stockLevel = stockTakes[i];
|
|
// Check whether there is another stock take for this location.
|
|
// This logic should not identify more than one stock take for any of the locations:
|
|
for (int j = i + 1; j < stockTakes.Count; j++)
|
|
{
|
|
SbmStockLevel stockLevelCompare = stockTakes[j];
|
|
if (stockLevelCompare.CommodityId != stockLevel.CommodityId) continue;
|
|
if (stockLevelCompare.LocationId != stockLevel.LocationId) continue;
|
|
if (stockLevelCompare.Recorded > stockLevel.Recorded)
|
|
{
|
|
// Remove the original item item from the list and reset position:
|
|
SaLogger.Debug($"{SysUtil.Fn} Duplicate stock level found for location [A].");
|
|
stockLevel = null;
|
|
stockTakes.RemoveAt(i);
|
|
i--;
|
|
break;
|
|
}
|
|
if (stockLevelCompare.Recorded <= stockLevel.Recorded)
|
|
{
|
|
// Remove this item from the list and reset position:
|
|
SaLogger.Debug("SapStockMgr_Odbc::CalculateStockLevel Duplicate stock level found for location [B].\n");
|
|
stockLevelCompare = null;
|
|
stockTakes.RemoveAt(j);
|
|
j--;
|
|
}
|
|
}
|
|
// If this stock take is still valid, make a note of its details:
|
|
if (stockLevel != null)
|
|
{
|
|
if (!lLocationQuantity.ContainsKey(stockLevel.LocationId))
|
|
lLocationQuantity[stockLevel.LocationId] = stockLevel.Quantity;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------
|
|
// Retrieve all stock movements since the most recent stock take:
|
|
if (!HasErrorState())
|
|
{
|
|
// Common database definitions:
|
|
String tableMovement = SbmDbDfn.GetTableName(SbmDbDfn.TableIdx.Movement);
|
|
String colMove_ItemType = SbmDbDfn.GetColumnName(SbmDbDfn.ColumnIdx.Movement_ItemType);
|
|
String colMove_ItemId = SbmDbDfn.GetColumnName(SbmDbDfn.ColumnIdx.Movement_ItemId);
|
|
String colMove_LocationIdFrom = SbmDbDfn.GetColumnName(SbmDbDfn.ColumnIdx.Movement_LocationIdFrom);
|
|
String colMove_LocationIdTo = SbmDbDfn.GetColumnName(SbmDbDfn.ColumnIdx.Movement_LocationIdTo);
|
|
String colMove_Quantity = SbmDbDfn.GetColumnName(SbmDbDfn.ColumnIdx.Movement_Quantity);
|
|
String colMove_Movement = SbmDbDfn.GetColumnName(SbmDbDfn.ColumnIdx.Movement_Movement);
|
|
String colMove_Status = SbmDbDfn.GetColumnName(SbmDbDfn.ColumnIdx.Movement_Status);
|
|
|
|
// Common movement filter parameters:
|
|
SbmMovement.Params prmMovement = new SbmMovement.Params();
|
|
prmMovement.ItemType = Sbm.ItemType.Commodity;
|
|
prmMovement.ItemIds.Add(commodityId);
|
|
prmMovement.StatusCat = SbmMovement.StatusCat.ActionedOnly;
|
|
|
|
//---------------------------------------------------------------
|
|
// Identify stock movements to this location (incrementing stock):
|
|
DbQuery qryIncrMove = new DbQuery(Database.DbmsType);
|
|
qryIncrMove.DistinctRows = true;
|
|
qryIncrMove.Tables.Add(tableMovement);
|
|
qryIncrMove.Columns.Add(colMove_LocationIdTo);
|
|
qryIncrMove.Columns.Add("SUM(" + colMove_Quantity + ")", "TotalQuantity");
|
|
qryIncrMove.Filters.AddRange(GetMovementQueryFilter(prmMovement));
|
|
// Filter the 'To' location to stock calculation locations:
|
|
List<String> lFilterLocation = new List<string>();
|
|
// The location is either one that we haven't seen before (ie. not in the 'Location Quantity' list):
|
|
lFilterLocation.Add(colMove_LocationIdTo + " IN (-1" + GetLocationIds_Calc().Select(id => SysId.ToString(id)).Concat("", ",") + ") AND " + colMove_LocationIdTo + " NOT IN (0" + lLocationQuantity.Keys.Select(id => SysId.ToString(id)).Concat("", ",") + ")");
|
|
// Or we have some record of it already (where we are only interested in recent movements if a stock take has occurred):
|
|
foreach (SysId locationId in lLocationQuantity.Keys)
|
|
{
|
|
SbmStockLevel stockTake = stockTakes.GetByLocationId(locationId);
|
|
if (stockTake != null)
|
|
// Location has a stock take so restrict to more recent than this date:
|
|
lFilterLocation.Add($"{colMove_LocationIdTo}={SysId.ToLong(locationId)} AND {colMove_Movement}>={DbQuery.FormatDateForCondition(DateUtil.IncrementDate(stockTake.Recorded.Date, 1))}");
|
|
else
|
|
// Location does not have a stock take so retrieve all movements:
|
|
lFilterLocation.Add($"{colMove_LocationIdTo}={SysId.ToLong(locationId)}");
|
|
}
|
|
qryIncrMove.Filters.Add(lFilterLocation.Concat(" OR ", "(", ")"));
|
|
qryIncrMove.GroupBy.Add(colMove_LocationIdTo);
|
|
using (DbResultSet resultSet = Database.ExecuteQuery(qryIncrMove.BuildQuery()))
|
|
{
|
|
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
|
|
{
|
|
NamedValue.Map values = resultSet.GetRecordValues();
|
|
SysId idLocation = new SysId(values.GetLong(colMove_LocationIdTo));
|
|
decimal quantity = values.GetDecimal("TotalQuantity");
|
|
if (lLocationQuantity.ContainsKey(idLocation))
|
|
lLocationQuantity[idLocation] += quantity;
|
|
else
|
|
lLocationQuantity[idLocation] = quantity;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
// Identify stock movements from this location (decrementing stock):
|
|
DbQuery qryDecrMove = new DbQuery(Database.DbmsType);
|
|
qryDecrMove.DistinctRows = true;
|
|
qryDecrMove.Tables.Add(tableMovement);
|
|
qryDecrMove.Columns.Add(colMove_LocationIdFrom);
|
|
qryDecrMove.Columns.Add("SUM(" + colMove_Quantity + ")", "TotalQuantity");
|
|
qryDecrMove.Filters.AddRange(GetMovementQueryFilter(prmMovement));
|
|
// The location is either one that we haven't seen before (ie. not in the 'Location Quantity' list):
|
|
lFilterLocation.Clear();
|
|
lFilterLocation.Add($"{colMove_LocationIdFrom} IN (-1{GetLocationIds_Calc().Select(id => SysId.ToString(id)).Concat("", ",")}) AND {colMove_LocationIdFrom} NOT IN (0{lLocationQuantity.Keys.Select(id => SysId.ToString(id)).Concat("", ",")})");
|
|
foreach (SysId locationId in lLocationQuantity.Keys)
|
|
{
|
|
SbmStockLevel stockTake = stockTakes.GetByLocationId(locationId);
|
|
if (stockTake != null)
|
|
// Location has a stock take so restrict to more recent than this date:
|
|
lFilterLocation.Add($"{colMove_LocationIdFrom}={SysId.ToLong(locationId)} AND {colMove_Movement}>={DbQuery.FormatDateForCondition(DateUtil.IncrementDate(stockTake.Recorded, 1))}");
|
|
else
|
|
// Location does not have a stock take so retrieve all movements:
|
|
lFilterLocation.Add($"{colMove_LocationIdFrom}={SysId.ToLong(locationId)}");
|
|
}
|
|
qryDecrMove.Filters.Add(lFilterLocation.Concat(" OR ", "(", ")"));
|
|
qryDecrMove.GroupBy.Add(colMove_LocationIdFrom);
|
|
using (DbResultSet resultSet = Database.ExecuteQuery(qryDecrMove.BuildQuery()))
|
|
{
|
|
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
|
|
{
|
|
NamedValue.Map values = resultSet.GetRecordValues();
|
|
SysId idLocation = new SysId(values.GetLong(colMove_LocationIdFrom));
|
|
decimal quantity = values.GetDecimal("TotalQuantity");
|
|
if (lLocationQuantity.ContainsKey(idLocation))
|
|
lLocationQuantity[idLocation] -= quantity;
|
|
else
|
|
lLocationQuantity[idLocation] = -quantity;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------
|
|
}
|
|
|
|
//---------------------------------------------------------------------
|
|
// Calculate the 'Current Level' stock level for each location:
|
|
if (!HasErrorState())
|
|
{
|
|
HashSet<SysId> lLocationIdUpdated = new HashSet<SysId>();
|
|
SbmStockLevel.Params prmStkLvl = new SbmStockLevel.Params();
|
|
prmStkLvl.CommodityId = commodityId;
|
|
prmStkLvl.Type = Sbm.StockLevelType.CurrentLevel;
|
|
prmStkLvl.RestrictToCalculateStockLocations = true;
|
|
|
|
foreach (SbmStockLevel stockLevel in GetStockLevelList(prmStkLvl))
|
|
{
|
|
SysId locationId = stockLevel.LocationId;
|
|
|
|
// Has this location been updated already:
|
|
if (!lLocationIdUpdated.Contains(locationId))
|
|
{
|
|
// Update the stock level's attributes:
|
|
stockLevel.Recorded = dToday;
|
|
stockLevel.Quantity = 0.0m;
|
|
if (lLocationQuantity.ContainsKey(locationId))
|
|
stockLevel.Quantity = lLocationQuantity[locationId];
|
|
stockLevel.Status = SaStatus.Current;
|
|
|
|
// Store the stock level:
|
|
if (UpdateStockLevel(stockLevel, dbUpdateInfo))
|
|
{
|
|
// Make a note of this location:
|
|
lLocationIdUpdated.Add(locationId);
|
|
}
|
|
else SetErrorState("Failed to update stock level");
|
|
}
|
|
else
|
|
{
|
|
// Location has laready been updated - remove this stock level:
|
|
DeleteStockLevel(stockLevel, dbUpdateInfo);
|
|
}
|
|
}
|
|
if (!HasErrorState())
|
|
{
|
|
// Create current stock levels for any remaining locations:
|
|
foreach (SysId locationId in lLocationQuantity.Keys)
|
|
{
|
|
decimal quantity = lLocationQuantity[locationId];
|
|
if (!lLocationIdUpdated.Contains(locationId) && GetLocationIds_Calc().Contains(locationId))
|
|
{
|
|
SbmStockLevel stockLevel = CreateStockLevel(commodityId, locationId, Sbm.StockLevelType.CurrentLevel, Sbm.StockLevelSource.System);
|
|
stockLevel.Recorded = dToday;
|
|
stockLevel.Quantity = quantity;
|
|
stockLevel.Status = SaStatus.Current;
|
|
if (InsertStockLevel(stockLevel, dbUpdateInfo))
|
|
{
|
|
}
|
|
else SetErrorState("Failed to add stock level");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------
|
|
return OK;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
}
|
|
//=======================================================================
|
|
}
|