Only returns false

Im trying to get a true is laborqty, including what the user is trying to report, is greater than production qty and false if not. However, this reports everything as false.

Erp.Tables.LaborDtl LaborDtl;
Erp.Tables.LaborDtl LaborDtl1;
Erp.Tables.JobAsmbl JobOpDtl;
string Company = "";
decimal LaborQty = 0.0m;
decimal LaborQty1 = 0.0m;
decimal LaborQty2 = 0.0m;
int OperationSeq = 0;
int LaborSeqDtl = 0;
decimal ProductionQty = 0.0m;
foreach (var ttLaborDtl_iterator in (from ttLaborDtl_Row in ttLaborDtl
where string.Equals(ttLaborDtl_Row.RowMod, IceRow.ROWSTATE_ADDED, StringComparison.OrdinalIgnoreCase) || string.Equals(ttLaborDtl_Row.RowMod, IceRow.ROWSTATE_UPDATED, StringComparison.OrdinalIgnoreCase)
select ttLaborDtl_Row))
{
var ttLaborDtlRow = ttLaborDtl_iterator;
if (ttLaborDtlRow.LaborType == "P")
{
Company = ttLaborDtlRow.Company;
LaborSeqDtl = ttLaborDtlRow.LaborDtlSeq;
LaborQty = ttLaborDtlRow.LaborQty;
JobOpDtl = (from JobOpDtl_Row in Db.JobAsmbl
where string.Compare(JobOpDtl_Row.Company,ttLaborDtlRow.Company, true) == 0 &&
JobOpDtl_Row.JobNum == ttLaborDtlRow.JobNum && JobOpDtl_Row.AssemblySeq == ttLaborDtlRow.AssemblySeq
select JobOpDtl_Row).FirstOrDefault();
if (JobOpDtl != null)
{
ProductionQty = JobOpDtl.RequiredQty;
}
foreach (var LaborDtl1_iterator in (from LaborDtl1_Row in Db.LaborDtl
where LaborDtl1_Row.Company == Company && LaborDtl1_Row.JobNum == ttLaborDtlRow.JobNum && LaborDtl1_Row.AssemblySeq == ttLaborDtlRow.AssemblySeq && LaborDtl1_Row.OprSeq == ttLaborDtlRow.OprSeq && LaborSeqDtl != LaborDtl1_Row.LaborDtlSeq
select LaborDtl1_Row))
{
LaborDtl1 = LaborDtl1_iterator;

                 LaborQty2 =  LaborQty2 + LaborDtl1.LaborQty;     
             
             }
LaborQty2 = LaborQty2 + LaborQty;
bool Overs;

if (LaborQty2 > ProductionQty)
{
     Overs = true;
} 
else
{
  Overs = false;
}
}
}

You might not be getting any results back from your query against LaborDtl. Try string.Compare as you had in your JobAsml query:

string.Compare(LaborDtl1_Row.Company, Company, true) == 0

I would actually just use string.Equals, unless you have reason to believe that you’ll need to ignore case:

string.Equals(LaborDtl1_Row.Company, Company)

You’ve also got some major scope issues that will bite you if your main foreach iterates more than once.
All of your variable declarations at the top really belong inside the main foreach, and your declaration of your Overs variable should either be outside of the foreach, or you need to use it while it exists (as it is, you assign a value to it, then it is blown away with the next iteration of you foreach or the end of the loop)

Thank you for the input. Can you point me where I can find examples of the best practices? I’m trying to learn as I go with getting these bpms working.

I made some changes, (phoned a friend for a bit of help…he’s not an Epicor perosn), and have one error message I can’t seem to get fixed. Error is “Member declartation is not allowed inside the code block”.

public class Company
{
    public string Name { get; set; }
    // Add any additional properties related to a company
}

decimal LaborQty2 = 0.0m; // Initialize LaborQty2 outside of the loop

foreach (var ttLaborDtl_iterator in (from ttLaborDtl_Row in ttLaborDtl
                                      where string.Equals(ttLaborDtl_Row.RowMod, IceRow.ROWSTATE_ADDED, StringComparison.OrdinalIgnoreCase) || string.Equals(ttLaborDtl_Row.RowMod, IceRow.ROWSTATE_UPDATED, StringComparison.OrdinalIgnoreCase)
                                      select ttLaborDtl_Row))
{
    var ttLaborDtlRow = ttLaborDtl_iterator;
    if (ttLaborDtlRow.LaborType == "P")
    {
        Company company = new Company { Name = ttLaborDtlRow.Company };
        LaborSeqDtl = ttLaborDtlRow.LaborDtlSeq;
        LaborQty = ttLaborDtlRow.LaborQty;
        LaborQty2 = 0.0m; // Reset LaborQty2 to zero before accumulating

        JobOpDtl = (from JobOpDtl_Row in Db.JobAsmbl
                    where string.Compare(JobOpDtl_Row.Company, company.Name, true) == 0 &&
                          JobOpDtl_Row.JobNum == ttLaborDtlRow.JobNum && JobOpDtl_Row.AssemblySeq == ttLaborDtlRow.AssemblySeq
                    select JobOpDtl_Row).FirstOrDefault();

        if (JobOpDtl != null)
        {
            ProductionQty = JobOpDtl.RequiredQty;
        }

        foreach (var LaborDtl1_iterator in (from LaborDtl1_Row in Db.LaborDtl
                                            where LaborDtl1_Row.Company == company.Name && LaborDtl1_Row.JobNum == ttLaborDtlRow.JobNum && LaborDtl1_Row.AssemblySeq == ttLaborDtlRow.AssemblySeq && LaborDtl1_Row.OprSeq == ttLaborDtlRow.OprSeq && LaborSeqDtl != LaborDtl1_Row.LaborDtlSeq
                                            select LaborDtl1_Row))
        {
            LaborDtl1 = LaborDtl1_iterator;
            LaborQty2 = LaborQty2 + LaborDtl1.LaborQty;
        }

        LaborQty2 = LaborQty2 + LaborQty;
        bool Overs;

        if (LaborQty2 > ProductionQty)
        {
            Overs = true;
        }
        else
        {
            Overs = false;
        }
    }
}

@Will79 You can’t use some otherwise normal C# inside of a code block.

Here is how I do this on End Activity on Labor.Update.

/* Stopping Over Reporting of Production Quantities */
string Customer = string.Empty;
int oldLaborQty = 0;
Erp.Tables.JobHead JobHead;

Erp.Tables.LaborDtl LaborDtl;
Ice.Diagnostics.Log.WriteEntry("MD Labor Update stop over reporting start");

             var orgLaborDtl = (from Old_Row in ds.LaborDtl
                                where Old_Row.Unchanged()
                                select Old_Row).FirstOrDefault();
        
            if (orgLaborDtl != null)
            {
              oldLaborQty = System.Convert.ToInt32(orgLaborDtl.LaborQty);
            }
            
            

 foreach(var ttLaborDtlRow in(from ttLaborDtl_Row in ds.LaborDtl
 where ttLaborDtl_Row.Updated() && (ttLaborDtl_Row.LaborType == "P")
 select ttLaborDtl_Row))
    {
      
        
//      Ice.Diagnostics.Log.WriteEntry($"Type {ttLaborDtlRow.LaborType}");
      if(ttLaborDtlRow.LaborType == "P")
       {

        
       
//          Ice.Diagnostics.Log.WriteEntry($"before joboper job {ttLaborDtlRow.JobNum} asm {ttLaborDtlRow.AssemblySeq} opr {ttLaborDtlRow.OprSeq}");
            var jo = Db.JobOper.Where(j => j.Company == CompanyID && j.JobNum  == ttLaborDtlRow.JobNum &&  j.AssemblySeq == ttLaborDtlRow.AssemblySeq  &&  j.OprSeq == ttLaborDtlRow.OprSeq).Select(j=> new {j.QtyCompleted,j.RunQty}).FirstOrDefault();
            
//            Ice.Diagnostics.Log.WriteEntry("after read");
            if (jo != null)
            {
            
//            Ice.Diagnostics.Log.WriteEntry($" End Act in JobOper Complete:{jo.QtyCompleted} LaborQ:{ttLaborDtlRow.LaborQty} Total {jo.RunQty} ");
       
     
                
//                   Ice.Diagnostics.Log.WriteEntry($" oldlabor {oldLaborQty}");
      
                   if((jo.QtyCompleted - oldLaborQty) + ttLaborDtlRow.LaborQty > jo.RunQty && ttLaborDtlRow.LaborQty != 0)
                  {
                   throw new Ice.BLException("There are already " + jo.QtyCompleted.ToString("N2")  + " complete. The operation required quantity is " + 
                                                                         jo.RunQty.ToString("N2") + ". You entered a quantity of " +  ttLaborDtlRow.LaborQty.ToString("N2") + " this will result in over reported quantities.");
                    ttLaborDtlRow.LaborQty = 0;
                  }

            }
      
        }
}   

Ice.Diagnostics.Log.WriteEntry("MD Labor Update stop over reporting exit");

You can’t declare a class within these code blocks - it adds no value in this case, anyway :thinking: just leave the Company value as a string, instead of embedding it into another object

1 Like

@jwphillips @gpayne

I could use a primer on Coding in BPM for Epicor! LOL! Thank you for the info!

On EpicWeb search for bpm cookbook

Looking at @gpayne’s code, you apparently don’t have to mess with string compare?? So we can clean that up…
I also kept getting confused with similarly named variables, so I put “this” on the front of local variables - just to keep them straight
I sometimes run into variables that the system won’t use in in expressions, so I dump any that I’ll be using into a local variable (especially if I’ll use it more than once)

foreach (var ttLaborDtlRow in (from ttLaborDtl_Row in ttLaborDtl
                               where ttLaborDtl_Row.RowMod == IceRow.ROWSTATE_ADDED ||
                                     ttLaborDtl_Row.RowMod == IceRow.ROWSTATE_UPDATED
                               select ttLaborDtl_Row))
{
    if (ttLaborDtlRow.LaborType == "P")
    {
        string thisCompany = ttLaborDtlRow.Company;
        string thisJobNum = ttLaborDtlRow.JobNum;
        int thisAssemblySeq = ttLaborDtlRow.AssemblySeq;
        int thisLaborDtlSeq = ttLaborDtlRow.LaborDtlSeq;
        int thisOprSeq = ttLaborDtlRow.OprSeq;
        decimal thisLaborQty = ttLaborDtlRow.LaborQty;

        var JobOpDtl =
            (from JobOpDtl_Row in Db.JobAsmbl
             where JobOpDtl_Row.Company == ttLaborDtlRow.Company &&
                   JobOpDtl_Row.JobNum == thisJobNum &&
                   JobOpDtl_Row.AssemblySeq == thisAssemblySeq
             select JobOpDtl_Row).FirstOrDefault();

        if (JobOpDtl == null)
        {
            // raise exception or return:
            //     everything after here is broken if there is no JobOpDtl found
        }
        decimal thisProductionQty = JobOpDtl.RequiredQty;

// Use LINQ capability to sum the selected values
//     (no need for a foreach and accumulator)
        decimal LaborQty2 =
            (from LaborDtl1_Row in Db.LaborDtl
             where LaborDtl1_Row.Company == thisCompany &&
                   LaborDtl1_Row.JobNum == thisJobNum &&
                   LaborDtl1_Row.AssemblySeq == thisAssemblySeq &&
                   LaborDtl1_Row.OprSeq == thisOprSeq &&
                   LaborDtl1_Row.LaborDtlSeq != thisLaborDtlSeq
             select LaborDtl1_Row)
             .Sum(laborDtl => laborDtl.LaborQty) // If no LaborDtl found, results in 0
                 + thisLaborQty;

// I still don't understand what you need to happen here
// Is there an output variable? Display a message like gpayne?
// This right here is a local variable whose life ends at the end of each foreach iteration
        bool Overs;
        if (LaborQty2 > ProductionQty)
        {
            Overs = true;
        }
        else
        {
            Overs = false;
        }
    }
}

I prefer inline notation for LINQ queries:

decimal LaborQty2 =
    Db.LaborDtl
        .Where(LaborDtl1_Row =>
               LaborDtl1_Row.Company == thisCompany &&
               LaborDtl1_Row.JobNum == thisJobNum &&
               LaborDtl1_Row.AssemblySeq == thisAssemblySeq &&
               LaborDtl1_Row.OprSeq == thisOprSeq &&
               LaborDtl1_Row.LaborDtlSeq != thisLaborDtlSeq)
        .Sum(laborDtl => laborDtl.LaborQty)
            + thisLaborQty;

NOTE: This is all untested… just coding

1 Like

The only error was on ProductionQty. It needed to be thisProductionQty. However, still seems I am back at square one as it only shows false for everything.

Above bool Overs; you state that you don’t understand what I am needing here. Basically I am needing for all the labor records (found in Job Tracker–>Operations–>Labor Transactions) and sum all those qty’s. Then I need to add in the current amount they are wanting to add to it. That total then is compared to the production qty, or required qty. So if the production qty is more than the required qty, give me a true, if not, false.
In the bpm, after the custom code, I will put a condition that says Overs = True. Then I’ll add an exception box under the true for the condition.

The code I posted does exactly what you are wanting.

You will only have a quantity on an updated row never on added.

You don’t need ROWSTATE You can use Added(), Updated(), Unchanged()

There is no need for JobOpDtl or summing of LaborDtls, JobOper holds all the values you need except for the current LaborQty

I refactored using LINQ because the Query syntax was driving me nuts:

var tt_LaborDtl = ttLaborDtl.Where( x => x.Added() || x.Updated() );

foreach ( var ttLaborRow in tt_LaborDtl )
{
    if ( ttLaborRow.LaborType != "P" )
        continue;

    decimal ProductionQty = 0m;

    var JobOpDtl = Db.JobAsmbl.Where( x => x.Company     == ttLaborRow.Company 
                                        && x.JobNum      == ttLaborRow.JobNum 
                                        && x.AssemblySeq == ttLaborRow.AssemblySeq )
                                .FirstOrDefault();

    if ( JobOpDtl != null )
    {
        ProductionQty = JobOpDtl.RequiredQty;
    }

    decimal TotalQty = ttLaborRow.LaborQty;

    TotalQty += Db.LaborDtl.Where( x => x.Company      == ttLaborRow.Company
                                      && x.JobNum      == ttLaborRow.JobNum 
                                      && x.AssemblySeq == ttLaborRow.AssemblySeq 
                                      && x.OprSeq      == ttLaborRow.OprSeq 
                                      && x.LaborDtlSeq != ttLaborRow.LaborDtlSeq )
                            .Sum( dbRows => dbRows.LaborQty);

    bool OverReport = TotalQty > ProductionQty;
}
1 Like

Listen to Greg :sweat_smile:
I am just a coder - he knows what he’s doing in Epicor

Great! What did you mean by end activity? Unsure on that part. Also, I did try yours and I got the error about ds wasn’t valid.

use ttTableName in Data Directives, ds.TableName in Method Directives

In addition to what @kve mentions you can right click and open parameters to see what Epicor is using for your version in MD, DD and BAQs.

image
Then use Ctrl+space to see options.
image

We use MES so quantities are reported when an End Activity is done on an operation. How are you testing your routine?

I put it in a dd on LaborDtl; testing through mes–>Report Qty.

Didn’t know about the Ctrl+space piece. Thanks!

Is that how your operators report quantities, not from their activities?

Yes, all qty is reported through mes.

This did work. I changed it to work in a Data directive. Thank you!

1 Like