Page Index Toggle Pages: [1] 2  Send Topic Send Topic Print Print
Very Hot Topic (More than 25 Replies) RunExitProgrammingOnCloseForm(state) ? (Read 3002 times)
lksseven
Full Member
***
Offline



Posts: 416
Location: Southwest
Joined: Jan 26th, 2009
RunExitProgrammingOnCloseForm(state) ?
Jan 30th, 2012 at 4:13pm
Print Post Print Post  
Upon entering and saving a sales order, I have a program section (at the end) in the OrderSave button that reads data from several of the form fields, and then calculates what other regularly ordered products are due for re-order (based upon that customer's order frequency of its different items) and then emails that information to the customer as a reminder. 

The intent is to be a passive reminder to encourage the customer to check their inventory levels and avoid 'stock out' scenarios, and also it happens automatically without my sales staff having to 'remember' to run the calculation.

But - for some of our larger clients, this program (which involves various loops) takes so long (sometimes 2 minutes for a large customer) to run that it's not practical to have the sales person wait on it without being able to do anything else in Sesame in the meantime.

Is there anyway to spin off this program section (RunExitProgrammingOnCloseForm(state)? ) so that it runs as a separate process after the sales order is saved, and allows the sales person to go on to other Sesame functions immediately?
  

Larry
Back to top
IP Logged
 
The Cow
YaBB Administrator
*****
Offline



Posts: 2530
Joined: Nov 22nd, 2002
Re: RunExitProgrammingOnCloseForm(state) ?
Reply #1 - Jan 30th, 2012 at 5:48pm
Print Post Print Post  
What you asking for is known in programming parlance as "multi-threading" and, no SBasic does not expose multi-threading to the end users. Even if we did, your code would probably have to be rewritten to deal with the complexities that that would introduce. For example, you would need to gather all of the data you plan to use in your calculations and place it in persistent storage, before you close the form. And, once you have finished calculating, you would need some way to convey the results, again with no form (or an entirely different form) at hand. The closest likely function you could employ is "RunRntryOnInterval" which can delay the start of code by some time. It will cancel, however, if you actually close the form.

That said, there a few ways you might be able to reduce the wait time significanty. There are certain functions that run much faster than others. There are also ways to run code on the server, rather than the client, which will speed up data access significantly. There are also quite a few optimization techniques, some of which are not entirely obvious, that you can employ. You may be able to cut "a couple of minutes" to a handful of seconds, with a careful rearrangement.
  

Mark Lasersohn&&Programmer&&Lantica Software, LLC
Back to top
IP Logged
 
lksseven
Full Member
***
Offline



Posts: 416
Location: Southwest
Joined: Jan 26th, 2009
Re: RunExitProgrammingOnCloseForm(state) ?
Reply #2 - Jan 30th, 2012 at 9:26pm
Print Post Print Post  
Hi Mark,

Thanks for your reply.  I'll bite -
1) where would I read up/research how to shift some of the heavy lifting to the server?

2) What my code does is:
a) search for all SOLines in the last 180 for that clientID; b) then it strips out backorders;
c) then it sorts in ItemNum order;
c) then it strips out duplicate ItemNums to get a unique list of ItemNums;
d) then I take the first ItemNum and find all SOLines for that ItemNum,
e) then it calculates the average qty ordered per record and the average # of days between orders;
f) then add that average # of days to the most recent order date for that item to arrive at a 'predicted order date (and avg order quantity)
g) then write that info to a 'holding variable';
h) then I discard that ItemNum and
i) then loop onto the next ItemNum and
j) repeat the process, etc, until I've discarded all ItemNums in my result set. 
k) Then I can either Writeln the 'holding variable' to the slate, or email it to the customer.

           I've tried to line up those steps logically - how might I 'optimize' that effort to be more efficient/speedy?

If this technique is beyond me, I can always nest my current programming inside a master loop for all active customers in the last 6 months and just run a calculation once a week to print out reminders for each customer of upcoming needs we predict they will have, and those printouts would then be faxed to the customers.  We used to do that with Forest&Trees calc/report that worked with our accounting software before I migrated the inventory and invoicing functions over to Sesame (yea!) - but I was hoping to find a way for Sesame to do this on a daily/hourly/realtime basis without as much need for human capital involvement.
  

Larry
Back to top
IP Logged
 
Hammer
YaBB Administrator
Lanticans
*****
Offline


Fire bad. Tree pretty.

Posts: 3436
Location: Ohio
Joined: Nov 22nd, 2002
Re: RunExitProgrammingOnCloseForm(state) ?
Reply #3 - Jan 30th, 2012 at 9:51pm
Print Post Print Post  
Code optimization is very much in the line-by-line details. Things like digging for where you are doing something ten times when you only have to do it once. It's likely we'll need to see your code to really take a run at this.

lksseven wrote on Jan 30th, 2012 at 9:26pm:
a) search for all SOLines in the last 180 for that clientID; b) then it strips out backorders;


What method are you using to do this search?

Quote:
c) then it sorts in ItemNum order;


What method are you using to do this sort?

Quote:
c) then it strips out duplicate ItemNums to get a unique list of ItemNums;


What method are you using to strip the duplicates?

  

- Hammer
The plural of anecdote is not data.
Back to top
IP Logged
 
The Cow
YaBB Administrator
*****
Offline



Posts: 2530
Joined: Nov 22nd, 2002
Re: RunExitProgrammingOnCloseForm(state) ?
Reply #4 - Jan 30th, 2012 at 10:36pm
Print Post Print Post  
Here is an example of something you can check for that applies to pretty much any bit of code. I have two loops. Both achieve exactly the same thing, they access an element, convert it to a number, add that number together, over and over again. The first one, does the access and the conversion every time through the loop. The second accesses the element and converts once, before the loop:
Code
Select All
var start as int
var loop as int
var aa as double
var bb as double

writeln("start")
start = @TimeInSeconds()
for loop = 0 to 1000000
	aa = 0
	aa = @ToNumber(Numerical) + @ToNumber(numerical)
next
writeln("R1: " + aa)
writeln(@TimeInSeconds() - start)
 



Code
Select All
var start as int
var loop as int
var aa as double
var bb as double

writeln("second one")
start = @TimeInSeconds()
bb = @ToNumber(numerical)
for loop = 0 to 1000000
	aa = 0
	aa = bb + bb
next
writeln("R2: " + aa)
writeln(@TimeInSeconds() - start)
 



Because the second one accesses variables instead of elements and only does the conversion once, rather than every time throgh the loop - it runs in 4 seconds. The first one, that accesses the element over and over again, rather than using a variable, takes 174 seconds.

The big difference is in the value access. Whenever possible, get a value from an element only once, store it in a variable, use that variable throughout your calculations and write the result back only once. Variables are many times faster than elements for both read and write.

If you can, call functions with the same value arguments only once and store the result in a variable. Most functions use more CPU to calculate than it takes to grab the result from a variable.
  

Mark Lasersohn&&Programmer&&Lantica Software, LLC
Back to top
IP Logged
 
lksseven
Full Member
***
Offline



Posts: 416
Location: Southwest
Joined: Jan 26th, 2009
Re: RunExitProgrammingOnCloseForm(state) ?
Reply #5 - Jan 30th, 2012 at 11:03pm
Print Post Print Post  
Hammer wrote on Jan 30th, 2012 at 9:51pm:
Code optimization is very much in the line-by-line details. Things like digging for where you are doing something ten times when you only have to do it once. It's likely we'll need to see your code to really take a run at this.

lksseven wrote on Jan 30th, 2012 at 9:26pm:
a) search for all SOLines in the last 180 for that clientID; b) then it strips out backorders;


What method are you using to do this search?

If NSnotify = ""
                              {
                             vClientEmail = ""
                             
                             vNeedSoonAddTo = ""
                             vNeedSoonFYI = ""
                             vNeedSoonHeader = "Need Soon Report from WordCom, Inc " + @NewLine() + "Hi, " + vOrderer + "," + @NewLine() + vClientID + " / " + vShiptoName + "(" + vShipToID + ")" + @NewLine() + @NewLine() + "WordCom's wise, all-seeing White Owl Software suggests that " + @NewLine() + "you are due to order the following products - please contact us " + @NewLine() + "via fax 999-555-9999, phone 999-555-9998, or email xxxxxxxxx@yyyyyyy.com." + @NewLine() + "We appreciate you !!!" + @NewLine() + @NewLine() + @NewLine()
                             vDate = @Date
               
                                    vStartDate = (@Date - 180)
                                  vEndDate = @Date
                                  vDateRange = (vStartDate + ".." + vEndDate)
                  

                             vRS6 = @XResultSetSearch(@FN, "Orders!SOLines", SEARCH_MODE_AND, SEARCH_SYNTAX_QA, "!ShipToClientID=" + vShipToClientID, "!SODate=" + vDateRange)
                                         
                                 If vRS6 > -1 
                             
                                     {
                                   vTotSO = 0
                                   vTotSO = @XResultSetTotal(vRS6)


                                   vLoopSO = 0                                                       
                                   For vLoopSO = 1 to vTotSO
                                       
                                         XResultSetCurrentPosition(vRS6, vLoopSO)
                                         vBO2 = ""
                                         vSONum2 = @XResultSetValue(vRS6, "SONum")
                                         vBO2 = @ContainsStringArray(vSONum2, "A;B;C;D;E;X", 0)
                                         If vBO2 <> ""
                                            {
                                                            XResultSetRemoveRecord(vRS6)
                                                  vLoopSO = vLoopSO - 1
                                                  vTotSO = vTotSO - 1                                 
                                                        }
                                           Next


Quote:
c) then it sorts in ItemNum order;


What method are you using to do this sort?

XResultSetSort(vRS6, "ItemNum:-1")

Quote:
c) then it strips out duplicate ItemNums to get a unique list of ItemNums;


What method are you using to strip the duplicates?

                                     // this below section will strip out all duplicate ItemNum's, leaving a list of ItemNums that this Client
                                   // has ordered in the date parameter.
                       
                                   vTot6 = @XResultSetTotal(vRS6)
//writeln("vTot6 " + vTot6)
                                    vLoop6 = 0
                                   vTotD = 0
                                   vCurPos = 0
                       
                                   For vLoop6 = 1 to vTot6
                             
                                         XResultSetCurrentPosition(vRS6, vLoop6)
                                         vItemNum6 = @XResultSetValue(vRS6, "ItemNum")
                                         vItemNumList = vItemNumList + @NewLine() + vItemNum6

                                         vCurPos = @XResultSetCurrentPosition(vRS6)
                                         vTotD = vTot6 - 1
                                         vCurPos = vCurPos + 1

                                         vLoop6b = 1
                                         For vLoop6b = vCurPos to vTotD
                                
                                               XResultSetCurrentPosition(vRS6, vLoop6b)
                                               vItemNum2 = ""
                                               vItemNum2 = @XResultSetValue(vRS6, "ItemNum")
                                               If vItemNum2 = vItemNum6
                                                  {
                                                        XResultSetRemoveRecord(vRS6)
                                                        vLoop6b = vLoop6b - 1
                                                        vTotD = vTotD - 1
                                                        vTot6 = vTot6 - 1
                                                  }
                                 
                                         Next
                                            vTot6 = @XResultSetTotal(vRS6)
                              
                                   Next

                                   vTot6 = @XResultSetTotal(vRS6)



  

Larry
Back to top
IP Logged
 
Hammer
YaBB Administrator
Lanticans
*****
Offline


Fire bad. Tree pretty.

Posts: 3436
Location: Ohio
Joined: Nov 22nd, 2002
Re: RunExitProgrammingOnCloseForm(state) ?
Reply #6 - Jan 31st, 2012 at 2:08pm
Print Post Print Post  
OK, first, a little one. In this snippet, you are asking the system for the current date three times instead of only once. This...

vDate = @Date
vStartDate = (@Date - 180)
vEndDate = @Date

.. would be faster if changed to this:

vDate = @Date
vStartDate = (vDate - 180)
vEndDate = vDate


Now for some bigger stuff. You can probably ditch a couple of passes through the result set.

On the pass you are using to strip backorders, are you sure you can't come up with retrieve criteria to add to your XResultSetSearch that excludes these records in the first place?

The pass you are using to ditch duplicates is not necessary. One you have sorted the records, you can simply use logic to process each item number only once, saving yourself the time of an extra pass through the result set and the time it takes to remove a record from the result set instead of just skipping it.
  

- Hammer
The plural of anecdote is not data.
Back to top
IP Logged
 
lksseven
Full Member
***
Offline



Posts: 416
Location: Southwest
Joined: Jan 26th, 2009
Re: RunExitProgrammingOnCloseForm(state) ?
Reply #7 - Jan 31st, 2012 at 4:53pm
Print Post Print Post  
Date:   oh!  Yes, of course.  Thx!

Backorders:    oh!  Yes, I can use search criteria "/..a;..b;..c;..d;..e;..x" instead of looping through.  Thx!

Duplicates:    I can't quite 'get' how to tell the calc to start with the first number and then find the next number that is different/do the calc, then find the NEXT ItemNum that is different from the last/do the calc, etc.

I adjusted the program regarding vDate and search criteria to exclude the backorders, but the program on a large customer still took 2 minutes (maybe saved a second or two).  This result set, by the way, has 901 records, and gets winnowed down to 90 distinct part #'s, each of which must then be looped through to calc avg days btwn order and avg qty per order.   Here is the program section that calcs the distinct part #'s:


                 // this next section will create a loop, take the first ItemNum, go find all SOLines records for this ItemNum, for this client,
                 // within the date parameter, and then remove the backorders, then loop through the remaining records to
                 // 1) count the number of records (the vTot after removing backorders),
                 // 2) determine the earlist SODate
                 // 3) determine the latest SODate
                 // 4) sum the qty of units
                 // 5) subtract the latest date from the earliest date to determine the span of days from earliest to latest SODate
                 //      and then divide that # of days by the count of orders, to arrive at an average span between orders.  Then
                 //      add the average span to the latest date to get the next predicted order date for this ItemNum
                 // 6) divide the total qty of units by the count of orders, to arrive at an average qty per order
                 // 7) writeln the ItemNum, the predicted date of next order, and the suggested (average) qty per order


                       vLoop6c = 0
                       For vLoop6c = 1 to vTot6

                             XResultSetCurrentPosition(vRS6, vLoop6c)
                             vItemNum3 = @XResultSetValue(vRS6, "ItemNum")
                             vItemDesc3 = @XResultSetValue(vRS6, "ItemDesc")

                             vQtyTotal = 0
                             vAvgInterval = 0
                             vPredictedDate = 0
                             vAvgQty = 0
                             vTot7 = 0
                             vRS7 = @XResultSetSearch(@FN, "Orders!SOLines", SEARCH_MODE_AND, SEARCH_SYNTAX_QA, "!ShipToClientID=" + vClientID, "!SODate=" + vDateRange, "!ItemNum=" + vItemNum3, "!ItemCat=/" + "DESC;LBR;RC", "!SONum=/" + "..a;..b;..c;..d;..e;..x")
                                         
                                 If vRS7 > -1 
                             
                                     {
                                 vTot7 = @XResultSetTotal(vRS7)
writeln("vTot7 - " + vTot7)


                                   vLoop7 = 0      
                                 For vLoop7 = 1 to vTot7
                                       
                                   XResultSetCurrentPosition(vRS7, vLoop7)
                                   vItemCat = 0
                                   vItemUoM = 0
                                   vSONum = @XResultSetValue(vRS7, "SONum")
                                   vItemCat = @XResultSetValue(vRS7, "ItemCat")
                                   vItemUoM = @XResultSetValue(vRS7, "ItemUoM")
/*
                                   vBO = @ContainsStringArray(vSONum, "A;B;C;D;E;X", 0)
                                   If (vBO <> "") or (vItemCat = "DESC") or (vItemCat = "LBR") or (vItemCat = "RC")
                                      {
                                                      XResultSetRemoveRecord(vRS7)
                                            vLoop7 = vLoop7 - 1
                                            vTot7 = vTot7 - 1                                 
                                                  }
*/
                                         Next
                                vTot7 = @XResultSetTotal(vRS7)
                                vCount8 = 0
                                vCount8 = vTot7

                                                          
                                   XResultSetSort(vRS7, "SODate:-1")

// now I need to determine the earliest SODate and the latest SODate

                                   
                                   vLoop8 = 0
                                      For vLoop8 = 1 to 1
                                         XResultSetCurrentPosition(vRS7, vLoop8)
                                         vMinSODate = @XResultSetValue(vRS7, "SODate")
                                   Next


                                XResultSetSort(vRS7, "SODate:1")
                                   vLoop8 = 0
                                   For vLoop8 = 1 to 1
                                         XResultSetCurrentPosition(vRS7, vLoop8)
                                         vMaxSODate = @XResultSetValue(vRS7, "SODate")
                                   Next


                                     vLoop8 = 0
                                   vQtyTotal = 0
                                   For vLoop8 = 1 to vTot7
                                         vQty = 0
                                         XResultSetCurrentPosition(vRS7, vLoop8)
                                         vQty = @XResultSetValue(vRS7, "ItemQtyShadow")
                                         vQtyTotal = vQtyTotal + vQty
                                   Next

// now if the number of times an ItemNum has been ordered is > 1, and the TotalQty > 1, then
                                
                                 If (vCount8 > 1) and (vQtyTotal > 1)
                                   {
                                   
                                      vAvgInterval = (@TD(vMaxSODate) - @TD(vMinSODate))/vCount8
                                      vPredictedDate = @TD(vMaxSODate) + vAvgInterval
                                      vAvgQty = vQtyTotal/vCount8
     
                                   If (vPredictedDate > (vDate - 14)) and (vPredictedDate < (vDate + 14))
                                     {
                                         //writeln("Reorder Date for " + vAvgQty + " " + vItemUoM + " of ItemNum #" + vItemNum3 + " / " + vItemDesc3 + @NewLine() + " should be " + vPredictedDate + @NewLine() + @NewLine())
                                         vNeedSoonAddTo = ""
                                         vNeedSoonAddTo = "Reorder Date for " + vAvgQty + " " + vItemUoM + " of ItemNum #" + vItemNum3 + " / " + vItemDesc3 + @NewLine() + " should be " + vPredictedDate + @NewLine() + @NewLine()
                                         vNeedSoonFYI = vNeedSoonFYI + vNeedSoonAddTo + @NewLine()
                                     }
                                   }

                             } // ends if vRS7 > -1
                             XResultSetClose(vRS7)



                       Next  // vLoop6c to vTot6
   
  

Larry
Back to top
IP Logged
 
Hammer
YaBB Administrator
Lanticans
*****
Offline


Fire bad. Tree pretty.

Posts: 3436
Location: Ohio
Joined: Nov 22nd, 2002
Re: RunExitProgrammingOnCloseForm(state) ?
Reply #8 - Jan 31st, 2012 at 5:35pm
Print Post Print Post  
lksseven wrote on Jan 31st, 2012 at 4:53pm:
[size=8]Duplicates:    I can't quite 'get' how to tell the calc to start with the first number and then find the next number that is different/do the calc, then find the NEXT ItemNum that is different from the last/do the calc, etc.


Here is a simple example that works with Customers. It will print each State only once.

Code
Select All
var vRS as Int
var vState as String
var vOldState as String
var vLoop as Int
var vCnt as Int

	vOldState = "This will never appear as a State"
	vRS = @XResultSetSearch(@FN, "Customers", 0, 2, "")
	If vRS > -1
	{
		XResultSetSort(vRS, "State:-1")
		vCnt = @XResultSetTotal(vRS)

		For vLoop = 1 To vCnt
			XResultSetCurrentPosition(vRS, vLoop)
			vState = @XResultSetValue(vRS, "State")
			If vOldState <> vState
			{
				WriteLn(vState)
				vOldState = vState
			}
		Next
		XResultSetClose(vRS)
	}
 



Quote:
I adjusted the program regarding vDate and search criteria to exclude the backorders, but the program on a large customer still took 2 minutes (maybe saved a second or two).


That's how it works. You chip away at everything that is using up time. The accumulation can really add up. Getting rid of the duplicate stripping will likely take a nice chunk off.

Quote:
Here is the program section that calcs the distinct part #'s:

I'll look at this when I get a chance. Let me know how ditching the duplicate stripping does.
  

- Hammer
The plural of anecdote is not data.
Back to top
IP Logged
 
lksseven
Full Member
***
Offline



Posts: 416
Location: Southwest
Joined: Jan 26th, 2009
Re: RunExitProgrammingOnCloseForm(state) ?
Reply #9 - Jan 31st, 2012 at 5:42pm
Print Post Print Post  
Just for context, on the client calc that takes 2 minutes, it takes 45 seconds to strip the 811 duplicates (from total 901 records), and 75 seconds to do the calcs on each of the remaining 90 ItemNums).

I did look through the last program section and got rid of a loop section (yea!).   I'll study your example and see if I can stretch my head around it.
  

Larry
Back to top
IP Logged
 
Hammer
YaBB Administrator
Lanticans
*****
Offline


Fire bad. Tree pretty.

Posts: 3436
Location: Ohio
Joined: Nov 22nd, 2002
Re: RunExitProgrammingOnCloseForm(state) ?
Reply #10 - Jan 31st, 2012 at 5:56pm
Print Post Print Post  
lksseven wrote on Jan 31st, 2012 at 5:42pm:
Just for context, on the client calc that takes 2 minutes, it takes 45 seconds to strip the 811 duplicates (from total 901 records), and 75 seconds to do the calcs on each of the remaining 90 ItemNums).

I did look through the last program section and got rid of a loop section (yea!).   I'll study your example and see if I can stretch my head around it.


Well, not stripping the duplicates should drop most of that 45 seconds. And, just at a glance, I see where a good chunk of time can come off the 75 seconds for the calcs. I'll get something together for you.
  

- Hammer
The plural of anecdote is not data.
Back to top
IP Logged
 
lksseven
Full Member
***
Offline



Posts: 416
Location: Southwest
Joined: Jan 26th, 2009
Re: RunExitProgrammingOnCloseForm(state) ?
Reply #11 - Jan 31st, 2012 at 7:53pm
Print Post Print Post  
ok, I have reworked the programming and lowered the 2minute calc down to 1min17sec (yea!).  However, to use this program where I want to in my application, I need to get that 1min17sec down to about 30sec.  Otherwise I can just add a master loop that will calc not just a single client, but all clients within the date parameter, and run the calc once/week. 

Whether the further speed increase is possible or not, thank you for schooling me on some better programming techniques!

If (@Mode() = 1) and (vFlag = 0)
  {
                                      // below is asking for a clientID and getting a ResultSet

             If ClientMultiLoc = ""
               {
                 vClientEmail = ""
                 vClientFax = ""
                 vClientName = ""
                 
                 vClientID = ClientID
                 vClientEmail = ClientEmail1
                 vClientFax = ClientFax
                 vClientName = ClientName
writeln(vClientID)
                    }
                else If ClientMultiLoc <> ""

                            {
                              vClientID = ClientID
                       vShipToEmail1 = ""
                       vShipToFax = ""
                       vClientName = ""
                       vClientEmail = ""
                            vRS20 = @XResultSetSearch(@FN, "Client!ShipTo", SEARCH_MODE_OR, SEARCH_SYNTAX_QA, "!ClientID=" + vClientID)
                         XResultSetSort(vRS20, "ShipToName")
                       If vRS20 > -1
                          {
                             vCount = @XResultSetTotal(vRS20)
                             If vCount > 0
                               {
                                    vList = ""
                                   For vLoop20 = 1 To vCount
                                         XResultSetCurrentPosition(vRS20, vLoop20)
                                         vLine = @XResultSetValue(vRS20, "ShipToName") + " ~ " + @XResultSetValue(vRS20, "ShipToId")
                                         vList = vList + vLine + ";"
                                   Next
                                   vList = @ReplLas(vList, ";", "")
                                          vList = @SortStringArray(vList, 0)
                                          PopupSelectPosition(4, @Xpos(ClientID)+10, @Ypos(ClientID)+10)
                                   vChoice = @PopupChoiceList(vList, "SELECT A ShipTo______________________________________________")
                                          If vChoice <> ""
                                            {
                                         vChoice = @Mid(vChoice, @Instr(vChoice, "~") + 2, 500)
                                         //ShipToID = vChoice
                                         vClientID = vClientID + "+" + vChoice
writeln(vClientID)
                 
                                            }
                                    }
// this next section will
// get the ShipToEmail and ShiptoFax
// and ShipToName

                       vRS21 = @XResultSetSearch(@FN, "Client!ShipTo", SEARCH_MODE_OR, SEARCH_SYNTAX_QA, "!ShipToClientID=" + vClientID)
                         If vRS21 > -1
                          {
                             vCount = 0
                             vCount = @XResultSetTotal(vRS21)
                             If vCount = 1
                               {
                                   vClientEmail = ""
                                   vShipToName = @XResultSetValue(vRS21, "ShipToName")
                                   vShipToEmail1 = @XResultSetValue(vRS21, "ShipToEmail1")
                                   vShipToFax = @XResultSetValue(vRS21, "ShipToFax")
                                   vClientEmail = vShiptoEmail1
                                   vClientFax = vShipToFax
                                   vClientName = vShipToName
                               }
                         }
                       XResultSetClose(vRS21)

                              }

                           XResultSetClose(vRS20)

                           } // ends If ClientMultiLoc <> ""


// the above has determined our vCliendID.

// the next calc will determine the date parameter for this program

             vDate = @Date
               
                  vStartDate = (vDate - 180)
                 vEndDate = vDate
                 vDateRange = (vStartDate + ".." + vEndDate)
                  
             If vClientID <> ""
                      {
// the next calc will find all records
// for this client within the date parameter
// that do not include backorders or
// description line items or labor items

                 vUniqueItemNum = "Begin here"
                 vRS6 = @XResultSetSearch(@FN, "Orders!SOLines", SEARCH_MODE_AND, SEARCH_SYNTAX_QA, "!ShipToClientID=" + vClientID, "!SODate=" + vDateRange, "!SONum=/" + "..a;..b;..c;..d;..e;..x")
writeln(vRS6)                                          
                     If vRS6 > -1 
                             
                        {
                       vTot = 0
                       vTot = @XResultSetTotal(vRS6)
writeln("vTot - " + vTot)


                                                          
                       XResultSetSort(vRS6, "ItemNum:-1")

           // this next section I want to take the first ItemNum, find out, within the date parameter
           // 1) how many times this item number has been ordered by this customer,
           // 2) how many total units have been ordered
           // 3) then I can determine the average # of days between orders
           // 4) and the average # of units per order
           // 5) then take the most recent date this item has been ordered, add the average span of days between orders
           //         to arrive at a predicted date of needed reorder
           // 6) divide the total qty of units ordered by the number of orders placed during the date parameter
           //      to arrive at an average qty ordered per order
 

           // this below section will find skip duplicate ItemNum's, calcing only on one of each ItemNum instance
           // ordered in the date parameter.
                       
                       vTot6 = @XResultSetTotal(vRS6)

                         vLoop6 = 0
                       For vLoop6 = 1 to vTot6
                             
                           XResultSetCurrentPosition(vRS6, vLoop6)
                           vItemNum = @XResultSetValue(vRS6, "ItemNum")
                           If vUniqueItemNum <> vItemNum

// begin vUniqueItemNum <> vItemNum
                             { // begin vUniqueItemNum <> vItemNum

                             vUniqueItemNum = vItemNum
                             vItemNumList = vItemNumList + @NewLine() + vItemNum
                             

                             vRS7 = @XResultSetSearch(@FN, "Orders!SOLines", SEARCH_MODE_AND, SEARCH_SYNTAX_QA, "!ShipToClientID=" + vClientID, "!SODate=" + vDateRange, "!ItemNum=" + vItemNum, "!ItemCat=/" + "DESC;LBR;RC", "!SONum=/" + "..a;..b;..c;..d;..e;..x")
                                         
                                 If vRS7 > -1 
                             
                                     {
                                 vItemDesc = @XResultSetValue(vRS6, "ItemDesc")
                                 vQtyTotal = 0
                                 vAvgInterval = 0
                                 vPredictedDate = 0
                                 vAvgQty = 0
                                 vTot7 = 0
                                 vTot7 = @XResultSetTotal(vRS7)
// only continue if there is more than one
// instance of this ItemNum within the date
// parameter
                                 If vTot7 > 1
                                   {

// this calc will determine the total qty
// of this ItemNum
                                   vLoop8 = 0
                                   vQtyTotal = 0
                                   For vLoop8 = 1 to vTot7
                                         vQty = 0
                                         XResultSetCurrentPosition(vRS7, vLoop8)
                                         vQty = @XResultSetValue(vRS7, "ItemQtyShadow")
                                         vQtyTotal = vQtyTotal + vQty
                                   Next

// only continue is Total quantity of this
// ItemNum is more than 1 unit
                                   If vQtyTotal > 1
                                     {

                                       vSONum = @XResultSetValue(vRS7, "SONum")
                                       vItemCat = @XResultSetValue(vRS7, "ItemCat")
                                       vItemUoM = @XResultSetValue(vRS7, "ItemUoM")
                                
// now I need to determine the earliest SODate
// and the latest SODate

// This calc will sort in ascend order and then
// take only the first record (minimum date)                                                          
                                             XResultSetSort(vRS7, "SODate:-1")
                                       vLoop8 = 0
                                          For vLoop8 = 1 to 1
                                         XResultSetCurrentPosition(vRS7, vLoop8)
                                         vMinSODate = @XResultSetValue(vRS7, "SODate")
                                       Next

// This calc will sort in descend order and
// then take only the first record (maximum date)
                                          XResultSetSort(vRS7, "SODate:1")
                                       vLoop8 = 0
                                       For vLoop8 = 1 to 1
                                         XResultSetCurrentPosition(vRS7, vLoop8)
                                         vMaxSODate = @XResultSetValue(vRS7, "SODate")
                                       Next

// this calc will determine the average # of days
// btwn orders, and the average qty per order
                                          vAvgInterval = (@TD(vMaxSODate) - @TD(vMinSODate))/vTot7
                                          vPredictedDate = @TD(vMaxSODate) + vAvgInterval
                                          vAvgQty = vQtyTotal/vTot7
                                       If vAvgQty < 1
                                         {
                                         vAvgQty = 1
                                         }
     
                                       If (vPredictedDate > (vDate - 7)) and (vPredictedDate < (vDate + 7))
                                         {
                                         //writeln("Reorder Date for " + vAvgQty + " " + vItemUoM + " of ItemNum #" + vItemNum + " / " + vItemDesc + @NewLine() + " should be " + vPredictedDate + @NewLine() + @NewLine())
                                         vNeedSoonAddTo = ""
                                         vNeedSoonAddTo = "Reorder Date for " + vAvgQty + " " + vItemUoM + " of ItemNum #" + vItemNum + " / " + vItemDesc + @NewLine() + " should be " + vPredictedDate + @NewLine() + @NewLine()
                                         vNeedSoonFYI = vNeedSoonFYI + vNeedSoonAddTo + @NewLine()
                                         }

                                     } // ends if vQtyTotal > 1

                                    } // ends if vTot7 > 1

                                  } // ends if vRS7 > -1
                                       XResultSetClose(vRS7)

                             } // ends If vUniqueItemNum <> vItemNum



                           Next  // vLoop6 to vTot6

writeln(vItemNumList)

                       If vNeedSoonFYI <> ""
                         {
                             vNeedSoonHeader = "Need Soon Report from MyCompany " + @NewLine() + "Hi, " + @NewLine() + vClientID + " / " + vClientName + @NewLine() + @NewLine() + "MyCompany's wise, all-seeing White Owl Software suggests that " + @NewLine() + "you are due to order the following products - please contact us " + @NewLine() + "via fax xxx-xxx-xxxx, phone xxx-xxx-xxxx, or email MySalesDept@mycompany.com." + @NewLine() + "We appreciate you !!!" + @NewLine() + @NewLine() + @NewLine()
                             vNeedSoonFYI = vNeedSoonHeader + vNeedSoonFYI
                             Writeln(vNeedSoonFYI)
                             vQuestion1 = @PromptforUserInput("If you wish to FAX this NeedSoon FYI to this client, Enter 'Y' and press <OK>; otherwise, just leave blank and press <OK> or <cancel> ", "")
                             If vQuestion1 = "Y"
                               {
                                   Cls()
                                   CloseSlate()
                                   OpenSlate()
                                   Writeln("Fax: " + vClientFax + @NewLine() + @NewLine() + vNeedSoonFYI)
                               }
                             If vClientEmail = ""
                               {
                                   @MsgBox("Please get correct email address for this client,","and enter it in either the main ClientEmail1 field, or","in the correct ShipToEmail1 field.")
        
                               }

                                 Else If vClientEmail <> ""
                                       {
                                         vQuestion1 = ""
                                         vQuestion1 = @PromptforUserInput("If you wish to EMAIL this NeedSoon FYI to this client, Enter 'Y' and press <OK>; otherwise, just leave blank and press <OK> or <cancel> ", "")
                                         If vQuestion1 = "Y"
                                            {
                                               vEmail = @sendmail("smtp2go.com","You are due for the following products from MyCompany","myserver@mycompany.com", vClientEmail,"","",vNeedSoonFYI,"","")
                                               writeln(vEmail)
                                            }
                                       }

                         } // ends If vNeedSoonFYI <> ""

       
                                                     
                       //vRpt = @XResultSetPrintReport("Client-ItemNumQtySum2-Ascend", vRS5, REPORT_MODE_HTML_PREVIEW)

                          } // ends If vRS6 > -1

                     XResultSetClose(vRS6)
                 vFlag = 1

               } // ends If vClientID <> ""

  } // ends program
  

Larry
Back to top
IP Logged
 
Hammer
YaBB Administrator
Lanticans
*****
Offline


Fire bad. Tree pretty.

Posts: 3436
Location: Ohio
Joined: Nov 22nd, 2002
Re: RunExitProgrammingOnCloseForm(state) ?
Reply #12 - Jan 31st, 2012 at 8:03pm
Print Post Print Post  
1. You are repeatedly looping from 1 to 1. Consider yourself backhanded.

2. I think we can cut your 90 @XResultSetSearch calls down to one. Stay tuned.
  

- Hammer
The plural of anecdote is not data.
Back to top
IP Logged
 
Hammer
YaBB Administrator
Lanticans
*****
Offline


Fire bad. Tree pretty.

Posts: 3436
Location: Ohio
Joined: Nov 22nd, 2002
Re: RunExitProgrammingOnCloseForm(state) ?
Reply #13 - Jan 31st, 2012 at 9:03pm
Print Post Print Post  
Give a technique like this a try. It does your calculations. It runs against Customers and produces calculations for 31 individual States in less than one second.

Code
Select All
var vRS as Int
var vState as String
var vOldState as String
var vLoop as Int
var vCnt as Int
var vStateList as String
var vTotal as Double
var vAmount as Double
var vRecs as Int
var vMinDate as Date
var vMaxDate as Date
var vDate as Date

SUBROUTINE CalcAndEmit()
// this calc will determine the average # of days
// btwn orders, and the average qty per order

var vAvgInterval as Int
var vPredictedDate as Date
var vAvgQty as Int

	vAvgInterval = 0
	vAvgQty = 0
	vPredictedDate = vDate
	If vRecs > 0
	{
		vAvgInterval = @DefinedRound((@ToNumber(vMaxDate) - @ToNumber(vMinDate)) / vRecs, 0, 1)
		vPredictedDate = vMaxDate + vAvgInterval
		vAvgQty = @DefinedRound(vTotal / vRecs, 0, 1)
		If vAvgQty < 1
		{
			vAvgQty = 1
		}
//		If (vPredictedDate > (vDate - 7)) and (vPredictedDate < (vDate + 7))
		If (1 = 1)	// Forcing output since none of the sample dates are current
		{
			WriteLn("Reorder Date for " + vAvgQty + " of ItemNum #" + vOldState + @NewLine() + " should be " + vPredictedDate + @NewLine() + @NewLine())
			// vNeedSoonAddTo = ""
			// vNeedSoonAddTo = "Reorder Date for " + vAvgQty + " " + vItemUoM + " of ItemNum #" + vItemNum + " / " + vItemDesc + @NewLine() + " should be " + vPredictedDate + @NewLine() + @NewLine()
			// vNeedSoonFYI = vNeedSoonFYI + vNeedSoonAddTo + @NewLine()
		}
	}

END SUBROUTINE

	vDate = @Date

	// Get list of unique States as a StringArray
	// We don't need them to be in order, so we're
	// not gonna waste time on sorting
	vStateList = ""
	vRS = @XResultSetSearch(@FN, "Customers", 0, 2, "")
	If vRS > -1
	{
		vCnt = @XResultSetTotal(vRS)
		For vLoop = 1 To vCnt
			XResultSetCurrentPosition(vRS, vLoop)
			vState = @XResultSetValue(vRS, "State")
			vStateList = vStateList + vState + ";"
		Next
		XResultSetClose(vRS)
	}
	vStateList = @Repllas(vStateList, ";", "")
	vStateList = @UniqueStringArray(vStateList)

	// Get calc info for all the States at once instead of doing
	// one @XResultSetSearch for each State by using the list assembled
	// above as search criteria.
	vRS = @XResultSetSearch(@FN, "Customers", 0, 2, "!State=" + vStateList, "!Company=/=")
 	If vRS > -1
	{
		// Now the sort order matters
		XResultSetSort(vRS, "State:-1;Date_Entered:-1")
		vCnt = @XResultSetTotal(vRS)
		vOldState = "This will never appear as a State"
		vTotal = 0
		vRecs = 0

		// Calc each State
		For vLoop = 1 To vCnt
			XResultSetCurrentPosition(vRS, vLoop)
			vState = @XResultSetValue(vRS, "State")
			If vOldState <> vState
			{
				If vOldState <> "This will never appear as a State"
				{
					CalcAndEmit()	// Emit result
				}
				// Reset for next State
				vOldState = vState
				vMinDate = @ToDate(@XResultSetValue(vRS, "Date_Entered"))
				vMaxDate = vMinDate
				vTotal = 0
				vRecs = 0
			}
			vMaxDate = @XResultSetValue(vRS, "Date_Entered")
			vAmount = @XResultSetValue(vRS, "Numerical")
			vTotal = vTotal + vAmount
			vRecs = vRecs + 1
		Next
		XResultSetClose(vRS)
		CalcAndEmit()	// Emit last State
	}
 

  

- Hammer
The plural of anecdote is not data.
Back to top
IP Logged
 
lksseven
Full Member
***
Offline



Posts: 416
Location: Southwest
Joined: Jan 26th, 2009
Re: RunExitProgrammingOnCloseForm(state) ?
Reply #14 - Jan 31st, 2012 at 9:32pm
Print Post Print Post  
loop from 1 to 1... I couldn't figure out a better way to find the oldest date in the result set, and the newest date in the result set, other than to sort in ascend order and then just loop once to grab the first record only(oldest/smallest date), then resort in descend order and again loop once to grab the first record only (newest/biggest date).

What's worrisome to me is that the admonishment that I've been backhanded made me laugh instead of grimace (kind of an Animal House fraternity paddle induction ceremony "thank you, sir, may I have another" thing).

Regarding taking 90 XRset searches down to one? If you can do that I'll start my own 'Hammer' religion.
  

Larry
Back to top
IP Logged
 
Page Index Toggle Pages: [1] 2 
Send Topic Send Topic Print Print