using GB5Shared.DTO.Framework.Criteria;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.QueryExecutor;
using MMDAL.DTO.Allocation;
using MMDAL.DTO.BOM;
using MMDAL.Query.Allocation;
using MMDAL.Query.BOM;
using Newtonsoft.Json;
using static GB5Shared.GB5Constant.Constant;
namespace MMDAL.CustomCode.Allocation
{
///
/// Data Access Layer for Allocation operations.
/// Executes raw SQL via and returns JSON-serialized results.
///
public class AllocationDAL : IAllocationDAL
{
private readonly IQueryExecutor _QueryExecutor;
public AllocationDAL(IQueryExecutor QueryExecutor)
{
_QueryExecutor = QueryExecutor;
}
// ─────────────────────────────────────────────────────────────────────
// GetAllocation
// ─────────────────────────────────────────────────────────────────────
///
public async Task GetAllocation(int AllocationId, LoginDTO LoginDTO)
{
try
{
string Sql = AllocationQB.GET_ALLOCATION;
var Parameters = new { allocationid = AllocationId };
AllocationDTO Result = await _QueryExecutor.QuerySingleAsync(LoginDTO, Sql, Parameters);
return JsonConvert.SerializeObject(Result);
}
catch (Exception)
{
throw;
}
}
// ─────────────────────────────────────────────────────────────────────
// LoadFromAllocationPicklist (SQL Server path)
// ─────────────────────────────────────────────────────────────────────
///
///
/// GB4 Migration Notes:
/// • Replaced nested for-loop criteria extraction with a single LINQ pass via
/// helper — avoids the repeated if/else chain.
/// • String concatenation SQL building is preserved as-is from GB4 because the
/// underlying query builder () relies on placeholder
/// replacement tokens (:replacestring, :showonlyavailbletowork, etc.).
/// Parameterised binding for these dynamic fragments is not yet supported by
/// the query builder; migrate when the QB supports it.
/// • SQL injection risk on HeaderName / ItemSearch: both values come from UI input
/// and are embedded with LIKE '% … %'. TODO: switch to parameterised LIKE once
/// the query executor supports it.
///
public async Task LoadFromAllocationPicklist(
int FirstNumber,
int MaxResult,
CriteriaDTO CriteriaDTO,
LoginDTO LoginDTO)
{
try
{
// ── 1. Extract filter values from criteria ──────────────────
int OUId = ExtractInt(CriteriaDTO, "ouid", -1);
int ItemWiseTag = ExtractInt(CriteriaDTO, "itemwisetag", 1);
int ShowOnlyAvailableToWork = ExtractInt(CriteriaDTO, "showonlyavailbletowork", 0);
int LoadForBizTypeId = ExtractInt(CriteriaDTO, "loadforbiztypeid", 0);
string HeaderName = ExtractString(CriteriaDTO, "headername");
// ItemCode and ItemName both map to the same search string; last one wins (GB4 behaviour preserved).
string ItemSearch = ExtractString(CriteriaDTO, "itemcode");
if (string.IsNullOrEmpty(ItemSearch))
ItemSearch = ExtractString(CriteriaDTO, "itemname");
// ── 2. Build item-wise SELECT fragment ──────────────────────
// When itemwisetag == 0, expand the SELECT to include item/SKU columns.
string ItemWiseSelectFragment = ItemWiseTag == 0
? ",ItemId,ItemCode,ItemName,SKUId,SKUCode,SKUName,AllocationTotalQuantity as AllocationQuantity"
: string.Empty;
// ── 3. Build base SQL from query builder ────────────────────
string Sql = AllocationQB.GET_NEWALLOCATION_BASED_DOCNO;
Sql = Sql.Replace(":replacestring", ItemWiseSelectFragment);
Sql = Sql.Replace(":showonlyavailbletowork", ShowOnlyAvailableToWork.ToString());
// ── 4. Apply biz-transaction-type pending quantity filter ────
// When LoadForBizTypeId is provided, filter pending qty across all
// quantity types (Good / Rejected / Rework / Other) based on the
// MBIZTRANSACTIONTYPE configuration flags.
if (LoadForBizTypeId != 0)
{
Sql = Sql.Replace(":loadfrombiztype", ",MBIZTRANSACTIONTYPE Loadtotype");
Sql += "\r\n" +
"and((loadtotype.isgoodquantity = 0 and a.pendingquantity > 0)\r\n" +
"or (loadtotype.isrejectedquantity = 0 and a.pendingrejectedquantity > 0)\r\n" +
"or (loadtotype.isreworkquantity = 0 and a.pendingreworkquantity > 0)\r\n" +
"or (loadtotype.isotherquantity = 0 and a.pendingotherquantity > 0))\r\n";
}
else
{
Sql = Sql.Replace(":loadfrombiztype", string.Empty);
Sql += "\r\n and a.pendingquantity > 0\r\n";
}
// ── 5. Optional OU filter ───────────────────────────────────
if (OUId != -1)
Sql += $" and (a.OUID = {OUId})";
// ── 6. Apply common picklist criteria (date range, biz class, etc.) ──
Sql = ApplyAllocationPicklistCriteria(Sql, CriteriaDTO, LoginDTO);
// ── 7. Optional document header / party reference search ────
if (!string.IsNullOrWhiteSpace(HeaderName))
Sql += $" and (a.DOCUMENTNUMBER like '%{HeaderName}%' or a.PARTYREFERENCENUMBER like '%{HeaderName}%')";
// ── 8. Optional item code / name search ────────────────────
if (!string.IsNullOrWhiteSpace(ItemSearch))
Sql += $" and (item.ItemCode like '%{ItemSearch}%' or item.ItemName like '%{ItemSearch}%')";
// ── 9. Close inner query and append GROUP BY + OFFSET/FETCH paging ─
// GB4 paging was handled by the NHibernate lazy-loader (FirstNumber, MaxResult).
// GB5 IQueryExecutor.QueryAsync has no skip/take overload, so pagination is
// embedded directly into SQL using OFFSET/FETCH NEXT — SQL Server 2012+ syntax.
Sql += " ) x ";
Sql += ItemWiseTag == 1
? AllocationQB.GROUPBY_NEWALLOCATION_BASED_DOCNO
: AllocationQB.GROUPBY_NEWALLOCATION_BASED_DOCNO_ITEM;
// Append OFFSET/FETCH after ORDER BY (already included in GROUPBY constants).
Sql += $"\r\nOFFSET {FirstNumber} ROWS FETCH NEXT {MaxResult} ROWS ONLY";
// ── 10. Execute paged query ─────────────────────────────────
// QueryAsync returns IEnumerable; materialise with ToList() for serialisation.
IEnumerable Results = await _QueryExecutor
.QueryAsync(LoginDTO, Sql);
return JsonConvert.SerializeObject(Results.ToList());
}
catch (Exception)
{
throw;
}
}
// ─────────────────────────────────────────────────────────────────────
// GetPendingAllocationForOracle (Oracle path — was GetPendingNew in GB4)
// ─────────────────────────────────────────────────────────────────────
///
///
/// GB4 Migration Notes:
/// • Renamed from GetPendingNew → GetPendingAllocationForOracle to clearly
/// communicate its purpose and prevent accidental use on SQL Server.
/// • Head-type branching (ObjectMMHead vs ObjectIndent) is preserved.
/// • The nested for-loop criteria extraction is replaced with helper methods.
/// • FilterOuId overriding OUId logic is preserved exactly as in GB4.
/// • NeedAllTag == 1 (full item detail expand) is preserved under ObjectMMHead.
///
public async Task GetPendingAllocationForOracle(CriteriaDTO CriteriaDTO, LoginDTO LoginDTO)
{
try
{
// ── 1. Extract all filter values ────────────────────────────
int BizClassId = ExtractInt(CriteriaDTO, "biztransactionclassid", 0);
int HeadTypeId = ExtractInt(CriteriaDTO, "objectheadertypeid", 0);
int ObjTypeId = ExtractInt(CriteriaDTO, "objecttypeid", 0);
int ItemWiseTag = ExtractInt(CriteriaDTO, "itemwisetag", -1);
int ProcessId = ExtractInt(CriteriaDTO, "headerprocessid", 0);
int OUId = ExtractInt(CriteriaDTO, "ouid", 0);
int FilterOUId = ExtractInt(CriteriaDTO, "filterouid", -1);
int PartyBranchId = ExtractInt(CriteriaDTO, "partybranchid", 0);
int OrderId = ExtractInt(CriteriaDTO, "orderid", 0);
int LoadForBizTypeId = ExtractInt(CriteriaDTO, "loadforbiztypeid", 0);
int IsMultiple = ExtractInt(CriteriaDTO, "ismultiple", 0);
int CombineIndent = ExtractInt(CriteriaDTO, "combineindent", -1);
int ItemPartyTag = ExtractInt(CriteriaDTO, "itempartytag", -1);
int NeedAllTag = ExtractInt(CriteriaDTO, "needall", 0);
int Priority = ExtractInt(CriteriaDTO, "priority", 0);
int PriorityTag = Priority != 0 ? 1 : 0;
int BeamType = ExtractInt(CriteriaDTO, "beamtypetag", 0);
int OnlyRemainingPackQty = ExtractInt(CriteriaDTO, "onlyremainingpackqty", 1);
int AllocationId = ExtractInt(CriteriaDTO, "allocationid", -1);
int IndentNature = ExtractInt(CriteriaDTO, "indentnature", 0);
int Incharge = ExtractInt(CriteriaDTO, "incharge", 0);
int WorkCenterId = ExtractInt(CriteriaDTO, "workcenterid", 0);
int FabricId = ExtractInt(CriteriaDTO, "fabricid", 0);
int MerchId = ExtractInt(CriteriaDTO, "merchandiserid", 0);
int MachineTypeId = ExtractInt(CriteriaDTO, "machinetypeid", 0);
int MachineId = ExtractInt(CriteriaDTO, "machineid", 0);
int StoreId = ExtractInt(CriteriaDTO, "storeid", 0);
string PartyId = ExtractString(CriteriaDTO, "partyid");
if (string.IsNullOrEmpty(PartyId))
PartyId = ExtractString(CriteriaDTO, "partybranchparty.id");
string ItemIds = ExtractString(CriteriaDTO, "itemids");
string ItemCode = ExtractString(CriteriaDTO, "itemcode");
string HeaderName = ExtractString(CriteriaDTO, "headername");
string IndentObjectTypeId = ExtractString(CriteriaDTO, "indentobjecttypeid");
// FilterOuId overrides OUId when explicitly provided (GB4 behaviour preserved).
if (FilterOUId != -1)
OUId = FilterOUId;
string Sql = string.Empty;
// ── 2. MM Document head type branch ─────────────────────────
if (HeadTypeId == EntityConstant.OBJECTMMHEAD)
{
Sql = BuildMMHeadQuery(
BizClassId, ItemWiseTag, NeedAllTag, LoadForBizTypeId,
OnlyRemainingPackQty, AllocationId, OUId, PartyId,
ItemCode, HeaderName, ItemPartyTag, CriteriaDTO, LoginDTO);
}
// ── 3. Indent head type branch ───────────────────────────────
else if (HeadTypeId == EntityConstant.OBJECTINDENT)
{
Sql = BuildIndentQuery(
BizClassId, IsMultiple, CombineIndent, OUId,
PartyBranchId, ItemCode, CriteriaDTO, LoginDTO);
}
// ── 4. Replace named parameters for head/object types ────────
Sql = ReplaceNamedParams(Sql,
new[] { "objectheadertypeid", "objecttypeid" },
new object[] { HeadTypeId, ObjTypeId });
// ── 5. Execute and return ────────────────────────────────────
// QueryAsync returns IEnumerable; materialise with ToList() for serialisation.
IEnumerable Results = await _QueryExecutor
.QueryAsync(LoginDTO, Sql);
return JsonConvert.SerializeObject(Results.ToList());
}
catch (Exception)
{
throw;
}
}
///
///
/// GB4 Source: AllocationBLL.GetPendingNewListForProductionPlan ->
/// AllocationDAL.GetPendingNewListForProductionPlan ("production plan For Hilife").
/// GB4 route: /Allocation/PendingAllocationSelectList — the closest textual match
/// to GB5's route name "GetSelectListTPendingAllocation", which is why this is the
/// method actually wired to that endpoint (not GetPendingSelectList, whose real
/// GB4 route is /Allocation/SelectList and returns the simpler AllocationDTO shape).
///
/// GB4 Migration Notes:
/// • Only PartyId and OUId are read from criteria and actually used in the query —
/// GB4 extracts many more fields (WorkCenterId, FabricId, MerchId, Priority, etc.)
/// but never references them in this specific method body; that dead extraction
/// is not reproduced here.
/// • HeadTypeId/NeedAllTag branching is preserved exactly:
/// - OBJECTMMHEAD && NeedAllTag == 0 → GET_PENDING_ALLOCATION_DOCUMENT_PRODUCTIONPLAN
/// (warp-type/label columns, the "Hilife" production-plan variant)
/// - OBJECTMMHEAD && NeedAllTag == 1 → GET_PENDING_ALLOCATION_FORALL (shared with
/// GetPendingAllocationForOracle's full-mode branch)
/// - OBJECTINDENT → GET_PENDING_ALLOCATION_FORINDENT (shared)
/// • ApplyGetPendingNewSources (GB4's generic criteria-field mapper) is not called
/// here, matching GetPendingAllocationForOracle's existing precedent — GB5's
/// AllocationQB.ApplyCriteria is not yet wired to the shared criteria engine.
///
public async Task GetPendingNewListForProductionPlan(CriteriaDTO CriteriaDTO, LoginDTO LoginDTO)
{
try
{
int HeadTypeId = ExtractInt(CriteriaDTO, "objectheadertypeid", 0);
int ObjTypeId = ExtractInt(CriteriaDTO, "objecttypeid", 0);
int NeedAllTag = ExtractInt(CriteriaDTO, "needall", 0);
int PartyId = ExtractInt(CriteriaDTO, "partyid", 0);
int PartyBranchId = ExtractInt(CriteriaDTO, "partybranchid", 0);
int OUId = ExtractInt(CriteriaDTO, "ouid", 0);
string Sql;
if (HeadTypeId == EntityConstant.OBJECTMMHEAD)
{
if (NeedAllTag == 0)
{
Sql = AllocationQB.GET_PENDING_ALLOCATION_DOCUMENT_PRODUCTIONPLAN;
Sql = Sql.Replace(":HeaderTablePart", ",MPARTY PARTY,MPARTYBRANCH PARTYBRANCH,MADDRESS ADDRESS,MCITY CITY");
Sql += " AND B.PARTYID = PARTY.PARTYID" +
" AND PARTYBRANCH.PARTYBRANCHID = B.PARTYBRANCHID" +
" AND PARTYBRANCH.DEFAULTADDRESSID = ADDRESS.ADDRESSID" +
" AND ADDRESS.CITYID = CITY.CITYID";
if (PartyId != 0)
Sql += $" AND B.PARTYID = {PartyId}";
if (OUId != 0)
Sql += $" AND B.OUID = {OUId}";
string SelectPart =
"DISTINCT B.DOCUMENTNUMBER AS HeaderName," +
"DTL.REQUIREDDELIVERYDATE AS DeliveryDate," +
"DTL.ACTUALQUANTITY AS ActualQuantity," +
"DTL.DOCUMENTDETAILID AS MMDetailId," +
"A.OBJECTHEADERID AS AllocationObjectHeaderId," +
"B.DOCUMENTDATE AS HeaderDate," +
"B.REFERENCENUMBER AS RefNumber," +
"B.REFERENCEDATE AS RefDate," +
"PARTY.PARTYID AS PartyId," +
"PARTY.PARTYNAME AS PartyName," +
"E.PENDINGQUANTITY AS AllocationQuantity," +
"LABEL.DESIGNNO AS DesignNo," +
"ITEM.ITEMID AS ItemId," +
"ITEM.ITEMNAME AS ItemName," +
"ITEM.ITEMCODE AS ItemCode," +
"MSKU.SKUID AS SKUId," +
"MSKU.SKUCODE AS SKUCode," +
"MSKU.SKUNAME AS SKUName," +
"PARTYBRANCH.PARTYBRANCHID AS PartyBranchId," +
"PARTYBRANCH.PARTYBRANCHNAME AS PartyBranchName," +
"LABEL.WARPTYPEID AS WarpTypeId," +
"GCM.GCMCODE AS WarpTypeCode," +
"GCM.GCMNAME AS WarpTypeName," +
"LABEL.LABELID AS LabelId," +
"LABEL.LABELCODE AS LabelCode," +
"LABEL.LABELNAME AS LabelName," +
"LABEL.NOOFREPEATS AS Repeats";
Sql = Sql.Replace(":HeaderReplacePart", SelectPart);
}
else
{
Sql = AllocationQB.GET_PENDING_ALLOCATION_FORALL;
Sql = Sql.Replace(":HeaderTablePart", ",MPARTY PARTY,MPARTYBRANCH PARTYBRANCH,MSKU SKU,MITEM ITEM");
Sql += " AND B.PARTYID = PARTY.PARTYID" +
" AND PARTYBRANCH.PARTYBRANCHID = B.PARTYBRANCHID" +
" AND DTL.ITEMID = ITEM.ITEMID" +
" AND DTL.SKUID = SKU.SKUID";
if (PartyId != 0)
Sql += $" AND B.PARTYID = {PartyId}";
if (OUId != 0)
Sql += $" AND B.OUID = {OUId}";
string SelectPart =
",B.DOCUMENTNUMBER AS HeaderName," +
"B.DOCUMENTDATE AS HeaderDate," +
"B.REFERENCENUMBER AS RefNumber," +
"B.REFERENCEDATE AS RefDate," +
"PARTY.PARTYID AS PartyId," +
"PARTY.PARTYNAME AS PartyName," +
"PARTYBRANCH.PARTYBRANCHID AS PartyBranchId," +
"PARTYBRANCH.PARTYBRANCHNAME AS PartyBranchName," +
"ITEM.ITEMID AS ItemId," +
"ITEM.ITEMCODE AS ItemCode," +
"ITEM.ITEMNAME AS ItemName," +
"SKU.SKUID AS SKUId," +
"SKU.SKUCODE AS SKUCode," +
"SKU.SKUNAME AS SKUName," +
"DTL.TRANSACTIONACTUALQUANTITY AS ActualQuantity";
Sql = Sql.Replace(":HeaderReplacePart", SelectPart);
}
Sql += " AND A.OBJECTID = DTL.DOCUMENTDETAILID";
Sql += " ORDER BY B.DOCUMENTNUMBER";
}
else if (HeadTypeId == EntityConstant.OBJECTINDENT)
{
Sql = AllocationQB.GET_PENDING_ALLOCATION_FORINDENT;
Sql = Sql.Replace(":HeaderTablePart", ",MPARTYBRANCH PARTYBRANCH,MSKU SKU,MITEM ITEM");
Sql += " AND PARTYBRANCH.PARTYBRANCHID = IND.PARTYBRANCHID" +
" AND INDDTL.ITEMID = ITEM.ITEMID" +
" AND INDDTL.SKUID = SKU.SKUID";
if (PartyBranchId != 0)
Sql += $" AND IND.PARTYBRANCHID = {PartyBranchId}";
if (OUId != 0)
Sql += $" AND IND.OUID = {OUId}";
string SelectPart =
",IND.INDENTNUMBER AS HeaderName," +
"IND.INDENTDATE AS HeaderDate," +
"IND.REFERENCENUMBER AS RefNumber," +
"IND.REFERENCEDATE AS RefDate," +
"IND.PARTYBRANCHID AS PartyBranchId," +
"PARTYBRANCH.PARTYBRANCHNAME AS PartyBranchName";
Sql = Sql.Replace(":HeaderReplacePart", SelectPart);
Sql += " AND A.OBJECTID = INDMTL.INDENTMATERIALID AND A.ALLOCATIONNATURE = 1";
}
else
{
return JsonConvert.SerializeObject(new List());
}
Sql = ReplaceNamedParams(Sql,
new[] { "objectheadertypeid", "objecttypeid" },
new object[] { HeadTypeId, ObjTypeId });
IEnumerable Results = await _QueryExecutor
.QueryAsync(LoginDTO, Sql);
return JsonConvert.SerializeObject(Results.ToList());
}
catch (Exception)
{
throw;
}
}
// ─────────────────────────────────────────────────────────────────────
// Private helpers
// ─────────────────────────────────────────────────────────────────────
///
/// Applies the standard picklist criteria mapping (biz class, date range, party, item, etc.)
/// to the SQL string using the common criteria engine.
/// Mirrors GB4's ApplyLoadFromAllocationPicklist.
///
private string ApplyAllocationPicklistCriteria(string Sql, CriteriaDTO CriteriaDTO, LoginDTO LoginDTO)
{
if (CriteriaDTO == null) return Sql;
// Maps UI criteria field names → SQL column aliases used in the query.
var CriteriaFieldMap = new Dictionary
{
{ "BizTransactionClassId", "d.BIZTRANSACTIONCLASSID" },
{ "BizTransactionSubClassId", "c.BIZTRANSACTIONSUBCLASSID" },
{ "HeaderIndentName", "a.DOCUMENTNUMBER" },
{ "Nature", "a.ALLOCATIONNATURE" },
{ "BizTransactionTypeId", "a.BizTransactionTypeId" },
{ "ObjectHeaderTypeId", "a.ObjectHeaderTypeId" },
{ "ObjectHeaderId", "a.ObjectHeaderId" },
{ "ObjectTypeId", "a.ObjectTypeId" },
{ "indentobjecttypeid", "a.ObjectTypeId" },
{ "ObjectId", "a.ObjectId" },
{ "ProcessId", "a.ProcessId" },
{ "ItemId", "dtl.ItemId" },
{ "PartyId", "party.PARTYID" },
{ "DocumentId", "a.OBJECTHEADERID" },
{ "BizTransactionId", "biz.BIZTRANSACTIONID" },
{ "loadforbiztypeid", "loadtotype.BIZTRANSACTIONTYPEID"},
{ "IndentId", "ind.INDENTID" },
{ "PeriodFromDate", "a.DOCUMENTDATE" },
{ "PeriodToDate", "a.DOCUMENTDATE" },
};
return AllocationQB.ApplyCriteria(Sql, CriteriaFieldMap, CriteriaDTO, LoginDTO);
}
///
/// Builds the SQL fragment for MM Document head-type pending allocation queries.
/// Handles both compact (NeedAllTag == 0) and full item-detail (NeedAllTag == 1) modes.
///
private string BuildMMHeadQuery(
int BizClassId, int ItemWiseTag, int NeedAllTag, int LoadForBizTypeId,
int OnlyRemainingPackQty, int AllocationId, int OUId, string PartyId,
string ItemCode, string HeaderName, int ItemPartyTag,
CriteriaDTO CriteriaDTO, LoginDTO LoginDTO)
{
string Sql;
if (NeedAllTag == 0)
{
// ── Compact mode: document-level grouping ──────────────────
Sql = AllocationQB.GET_PENDING_ALLOCATION_DOCUMENT_NEW;
// Filter to only rows where remaining pack quantity > 0 (Added Apr 2023, Redmine #38762).
if (OnlyRemainingPackQty == 0)
Sql += "\r\n and Isnull(dtl.GOODQUANTITY,0) - isnull(packdtl.GOODQUANTITY,0) > 0\r\n";
// Purchase Requisitions carry PARTYID = -1 so we must include that
// to avoid filtering them out when IsItemFromParty is enabled.
if (!string.IsNullOrEmpty(PartyId))
Sql += $" and (b.PARTYID in ({PartyId}) or b.PARTYID = -1)";
if (OUId != 0)
Sql += $" and (b.OUID = {OUId} or dtl.INTEROUID = {OUId})";
if (AllocationId != -1)
Sql += $" and (b.AllocationId = {AllocationId} or dtl.AllocationId = {AllocationId})";
// Build SELECT replacement part based on item-wise grouping flag.
string SelectPart = BuildMMHeadSelectPart(ItemWiseTag);
Sql = Sql.Replace(":HeaderReplacePart", SelectPart);
Sql = Sql.Replace(":loadfrombiztype",
LoadForBizTypeId != 0 ? ",MBIZTRANSACTIONTYPE Loadtotype" : string.Empty);
// Pending quantity filter: biz-type-aware or simple > 0.
Sql = AppendPendingQtyFilter(Sql, LoadForBizTypeId, LoadForBizTypeId != 0,
UseAlias: "a", RemoveExisting: "and a.PENDINGQUANTITY>0");
Sql += " AND B.STATUS IN (1)\r\n";
if (ItemPartyTag == 0)
Sql += " and itp.ITEMPARTYID=itpd.ITEMPARTYID and itp.PARTYID=b.PARTYID and itp.ITEMID=dtl.ITEMID";
if (!string.IsNullOrWhiteSpace(HeaderName))
Sql += $" AND (b.DOCUMENTNUMBER like '%{HeaderName}%' or b.partyreferencenumber like '%{HeaderName}%')";
Sql += " order by b.DOCUMENTNUMBER";
}
else
{
// ── Full mode: item-level detail including SKU, UOM, actual qty ──
Sql = AllocationQB.GET_PENDING_ALLOCATION_FORALL;
string FromPart = ",MPARTY party,MPARTYBRANCH partybranch,MSKU sku,MITEM item,MALLOCATION mal,MUSER mod,MUOM stkuom,MUOM puruom";
if (LoadForBizTypeId != 0)
FromPart += ",MBIZTRANSACTIONTYPE Loadtotype";
Sql = Sql.Replace(":HeaderTablePart", FromPart);
Sql += " and b.PARTYID=party.PARTYID" +
" and partybranch.PARTYBRANCHID=b.PARTYBRANCHID" +
" and dtl.ITEMID=item.ITEMID" +
" and dtl.SKUID=sku.SKUID" +
" and mal.ALLOCATIONID=b.ALLOCATIONID" +
" and item.STOCKUOMID=stkuom.UOMID" +
" and item.PURCHASEUOMID=puruom.UOMID";
if (!string.IsNullOrEmpty(PartyId))
Sql += $" and b.PARTYID in ({PartyId})";
if (OUId != 0)
Sql += $" and (b.OUID = {OUId} or dtl.INTEROUID = {OUId})";
Sql += " and mod.USERID=b.CREATEDBYID";
string SelectPart = BuildMMHeadFullSelectPart();
Sql = Sql.Replace(":HeaderReplacePart", SelectPart);
Sql = AppendPendingQtyFilter(Sql, LoadForBizTypeId, LoadForBizTypeId != 0,
UseAlias: "e", RemoveExisting: "and e.PENDINGQUANTITY>0");
Sql += " AND B.STATUS IN (1)";
if (!string.IsNullOrWhiteSpace(ItemCode))
Sql += $" AND (item.ItemCode LIKE ('%{ItemCode}%') or item.ItemName LIKE ('%{ItemCode}%'))";
}
Sql += " and a.OBJECTID=dtl.DOCUMENTDETAILID";
return Sql;
}
///
/// Builds the compact MM-head SELECT replacement clause.
/// Item-wise grouping (ItemWiseTag == -1) omits pending quantity columns.
///
private static string BuildMMHeadSelectPart(int ItemWiseTag)
{
string Base =
"distinct b.DOCUMENTNUMBER as HeaderName," +
"a.OBJECTHEADERID as AllocationObjectHeaderId," +
"b.DOCUMENTDATE as HeaderDate," +
"b.ALLOCATIONID as DocumentAllocationId," +
"mal.ALLOCATIONNAME as DocumentAllocationName," +
"ISNULL(b.AMENDMENTSLNO,0) as AmendmentSlNo," +
"b.PARTYREFERENCENUMBER as RefNumber," +
"b.PARTYREFERENCEDATE as RefDate," +
"b.TOTALQUANTITY as AllocationTotalQuantity," +
"b.REFERENCENUMBER as HeadReferenceNumber,";
Base += ItemWiseTag == -1
? "ISNULL(party.PARTYID,-1) as PartyId,ISNULL(party.PARTYNAME,'') as PartyName,"
: "ISNULL(party.PARTYID,-1) as PartyId,ISNULL(party.PARTYNAME,'NONE') as PartyName," +
"e.PENDINGQUANTITY as AllocationQuantity," +
"e.PENDINGREJECTEDQUANTITY as AllocationRejectedQuantity," +
"e.PENDINGREWORKQUANTITY as AllocationReworkQuantity," +
"e.PENDINGOTHERQUANTITY as AllocationOtherQuantity," +
"ISNULL(UOM.UOMNAME,'') as StockUOMName";
Base += " partybranch.PARTYBRANCHID as PartyBranchId," +
"partybranch.PARTYBRANCHNAME as PartyBranchName," +
"mod.USERNAME as ModifiedBy";
return Base;
}
///
/// Builds the full item-detail SELECT replacement clause used when NeedAllTag == 1.
/// Includes SKU, UOM (stock and purchase), actual quantity, and modifier fields.
/// Added STOCKUOMID and following fields for Redmine #37788 (Jun 2023).
///
private static string BuildMMHeadFullSelectPart()
{
return ",b.DOCUMENTNUMBER as HeaderName," +
"b.DOCUMENTDATE as HeaderDate," +
"b.ALLOCATIONID as DocumentAllocationId," +
"mal.ALLOCATIONNAME as DocumentAllocationName," +
"b.PARTYREFERENCENUMBER as RefNumber," +
"b.PARTYREFERENCEDATE as RefDate," +
"party.PARTYID as PartyId," +
"party.PARTYNAME as PartyName," +
"partybranch.PARTYBRANCHID as PartyBranchId," +
"partybranch.PARTYBRANCHNAME as PartyBranchName," +
"item.ITEMID as ItemId," +
"item.ITEMCODE as ItemCode," +
"item.ITEMNAME as ItemName," +
"sku.SKUID as SKUId," +
"sku.SKUCODE as SKUCode," +
"sku.SKUNAME as SKUName," +
"dtl.SLNO as SlNo," +
"dtl.TRANSACTIONACTUALQUANTITY as ActualQuantity," +
"mod.USERNAME as ModifiedBy," +
"item.STOCKUOMID as StockUOMId," +
"stkuom.UOMCODE as StockUOMCode," +
"stkuom.UOMNAME as StockUOMName," +
"item.PURCHASEUOMID as PurchaseUOMId," +
"puruom.UOMCODE as PurchaseUOMCode," +
"puruom.UOMNAME as PurchaseUOMName";
}
///
/// Builds the SQL fragment for Indent head-type pending allocation queries.
/// Supports single-line, multi-BOM, and combine-indent query variants.
///
private string BuildIndentQuery(
int BizClassId, int IsMultiple, int CombineIndent, int OUId,
int PartyBranchId, string ItemCode, CriteriaDTO CriteriaDTO, LoginDTO LoginDTO)
{
string Sql;
if (IsMultiple == 1)
{
Sql = AllocationQB.GET_PENDING_ALLOCATION_FORINDENT_NEW;
// BIZ Class -1399999916 indicates a multi-routing/BOM scenario (Redmine CR #31009, Jan 2021).
if (BizClassId == -1399999916)
Sql += "\r\n and (posteddtl.ROUTINGDETAILSLNO=1 or ISNULL(bomitemsection.Indmatcount,0)>1)\r\n";
}
else if (IsMultiple == 0)
{
Sql = AllocationQB.GET_PENDING_ALLOCATION_FORINDENT;
}
else
{
throw new InvalidOperationException("IsMultiple value must be 0 or 1 for Indent head type.");
}
// Combine-indent overrides the normal indent query when requested.
if (CombineIndent == 0)
Sql = AllocationQB.GET_PENDING_ALLOCATION_FORINDENT_BASED_ON_COMBINE_INDENT_BASED;
string FromPart = ",MPARTYBRANCH partybranch,MSKU sku,MITEM item";
Sql = Sql.Replace(":HeaderTablePart", FromPart);
Sql += " and partybranch.PARTYBRANCHID=ind.PARTYBRANCHID" +
" and inddtl.ITEMID=item.ITEMID" +
" and inddtl.SKUID=sku.SKUID";
if (PartyBranchId != 0)
Sql += $" and ind.PARTYBRANCHID = {PartyBranchId}";
if (OUId != 0)
Sql += $" and ind.OUID = {OUId}";
string SelectPart =
",ind.IndentNumber as HeaderName," +
"ind.IndentDate as HeaderDate," +
"ind.ReferenceNumber as RefNumber," +
"ind.ReferenceDate as RefDate," +
"ind.PartyBranchId as PartyBranchId," +
"partybranch.PartyBranchName as PartyBranchName," +
"item.ITEMID as ItemId," +
"item.ITEMCODE as ItemCode," +
"item.ITEMNAME as ItemName";
Sql = Sql.Replace(":HeaderReplacePart", SelectPart);
// Indent queries use INDENTID instead of DOCUMENTID in criteria.
Sql = Sql.Replace("and b.DOCUMENTID", "and ind.INDENTID");
Sql += " and (a.OBJECTID=inddtl.INDENTDETAILID or a.OBJECTID=indmtl.INDENTMATERIALID)";
if (!string.IsNullOrWhiteSpace(ItemCode))
Sql += $" AND (item.ItemCode LIKE ('%{ItemCode}%') or item.ItemName LIKE ('%{ItemCode}%'))";
return Sql;
}
///
/// Appends the pending quantity WHERE clause.
/// When is true, removes the simple
/// pendingquantity > 0 placeholder and adds per-quantity-type conditions
/// based on MBIZTRANSACTIONTYPE flags (Good / Rejected / Rework / Other).
///
private static string AppendPendingQtyFilter(
string Sql,
int LoadForBizTypeId,
bool UseBizTypeFilter,
string UseAlias,
string RemoveExisting)
{
if (UseBizTypeFilter)
{
Sql = Sql.Replace(RemoveExisting, string.Empty);
Sql += $" and Loadtotype.BIZTRANSACTIONTYPEID = {LoadForBizTypeId}\r\n";
Sql += $"and (\r\n" +
$" (loadtotype.isgoodquantity = 0 and {UseAlias}.pendingquantity > 0) or\r\n" +
$" (loadtotype.isrejectedquantity = 0 and {UseAlias}.pendingrejectedquantity > 0) or\r\n" +
$" (loadtotype.isreworkquantity = 0 and {UseAlias}.pendingreworkquantity > 0) or\r\n" +
$" (loadtotype.isotherquantity = 0 and {UseAlias}.pendingotherquantity > 0)\r\n" +
$")";
}
else
{
Sql += $" and {UseAlias}.PENDINGQUANTITY > 0\r\n";
}
return Sql;
}
///
/// Replaces named SQL parameters (e.g. :objectheadertypeid) with their runtime values.
/// Mirrors GB4's CommonFunctionFrameDAL.ChangedByNamedParam.
///
private static string ReplaceNamedParams(string Sql, string[] Names, object[] Values)
{
for (int i = 0; i < Names.Length; i++)
Sql = Sql.Replace($":{Names[i]}", Values[i]?.ToString() ?? string.Empty);
return Sql;
}
// ─────────────────────────────────────────────────────────────────────
// Criteria extraction helpers
// ─────────────────────────────────────────────────────────────────────
///
/// Finds the first matching criteria attribute by field name (case-insensitive)
/// and returns its integer value, or if not found.
///
private static int ExtractInt(CriteriaDTO CriteriaDTO, string FieldName, int DefaultValue)
{
string? Raw = FindCriteriaValue(CriteriaDTO, FieldName);
return Raw != null && int.TryParse(Raw, out int Parsed) ? Parsed : DefaultValue;
}
///
/// Finds the first matching criteria attribute by field name (case-insensitive)
/// and returns its string value, or if not found.
///
private static string ExtractString(CriteriaDTO CriteriaDTO, string FieldName)
{
return FindCriteriaValue(CriteriaDTO, FieldName) ?? string.Empty;
}
///
/// Iterates the criteria sections and attributes to find a field value by name.
/// Returns null when the field is absent.
///
private static string? FindCriteriaValue(CriteriaDTO CriteriaDTO, string FieldName)
{
if (CriteriaDTO == null) return null;
string Key = FieldName.ToLower();
foreach (var Section in CriteriaDTO.SectionCriteriaList)
{
foreach (var Attribute in Section.AttributesCriteriaList)
{
if (Attribute.FieldName.ToLower() == Key)
return Attribute.FieldValue?.ToString();
}
}
return null;
}
// GetSelectListTPendingAllocation moved to MMBLL.Allocation.PendingAllocationBLL /
// MMDAL.Query.Allocation.PendingAllocationSelectListQB — it now runs through the
// typed List-Query pipeline (IQueryBuilder + GenericListHandler) instead of raw
// CriteriaDTO-driven SQL here, so it gets typed criteria binding and no longer
// depends on the generic CriteriaBuilder blindly turning every client field into
// a literal SQL column reference.
}
}