Adding Attachments to PO Lines during PO Generation/Creation within New PO Suggestions

So, I got some code working so that when you add or change a part/line in PO Entry, it will pull in the associated attachments from the part master. Cool.
Next step, I want to do the same for when they click “Generate POs” in New PO Suggestions.
I ran a trace and I don’t see anything in there that I could use to run the method on.
I don’t even see a point in which I could get a PO number from.
Anyone have any [PO] suggestions? (Dad joke)

The Generate process runs outside the standard BPM engine for the most part. You could however hang a BPM on the process that turns the suggestions into POs … or the GetByID / GetList method of that object.

To clarify the “Generate Process”, I’m not talking about “Generate Suggestions”. I’m referring to this. Does your comment still hold?

Edit - My title is bad; I see that now.

AH! makes more sense now. A BPM could still be used here, though you may need some creative gymnastics to figure out what PO was created I don’t believe that information is put anywhere off the bat… :thinking:…thinking…

My initial thought was to grab the most recent PO that matches the vendor, but there’s always that small chance that someone is going to create a manual PO at the same time and muddle things up…

1 Like

:Grumble:
I can’t even get the vendor number from the POSugg.Generate BO.

Why not? that’s there

Not sure.
I just put in a message widget to spit out the vendor number and it came up blank.

Did you put a RowMod condition on it? If so that’s your problem the record is there but it isn’t RowModed… Also you could use the SugList parameter and split it and do lookups in code

So here’s a “HACK”

I created a Pre-Processing directive on POSug.Generate method and set my CallContextBPMData.Character01 = “TEST RAINBOW”

Then in a Data Directive in POHeader condition (Added) I display that CallContextBPMData.Character01. My hope was that it would pass the call context along (and it does).
http://jcg.pictures/gdggujdsQiaIhj.png

So if you stick some clever references in the CallContext in Generate IE A GUID to tell you which POs were created at that time. Stick that GUID from the call context in a UDfield in POHeader. Then on Post Processing on Generate you can find all PO’s that have that GUID in the UD field to know which are the ones you need to frock with :wink:

2 Likes

Lol, no kidding.
They certainly didn’t make this easy, did they?

Breaking it Down

Pre-Processing Directive on Generate
Set BPMCallContext.Character01 = Guid.NewGuid();

In Tran Data Directive on POHeader

Condition Row Added
IF BPMCallContext.Character01 <> Empty
Then Set POHeader.SuggestionGuid_c = BPMCallContext.Character01;

Then Post-Procesing BPM on Generate
Find all POHeaders which contain POHeader.SuggestionGuid_c == BPMCallContext.Character01
boom you found all youe newly created POs…
Reset BPMCallContext.Character01 = " " //Be a good citizen and clean up

Then smooth sailing call BO’s to add attachments etc.

4 Likes

While you were working on that, this was my approach. Yours seems a little safer, however.

        callContextBpmData.Character01 = "";
        foreach(var r in ttSugPoDtl)
        {
           int vNum = (int)r.VendorNum;
           string jNum = r.JobNum.ToString();
           int oNum = (int)r.OrderNum;
           int oLine = (int)r.OrderLine;
           int oRel = (int)r.OrderRelNum;
           string pNum = r.PartNum.ToString();
           DateTime dDate = Convert.ToDateTime(r.DueDate);

           int poNum = (
              from ph in Db.POHeader.With(LockHint.NoLock)
              join pd in Db.PODetail.With(LockHint.NoLock) on new {ph.Company, PO = ph.PONum} equals new {pd.Company, PO = pd.PONUM}
              join pr in Db.PORel.With(LockHint.NoLock) on new {pd.Company, POn = pd.PONUM, pd.POLine} equals new {pr.Company, POn = pr.PONum, pr.POLine}
              where ph.VendorNum==vNum &&
                       pr.JobNum==jNum &&
                       pd.OrderNum==oNum &&
                       pd.OrderLine==oLine &&
                       pd.PartNum==pNum &&
                       pd.DueDate==dDate
              select ph.PONum).DefaultIfEmpty(0).FirstOrDefault();

           callContextBpmData.Character01 = poNum.ToString() + Environment.NewLine;
        }

1 Like

*** Edit - I got it. Apparently, that’s yelling at me for having nested Linq statements? Or something to that affect? I rewrote one and put the data into a list to iterate through and it seemed to work. Just gotta button up a few things and it should be good to go.

I’m having issues with threading, apparently. This is normally solved by adding a transaction scope, which I did, but it didn’t seem to help. Maybe it’s in the wrong spot?

Error:

  135563

  =====================================================================
  Trace: System.Data.EntityException: The underlying provider failed on EnlistTransaction. ---> System.Data.SqlClient.SqlException: The operation failed because the session is not single threaded.
     at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
     at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
     at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
     at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
     at System.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest(Byte[] buffer, TransactionManagerRequestType request, String transactionName, TransactionManagerIsolationLevel isoLevel, Int32 timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj, Boolean isDelegateControlRequest)
     at System.Data.SqlClient.SqlInternalConnectionTds.PropagateTransactionCookie(Byte[] cookie)
     at System.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx)
     at System.Data.SqlClient.SqlInternalConnection.Enlist(Transaction tx)
     at System.Data.SqlClient.SqlInternalConnection.EnlistTransaction(Transaction transaction)
     at System.Data.SqlClient.SqlConnection.EnlistTransaction(Transaction transaction)
     at Epicor.Data.Provider.EpiConnection.EnlistTransaction(Transaction transaction) in C:\_Releases\ICE\3.1.500.16\Source\Framework\Epicor.System\Data\EpiProvider2\EpiConnection.cs:line 209
     at System.Data.EntityClient.EntityConnection.EnlistTransaction(Transaction transaction)
     --- End of inner exception stack trace ---
     at System.Data.EntityClient.EntityConnection.EnlistTransaction(Transaction transaction)
     at System.Data.Objects.ObjectContext.EnsureConnection()
     at System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
     at System.Data.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
     at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
     at System.Data.Objects.CompiledQuery.ExecuteQuery[TResult](ObjectContext context, Object[] parameterValues)
     at Epicor.Data.DBExpressionCompiler.GetResult[TContext,TQuery,TResult](Func`3 executeQuery, Cache cacheSetting, TContext dataContext, TQuery query) in C:\_Releases\ICE\3.1.500.16\Source\Framework\Epicor.System\Data\DBExpressionCompiler.cs:line 441
     at Epicor.Data.DBExpressionCompiler.InvokeSingle[TContext,TQuery,TResult](Expression expression, Cache currentCacheSetting, Boolean cacheQuery, TContext dataContext, Func`2 getDataCacheKey, Func`2 compileQuery, Func`3 executeQuery) in C:\_Releases\ICE\3.1.500.16\Source\Framework\Epicor.System\Data\DBExpressionCompiler.cs:line 302
     at Epicor.Data.DBExpressionCompiler.<>c__DisplayClass33_0`3.<Compile>b__0(TContext context, TArg1 arg1) in C:\_Releases\ICE\3.1.500.16\Source\Framework\Epicor.System\Data\DBExpressionCompiler.Generated.cs:line 1082
     at Erp.Services.BO.POSvc.Initialize() in C:\_Releases\ERP\UD10.1.500.16\Source\Server\Services\BO\PO\PO.cs:line 743
     at Epicor.Hosting.OperationBoundClass.Initialize(Operation operation, Boolean root) in C:\_Releases\ICE\3.1.500.16\Source\Framework\Epicor.System\Hosting\OperationBoundClass.cs:line 55
     at Epicor.Customization.Bpm.SvcFacadeBase`3.CoreInitialize(Operation operation, Boolean root) in c:\_Releases\ICE\3.1.500\Current\Source\Server\Internal\Lib\Epicor.Customization.Bpm\SvcFacadeBase.Generic.cs:line 93
     at Epicor.Hosting.OperationBoundClass.Initialize(Operation operation, Boolean root) in C:\_Releases\ICE\3.1.500.16\Source\Framework\Epicor.System\Hosting\OperationBoundClass.cs:line 53
     at Ice.Assemblies.ServiceRenderer.GetService[TService](IceDataContext context, Boolean ignoreFacade) in C:\_Releases\ICE\3.1.500.16\Source\Framework\Epicor.System\Assemblies\ServiceRenderer.cs:line 123
     at Epicor.Customization.Bpm.BO8CEB157C0F9F41A1A98F39A807931631.GeneratePostProcessingDirective_POSuggAttach_ADW_C9E8719FCDF445F3B8D743B74A85BCD1.A001_CustomCodeAction()
  =====================================================================

Code:

  callContextBpmData.Character01 = "";
  callContextBpmData.Character08 = "";
  const string RELATEDPART = "Part";

  foreach(var r in ttSugPoDtl)
  {
     int vNum = (int)r.VendorNum;
     string jNum = r.JobNum.ToString();
     int oNum = (int)r.OrderNum;
     int oLine = (int)r.OrderLine;
     int oRel = (int)r.OrderRelNum;
     string pNum = r.PartNum.ToString();
     DateTime dDate = Convert.ToDateTime(r.DueDate);

     foreach(var poNum in(
        from ph in Db.POHeader.With(LockHint.NoLock)
        join pd in Db.PODetail.With(LockHint.NoLock) on new {ph.Company, PO = ph.PONum} equals new {pd.Company, PO = pd.PONUM}
        join pr in Db.PORel.With(LockHint.NoLock) on new {pd.Company, POn = pd.PONUM, pd.POLine} equals new {pr.Company, POn = pr.PONum, pr.POLine}
        where ph.VendorNum==vNum &&
                 pr.JobNum==jNum &&
                 pd.OrderNum==oNum &&
                 pd.OrderLine==oLine &&
                 pd.PartNum==pNum &&
                 pd.DueDate==dDate
        select new {pd}))
     using(var scope = IceDataContext.CreateDefaultTransactionScope())
     {
        if( poNum.pd != null )
        {
           callContextBpmData.Character01 += poNum.pd.PONUM.ToString() + Environment.NewLine;
           int vPONum = (int)poNum.pd.PONUM;
           int vPOLine = (int)poNum.pd.POLine;
           int iter = 0;
           string strPONum = poNum.pd.PONUM.ToString();

           try{
              using(var poBO = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.POSvcContract>(Db))
              {
                 POTableset poTS = new POTableset();
                 poTS = poBO.GetByID(vPONum);
        
                 foreach( var rows in (
                    from xa in Db.XFileAttch.With(LockHint.NoLock)
                    join xr in Db.XFileRef.With(LockHint.NoLock) on new {xa.Company, xa.XFileRefNum} equals new {xr.Company, xr.XFileRefNum}
                    where xa.Company==Session.CompanyID && xa.RelatedToFile==RELATEDPART && xa.Key1==pNum
                    select new {xr}))
                 {
                    poBO.GetNewPODetailAttch(ref poTS, vPONum, vPOLine);
                    
                    poTS.PODetailAttch[iter].Company = Session.CompanyID;
                    poTS.PODetailAttch[iter].PONUM = vPONum;
                    poTS.PODetailAttch[iter].POLine = vPOLine;
                    poTS.PODetailAttch[iter].DrawingSeq = iter;
                    poTS.PODetailAttch[iter].XFileRefNum = 0;
                    poTS.PODetailAttch[iter].SysRevID = 0;
                    poTS.PODetailAttch[iter].SysRowID = Guid.Empty;
                    poTS.PODetailAttch[iter].ForeignSysRowID = Guid.Empty;
                    poTS.PODetailAttch[iter].DrawDesc = rows.xr.XFileDesc.ToString();
                    poTS.PODetailAttch[iter].FileName = rows.xr.XFileName.ToString();
                    poTS.PODetailAttch[iter].PDMDocID = "";
                    poTS.PODetailAttch[iter].DocTypeID = rows.xr.DocTypeID.ToString();
                    poTS.PODetailAttch[iter].RowMod = "A";
                    iter++;
                 }
                    poBO.Update(ref poTS);
              }
           }
           catch(Exception e)
           {
              callContextBpmData.Character08 = "=====================================================================" + Environment.NewLine + 
                                                               "Trace: " + e.ToString() + Environment.NewLine +
                                                               "=====================================================================" + Environment.NewLine;
           }
        }
        scope.Complete();
     }
  }

As a rule I don’t like to iterate through LINQ results. I prefer a ToList() and then iterate. LINQ does lazy execution which is cool (most of the time) but if you are iterating through DB objects it can cause issues.

Got it working.
Here’s the final product (such as it is):

      callContextBpmData.Character01 = "";
      callContextBpmData.Character08 = "";
      const string RELATEDPART = "Part";
      const string RELATEDPO = "PODetail";

      foreach(var r in ttSugPoDtl)
      {
         int vNum = (int)r.VendorNum;
         string jNum = r.JobNum.ToString();
         int oNum = (int)r.OrderNum;
         int oLine = (int)r.OrderLine;
         int oRel = (int)r.OrderRelNum;
         string pNum = r.PartNum.ToString();
         DateTime dDate = Convert.ToDateTime(r.DueDate);

         var poNumList = (
            from ph in Db.POHeader.With(LockHint.NoLock)
            join pd in Db.PODetail.With(LockHint.NoLock) on new {ph.Company, PO = ph.PONum} equals new {pd.Company, PO = pd.PONUM}
            join pr in Db.PORel.With(LockHint.NoLock) on new {pd.Company, POn = pd.PONUM, pd.POLine} equals new {pr.Company, POn = pr.PONum, pr.POLine}
            where ph.VendorNum==vNum &&
                     pr.JobNum==jNum &&
                     pd.OrderNum==oNum &&
                     pd.OrderLine==oLine &&
                     pd.PartNum==pNum &&
                     pd.DueDate==dDate
            select new {pd}).ToList();

         if( poNumList.Count > 0 )
         {
            foreach(var poNum in poNumList)
            {
               callContextBpmData.Character01 += poNum.pd.PONUM.ToString() + Environment.NewLine;
               int vPONum = (int)poNum.pd.PONUM;
               int vPOLine = (int)poNum.pd.POLine;
               int iter = 0;
               string strPONum = poNum.pd.PONUM.ToString();
         
               try{
                  using(var poBO = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.POSvcContract>(Db))
                  {
                     POTableset poTS = new POTableset();
                     poTS = poBO.GetByID(vPONum);

                     iter = (
                        from xa in Db.XFileAttch.With(LockHint.NoLock)
                        join xr in Db.XFileRef.With(LockHint.NoLock) on new {xa.Company, xa.XFileRefNum} equals new {xr.Company, xr.XFileRefNum}
                        where xa.Company==Session.CompanyID && xa.RelatedToFile==RELATEDPO && xa.Key1==strPONum
                        select xa.AttachNum).Count();
            
                     foreach( var rows in (
                        from xa in Db.XFileAttch.With(LockHint.NoLock)
                        join xr in Db.XFileRef.With(LockHint.NoLock) on new {xa.Company, xa.XFileRefNum} equals new {xr.Company, xr.XFileRefNum}
                        where xa.Company==Session.CompanyID && xa.RelatedToFile==RELATEDPART && xa.Key1==pNum
                        select new {xr}))
                     using(var scope = IceDataContext.CreateDefaultTransactionScope())
                     {
                        poBO.GetNewPODetailAttch(ref poTS, vPONum, vPOLine);
                        
                        poTS.PODetailAttch[iter].Company = Session.CompanyID;
                        poTS.PODetailAttch[iter].PONUM = vPONum;
                        poTS.PODetailAttch[iter].POLine = vPOLine;
                        poTS.PODetailAttch[iter].DrawingSeq = iter;
                        poTS.PODetailAttch[iter].XFileRefNum = 0;
                        poTS.PODetailAttch[iter].SysRevID = 0;
                        poTS.PODetailAttch[iter].SysRowID = Guid.Empty;
                        poTS.PODetailAttch[iter].ForeignSysRowID = Guid.Empty;
                        poTS.PODetailAttch[iter].DrawDesc = rows.xr.XFileDesc.ToString();
                        poTS.PODetailAttch[iter].FileName = rows.xr.XFileName.ToString();
                        poTS.PODetailAttch[iter].PDMDocID = "";
                        poTS.PODetailAttch[iter].DocTypeID = rows.xr.DocTypeID.ToString();
                        poTS.PODetailAttch[iter].RowMod = "A";
                        iter++;
                        scope.Complete();
                     }
                     poBO.Update(ref poTS);
                  }
               }
               catch(Exception e)
               {
                  callContextBpmData.Character08 = "=====================================================================" + Environment.NewLine + 
                                                                   "Trace: " + e.ToString() + Environment.NewLine +
                                                                   "=====================================================================" + Environment.NewLine;
               }
            }
         }
      }

So is this running in post proc generate?

Yup.

and your ttSugPoDtl isn’t empty at that point? #Confused

All that you know is a lie.

… Also, I have no idea. Lol. I wield my C# sword without aim.

1 Like